2015-08-26 01:42:46 +00:00
|
|
|
module Ci
|
|
|
|
class GitlabCiYamlProcessor
|
2017-03-01 11:00:37 +00:00
|
|
|
ValidationError = Class.new(StandardError)
|
2015-08-26 01:42:46 +00:00
|
|
|
|
2016-11-14 09:31:45 +00:00
|
|
|
include Gitlab::Ci::Config::Entry::LegacyValidationHelpers
|
2016-06-06 09:05:15 +00:00
|
|
|
|
2016-09-20 13:19:55 +00:00
|
|
|
attr_reader :path, :cache, :stages, :jobs
|
2015-08-26 01:42:46 +00:00
|
|
|
|
2015-11-02 13:44:06 +00:00
|
|
|
def initialize(config, path = nil)
|
2016-06-06 10:23:27 +00:00
|
|
|
@ci_config = Gitlab::Ci::Config.new(config)
|
2016-07-20 08:59:49 +00:00
|
|
|
@config = @ci_config.to_hash
|
|
|
|
@path = path
|
2015-08-26 01:42:46 +00:00
|
|
|
|
2016-06-21 10:10:13 +00:00
|
|
|
unless @ci_config.valid?
|
|
|
|
raise ValidationError, @ci_config.errors.first
|
|
|
|
end
|
2015-08-26 01:42:46 +00:00
|
|
|
|
2016-06-21 10:10:13 +00:00
|
|
|
initial_parsing
|
2016-06-07 08:26:38 +00:00
|
|
|
rescue Gitlab::Ci::Config::Loader::FormatError => e
|
2016-06-03 12:20:34 +00:00
|
|
|
raise ValidationError, e.message
|
2015-08-26 01:42:46 +00:00
|
|
|
end
|
|
|
|
|
2017-06-01 19:55:57 +00:00
|
|
|
def jobs_for_ref(ref, tag = false, source = nil)
|
2016-07-16 18:10:22 +00:00
|
|
|
@jobs.select do |_, job|
|
2017-06-01 19:55:57 +00:00
|
|
|
process?(job[:only], job[:except], ref, tag, source)
|
2016-06-02 11:52:19 +00:00
|
|
|
end
|
2015-08-26 01:42:46 +00:00
|
|
|
end
|
|
|
|
|
2017-06-01 19:55:57 +00:00
|
|
|
def jobs_for_stage_and_ref(stage, ref, tag = false, source = nil)
|
|
|
|
jobs_for_ref(ref, tag, source).select do |_, job|
|
2016-07-16 18:10:22 +00:00
|
|
|
job[:stage] == stage
|
2015-08-26 01:42:46 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-06-01 19:55:57 +00:00
|
|
|
def builds_for_ref(ref, tag = false, source = nil)
|
|
|
|
jobs_for_ref(ref, tag, source).map do |name, _|
|
2016-07-19 12:52:12 +00:00
|
|
|
build_attributes(name)
|
2016-07-16 18:10:22 +00:00
|
|
|
end
|
2016-04-15 10:18:46 +00:00
|
|
|
end
|
|
|
|
|
2017-06-01 19:55:57 +00:00
|
|
|
def builds_for_stage_and_ref(stage, ref, tag = false, source = nil)
|
|
|
|
jobs_for_stage_and_ref(stage, ref, tag, source).map do |name, _|
|
2016-07-19 11:23:14 +00:00
|
|
|
build_attributes(name)
|
2016-07-16 18:10:22 +00:00
|
|
|
end
|
|
|
|
end
|
2016-04-18 10:41:13 +00:00
|
|
|
|
2016-07-16 18:10:22 +00:00
|
|
|
def builds
|
2016-07-19 11:23:14 +00:00
|
|
|
@jobs.map do |name, _|
|
|
|
|
build_attributes(name)
|
2016-07-16 18:10:22 +00:00
|
|
|
end
|
2016-04-15 10:18:46 +00:00
|
|
|
end
|
|
|
|
|
2017-06-02 10:16:11 +00:00
|
|
|
def stage_seeds(pipeline)
|
|
|
|
seeds = @stages.uniq.map do |stage|
|
|
|
|
builds = builds_for_stage_and_ref(
|
2017-06-07 13:45:24 +00:00
|
|
|
stage, pipeline.ref, pipeline.tag?, pipeline.source)
|
2017-06-02 10:16:11 +00:00
|
|
|
|
|
|
|
Gitlab::Ci::Stage::Seed.new(pipeline, stage, builds) if builds.any?
|
2017-05-30 10:48:05 +00:00
|
|
|
end
|
2017-06-02 10:16:11 +00:00
|
|
|
|
|
|
|
seeds.compact
|
2017-05-30 10:48:05 +00:00
|
|
|
end
|
|
|
|
|
2016-07-19 11:23:14 +00:00
|
|
|
def build_attributes(name)
|
|
|
|
job = @jobs[name.to_sym] || {}
|
2017-05-30 10:48:05 +00:00
|
|
|
|
|
|
|
{ stage_idx: @stages.index(job[:stage]),
|
2016-07-19 11:23:14 +00:00
|
|
|
stage: job[:stage],
|
2016-08-29 11:11:35 +00:00
|
|
|
commands: job[:commands],
|
2016-07-19 11:23:14 +00:00
|
|
|
tag_list: job[:tags] || [],
|
2016-08-11 13:22:35 +00:00
|
|
|
name: job[:name].to_s,
|
2017-03-06 11:01:33 +00:00
|
|
|
allow_failure: job[:ignore],
|
2016-07-19 11:23:14 +00:00
|
|
|
when: job[:when] || 'on_success',
|
2016-09-16 09:25:37 +00:00
|
|
|
environment: job[:environment_name],
|
2016-11-26 03:02:08 +00:00
|
|
|
coverage_regex: job[:coverage],
|
2016-07-19 11:23:14 +00:00
|
|
|
yaml_variables: yaml_variables(name),
|
|
|
|
options: {
|
2016-08-29 11:10:57 +00:00
|
|
|
image: job[:image],
|
|
|
|
services: job[:services],
|
2016-07-19 11:23:14 +00:00
|
|
|
artifacts: job[:artifacts],
|
2016-08-29 11:10:57 +00:00
|
|
|
cache: job[:cache],
|
2016-07-19 11:23:14 +00:00
|
|
|
dependencies: job[:dependencies],
|
2016-08-29 11:10:57 +00:00
|
|
|
after_script: job[:after_script],
|
2017-05-03 11:22:03 +00:00
|
|
|
environment: job[:environment]
|
2017-05-30 13:30:45 +00:00
|
|
|
}.compact }
|
2016-04-15 10:18:46 +00:00
|
|
|
end
|
|
|
|
|
2016-08-29 13:07:19 +00:00
|
|
|
def self.validation_message(content)
|
2016-08-30 11:03:29 +00:00
|
|
|
return 'Please provide content of .gitlab-ci.yml' if content.blank?
|
2016-08-30 13:38:36 +00:00
|
|
|
|
2016-08-30 11:03:29 +00:00
|
|
|
begin
|
|
|
|
Ci::GitlabCiYamlProcessor.new(content)
|
|
|
|
nil
|
|
|
|
rescue ValidationError, Psych::SyntaxError => e
|
|
|
|
e.message
|
2016-08-25 11:40:35 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-08-26 01:42:46 +00:00
|
|
|
private
|
|
|
|
|
|
|
|
def initial_parsing
|
2016-07-09 15:06:53 +00:00
|
|
|
##
|
|
|
|
# Global config
|
|
|
|
#
|
2016-06-21 10:10:13 +00:00
|
|
|
@before_script = @ci_config.before_script
|
|
|
|
@image = @ci_config.image
|
2016-06-21 11:02:14 +00:00
|
|
|
@after_script = @ci_config.after_script
|
2016-06-21 10:40:52 +00:00
|
|
|
@services = @ci_config.services
|
2016-06-22 09:22:53 +00:00
|
|
|
@variables = @ci_config.variables
|
2016-06-23 11:51:07 +00:00
|
|
|
@stages = @ci_config.stages
|
2016-06-29 07:49:46 +00:00
|
|
|
@cache = @ci_config.cache
|
2016-06-21 11:02:14 +00:00
|
|
|
|
2016-07-09 15:06:53 +00:00
|
|
|
##
|
|
|
|
# Jobs
|
|
|
|
#
|
|
|
|
@jobs = @ci_config.jobs
|
2016-07-06 12:24:31 +00:00
|
|
|
|
|
|
|
@jobs.each do |name, job|
|
2016-07-20 12:15:18 +00:00
|
|
|
# logical validation for job
|
|
|
|
|
|
|
|
validate_job_stage!(name, job)
|
|
|
|
validate_job_dependencies!(name, job)
|
2016-10-18 10:22:51 +00:00
|
|
|
validate_job_environment!(name, job)
|
2016-07-06 12:24:31 +00:00
|
|
|
end
|
2015-08-26 01:42:46 +00:00
|
|
|
end
|
|
|
|
|
2016-07-16 18:10:22 +00:00
|
|
|
def yaml_variables(name)
|
2016-07-20 12:15:18 +00:00
|
|
|
variables = (@variables || {})
|
|
|
|
.merge(job_variables(name))
|
|
|
|
|
2016-07-16 18:10:22 +00:00
|
|
|
variables.map do |key, value|
|
2016-12-19 13:15:47 +00:00
|
|
|
{ key: key.to_s, value: value, public: true }
|
2016-07-16 18:10:22 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def job_variables(name)
|
|
|
|
job = @jobs[name.to_sym]
|
|
|
|
return {} unless job
|
|
|
|
|
|
|
|
job[:variables] || {}
|
2015-08-26 01:42:46 +00:00
|
|
|
end
|
|
|
|
|
2016-07-14 13:23:52 +00:00
|
|
|
def validate_job_stage!(name, job)
|
2016-07-20 12:15:18 +00:00
|
|
|
return unless job[:stage]
|
|
|
|
|
2016-07-14 13:23:52 +00:00
|
|
|
unless job[:stage].is_a?(String) && job[:stage].in?(@stages)
|
|
|
|
raise ValidationError, "#{name} job: stage parameter should be #{@stages.join(", ")}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-03-11 13:15:13 +00:00
|
|
|
def validate_job_dependencies!(name, job)
|
2016-07-20 12:15:18 +00:00
|
|
|
return unless job[:dependencies]
|
2016-03-11 13:15:13 +00:00
|
|
|
|
2016-06-23 11:51:07 +00:00
|
|
|
stage_index = @stages.index(job[:stage])
|
2016-03-11 13:15:13 +00:00
|
|
|
|
|
|
|
job[:dependencies].each do |dependency|
|
2016-03-22 14:25:53 +00:00
|
|
|
raise ValidationError, "#{name} job: undefined dependency: #{dependency}" unless @jobs[dependency.to_sym]
|
2016-03-11 13:15:13 +00:00
|
|
|
|
2016-06-23 11:51:07 +00:00
|
|
|
unless @stages.index(@jobs[dependency.to_sym][:stage]) < stage_index
|
2016-03-11 13:15:13 +00:00
|
|
|
raise ValidationError, "#{name} job: dependency #{dependency} is not defined in prior stages"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-10-18 10:22:51 +00:00
|
|
|
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
|
|
|
|
raise ValidationError, "#{name} job: on_stop job #{on_stop} is not defined"
|
|
|
|
end
|
|
|
|
|
|
|
|
unless on_stop_job[:environment]
|
|
|
|
raise ValidationError, "#{name} job: on_stop job #{on_stop} does not have environment defined"
|
|
|
|
end
|
|
|
|
|
|
|
|
unless on_stop_job[:environment][:name] == environment[:name]
|
|
|
|
raise ValidationError, "#{name} job: on_stop job #{on_stop} have different environment name"
|
|
|
|
end
|
|
|
|
|
|
|
|
unless on_stop_job[:environment][:action] == 'stop'
|
|
|
|
raise ValidationError, "#{name} job: on_stop job #{on_stop} needs to have action stop defined"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-06-01 19:55:57 +00:00
|
|
|
def process?(only_params, except_params, ref, tag, source)
|
2015-11-02 13:44:06 +00:00
|
|
|
if only_params.present?
|
2017-06-01 19:55:57 +00:00
|
|
|
return false unless matching?(only_params, ref, tag, source)
|
2015-11-02 13:44:06 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
if except_params.present?
|
2017-06-01 19:55:57 +00:00
|
|
|
return false if matching?(except_params, ref, tag, source)
|
2015-11-02 13:44:06 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
2017-06-01 19:55:57 +00:00
|
|
|
def matching?(patterns, ref, tag, source)
|
2015-11-02 13:44:06 +00:00
|
|
|
patterns.any? do |pattern|
|
2017-06-05 15:15:15 +00:00
|
|
|
pattern, path = pattern.split('@', 2)
|
2017-06-07 13:24:11 +00:00
|
|
|
matches_path?(path) && matches_pattern?(pattern, ref, tag, source)
|
2015-11-02 13:44:06 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-06-07 13:24:11 +00:00
|
|
|
def matches_path?(path)
|
|
|
|
return true unless path
|
2017-06-07 13:26:04 +00:00
|
|
|
|
2017-06-07 13:24:11 +00:00
|
|
|
path == self.path
|
2017-06-05 15:15:15 +00:00
|
|
|
end
|
|
|
|
|
2017-06-07 13:14:36 +00:00
|
|
|
def matches_pattern?(pattern, ref, tag, source)
|
2015-11-02 13:44:06 +00:00
|
|
|
return true if tag && pattern == 'tags'
|
|
|
|
return true if !tag && pattern == 'branches'
|
2017-06-05 15:15:15 +00:00
|
|
|
return true if source_to_pattern(source) == pattern
|
2015-11-02 13:44:06 +00:00
|
|
|
|
|
|
|
if pattern.first == "/" && pattern.last == "/"
|
|
|
|
Regexp.new(pattern[1...-1]) =~ ref
|
|
|
|
else
|
|
|
|
pattern == ref
|
|
|
|
end
|
|
|
|
end
|
2017-06-02 17:02:56 +00:00
|
|
|
|
|
|
|
def source_to_pattern(source)
|
2017-06-07 13:59:06 +00:00
|
|
|
if %w[api external web].include?(source)
|
2017-06-07 13:14:36 +00:00
|
|
|
source
|
|
|
|
else
|
2017-06-07 13:26:04 +00:00
|
|
|
source&.pluralize
|
2017-06-07 13:14:36 +00:00
|
|
|
end
|
2017-06-02 17:02:56 +00:00
|
|
|
end
|
2015-08-26 01:42:46 +00:00
|
|
|
end
|
2015-12-09 20:58:53 +00:00
|
|
|
end
|