gitlab_ci_yaml_processor.rb 6.4 KB
Newer Older
D
Douwe Maan 已提交
1 2
module Ci
  class GitlabCiYamlProcessor
3
    ValidationError = Class.new(StandardError)
D
Douwe Maan 已提交
4

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

K
Katarzyna Kobierska 已提交
7
    attr_reader :path, :cache, :stages, :jobs
D
Douwe Maan 已提交
8

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

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

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

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

29 30 31
    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 已提交
32 33 34
      end
    end

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

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

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

53 54 55 56 57 58 59 60 61 62
    def stages_for_ref(ref, tag = false, trigger_request = nil)
      stages = @stages.map do |stage|
        builds = builds_for_stage_and_ref(stage, ref, tag, trigger_request)

        { name: stage, builds_attributes: builds.to_a } if builds.any?
      end

      stages.compact.sort_by { |stage| @stages.index(stage[:name]) }
    end

63 64
    def build_attributes(name)
      job = @jobs[name.to_sym] || {}
65 66

      { stage_idx: @stages.index(job[:stage]),
67
        stage: job[:stage],
68
        commands: job[:commands],
69
        tag_list: job[:tags] || [],
70
        name: job[:name].to_s,
71
        allow_failure: job[:ignore],
72
        when: job[:when] || 'on_success',
73
        environment: job[:environment_name],
74
        coverage_regex: job[:coverage],
75 76
        yaml_variables: yaml_variables(name),
        options: {
77 78
          image: job[:image],
          services: job[:services],
79
          artifacts: job[:artifacts],
80
          cache: job[:cache],
81
          dependencies: job[:dependencies],
82
          after_script: job[:after_script],
83
          environment: job[:environment]
84
        }.compact }
85 86
    end

K
Katarzyna Kobierska 已提交
87
    def self.validation_message(content)
K
Katarzyna Kobierska 已提交
88
      return 'Please provide content of .gitlab-ci.yml' if content.blank?
K
Katarzyna Kobierska 已提交
89

K
Katarzyna Kobierska 已提交
90 91 92 93 94
      begin
        Ci::GitlabCiYamlProcessor.new(content)
        nil
      rescue ValidationError, Psych::SyntaxError => e
        e.message
95 96 97
      end
    end

D
Douwe Maan 已提交
98 99 100
    private

    def initial_parsing
101 102 103
      ##
      # Global config
      #
104 105
      @before_script = @ci_config.before_script
      @image = @ci_config.image
106
      @after_script = @ci_config.after_script
107
      @services = @ci_config.services
108
      @variables = @ci_config.variables
109
      @stages = @ci_config.stages
110
      @cache = @ci_config.cache
111

112 113 114 115
      ##
      # Jobs
      #
      @jobs = @ci_config.jobs
116 117

      @jobs.each do |name, job|
118 119 120 121
        # logical validation for job

        validate_job_stage!(name, job)
        validate_job_dependencies!(name, job)
122
        validate_job_environment!(name, job)
123
      end
D
Douwe Maan 已提交
124 125
    end

126
    def yaml_variables(name)
127 128 129
      variables = (@variables || {})
        .merge(job_variables(name))

130
      variables.map do |key, value|
131
        { key: key.to_s, value: value, public: true }
132 133 134 135 136 137 138 139
      end
    end

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

      job[:variables] || {}
D
Douwe Maan 已提交
140 141
    end

142
    def validate_job_stage!(name, job)
143 144
      return unless job[:stage]

145 146 147 148 149
      unless job[:stage].is_a?(String) && job[:stage].in?(@stages)
        raise ValidationError, "#{name} job: stage parameter should be #{@stages.join(", ")}"
      end
    end

150
    def validate_job_dependencies!(name, job)
151
      return unless job[:dependencies]
152

153
      stage_index = @stages.index(job[:stage])
154 155

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

158
        unless @stages.index(@jobs[dependency.to_sym][:stage]) < stage_index
159 160 161 162 163
          raise ValidationError, "#{name} job: dependency #{dependency} is not defined in prior stages"
        end
      end
    end

164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
    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

193
    def process?(only_params, except_params, ref, tag, trigger_request)
194
      if only_params.present?
195
        return false unless matching?(only_params, ref, tag, trigger_request)
196 197 198
      end

      if except_params.present?
199
        return false if matching?(except_params, ref, tag, trigger_request)
200 201 202 203 204
      end

      true
    end

205
    def matching?(patterns, ref, tag, trigger_request)
206
      patterns.any? do |pattern|
207
        match_ref?(pattern, ref, tag, trigger_request)
208 209 210
      end
    end

J
Jason Roehm 已提交
211
    def match_ref?(pattern, ref, tag, trigger_request)
212 213 214 215
      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 已提交
216
      return true if trigger_request.present? && pattern == 'triggers'
217 218 219 220 221 222 223

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