diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-07-01 09:10:05 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-07-01 09:10:05 +0300 |
commit | a6dce21d917a0a359b3521ec3cef02ab3e6199cf (patch) | |
tree | a58db0dfc50d8c85fdc4edb455037e154edc1cd4 | |
parent | d9a9116e0e78fb7f2690e88878a4b6384d80f763 (diff) |
Add latest changes from gitlab-org/gitlab@master
-rw-r--r-- | doc/api/users.md | 6 | ||||
-rw-r--r-- | qa/qa/page/base.rb | 20 | ||||
-rw-r--r-- | qa/qa/page/component/design_management.rb | 11 | ||||
-rw-r--r-- | qa/qa/page/component/invite_members_modal.rb | 12 | ||||
-rw-r--r-- | qa/qa/resource/design.rb | 52 | ||||
-rw-r--r-- | qa/qa/runtime/env.rb | 19 | ||||
-rw-r--r-- | qa/qa/service/docker_run/base.rb | 23 | ||||
-rw-r--r-- | qa/qa/service/docker_run/mixins/third_party_docker.rb | 59 | ||||
-rw-r--r-- | qa/qa/service/shellout.rb | 2 | ||||
-rw-r--r-- | qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb | 62 | ||||
-rw-r--r-- | qa/qa/specs/features/browser_ui/1_manage/project/invite_group_to_project_spec.rb | 16 | ||||
-rw-r--r-- | qa/qa/specs/features/browser_ui/3_create/design_management/archive_design_content_spec.rb | 2 | ||||
-rw-r--r-- | qa/qa/specs/features/browser_ui/3_create/design_management/modify_design_content_spec.rb | 7 | ||||
-rw-r--r-- | qa/qa/support/waiter.rb | 6 | ||||
-rw-r--r-- | qa/spec/service/docker_run/base_spec.rb | 31 | ||||
-rw-r--r-- | qa/spec/service/docker_run/mixins/third_party_docker_spec.rb | 68 | ||||
-rw-r--r-- | qa/spec/service/shellout_spec.rb | 6 |
17 files changed, 343 insertions, 59 deletions
diff --git a/doc/api/users.md b/doc/api/users.md index 4c6f1f4ab3a..75561f8e33d 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -105,7 +105,7 @@ parameter `without_project_bots=true`. GET /users?without_project_bots=true ``` -### For administrators +### For administrators **(FREE SELF)** > The `namespace_id` field in the response was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82045) in GitLab 14.10. @@ -314,7 +314,7 @@ Parameters: } ``` -### For administrator +### For administrators **(FREE SELF)** > The `namespace_id` field in the response was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82045) in GitLab 14.10. @@ -623,7 +623,7 @@ GET /user Users on [GitLab Premium or higher](https://about.gitlab.com/pricing/) also see the `shared_runners_minutes_limit`, `extra_shared_runners_minutes_limit` parameters. -### For administrators +### For administrators **(FREE SELF)** > The `namespace_id` field in the response was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82045) in GitLab 14.10. diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index 775a5ead5f7..d7e0101ff2c 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -53,8 +53,20 @@ module QA wait_for_requests(skip_finished_loading_check: skip_finished_loading_check) end - def wait_until(max_duration: 60, sleep_interval: 0.1, reload: true, raise_on_failure: true, skip_finished_loading_check_on_refresh: false) - Support::Waiter.wait_until(max_duration: max_duration, sleep_interval: sleep_interval, raise_on_failure: raise_on_failure) do + def wait_until( + max_duration: 60, + sleep_interval: 0.1, + reload: true, + raise_on_failure: true, + skip_finished_loading_check_on_refresh: false, + message: nil + ) + Support::Waiter.wait_until( + max_duration: max_duration, + sleep_interval: sleep_interval, + raise_on_failure: raise_on_failure, + message: message + ) do yield || (reload && refresh(skip_finished_loading_check: skip_finished_loading_check_on_refresh) && false) end end @@ -80,7 +92,7 @@ module QA ) end - def scroll_to(selector, text: nil) + def scroll_to(selector, text: nil, &block) wait_for_requests page.execute_script <<~JS @@ -94,7 +106,7 @@ module QA } JS - page.within(selector) { yield } if block_given? + page.within(selector, &block) if block end # Returns true if successfully GETs the given URL diff --git a/qa/qa/page/component/design_management.rb b/qa/qa/page/component/design_management.rb index 73ba5713bda..90c86688882 100644 --- a/qa/qa/page/component/design_management.rb +++ b/qa/qa/page/component/design_management.rb @@ -54,8 +54,9 @@ module QA # We'll check for the annotation in a test, but here we'll at least # wait for the "Save comment" button to disappear saved = has_no_element?(:save_comment_button) + return if saved - raise RSpec::Expectations::ExpectationNotMetError, %q(There was a problem while adding the annotation) unless saved + raise RSpec::Expectations::ExpectationNotMetError, %q(There was a problem while adding the annotation) end def add_design(design_file_path) @@ -69,15 +70,11 @@ module QA filename = ::File.basename(design_file_path) - found = wait_until(reload: false, sleep_interval: 1) do + wait_until(reload: false, sleep_interval: 1, message: "Design upload") do image = find_element(:design_image, filename: filename) - has_element?(:design_file_name, text: filename) && - image["complete"] && - image["naturalWidth"].to_i > 0 + has_element?(:design_file_name, text: filename) && image["complete"] && image["naturalWidth"].to_i > 0 end - - raise ElementNotFound, %Q(Attempted to attach design "#{filename}" but it did not appear) unless found end def update_design(filename) diff --git a/qa/qa/page/component/invite_members_modal.rb b/qa/qa/page/component/invite_members_modal.rb index c9c8d25e1b3..5c39cfd3695 100644 --- a/qa/qa/page/component/invite_members_modal.rb +++ b/qa/qa/page/component/invite_members_modal.rb @@ -62,12 +62,14 @@ module QA Support::Waiter.wait_until { has_element?(:group_select_dropdown_item) } - # Helps stabilize race condition with concurrent group API calls while searching - # TODO: Replace with `fill_element :group_select_dropdown_search_field, group_name` when this bug is resolved: https://gitlab.com/gitlab-org/gitlab/-/issues/349379 - send_keys_to_element(:group_select_dropdown_search_field, group_name) + # Workaround for race condition with concurrent group API calls while searching + # Remove Retrier after https://gitlab.com/gitlab-org/gitlab/-/issues/349379 is resolved + Support::Retrier.retry_on_exception do + fill_element :group_select_dropdown_search_field, group_name + Support::WaitForRequests.wait_for_requests + click_button group_name + end - Support::WaitForRequests.wait_for_requests - click_button group_name set_access_level(access_level) end diff --git a/qa/qa/resource/design.rb b/qa/qa/resource/design.rb index 182985f2d9f..4a3214f3c0e 100644 --- a/qa/qa/resource/design.rb +++ b/qa/qa/resource/design.rb @@ -3,19 +3,20 @@ module QA module Resource class Design < Base - attr_reader :id - attr_accessor :filename - attribute :issue do Issue.fabricate_via_api! end + attributes :id, + :filename, + :full_path, + :image + def initialize @update = false @filename = 'banana_sample.gif' end - # TODO This will be replaced as soon as file uploads over GraphQL are implemented def fabricate! issue.visit! @@ -24,10 +25,51 @@ module QA end end + def api_get_path + '/graphql' + end + + alias_method :api_post_path, :api_get_path + + # Fetch design + # + # @return [Hash] + def api_get + process_api_response( + api_post_to( + api_get_path, + <<~GQL + query { + issue(id: "gid://gitlab/Issue/#{issue.id}") { + designCollection { + design(filename: "#{filename}") { + id + fullPath + image + filename + } + } + } + } + GQL + ) + ) + end + + # Graphql mutation for design creation + # + # @return [String] + def api_post_body + # TODO: design creation requires file upload via multipart/form-data request type with file passed in mutation + # which currently isn't supported by our api implementation + # https://gitlab.com/gitlab-org/gitlab/-/issues/366592 + raise NotImplementedError, "File uploads are not supported" + end + private def filepath - ::File.absolute_path(::File.join('qa', 'fixtures', 'designs', @filename)) + ::File.absolute_path(::File.join('qa', 'fixtures', 'designs', filename)) end end end diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index 1b27c822c31..f7aca2571c9 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -461,6 +461,25 @@ module QA enabled?(ENV['QA_SKIP_SMOKE_RELIABLE'], default: false) end + # ENV variables for authenticating against a private container registry + # These need to be set if using the + # Service::DockerRun::Mixins::ThirdPartyDocker module + def third_party_docker_registry + ENV['QA_THIRD_PARTY_DOCKER_REGISTRY'] + end + + def third_party_docker_repository + ENV['QA_THIRD_PARTY_DOCKER_REPOSITORY'] + end + + def third_party_docker_user + ENV['QA_THIRD_PARTY_DOCKER_USER'] + end + + def third_party_docker_password + ENV['QA_THIRD_PARTY_DOCKER_PASSWORD'] + end + def max_capybara_wait_time ENV.fetch('MAX_CAPYBARA_WAIT_TIME', 10).to_i end diff --git a/qa/qa/service/docker_run/base.rb b/qa/qa/service/docker_run/base.rb index 53980b8e051..c91f68d31a0 100644 --- a/qa/qa/service/docker_run/base.rb +++ b/qa/qa/service/docker_run/base.rb @@ -6,11 +6,34 @@ module QA class Base include Service::Shellout + def self.authenticated_registries + @authenticated_registries ||= {} + end + def initialize @network = Runtime::Scenario.attributes[:network] || 'test' @runner_network = Runtime::Scenario.attributes[:runner_network] || @network end + # Authenticate against a container registry + # If authentication is successful, will cache registry + # + # @param registry [String] registry to authenticate against + # @param user [String] + # @param password [String] + # @param force [Boolean] force authentication if already authenticated + # @return [Void] + def login(registry, user:, password:, force: false) + return if self.class.authenticated_registries[registry] && !force + + shell( + %(docker login --username "#{user}" --password "#{password}" #{registry}), + mask_secrets: [password] + ) + + self.class.authenticated_registries[registry] = true + end + def logs shell "docker logs #{@name}" end diff --git a/qa/qa/service/docker_run/mixins/third_party_docker.rb b/qa/qa/service/docker_run/mixins/third_party_docker.rb new file mode 100644 index 00000000000..b8c66d75d6b --- /dev/null +++ b/qa/qa/service/docker_run/mixins/third_party_docker.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module QA + module Service + module DockerRun + ThirdPartyValidationError = Class.new(StandardError) + + module Mixins + # Mixin for classes that inherit from Service::DockerRun::Base + # + # Helper for authenticating against private repositories. + # registry.gitlab.com/gitlab-org/quality/third-party-docker-images + module ThirdPartyDocker + # @return [Void] + def authenticate_third_party(force: false) + raise_validation_error unless can_authenticate_third_party? + + login( + third_party_registry, + user: third_party_registry_user, + password: third_party_registry_password, + force: force + ) + end + + def third_party_registry + Runtime::Env.third_party_docker_registry + end + + def third_party_repository + Runtime::Env.third_party_docker_repository + end + + def third_party_registry_user + Runtime::Env.third_party_docker_user + end + + def third_party_registry_password + Runtime::Env.third_party_docker_password + end + + private + + def raise_validation_error + raise ThirdPartyValidationError, 'Third party docker environment variable(s) are not set' + end + + def can_authenticate_third_party? + [ + :third_party_registry, + :third_party_registry_user, + :third_party_registry_password + ].all? { |method| send(method).present? } + end + end + end + end + end +end diff --git a/qa/qa/service/shellout.rb b/qa/qa/service/shellout.rb index 376e4f74845..9b93297f6ff 100644 --- a/qa/qa/service/shellout.rb +++ b/qa/qa/service/shellout.rb @@ -14,7 +14,7 @@ module QA def shell(command, stdin_data: nil, fail_on_exception: true, stream_progress: true, mask_secrets: []) # rubocop:disable Metrics/CyclomaticComplexity cmd_string = Array(command).join(' ') - QA::Runtime::Logger.info("Executing: `#{cmd_string.cyan}`") + QA::Runtime::Logger.info("Executing: `#{mask_secrets_on_string(cmd_string, mask_secrets).cyan}`") Open3.popen2e(*command) do |stdin, out, wait| stdin.puts(stdin_data) if stdin_data diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb index bb4b0472398..74a00e1c74c 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb @@ -7,27 +7,27 @@ module QA describe 'Gitlab migration' do include_context 'with gitlab project migration' - context 'with project issues' do - let!(:source_issue) do - Resource::Issue.fabricate_via_api! do |issue| - issue.api_client = api_client - issue.project = source_project - issue.labels = %w[label_one label_two] - end + let!(:source_issue) do + Resource::Issue.fabricate_via_api! do |issue| + issue.api_client = api_client + issue.project = source_project + issue.labels = %w[label_one label_two] end + end - let!(:source_comment) { source_issue.add_comment(body: 'This is a test comment!') } - - let(:imported_issues) { imported_projects.first.issues } + let(:imported_issues) { imported_projects.first.issues } - let(:imported_issue) do - issue = imported_issues.first - Resource::Issue.init do |resource| - resource.api_client = api_client - resource.project = imported_projects.first - resource.iid = issue[:iid] - end + let(:imported_issue) do + issue = imported_issues.first + Resource::Issue.init do |resource| + resource.api_client = api_client + resource.project = imported_projects.first + resource.iid = issue[:iid] end + end + + context 'with project issues' do + let!(:source_comment) { source_issue.add_comment(body: 'This is a test comment!') } let(:imported_comments) { imported_issue.comments } @@ -46,6 +46,34 @@ module QA end end end + + context "with designs" do + let!(:source_design) do + Flow::Login.sign_in(as: user) + + Resource::Design.fabricate_via_browser_ui! do |design| + design.api_client = api_client + design.issue = source_issue + end.reload! + end + + let(:imported_design) do + Resource::Design.init do |design| + design.api_client = api_client + design.issue = imported_issue.reload! + end.reload! + end + + it( + 'successfully imports design', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/366449' + ) do + expect_import_finished + expect(imported_issues.count).to eq(1) + + expect(imported_design.full_path).to eq(source_design.full_path) + end + end end end end diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/invite_group_to_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/invite_group_to_project_spec.rb index dd27e85af3c..8201e2772aa 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/invite_group_to_project_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/invite_group_to_project_spec.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true module QA - # Tagging with issue for a transient invite group modal search bug, but does not require quarantine at this time - RSpec.describe 'Manage', :transient, issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/349379' do + RSpec.describe 'Manage' do describe 'Invite group' do shared_examples 'invites group to project' do it 'verifies group is added and members can access project with correct access level' do @@ -13,7 +12,7 @@ module QA expect(project_members).to have_group(group.path) end - Flow::Login.sign_in(as: @user) + Flow::Login.sign_in(as: user) Page::Dashboard::Projects.perform do |projects| projects.filter_by_name(project.name) @@ -29,13 +28,11 @@ module QA end end - before(:context) do - @user = Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) - end + let(:user) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) } before do Flow::Login.sign_in - group.add_member(@user, Resource::Members::AccessLevel::MAINTAINER) + group.add_member(user, Resource::Members::AccessLevel::MAINTAINER) project.visit! end @@ -75,11 +72,6 @@ module QA it_behaves_like 'invites group to project' end - - after do - project&.remove_via_api! - group&.remove_via_api! - end end end end diff --git a/qa/qa/specs/features/browser_ui/3_create/design_management/archive_design_content_spec.rb b/qa/qa/specs/features/browser_ui/3_create/design_management/archive_design_content_spec.rb index de7c95841d6..143b2633d84 100644 --- a/qa/qa/specs/features/browser_ui/3_create/design_management/archive_design_content_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/design_management/archive_design_content_spec.rb @@ -13,7 +13,7 @@ module QA end let(:third_design) do - Resource::Design.fabricate! do |design| + Resource::Design.fabricate_via_browser_ui! do |design| design.issue = second_design.issue design.filename = 'testfile.png' end diff --git a/qa/qa/specs/features/browser_ui/3_create/design_management/modify_design_content_spec.rb b/qa/qa/specs/features/browser_ui/3_create/design_management/modify_design_content_spec.rb index 726e86ccdb4..257ef6c23fc 100644 --- a/qa/qa/specs/features/browser_ui/3_create/design_management/modify_design_content_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/design_management/modify_design_content_spec.rb @@ -4,7 +4,7 @@ module QA RSpec.describe 'Create' do context 'Design Management' do let(:design) do - Resource::Design.fabricate! do |design| + Resource::Design.fabricate_via_browser_ui! do |design| design.filename = 'testfile.png' end end @@ -13,7 +13,10 @@ module QA Flow::Login.sign_in end - it 'user adds a design and modifies it', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347712' do + it( + 'user adds a design and modifies it', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347712' + ) do design.issue.visit! Page::Project::Issue::Show.perform do |issue| diff --git a/qa/qa/support/waiter.rb b/qa/qa/support/waiter.rb index 6dbbd197b01..4a8fbaf1c4e 100644 --- a/qa/qa/support/waiter.rb +++ b/qa/qa/support/waiter.rb @@ -13,7 +13,8 @@ module QA sleep_interval: 0.1, raise_on_failure: true, retry_on_exception: false, - log: true + log: true, + message: nil ) result = nil repeat_until( @@ -22,7 +23,8 @@ module QA sleep_interval: sleep_interval, raise_on_failure: raise_on_failure, retry_on_exception: retry_on_exception, - log: log + log: log, + message: message ) do result = yield end diff --git a/qa/spec/service/docker_run/base_spec.rb b/qa/spec/service/docker_run/base_spec.rb new file mode 100644 index 00000000000..4c6638cdcf5 --- /dev/null +++ b/qa/spec/service/docker_run/base_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module QA + RSpec.describe Service::DockerRun::Base do + context 'when authenticating' do + let(:instance_one) { Service::DockerRun::Base.new } + let(:instance_two) { Service::DockerRun::Base.new } + + before do + # reset singleton registry state + Service::DockerRun::Base.authenticated_registries.transform_values! { |_v| false } + end + + it 'caches the the registry' do + expect(instance_one).to receive(:shell).once.and_return(nil) + expect(instance_two).not_to receive(:shell) + + instance_one.login('registry.foobar.com', user: 'foobar', password: 'secret') + instance_two.login('registry.foobar.com', user: 'foobar', password: 'secret') + end + + it 'forces authentication if the registry is cached' do + expect(instance_one).to receive(:shell).once.and_return(nil) + expect(instance_two).to receive(:shell).once.and_return(nil) + + instance_one.login('registry.foobar.com', user: 'foobar', password: 'secret') + instance_two.login('registry.foobar.com', user: 'foobar', password: 'secret', force: true) + end + end + end +end diff --git a/qa/spec/service/docker_run/mixins/third_party_docker_spec.rb b/qa/spec/service/docker_run/mixins/third_party_docker_spec.rb new file mode 100644 index 00000000000..5488dca4c40 --- /dev/null +++ b/qa/spec/service/docker_run/mixins/third_party_docker_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module QA + RSpec.describe QA::Service::DockerRun::Mixins::ThirdPartyDocker do + include QA::Support::Helpers::StubEnv + + let(:klass) do + Class.new(Service::DockerRun::Base) do + include Service::DockerRun::Mixins::ThirdPartyDocker + + def initialize(repo: Runtime::Env.third_party_docker_repository) + @image = "#{repo}/some-image:latest" + end + end + end + + let(:service) { klass.new } + + before do + Service::DockerRun::Base.authenticated_registries.transform_values! { |_v| false } + end + + context 'with environment set' do + before do + stub_env('QA_THIRD_PARTY_DOCKER_REGISTRY', 'registry.foobar.com') + stub_env('QA_THIRD_PARTY_DOCKER_REPOSITORY', 'registry.foobar.com/some/path') + stub_env('QA_THIRD_PARTY_DOCKER_USER', 'username') + stub_env('QA_THIRD_PARTY_DOCKER_PASSWORD', 'secret') + end + + it 'resolves the registry from the environment' do + expect(service.third_party_registry).to eql('registry.foobar.com') + end + + it 'sends a command to authenticate against the registry' do + expect(service).to receive(:shell) + .with( + 'docker login --username "username" --password "secret" registry.foobar.com', + mask_secrets: ['secret'] + ) + .and_return(nil) + + service.authenticate_third_party + end + end + + context 'without environment set' do + before do + stub_env('QA_THIRD_PARTY_DOCKER_REGISTRY', nil) + stub_env('QA_THIRD_PARTY_DOCKER_USER', 'username') + stub_env('QA_THIRD_PARTY_DOCKER_PASSWORD', 'secret') + end + + it 'resolving the registry returns nil' do + expect(service.third_party_registry).to be(nil) + end + + it 'throws if environment is missing' do + expect(service).not_to receive(:shell) + + expect { service.authenticate_third_party }.to raise_error do |err| + expect(err.class).to be(Service::DockerRun::ThirdPartyValidationError) + expect(err.message).to eql('Third party docker environment variable(s) are not set') + end + end + end + end +end diff --git a/qa/spec/service/shellout_spec.rb b/qa/spec/service/shellout_spec.rb index 9d7adeb0e94..52f095f165a 100644 --- a/qa/spec/service/shellout_spec.rb +++ b/qa/spec/service/shellout_spec.rb @@ -13,6 +13,12 @@ module QA allow(Open3).to receive(:popen2e).and_yield(stdin, stdout, wait_thread) end + it 'masks secrets when logging the command itself' do + expect(Runtime::Logger).to receive(:info).with('Executing: `docker login -u **** -p ****`') + expect(wait_thread).to receive(:value).twice.and_return(non_errored_wait) + subject.shell('docker login -u user -p secret', mask_secrets: %w[secret user]) + end + it 'masks command secrets on CommandError' do expect(wait_thread).to receive(:value).twice.and_return(errored_wait) |