lib/gitlab/qa/support/gitlab_upgrade_path.rb (128 lines of code) (raw):

# frozen_string_literal: true require "active_support/core_ext/module/delegation" require "yaml" module Gitlab module QA module Support class GitlabUpgradePath # Get upgrade path between N - 1 and current version not including current release # # @param [String] current_version # @param [String] semver_component version number component for previous version detection - major|minor|patch # @param [String] edition GitLab edition - ee or ce def initialize(current_version, semver_component, edition) @logger = Runtime::Logger.logger unless current_version.match?(GitlabVersionInfo::VERSION_PATTERN) logger.error("Invalid 'current_version' format: #{current_version}. Expected format: MAJOR.MINOR.PATCH (e.g., 17.8.2)") exit 1 end @version_info = GitlabVersionInfo.new(current_version, edition) @current_version = Gem::Version.new(current_version.match(GitlabVersionInfo::VERSION_PATTERN)[:version]) # Extract version without postfixes like pre or ee @semver_component = semver_component @edition = edition end # Get upgrade path between releases # # Return array with only previous version for updates from previous minor, patch versions # # @return [Array<QA::Release>] def fetch case semver_component when "patch" patch_upgrade_path when "minor" minor_upgrade_path when "major" major_upgrade_path when "from_patch" from_patch_upgrade_path when "internal_patch" internal_patch_upgrade_path else raise ArgumentError, "Unknown semver component: #{semver_component}" end rescue GitlabVersionInfo::VersionNotFoundError logger.error("Failed to construct gitlab upgrade path") raise end private delegate :latest_patch, to: :version_info attr_reader :version_info, :current_version, :semver_component, :edition, :logger # Upgrade path for patch version update # Returns array with current version as we're testing upgrade from # latest stable patch to development version # # @return [Array] def patch_upgrade_path verify_current_version_exists [release(latest_patch(current_version))] end # Upgrade path from previous minor version # # @return [Array] def minor_upgrade_path [release(latest_patch(previous_version))] end # Upgrade path from previous major version # # @return [Array] def major_upgrade_path # get versions between previous major and current version in gitlab upgrade path path = full_upgrade_path.each_with_object([]) do |ver, arr| next if ver <= previous_version || ver >= current_version arr << ver end [previous_version, *path].map do |ver| release(version_info.latest_patch(ver)) end end # Upgrade path from current version to next stable version # Checks if current version exists in releases # Gets next available major.minor version and its latest patch # # @return [Array] Array with next version's latest patch release or exits with message def from_patch_upgrade_path verify_current_version_exists next_version = version_info.next_version(current_version.to_s) unless next_version logger.info("Skipping upgrade test as next version after #{current_version} is not yet available") exit 0 end [release(latest_patch(next_version))] end # Upgrade path for internal patch version # Sets up authentication for internal registry # Finds the latest internal build for the current version # # @return [Array<QA::Release>] Array with the latest internal build for current version or exits with message def internal_patch_upgrade_path unless Runtime::Env.dev_access_token_variable logger.error("Skipping upgrade test as internal patch upgrades are not supported without dev access token") exit 0 end verify_current_version_exists gitlab_int_reg_repo = "dev.gitlab.org:5005/gitlab/omnibus-gitlab/gitlab-ee" # Internal releases are stored in private repo and # not available for search with API release = QA::Release.new("#{gitlab_int_reg_repo}:latest") docker = Docker::Engine.new docker.login(**release.login_params) if release.login_params latest_internal_tag = find_latest_internal_tag(gitlab_int_reg_repo, docker) if latest_internal_tag [QA::Release.new("#{gitlab_int_reg_repo}:#{latest_internal_tag}")] else logger.warn("No internal image found for GitLab version #{current_version}") exit 0 end end # Find the latest internal tag for the current version # Searches for tags in descending order, from 10 down to 0 # Returns the first available tag or nil if none found # # @param [String] gitlab_int_reg_repo Registry repository path # @param [Docker::Engine] docker_engine Docker engine instance # @return [String, nil] Tag name if found, nil otherwise def find_latest_internal_tag(gitlab_int_reg_repo, docker) # Try to find the highest internal release tag, starting from 10 latest_internal_tag = nil logger.info("Start searching for the latest released internal image for gitlab version: #{current_version}...") # Release team note: no more than 10 internal releases expected for version 10.downto(0) do |internal_num| tag = "#{current_version}-internal#{internal_num}-0" image_uri = "#{gitlab_int_reg_repo}:#{tag}" logger.info("Checking for image: #{image_uri}") begin # Try to pull the image (this will fail if image doesn't exist) docker.pull(image: gitlab_int_reg_repo, tag: tag) latest_internal_tag = tag logger.info("Found image: #{image_uri}") break rescue Support::ShellCommand::StatusError => e logger.info("x - Image not found: #{image_uri}, \n #{e}") end end latest_internal_tag end # Docker release image # # @param [String] version # @return [QA::Release] def release(version) QA::Release.new("gitlab/gitlab-#{edition}:#{version}-#{edition}.0") end # Previous gitlab version # # @return [Gem::Version] def previous_version @previous_version ||= version_info.previous_version(semver_component) end # Verify if current version exists in GitLab releases # Exit with message if version is not yet released # # @return [void] def verify_current_version_exists return if version_info.version_exists?(current_version.to_s) logger.info("Skipping upgrade test as version #{current_version} is not yet released") exit 0 end # Gitlab upgrade path # # @return [Array<Gem::Version>] def full_upgrade_path @full_upgrade_path ||= ::YAML .safe_load(upgrade_path_yml, symbolize_names: true) .map { |version| Gem::Version.new("#{version[:major]}.#{version[:minor]}") } end # Upgrade path yml # # @return [String] def upgrade_path_yml @upgrade_path_yml ||= begin logger.info("Fetching gitlab upgrade path from 'gitlab.com/gitlab-org/gitlab' project") HttpRequest.make_http_request( url: "https://gitlab.com/gitlab-org/gitlab/-/raw/master/config/upgrade_path.yml" ).body end end end end end end