gitlab_ci_yaml_processor.rb 6.0 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
    def build_attributes(name)
      job = @jobs[name.to_sym] || {}
      {
        stage_idx: @stages.index(job[:stage]),
        stage: job[:stage],
58
        commands: job[:commands],
59
        tag_list: job[:tags] || [],
60
        name: job[:name].to_s,
61
        allow_failure: job[:ignore],
62
        when: job[:when] || 'on_success',
63
        environment: job[:environment_name],
64
        coverage_regex: job[:coverage],
65 66
        yaml_variables: yaml_variables(name),
        options: {
67 68
          image: job[:image],
          services: job[:services],
69
          artifacts: job[:artifacts],
70
          cache: job[:cache],
71
          dependencies: job[:dependencies],
72
          after_script: job[:after_script],
73
          environment: job[:environment]
74 75
        }.compact
      }
76 77
    end

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

K
Katarzyna Kobierska 已提交
81 82 83 84 85
      begin
        Ci::GitlabCiYamlProcessor.new(content)
        nil
      rescue ValidationError, Psych::SyntaxError => e
        e.message
86 87 88
      end
    end

D
Douwe Maan 已提交
89 90 91
    private

    def initial_parsing
92 93 94
      ##
      # Global config
      #
95 96
      @before_script = @ci_config.before_script
      @image = @ci_config.image
97
      @after_script = @ci_config.after_script
98
      @services = @ci_config.services
99
      @variables = @ci_config.variables
100
      @stages = @ci_config.stages
101
      @cache = @ci_config.cache
102

103 104 105 106
      ##
      # Jobs
      #
      @jobs = @ci_config.jobs
107 108

      @jobs.each do |name, job|
109 110 111 112
        # logical validation for job

        validate_job_stage!(name, job)
        validate_job_dependencies!(name, job)
113
        validate_job_environment!(name, job)
114
      end
D
Douwe Maan 已提交
115 116
    end

117
    def yaml_variables(name)
118 119 120
      variables = (@variables || {})
        .merge(job_variables(name))

121
      variables.map do |key, value|
122
        { key: key.to_s, value: value, public: true }
123 124 125 126 127 128 129 130
      end
    end

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

      job[:variables] || {}
D
Douwe Maan 已提交
131 132
    end

133
    def validate_job_stage!(name, job)
134 135
      return unless job[:stage]

136 137 138 139 140
      unless job[:stage].is_a?(String) && job[:stage].in?(@stages)
        raise ValidationError, "#{name} job: stage parameter should be #{@stages.join(", ")}"
      end
    end

141
    def validate_job_dependencies!(name, job)
142
      return unless job[:dependencies]
143

144
      stage_index = @stages.index(job[:stage])
145 146

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

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

155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
    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

184
    def process?(only_params, except_params, ref, tag, trigger_request)
185
      if only_params.present?
186
        return false unless matching?(only_params, ref, tag, trigger_request)
187 188 189
      end

      if except_params.present?
190
        return false if matching?(except_params, ref, tag, trigger_request)
191 192 193 194 195
      end

      true
    end

196
    def matching?(patterns, ref, tag, trigger_request)
197
      patterns.any? do |pattern|
198
        match_ref?(pattern, ref, tag, trigger_request)
199 200 201
      end
    end

J
Jason Roehm 已提交
202
    def match_ref?(pattern, ref, tag, trigger_request)
203 204 205 206
      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 已提交
207
      return true if trigger_request.present? && pattern == 'triggers'
208 209 210 211 212 213 214

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