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