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