Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaxim Rydkin <maks.rydkin@gmail.com>2017-09-06 14:23:24 +0300
committerMaxim Rydkin <maks.rydkin@gmail.com>2017-09-12 22:32:02 +0300
commitc45ace8972f18af1f232f9074d6e4104fc4f0c14 (patch)
tree0234b8da9c0eeafb67e1523b1d05d86ef33ca5c0 /lib/gitlab
parentc295d3362b610945de5c722616b39323a48c377e (diff)
move `lib/ci/gitlab_ci_yaml_processor.rb` into `lib/gitlab/ci/yaml_processor.rb`
Diffstat (limited to 'lib/gitlab')
-rw-r--r--lib/gitlab/ci/yaml_processor.rb253
1 files changed, 253 insertions, 0 deletions
diff --git a/lib/gitlab/ci/yaml_processor.rb b/lib/gitlab/ci/yaml_processor.rb
new file mode 100644
index 00000000000..7582964b24e
--- /dev/null
+++ b/lib/gitlab/ci/yaml_processor.rb
@@ -0,0 +1,253 @@
+module Gitlab
+ module Ci
+ class YamlProcessor
+ ValidationError = Class.new(StandardError)
+
+ include Gitlab::Ci::Config::Entry::LegacyValidationHelpers
+
+ attr_reader :path, :cache, :stages, :jobs
+
+ def initialize(config, path = nil)
+ @ci_config = Gitlab::Ci::Config.new(config)
+ @config = @ci_config.to_hash
+ @path = path
+
+ unless @ci_config.valid?
+ raise ValidationError, @ci_config.errors.first
+ end
+
+ initial_parsing
+ rescue Gitlab::Ci::Config::Loader::FormatError => e
+ raise ValidationError, e.message
+ end
+
+ def builds_for_stage_and_ref(stage, ref, tag = false, source = nil)
+ jobs_for_stage_and_ref(stage, ref, tag, source).map do |name, _|
+ build_attributes(name)
+ end
+ end
+
+ def builds
+ @jobs.map do |name, _|
+ build_attributes(name)
+ end
+ end
+
+ def stage_seeds(pipeline)
+ seeds = @stages.uniq.map do |stage|
+ builds = pipeline_stage_builds(stage, pipeline)
+
+ Gitlab::Ci::Stage::Seed.new(pipeline, stage, builds) if builds.any?
+ end
+
+ seeds.compact
+ end
+
+ def build_attributes(name)
+ job = @jobs[name.to_sym] || {}
+
+ { stage_idx: @stages.index(job[:stage]),
+ stage: job[:stage],
+ commands: job[:commands],
+ tag_list: job[:tags] || [],
+ name: job[:name].to_s,
+ allow_failure: job[:ignore],
+ when: job[:when] || 'on_success',
+ environment: job[:environment_name],
+ coverage_regex: job[:coverage],
+ yaml_variables: yaml_variables(name),
+ options: {
+ image: job[:image],
+ services: job[:services],
+ artifacts: job[:artifacts],
+ cache: job[:cache],
+ dependencies: job[:dependencies],
+ before_script: job[:before_script],
+ script: job[:script],
+ after_script: job[:after_script],
+ environment: job[:environment],
+ retry: job[:retry]
+ }.compact }
+ end
+
+ def self.validation_message(content)
+ return 'Please provide content of .gitlab-ci.yml' if content.blank?
+
+ begin
+ Gitlab::Ci::YamlProcessor.new(content)
+ nil
+ rescue ValidationError, Psych::SyntaxError => e
+ e.message
+ end
+ end
+
+ private
+
+ def pipeline_stage_builds(stage, pipeline)
+ builds = builds_for_stage_and_ref(
+ stage, pipeline.ref, pipeline.tag?, pipeline.source)
+
+ builds.select do |build|
+ job = @jobs[build.fetch(:name).to_sym]
+ has_kubernetes = pipeline.has_kubernetes_active?
+ only_kubernetes = job.dig(:only, :kubernetes)
+ except_kubernetes = job.dig(:except, :kubernetes)
+
+ [!only_kubernetes && !except_kubernetes,
+ only_kubernetes && has_kubernetes,
+ except_kubernetes && !has_kubernetes].any?
+ end
+ end
+
+ def jobs_for_ref(ref, tag = false, source = nil)
+ @jobs.select do |_, job|
+ process?(job.dig(:only, :refs), job.dig(:except, :refs), ref, tag, source)
+ end
+ end
+
+ def jobs_for_stage_and_ref(stage, ref, tag = false, source = nil)
+ jobs_for_ref(ref, tag, source).select do |_, job|
+ job[:stage] == stage
+ end
+ end
+
+ def initial_parsing
+ ##
+ # Global config
+ #
+ @before_script = @ci_config.before_script
+ @image = @ci_config.image
+ @after_script = @ci_config.after_script
+ @services = @ci_config.services
+ @variables = @ci_config.variables
+ @stages = @ci_config.stages
+ @cache = @ci_config.cache
+
+ ##
+ # Jobs
+ #
+ @jobs = @ci_config.jobs
+
+ @jobs.each do |name, job|
+ # logical validation for job
+
+ validate_job_stage!(name, job)
+ validate_job_dependencies!(name, job)
+ validate_job_environment!(name, job)
+ end
+ end
+
+ def yaml_variables(name)
+ variables = (@variables || {})
+ .merge(job_variables(name))
+
+ variables.map do |key, value|
+ { key: key.to_s, value: value, public: true }
+ end
+ end
+
+ def job_variables(name)
+ job = @jobs[name.to_sym]
+ return {} unless job
+
+ job[:variables] || {}
+ end
+
+ def validate_job_stage!(name, job)
+ return unless job[:stage]
+
+ unless job[:stage].is_a?(String) && job[:stage].in?(@stages)
+ raise ValidationError, "#{name} job: stage parameter should be #{@stages.join(", ")}"
+ end
+ end
+
+ def validate_job_dependencies!(name, job)
+ return unless job[:dependencies]
+
+ stage_index = @stages.index(job[:stage])
+
+ job[:dependencies].each do |dependency|
+ raise ValidationError, "#{name} job: undefined dependency: #{dependency}" unless @jobs[dependency.to_sym]
+
+ unless @stages.index(@jobs[dependency.to_sym][:stage]) < stage_index
+ raise ValidationError, "#{name} job: dependency #{dependency} is not defined in prior stages"
+ end
+ end
+ end
+
+ 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
+
+ def process?(only_params, except_params, ref, tag, source)
+ if only_params.present?
+ return false unless matching?(only_params, ref, tag, source)
+ end
+
+ if except_params.present?
+ return false if matching?(except_params, ref, tag, source)
+ end
+
+ true
+ end
+
+ def matching?(patterns, ref, tag, source)
+ patterns.any? do |pattern|
+ pattern, path = pattern.split('@', 2)
+ matches_path?(path) && matches_pattern?(pattern, ref, tag, source)
+ end
+ end
+
+ def matches_path?(path)
+ return true unless path
+
+ path == self.path
+ end
+
+ def matches_pattern?(pattern, ref, tag, source)
+ return true if tag && pattern == 'tags'
+ return true if !tag && pattern == 'branches'
+ return true if source_to_pattern(source) == pattern
+
+ if pattern.first == "/" && pattern.last == "/"
+ Regexp.new(pattern[1...-1]) =~ ref
+ else
+ pattern == ref
+ end
+ end
+
+ def source_to_pattern(source)
+ if %w[api external web].include?(source)
+ source
+ else
+ source&.pluralize
+ end
+ end
+ end
+ end
+end