lib/release_tools/passing_build.rb (84 lines of code) (raw):
# frozen_string_literal: true
module ReleaseTools
class PassingBuild
include ::SemanticLogger::Loggable
attr_reader :ref
def initialize(ref, project = ReleaseTools::Project::GitlabEe, client: ReleaseTools::GitlabClient)
@project = project
@ref = ref
@client = client
end
def for_auto_deploy_branch
@for_auto_deploy_branch ||= passing_build_commit(:branch)
end
def for_auto_deploy_tag
@for_auto_deploy_tag ||= passing_build_commit(:tag)
end
def success_for_auto_deploy_rollout?(sha)
success?(sha, :rollout)
end
# Find the latest successful commit for the current project and ref.
# @return [ObjectifiedHash, nil] Return the newest commit with a successful
# pipeline
def latest_successful
commits.detect { |commit| success?(commit.id, :latest_successful) }
end
def next_commit
commits.next_commit(for_auto_deploy_branch.id)
end
def previous_package_sha
commits.find_last_auto_deploy_limit_sha
end
private
def commits
@commits ||= ReleaseTools::Commits.new(@project, ref: ref, client: @client)
end
def passing_build_commit(operation)
return commits.latest if Feature.enabled?(:auto_deploy_tag_latest)
commit = commits.latest_successful_on_build(since_last_auto_deploy: true) do |sha|
next true if success?(sha, operation)
logger.info(
'Skipping commit because the pipeline did not succeed',
commit: sha
)
false
end
if commit.nil?
raise "Unable to find a passing #{@project} build for `#{ref}` on dev"
end
commit
end
def success?(sha, operation)
result = Retriable.with_context(:api) { @client.commit(@project.auto_deploy_path, ref: sha) }
return success_for_gitlab(result, operation) if @project == ReleaseTools::Project::GitlabEe
result.status == 'success'
end
def success_for_gitlab(commit, operation)
logger.debug('validating gitlab commit', sha: commit.id, operation: operation, status: commit.status, url: commit.web_url)
if commit.status == 'success'
# Documentation-only changes result in a pipeline with only a few jobs.
# If we were to include a passing documentation pipeline/commit, we may
# end up also including code that broke a previous full pipeline.
#
# We also take into account QA only pipelines. These pipelines _should_ be
# considered, but don't have a regular full pipeline. The "build-assets-image"
# job is present for both regular and QA pipelines, but not for
# documentation pipelines; hence we check for the presence of this job.
# This is more reliable than just checking the amount of jobs.
full_pipeline?(commit.last_pipeline)
elsif operation == :tag && commit.status != 'failed'
# When tagging we already ensured a full pipeline with the branch creation,
# in this case we are either looking at a second run of the same pipeline
# from the auto_deploy branch or at a pick into auto-deploy.
#
# In both cases we are ok in allowing the tag operation if the pipeline
# is not failed. The formal validation of a successful run will be
# delayed before the rollout
true
else
# When a new auto-deploy branch is created, some commits may only have a
# pipeline on the default branch. It's also possible that there _is_ a
# pipeline, but that it only recently started.
#
# In both cases it's fine to tag the commit if it passed on the default
# branch.
#
# When we delay package validation it is possible that a SHA that was
# tagged while running on the auto-deploy branch and green on the
# default branch will be red on the auto-deploy branch.
# This can happened because of test flakiness. To keep things aligned
# with the upfront validation logic in case of failure on the GitLab
# repository we consider the status on the default branch.
success_on_default_branch?(commit.id)
end
end
def full_pipeline?(pipeline)
@client
.pipeline_jobs(@project.auto_deploy_path, pipeline.id)
.auto_paginate do |job|
# Latest master pipelines will not contain 'build-assets-image' job anymore
# To keep backward compatibility for older pipelines, retain the check for 'build-assets-image' job
return true if %w[build-assets-image compile-production-assets].any? { |name| job.name.start_with?(name) }
end
false
end
def success_on_default_branch?(sha)
@client
.pipelines(
@project.auto_deploy_path,
sha: sha,
ref: @project.default_branch,
status: 'success'
)
.auto_paginate do |pipeline|
return true if full_pipeline?(pipeline)
end
false
end
end
end