2020-08-24 11:10:11 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
module Gitlab
|
|
|
|
module Ci
|
|
|
|
class Lint
|
|
|
|
class Result
|
2020-10-05 08:08:47 -04:00
|
|
|
attr_reader :jobs, :merged_yaml, :errors, :warnings
|
2020-08-24 11:10:11 -04:00
|
|
|
|
2020-10-05 08:08:47 -04:00
|
|
|
def initialize(jobs:, merged_yaml:, errors:, warnings:)
|
2020-08-24 11:10:11 -04:00
|
|
|
@jobs = jobs
|
2020-10-05 08:08:47 -04:00
|
|
|
@merged_yaml = merged_yaml
|
2020-08-24 11:10:11 -04:00
|
|
|
@errors = errors
|
|
|
|
@warnings = warnings
|
|
|
|
end
|
|
|
|
|
|
|
|
def valid?
|
|
|
|
@errors.empty?
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-02-15 13:14:39 -05:00
|
|
|
LOG_MAX_DURATION_THRESHOLD = 2.seconds
|
|
|
|
|
2021-01-19 04:10:32 -05:00
|
|
|
def initialize(project:, current_user:, sha: nil)
|
2020-08-24 11:10:11 -04:00
|
|
|
@project = project
|
|
|
|
@current_user = current_user
|
2021-08-26 08:10:28 -04:00
|
|
|
@sha = sha || project&.repository&.commit&.sha
|
2020-08-24 11:10:11 -04:00
|
|
|
end
|
|
|
|
|
2022-02-14 07:14:02 -05:00
|
|
|
def validate(content, dry_run: false, ref: @project&.default_branch)
|
2020-11-12 10:09:09 -05:00
|
|
|
if dry_run
|
2022-02-14 07:14:02 -05:00
|
|
|
simulate_pipeline_creation(content, ref)
|
2020-08-24 11:10:11 -04:00
|
|
|
else
|
|
|
|
static_validation(content)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2022-02-14 07:14:02 -05:00
|
|
|
def simulate_pipeline_creation(content, ref)
|
2020-08-24 11:10:11 -04:00
|
|
|
pipeline = ::Ci::CreatePipelineService
|
2022-02-14 07:14:02 -05:00
|
|
|
.new(@project, @current_user, ref: ref)
|
2020-08-24 11:10:11 -04:00
|
|
|
.execute(:push, dry_run: true, content: content)
|
2021-07-20 08:10:29 -04:00
|
|
|
.payload
|
2020-08-24 11:10:11 -04:00
|
|
|
|
|
|
|
Result.new(
|
|
|
|
jobs: dry_run_convert_to_jobs(pipeline.stages),
|
2020-10-05 08:08:47 -04:00
|
|
|
merged_yaml: pipeline.merged_yaml,
|
2020-08-24 11:10:11 -04:00
|
|
|
errors: pipeline.error_messages.map(&:content),
|
2020-09-03 02:08:22 -04:00
|
|
|
warnings: pipeline.warning_messages(limit: ::Gitlab::Ci::Warnings::MAX_LIMIT).map(&:content)
|
2020-08-24 11:10:11 -04:00
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
def static_validation(content)
|
2022-02-15 13:14:39 -05:00
|
|
|
logger = build_logger
|
|
|
|
|
|
|
|
result = yaml_processor_result(content, logger)
|
2020-08-24 11:10:11 -04:00
|
|
|
|
|
|
|
Result.new(
|
2020-09-01 14:10:48 -04:00
|
|
|
jobs: static_validation_convert_to_jobs(result),
|
2020-10-05 08:08:47 -04:00
|
|
|
merged_yaml: result.merged_yaml,
|
2020-08-24 11:10:11 -04:00
|
|
|
errors: result.errors,
|
2020-09-03 02:08:22 -04:00
|
|
|
warnings: result.warnings.take(::Gitlab::Ci::Warnings::MAX_LIMIT) # rubocop: disable CodeReuse/ActiveRecord
|
2020-08-24 11:10:11 -04:00
|
|
|
)
|
2022-02-15 13:14:39 -05:00
|
|
|
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
|
2020-08-24 11:10:11 -04:00
|
|
|
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,
|
2020-09-03 11:08:29 -04:00
|
|
|
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,
|
2020-08-24 11:10:11 -04:00
|
|
|
environment: job.options.dig(:environment, :name),
|
|
|
|
when: job.when,
|
|
|
|
allow_failure: job.allow_failure
|
|
|
|
}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-09-01 14:10:48 -04:00
|
|
|
def static_validation_convert_to_jobs(result)
|
2020-08-24 11:10:11 -04:00
|
|
|
jobs = []
|
2020-09-01 14:10:48 -04:00
|
|
|
return jobs unless result.valid?
|
2020-08-24 11:10:11 -04:00
|
|
|
|
2020-09-01 14:10:48 -04:00
|
|
|
result.stages.each do |stage_name|
|
|
|
|
result.builds.each do |job|
|
2020-08-24 11:10:11 -04:00
|
|
|
next unless job[:stage] == stage_name
|
|
|
|
|
|
|
|
jobs << {
|
|
|
|
name: job[:name],
|
|
|
|
stage: stage_name,
|
2020-09-03 11:08:29 -04:00
|
|
|
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,
|
2020-08-24 11:10:11 -04:00
|
|
|
tag_list: job[:tag_list].to_a,
|
|
|
|
only: job[:only],
|
|
|
|
except: job[:except],
|
|
|
|
environment: job[:environment],
|
|
|
|
when: job[:when],
|
2021-01-13 07:10:27 -05:00
|
|
|
allow_failure: job[:allow_failure],
|
|
|
|
needs: job.dig(:needs_attributes)
|
2020-08-24 11:10:11 -04:00
|
|
|
}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
jobs
|
|
|
|
end
|
2022-02-15 13:14:39 -05:00
|
|
|
|
|
|
|
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
|
2020-08-24 11:10:11 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|