summaryrefslogtreecommitdiff
path: root/app/services/ci/create_pipeline_service.rb
blob: 4a7f62de9e1223684cab0d0771893639f3a5c6e9 (plain)
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
# frozen_string_literal: true

module Ci
  class CreatePipelineService < BaseService
    attr_reader :pipeline

    CreateError = Class.new(StandardError)

    SEQUENCE = [Gitlab::Ci::Pipeline::Chain::Build,
                Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs,
                Gitlab::Ci::Pipeline::Chain::Validate::Abilities,
                Gitlab::Ci::Pipeline::Chain::Validate::Repository,
                Gitlab::Ci::Pipeline::Chain::Validate::Config,
                Gitlab::Ci::Pipeline::Chain::Skip,
                Gitlab::Ci::Pipeline::Chain::Limit::Size,
                Gitlab::Ci::Pipeline::Chain::Populate,
                Gitlab::Ci::Pipeline::Chain::Create,
                Gitlab::Ci::Pipeline::Chain::Limit::Activity,
                Gitlab::Ci::Pipeline::Chain::Limit::JobActivity].freeze

    # rubocop: disable Metrics/ParameterLists
    def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil, external_pull_request: nil, **options, &block)
      @pipeline = Ci::Pipeline.new

      command = Gitlab::Ci::Pipeline::Chain::Command.new(
        source: source,
        origin_ref: params[:ref],
        checkout_sha: params[:checkout_sha],
        after_sha: params[:after],
        before_sha: params[:before],          # The base SHA of the source branch (i.e merge_request.diff_base_sha).
        source_sha: params[:source_sha],      # The HEAD SHA of the source branch (i.e merge_request.diff_head_sha).
        target_sha: params[:target_sha],      # The HEAD SHA of the target branch.
        trigger_request: trigger_request,
        schedule: schedule,
        merge_request: merge_request,
        external_pull_request: external_pull_request,
        ignore_skip_ci: ignore_skip_ci,
        save_incompleted: save_on_errors,
        seeds_block: block,
        variables_attributes: params[:variables_attributes],
        project: project,
        current_user: current_user,
        push_options: params[:push_options] || {},
        chat_data: params[:chat_data],
        **extra_options(options))

      sequence = Gitlab::Ci::Pipeline::Chain::Sequence
        .new(pipeline, command, SEQUENCE)

      sequence.build! do |pipeline, sequence|
        schedule_head_pipeline_update

        if sequence.complete?
          cancel_pending_pipelines if project.auto_cancel_pending_pipelines?
          pipeline_created_counter.increment(source: source)

          pipeline.process!
        end
      end

      # If pipeline is not persisted, try to recover IID
      pipeline.reset_project_iid unless pipeline.persisted? ||
          Feature.disabled?(:ci_pipeline_rewind_iid, project, default_enabled: true)

      pipeline
    end
    # rubocop: enable Metrics/ParameterLists

    def execute!(*args, &block)
      execute(*args, &block).tap do |pipeline|
        unless pipeline.persisted?
          raise CreateError, pipeline.error_messages
        end
      end
    end

    private

    def commit
      @commit ||= project.commit(origin_sha || origin_ref)
    end

    def sha
      commit.try(:id)
    end

    def cancel_pending_pipelines
      Gitlab::OptimisticLocking.retry_lock(auto_cancelable_pipelines) do |cancelables|
        cancelables.find_each do |cancelable|
          cancelable.auto_cancel_running(pipeline)
        end
      end
    end

    # rubocop: disable CodeReuse/ActiveRecord
    def auto_cancelable_pipelines
      # TODO: Introduced by https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23464
      if Feature.enabled?(:ci_support_interruptible_pipelines, project, default_enabled: true)
        project.ci_pipelines
          .where(ref: pipeline.ref)
          .where.not(id: pipeline.id)
          .where.not(sha: project.commit(pipeline.ref).try(:id))
          .alive_or_scheduled
          .without_interruptible_builds
      else
        project.ci_pipelines
          .where(ref: pipeline.ref)
          .where.not(id: pipeline.id)
          .where.not(sha: project.commit(pipeline.ref).try(:id))
          .created_or_pending
      end
    end
    # rubocop: enable CodeReuse/ActiveRecord

    def pipeline_created_counter
      @pipeline_created_counter ||= Gitlab::Metrics
        .counter(:pipelines_created_total, "Counter of pipelines created")
    end

    def schedule_head_pipeline_update
      pipeline.all_merge_requests.opened.each do |merge_request|
        UpdateHeadPipelineForMergeRequestWorker.perform_async(merge_request.id)
      end
    end

    def extra_options(options = {})
      # In Ruby 2.4, even when options is empty, f(**options) doesn't work when f
      # doesn't have any parameters. We reproduce the Ruby 2.5 behavior by
      # checking explicitly that no arguments are given.
      raise ArgumentError if options.any?

      {} # overridden in EE
    end
  end
end