build.rb 10.3 KB
Newer Older
D
Douwe Maan 已提交
1
module Ci
K
Kamil Trzcinski 已提交
2
  class Build < CommitStatus
D
Douwe Maan 已提交
3 4
    belongs_to :runner, class_name: 'Ci::Runner'
    belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
5
    belongs_to :erased_by, class_name: 'User'
D
Douwe Maan 已提交
6 7 8 9

    serialize :options

    validates :coverage, numericality: true, allow_blank: true
K
Kamil Trzcinski 已提交
10
    validates_presence_of :ref
D
Douwe Maan 已提交
11 12

    scope :unstarted, ->() { where(runner_id: nil) }
K
Kamil Trzcinski 已提交
13
    scope :ignore_failures, ->() { where(allow_failure: false) }
D
Douwe Maan 已提交
14

K
Kamil Trzcinski 已提交
15
    mount_uploader :artifacts_file, ArtifactUploader
16
    mount_uploader :artifacts_metadata, ArtifactUploader
K
Kamil Trzcinski 已提交
17

D
Douwe Maan 已提交
18 19
    acts_as_taggable

G
Grzegorz Bizon 已提交
20 21
    before_destroy { project }

K
Kamil Trzcinski 已提交
22
    after_create :execute_hooks
D
Douwe Maan 已提交
23 24 25 26 27 28 29 30 31 32 33 34

    class << self
      def last_month
        where('created_at > ?', Date.today - 1.month)
      end

      def first_pending
        pending.unstarted.order('created_at ASC').first
      end

      def create_from(build)
        new_build = build.dup
K
Kamil Trzcinski 已提交
35
        new_build.status = 'pending'
D
Douwe Maan 已提交
36
        new_build.runner_id = nil
K
Kamil Trzcinski 已提交
37
        new_build.trigger_request_id = nil
D
Douwe Maan 已提交
38 39 40
        new_build.save
      end

41
      def retry(build, user = nil)
K
Kamil Trzcinski 已提交
42
        new_build = Ci::Build.new(status: 'pending')
K
Kamil Trzcinski 已提交
43 44
        new_build.ref = build.ref
        new_build.tag = build.tag
D
Douwe Maan 已提交
45 46 47
        new_build.options = build.options
        new_build.commands = build.commands
        new_build.tag_list = build.tag_list
48 49
        new_build.project = build.project
        new_build.pipeline = build.pipeline
D
Douwe Maan 已提交
50 51 52
        new_build.name = build.name
        new_build.allow_failure = build.allow_failure
        new_build.stage = build.stage
K
Kamil Trzcinski 已提交
53
        new_build.stage_idx = build.stage_idx
D
Douwe Maan 已提交
54
        new_build.trigger_request = build.trigger_request
55
        new_build.user = user
D
Douwe Maan 已提交
56
        new_build.save
57
        MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build)
D
Douwe Maan 已提交
58 59 60 61 62
        new_build
      end
    end

    state_machine :status, initial: :pending do
63
      after_transition pending: :running do |build|
64 65 66
        build.execute_hooks
      end

67 68 69
      # We use around_transition to create builds for next stage as soon as possible, before the `after_*` is executed
      around_transition any => [:success, :failed, :canceled] do |build, block|
        block.call
70
        build.pipeline.create_next_builds(build) if build.pipeline
71
      end
72

73
      after_transition any => [:success, :failed, :canceled] do |build|
K
Kamil Trzcinski 已提交
74
        build.update_coverage
75
        build.execute_hooks
D
Douwe Maan 已提交
76
      end
77 78 79 80 81 82

      after_transition any: :success do |build|
        if build.environment.present?
          CreateDeploymentService.new(build.project, build.user, environment: build.environment).execute(build)
        end
      end
D
Douwe Maan 已提交
83 84
    end

K
Kamil Trzcinski 已提交
85
    def retryable?
K
Kamil Trzcinski 已提交
86
      project.builds_enabled? && commands.present?
K
Kamil Trzcinski 已提交
87 88 89
    end

    def retried?
90
      !self.pipeline.statuses.latest.include?(self)
K
Kamil Trzcinski 已提交
91 92
    end

93 94
    def depends_on_builds
      # Get builds of the same type
95
      latest_builds = self.pipeline.builds.latest
96 97 98 99 100

      # Return builds from previous stages
      latest_builds.where('stage_idx < ?', stage_idx)
    end

D
Douwe Maan 已提交
101
    def trace_html
K
Kamil Trzcinski 已提交
102
      trace_with_state[:html] || ''
103 104 105 106 107
    end

    def trace_with_state(state = nil)
      trace_with_state = Ci::Ansi2html::convert(trace, state) if trace.present?
      trace_with_state || {}
D
Douwe Maan 已提交
108 109 110
    end

    def timeout
K
Kamil Trzcinski 已提交
111
      project.build_timeout
