Class: Ci::Build

Constant Summary collapse

RUNNER_FEATURES =
{
  upload_multiple_artifacts: -> (build) { build.publishes_artifacts_reports? },
  refspecs: -> (build) { build.merge_request_ref? },
  artifacts_exclude: -> (build) { build.supports_artifacts_exclude? },
  multi_build_steps: -> (build) { build.multi_build_steps? },
  return_exit_code: -> (build) { build.exit_codes_defined? },
  fallback_cache_keys: -> (build) { build.fallback_cache_keys_defined? }
}.freeze
DEGRADATION_THRESHOLD_VARIABLE_NAME =
'DEGRADATION_THRESHOLD'
RUNNERS_STATUS_CACHE_EXPIRATION =
1.minute
DEPLOYMENT_NAMES =
%w[deploy release rollout].freeze

Constants included from BulkInsertableTags

Ci::BulkInsertableTags::BULK_INSERT_TAG_THREAD_KEY

Constants included from TaggableQueries

TaggableQueries::MAX_TAGS_IDS, TaggableQueries::TooManyTagsError

Constants included from HasStatus

HasStatus::ACTIVE_STATUSES, HasStatus::ALIVE_STATUSES, HasStatus::AVAILABLE_STATUSES, HasStatus::BLOCKED_STATUS, HasStatus::CANCELABLE_STATUSES, HasStatus::COMPLETED_STATUSES, HasStatus::DEFAULT_STATUS, HasStatus::IGNORED_STATUSES, HasStatus::ORDERED_STATUSES, HasStatus::PASSED_WITH_WARNINGS_STATUSES, HasStatus::STARTED_STATUSES, HasStatus::STATUSES_ENUM, HasStatus::STOPPED_STATUSES, HasStatus::UnknownStatusError

Constants included from Partitionable

Partitionable::MUTEX

Constants inherited from ApplicationRecord

ApplicationRecord::MAX_PLUCK

Constants included from ResetOnUnionError

ResetOnUnionError::MAX_RESET_PERIOD

Instance Attribute Summary

Attributes included from Importable

#imported, #importing

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Gitlab::Utils::Override

extended, extensions, included, method_added, override, prepended, queue_verification, verify!

Methods included from TrackEnvironmentUsage

#count_user_deployment?, #deployment_name?, #track_deployment_usage, #track_verify_environment_usage, #verifies_environment?

Methods included from HasRef

#branch?, #git_ref, #ref_slug

Methods included from Presentable

#present

Methods included from AfterCommitQueue

#run_after_commit, #run_after_commit_or_now

Methods included from Deployable

#actual_persisted_environment, #deployment_job?, #deployment_status, #environment_action, #environment_slug, #environment_status, #environment_tier, #environment_tier_from_options, #environment_url, #expanded_environment_name, #expanded_kubernetes_namespace, #has_environment_keyword?, #on_stop, #outdated_deployment?, #persisted_environment, #persisted_environment=, #stop_action_successful?, #stops_environment?, #successful_deployment_status

Methods included from Contextable

#scoped_variables, #simple_variables, #simple_variables_without_dependencies, #track_duration

Methods included from BulkInsertableTags

#save_tags, with_bulk_insert_tags

Methods inherited from Processable

#aggregated_needs_names, #all_dependencies, #dependency_variables, #ensure_scheduling_type!, #expanded_environment_name, #find_legacy_scheduling_type, #needs_attributes, #other_manual_actions, #persisted_environment, populate_scheduling_type!, #retryable?, #scheduling_type_dag?, select_with_aggregated_needs, #when, #with_resource_group?

Methods included from Metadatable

#cancel_gracefully?, #degenerate!, #degenerated?, #enqueue_immediately?, #ensure_metadata, #has_exposed_artifacts?, #id_tokens=, #id_tokens?, #interruptible, #interruptible=, #options, #options=, #set_enqueue_immediately!, #yaml_variables, #yaml_variables=

Methods inherited from CommitStatus

#auto_canceled?, #duration, #expire_etag_cache!, #group_name, #importing?, #latest?, locking_enabled?, #locking_enabled?, names, #queued_duration, #recoverable?, #resource_parent, #retryable?, #sortable_name, #stage_name, switch_table_names, #to_ability_name, update_as_processed!, #update_older_statuses_retried!

Methods included from TaggableQueries

#tags_ids

Methods included from BulkInsertableAssociations

#bulk_insert_associations!, bulk_inserts_enabled?, with_bulk_insert

Methods included from HasStatus

#active?, #blocked?, #complete?, #incomplete?, #started?

