build.rb 23.3 KB
Newer Older
1 2
# frozen_string_literal: true

D
Douwe Maan 已提交
3
module Ci
K
Kamil Trzcinski 已提交
4
  class Build < CommitStatus
Z
Zeger-Jan van de Weg 已提交
5
    prepend ArtifactMigratable
6
    include Ci::Processable
7
    include Ci::Metadatable
8
    include Ci::Contextable
9
    include TokenAuthenticatable
10
    include AfterCommitQueue
11
    include ObjectStorage::BackgroundMove
R
Rémy Coutable 已提交
12
    include Presentable
S
Shinya Maeda 已提交
13
    include Importable
14
    include IgnorableColumn
15
    include Gitlab::Utils::StrongMemoize
S
Shinya Maeda 已提交
16
    include Deployable
17
    include HasRef
18

19 20 21 22
    BuildArchivedError = Class.new(StandardError)

    ignore_column :commands

23
    belongs_to :project, inverse_of: :builds
24 25
    belongs_to :runner
    belongs_to :trigger_request
26
    belongs_to :erased_by, class_name: 'User'
D
Douwe Maan 已提交
27

28 29 30 31
    RUNNER_FEATURES = {
      upload_multiple_artifacts: -> (build) { build.publishes_artifacts_reports? }
    }.freeze

S
Shinya Maeda 已提交
32
    has_one :deployment, as: :deployable, class_name: 'Deployment'
33
    has_many :trace_sections, class_name: 'Ci::BuildTraceSection'
34
    has_many :trace_chunks, class_name: 'Ci::BuildTraceChunk', foreign_key: :build_id
35

36
    has_many :job_artifacts, class_name: 'Ci::JobArtifact', foreign_key: :job_id, dependent: :destroy, inverse_of: :job # rubocop:disable Cop/ActiveRecordDependent
