1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
|
#!/usr/bin/env ruby
# frozen_string_literal: true
# We need to take some precautions when using the `gitlab` gem in this project.
#
# See https://docs.gitlab.com/ee/development/pipelines/internals.html#using-the-gitlab-ruby-gem-in-the-canonical-project.
if Object.const_defined?(:RSpec)
# Ok, we're testing, we know we're going to stub `Gitlab`, so we just ignore
else
require 'gitlab'
if Gitlab.singleton_class.method_defined?(:com?)
abort 'lib/gitlab.rb is loaded, and this means we can no longer load the client and we cannot proceed'
end
end
require 'net/http'
class SetPipelineName
DOCS = ['docs-lint markdown', 'docs-lint links'].freeze
RSPEC_PREDICTIVE = ['rspec:predictive:trigger', 'rspec-ee:predictive:trigger'].freeze
CODE = ['retrieve-tests-metadata'].freeze
QA_GDK = ['e2e:test-on-gdk'].freeze
REVIEW_APP = ['start-review-app-pipeline'].freeze
QA = [
'e2e:test-on-omnibus-ce',
'e2e:test-on-omnibus-ee',
'follow-up:e2e:test-on-omnibus-ee'
].freeze
# Ordered by expected duration, DESC
PIPELINE_TYPES_ORDERED = %w[qa review-app qa-gdk code rspec-predictive docs].freeze
# We need an access token that isn't CI_JOB_TOKEN because we are querying
# the pipelines API to fetch jobs and bridge jobs.
# We are still using CI_JOB_TOKEN to update the pipeline name.
#
# See https://docs.gitlab.com/ee/ci/jobs/ci_job_token.html for more info.
def initialize(api_endpoint:, gitlab_access_token:)
@api_endpoint = api_endpoint
@gitlab_access_token = gitlab_access_token
end
def execute
# If we already added metadata to the pipeline name, we discard it, and recompute it.
#
# This is in case we retry a CI job that runs this script.
pipeline_name = ENV['CI_PIPELINE_NAME'].sub(/ \[.*\]\z/, '')
pipeline_suffixes = {}
pipeline_suffixes[:tier] = pipeline_tier || 'N/A'
pipeline_suffixes[:types] = pipeline_types.join(',')
pipeline_suffixes[:opts] = pipeline_opts.join(',')
pipeline_suffix = pipeline_suffixes.map { |key, value| "#{key}:#{value}" }.join(', ')
pipeline_name += " [#{pipeline_suffix}]"
puts "New pipeline name: #{pipeline_name}"
set_pipeline_name(pipeline_name)
rescue Gitlab::Error::Error => error
puts "GitLab error: #{error}"
allow_to_fail_return_code
end
private
attr_reader :api_endpoint, :gitlab_access_token
def api_client
@api_client ||= Gitlab.client(
endpoint: api_endpoint,
private_token: gitlab_access_token
)
end
def pipeline_tier
return unless ENV['CI_MERGE_REQUEST_LABELS']
return if expedited_pipeline?
tier_label = merge_request_labels.find { |label| label.start_with?('pipeline::tier-') }
return if tier_label.nil?
tier_label[/\d+\z/]
end
def merge_request_labels
# The first pipeline of any MR won't have any tier label, unless the label was added in the MR description
# before creating the MR. This is a known limitation.
#
# Fetching the labels from the API instead of relying on ENV['CI_MERGE_REQUEST_LABELS']
# would solve this problem, but it would also mean that we would update the tier information
# based on the merge request labels at the time of retrying the job, which isn't what we want.
@merge_request_labels ||= ENV.fetch('CI_MERGE_REQUEST_LABELS', '').split(',').tap do |labels|
puts "Labels from the MR: #{labels}"
end
end
def expedited_pipeline?
merge_request_labels.any?('pipeline::expedited') ||
# TODO: Remove once the label is renamed to be scoped
merge_request_labels.any?('pipeline:expedite')
end
def pipeline_types
return ['expedited'] if expedited_pipeline?
types = Set.new
api_client.pipeline_bridges(ENV['CI_PROJECT_ID'], ENV['CI_PIPELINE_ID']).auto_paginate do |job|
types.merge(pipeline_types_for(job))
end
api_client.pipeline_jobs(ENV['CI_PROJECT_ID'], ENV['CI_PIPELINE_ID']).auto_paginate do |job|
types.merge(pipeline_types_for(job))
end
types.sort_by { |type| PIPELINE_TYPES_ORDERED.index(type) }
end
def pipeline_opts
return [] unless ENV['CI_MERGE_REQUEST_LABELS']
opts_label = merge_request_labels.select { |label| label.start_with?(/pipeline:\w/) }
return if opts_label.nil?
opts_label.map { |opt_label| opt_label.delete_prefix('pipeline:') }
end
def pipeline_types_for(job)
types = Set.new
types << 'rspec-predictive' if RSPEC_PREDICTIVE.include?(job.name)
types << 'qa-gdk' if QA_GDK.include?(job.name)
types << 'review-app' if REVIEW_APP.include?(job.name)
types << 'qa' if QA.include?(job.name)
types << 'docs' if DOCS.include?(job.name)
types << 'code' if CODE.include?(job.name)
types
end
def set_pipeline_name(pipeline_name)
# TODO: Replace with the following once a version of the gem is above 4.19.0:
#
# Gitlab.update_pipeline_metadata(ENV['CI_PROJECT_ID'], ENV['CI_PIPELINE_ID'], name: pipeline_name)
#
# New endpoint added in https://github.com/NARKOZ/gitlab/pull/685 (merged on 2024-04-30)
# Latest release was made on 2022-07-10: https://github.com/NARKOZ/gitlab/releases/tag/v4.19.0
uri = URI("#{ENV['CI_API_V4_URL']}/projects/#{ENV['CI_PROJECT_ID']}/pipelines/#{ENV['CI_PIPELINE_ID']}/metadata")
success = false
error = nil
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
request = Net::HTTP::Put.new uri
request['JOB-TOKEN'] = ENV['CI_JOB_TOKEN']
request.body = "name=#{pipeline_name}"
response = http.request request
if response.code == '200'
success = true
else
error = response.body
end
end
return 0 if success
puts "Failed to set pipeline name: #{error}"
allow_to_fail_return_code
end
# Exit with a different error code, so that we can allow the CI job to fail
def allow_to_fail_return_code
3
end
end
if $PROGRAM_NAME == __FILE__
exit SetPipelineName.new(
api_endpoint: ENV['CI_API_V4_URL'],
gitlab_access_token: ENV['PROJECT_TOKEN_FOR_CI_SCRIPTS_API_USAGE']
).execute
end
|