summaryrefslogtreecommitdiff
path: root/app/services/ci/create_pipeline_service.rb
blob: 02f25a82307d6364178da2eb74069c24c1484e3e (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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# frozen_string_literal: true

module Ci
  class CreatePipelineService < BaseService
    attr_reader :pipeline, :logger

    CreateError = Class.new(StandardError)

    LOG_MAX_DURATION_THRESHOLD = 3.seconds
    LOG_MAX_PIPELINE_SIZE = 2_000
    LOG_MAX_CREATION_THRESHOLD = 20.seconds

    SEQUENCE = [Gitlab::Ci::Pipeline::Chain::Build,
                Gitlab::Ci::Pipeline::Chain::Build::Associations,
                Gitlab::Ci::Pipeline::Chain::Validate::Abilities,
                Gitlab::Ci::Pipeline::Chain::Validate::Repository,
                Gitlab::Ci::Pipeline::Chain::Limit::RateLimit,
                Gitlab::Ci::Pipeline::Chain::Validate::SecurityOrchestrationPolicy,
                Gitlab::Ci::Pipeline::Chain::Skip,
                Gitlab::Ci::Pipeline::Chain::Config::Content,
                Gitlab::Ci::Pipeline::Chain::Config::Process,
                Gitlab::Ci::Pipeline::Chain::Validate::AfterConfig,
                Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs,
                Gitlab::Ci::Pipeline::Chain::SeedBlock,
                Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules,
                Gitlab::Ci::Pipeline::Chain::Seed,
                Gitlab::Ci::Pipeline::Chain::Limit::Size,
                Gitlab::Ci::Pipeline::Chain::Limit::Deployments,
                Gitlab::Ci::Pipeline::Chain::Validate::External,
                Gitlab::Ci::Pipeline::Chain::Populate,
                Gitlab::Ci::Pipeline::Chain::StopDryRun,
                Gitlab::Ci::Pipeline::Chain::EnsureEnvironments,
                Gitlab::Ci::Pipeline::Chain::EnsureResourceGroups,
                Gitlab::Ci::Pipeline::Chain::Create,
                Gitlab::Ci::Pipeline::Chain::CreateDeployments,
                Gitlab::Ci::Pipeline::Chain::CreateCrossDatabaseAssociations,
                Gitlab::Ci::Pipeline::Chain::Limit::Activity,
                Gitlab::Ci::Pipeline::Chain::Limit::JobActivity,
                Gitlab::Ci::Pipeline::Chain::CancelPendingPipelines,
                Gitlab::Ci::Pipeline::Chain::Metrics,
                Gitlab::Ci::Pipeline::Chain::TemplateUsage,
                Gitlab::Ci::Pipeline::Chain::Pipeline::Process].freeze

    # Create a new pipeline in the specified project.
    #
    # @param [Symbol] source                             What event (Ci::Pipeline.sources) triggers the pipeline
    #                                                    creation.
    # @param [Boolean] ignore_skip_ci                    Whether skipping a pipeline creation when `[skip ci]` comment
    #                                                    is present in the commit body
    # @param [Boolean] save_on_errors                    Whether persisting an invalid pipeline when it encounters an
    #                                                    error during creation (e.g. invalid yaml)
    # @param [Ci::TriggerRequest] trigger_request        The pipeline trigger triggers the pipeline creation.
    # @param [Ci::PipelineSchedule] schedule             The pipeline schedule triggers the pipeline creation.
    # @param [MergeRequest] merge_request                The merge request triggers the pipeline creation.
    # @param [ExternalPullRequest] external_pull_request The external pull request triggers the pipeline creation.
    # @param [Ci::Bridge] bridge                         The bridge job that triggers the downstream pipeline creation.
    # @param [String] content                            The content of .gitlab-ci.yml to override the default config
    #                                                    contents (e.g. .gitlab-ci.yml in repostiry). Mainly used for
    #                                                    generating a dangling pipeline.
    #
    # @return [Ci::Pipeline]                             The created Ci::Pipeline object.
    # 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, bridge: nil, **options, &block)
      @logger = build_logger
      @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],
        bridge: bridge,
        logger: @logger,
        **extra_options(**options))

      # Ensure we never persist the pipeline when dry_run: true
      @pipeline.readonly! if command.dry_run?

      Gitlab::Ci::Pipeline::Chain::Sequence
        .new(pipeline, command, SEQUENCE)
        .build!

      if pipeline.persisted?
        Gitlab::EventStore.publish(
          Ci::PipelineCreatedEvent.new(data: { pipeline_id: pipeline.id })
        )

        create_namespace_onboarding_action
      else
        # If pipeline is not persisted, try to recover IID
        pipeline.reset_project_iid
      end

      if error_message = pipeline.full_error_messages.presence || pipeline.failure_reason.presence
        ServiceResponse.error(message: error_message, payload: pipeline)
      else
        ServiceResponse.success(payload: pipeline)
      end

    ensure
      @logger.commit(pipeline: pipeline, caller: self.class.name)
    end
    # rubocop: enable Metrics/ParameterLists

    def execute!(*args, &block)
      source = args[0]
      params = Hash(args[1])

      execute(source, **params, &block).tap do |response|
        unless response.payload.persisted?
          raise CreateError, pipeline.full_error_messages
        end
      end
    end

    private

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

    def sha
      commit.try(:id)
    end

    def create_namespace_onboarding_action
      Namespaces::OnboardingPipelineCreatedWorker.perform_async(project.namespace_id)
    end

    def extra_options(content: nil, dry_run: false)
      { content: content, dry_run: dry_run }
    end

    def build_logger
      Gitlab::Ci::Pipeline::Logger.new(project: project) do |l|
        l.log_when do |observations|
          observations.any? do |name, values|
            values.any? &&
            name.to_s.end_with?('duration_s') &&
            values.max >= LOG_MAX_DURATION_THRESHOLD
          end
        end

        l.log_when do |observations|
          values = observations['pipeline_size_count']
          next false if values.empty?

          values.max >= LOG_MAX_PIPELINE_SIZE
        end

        l.log_when do |observations|
          values = observations['pipeline_creation_duration_s']
          next false if values.empty?

          values.max >= LOG_MAX_CREATION_THRESHOLD
        end
      end
    end
  end
end

Ci::CreatePipelineService.prepend_mod_with('Ci::CreatePipelineService')