gitlab-ce/app/services/ci/create_commit_status_servic...

164 lines
4.3 KiB
Ruby

# frozen_string_literal: true
module Ci
class CreateCommitStatusService < BaseService
include ::Gitlab::ExclusiveLeaseHelpers
include ::Gitlab::Utils::StrongMemoize
include ::Services::ReturnServiceResponses
delegate :sha, to: :commit
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
pipelines = project.ci_pipelines.newest_first(sha: sha)
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!
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