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')
|