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
TOKEN_PREFIX =
'glcbt-'

Constants included from BulkInsertableTags

Ci::BulkInsertableTags::BULK_INSERT_TAG_THREAD_KEY

Constants inherited from Processable

Processable::ACTIONABLE_WHEN

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::COMPLETED_WITH_MANUAL_STATUSES, HasStatus::DEFAULT_STATUS, HasStatus::EXECUTING_STATUSES, HasStatus::IGNORED_STATUSES, HasStatus::ORDERED_STATUSES, HasStatus::PASSED_WITH_WARNINGS_STATUSES, HasStatus::STARTED_STATUSES, HasStatus::STATUSES_ENUM, HasStatus::STOPPED_STATUSES, HasStatus::UnknownStatusError

Constants inherited from ApplicationRecord

ApplicationRecord::MAX_PLUCK

Constants included from HasCheckConstraints

HasCheckConstraints::NOT_NULL_CHECK_PATTERN

Constants included from ResetOnColumnErrors

ResetOnColumnErrors::MAX_RESET_PERIOD

Instance Attribute Summary

Attributes included from Importable

#importing, #user_contributions

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 ChronicDurationAttribute

#chronic_duration_attributes, #output_chronic_duration_attribute

Methods included from Taggable

#reload, #tag_list=

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

#accesses_environment?, #actual_persisted_environment, #deployment_job?, #deployment_status, #environment_action, #environment_auto_stop_in, #environment_options_for_permanent_storage, #environment_permanent_metadata, #environment_slug, #environment_status, #environment_tier, #environment_url, #expanded_auto_stop_in, #expanded_deployment_tier, #expanded_environment_name, #expanded_kubernetes_namespace, #has_environment_keyword?, #has_outdated_deployment?, #link_to_environment, #on_stop, #persisted_environment, #persisted_environment=, #prepares_environment?, #stop_action_successful?, #stops_environment?, #successful_deployment_status, #verifies_environment?

Methods included from Contextable

#manual_variables, #scoped_variables, #simple_variables, #simple_variables_without_dependencies, #track_duration, #unprotected_scoped_variables

Methods included from BulkInsertableTags

#save_tags, with_bulk_insert_tags

Methods inherited from Processable

#aggregated_needs_names, #all_dependencies, #archived?, #assign_resource_from_resource_group, #dependency_variables, #enqueue_immediately?, #ensure_scheduling_type!, #expanded_environment_name, fabricate, #find_legacy_scheduling_type, #job_dependencies_with_accessible_artifacts, #manual_confirmation_message, #manual_job?, #needs_attributes, #other_manual_actions, #persisted_environment, populate_scheduling_type!, #redis_state, #retryable?, #scheduling_type_dag?, #scoped_user, select_with_aggregated_needs, #set_enqueue_immediately!, #when, #with_resource_group?

Methods included from Metadatable

#artifacts_exposed_as, #artifacts_exposed_paths, #debug_trace_enabled?, #downstream_errors, #enable_debug_trace!, #exit_code, #exit_code=, #has_exposed_artifacts?, #id_tokens, #id_tokens=, #id_tokens?, #interruptible, #interruptible=, #options, #options=, #scoped_user_id, #secrets=, #timeout_human_readable_value, #timeout_source_value, #timeout_value, #update_timeout_state, #yaml_variables, #yaml_variables=

Methods inherited from CommitStatus

#archived?, #auto_canceled?, #duration, #exit_code=, #expire_etag_cache!, #group_name, #latest?, #locking_enabled?, locking_enabled?, names, #queued_duration, #recoverable?, #resource_parent, #retryable?, #sortable_name, #stage_name, #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?, #complete_or_manual?, #incomplete?, #started?

Methods included from Partitionable

registered_models

Methods inherited from ApplicationRecord

table_name_prefix

Methods inherited from ApplicationRecord

===, cached_column_list, #create_or_load_association, current_transaction, declarative_enum, default_select_columns, delete_all_returning, #deleted_from_database?, id_in, id_not_in, iid_in, nullable_column?, primary_key_in, #readable_by?, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, sharding_keys, #to_ability_name, underscore, where_exists, where_not_exists, with_fast_read_statement_timeout, without_order

