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
|
# frozen_string_literal: true
module Ci
class CreateCommitStatusService < BaseService
include ::Gitlab::ExclusiveLeaseHelpers
include ::Gitlab::Utils::StrongMemoize
include ::Services::ReturnServiceResponses
delegate :sha, to: :commit
# Default number of pipelines to return
DEFAULT_LIMIT_PIPELINES = 100
def execute(optional_commit_status_params:)
in_lock(pipeline_lock_key, **pipeline_lock_params) do
@optional_commit_status_params = optional_commit_status_params
unsafe_execute
end
end
private
attr_reader :pipeline, :stage, :commit_status, :optional_commit_status_params
def unsafe_execute
result = validate
return result if result&.error?
@pipeline = first_matching_pipeline || create_pipeline
return forbidden unless ::Ability.allowed?(current_user, :update_pipeline, pipeline)
@stage = find_or_create_external_stage
@commit_status = find_or_build_external_commit_status
return bad_request(commit_status.errors.messages) if commit_status.invalid?
response = add_or_update_external_job
return bad_request(response.message) if response.error?
response
end
def validate
return not_found('Commit') if commit.blank?
return bad_request('State is required') if params[:state].blank?
return not_found('References for commit') if ref.blank?
return unless params[:pipeline_id] && !first_matching_pipeline
not_found("Pipeline for pipeline_id, sha and ref")
end
def ref
params[:ref] || first_matching_pipeline&.ref ||
repository.branch_names_contains(sha).first
end
strong_memoize_attr :ref
def commit
project.commit(params[:sha])
end
strong_memoize_attr :commit
def first_matching_pipeline
limit = params[:pipeline_id] ? nil : DEFAULT_LIMIT_PIPELINES
pipelines = project.ci_pipelines.newest_first(sha: sha, limit: limit)
pipelines = pipelines.for_ref(params[:ref]) if params[:ref]
pipelines = pipelines.id_in(params[:pipeline_id]) if params[:pipeline_id]
pipelines.first
end
strong_memoize_attr :first_matching_pipeline
def name
params[:name] || params[:context] || 'default'
end
def create_pipeline
project.ci_pipelines.build(
source: :external,
sha: sha,
ref: ref,
user: current_user,
protected: project.protected_for?(ref)
).tap do |new_pipeline|
new_pipeline.ensure_project_iid!
new_pipeline.save!
Gitlab::EventStore.publish(
Ci::PipelineCreatedEvent.new(data: { pipeline_id: new_pipeline.id })
)
end
end
def find_or_create_external_stage
pipeline.stages.safe_find_or_create_by!(name: 'external') do |stage| # rubocop:disable Performance/ActiveRecordSubtransactionMethods
stage.position = ::GenericCommitStatus::EXTERNAL_STAGE_IDX
stage.project = project
end
end
def find_or_build_external_commit_status
::GenericCommitStatus.running_or_pending.find_or_initialize_by( # rubocop:disable CodeReuse/ActiveRecord
project: project,
pipeline: pipeline,
name: name,
ref: ref,
user: current_user,
protected: project.protected_for?(ref),
ci_stage: stage,
stage_idx: stage.position,
stage: 'external',
partition_id: pipeline.partition_id
).tap do |new_commit_status|
new_commit_status.assign_attributes(optional_commit_status_params)
end
end
def add_or_update_external_job
::Ci::Pipelines::AddJobService.new(pipeline).execute!(commit_status) do |job|
apply_job_state!(job)
end
end
def apply_job_state!(job)
case params[:state]
when 'pending'
job.enqueue!
when 'running'
job.enqueue
job.run!
when 'success'
job.success!
when 'failed'
job.drop!(:api_failure)
when 'canceled'
job.cancel!
when 'skipped'
job.skip!
else
raise('invalid state')
end
end
def pipeline_lock_key
"api:commit_statuses:project:#{project.id}:sha:#{params[:sha]}"
end
def pipeline_lock_params
{
ttl: 5.seconds,
sleep_sec: 0.1.seconds,
retries: 20
}
end
def not_found(message)
error("404 #{message} Not Found", :not_found)
end
def bad_request(message)
error(message, :bad_request)
end
def forbidden
error("403 Forbidden", :forbidden)
end
end
end
|