Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-09-05 15:11:04 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-09-05 15:11:04 +0300
commit164ac94bbd2eadc02ab54322a6fe12ed48ae8041 (patch)
tree6eec29a4fd554eeb6f4a1b296e37bdf6fb5f6f80 /app/services/ci
parent8934df30a36d16ac9de9aebb079e16f16fda6912 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/services/ci')
-rw-r--r--app/services/ci/create_commit_status_service.rb158
1 files changed, 158 insertions, 0 deletions
diff --git a/app/services/ci/create_commit_status_service.rb b/app/services/ci/create_commit_status_service.rb
new file mode 100644
index 00000000000..e5b446a07e2
--- /dev/null
+++ b/app/services/ci/create_commit_status_service.rb
@@ -0,0 +1,158 @@
+# 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
+ 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?
+
+ @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?
+
+ update_merge_request_head_pipeline
+ response
+ 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!
+ 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'
+ ).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 update_merge_request_head_pipeline
+ return unless pipeline.latest?
+
+ ::MergeRequest
+ .from_project(project).from_source_branches(ref)
+ .update_all(head_pipeline_id: pipeline.id)
+ 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