S
Shinya Maeda 已提交
37 38 39 40

    Ci::JobArtifact.file_types.each do |key, value|
      has_one :"job_artifacts_#{key}", -> { where(file_type: value) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id
    end
41

F
Francisco Javier López 已提交
42 43 44 45 46 47
    has_one :runner_session, class_name: 'Ci::BuildRunnerSession', validate: true, inverse_of: :build

    accepts_nested_attributes_for :runner_session

    delegate :url, to: :runner_session, prefix: true, allow_nil: true
    delegate :terminal_specification, to: :runner_session, allow_nil: true
48
    delegate :gitlab_deploy_token, to: :project
49
    delegate :trigger_short_token, to: :trigger_request, allow_nil: true
50
    delegate :merge_request_event?, to: :pipeline
T
Tomasz Maczukin 已提交
51

52
    ##
53 54 55 56 57
    # Since Gitlab 11.5, deployments records started being created right after
    # `ci_builds` creation. We can look up a relevant `environment` through
    # `deployment` relation today. This is much more efficient than expanding
    # environment name with variables.
    # (See more https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22380)
58
    #
59 60 61 62 63
    # However, we have to still expand environment name if it's a stop action,
    # because `deployment` persists information for start action only.
    #
    # We will follow up this by persisting expanded name in build metadata or
    # persisting stop action in database.
64
    def persisted_environment
65 66 67
      return unless has_environment?

      strong_memoize(:persisted_environment) do
68 69
        deployment&.environment ||
          Environment.find_by(name: expanded_environment_name, project: project)
70
      end
71 72
    end

73 74
    serialize :options # rubocop:disable Cop/ActiveRecordSerialize
    serialize :yaml_variables, Gitlab::Serializer::Ci::Variables # rubocop:disable Cop/ActiveRecordSerialize
D
Douwe Maan 已提交
75

D
Douwe Maan 已提交
76 77
    delegate :name, to: :project, prefix: true

D
Douwe Maan 已提交
78
    validates :coverage, numericality: true, allow_blank: true
D
Douwe Maan 已提交
79
    validates :ref, presence: true
D
Douwe Maan 已提交
80 81

    scope :unstarted, ->() { where(runner_id: nil) }
K
Kamil Trzcinski 已提交
82
    scope :ignore_failures, ->() { where(allow_failure: false) }
83
    scope :with_artifacts_archive, ->() do
84
      where('(artifacts_file IS NOT NULL AND artifacts_file <> ?) OR EXISTS (?)',
85
        '', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive)
86
    end
87

88 89 90 91
    scope :with_existing_job_artifacts, ->(query) do
      where('EXISTS (?)', ::Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').merge(query))
    end

92
    scope :with_archived_trace, ->() do
93
      with_existing_job_artifacts(Ci::JobArtifact.trace)
94 95
    end

96 97 98 99
    scope :without_archived_trace, ->() do
      where('NOT EXISTS (?)', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').trace)
    end

S
Shinya Maeda 已提交
100
    scope :with_test_reports, ->() do
101 102
      with_existing_job_artifacts(Ci::JobArtifact.test_reports)
        .eager_load_job_artifacts
S
Shinya Maeda 已提交
103 104
    end

105 106
    scope :eager_load_job_artifacts, -> { includes(:job_artifacts) }

107
    scope :with_artifacts_stored_locally, -> { with_artifacts_archive.where(artifacts_file_store: [nil, LegacyArtifactUploader::Store::LOCAL]) }
108
    scope :with_archived_trace_stored_locally, -> { with_archived_trace.where(artifacts_file_store: [nil, LegacyArtifactUploader::Store::LOCAL]) }
109 110
    scope :with_artifacts_not_expired, ->() { with_artifacts_archive.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) }
    scope :with_expired_artifacts, ->() { with_artifacts_archive.where('artifacts_expire_at < ?', Time.now) }
111
    scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
112 113
    scope :manual_actions, ->() { where(when: :manual, status: COMPLETED_STATUSES + %i[manual]) }
    scope :scheduled_actions, ->() { where(when: :delayed, status: COMPLETED_STATUSES + %i[scheduled]) }
114
    scope :ref_protected, -> { where(protected: true) }
115
    scope :with_live_trace, -> { where('EXISTS (?)', Ci::BuildTraceChunk.where('ci_builds.id = ci_build_trace_chunks.build_id').select(1)) }
116

117 118
    scope :matches_tag_ids, -> (tag_ids) do
      matcher = ::ActsAsTaggableOn::Tagging
119
        .where(taggable_type: CommitStatus.name)
120 121 122 123 124 125 126 127 128
        .where(context: 'tags')
        .where('taggable_id = ci_builds.id')
        .where.not(tag_id: tag_ids).select('1')

      where("NOT EXISTS (?)", matcher)
    end

    scope :with_any_tags, -> do
      matcher = ::ActsAsTaggableOn::Tagging
129
        .where(taggable_type: CommitStatus.name)
130 131 132 133 134 135
        .where(context: 'tags')
        .where('taggable_id = ci_builds.id').select('1')

      where("EXISTS (?)", matcher)
    end

136 137
    mount_uploader :legacy_artifacts_file, LegacyArtifactUploader, mount_on: :artifacts_file
    mount_uploader :legacy_artifacts_metadata, LegacyArtifactUploader, mount_on: :artifacts_metadata
K
Kamil Trzcinski 已提交
138

D
Douwe Maan 已提交
139 140
    acts_as_taggable

K
Kamil Trzciński 已提交
141
    add_authentication_token_field :token, encrypted: true, fallback: true
142

L
Lin Jen-Shin 已提交
143
    before_save :update_artifacts_size, if: :artifacts_file_changed?
144
    before_save :ensure_token
145
    before_destroy { unscoped_project }
G
Grzegorz Bizon 已提交
146

147
    after_create unless: :importing? do |build|
148
      run_after_commit { BuildHooksWorker.perform_async(build.id) }
149 150
    end

151 152
    after_save :update_project_statistics_after_save, if: :artifacts_size_changed?
    after_destroy :update_project_statistics_after_destroy, unless: :project_destroyed?
D
Douwe Maan 已提交
153 154

    class << self
155 156 157 158 159 160
      # This is needed for url_for to work,
      # as the controller is JobsController
      def model_name
        ActiveModel::Name.new(self, nil, 'job')
      end

D
Douwe Maan 已提交
161 162 163 164
      def first_pending
        pending.unstarted.order('created_at ASC').first
      end

165
      def retry(build, current_user)
166
        # rubocop: disable CodeReuse/ServiceClass
167 168 169
        Ci::RetryBuildService
          .new(build.project, current_user)
          .execute(build)
170
        # rubocop: enable CodeReuse/ServiceClass
D
Douwe Maan 已提交
171 172 173
      end
    end

174
    state_machine :status do
175 176
      event :actionize do
        transition created: :manual
K
Kamil Trzcinski 已提交
177 178
      end

179 180 181 182 183 184 185 186
      event :schedule do
        transition created: :scheduled
      end

      event :unschedule do
        transition scheduled: :manual
      end

S
Shinya Maeda 已提交
187
      event :enqueue_scheduled do
188 189
        transition scheduled: :pending, if: ->(build) do
          build.scheduled_at && build.scheduled_at < Time.now
S
Shinya Maeda 已提交
190
        end
191 192 193
      end

      before_transition scheduled: any do |build|
S
Shinya Maeda 已提交
194 195 196 197
        build.scheduled_at = nil
      end

      before_transition created: :scheduled do |build|
S
Shinya Maeda 已提交
198
        build.scheduled_at = build.options_scheduled_at
S
Shinya Maeda 已提交
199 200 201 202 203 204
      end

      after_transition created: :scheduled do |build|
        build.run_after_commit do
          Ci::BuildScheduleWorker.perform_at(build.scheduled_at, build.id)
        end
205 206
      end

207 208
      after_transition any => [:pending] do |build|
        build.run_after_commit do
K
linting  
Kim "BKC" Carlbäcker 已提交
209
          BuildQueueWorker.perform_async(id)
210 211 212
        end
      end

213
      after_transition pending: :running do |build|
S
Shinya Maeda 已提交
214 215
        build.deployment&.run

216 217 218
        build.run_after_commit do
          BuildHooksWorker.perform_async(id)
        end
219 220
      end

221
      after_transition any => [:success, :failed, :canceled] do |build|
222
        build.run_after_commit do
223
          BuildFinishedWorker.perform_async(id)
224
        end
D
Douwe Maan 已提交
225
      end
226

227
      after_transition any => [:success] do |build|
S
Shinya Maeda 已提交
228 229
        build.deployment&.succeed

230
        build.run_after_commit do
231
          BuildSuccessWorker.perform_async(id)
232
          PagesWorker.perform_async(:deploy, id) if build.pages_generator?
233 234
        end
      end
235

236
      before_transition any => [:failed] do |build|
237
        next unless build.project
238
        next unless build.deployment
S
Shinya Maeda 已提交
239

240 241 242 243 244 245 246
        begin
          build.deployment.drop!
        rescue => e
          Gitlab::Sentry.track_exception(e, extra: { build_id: build.id })
        end

        true
247 248 249 250
      end

      after_transition any => [:failed] do |build|
        next unless build.project
S
Shinya Maeda 已提交
251

252
        if build.retry_failure?
253 254 255 256 257
          begin
            Ci::Build.retry(build, build.user)
          rescue Gitlab::Access::AccessDeniedError => ex
            Rails.logger.error "Unable to auto-retry job #{build.id}: #{ex}"
          end
258 259
        end
      end
260

261
      after_transition pending: :running do |build|
262
        build.ensure_metadata.update_timeout_state
T
Tomasz Maczukin 已提交
263
      end
F
Francisco Javier López 已提交
264 265 266 267

      after_transition running: any do |build|
        Ci::BuildRunnerSession.where(build: build).delete_all
      end
S
Shinya Maeda 已提交
268 269 270 271

      after_transition any => [:skipped, :canceled] do |build|
        build.deployment&.cancel
      end
D
Douwe Maan 已提交
272 273
    end

274
    def detailed_status(current_user)
275 276 277
      Gitlab::Ci::Status::Build::Factory
        .new(self, current_user)
        .fabricate!
K
Kamil Trzcinski 已提交
278 279
    end

280
    def other_manual_actions
281
      pipeline.manual_actions.where.not(name: name)
282 283
    end

284 285
    def other_scheduled_actions
      pipeline.scheduled_actions.where.not(name: name)
286 287
    end

288 289 290 291 292
    def pages_generator?
      Gitlab.config.pages.enabled &&
        self.name == 'pages'
    end

293 294 295 296
    def runnable?
      true
    end

297 298 299 300 301 302 303
    def archived?
      return true if degenerated?

      archive_builds_older_than = Gitlab::CurrentSettings.current_application_settings.archive_builds_older_than
      archive_builds_older_than.present? && created_at < archive_builds_older_than
    end

304
    def playable?
305
      action? && !archived? && (manual? || scheduled? || retryable?)
K
Kamil Trzcinski 已提交
306 307
    end

S
Shinya Maeda 已提交
308
    def schedulable?
309
      self.when == 'delayed' && options[:start_in].present?
S
Shinya Maeda 已提交
310 311
    end

S
Shinya Maeda 已提交
312
    def options_scheduled_at
S
Shinya Maeda 已提交
313
      ChronicDuration.parse(options[:start_in])&.seconds&.from_now
K
Kamil Trzcinski 已提交
314 315
    end

316
    def action?
317
      %w[manual delayed].include?(self.when)
318 319
    end

320
    # rubocop: disable CodeReuse/ServiceClass
321
    def play(current_user)
322 323 324
      Ci::PlayBuildService
        .new(project, current_user)
        .execute(self)
325
    end
326
    # rubocop: enable CodeReuse/ServiceClass
327

K
Kamil Trzcinski 已提交
328
    def cancelable?
J
Jacopo 已提交
329
      active? || created?
K
Kamil Trzcinski 已提交
330 331
    end

K
Kamil Trzcinski 已提交
332
    def retryable?
333
      !archived? && (success? || failed? || canceled?)
K
Kamil Trzcinski 已提交
334
    end
335 336 337 338 339 340

    def retries_count
      pipeline.builds.retried.where(name: self.name).count
    end

    def retries_max
M
Markus Doits 已提交
341
      normalized_retry.fetch(:max, 0)
342
    end
K
Kamil Trzcinski 已提交
343

344
    def retry_when
M
Markus Doits 已提交
345
      normalized_retry.fetch(:when, ['always'])
346 347
    end

348 349 350
    def retry_failure?
      return false if retries_max.zero? || retries_count >= retries_max

351
      retry_when.include?('always') || retry_when.include?(failure_reason.to_s)
352
    end
K
Kamil Trzcinski 已提交
353

354 355
    def latest?
      !retried?
K
Kamil Trzcinski 已提交
356 357
    end

358
    def expanded_environment_name
359 360 361
      return unless has_environment?

      strong_memoize(:expanded_environment_name) do
362 363
        ExpandVariables.expand(environment, simple_variables)
      end
364 365
    end

366
    def has_environment?
367
      environment.present?
368 369
    end

370
    def starts_environment?
371
      has_environment? && self.environment_action == 'start'
372 373 374
    end

    def stops_environment?
375
      has_environment? && self.environment_action == 'stop'
376 377 378
    end

    def environment_action
379
      self.options.fetch(:environment, {}).fetch(:action, 'start') if self.options
380 381
    end

S
Shinya Maeda 已提交
382 383 384 385
    def has_deployment?
      !!self.deployment
    end

386
    def outdated_deployment?
S
Shinya Maeda 已提交
387
      success? && !deployment.try(:last?)
388
    end
389

390 391
    def depends_on_builds
      # Get builds of the same type
392
      latest_builds = self.pipeline.builds.latest
393 394 395 396 397

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

398
    def triggered_by?(current_user)
S
Shinya Maeda 已提交
399 400 401
      user == current_user
    end

S
Shinya Maeda 已提交
402 403 404 405
    def on_stop
      options&.dig(:environment, :on_stop)
    end

406 407 408 409
    ##
    # All variables, including persisted environment variables.
    #
    def variables
410 411 412 413 414 415 416
      strong_memoize(:variables) do
        Gitlab::Ci::Variables::Collection.new
          .concat(persisted_variables)
          .concat(scoped_variables)
          .concat(persisted_environment_variables)
          .to_runner_variables
      end
417 418
    end

419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458
    CI_REGISTRY_USER = 'gitlab-ci-token'.freeze

    def persisted_variables
      Gitlab::Ci::Variables::Collection.new.tap do |variables|
        break variables unless persisted?

        variables
          .concat(pipeline.persisted_variables)
          .append(key: 'CI_JOB_ID', value: id.to_s)
          .append(key: 'CI_JOB_URL', value: Gitlab::Routing.url_helpers.project_job_url(project, self))
          .append(key: 'CI_JOB_TOKEN', value: token.to_s, public: false)
          .append(key: 'CI_BUILD_ID', value: id.to_s)
          .append(key: 'CI_BUILD_TOKEN', value: token.to_s, public: false)
          .append(key: 'CI_REGISTRY_USER', value: CI_REGISTRY_USER)
          .append(key: 'CI_REGISTRY_PASSWORD', value: token.to_s, public: false)
          .append(key: 'CI_REPOSITORY_URL', value: repo_url.to_s, public: false)
          .concat(deploy_token_variables)
      end
    end

    def persisted_environment_variables
      Gitlab::Ci::Variables::Collection.new.tap do |variables|
        break variables unless persisted? && persisted_environment.present?

        variables.concat(persisted_environment.predefined_variables)

        # Here we're passing unexpanded environment_url for runner to expand,
        # and we need to make sure that CI_ENVIRONMENT_NAME and
        # CI_ENVIRONMENT_SLUG so on are available for the URL be expanded.
        variables.append(key: 'CI_ENVIRONMENT_URL', value: environment_url) if environment_url
      end
    end

    def deploy_token_variables
      Gitlab::Ci::Variables::Collection.new.tap do |variables|
        break variables unless gitlab_deploy_token

        variables.append(key: 'CI_DEPLOY_USER', value: gitlab_deploy_token.username)
        variables.append(key: 'CI_DEPLOY_PASSWORD', value: gitlab_deploy_token.token, public: false)
      end
459 460
    end

461 462 463 464
    def features
      { trace_sections: true }
    end

465
    def merge_request
Z
Z.J. van de Weg 已提交
466
      return @merge_request if defined?(@merge_request)
Z
Z.J. van de Weg 已提交
467

468 469
      @merge_request ||=
        begin
470
          merge_requests = MergeRequest.includes(:latest_merge_request_diff)
471 472
            .where(source_branch: ref,
                   source_project: pipeline.project)
Z
Z.J. van de Weg 已提交
473
            .reorder(iid: :desc)
474 475

          merge_requests.find do |merge_request|
476
            merge_request.commit_shas.include?(pipeline.sha)
477 478
          end
        end
479 480
    end

D
Douwe Maan 已提交
481
    def repo_url
K
Kamil Trzciński 已提交
482 483 484
      return unless token

      auth = "gitlab-ci-token:#{token}@"
485
      project.http_url_to_repo.sub(%r{^https?://}) do |prefix|
K
Kamil Trzcinski 已提交
486 487
        prefix + auth
      end
D
Douwe Maan 已提交
488 489 490
    end

    def allow_git_fetch
K
Kamil Trzcinski 已提交
491
      project.build_allow_git_fetch
D
Douwe Maan 已提交
492 493 494
    end

    def update_coverage
495
      coverage = trace.extract_coverage(coverage_regex)
L
Lin Jen-Shin 已提交
496
      update(coverage: coverage) if coverage.present?
D
Douwe Maan 已提交
497 498
    end

499
    # rubocop: disable CodeReuse/ServiceClass
500
    def parse_trace_sections!
501
      ExtractSectionsFromBuildTraceService.new(project, user).execute(self)
502
    end
503
    # rubocop: enable CodeReuse/ServiceClass
504

505 506
    def trace
      Gitlab::Ci::Trace.new(self)
507 508
    end

509
    def has_trace?
510
      trace.exist?
T
Tomasz Maczukin 已提交
511 512
    end

513 514
    def has_job_artifacts?
      job_artifacts.any?
S
Shinya Maeda 已提交
515 516
    end

S
Shinya Maeda 已提交
517 518 519 520
    def has_old_trace?
      old_trace.present?
    end

521 522
    def trace=(data)
      raise NotImplementedError
T
Tomasz Maczukin 已提交
523 524
    end

525 526
    def old_trace
      read_attribute(:trace)
527 528
    end

529
    def erase_old_trace!
530
      return unless has_old_trace?
S
Shinya Maeda 已提交
531

532
      update_column(:trace, nil)
D
Douwe Maan 已提交
533 534
    end

535 536 537 538
    def needs_touch?
      Time.now - updated_at > 15.minutes.to_i
    end

L
Lin Jen-Shin 已提交
539
    def valid_token?(token)
540
      self.token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token)
K
Kamil Trzcinski 已提交
541 542
    end

543 544 545 546
    def has_tags?
      tag_list.any?
    end

547
    def any_runners_online?
548
      project.any_runners? { |runner| runner.active? && runner.online? && runner.can_pick?(self) }
549 550
    end

K
Kamil Trzcinski 已提交
551
    def stuck?
552 553 554
      pending? && !any_runners_online?
    end

555
    def execute_hooks
556
      return unless project
557

558
      build_data = Gitlab::DataBuilder::Build.build(self)
559 560
      project.execute_hooks(build_data.dup, :job_hooks)
      project.execute_services(build_data.dup, :job_hooks)
561 562
    end

563 564 565 566
    def browsable_artifacts?
      artifacts_metadata?
    end

567
    def artifacts_metadata_entry(path, **options)
568
      artifacts_metadata.open do |metadata_stream|
569
        metadata = Gitlab::Ci::Build::Artifacts::Metadata.new(
570
          metadata_stream,
571 572
          path,
          **options)
573

574 575
        metadata.to_entry
      end
576 577
    end

578 579 580 581
    # and use that for `ExpireBuildInstanceArtifactsWorker`?
    def erase_erasable_artifacts!
      job_artifacts.erasable.destroy_all # rubocop: disable DestroyAll
      erase_old_artifacts!
S
Shinya Maeda 已提交
582 583
    end

584 585 586
    def erase(opts = {})
      return false unless erasable?

587 588
      job_artifacts.destroy_all # rubocop: disable DestroyAll
      erase_old_artifacts!
589 590 591 592 593
      erase_trace!
      update_erased!(opts[:erased_by])
    end

    def erasable?
594
      complete? && (artifacts? || has_job_artifacts? || has_trace?)
595 596 597 598 599 600
    end

    def erased?
      !self.erased_at.nil?
    end

601
    def artifacts_expired?
602
      artifacts_expire_at && artifacts_expire_at < Time.now
603 604
    end

605 606 607 608 609
    def artifacts_expire_in
      artifacts_expire_at - Time.now if artifacts_expire_at
    end

    def artifacts_expire_in=(value)
K
Kamil Trzcinski 已提交
610 611
      self.artifacts_expire_at =
        if value
612
          ChronicDuration.parse(value)&.seconds&.from_now
K
Kamil Trzcinski 已提交
613
        end
614 615
    end

616
    def has_expiring_artifacts?
Z
Z.J. van de Weg 已提交
617
      artifacts_expire_at.present? && artifacts_expire_at > Time.now
618 619
    end

620
    def keep_artifacts!
621
      self.update(artifacts_expire_at: nil)
622
      self.job_artifacts.update_all(expire_at: nil)
623 624
    end

625 626 627 628 629 630 631
    def artifacts_file_for_type(type)
      file = job_artifacts.find_by(file_type: Ci::JobArtifact.file_types[type])&.file
      # TODO: to be removed once legacy artifacts is removed
      file ||= legacy_artifacts_file if type == :archive
      file
    end

632
    def coverage_regex
633
      super || project.try(:build_coverage_regex)
634 635
    end

636
    def steps
T
Tomasz Maczukin 已提交
637 638
      [Gitlab::Ci::Build::Step.from_commands(self),
       Gitlab::Ci::Build::Step.from_after_script(self)].compact
639 640 641
    end

    def image
642
      Gitlab::Ci::Build::Image.from_image(self)
643 644 645
    end

    def services
646
      Gitlab::Ci::Build::Image.from_services(self)
647 648 649
    end

    def cache
M
Matija Čupić 已提交
650 651 652 653
      cache = options[:cache]

      if cache && project.jobs_cache_index
        cache = cache.merge(
654
          key: "#{cache[:key]}-#{project.jobs_cache_index}")
655
      end
M
Matija Čupić 已提交
656 657

      [cache]
658 659
    end

660
    def credentials
661
      Gitlab::Ci::Build::Credentials::Factory.new(self).create!
662 663
    end

T
Tomasz Maczukin 已提交
664
    def dependencies
665 666
      return [] if empty_dependencies?

T
Tomasz Maczukin 已提交
667 668
      depended_jobs = depends_on_builds

669
      return depended_jobs unless options[:dependencies].present?
T
Tomasz Maczukin 已提交
670

671 672
      depended_jobs.select do |job|
        options[:dependencies].include?(job.name)
T
Tomasz Maczukin 已提交
673 674 675
      end
    end

676 677 678 679
    def empty_dependencies?
      options[:dependencies]&.empty?
    end

K
Kamil Trzciński 已提交
680
    def has_valid_build_dependencies?
K
Kamil Trzciński 已提交
681
      return true if Feature.enabled?('ci_disable_validates_dependencies')
682

K
Kamil Trzciński 已提交
683
      dependencies.all?(&:valid_dependency?)
684 685
    end

K
Kamil Trzciński 已提交
686
    def valid_dependency?
S
Shinya Maeda 已提交
687 688 689 690 691 692
      return false if artifacts_expired?
      return false if erased?

      true
    end

693 694 695 696 697 698 699 700
    def runner_required_feature_names
      strong_memoize(:runner_required_feature_names) do
        RUNNER_FEATURES.select do |feature, method|
          method.call(self)
        end.keys
      end
    end

701
    def supported_runner?(features)
702
      runner_required_feature_names.all? do |feature_name|
K
Kamil Trzciński 已提交
703
        features&.dig(feature_name)
704 705 706
      end
    end

707
    def publishes_artifacts_reports?
708
      options&.dig(:artifacts, :reports)&.any?
709 710
    end

711 712 713 714
    def hide_secrets(trace)
      return unless trace

      trace = trace.dup
715
      Gitlab::Ci::MaskSecret.mask!(trace, project.runners_token) if project
K
Kamil Trzciński 已提交
716
      Gitlab::Ci::MaskSecret.mask!(trace, token) if token
717 718 719
      trace
    end

720
    def serializable_hash(options = {})
J
James Lopez 已提交
721
      super(options).merge(when: read_attribute(:when))
722 723
    end

F
Francisco Javier López 已提交
724 725 726 727
    def has_terminal?
      running? && runner_session_url.present?
    end

S
Shinya Maeda 已提交
728 729
    def collect_test_reports!(test_reports)
      test_reports.get_suite(group_name).tap do |test_suite|
730
        each_report(Ci::JobArtifact::TEST_REPORT_FILE_TYPES) do |file_type, blob|
G
Gilbert Roulot 已提交
731
          Gitlab::Ci::Parsers.fabricate!(file_type).parse!(blob, test_suite)
S
Shinya Maeda 已提交
732 733 734 735
        end
      end
    end

736 737 738 739 740 741
    # Virtual deployment status depending on the environment status.
    def deployment_status
      return nil unless starts_environment?

      if success?
        return successful_deployment_status
S
Shinya Maeda 已提交
742
      elsif failed?
743 744 745 746 747 748
        return :failed
      end

      :creating
    end

749 750
    private

751 752 753 754 755 756 757
    def erase_old_artifacts!
      # TODO: To be removed once we get rid of
      remove_artifacts_file!
      remove_artifacts_metadata!
      save
    end

758
    def successful_deployment_status
S
Shinya Maeda 已提交
759 760 761 762
      if deployment&.last?
        :last
      else
        :out_of_date
763 764 765
      end
    end

766 767 768 769
    def each_report(report_types)
      job_artifacts_for_types(report_types).each do |report_artifact|
        report_artifact.each_blob do |blob|
          yield report_artifact.file_type, blob
S
Shinya Maeda 已提交
770 771 772 773
        end
      end
    end

774 775 776 777 778
    def job_artifacts_for_types(report_types)
      # Use select to leverage cached associations and avoid N+1 queries
      job_artifacts.select { |artifact| artifact.file_type.in?(report_types) }
    end

L
Lin Jen-Shin 已提交
779
    def update_artifacts_size
K
Kamil Trzcinski 已提交
780
      self.artifacts_size = legacy_artifacts_file&.size
L
Lin Jen-Shin 已提交
781 782
    end

783
    def erase_trace!
784
      trace.erase!
785 786 787
    end

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

791
    def unscoped_project
K
Kamil Trzciński 已提交
792
      @unscoped_project ||= Project.unscoped.find_by(id: project_id)
793 794
    end

795
    def environment_url
796
      options&.dig(:environment, :url) || persisted_environment&.external_url
797 798
    end

799 800 801 802 803
    # The format of the retry option changed in GitLab 11.5: Before it was
    # integer only, after it is a hash. New builds are created with the new
    # format, but builds created before GitLab 11.5 and saved in database still
    # have the old integer only format. This method returns the retry option
    # normalized as a hash in 11.5+ format.
M
Markus Doits 已提交
804
    def normalized_retry
805 806 807 808 809
      strong_memoize(:normalized_retry) do
        value = options&.dig(:retry)
        value = value.is_a?(Integer) ? { max: value } : value.to_h
        value.with_indifferent_access
      end
M
Markus Doits 已提交
810 811
    end

812 813
    def build_attributes_from_config
      return {} unless pipeline.config_processor
814

815 816
      pipeline.config_processor.build_attributes(name)
    end
817

818 819 820
    def update_project_statistics_after_save
      update_project_statistics(read_attribute(:artifacts_size).to_i - artifacts_size_was.to_i)
    end
821

822 823
    def update_project_statistics_after_destroy
      update_project_statistics(-artifacts_size)
824
    end
825

826 827 828 829 830 831
    def update_project_statistics(difference)
      ProjectStatistics.increment_statistic(project_id, :build_artifacts_size, difference)
    end

    def project_destroyed?
      project.pending_delete?
832
    end
D
Douwe Maan 已提交
833 834
  end
end