diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-09-05 15:11:04 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-09-05 15:11:04 +0300 |
commit | 164ac94bbd2eadc02ab54322a6fe12ed48ae8041 (patch) | |
tree | 6eec29a4fd554eeb6f4a1b296e37bdf6fb5f6f80 /app/services/ci | |
parent | 8934df30a36d16ac9de9aebb079e16f16fda6912 (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.rb | 158 |
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 |