D
Douwe Maan 已提交
112 113 114
    end

    def variables
115
      predefined_variables + yaml_variables + project_variables + trigger_variables
D
Douwe Maan 已提交
116 117
    end

118 119
    def merge_request
      merge_requests = MergeRequest.includes(:merge_request_diff)
120
                                   .where(source_branch: ref, source_project_id: pipeline.gl_project_id)
121 122 123
                                   .reorder(iid: :asc)

      merge_requests.find do |merge_request|
124
        merge_request.commits.any? { |ci| ci.id == pipeline.sha }
125 126 127
      end
    end

D
Douwe Maan 已提交
128
    def project_id
129
      pipeline.project_id
D
Douwe Maan 已提交
130 131 132 133 134 135 136
    end

    def project_name
      project.name
    end

    def repo_url
K
Kamil Trzcinski 已提交
137 138 139 140
      auth = "gitlab-ci-token:#{token}@"
      project.http_url_to_repo.sub(/^https?:\/\//) do |prefix|
        prefix + auth
      end
D
Douwe Maan 已提交
141 142 143
    end

    def allow_git_fetch
K
Kamil Trzcinski 已提交
144
      project.build_allow_git_fetch
D
Douwe Maan 已提交
145 146 147
    end

    def update_coverage
148
      return unless project
K
Kamil Trzcinski 已提交
149 150 151
      coverage_regex = project.build_coverage_regex
      return unless coverage_regex
      coverage = extract_coverage(trace, coverage_regex)
D
Douwe Maan 已提交
152 153 154 155 156 157 158 159

      if coverage.is_a? Numeric
        update_attributes(coverage: coverage)
      end
    end

    def extract_coverage(text, regex)
      begin
J
Jared Szechy 已提交
160 161
        matches = text.scan(Regexp.new(regex)).last
        matches = matches.last if matches.kind_of?(Array)
D
Douwe Maan 已提交
162 163 164 165 166
        coverage = matches.gsub(/\d+(\.\d+)?/).first

        if coverage.present?
          coverage.to_f
        end
G
Guilherme Garnier 已提交
167
      rescue
D
Douwe Maan 已提交
168 169 170 171 172
        # if bad regex or something goes wrong we dont want to interrupt transition
        # so we just silentrly ignore error for now
      end
    end

173 174
    def has_trace?
      raw_trace.present?
175 176
    end

177
    def raw_trace
178
      if File.file?(path_to_trace)
D
Douwe Maan 已提交
179
        File.read(path_to_trace)
180
      elsif project.ci_id && File.file?(old_path_to_trace)
181 182
        # Temporary fix for build trace data integrity
        File.read(old_path_to_trace)
D
Douwe Maan 已提交
183 184 185 186 187
      else
        # backward compatibility
        read_attribute :trace
      end
    end
188 189 190

    def trace
      trace = raw_trace
191
      if project && trace.present? && project.runners_token.present?
K
Kamil Trzcinski 已提交
192
        trace.gsub(project.runners_token, 'xxxxxx')
193 194 195 196
      else
        trace
      end
    end
D
Douwe Maan 已提交
197

T
Tomasz Maczukin 已提交
198
    def trace_length
199
      if raw_trace
200
        raw_trace.bytesize
T
Tomasz Maczukin 已提交
201
      else
202
        0
T
Tomasz Maczukin 已提交
203 204 205
      end
    end

D
Douwe Maan 已提交
206
    def trace=(trace)
207 208 209 210 211
      recreate_trace_dir
      File.write(path_to_trace, trace)
    end

    def recreate_trace_dir
212
      unless Dir.exist?(dir_to_trace)
213
        FileUtils.mkdir_p(dir_to_trace)
D
Douwe Maan 已提交
214
      end
215 216
    end
    private :recreate_trace_dir
D
Douwe Maan 已提交
217

218
    def append_trace(trace_part, offset)
219 220
      recreate_trace_dir

221
      File.truncate(path_to_trace, offset) if File.exist?(path_to_trace)
222
      File.open(path_to_trace, 'ab') do |f|
223 224
        f.write(trace_part)
      end
D
Douwe Maan 已提交
225 226 227 228
    end

    def dir_to_trace
      File.join(
V
Valery Sizov 已提交
229
        Settings.gitlab_ci.builds_path,
D
Douwe Maan 已提交
230 231 232 233 234 235 236 237 238
        created_at.utc.strftime("%Y_%m"),
        project.id.to_s
      )
    end

    def path_to_trace
      "#{dir_to_trace}/#{id}.log"
    end

239 240 241
    ##
    # Deprecated
    #
242 243 244
    # This is a hotfix for CI build data integrity, see #4246
    # Should be removed in 8.4, after CI files migration has been done.
    #
245 246 247 248 249 250 251 252 253 254 255
    def old_dir_to_trace
      File.join(
        Settings.gitlab_ci.builds_path,
        created_at.utc.strftime("%Y_%m"),
        project.ci_id.to_s
      )
    end

    ##
    # Deprecated
    #
256 257 258
    # This is a hotfix for CI build data integrity, see #4246
    # Should be removed in 8.4, after CI files migration has been done.
    #
259 260 261 262
    def old_path_to_trace
      "#{old_dir_to_trace}/#{id}.log"
    end

263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
    ##
    # Deprecated
    #
    # This contains a hotfix for CI build data integrity, see #4246
    #
    # This method is used by `ArtifactUploader` to create a store_dir.
    # Warning: Uploader uses it after AND before file has been stored.
    #
    # This method returns old path to artifacts only if it already exists.
    #
    def artifacts_path
      old = File.join(created_at.utc.strftime('%Y_%m'),
                      project.ci_id.to_s,
                      id.to_s)

      old_store = File.join(ArtifactUploader.artifacts_path, old)
      return old if project.ci_id && File.directory?(old_store)

      File.join(
        created_at.utc.strftime('%Y_%m'),
        project.id.to_s,
        id.to_s
      )
    end

K
Kamil Trzcinski 已提交
288
    def token
K
Kamil Trzcinski 已提交
289
      project.runners_token
K
Kamil Trzcinski 已提交
290 291
    end

292
    def valid_token?(token)
K
Kamil Trzcinski 已提交
293
      project.valid_runners_token? token
K
Kamil Trzcinski 已提交
294 295
    end

296
    def can_be_served?(runner)
297
      return false unless has_tags? || runner.run_untagged?
298

299 300 301
      (tag_list - runner.tag_list).empty?
    end

302 303 304 305
    def has_tags?
      tag_list.any?
    end

306 307 308 309
    def any_runners_online?
      project.any_runners? { |runner| runner.active? && runner.online? && can_be_served?(runner) }
    end

K
Kamil Trzcinski 已提交
310
    def stuck?
311 312 313
      pending? && !any_runners_online?
    end

314
    def execute_hooks
315
      return unless project
316
      build_data = Gitlab::BuildDataBuilder.build(self)
K
Kamil Trzcinski 已提交
317 318
      project.execute_hooks(build_data.dup, :build_hooks)
      project.execute_services(build_data.dup, :build_hooks)
J
Josh Frye 已提交
319
      project.running_or_pending_build_count(force: true)
320 321
    end

322 323 324 325
    def artifacts?
      artifacts_file.exists?
    end

326
    def artifacts_metadata?
327
      artifacts? && artifacts_metadata.exists?
328 329
    end

330 331
    def artifacts_metadata_entry(path, **options)
      Gitlab::Ci::Build::Artifacts::Metadata.new(artifacts_metadata.path, path, **options).to_entry
332 333
    end

334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
    def erase(opts = {})
      return false unless erasable?

      remove_artifacts_file!
      remove_artifacts_metadata!
      erase_trace!
      update_erased!(opts[:erased_by])
    end

    def erasable?
      complete? && (artifacts? || has_trace?)
    end

    def erased?
      !self.erased_at.nil?
    end

    private

    def erase_trace!
      self.trace = nil
    end

    def update_erased!(user = nil)
      self.update(erased_by: user, erased_at: Time.now)
    end

D
Douwe Maan 已提交
361
    def yaml_variables
362 363 364 365
      global_yaml_variables + job_yaml_variables
    end

    def global_yaml_variables
366 367
      if pipeline.config_processor
        pipeline.config_processor.global_variables.map do |key, value|
D
Douwe Maan 已提交
368 369 370 371 372 373 374
          { key: key, value: value, public: true }
        end
      else
        []
      end
    end

375
    def job_yaml_variables
376 377
      if pipeline.config_processor
        pipeline.config_processor.job_variables(name).map do |key, value|
378 379 380 381
          { key: key, value: value, public: true }
        end
      else
        []
382 383 384
      end
    end

D
Douwe Maan 已提交
385
    def project_variables
386
      project.variables.map do |variable|
D
Douwe Maan 已提交
387 388 389 390 391 392 393 394 395 396 397 398 399
        { key: variable.key, value: variable.value, public: false }
      end
    end

    def trigger_variables
      if trigger_request && trigger_request.variables
        trigger_request.variables.map do |key, value|
          { key: key, value: value, public: false }
        end
      else
        []
      end
    end
400 401 402

    def predefined_variables
      variables = []
403
      variables << { key: :CI_BUILD_TAG, value: ref, public: true } if tag?
404 405 406 407 408
      variables << { key: :CI_BUILD_NAME, value: name, public: true }
      variables << { key: :CI_BUILD_STAGE, value: stage, public: true }
      variables << { key: :CI_BUILD_TRIGGERED, value: 'true', public: true } if trigger_request
      variables
    end
D
Douwe Maan 已提交
409 410
  end
end