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 Gitlab
module Ci
class Lint
class Result
attr_reader :jobs, :merged_yaml, :errors, :warnings
def initialize(jobs:, merged_yaml:, errors:, warnings:)
@jobs = jobs
@merged_yaml = merged_yaml
@errors = errors
@warnings = warnings
end
def valid?
@errors.empty?
end
end
LOG_MAX_DURATION_THRESHOLD = 2.seconds
def initialize(project:, current_user:, sha: nil)
@project = project
@current_user = current_user
@sha = sha || project&.repository&.commit&.sha
end
def validate(content, dry_run: false, ref: @project&.default_branch)
if dry_run
simulate_pipeline_creation(content, ref)
else
static_validation(content)
end
end
private
def simulate_pipeline_creation(content, ref)
pipeline = ::Ci::CreatePipelineService
.new(@project, @current_user, ref: ref)
.execute(:push, dry_run: true, content: content)
.payload
Result.new(
jobs: dry_run_convert_to_jobs(pipeline.stages),
merged_yaml: pipeline.merged_yaml,
errors: pipeline.error_messages.map(&:content),
warnings: pipeline.warning_messages(limit: ::Gitlab::Ci::Warnings::MAX_LIMIT).map(&:content)
)
end
def static_validation(content)
logger = build_logger
result = yaml_processor_result(content, logger)
Result.new(
jobs: static_validation_convert_to_jobs(result),
merged_yaml: result.merged_yaml,
errors: result.errors,
warnings: result.warnings.take(::Gitlab::Ci::Warnings::MAX_LIMIT) # rubocop: disable CodeReuse/ActiveRecord
)
ensure
logger.commit(pipeline: ::Ci::Pipeline.new, caller: self.class.name)
end
def yaml_processor_result(content, logger)
logger.instrument(:yaml_process) do
Gitlab::Ci::YamlProcessor.new(content, project: @project,
user: @current_user,
sha: @sha,
logger: logger).execute
end
end
def dry_run_convert_to_jobs(stages)
stages.reduce([]) do |jobs, stage|
jobs + stage.statuses.map do |job|
{
name: job.name,
stage: stage.name,
before_script: job.options[:before_script].to_a,
script: job.options[:script].to_a,
after_script: job.options[:after_script].to_a,
tag_list: (job.tag_list if job.is_a?(::Ci::Build)).to_a,
environment: job.options.dig(:environment, :name),
when: job.when,
allow_failure: job.allow_failure
}
end
end
end
def static_validation_convert_to_jobs(result)
jobs = []
return jobs unless result.valid?
result.stages.each do |stage_name|
result.builds.each do |job|
next unless job[:stage] == stage_name
jobs << {
name: job[:name],
stage: stage_name,
before_script: job.dig(:options, :before_script).to_a,
script: job.dig(:options, :script).to_a,
after_script: job.dig(:options, :after_script).to_a,
tag_list: job[:tag_list].to_a,
only: job[:only],
except: job[:except],
environment: job[:environment],
when: job[:when],
allow_failure: job[:allow_failure],
needs: job.dig(:needs_attributes)
}
end
end
jobs
end
def build_logger
Gitlab::Ci::Pipeline::Logger.new(project: @project) do |l|
l.log_when do |observations|
values = observations['yaml_process_duration_s']
next false if values.empty?
values.max >= LOG_MAX_DURATION_THRESHOLD
end
end
end
end
end
end
|