Methods inherited from ApplicationRecord

table_name_prefix

Methods inherited from ApplicationRecord

cached_column_list, #create_or_load_association, declarative_enum, default_select_columns, id_in, id_not_in, iid_in, pluck_primary_key, primary_key_in, #readable_by?, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, #to_ability_name, underscore, where_exists, where_not_exists, with_fast_read_statement_timeout, without_order

Class Method Details

.build_matchers(project) ⇒ Object



344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'app/models/ci/build.rb', line 344

def self.build_matchers(project)
  unique_params = [
    :protected,
    Arel.sql("(#{arel_tag_names_array.to_sql})")
  ]

  group(*unique_params).pluck('array_agg(id)', *unique_params).map do |values|
    Gitlab::Ci::Matching::BuildMatcher.new({
      build_ids: values[0],
      protected: values[1],
      tag_list: values[2],
      project: project
    })
  end
end

.clone_accessorsObject



206
207
208
209
210
211
212
213
# File 'app/models/ci/build.rb', line 206

def clone_accessors
  %i[pipeline project ref tag options name
     allow_failure stage stage_idx trigger_request
     yaml_variables when environment coverage_regex
     description tag_list protected needs_attributes
     job_variables_attributes resource_group scheduling_type
     ci_stage partition_id id_tokens].freeze
end

.keep_artifacts!Object



754
755
756
757
# File 'app/models/ci/build.rb', line 754

def self.keep_artifacts!
  update_all(artifacts_expire_at: nil)
  Ci::JobArtifact.where(job: self.select(:id)).update_all(expire_at: nil)
end

.model_nameObject

This is needed for url_for to work, as the controller is JobsController



198
199
200
# File 'app/models/ci/build.rb', line 198

def model_name
  ActiveModel::Name.new(self, nil, 'job')
end

.with_preloadsObject



202
203
204
# File 'app/models/ci/build.rb', line 202

def with_preloads
  preload(:job_artifacts_archive, :job_artifacts, :tags, project: [:namespace])
end

Instance Method Details

#action?Boolean

Returns:

  • (Boolean)


420
421
422
# File 'app/models/ci/build.rb', line 420

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

#all_met_to_become_pending?Boolean

Returns:

  • (Boolean)


441
442
443
# File 'app/models/ci/build.rb', line 441

def all_met_to_become_pending?
  super && !any_unmet_prerequisites?
end

#all_queuing_entriesObject

