From e7550b1deccc8de9d722862ec42c8cda0f18c106 Mon Sep 17 00:00:00 2001 From: Andrejs Cunskis <acunskis@gitlab.com> Date: Tue, 24 Sep 2024 20:02:46 +0000 Subject: [PATCH] Reset admin password in global hook instead of license fabrication class Use better method name and warning message --- qa/qa/ce/strategy.rb | 90 +++++++++++++-------- qa/qa/ee/resource/license.rb | 15 +--- qa/qa/ee/strategy.rb | 17 ++-- qa/qa/tools/readiness_check.rb | 109 ++++++++++++++++++++++++++ qa/spec/tools/readiness_check_spec.rb | 66 ++++++++++++++++ 5 files changed, 243 insertions(+), 54 deletions(-) create mode 100644 qa/qa/tools/readiness_check.rb create mode 100644 qa/spec/tools/readiness_check_spec.rb diff --git a/qa/qa/ce/strategy.rb b/qa/qa/ce/strategy.rb index 608060ecfeccd..a926c9dac1369 100644 --- a/qa/qa/ce/strategy.rb +++ b/qa/qa/ce/strategy.rb @@ -3,49 +3,75 @@ module QA module CE module Strategy - extend self - - # Perform global setup - # - # @return [Boolean] returns true if hooks were performed successfully - def perform_before_hooks - if QA::Runtime::Env.admin_personal_access_token.present? - QA::Resource::PersonalAccessTokenCache.set_token_for_username( - QA::Runtime::User.admin_username, - QA::Runtime::Env.admin_personal_access_token - ) + class << self + # Perform global setup + # + # @return [Boolean] returns true if hooks were performed successfully + def perform_before_hooks + cache_tokens! + log_browser_versions + + if Runtime::Env.rspec_retried? + Runtime::Logger.info('Skipping global hooks due to retry process') + return false + end + + # Perform app readiness check before continuing with the whole test suite + Tools::ReadinessCheck.perform(wait: 60) + + # Reset admin password if admin token is present but can't be used due to expired password + reset_admin_password! + + if Runtime::Env.allow_local_requests? + Runtime::ApplicationSettings.set_application_settings( + allow_local_requests_from_web_hooks_and_services: true + ) + end + + true end - if QA::Runtime::Env.personal_access_token.present? && QA::Runtime::Env.user_username.present? - QA::Resource::PersonalAccessTokenCache.set_token_for_username( - QA::Runtime::Env.user_username, - QA::Runtime::Env.personal_access_token + private + + def cache_tokens! + if Runtime::Env.admin_personal_access_token.present? + Resource::PersonalAccessTokenCache.set_token_for_username( + Runtime::User.admin_username, + Runtime::Env.admin_personal_access_token + ) + end + + return unless Runtime::Env.personal_access_token.present? && Runtime::Env.user_username.present? + + Resource::PersonalAccessTokenCache.set_token_for_username( + Runtime::Env.user_username, + Runtime::Env.personal_access_token ) end - QA::Runtime::Logger.info("Using Browser: #{QA::Runtime::Env.browser}") + def log_browser_versions + Runtime::Logger.info("Using Browser: #{Runtime::Env.browser}") + return unless Runtime::Env.use_selenoid? - if QA::Runtime::Env.use_selenoid? - QA::Runtime::Logger.info("Using Selenoid Browser version: #{QA::Runtime::Env.selenoid_browser_version}") + Runtime::Logger.info("Using Selenoid Browser version: #{Runtime::Env.selenoid_browser_version}") end - if Runtime::Env.rspec_retried? - Runtime::Logger.info('Skipping global hooks due to retry process') - return false - end + def reset_admin_password! + return unless Runtime::Env.admin_personal_access_token.present? - # The login page could take some time to load the first time it is visited. - # We visit the login page and wait for it to properly load only once before the tests. - QA::Runtime::Logger.info("Performing sanity check for environment!") - QA::Support::Retrier.retry_on_exception do - QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login) - end + response = Support::API.get(Runtime::API::Request.new(Runtime::API::Client.as_admin, "/user").url) + return unless response.code == 403 && response.body.include?("Your password expired") - if QA::Runtime::Env.allow_local_requests? - Runtime::ApplicationSettings.set_application_settings(allow_local_requests_from_web_hooks_and_services: true) - end + # Mostly issue with gdk where default seeded password for admin user will be expired + Runtime::Logger.warn( + "Admin password must be reset before the configured access token can be used. Setting password now..." + ) - true + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform(&:sign_in_using_admin_credentials) + Page::Main::Login.perform(&:set_up_new_admin_password_if_required) + Page::Main::Menu.perform(&:sign_out_if_signed_in) + end end end end diff --git a/qa/qa/ee/resource/license.rb b/qa/qa/ee/resource/license.rb index 7b1904399a417..c5e2242bce863 100644 --- a/qa/qa/ee/resource/license.rb +++ b/qa/qa/ee/resource/license.rb @@ -40,6 +40,7 @@ def initialize end def fabricate! + QA::Page::Main::Menu.perform(&:sign_out_if_signed_in) QA::Page::Main::Login.perform(&:sign_in_using_admin_credentials) QA::Page::Main::Menu.perform(&:go_to_admin_area) QA::Page::Main::Login.perform(&:set_up_new_admin_password_if_required) @@ -83,18 +84,8 @@ def fabricate_via_api! api_post.tap { QA::Runtime::Logger.info("Successfully added license key. Details:\n#{license_info}") } rescue RuntimeError => e - unless e.message.include?('Your password expired') - QA::Runtime::Logger.error("Following license fabrication failed: #{base_license_info}") - raise(e) - end - - QA::Runtime::Logger.warn('Admin password must be reset before the default access token can be used. ' \ - 'Setting password now...') - - QA::Page::Main::Login.perform(&:sign_in_using_admin_credentials) - QA::Page::Main::Login.perform(&:set_up_new_admin_password_if_required) - - retry + QA::Runtime::Logger.error("Following license fabrication failed: #{base_license_info}") + raise(e) end end diff --git a/qa/qa/ee/strategy.rb b/qa/qa/ee/strategy.rb index 61ff4bd51956e..a4efc2e071ee8 100644 --- a/qa/qa/ee/strategy.rb +++ b/qa/qa/ee/strategy.rb @@ -12,23 +12,20 @@ def perform_before_hooks if QA::Runtime::Env.ee_license.present? QA::Runtime::Logger.info("Performing initial license fabrication!") - QA::Page::Main::Menu.perform(&:sign_out_if_signed_in) EE::Resource::License.fabricate! do |resource| resource.license = QA::Runtime::Env.ee_license end end - unless QA::Runtime::Env.running_on_dot_com? - QA::Runtime::Logger.info("Disabling sync with External package metadata database") - # we can't pass [] here, otherwise it causes a validation error, because the value we pass - # must be a valid purl_type. Instead, we pass the `deb` purl_type which is only used for - # container scanning advisories, which are not yet supported/ingested, so this is effectively - # the same thing as disabling the sync. - QA::Runtime::ApplicationSettings.set_application_settings(package_metadata_purl_types: [DEB_PURL_TYPE]) - end + return if QA::Runtime::Env.running_on_dot_com? - QA::Page::Main::Menu.perform(&:sign_out_if_signed_in) + QA::Runtime::Logger.info("Disabling sync with External package metadata database") + # we can't pass [] here, otherwise it causes a validation error, because the value we pass + # must be a valid purl_type. Instead, we pass the `deb` purl_type which is only used for + # container scanning advisories, which are not yet supported/ingested, so this is effectively + # the same thing as disabling the sync. + QA::Runtime::ApplicationSettings.set_application_settings(package_metadata_purl_types: [DEB_PURL_TYPE]) end end end diff --git a/qa/qa/tools/readiness_check.rb b/qa/qa/tools/readiness_check.rb new file mode 100644 index 0000000000000..d15f11dc79d5b --- /dev/null +++ b/qa/qa/tools/readiness_check.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +require "nokogiri" + +module QA + module Tools + # Helper to assert GitLab instance readiness without starting a web server + # + class ReadinessCheck + include Support::API + + def self.perform(wait: 60) + new(wait: wait).perform + end + + def initialize(wait:) + @wait = wait + end + + # Validate gitlab readiness via check for presence of sign-in-form element + # + # @return [void] + def perform + error = nil + + info("Waiting for Gitlab to become ready!") + Support::Retrier.retry_until(max_duration: wait, sleep_interval: 1, raise_on_failure: false, log: false) do + result = !required_elements_missing? + error = nil + + result + rescue StandardError => e + error = "#{error_base} #{e.message}" + + false + end + raise error if error + + info("Gitlab is ready!") + end + + private + + delegate :debug, :info, to: QA::Runtime::Logger + + attr_reader :wait + + # Sign in page url + # + # @return [String] + def url + @url ||= "#{Support::GitlabAddress.address_with_port(with_default_port: false)}/users/sign_in" + end + + # Error message base + # + # @return [String] + def error_base + @error_base ||= "Gitlab readiness check failed, valid sign_in page did not appear within #{wait} seconds!" + end + + # Required elements css selectors + # + # @return [Array<String>] + def elements_css + @element_css ||= QA::Page::Main::Login.elements.select(&:required?).map(&:selector_css) + end + + # Check for missing required elements on sign-in page + # + # @return [Boolean] + def required_elements_missing? + debug("Checking for required element presence on '#{url}'") + response = get(url) + + unless ok_response?(response) + msg = "Got unsucessfull response code: #{response.code}" + debug(msg) && raise(msg) + end + + unless required_elements_present?(response) + msg = "Sign in page missing required elements: '#{elements_css}'" + debug(msg) && raise(msg) + end + + debug("Required elements are present!") + false + end + + # Validate response code is 200 + # + # @param [RestClient::Response] response + # @return [Boolean] + def ok_response?(response) + response.code == Support::API::HTTP_STATUS_OK + end + + # Check required elements are present on sign-in page + # + # @param [RestClient::Response] response + # @return [Boolean] + def required_elements_present?(response) + doc = Nokogiri::HTML.parse(response.body) + + elements_css.all? { |sel| doc.css(sel).any? } + end + end + end +end diff --git a/qa/spec/tools/readiness_check_spec.rb b/qa/spec/tools/readiness_check_spec.rb new file mode 100644 index 0000000000000..32abb6aea6db0 --- /dev/null +++ b/qa/spec/tools/readiness_check_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +RSpec.describe QA::Tools::ReadinessCheck do + subject(:readiness_check) { described_class.new(wait: wait) } + + let(:url) { "example.com" } + let(:wait) { 1 } + let(:msg_base) { "Gitlab readiness check failed, valid sign_in page did not appear within #{wait} seconds!" } + + let(:response) { instance_double(RestClient::Response, code: code, body: body) } + let(:code) { 200 } + let(:body) { "" } + + before do + allow(QA::Support::GitlabAddress).to receive(:address_with_port).with(with_default_port: false).and_return(url) + allow(readiness_check).to receive(:get).with("#{url}/users/sign_in").and_return(response) + end + + context "with successfull response" do + let(:body) do + <<~HTML + <!DOCTYPE html> + <body data-testid="login-page"> + </body> + </html> + HTML + end + + it "validates readiness" do + expect { readiness_check.perform }.not_to raise_error + end + end + + context "with missing sign in form" do + let(:body) do + <<~HTML + <!DOCTYPE html> + </html> + HTML + end + + it "raises an error on validation" do + expect { readiness_check.perform }.to raise_error(/#{msg_base} Sign in page missing required elements/) + end + end + + context "with unsuccessfull response code" do + let(:code) { 500 } + + it "raises an error on validation" do + expect { readiness_check.perform }.to raise_error( + "#{msg_base} Got unsucessfull response code: #{code}" + ) + end + end + + context "with request timeout" do + before do + allow(readiness_check).to receive(:get).and_raise(RestClient::Exceptions::OpenTimeout) + end + + it "raises an error on validation" do + expect { readiness_check.perform }.to raise_error("#{msg_base} Timed out connecting to server") + end + end +end -- GitLab