debian-mirror-gitlab/lib/gitlab/ci/yaml_processor.rb

155 lines
4.8 KiB
Ruby
Raw Normal View History

2018-12-13 13:39:08 +05:30
# frozen_string_literal: true
2020-11-24 15:15:51 +05:30
# This is the CI Linter component that runs the syntax validations
# while parsing the YAML config into a data structure that is
# then presented to the caller as result object.
# After syntax validations (done by Ci::Config), this component also
# runs logical validation on the built data structure.
2018-03-17 18:26:18 +05:30
module Gitlab
module Ci
class YamlProcessor
2020-11-24 15:15:51 +05:30
ValidationError = Class.new(StandardError)
2018-03-17 18:26:18 +05:30
2020-11-24 15:15:51 +05:30
def initialize(config_content, opts = {})
@config_content = config_content
@opts = opts
end
2020-07-28 23:09:34 +05:30
2020-11-24 15:15:51 +05:30
def execute
if @config_content.blank?
return Result.new(errors: ['Please provide content of .gitlab-ci.yml'])
2020-01-01 13:55:28 +05:30
end
2020-11-24 15:15:51 +05:30
@ci_config = Gitlab::Ci::Config.new(@config_content, **@opts)
2018-03-17 18:26:18 +05:30
unless @ci_config.valid?
2020-11-24 15:15:51 +05:30
return Result.new(ci_config: @ci_config, errors: @ci_config.errors, warnings: @ci_config.warnings)
2018-03-17 18:26:18 +05:30
end
2020-11-24 15:15:51 +05:30
run_logical_validations!
2020-01-01 13:55:28 +05:30
2020-11-24 15:15:51 +05:30
Result.new(ci_config: @ci_config, warnings: @ci_config&.warnings)
2020-07-28 23:09:34 +05:30
rescue Gitlab::Ci::Config::ConfigError => e
2020-11-24 15:15:51 +05:30
Result.new(ci_config: @ci_config, errors: [e.message], warnings: @ci_config&.warnings)
rescue ValidationError => e
Result.new(ci_config: @ci_config, errors: [e.message], warnings: @ci_config&.warnings)
2018-03-17 18:26:18 +05:30
end
private
2020-11-24 15:15:51 +05:30
def run_logical_validations!
2018-03-17 18:26:18 +05:30
@stages = @ci_config.stages
2020-11-24 15:15:51 +05:30
@jobs = @ci_config.normalized_jobs
2018-03-17 18:26:18 +05:30
@jobs.each do |name, job|
2020-11-24 15:15:51 +05:30
validate_job!(name, job)
2018-03-17 18:26:18 +05:30
end
2021-09-30 23:02:18 +05:30
2021-11-11 11:23:49 +05:30
YamlProcessor::Dag.check_circular_dependencies!(@jobs)
2018-03-17 18:26:18 +05:30
end
2020-11-24 15:15:51 +05:30
def validate_job!(name, job)
validate_job_stage!(name, job)
validate_job_dependencies!(name, job)
validate_job_needs!(name, job)
validate_dynamic_child_pipeline_dependencies!(name, job)
validate_job_environment!(name, job)
2018-03-17 18:26:18 +05:30
end
def validate_job_stage!(name, job)
return unless job[:stage]
unless job[:stage].is_a?(String) && job[:stage].in?(@stages)
2020-07-28 23:09:34 +05:30
error!("#{name} job: chosen stage does not exist; available stages are #{@stages.join(", ")}")
2018-03-17 18:26:18 +05:30
end
end
def validate_job_dependencies!(name, job)
return unless job[:dependencies]
job[:dependencies].each do |dependency|
2020-04-22 19:07:51 +05:30
validate_job_dependency!(name, dependency)
end
end
2018-03-17 18:26:18 +05:30
2020-04-22 19:07:51 +05:30
def validate_dynamic_child_pipeline_dependencies!(name, job)
return unless includes = job.dig(:trigger, :include)
2019-10-12 21:52:04 +05:30
2020-04-22 19:07:51 +05:30
Array(includes).each do |included|
next unless included.is_a?(Hash)
next unless dependency = included[:job]
validate_job_dependency!(name, dependency)
2018-03-17 18:26:18 +05:30
end
end
2019-10-12 21:52:04 +05:30
def validate_job_needs!(name, job)
2020-04-22 19:07:51 +05:30
return unless needs = job.dig(:needs, :job)
2019-10-12 21:52:04 +05:30
2022-03-02 08:16:31 +05:30
validate_duplicate_needs!(name, needs)
2020-04-22 19:07:51 +05:30
needs.each do |need|
validate_job_dependency!(name, need[:name], 'need')
end
end
2019-12-26 22:10:19 +05:30
2022-03-02 08:16:31 +05:30
def validate_duplicate_needs!(name, needs)
unless needs.uniq == needs
error!("#{name} has duplicate entries in the needs section.")
end
end
2020-04-22 19:07:51 +05:30
def validate_job_dependency!(name, dependency, dependency_type = 'dependency')
unless @jobs[dependency.to_sym]
2020-07-28 23:09:34 +05:30
error!("#{name} job: undefined #{dependency_type}: #{dependency}")
2020-04-22 19:07:51 +05:30
end
2019-10-12 21:52:04 +05:30
2020-04-22 19:07:51 +05:30
job_stage_index = stage_index(name)
dependency_stage_index = stage_index(dependency)
2019-10-12 21:52:04 +05:30
2021-11-11 11:23:49 +05:30
unless dependency_stage_index.present? && dependency_stage_index <= job_stage_index
error!("#{name} job: #{dependency_type} #{dependency} is not defined in current or prior stages")
2019-10-12 21:52:04 +05:30
end
end
2020-04-22 19:07:51 +05:30
def stage_index(name)
stage = @jobs.dig(name.to_sym, :stage)
@stages.index(stage)
end
2018-03-17 18:26:18 +05:30
def validate_job_environment!(name, job)
return unless job[:environment]
return unless job[:environment].is_a?(Hash)
environment = job[:environment]
validate_on_stop_job!(name, environment, environment[:on_stop])
end
def validate_on_stop_job!(name, environment, on_stop)
return unless on_stop
on_stop_job = @jobs[on_stop.to_sym]
unless on_stop_job
2020-07-28 23:09:34 +05:30
error!("#{name} job: on_stop job #{on_stop} is not defined")
2018-03-17 18:26:18 +05:30
end
unless on_stop_job[:environment]
2020-07-28 23:09:34 +05:30
error!("#{name} job: on_stop job #{on_stop} does not have environment defined")
2018-03-17 18:26:18 +05:30
end
unless on_stop_job[:environment][:name] == environment[:name]
2020-07-28 23:09:34 +05:30
error!("#{name} job: on_stop job #{on_stop} have different environment name")
2018-03-17 18:26:18 +05:30
end
unless on_stop_job[:environment][:action] == 'stop'
2020-07-28 23:09:34 +05:30
error!("#{name} job: on_stop job #{on_stop} needs to have action stop defined")
2018-03-17 18:26:18 +05:30
end
end
2020-11-24 15:15:51 +05:30
def error!(message)
2021-06-08 01:23:25 +05:30
raise ValidationError, message
2020-11-24 15:15:51 +05:30
end
2018-03-17 18:26:18 +05:30
end
end
end