We can have only one queuing entry or running build tracking entry, because there is a unique index on ‘build_id` in each table, but we need a relation to remove these entries more efficiently in a single statement without actually loading data.



985
986
987
# File 'app/models/ci/build.rb', line 985

def all_queuing_entries
  ::Ci::PendingBuild.where(build_id: id)
end

#all_runtime_metadataObject



989
990
991
# File 'app/models/ci/build.rb', line 989

def 
  ::Ci::RunningBuild.where(build_id: id)
end

#allow_git_fetchObject



563
564
565
# File 'app/models/ci/build.rb', line 563

def allow_git_fetch
  project.build_allow_git_fetch
end

#allowed_to_fail_with_code?(exit_code) ⇒ Boolean

Returns:

  • (Boolean)


1007
1008
1009
1010
1011
1012
# File 'app/models/ci/build.rb', line 1007

def allowed_to_fail_with_code?(exit_code)
  options
    .dig(:allow_failure_criteria, :exit_codes)
    .to_a
    .include?(exit_code)
end

#any_runners_available?Boolean

Returns:

  • (Boolean)


677
678
679
680
681
# File 'app/models/ci/build.rb', line 677

def any_runners_available?
  cache_for_available_runners do
    project.active_runners.exists?
  end
end

#any_runners_online?Boolean

Returns:

  • (Boolean)


671
672
673
674
675
# File 'app/models/ci/build.rb', line 671

def any_runners_online?
  cache_for_online_runners do
    project.any_online_runners? { |runner| runner.match_build_if_online?(self) }
  end
end

#any_unmet_prerequisites?Boolean

Returns:

  • (Boolean)


445
446
447
# File 'app/models/ci/build.rb', line 445

def any_unmet_prerequisites?
  prerequisites.present?
end

#apple_app_store_variablesObject



529
530
531
532
533
# File 'app/models/ci/build.rb', line 529

def apple_app_store_variables
  return [] unless apple_app_store_integration.try(:activated?)

  Gitlab::Ci::Variables::Collection.new(apple_app_store_integration.ci_variables(protected_ref: pipeline.protected_ref?))
end

#archived?Boolean

Returns:

  • (Boolean)


401
402
403
404
405
406
# File 'app/models/ci/build.rb', line 401

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

#artifact_for_type(type) ⇒ Object



764
765
766
767
768
# File 'app/models/ci/build.rb', line 764

def artifact_for_type(type)
  file_types = Ci::JobArtifact.associated_file_types_for(type)
  file_types_ids = file_types&.map { |file_type| Ci::JobArtifact.file_types[file_type] }
  job_artifacts.find_by(file_type: file_types_ids)
end

#artifacts?Boolean

Returns:

  • (Boolean)


600
601
602
# File 'app/models/ci/build.rb', line 600

def artifacts?
  !artifacts_expired? && artifacts_file&.exists?
end

#artifacts_expire_inObject



734
735
736
# File 'app/models/ci/build.rb', line 734

def artifacts_expire_in
  artifacts_expire_at - Time.current if artifacts_expire_at
end

#artifacts_expire_in=(value) ⇒ Object



738
739
740
741
742
743
# File 'app/models/ci/build.rb', line 738

def artifacts_expire_in=(value)
  self.artifacts_expire_at =
    if value
      ChronicDuration.parse(value, use_complete_matcher: true)&.seconds&.from_now
    end
end

#artifacts_expired?Boolean

Returns:

  • (Boolean)


730
731
732
# File 'app/models/ci/build.rb', line 730

def artifacts_expired?
  artifacts_expire_at && artifacts_expire_at < Time.current
end

#artifacts_expose_asObject



631
632
633
# File 'app/models/ci/build.rb', line 631

def artifacts_expose_as
  options.dig(:artifacts, :expose_as)
end

#artifacts_fileObject



588
589
590
# File 'app/models/ci/build.rb', line 588

def artifacts_file
  job_artifacts_archive&.file
end

#artifacts_metadataObject



596
597
598
# File 'app/models/ci/build.rb', line 596

def 
  &.file
end

#artifacts_metadata?Boolean

Returns:

  • (Boolean)


615
616
617
# File 'app/models/ci/build.rb', line 615

def artifacts_metadata?
  artifacts? && &.exists?
end

#artifacts_metadata_entry(path, **options) ⇒ Object



711
712
713
714
715
716
717
718
719
720
# File 'app/models/ci/build.rb', line 711

def (path, **options)
  .open do ||
     = Gitlab::Ci::Build::Artifacts::Metadata.new(
      ,
      path,
      **options)

    .to_entry
  end
end

#artifacts_pathsObject



635
636
637
# File 'app/models/ci/build.rb', line 635

def artifacts_paths
  options.dig(:artifacts, :paths)
end

#artifacts_public?Boolean

Returns:

  • (Boolean)


701
702
703
704
705
706
707
708
709
# File 'app/models/ci/build.rb', line 701

def artifacts_public?
  return true unless Feature.enabled?(:non_public_artifacts, type: :development)

  artifacts_public = options.dig(:artifacts, :public)

  return true if artifacts_public.nil? # Default artifacts:public to true

  options.dig(:artifacts, :public)
end

#artifacts_sizeObject



592
593
594
# File 'app/models/ci/build.rb', line 592

def artifacts_size
  job_artifacts_archive&.size
end

#auto_retry_allowed?Boolean

Returns:

  • (Boolean)


371
372
373
# File 'app/models/ci/build.rb', line 371

def auto_retry_allowed?
  auto_retry.allowed?
end

#auto_retry_expected?Boolean

Returns:

  • (Boolean)


375
376
377
# File 'app/models/ci/build.rb', line 375

def auto_retry_expected?
  failed? && auto_retry_allowed?
end

#available_artifacts?Boolean

This method is similar to #artifacts? but it includes the artifacts locking mechanics. A new method was created to prevent breaking existing behavior and avoid introducing N+1s.

Returns:

  • (Boolean)


611
612
613
# File 'app/models/ci/build.rb', line 611

def available_artifacts?
  (!artifacts_expired? || pipeline.artifacts_locked?) && job_artifacts_archive&.exists?
end

#browsable_artifacts?Boolean

Returns:

  • (Boolean)


697
698
699
# File 'app/models/ci/build.rb', line 697

def browsable_artifacts?
  artifacts_metadata?
end

#build_matcherObject



360
361
362
363
364
365
366
367
368
369
# File 'app/models/ci/build.rb', line 360

def build_matcher
  strong_memoize(:build_matcher) do
    Gitlab::Ci::Matching::BuildMatcher.new({
      protected: protected?,
      tag_list: tag_list,
      build_ids: [id],
      project: project
    })
  end
end

#cacheObject



788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
# File 'app/models/ci/build.rb', line 788

def cache
  cache = Array.wrap(options[:cache])

  cache.each do |single_cache|
    single_cache[:fallback_keys] = [] unless single_cache.key?(:fallback_keys)
  end

  if project.jobs_cache_index
    cache = cache.map do |single_cache|
      cache = single_cache.merge(key: "#{single_cache[:key]}-#{project.jobs_cache_index}")
      fallback = cache.slice(:fallback_keys).transform_values { |keys| keys.map { |key| "#{key}-#{project.jobs_cache_index}" } }
      cache.merge(fallback.compact)
    end
  end

  return cache unless project.ci_separated_caches

  cache.map do |entry|
    type_suffix = !entry[:unprotect] && pipeline.protected_ref? ? 'protected' : 'non_protected'

    cache = entry.merge(key: "#{entry[:key]}-#{type_suffix}")
    fallback = cache.slice(:fallback_keys).transform_values { |keys| keys.map { |key| "#{key}-#{type_suffix}" } }
    cache.merge(fallback.compact)
  end
end

#cancelable?Boolean

rubocop: enable CodeReuse/ServiceClass

Returns:

  • (Boolean)


432
433
434
# File 'app/models/ci/build.rb', line 432

def cancelable?
  active? || created?
end

#clone(current_user:, new_job_variables_attributes: []) ⇒ Object



1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
# File 'app/models/ci/build.rb', line 1022

def clone(current_user:, new_job_variables_attributes: [])
  new_build = super

  if action? && new_job_variables_attributes.any?
    new_build.job_variables = []
    new_build.job_variables_attributes = new_job_variables_attributes
  end

  new_build
end

#collect_accessibility_reports!(accessibility_report) ⇒ Object



892
893
894
895
896
897
898
# File 'app/models/ci/build.rb', line 892

def collect_accessibility_reports!(accessibility_report)
  each_report(Ci::JobArtifact.file_types_for_report(:accessibility)) do |file_type, blob|
    Gitlab::Ci::Parsers.fabricate!(file_type).parse!(blob, accessibility_report)
  end

  accessibility_report
end

#collect_codequality_reports!(codequality_report) ⇒ Object



900
901
902
903
904
905
906
# File 'app/models/ci/build.rb', line 900

def collect_codequality_reports!(codequality_report)
  each_report(Ci::JobArtifact.file_types_for_report(:codequality)) do |file_type, blob|
    Gitlab::Ci::Parsers.fabricate!(file_type).parse!(blob, codequality_report, { project: project, commit_sha: pipeline.sha })
  end

  codequality_report
end

#collect_terraform_reports!(terraform_reports) ⇒ Object



908
909
910
911
912
913
914
# File 'app/models/ci/build.rb', line 908

def collect_terraform_reports!(terraform_reports)
  each_report(::Ci::JobArtifact.file_types_for_report(:terraform)) do |file_type, blob, report_artifact|
    ::Gitlab::Ci::Parsers.fabricate!(file_type).parse!(blob, terraform_reports, artifact: report_artifact)
  end

  terraform_reports
end

#collect_test_reports!(test_reports) ⇒ Object



884
885
886
887
888
889
890
# File 'app/models/ci/build.rb', line 884

def collect_test_reports!(test_reports)
  each_report(Ci::JobArtifact.file_types_for_report(:test)) do |file_type, blob|
    Gitlab::Ci::Parsers.fabricate!(file_type).parse!(blob, test_reports, job: self)
  end

  test_reports
end

#create_queuing_entry!Object



975
976
977
# File 'app/models/ci/build.rb', line 975

def create_queuing_entry!
  ::Ci::PendingBuild.upsert_from_build!(self)
end

#credentialsObject



818
819
820
# File 'app/models/ci/build.rb', line 818

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

#custom_contextsObject



655
656
657
# File 'app/models/ci/build.rb', line 655

def custom_contexts
  []
end

#debug_mode?Boolean

Returns:

  • (Boolean)


957
958
959
960
961
962
# File 'app/models/ci/build.rb', line 957

def debug_mode?
  # perform the check on both sides in case the runner version is old
  debug_trace_enabled? ||
    Gitlab::Utils.to_boolean(variables['CI_DEBUG_SERVICES']&.value, default: false) ||
    Gitlab::Utils.to_boolean(variables['CI_DEBUG_TRACE']&.value, default: false)
end

#degradation_thresholdObject



938
939
940
941
# File 'app/models/ci/build.rb', line 938

def degradation_threshold
  var = yaml_variables.find { |v| v[:key] == DEGRADATION_THRESHOLD_VARIABLE_NAME }
  var[:value]&.to_i if var
end

#dependency_proxy_variablesObject



514
515
516
517
518
519
520
521
# File 'app/models/ci/build.rb', line 514

def dependency_proxy_variables
  Gitlab::Ci::Variables::Collection.new.tap do |variables|
    break variables unless Gitlab.config.dependency_proxy.enabled

    variables.append(key: 'CI_DEPENDENCY_PROXY_USER', value: ::Gitlab::Auth::CI_JOB_USER)
    variables.append(key: 'CI_DEPENDENCY_PROXY_PASSWORD', value: token.to_s, public: false, masked: true)
  end
end

#deploy_token_variablesObject



505
506
507
508
509
510
511
512
# File 'app/models/ci/build.rb', line 505

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, masked: true)
  end
end

#detailed_status(current_user) ⇒ Object



379
380
381
382
383
# File 'app/models/ci/build.rb', line 379

def detailed_status(current_user)
  Gitlab::Ci::Status::Build::Factory
    .new(present, current_user)
    .fabricate!
end

#doom!Object

Consider this object to have a structural integrity problems



921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
# File 'app/models/ci/build.rb', line 921

def doom!
  transaction do
    update_columns(status: :failed, failure_reason: :data_integrity_failure)
    all_queuing_entries.delete_all
    .delete_all
  end

  deployment&.sync_status_with(self)

  Gitlab::AppLogger.info(
    message: 'Build doomed',
    class: self.class.name,
    build_id: id,
    pipeline_id: pipeline_id,
    project_id: project_id)
end

#drop_with_exit_code!(failure_reason, exit_code) ⇒ Object



964
965
966
967
968
969
# File 'app/models/ci/build.rb', line 964

def drop_with_exit_code!(failure_reason, exit_code)
  failure_reason ||= :unknown_failure
  result = drop!(::Gitlab::Ci::Build::Status::Reason.new(self, failure_reason, exit_code))
  ::Ci::TrackFailedBuildWorker.perform_async(id, exit_code, failure_reason)
  result
end

#each_report(report_types) ⇒ Object



1014
1015
1016
1017
1018
1019
1020
# File 'app/models/ci/build.rb', line 1014

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, report_artifact
    end
  end
end

#ensure_trace_metadata!Object



627
628
629
# File 'app/models/ci/build.rb', line 627

def ensure_trace_metadata!
  Ci::BuildTraceMetadata.find_or_upsert_for!(id, partition_id)
end

#erasable?Boolean

Returns:

  • (Boolean)


722
723
724
# File 'app/models/ci/build.rb', line 722

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

#erased?Boolean

Returns:

  • (Boolean)


726
727
728
# File 'app/models/ci/build.rb', line 726

def erased?
  !erased_at.nil?
end

#execute_hooksObject



687
688
689
690
691
692
693
694
695
# File 'app/models/ci/build.rb', line 687

def execute_hooks
  return unless project
  return if user&.blocked?

  ActiveRecord::Associations::Preloader.new(records: [self], associations: { runner: :tags }).call

  project.execute_hooks(build_data.dup, :job_hooks) if project.has_active_hooks?(:job_hooks)
  project.execute_integrations(build_data.dup, :job_hooks) if project.has_active_integrations?(:job_hooks)
end

#exit_codes_defined?Boolean

Returns:

  • (Boolean)


971
972
973
# File 'app/models/ci/build.rb', line 971

def exit_codes_defined?
  options.dig(:allow_failure_criteria, :exit_codes).present?
end

#fallback_cache_keys_defined?Boolean

Returns:

  • (Boolean)


814
815
816
# File 'app/models/ci/build.rb', line 814

def fallback_cache_keys_defined?
  Array.wrap(options[:cache]).any? { |cache| cache[:fallback_keys].present? }
end

#featuresObject



541
542
543
544
545
546
# File 'app/models/ci/build.rb', line 541

def features
  {
    trace_sections: true,
    failure_reasons: self.class.failure_reasons.keys
  }
end

#google_play_variablesObject



535
536
537
538
539
# File 'app/models/ci/build.rb', line 535

def google_play_variables
  return [] unless google_play_integration.try(:activated?)

  Gitlab::Ci::Variables::Collection.new(google_play_integration.ci_variables(protected_ref: pipeline.protected_ref?))
end

#harbor_variablesObject



523
524
525
526
527
# File 'app/models/ci/build.rb', line 523

def harbor_variables
  return [] unless harbor_integration.try(:activated?)

  Gitlab::Ci::Variables::Collection.new(harbor_integration.ci_variables)
end

#has_archived_trace?Boolean

Returns:

  • (Boolean)


584
585
586
# File 'app/models/ci/build.rb', line 584

def has_archived_trace?
  trace.archived?
end

#has_expired_locked_archive_artifacts?Boolean

Returns:

  • (Boolean)


745
746
747
748
# File 'app/models/ci/build.rb', line 745

def has_expired_locked_archive_artifacts?
  locked_artifacts? &&
    artifacts_expire_at.present? && artifacts_expire_at < Time.current
end

#has_expiring_archive_artifacts?Boolean

Returns:

  • (Boolean)


750
751
752
# File 'app/models/ci/build.rb', line 750

def has_expiring_archive_artifacts?
  has_expiring_artifacts? && job_artifacts_archive.present?
end

#has_job_artifacts?Boolean

Returns:

  • (Boolean)


619
620
621
# File 'app/models/ci/build.rb', line 619

def has_job_artifacts?
  job_artifacts.any?
end

#has_live_trace?Boolean

Returns:

  • (Boolean)


580
581
582
# File 'app/models/ci/build.rb', line 580

def has_live_trace?
  trace.live?
end

#has_tags?Boolean

Returns:

  • (Boolean)


667
668
669
# File 'app/models/ci/build.rb', line 667

def has_tags?
  tag_list.any?
end

#has_terminal?Boolean

Returns:

  • (Boolean)


880
881
882
# File 'app/models/ci/build.rb', line 880

def has_terminal?
  running? && runner_session_url.present?
end

#has_test_reports?Boolean

Returns:

  • (Boolean)


623
624
625
# File 'app/models/ci/build.rb', line 623

def has_test_reports?
  job_artifacts.of_report_type(:test).exists?
end

#has_trace?Boolean

Returns:

  • (Boolean)


576
577
578
# File 'app/models/ci/build.rb', line 576

def has_trace?
  trace.exist?
end

#has_valid_build_dependencies?Boolean

Returns:

  • (Boolean)


822
823
824
# File 'app/models/ci/build.rb', line 822

def has_valid_build_dependencies?
  dependencies.valid?
end

#hide_secrets(data, metrics = ::Gitlab::Ci::Trace::Metrics.new) ⇒ Object



863
864
865
866
867
868
869
870
871
872
873
874
# File 'app/models/ci/build.rb', line 863

def hide_secrets(data, metrics = ::Gitlab::Ci::Trace::Metrics.new)
  return unless trace

  data.dup.tap do |trace|
    Gitlab::Ci::MaskSecret.mask!(trace, project.runners_token) if project
    Gitlab::Ci::MaskSecret.mask!(trace, token) if token

    if trace != data
      metrics.increment_trace_operation(operation: :mutated)
    end
  end
end

#imageObject



780
781
782
# File 'app/models/ci/build.rb', line 780

def image
  Gitlab::Ci::Build::Image.from_image(self)
end

#invalid_dependenciesObject



826
827
828
# File 'app/models/ci/build.rb', line 826

def invalid_dependencies
  dependencies.invalid_local
end

#job_artifact_typesObject



1033
1034
1035
# File 'app/models/ci/build.rb', line 1033

def job_artifact_types
  job_artifacts.map(&:file_type)
end

#job_variables_attributesObject



997
998
999
1000
1001
1002
1003
1004
1005
# File 'app/models/ci/build.rb', line 997

def job_variables_attributes
  strong_memoize(:job_variables_attributes) do
    job_variables.internal_source.map do |variable|
      variable.attributes.except('id', 'job_id', 'encrypted_value', 'encrypted_value_iv').tap do |attrs|
        attrs[:value] = variable.value
      end
    end
  end
end

#keep_artifacts!Object



759
760
761
762
# File 'app/models/ci/build.rb', line 759

def keep_artifacts!
  update(artifacts_expire_at: nil)
  job_artifacts.update_all(expire_at: nil)
end

#locked_artifacts?Boolean

Returns:

  • (Boolean)


604
605
606
# File 'app/models/ci/build.rb', line 604

def locked_artifacts?
  pipeline.artifacts_locked? && artifacts_file&.exists?
end

#max_test_cases_per_reportObject



951
952
953
954
955
# File 'app/models/ci/build.rb', line 951

def max_test_cases_per_report
  # NOTE: This is temporary and will be replaced later by a value
  # that would come from an actual application limit.
  ::Gitlab.com? ? 500_000 : 0
end

#merge_requestObject



548
549
550
551
552
# File 'app/models/ci/build.rb', line 548

def merge_request
  strong_memoize(:merge_request) do
    pipeline.all_merge_requests.order(iid: :asc).first
  end
end

#multi_build_steps?Boolean

Returns:

  • (Boolean)


859
860
861
# File 'app/models/ci/build.rb', line 859

def multi_build_steps?
  options.dig(:release)&.any?
end

#needs_touch?Boolean

Returns:

  • (Boolean)


639
640
641
# File 'app/models/ci/build.rb', line 639

def needs_touch?
  Time.current - updated_at > 15.minutes.to_i
end

#options_scheduled_atObject



416
417
418
# File 'app/models/ci/build.rb', line 416

def options_scheduled_at
  ChronicDuration.parse(options[:start_in], use_complete_matcher: true)&.seconds&.from_now
end

#other_scheduled_actionsObject



385
386
387
# File 'app/models/ci/build.rb', line 385

def other_scheduled_actions
  pipeline.scheduled_actions.reject { |action| action.name == name }
end

#pages_generator?Boolean

Returns:

  • (Boolean)


389
390
391
392
# File 'app/models/ci/build.rb', line 389

def pages_generator?
  Gitlab.config.pages.enabled &&
    name == 'pages'
end

#pages_path_prefixObject

overridden on EE



395
# File 'app/models/ci/build.rb', line 395

def pages_path_prefix; end

#persisted_environment_variablesObject



492
493
494
495
496
497
498
499
500
501
502
503
# File 'app/models/ci/build.rb', line 492

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

    variables.append(key: 'CI_ENVIRONMENT_SLUG', value: environment_slug)

    # 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

#persisted_variablesObject



472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
# File 'app/models/ci/build.rb', line 472

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, masked: true)
      .append(key: 'CI_JOB_STARTED_AT', value: started_at&.iso8601)
      .append(key: 'CI_REGISTRY_USER', value: ::Gitlab::Auth::CI_JOB_USER)
      .append(key: 'CI_REGISTRY_PASSWORD', value: token.to_s, public: false, masked: true)
      .append(key: 'CI_REPOSITORY_URL', value: repo_url.to_s, public: false)
      .concat(deploy_token_variables)
      .concat(harbor_variables)
      .concat(apple_app_store_variables)
      .concat(google_play_variables)
  end
end

#play(current_user, job_variables_attributes = nil) ⇒ Object

rubocop: disable CodeReuse/ServiceClass



425
426
427
428
429
# File 'app/models/ci/build.rb', line 425

def play(current_user, job_variables_attributes = nil)
  Ci::PlayBuildService
    .new(project, current_user)
    .execute(self, job_variables_attributes)
end

#playable?Boolean

Returns:

  • (Boolean)


408
409
410
# File 'app/models/ci/build.rb', line 408

def playable?
  action? && !archived? && (manual? || scheduled? || retryable?)
end

#prerequisitesObject



449
450
451
# File 'app/models/ci/build.rb', line 449

def prerequisites
  Gitlab::Ci::Build::Prerequisite::Factory.new(self).unmet
end

#publishes_artifacts_reports?Boolean

Returns:

  • (Boolean)


851
852
853
# File 'app/models/ci/build.rb', line 851

def publishes_artifacts_reports?
  options&.dig(:artifacts, :reports)&.any?
end

#remove_pending_state!Object



943
944
945
# File 'app/models/ci/build.rb', line 943

def remove_pending_state!
  pending_state.try(:delete)
end

#remove_token!Object



647
648
649
# File 'app/models/ci/build.rb', line 647

def remove_token!
  update!(token_encrypted: nil)
end

#repo_urlObject



554
555
556
557
558
559
560
561
# File 'app/models/ci/build.rb', line 554

def repo_url
  return unless token

  auth = "#{::Gitlab::Auth::CI_JOB_USER}:#{token}@"
  project.http_url_to_repo.sub(%r{^https?://}) do |prefix|
    prefix + auth
  end
end

#report_artifactsObject



916
917
918
# File 'app/models/ci/build.rb', line 916

def report_artifacts
  job_artifacts.all_reports
end

#retries_countObject



436
437
438
# File 'app/models/ci/build.rb', line 436

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

#run_on_status_commit(&block) ⇒ Object



947
948
949
# File 'app/models/ci/build.rb', line 947

def run_on_status_commit(&block)
  status_commit_hooks.push(block)
end

#runnable?Boolean

Returns:

  • (Boolean)


397
398
399
# File 'app/models/ci/build.rb', line 397

def runnable?
  true
end

#runner_required_feature_namesObject



837
838
839
840
841
842
843
# File 'app/models/ci/build.rb', line 837

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

#runtime_hooksObject



776
777
778
# File 'app/models/ci/build.rb', line 776

def runtime_hooks
  Gitlab::Ci::Build::Hook.from_hooks(self)
end

#schedulable?Boolean

Returns:

  • (Boolean)


412
413
414
# File 'app/models/ci/build.rb', line 412

def schedulable?
  self.when == 'delayed' && options[:start_in].present?
end

#serializable_hash(options = {}) ⇒ Object



876
877
878
# File 'app/models/ci/build.rb', line 876

def serializable_hash(options = {})
  super(options).merge(when: read_attribute(:when))
end

#servicesObject



784
785
786
# File 'app/models/ci/build.rb', line 784

def services
  Gitlab::Ci::Build::Image.from_services(self)
end

#shared_runner_build?Boolean

Returns:

  • (Boolean)


993
994
995
# File 'app/models/ci/build.rb', line 993

def shared_runner_build?
  runner&.instance_type?
end

#stepsObject



770
771
772
773
774
# File 'app/models/ci/build.rb', line 770

def steps
  [Gitlab::Ci::Build::Step.from_commands(self),
   Gitlab::Ci::Build::Step.from_release(self),
   Gitlab::Ci::Build::Step.from_after_script(self)].compact
end

#stuck?Boolean

Returns:

  • (Boolean)


683
684
685
# File 'app/models/ci/build.rb', line 683

def stuck?
  pending? && !any_runners_online?
end

#supported_runner?(features) ⇒ Boolean

Returns:

  • (Boolean)


845
846
847
848
849
# File 'app/models/ci/build.rb', line 845

def supported_runner?(features)
  runner_required_feature_names.all? do |feature_name|
    features&.dig(feature_name)
  end
end

#supports_artifacts_exclude?Boolean

Returns:

  • (Boolean)


855
856
857
# File 'app/models/ci/build.rb', line 855

def supports_artifacts_exclude?
  options&.dig(:artifacts, :exclude)&.any?
end

#tag_listObject



659
660
661
662
663
664
665
# File 'app/models/ci/build.rb', line 659

def tag_list
  if tags.loaded?
    tags.map(&:name)
  else
    super
  end
end

#test_suite_nameObject



1037
1038
1039
1040
1041
1042
1043
# File 'app/models/ci/build.rb', line 1037

def test_suite_name
  if matrix_build?
    name
  else
    group_name
  end
end

#time_in_queue_secondsObject



1045
1046
1047
1048
1049
# File 'app/models/ci/build.rb', line 1045

def time_in_queue_seconds
  return if queued_at.nil?

  (::Time.current - queued_at).seconds.to_i
end

#traceObject



572
573
574
# File 'app/models/ci/build.rb', line 572

def trace
  Gitlab::Ci::Trace.new(self)
end

#triggered_by?(current_user) ⇒ Boolean

Returns:

  • (Boolean)


453
454
455
# File 'app/models/ci/build.rb', line 453

def triggered_by?(current_user)
  user == current_user
end

#update_coverageObject



567
568
569
570
# File 'app/models/ci/build.rb', line 567

def update_coverage
  coverage = trace.extract_coverage(coverage_regex)
  update(coverage: coverage) if coverage.present?
end

#valid_dependency?Boolean

Returns:

  • (Boolean)


830
831
832
833
834
835
# File 'app/models/ci/build.rb', line 830

def valid_dependency?
  return false if artifacts_expired? && !pipeline.artifacts_locked?
  return false if erased?

  true
end

#valid_token?(token) ⇒ Boolean

Returns:

  • (Boolean)


643
644
645
# File 'app/models/ci/build.rb', line 643

def valid_token?(token)
  self.token && token.present? && ActiveSupport::SecurityUtils.secure_compare(token, self.token)
end

#variablesObject

All variables, including persisted environment variables.



460
461
462
463
464
465
466
467
468
469
470
# File 'app/models/ci/build.rb', line 460

def variables
  strong_memoize(:variables) do
    Gitlab::Ci::Variables::Collection.new
      .concat(persisted_variables)
      .concat(dependency_proxy_variables)
      .concat(job_jwt_variables)
      .concat(scoped_variables)
      .concat(job_variables)
      .concat(persisted_environment_variables)
  end
end