gitlab_ci_yaml_processor.rb 5.3 KB
Newer Older
D
Douwe Maan 已提交
1 2
module Ci
  class GitlabCiYamlProcessor
3
    class ValidationError < StandardError; end
D
Douwe Maan 已提交
4

5
    include Gitlab::Ci::Config::Node::LegacyValidationHelpers
6

7
    attr_reader :path, :cache, :stages
D
Douwe Maan 已提交
8

9
    def initialize(config, path = nil)
10
      @ci_config = Gitlab::Ci::Config.new(config)
11
      @config, @path = @ci_config.to_hash, path
D
Douwe Maan 已提交
12

13 14 15
      unless @ci_config.valid?
        raise ValidationError, @ci_config.errors.first
      end
D
Douwe Maan 已提交
16

17
      initial_parsing
18
    rescue Gitlab::Ci::Config::Loader::FormatError => e
19
      raise ValidationError, e.message
D
Douwe Maan 已提交
20 21
    end

22 23 24
    def jobs_for_ref(ref, tag = false, trigger_request = nil)
      @jobs.select do |_, job|
        process?(job[:only], job[:except], ref, tag, trigger_request)
25
      end
D
Douwe Maan 已提交
26 27
    end

28 29 30
    def jobs_for_stage_and_ref(stage, ref, tag = false, trigger_request = nil)
      jobs_for_ref(ref, tag, trigger_request).select do |_, job|
        job[:stage] == stage
D
Douwe Maan 已提交
31 32 33
      end
    end

34
    def builds_for_ref(ref, tag = false, trigger_request = nil)
K
Kamil Trzcinski 已提交
35
      jobs_for_ref(ref, tag, trigger_request).map do |name, _|
K
Kamil Trzcinski 已提交
36
        build_attributes(name)
37
      end
38 39
    end

40
    def builds_for_stage_and_ref(stage, ref, tag = false, trigger_request = nil)
41 42
      jobs_for_stage_and_ref(stage, ref, tag, trigger_request).map do |name, _|
        build_attributes(name)
43 44
      end
    end
G
Grzegorz Bizon 已提交
45

46
    def builds
47 48
      @jobs.map do |name, _|
        build_attributes(name)
49
      end
50 51
    end

52 53 54 55 56 57 58 59 60 61 62 63
    def build_attributes(name)
      job = @jobs[name.to_sym] || {}
      {
        stage_idx: @stages.index(job[:stage]),
        stage: job[:stage],
        ##
        # Refactoring note:
        #  - before script behaves differently than after script
        #  - after script returns an array of commands
        #  - before script should be a concatenated command
        commands: [job[:before_script] || @before_script, job[:script]].flatten.compact.join("\n"),
        tag_list: job[:tags] || [],
64
        name: job[:name],
65 66 67 68 69 70 71 72 73 74 75 76 77
        allow_failure: job[:allow_failure] || false,
        when: job[:when] || 'on_success',
        environment: job[:environment],
        yaml_variables: yaml_variables(name),
        options: {
          image: job[:image] || @image,
          services: job[:services] || @services,
          artifacts: job[:artifacts],
          cache: job[:cache] || @cache,
          dependencies: job[:dependencies],
          after_script: job[:after_script] || @after_script,
        }.compact
      }
78 79
    end

D
Douwe Maan 已提交
80 81 82
    private

    def initial_parsing
83 84 85
      ##
      # Global config
      #
86 87
      @before_script = @ci_config.before_script
      @image = @ci_config.image
88
      @after_script = @ci_config.after_script
89
      @services = @ci_config.services
90
      @variables = @ci_config.variables
91
      @stages = @ci_config.stages
92
      @cache = @ci_config.cache
93

94 95 96 97
      ##
      # Jobs
      #
      @jobs = @ci_config.jobs
98 99 100 101

      @jobs.each do |name, job|
        validate_job!(name, job)
      end
D
Douwe Maan 已提交
102 103
    end

104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
    def yaml_variables(name)
      variables = global_variables.merge(job_variables(name))
      variables.map do |key, value|
        { key: key, value: value, public: true }
      end
    end

    def global_variables
      @variables || {}
    end

    def job_variables(name)
      job = @jobs[name.to_sym]
      return {} unless job

      job[:variables] || {}
D
Douwe Maan 已提交
120 121 122
    end

    def validate_job!(name, job)
123 124
      raise ValidationError, "Unknown parameter: #{name}" unless job.is_a?(Hash) && job.has_key?(:script)

125
      validate_job_stage!(name, job) if job[:stage]
126
      validate_job_dependencies!(name, job) if job[:dependencies]
127 128
    end

129 130 131 132 133 134
    def validate_job_stage!(name, job)
      unless job[:stage].is_a?(String) && job[:stage].in?(@stages)
        raise ValidationError, "#{name} job: stage parameter should be #{@stages.join(", ")}"
      end
    end

135
    def validate_job_dependencies!(name, job)
136
      unless validate_array_of_strings(job[:dependencies])
137 138 139
        raise ValidationError, "#{name} job: dependencies parameter should be an array of strings"
      end

140
      stage_index = @stages.index(job[:stage])
141 142

      job[:dependencies].each do |dependency|
143
        raise ValidationError, "#{name} job: undefined dependency: #{dependency}" unless @jobs[dependency.to_sym]
144

145
        unless @stages.index(@jobs[dependency.to_sym][:stage]) < stage_index
146 147 148 149 150
          raise ValidationError, "#{name} job: dependency #{dependency} is not defined in prior stages"
        end
      end
    end

151
    def process?(only_params, except_params, ref, tag, trigger_request)
152
      if only_params.present?
153
        return false unless matching?(only_params, ref, tag, trigger_request)
154 155 156
      end

      if except_params.present?
157
        return false if matching?(except_params, ref, tag, trigger_request)
158 159 160 161 162
      end

      true
    end

163
    def matching?(patterns, ref, tag, trigger_request)
164
      patterns.any? do |pattern|
165
        match_ref?(pattern, ref, tag, trigger_request)
166 167 168
      end
    end

J
Jason Roehm 已提交
169
    def match_ref?(pattern, ref, tag, trigger_request)
170 171 172 173
      pattern, path = pattern.split('@', 2)
      return false if path && path != self.path
      return true if tag && pattern == 'tags'
      return true if !tag && pattern == 'branches'
J
Jason Roehm 已提交
174
      return true if trigger_request.present? && pattern == 'triggers'
175 176 177 178 179 180 181

      if pattern.first == "/" && pattern.last == "/"
        Regexp.new(pattern[1...-1]) =~ ref
      else
        pattern == ref
      end
    end
D
Douwe Maan 已提交
182
  end
183
end