summaryrefslogtreecommitdiff
path: root/qa/qa/resource
diff options
context:
space:
mode:
Diffstat (limited to 'qa/qa/resource')
-rw-r--r--qa/qa/resource/api_fabricator.rb2
-rw-r--r--qa/qa/resource/base.rb6
-rw-r--r--qa/qa/resource/group_base.rb6
-rw-r--r--qa/qa/resource/group_runner.rb29
-rw-r--r--qa/qa/resource/issue.rb11
-rw-r--r--qa/qa/resource/merge_request.rb43
-rw-r--r--qa/qa/resource/project.rb40
-rw-r--r--qa/qa/resource/project_runner.rb29
-rw-r--r--qa/qa/resource/reusable.rb163
-rw-r--r--qa/qa/resource/reusable_collection.rb60
-rw-r--r--qa/qa/resource/reusable_group.rb43
-rw-r--r--qa/qa/resource/reusable_project.rb51
-rw-r--r--qa/qa/resource/runner.rb148
-rw-r--r--qa/qa/resource/runner_base.rb129
14 files changed, 253 insertions, 507 deletions
diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb
index d82109c1d54..44520b04e9a 100644
--- a/qa/qa/resource/api_fabricator.rb
+++ b/qa/qa/resource/api_fabricator.rb
@@ -104,7 +104,7 @@ module QA
raise ResourceNotFoundError, "Resource at #{request.mask_url} could not be found (#{response.code}): `#{response}`.\n#{QA::Support::Loglinking.failure_metadata(response.headers[:x_request_id])}"
end
- @api_fabrication_http_method = :get # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ @api_fabrication_http_method ||= :get # rubocop:disable Gitlab/ModuleWithInstanceVariables
response
end
diff --git a/qa/qa/resource/base.rb b/qa/qa/resource/base.rb
index 00c002cae9c..2abe1904c92 100644
--- a/qa/qa/resource/base.rb
+++ b/qa/qa/resource/base.rb
@@ -96,11 +96,7 @@ module QA
result = yield.tap do
fabrication_time = Time.now - start
fabrication_http_method = if resource.api_fabrication_http_method == :get || resource.retrieved_from_cache
- if include?(Reusable)
- "Retrieved for reuse"
- else
- "Retrieved"
- end
+ "Retrieved"
else
"Built"
end
diff --git a/qa/qa/resource/group_base.rb b/qa/qa/resource/group_base.rb
index c5b1a4ecea0..5e2a567119d 100644
--- a/qa/qa/resource/group_base.rb
+++ b/qa/qa/resource/group_base.rb
@@ -16,7 +16,8 @@ module QA
:name,
:full_path,
# Add visibility to enable create private group
- :visibility
+ :visibility,
+ :shared_with_groups
# Get group projects
#
@@ -140,7 +141,8 @@ module QA
:require_two_factor_authentication,
:share_with_group_lock,
:subgroup_creation_level,
- :two_factor_grace_perion
+ :shared_with_groups,
+ :two_factor_grace_period
# TODO: Add back visibility comparison once https://gitlab.com/gitlab-org/gitlab/-/issues/331252 is fixed
# :visibility
)
diff --git a/qa/qa/resource/group_runner.rb b/qa/qa/resource/group_runner.rb
new file mode 100644
index 00000000000..d7fa26a1d53
--- /dev/null
+++ b/qa/qa/resource/group_runner.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module QA
+ module Resource
+ class GroupRunner < RunnerBase
+ attribute :group do
+ Resource::Group.fabricate_via_api! do |resource|
+ resource.name = "group-with-ci-cd-#{SecureRandom.hex(8)}"
+ resource.description = 'Group with CI/CD Pipelines'
+ end
+ end
+
+ attribute :token do
+ group.runners_token
+ rescue NoValueError
+ group.reload!.runners_token
+ end
+
+ private
+
+ def runner(**kwargs)
+ fail_msg = "Wait for runner '#{name}' to register in group '#{group.name}'"
+ Support::Retrier.retry_until(max_duration: 60, sleep_interval: 1, message: fail_msg) do
+ auto_paginated_response(request_url("/runners", **kwargs)).find { |runner| runner[:description] == name }
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/resource/issue.rb b/qa/qa/resource/issue.rb
index 2e18e1d0323..15c2c25757f 100644
--- a/qa/qa/resource/issue.rb
+++ b/qa/qa/resource/issue.rb
@@ -65,6 +65,10 @@ module QA
end
end
+ def api_related_mrs_path
+ "#{api_get_path}/related_merge_requests"
+ end
+
def set_issue_assignees(assignee_ids:)
put_body = { assignee_ids: assignee_ids }
response = put Runtime::API::Request.new(api_client, api_put_path).url, put_body
@@ -79,6 +83,13 @@ module QA
QA::Runtime::Logger.debug("Successfully updated issue assignees to #{assignee_ids}")
end
+ # Related merge requests
+ #
+ # @return [Array<Hash>]
+ def related_merge_requests
+ parse_body(api_get_from(api_related_mrs_path))
+ end
+
protected
# Return subset of fields for comparing issues
diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb
index d1d99393ca2..50ef9538fb0 100644
--- a/qa/qa/resource/merge_request.rb
+++ b/qa/qa/resource/merge_request.rb
@@ -6,27 +6,27 @@ module QA
include ApprovalConfiguration
attr_accessor :approval_rules,
- :source_branch,
- :target_new_branch,
- :update_existing_file,
- :assignee,
- :milestone,
- :labels,
- :file_name,
- :file_content,
- :reviewer_ids
+ :source_branch,
+ :target_new_branch,
+ :update_existing_file,
+ :assignee,
+ :milestone,
+ :labels,
+ :file_name,
+ :file_content,
+ :reviewer_ids
attr_writer :no_preparation,
- :wait_for_merge,
- :template
+ :wait_for_merge,
+ :template
attributes :iid,
- :title,
- :description,
- :merge_when_pipeline_succeeds,
- :merge_status,
- :state,
- :reviewers
+ :title,
+ :description,
+ :merge_when_pipeline_succeeds,
+ :merge_status,
+ :state,
+ :reviewers
attribute :project do
Project.fabricate_via_api! do |resource|
@@ -143,6 +143,13 @@ module QA
}
end
+ # Get merge request reviews
+ #
+ # @return [Array<Hash>]
+ def reviews
+ parse_body(api_get_from(api_reviewers_path))
+ end
+
def merge_via_api!
Support::Waiter.wait_until(sleep_interval: 1) do
QA::Runtime::Logger.debug("Waiting until merge request with id '#{iid}' can be merged")
@@ -179,7 +186,7 @@ module QA
def fabricate_large_merge_request
@project = Resource::ImportProject.fabricate_via_browser_ui!
# Setting the name here, since otherwise some tests will look for an existing file in
- # the proejct without ever knowing what is in it.
+ # the project without ever knowing what is in it.
@file_name = "added_file-00000000.txt"
@source_branch = "large_merge_request"
visit("#{project.web_url}/-/merge_requests/1")
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index 3cbc002fa86..3f42c6b649e 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -10,25 +10,25 @@ module QA
include ApprovalConfiguration
attr_accessor :initialize_with_readme,
- :auto_devops_enabled,
- :github_personal_access_token,
- :github_repository_path,
- :gitlab_repository_path,
- :personal_namespace
+ :auto_devops_enabled,
+ :github_personal_access_token,
+ :github_repository_path,
+ :gitlab_repository_path,
+ :personal_namespace
attr_reader :repository_storage
attributes :id,
- :name,
- :path,
- :add_name_uuid,
- :runners_token,
- :visibility,
- :template_name,
- :import,
- :import_status,
- :import_error,
- :description
+ :name,
+ :path,
+ :add_name_uuid,
+ :runners_token,
+ :visibility,
+ :template_name,
+ :import,
+ :import_status,
+ :import_error,
+ :description
attribute :group do
Group.fabricate! do |group|
@@ -43,7 +43,7 @@ module QA
alias_method :full_path, :path_with_namespace
def sandbox_path
- return '' if personal_namespace || !group.respond_to?('sandbox')
+ return '' if personal_namespace || !group.respond_to?(:sandbox)
"#{group.sandbox.path}/"
end
@@ -450,6 +450,14 @@ module QA
parse_body(response)
end
+ # Fetch project specific runners
+ #
+ # @param [Hash] **kwargs optional query arguments, see: https://docs.gitlab.com/ee/api/runners.html#list-projects-runners
+ # @return [Array]
+ def runners(**kwargs)
+ auto_paginated_response(request_url(api_runners_path, **kwargs))
+ end
+
# Uses the API to wait until a pull mirroring update is successful (pull mirroring is treated as an import)
def wait_for_pull_mirroring
mirror_succeeded = Support::Retrier.retry_until(
diff --git a/qa/qa/resource/project_runner.rb b/qa/qa/resource/project_runner.rb
new file mode 100644
index 00000000000..173b17550c7
--- /dev/null
+++ b/qa/qa/resource/project_runner.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module QA
+ module Resource
+ class ProjectRunner < RunnerBase
+ attribute :project do
+ Project.fabricate_via_api! do |resource|
+ resource.name = 'project-with-ci-cd'
+ resource.description = 'Project with CI/CD Pipelines'
+ end
+ end
+
+ attribute :token do
+ project.runners_token
+ rescue NoValueError
+ project.reload!.runners_token
+ end
+
+ private
+
+ def runner(**kwargs)
+ fail_msg = "Wait for runner '#{name}' to register in project '#{project.name}'"
+ Support::Retrier.retry_until(max_duration: 60, sleep_interval: 1, message: fail_msg) do
+ project.runners(**kwargs).find { |runner| runner[:description] == name }
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/resource/reusable.rb b/qa/qa/resource/reusable.rb
deleted file mode 100644
index 536f70b50b4..00000000000
--- a/qa/qa/resource/reusable.rb
+++ /dev/null
@@ -1,163 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Resource
- #
- # This module includes methods that allow resource classes to be reused safely. It should be prepended to a new
- # reusable version of an existing resource class. See Resource::Project and ReusableResource::Project for an example.
- # Reusable resource classes must also be registered with a resource collection that will manage cleanup.
- #
- # @example Register a resource class with a collection
- # QA::Resource::ReusableCollection.register_resource_classes do |collection|
- # QA::Resource::ReusableProject.register(collection)
- # end
- module Reusable
- attr_accessor :reuse,
- :reuse_as
-
- ResourceReuseError = Class.new(RuntimeError)
-
- def self.prepended(base)
- base.extend(ClassMethods)
- end
-
- # Gets an existing resource if it exists and the specified attributes of the resource are valid.
- # Creates a new instance of the resource if it does not exist.
- #
- # @return [String] The URL of the resource.
- def fabricate_via_api!
- validate_reuse_preconditions
-
- resource_web_url(api_get)
- rescue Errors::ResourceNotFoundError
- super
- ensure
- self.class.resources[reuse_as] ||= {
- tests: Set.new,
- resource: self
- }
-
- self.class.resources[reuse_as][:attributes] ||= all_attributes.index_with do |attribute_name|
- instance_variable_get("@#{attribute_name}")
- end
- self.class.resources[reuse_as][:tests] << Runtime::Example.location
- end
-
- # Overrides remove_via_api! to log a debug message stating that removal will happen after the suite completes.
- #
- # @return [nil]
- def remove_via_api!
- QA::Runtime::Logger.debug("#{self.class.name} - deferring removal until after suite")
- end
-
- # Object comparison
- #
- # @param [QA::Resource::Base] other
- # @return [Boolean]
- def ==(other)
- self.class <= other.class && comparable == other.comparable
- end
-
- # Confirms that reuse of the resource did not change it in a way that breaks later reuse.
- # For example, this should fail if a reusable resource should have a specific name, but the name has been changed.
- def validate_reuse
- QA::Runtime::Logger.debug(["Validating a #{self.class.name} that was reused as #{reuse_as}", identifier].compact.join(' '))
-
- fresh_resource = reference_resource
- diff = reuse_validation_diff(fresh_resource)
-
- if diff.present?
- raise ResourceReuseError, <<~ERROR
- The reused #{self.class.name} resource does not have the attributes expected.
- The following change was found: #{diff}"
- The resource's web_url is #{web_url}.
- It was used in these tests: #{self.class.resources[reuse_as][:tests].to_a.join(', ')}
- ERROR
- end
-
- ensure
- fresh_resource.remove_via_api!
- end
-
- private
-
- # Creates a new resource that can be compared to a reused resource, using the post body of the original.
- # Must be implemented by classes that include this module.
- def reference_resource
- return super if defined?(super)
-
- raise NotImplementedError
- end
-
- # Confirms that the resource attributes specified in its fabricate_via_api! block will allow it to be reused.
- #
- # @return [nil] returns nil unless an error is raised
- def validate_reuse_preconditions
- return unless self.class.resources.key?(reuse_as)
-
- attributes = unique_identifiers.each_with_object({ proposed: {}, existing: {} }) do |id, attrs|
- proposed = public_send(id)
- existing = self.class.resources[reuse_as][:resource].public_send(id)
-
- next if proposed == existing
-
- attrs[:proposed][id] = proposed
- attrs[:existing][id] = existing
- end
-
- unless attributes[:proposed].empty? && attributes[:existing].empty?
- raise ResourceReuseError, "Reusable resources must use the same unique identifier(s). " \
- "The #{self.class.name} to be reused as :#{reuse_as} has the identifier(s) #{attributes[:proposed]} " \
- "but it should have #{attributes[:existing]}"
- end
- end
-
- # Compares the attributes of the current reused resource with a reference instance.
- #
- # @return [Hash] any differences between the resources.
- def reuse_validation_diff(other)
- original, reference = prepare_reuse_validation_diff(other)
-
- return if original == reference
-
- diff_values = original.to_a - reference.to_a
- diff_values.to_h
- end
-
- # Compares the current reusable resource to a reference instance, ignoring identifying unique attributes that
- # had to be changed.
- #
- # @return [Hash, Hash] the current and reference resource attributes, respectively.
- def prepare_reuse_validation_diff(other)
- original = self.reload!.comparable
- reference = other.reload!.comparable
- unique_identifiers.each { |id| reference[id] = original[id] }
- [original, reference]
- end
-
- # The attributes of the resource that should be the same whenever a test wants to reuse a resource. Must be
- # implemented by classes that include this module.
- #
- # @return [Array<Symbol>] the attribute names.
- def unique_identifiers
- return super if defined?(super)
-
- raise NotImplementedError
- end
-
- module ClassMethods
- # Includes the resources created/reused by this class in the specified collection
- def register(collection)
- collection[self.name] = resources
- end
-
- # The resources created/reused by this resource class.
- #
- # @return [Hash<Symbol, Hash>] the resources created/reused by this resource class.
- def resources
- @resources ||= {}
- end
- end
- end
- end
-end
diff --git a/qa/qa/resource/reusable_collection.rb b/qa/qa/resource/reusable_collection.rb
deleted file mode 100644
index 99a55800d1c..00000000000
--- a/qa/qa/resource/reusable_collection.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-# frozen_string_literal: true
-
-require 'singleton'
-
-module QA
- module Resource
- #
- # This singleton class collects all reusable resources used by tests and allows operations to be performed on them
- # all. For example, verifying their state after tests have run and might have changed them.
- #
- class ReusableCollection
- include Singleton
-
- attr_accessor :resource_classes
-
- def initialize
- @resource_classes = {}
- end
-
- # Yields each resource in the collection.
- #
- # @yieldparam [Symbol] reuse_as the name that identifies the resource instance.
- # @yieldparam [QA::Resource] reuse_instance the resource.
- def each_resource
- resource_classes.each_value do |reuse_instances|
- reuse_instances.each do |reuse_as, reuse_instance|
- yield reuse_as, reuse_instance[:resource]
- end
- end
- end
-
- class << self
- # Removes all created resources that are included in the collection.
- def remove_all_via_api!
- instance.each_resource do |reuse_as, resource|
- next QA::Runtime::Logger.debug("#{resource.class.name} reused as :#{reuse_as} has already been removed.") unless resource.exists?
-
- if resource.respond_to?(:marked_for_deletion?) && resource.marked_for_deletion?
- next QA::Runtime::Logger.debug("#{resource.class.name} reused as :#{reuse_as} is already scheduled to be removed.")
- end
-
- resource.method(:remove_via_api!).super_method.call
- end
- end
-
- # Validates the reuse of each resource as defined by the resource class of each resource in the collection.
- def validate_resource_reuse
- instance.each_resource { |_, resource| resource.validate_reuse }
- end
-
- # Yields the collection of resources to allow resource classes to register themselves with the collection.
- #
- # @yieldparam [Hash] resource_classes the resource classes in the collection.
- def register_resource_classes
- yield instance.resource_classes
- end
- end
- end
- end
-end
diff --git a/qa/qa/resource/reusable_group.rb b/qa/qa/resource/reusable_group.rb
deleted file mode 100644
index 05ff38249f6..00000000000
--- a/qa/qa/resource/reusable_group.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Resource
- class ReusableGroup < Group
- prepend Reusable
-
- def initialize
- super
-
- @name = @path = QA::Runtime::Env.reusable_group_path
- @description = "QA reusable group"
- @reuse_as = :default_group
- end
-
- private
-
- # Creates a new group that can be compared to a reused group, using the attributes of the original. Attributes that
- # must be unique (path and name) are replaced with new unique values.
- #
- # @return [QA::Resource] a new instance of Resource::ReusableGroup that should be a copy of the original resource
- def reference_resource
- attributes = self.class.resources[reuse_as][:attributes]
- name = "ref#{SecureRandom.hex(8)}_#{attributes.delete(:path)}"[0...MAX_NAME_LENGTH]
-
- Group.fabricate_via_api! do |resource|
- self.class.resources[reuse_as][:attributes].each do |attribute_name, attribute_value|
- resource.instance_variable_set("@#{attribute_name}", attribute_value) if attribute_value
- end
- resource.path = name
- resource.name = name
- end
- end
-
- # The attributes of the resource that should be the same whenever a test wants to reuse a group.
- #
- # @return [Array<Symbol>] the attribute names.
- def unique_identifiers
- [:name, :path]
- end
- end
- end
-end
diff --git a/qa/qa/resource/reusable_project.rb b/qa/qa/resource/reusable_project.rb
deleted file mode 100644
index 8a12c25b6f0..00000000000
--- a/qa/qa/resource/reusable_project.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Resource
- class ReusableProject < Project
- prepend Reusable
-
- attribute :group do
- ReusableGroup.fabricate_via_api! do |resource|
- resource.api_client = api_client
- end
- end
-
- def initialize
- super
-
- @add_name_uuid = false
- @name = @path = QA::Runtime::Env.reusable_project_path
- @reuse_as = :default_project
- @initialize_with_readme = true
- end
-
- private
-
- # Creates a new project that can be compared to a reused project, using the attributes of the original. Attributes
- # that must be unique (path and name) are replaced with new unique values.
- #
- # @return [QA::Resource] a new instance of Resource::ReusableProject that should be a copy of the original resource
- def reference_resource
- attributes = self.class.resources[reuse_as][:attributes]
- name = "reference_resource_#{SecureRandom.hex(8)}_for_#{attributes.delete(:name)}"
-
- Project.fabricate_via_api! do |project|
- self.class.resources[reuse_as][:attributes].each do |attribute_name, attribute_value|
- project.instance_variable_set("@#{attribute_name}", attribute_value) if attribute_value
- end
- project.name = name
- project.path = name
- project.path_with_namespace = "#{project.group.full_path}/#{project.name}"
- end
- end
-
- # The attributes of the resource that should be the same whenever a test wants to reuse a project.
- #
- # @return [Array<Symbol>] the attribute names.
- def unique_identifiers
- [:name, :path]
- end
- end
- end
-end
diff --git a/qa/qa/resource/runner.rb b/qa/qa/resource/runner.rb
deleted file mode 100644
index 3c74d8de21a..00000000000
--- a/qa/qa/resource/runner.rb
+++ /dev/null
@@ -1,148 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- module Resource
- class Runner < Base
- attributes :id,
- :active,
- :paused,
- :runner_type,
- :online,
- :status,
- :ip_address,
- :token,
- :tags,
- :config,
- :run_untagged,
- :name, # This attribute == runner[:description]
- :image,
- :executor,
- :executor_image
-
- attribute :project do
- Project.fabricate_via_api! do |resource|
- resource.name = 'project-with-ci-cd'
- resource.description = 'Project with CI/CD Pipelines'
- end
- end
-
- def initialize
- @tags = nil
- @config = nil
- @run_untagged = nil
- @name = "qa-runner-#{SecureRandom.hex(4)}"
- @image = 'registry.gitlab.com/gitlab-org/gitlab-runner:alpine'
- @executor = :shell
- @executor_image = 'registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.7'
- end
-
- # Initially we only support fabricate
- # via API
- def fabricate!
- fabricate_via_api!
- end
-
- # Start container and register runner
- # Fetch via API and populate attributes
- #
- def fabricate_via_api!
- start_container_and_register
- populate_runner_attributes
- end
-
- def remove_via_api!
- super
- ensure
- @docker_container.remove!
- end
-
- def reload!
- populate_runner_attributes
- end
-
- def api_delete_path
- "/runners/#{id}"
- end
-
- def api_get_path
- "/runners"
- end
-
- def api_post_path
- "/runners"
- end
-
- def api_post_body; end
-
- def not_found_by_tags?
- url = "#{api_get_path}?tag_list=#{tags.compact.join(',')}"
- auto_paginated_response(request_url(url)).empty?
- end
-
- def runners_list
- runners_list = nil
- url = tags ? "#{api_get_path}?tag_list=#{tags.compact.join(',')}" : api_get_path
- Runtime::Logger.info('Looking for list of runners via API...')
- Support::Retrier.retry_until(max_duration: 60, sleep_interval: 1) do
- runners_list = auto_paginated_response(request_url(url))
- runners_list.present?
- end
-
- runners_list
- end
-
- def wait_until_online
- Runtime::Logger.info('Waiting for runner to come online...')
- Support::Retrier.retry_until(max_duration: 60, sleep_interval: 1) do
- this_runner[:status] == 'online'
- end
- end
-
- def restart
- Runtime::Logger.info("Restarting runner container #{name}...")
- @docker_container.restart
- wait_until_online
- end
-
- private
-
- def start_container_and_register
- @docker_container = Service::DockerRun::GitlabRunner.new(name).tap do |runner|
- Support::Retrier.retry_on_exception(sleep_interval: 5) do
- runner.pull
- end
-
- runner.token = @token ||= project.runners_token
- runner.address = Runtime::Scenario.gitlab_address
- runner.tags = tags if tags
- runner.image = image
- runner.config = config if config
- runner.executor = executor
- runner.executor_image = executor_image if executor == :docker
- runner.run_untagged = run_untagged if run_untagged
- runner.register!
- end
- end
-
- def this_runner
- runner = nil
- Support::Retrier.retry_until(max_duration: 60, sleep_interval: 1) do
- runner = runners_list.find { |runner| runner[:description] == name }
- !runner.nil?
- end
- runner
- end
-
- def populate_runner_attributes
- runner = this_runner
- @id = runner[:id]
- @active = runner[:active]
- @paused = runner[:paused]
- @runner_type = runner[:typed]
- @online = runner[:online]
- @status = runner[:status]
- @ip_address = runner[:ip_address]
- end
- end
- end
-end
diff --git a/qa/qa/resource/runner_base.rb b/qa/qa/resource/runner_base.rb
new file mode 100644
index 00000000000..7580aa108c9
--- /dev/null
+++ b/qa/qa/resource/runner_base.rb
@@ -0,0 +1,129 @@
+# frozen_string_literal: true
+
+module QA
+ module Resource
+ class RunnerBase < Base
+ attr_accessor :run_untagged,
+ :image,
+ :executor,
+ :executor_image,
+ :tags,
+ :config
+
+ attributes :id,
+ :active,
+ :paused,
+ :runner_type,
+ :online,
+ :status,
+ :ip_address,
+ :description,
+ :name,
+ :is_shared,
+ :contacted_at,
+ :platform,
+ :architecture,
+ :projects,
+ :revision,
+ :tag_list,
+ :version,
+ :access_level,
+ :maximum_timeout
+
+ def initialize
+ @tags = nil
+ @config = nil
+ @run_untagged = nil
+ @name = "qa-runner-#{SecureRandom.hex(4)}"
+ @image = 'registry.gitlab.com/gitlab-org/gitlab-runner:alpine'
+ @executor = :shell
+ @executor_image = 'registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.7'
+ end
+
+ # Initially we only support fabricate via API
+ def fabricate!
+ fabricate_via_api!
+ end
+
+ # Start container and register runner
+ # Fetch via API and populate attributes
+ #
+ def fabricate_via_api!
+ api_get
+ rescue NoValueError
+ # Start container on initial fabrication and populate all attributes once id is known
+ # see: https://docs.gitlab.com/ee/api/runners.html#get-runners-details
+ start_container_and_register
+ api_get
+ end
+
+ def remove_via_api!
+ super
+ ensure
+ @docker_container.remove!
+ @docker_container = nil
+ end
+
+ def api_get_path
+ "/runners/#{id}"
+ end
+
+ def api_post_path
+ "/runners"
+ end
+
+ def api_delete_path
+ api_get_path
+ end
+
+ def api_post_body; end
+
+ def wait_until_online
+ Runtime::Logger.info('Waiting for runner to come online...')
+ Support::Retrier.retry_until(max_duration: 60, sleep_interval: 1) do
+ reload! && status == 'online'
+ end
+ end
+
+ def restart
+ Runtime::Logger.info("Restarting runner container #{name}...")
+ @docker_container.restart
+ wait_until_online
+ end
+
+ private
+
+ def start_container_and_register
+ @docker_container ||= Service::DockerRun::GitlabRunner.new(name).tap do |runner|
+ Support::Retrier.retry_on_exception(sleep_interval: 5) do
+ runner.pull
+ end
+
+ runner.token = token
+ runner.address = Runtime::Scenario.gitlab_address
+ runner.tags = tags if tags
+ runner.image = image
+ runner.config = config if config
+ runner.executor = executor
+ runner.executor_image = executor_image if executor == :docker
+ runner.run_untagged = run_untagged if run_untagged
+ runner.register!
+ end
+ populate_initial_id
+ rescue StandardError => e
+ @docker_container&.remove!
+ raise(e)
+ end
+
+ def populate_initial_id
+ tag_list = tags ? { tag_list: tags.compact.join(',') } : {}
+ runner = runner(**tag_list)
+ @id = runner[:id]
+ end
+
+ def runner(**kwargs)
+ raise("Not implemented!")
+ end
+ end
+ end
+end