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 85
        }.compact
      }
86 87
    end

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

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

D
Douwe Maan 已提交
99 100 101
    private

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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 193
    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

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

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

      true
    end

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

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

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