Methods included from ResetOnColumnErrors

#reset_on_union_error, #reset_on_unknown_attribute_error

Class Method Details

.build_matchers(project) ⇒ Object



427
428
429
430
431
# File 'app/models/ci/build.rb', line 427

def self.build_matchers(project)
  return legacy_build_matchers(project) if Feature.disabled?(:ci_build_uses_job_definition_tag_list, project)

  new_build_matchers(project)
end

.clone_accessorsObject



277
278
279
280
281
282
283
# File 'app/models/ci/build.rb', line 277

def clone_accessors
  %i[pipeline project ref tag name allow_failure stage_idx when
    environment coverage_regex description tag_list protected
    needs_attributes job_variables_attributes resource_group
    scheduling_type timeout timeout_source debug_trace_enabled
    ci_stage partition_id execution_config_id inputs_attributes].freeze
end

.has_any_job_definition?Boolean

TODO: remove this method with ‘ci_builds_metadata`

Returns:

  • (Boolean)


495
496
497
# File 'app/models/ci/build.rb', line 495

def self.has_any_job_definition?
  left_joins(:job_definition_instance).limit(1).pick(:job_id).present?
end

.ids_in_merge_request(merge_request_id) ⇒ Object



481
482
483
# File 'app/models/ci/build.rb', line 481

def self.ids_in_merge_request(merge_request_id)
  in_merge_request(merge_request_id).pluck(:id)
end

.keep_artifacts!Object



489
490
491
492
# File 'app/models/ci/build.rb', line 489

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

.legacy_build_matchers(project) ⇒ Object



454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
# File 'app/models/ci/build.rb', line 454

def self.legacy_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

.model_nameObject

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



269
270
271
# File 'app/models/ci/build.rb', line 269

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

.new_build_matchers(project) ⇒ Object



433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
# File 'app/models/ci/build.rb', line 433

def self.new_build_matchers(project)
  unique_params = [
    :protected,
    Arel.sql(tag_names_array_query)
  ]

  pluck_params = [
    "array_agg(#{quoted_table_name}.id) as ids",
    *unique_params
  ]

  joins(:job_definition).group(*unique_params).pluck(*pluck_params).map do |values|
    Gitlab::Ci::Matching::BuildMatcher.new(
      build_ids: values[0],
      protected: values[1],
      tag_list: values[2],
      project: project
    )
  end
end

.supported_keyset_orderingsObject



285
286
287
# File 'app/models/ci/build.rb', line 285

def supported_keyset_orderings
  { id: [:desc] }
end

.tag_names_array_queryObject



470
471
472
473
474
475
476
477
478
479
# File 'app/models/ci/build.rb', line 470

def self.tag_names_array_query
  <<~SQL.squish
    (
      SELECT COALESCE(array_agg(tag_name ORDER BY tag_name), '{}')
        FROM jsonb_array_elements_text(
          #{Ci::JobDefinition.quoted_table_name}.config->'tag_list'
        ) AS tag_name
    )
  SQL
end

.taggings_join_modelObject



485
486
487
# File 'app/models/ci/build.rb', line 485

def self.taggings_join_model
  ::Ci::BuildTag
end

.with_preloadsObject



273
274
275
# File 'app/models/ci/build.rb', line 273

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

Instance Method Details

#action?Boolean

Returns:

  • (Boolean)


609
610
611
# File 'app/models/ci/build.rb', line 609

def action?
  ACTIONABLE_WHEN.include?(self.when)
end

#all_met_to_become_pending?Boolean

Returns:

  • (Boolean)


637
638
639
# File 'app/models/ci/build.rb', line 637

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.



1218
1219
1220
# File 'app/models/ci/build.rb', line 1218

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

#all_runtime_metadataObject



1222
1223
1224
# File 'app/models/ci/build.rb', line 1222

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

#allow_git_fetchObject



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

def allow_git_fetch
  project.build_allow_git_fetch
end

#allowed_to_fail_with_code?(exit_code) ⇒ Boolean

Returns:

  • (Boolean)


1248
1249
1250
1251
1252
1253
# File 'app/models/ci/build.rb', line 1248

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)


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

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

#any_runners_online?Boolean

Returns:

  • (Boolean)


869
870
871
872
873
# File 'app/models/ci/build.rb', line 869

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)


641
642
643
# File 'app/models/ci/build.rb', line 641

def any_unmet_prerequisites?
  prerequisites.present?
end

#apple_app_store_variablesObject



723
724
725
726
727
# File 'app/models/ci/build.rb', line 723

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

#artifact_access_setting_in_configObject



910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
# File 'app/models/ci/build.rb', line 910

def artifact_access_setting_in_config
  artifacts_public = options.dig(:artifacts, :public)
  artifacts_access = options.dig(:artifacts, :access)&.to_s

  if artifacts_public.present? && artifacts_access.present?
    raise ArgumentError, 'artifacts:public and artifacts:access are mutually exclusive'
  end

  return :public     if artifacts_public == true || artifacts_access == 'all'
  return :private    if artifacts_public == false || artifacts_access == 'developer'
  return :maintainer if artifacts_access == 'maintainer'
  return :none       if artifacts_access == 'none'

  :public
end

#artifact_for_type(type) ⇒ Object



972
973
974
975
976
# File 'app/models/ci/build.rb', line 972

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)


816
817
818
# File 'app/models/ci/build.rb', line 816

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

#artifacts_expire_inObject



949
950
951
# File 'app/models/ci/build.rb', line 949

def artifacts_expire_in
  artifacts_expire_at - Time.current if artifacts_expire_at
end

#artifacts_expire_in=(value) ⇒ Object



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

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

#artifacts_expired?Boolean

Returns:

  • (Boolean)


945
946
947
# File 'app/models/ci/build.rb', line 945

def artifacts_expired?
  artifacts_expire_at&.past?
end

#artifacts_fileObject



804
805
806
# File 'app/models/ci/build.rb', line 804

def artifacts_file
  job_artifacts_archive&.file
end

#artifacts_metadataObject



812
813
814
# File 'app/models/ci/build.rb', line 812

def 
  &.file
end

#artifacts_metadata?Boolean

Returns:

  • (Boolean)


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

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

#artifacts_metadata_entry(path, **options) ⇒ Object



926
927
928
929
930
931
932
933
934
935
# File 'app/models/ci/build.rb', line 926

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

    .to_entry
  end
end

#artifacts_no_access?Boolean

Returns:

  • (Boolean)


904
905
906
907
908
# File 'app/models/ci/build.rb', line 904

def artifacts_no_access?
  return false if job_artifacts_archive.nil? # To backward compatibility return false if no artifacts found

  job_artifacts_archive.none_access?
end

#artifacts_public?Boolean

Returns:

  • (Boolean)


898
899
900
901
902
# File 'app/models/ci/build.rb', line 898

def artifacts_public?
  return true if job_artifacts_archive.nil? # To backward compatibility return true if no artifacts found

  job_artifacts_archive.public_access?
end

#artifacts_sizeObject



808
809
810
# File 'app/models/ci/build.rb', line 808

def artifacts_size
  job_artifacts_archive&.size
end

#auto_retry_allowed?Boolean

Returns:

  • (Boolean)


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

def auto_retry_allowed?
  auto_retry.allowed?
end

#auto_retry_expected?Boolean

Returns:

  • (Boolean)


554
555
556
# File 'app/models/ci/build.rb', line 554

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)


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

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

#base_variablesObject



747
748
749
750
751
752
753
754
# File 'app/models/ci/build.rb', line 747

def base_variables
  ::Gitlab::Ci::Variables::Collection.new
    .concat(persisted_variables)
    .concat(dependency_proxy_variables)
    .concat(job_jwt_variables)
    .concat(scoped_variables)
    .concat(persisted_environment_variables)
end

#browsable_artifacts?Boolean

Returns:

  • (Boolean)


894
895
896
# File 'app/models/ci/build.rb', line 894

def browsable_artifacts?
  artifacts_metadata?
end

#build_matcherObject



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

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



996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
# File 'app/models/ci/build.rb', line 996

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

#can_auto_cancel_pipeline_on_job_failure?Boolean

Returns:

  • (Boolean)


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

def can_auto_cancel_pipeline_on_job_failure?
  # A job that doesn't need to be auto-retried can auto-cancel its own pipeline
  !auto_retry_expected?
end

#cancel_gracefully?Boolean

Returns:

  • (Boolean)


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

def cancel_gracefully?
  !!runner_manager&.supports_after_script_on_cancel?
end

#cancelable?Boolean

rubocop: enable CodeReuse/ServiceClass

Returns:

  • (Boolean)


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

def cancelable?
  (active? || created?) && !canceling?
end

#ci_job_processed_rate_limited?Boolean

Returns:

  • (Boolean)


509
510
511
512
513
514
# File 'app/models/ci/build.rb', line 509

def ci_job_processed_rate_limited?
  Gitlab::ApplicationRateLimiter.throttled?(
    :ci_job_processed_subscription,
    scope: project
  )
end

#collect_accessibility_reports!(accessibility_report) ⇒ Object



1113
1114
1115
1116
1117
1118
1119
# File 'app/models/ci/build.rb', line 1113

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



1121
1122
1123
1124
1125
1126
1127
# File 'app/models/ci/build.rb', line 1121

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



1129
1130
1131
1132
1133
1134
1135
# File 'app/models/ci/build.rb', line 1129

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



1105
1106
1107
1108
1109
1110
1111
# File 'app/models/ci/build.rb', line 1105

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



1208
1209
1210
# File 'app/models/ci/build.rb', line 1208

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

#credentialsObject



1026
1027
1028
# File 'app/models/ci/build.rb', line 1026

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

#debug_mode?Boolean

Returns:

  • (Boolean)


1190
1191
1192
1193
1194
1195
# File 'app/models/ci/build.rb', line 1190

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

#degenerate!Object



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

def degenerate!
  super do
    execution_config&.destroy
  end
end

#degenerated?Boolean

Returns:

  • (Boolean)


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

def degenerated?
  super && execution_config_id.nil?
end

#degradation_thresholdObject



1171
1172
1173
1174
# File 'app/models/ci/build.rb', line 1171

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

#dependency_proxy_variablesObject



708
709
710
711
712
713
714
715
# File 'app/models/ci/build.rb', line 708

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



699
700
701
702
703
704
705
706
# File 'app/models/ci/build.rb', line 699

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



558
559
560
561
562
# File 'app/models/ci/build.rb', line 558

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

#diffblue_cover_variablesObject



735
736
737
738
739
# File 'app/models/ci/build.rb', line 735

def diffblue_cover_variables
  return [] unless diffblue_cover_integration.try(:activated?)

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

#doom!Object

Consider this object to have an unknown job problem



1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
# File 'app/models/ci/build.rb', line 1142

def doom!
  transaction do
    now = Time.current
    attributes = {
      status: :failed,
      failure_reason: :data_integrity_failure,
      updated_at: now
    }
    attributes[:finished_at] = now unless finished_at.present?

    update_columns(attributes)
    all_queuing_entries.delete_all
    .delete_all
  end

  deployment&.sync_status_with(self)

  ::Gitlab::Ci::Pipeline::Metrics
    .job_failure_reason_counter
    .increment(reason: :data_integrity_failure)

  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



1197
1198
1199
1200
1201
1202
# File 'app/models/ci/build.rb', line 1197

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



1255
1256
1257
1258
1259
1260
1261
1262
1263
# File 'app/models/ci/build.rb', line 1255

def each_report(report_types)
  job_artifacts_for_types(report_types).each do |report_artifact|
    next if report_artifact&.artifact_report&.faulty?

    report_artifact.each_blob do |blob|
      yield report_artifact.file_type, blob, report_artifact
    end
  end
end

#ensure_trace_metadata!Object



843
844
845
# File 'app/models/ci/build.rb', line 843

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

#erasable?Boolean

Returns:

  • (Boolean)


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

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

#erased?Boolean

Returns:

  • (Boolean)


941
942
943
# File 'app/models/ci/build.rb', line 941

def erased?
  !erased_at.nil?
end

#execute_hooksObject



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

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

  return unless project.has_active_hooks?(:job_hooks) || project.has_active_integrations?(:job_hooks)

  Ci::ExecuteBuildHooksWorker.perform_async(project.id, build_data)
end

#exit_codes_defined?Boolean

Returns:

  • (Boolean)


1204
1205
1206
# File 'app/models/ci/build.rb', line 1204

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

#expanded_publish_pathObject



1078
1079
1080
# File 'app/models/ci/build.rb', line 1078

def expanded_publish_path
  ExpandVariables.expand(publish_path.to_s, -> { base_variables.sort_and_expand_all })
end

#fallback_cache_keys_defined?Boolean

Returns:

  • (Boolean)


1022
1023
1024
# File 'app/models/ci/build.rb', line 1022

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

#featuresObject



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

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

#force_cancelable?Boolean

Returns:

  • (Boolean)


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

def force_cancelable?
  canceling? && supports_force_cancel?
end

#google_play_variablesObject



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

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



717
718
719
720
721
# File 'app/models/ci/build.rb', line 717

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)


800
801
802
# File 'app/models/ci/build.rb', line 800

def has_archived_trace?
  trace.archived?
end

#has_expired_locked_archive_artifacts?Boolean

Returns:

  • (Boolean)


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

def has_expired_locked_archive_artifacts?
  locked_artifacts? &&
    artifacts_expire_at&.past?
end

#has_expiring_archive_artifacts?Boolean

Returns:

  • (Boolean)


963
964
965
# File 'app/models/ci/build.rb', line 963

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

#has_job_artifacts?Boolean

Returns:

  • (Boolean)


835
836
837
# File 'app/models/ci/build.rb', line 835

def has_job_artifacts?
  job_artifacts.any?
end

#has_live_trace?Boolean

Returns:

  • (Boolean)


796
797
798
# File 'app/models/ci/build.rb', line 796

def has_live_trace?
  trace.live?
end

#has_tags?Boolean

Returns:

  • (Boolean)


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

def has_tags?
  tag_list.any?
end

#has_terminal?Boolean

Returns:

  • (Boolean)


1101
1102
1103
# File 'app/models/ci/build.rb', line 1101

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

#has_test_reports?Boolean

Returns:

  • (Boolean)


839
840
841
# File 'app/models/ci/build.rb', line 839

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

#has_trace?Boolean

Returns:

  • (Boolean)


792
793
794
# File 'app/models/ci/build.rb', line 792

def has_trace?
  trace.exist?
end

#has_valid_build_dependencies?Boolean

Returns:

  • (Boolean)


1030
1031
1032
# File 'app/models/ci/build.rb', line 1030

def has_valid_build_dependencies?
  dependencies.valid?
end

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



1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
# File 'app/models/ci/build.rb', line 1086

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

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

#imageObject



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

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

#inputs_attributesObject



1240
1241
1242
1243
1244
1245
1246
# File 'app/models/ci/build.rb', line 1240

def inputs_attributes
  strong_memoize(:inputs_attributes) do
    inputs.map do |input|
      input.attributes.except('id', 'job_id', 'created_at', 'updated_at')
    end
  end
end

#invalid_dependenciesObject



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

def invalid_dependencies
  dependencies.invalid
end

#job_artifact_typesObject



1265
1266
1267
# File 'app/models/ci/build.rb', line 1265

def job_artifact_types
  job_artifacts.map(&:file_type)
end

#job_variables_attributesObject



1230
1231
1232
1233
1234
1235
1236
1237
1238
# File 'app/models/ci/build.rb', line 1230

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



967
968
969
970
# File 'app/models/ci/build.rb', line 967

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

#locked_artifacts?Boolean

Returns:

  • (Boolean)


820
821
822
# File 'app/models/ci/build.rb', line 820

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

#max_test_cases_per_reportObject



1184
1185
1186
1187
1188
# File 'app/models/ci/build.rb', line 1184

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



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

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

#multi_build_steps?Boolean

Returns:

  • (Boolean)


1082
1083
1084
# File 'app/models/ci/build.rb', line 1082

def multi_build_steps?
  options[:release]&.any?
end

#needs_maintainer_role_for_artifact_access?Boolean

Returns:

  • (Boolean)


499
500
501
502
503
# File 'app/models/ci/build.rb', line 499

def needs_maintainer_role_for_artifact_access?
  return false if job_artifacts_archive.nil?

  job_artifacts_archive.maintainer_access?
end

#needs_touch?Boolean

Returns:

  • (Boolean)


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

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

#options_scheduled_atObject



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

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

#other_scheduled_actionsObject



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

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

#pagesObject



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

def pages
  return {} unless pages_generator? && publish_path_available?

  { publish: expanded_publish_path }
end

#pages_generator?Boolean

Returns:

  • (Boolean)


568
569
570
571
572
573
574
# File 'app/models/ci/build.rb', line 568

def pages_generator?
  return false unless Gitlab.config.pages.enabled
  return false unless options.present?
  return true if options[:pages].is_a?(Hash) || options[:pages] == true

  options[:pages] != false && name == 'pages' # Legacy behaviour
end

#pages_variablesObject



741
742
743
744
745
# File 'app/models/ci/build.rb', line 741

def pages_variables
  ::Gitlab::Ci::Variables::Collection.new.tap do |variables|
    variables.append(key: 'CI_PAGES_URL', value: project.pages_url(pages))
  end
end

#persisted_environment_variablesObject



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

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

    variables.append(key: 'CI_ENVIRONMENT_ID', value: persisted_environment.id.to_s)
    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



664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
# File 'app/models/ci/build.rb', line 664

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)
      .concat(diffblue_cover_variables)
  end
end

#play(current_user, job_variables_attributes = nil) ⇒ Object

rubocop: disable CodeReuse/ServiceClass



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

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

#playable?Boolean

Returns:

  • (Boolean)


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

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

#prerequisitesObject



645
646
647
# File 'app/models/ci/build.rb', line 645

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

#publish_pathObject



1067
1068
1069
1070
1071
1072
# File 'app/models/ci/build.rb', line 1067

def publish_path
  return unless options.present?
  return options[:publish] unless options[:pages].is_a?(Hash)

  options.dig(:pages, :publish) || options[:publish]
end

#publish_path_available?Boolean

Returns:

  • (Boolean)


1074
1075
1076
# File 'app/models/ci/build.rb', line 1074

def publish_path_available?
  publish_path.present?
end

#publishes_artifacts_reports?Boolean

Returns:

  • (Boolean)


1059
1060
1061
# File 'app/models/ci/build.rb', line 1059

def publishes_artifacts_reports?
  reports_definitions&.any?
end

#remove_pending_state!Object



1176
1177
1178
# File 'app/models/ci/build.rb', line 1176

def remove_pending_state!
  pending_state.try(:delete)
end

#remove_token!Object



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

def remove_token!
  update!(token_encrypted: nil)
end

#repo_urlObject



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

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



1137
1138
1139
# File 'app/models/ci/build.rb', line 1137

def report_artifacts
  job_artifacts.all_reports
end

#retries_countObject



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

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

#run_on_status_commit(&block) ⇒ Object



1180
1181
1182
# File 'app/models/ci/build.rb', line 1180

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

#runnable?Boolean

Returns:

  • (Boolean)


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

def runnable?
  true
end

#runner_ack_wait_statusSymbol

Returns the current status of the runner acknowledgement wait process for two-phase job acceptance.

Returns:

  • (Symbol)

    One of:

    • ‘:waiting` - Pending job has been assigned to a runner and is actively waiting for the runner to acknowledge

      that it has picked up the job. The runner manager ID is present in Redis.
      
    • ‘:not_waiting` - Job is not in a waiting state. This occurs when either the job is not in pending

      status, or the job has not been assigned to a runner yet (no `runner_id``).
      
    • ‘:wait_expired` - Job was previously assigned to a runner and was waiting for acknowledgement,

      but the wait period has expired. The runner manager ID is no longer present in Redis,
      indicating the runner failed to acknowledge within the expected timeframe.
      


1305
1306
1307
1308
1309
1310
# File 'app/models/ci/build.rb', line 1305

def runner_ack_wait_status
  return :not_waiting unless pending? && runner_id.present?
  return :waiting if runner_manager_id_waiting_for_ack.present?

  :wait_expired
end

#runner_required_feature_namesObject



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

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



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

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

#schedulable?Boolean

Returns:

  • (Boolean)


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

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

#serializable_hash(options = {}) ⇒ Object



1097
1098
1099
# File 'app/models/ci/build.rb', line 1097

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

#servicesObject



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

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

#shared_runner_build?Boolean

Returns:

  • (Boolean)


1226
1227
1228
# File 'app/models/ci/build.rb', line 1226

def shared_runner_build?
  runner&.instance_type?
end

#sourceObject



1284
1285
1286
# File 'app/models/ci/build.rb', line 1284

def source
  build_source&.source || pipeline.source
end

#stepsObject



978
979
980
981
982
# File 'app/models/ci/build.rb', line 978

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)


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

def stuck?
  pending? && !any_runners_online?
end

#supported_runner?(features) ⇒ Boolean

Returns:

  • (Boolean)


1053
1054
1055
1056
1057
# File 'app/models/ci/build.rb', line 1053

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

#supports_artifacts_exclude?Boolean

Returns:

  • (Boolean)


1063
1064
1065
# File 'app/models/ci/build.rb', line 1063

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

#supports_canceling?Boolean

A Ci::Bridge may transition to ‘canceling` as a result of strategy: :depend but only a Ci::Build will transition to `canceling“ via `.cancel`

Returns:

  • (Boolean)


527
528
529
# File 'app/models/ci/build.rb', line 527

def supports_canceling?
  cancel_gracefully?
end

#supports_force_cancel?Boolean

Returns:

  • (Boolean)


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

def supports_force_cancel?
  true
end

#tag_listObject



860
861
862
863
864
865
866
# File 'app/models/ci/build.rb', line 860

def tag_list
  # Check job_definition first to avoid loading project if not needed
  return super if job_definition.nil?
  return super if Feature.disabled?(:ci_build_uses_job_definition_tag_list, project)

  Gitlab::Ci::Tags::TagList.new(job_definition.config.fetch(:tag_list, []))
end

#test_suite_nameObject



1269
1270
1271
1272
1273
1274
1275
# File 'app/models/ci/build.rb', line 1269

def test_suite_name
  if matrix_build?
    name
  else
    group_name
  end
end

#time_in_queue_secondsObject



1277
1278
1279
1280
1281
# File 'app/models/ci/build.rb', line 1277

def time_in_queue_seconds
  return if queued_at.nil?

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

#tokenObject



1289
1290
1291
1292
1293
# File 'app/models/ci/build.rb', line 1289

def token
  return encoded_jwt if user&.composite_identity_enforced? || use_jwt_for_ci_cd_job_token?

  super
end

#traceObject



788
789
790
# File 'app/models/ci/build.rb', line 788

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

#trigger_job_processed_subscriptionsObject



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

def trigger_job_processed_subscriptions
  return unless Feature.enabled?(:ci_job_created_subscription, project)

  return if ci_job_processed_rate_limited?

  GraphqlTriggers.ci_job_processed(self)
  GraphqlTriggers.ci_job_processed_with_artifacts(self)
end

#trigger_job_status_change_subscriptionObject



505
506
507
# File 'app/models/ci/build.rb', line 505

def trigger_job_status_change_subscription
  GraphqlTriggers.ci_job_status_updated(self)
end

#triggered_by?(current_user) ⇒ Boolean

Returns:

  • (Boolean)


649
650
651
# File 'app/models/ci/build.rb', line 649

def triggered_by?(current_user)
  user == current_user
end

#update_coverageObject



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

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

#valid_dependency?Boolean

Returns:

  • (Boolean)


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

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

  true
end

#variablesObject

All variables, including persisted environment variables.



656
657
658
659
660
661
662
# File 'app/models/ci/build.rb', line 656

def variables
  strong_memoize(:variables) do
    Gitlab::Ci::Variables::Collection.new
      .concat(base_variables)
      .concat(pages_variables)
  end
end