summaryrefslogtreecommitdiff
path: root/app/services/ci/create_downstream_pipeline_service.rb
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-09-19 01:45:44 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-09-19 01:45:44 +0000
commit85dc423f7090da0a52c73eb66faf22ddb20efff9 (patch)
tree9160f299afd8c80c038f08e1545be119f5e3f1e1 /app/services/ci/create_downstream_pipeline_service.rb
parent15c2c8c66dbe422588e5411eee7e68f1fa440bb8 (diff)
downloadgitlab-ce-85dc423f7090da0a52c73eb66faf22ddb20efff9.tar.gz
Add latest changes from gitlab-org/gitlab@13-4-stable-ee
Diffstat (limited to 'app/services/ci/create_downstream_pipeline_service.rb')
-rw-r--r--app/services/ci/create_downstream_pipeline_service.rb128
1 files changed, 128 insertions, 0 deletions
diff --git a/app/services/ci/create_downstream_pipeline_service.rb b/app/services/ci/create_downstream_pipeline_service.rb
new file mode 100644
index 00000000000..0394cfb6119
--- /dev/null
+++ b/app/services/ci/create_downstream_pipeline_service.rb
@@ -0,0 +1,128 @@
+# frozen_string_literal: true
+
+module Ci
+ # Takes in input a Ci::Bridge job and creates a downstream pipeline
+ # (either multi-project or child pipeline) according to the Ci::Bridge
+ # specifications.
+ class CreateDownstreamPipelineService < ::BaseService
+ include Gitlab::Utils::StrongMemoize
+
+ DuplicateDownstreamPipelineError = Class.new(StandardError)
+
+ MAX_DESCENDANTS_DEPTH = 2
+
+ def execute(bridge)
+ @bridge = bridge
+
+ if bridge.has_downstream_pipeline?
+ Gitlab::ErrorTracking.track_exception(
+ DuplicateDownstreamPipelineError.new,
+ bridge_id: @bridge.id, project_id: @bridge.project_id
+ )
+ return
+ end
+
+ pipeline_params = @bridge.downstream_pipeline_params
+ target_ref = pipeline_params.dig(:target_revision, :ref)
+
+ return unless ensure_preconditions!(target_ref)
+
+ service = ::Ci::CreatePipelineService.new(
+ pipeline_params.fetch(:project),
+ current_user,
+ pipeline_params.fetch(:target_revision))
+
+ downstream_pipeline = service.execute(
+ pipeline_params.fetch(:source), pipeline_params[:execute_params]) do |pipeline|
+ pipeline.variables.build(@bridge.downstream_variables)
+ end
+
+ downstream_pipeline.tap do |pipeline|
+ update_bridge_status!(@bridge, pipeline)
+ end
+ end
+
+ private
+
+ def update_bridge_status!(bridge, pipeline)
+ Gitlab::OptimisticLocking.retry_lock(bridge) do |subject|
+ if pipeline.created_successfully?
+ # If bridge uses `strategy:depend` we leave it running
+ # and update the status when the downstream pipeline completes.
+ subject.success! unless subject.dependent?
+ else
+ subject.options[:downstream_errors] = pipeline.errors.full_messages
+ subject.drop!(:downstream_pipeline_creation_failed)
+ end
+ end
+ rescue StateMachines::InvalidTransition => e
+ Gitlab::ErrorTracking.track_exception(
+ Ci::Bridge::InvalidTransitionError.new(e.message),
+ bridge_id: bridge.id,
+ downstream_pipeline_id: pipeline.id)
+ end
+
+ def ensure_preconditions!(target_ref)
+ unless downstream_project_accessible?
+ @bridge.drop!(:downstream_bridge_project_not_found)
+ return false
+ end
+
+ # TODO: Remove this condition if favour of model validation
+ # https://gitlab.com/gitlab-org/gitlab/issues/38338
+ if downstream_project == project && !@bridge.triggers_child_pipeline?
+ @bridge.drop!(:invalid_bridge_trigger)
+ return false
+ end
+
+ # TODO: Remove this condition if favour of model validation
+ # https://gitlab.com/gitlab-org/gitlab/issues/38338
+ if ::Gitlab::Ci::Features.child_of_child_pipeline_enabled?(project)
+ if has_max_descendants_depth?
+ @bridge.drop!(:reached_max_descendant_pipelines_depth)
+ return false
+ end
+ else
+ if @bridge.triggers_child_pipeline? && @bridge.pipeline.parent_pipeline.present?
+ @bridge.drop!(:bridge_pipeline_is_child_pipeline)
+ return false
+ end
+ end
+
+ unless can_create_downstream_pipeline?(target_ref)
+ @bridge.drop!(:insufficient_bridge_permissions)
+ return false
+ end
+
+ true
+ end
+
+ def downstream_project_accessible?
+ downstream_project.present? &&
+ can?(current_user, :read_project, downstream_project)
+ end
+
+ def can_create_downstream_pipeline?(target_ref)
+ can?(current_user, :update_pipeline, project) &&
+ can?(current_user, :create_pipeline, downstream_project) &&
+ can_update_branch?(target_ref)
+ end
+
+ def can_update_branch?(target_ref)
+ ::Gitlab::UserAccess.new(current_user, container: downstream_project).can_update_branch?(target_ref)
+ end
+
+ def downstream_project
+ strong_memoize(:downstream_project) do
+ @bridge.downstream_project
+ end
+ end
+
+ def has_max_descendants_depth?
+ return false unless @bridge.triggers_child_pipeline?
+
+ ancestors_of_new_child = @bridge.pipeline.base_and_ancestors(same_project: true)
+ ancestors_of_new_child.count > MAX_DESCENDANTS_DEPTH
+ end
+ end
+end