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 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_slug, #environment_status, #environment_tier, #environment_tier_from_options, #environment_url, #expanded_auto_stop_in, #expanded_environment_name, #expanded_kubernetes_namespace, #has_environment_keyword?, #has_outdated_deployment?, #on_stop, #persisted_environment, #persisted_environment=, #prepares_environment?, #stop_action_successful?, #stops_environment?, #successful_deployment_status, #verifies_environment?

Methods included from Contextable

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

Methods included from BulkInsertableTags

with_bulk_insert_tags

Methods inherited from Processable

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

Methods included from Metadatable

#cancel_gracefully?, #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, #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, declarative_enum, default_select_columns, id_in, id_not_in, iid_in, nullable_column?, 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

Methods included from ResetOnColumnErrors

#reset_on_union_error, #reset_on_unknown_attribute_error

Class Method Details

.arel_tag_names_arrayObject



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

def self.arel_tag_names_array
  ::Ci::BuildTag
    .joins(:tag)
    .where(::Ci::BuildTag.arel_table[:build_id].eq(arel_table[:id]))
    .where(::Ci::BuildTag.arel_table[:partition_id].eq(arel_table[:partition_id]))
    .select('COALESCE(array_agg(tags.name ORDER BY name), ARRAY[]::text[])')
end

.build_matchers(project) ⇒ Object



408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
# File 'app/models/ci/build.rb', line 408

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



264
265
266
267
268
269
270
271
# File 'app/models/ci/build.rb', line 264

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 interruptible execution_config_id].freeze
end

.ids_in_merge_request(merge_request_id) ⇒ Object



424
425
426
# File 'app/models/ci/build.rb', line 424

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

.keep_artifacts!Object



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

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



256
257
258
# File 'app/models/ci/build.rb', line 256

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

.supported_keyset_orderingsObject



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

def supported_keyset_orderings
  { id: [:desc] }
end

.with_preloadsObject



260
261
262
# File 'app/models/ci/build.rb', line 260

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

Instance Method Details

#action?Boolean

Returns:

  • (Boolean)


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

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

#all_met_to_become_pending?Boolean

Returns:

  • (Boolean)


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

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.



1142
1143
1144
# File 'app/models/ci/build.rb', line 1142

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

#all_runtime_metadataObject



1146
1147
1148
# File 'app/models/ci/build.rb', line 1146

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

#allow_git_fetchObject



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

def allow_git_fetch
  project.build_allow_git_fetch
end

#allowed_to_fail_with_code?(exit_code) ⇒ Boolean

Returns:

  • (Boolean)


1164
1165
1166
1167
1168
1169
# File 'app/models/ci/build.rb', line 1164

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)


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

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

#any_runners_online?Boolean

Returns:

  • (Boolean)


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

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)


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

def any_unmet_prerequisites?
  prerequisites.present?
end

#apple_app_store_variablesObject



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

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)


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

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_access_setting_in_configObject



844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
# File 'app/models/ci/build.rb', line 844

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

  if !artifacts_public.nil? && !artifacts_access.nil?
    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 :none if artifacts_access == 'none'

  # default behaviour
  :public
end

#artifact_for_type(type) ⇒ Object



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

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)


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

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

#artifacts_expire_inObject



883
884
885
# File 'app/models/ci/build.rb', line 883

def artifacts_expire_in
  artifacts_expire_at - Time.current if artifacts_expire_at
end

#artifacts_expire_in=(value) ⇒ Object



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

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

#artifacts_expired?Boolean

Returns:

  • (Boolean)


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

def artifacts_expired?
  artifacts_expire_at&.past?
end

#artifacts_expose_asObject



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

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

#artifacts_fileObject



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

def artifacts_file
  job_artifacts_archive&.file
end

#artifacts_metadataObject



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

def 
  &.file
end

#artifacts_metadata?Boolean

Returns:

  • (Boolean)


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

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

#artifacts_metadata_entry(path, **options) ⇒ Object



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

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

    .to_entry
  end
end

#artifacts_no_access?Boolean

Returns:

  • (Boolean)


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

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_pathsObject



769
770
771
# File 'app/models/ci/build.rb', line 769

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

#artifacts_public?Boolean

Returns:

  • (Boolean)


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

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



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

def artifacts_size
  job_artifacts_archive&.size
end

#auto_retry_allowed?Boolean

Returns:

  • (Boolean)


457
458
459
# File 'app/models/ci/build.rb', line 457

def auto_retry_allowed?
  auto_retry.allowed?
end

#auto_retry_expected?Boolean

Returns:

  • (Boolean)


467
468
469
# File 'app/models/ci/build.rb', line 467

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)


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

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

#browsable_artifacts?Boolean

Returns:

  • (Boolean)


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

def browsable_artifacts?
  artifacts_metadata?
end

#build_matcherObject



446
447
448
449
450
451
452
453
454
455
# File 'app/models/ci/build.rb', line 446

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



935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
# File 'app/models/ci/build.rb', line 935

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)


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

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

#cancelable?Boolean

rubocop: enable CodeReuse/ServiceClass

Returns:

  • (Boolean)


546
547
548
# File 'app/models/ci/build.rb', line 546

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

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



1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
# File 'app/models/ci/build.rb', line 1181

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



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

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



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

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



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

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



1029
1030
1031
1032
1033
1034
1035
# File 'app/models/ci/build.rb', line 1029

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



1132
1133
1134
# File 'app/models/ci/build.rb', line 1132

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

#credentialsObject



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

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

#debug_mode?Boolean

Returns:

  • (Boolean)


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

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



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

def degenerate!
  super do
    execution_config&.destroy
  end
end

#degenerated?Boolean

Returns:

  • (Boolean)


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

def degenerated?
  super && execution_config_id.nil?
end

#degradation_thresholdObject



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

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

#dependency_proxy_variablesObject



630
631
632
633
634
635
636
637
# File 'app/models/ci/build.rb', line 630

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



621
622
623
624
625
626
627
628
# File 'app/models/ci/build.rb', line 621

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



471
472
473
474
475
# File 'app/models/ci/build.rb', line 471

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

#diffblue_cover_variablesObject



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

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



1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
# File 'app/models/ci/build.rb', line 1066

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



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

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



1171
1172
1173
1174
1175
1176
1177
1178
1179
# File 'app/models/ci/build.rb', line 1171

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



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

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

#erasable?Boolean

Returns:

  • (Boolean)


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

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

#erased?Boolean

Returns:

  • (Boolean)


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

def erased?
  !erased_at.nil?
end

#execute_hooksObject



818
819
820
821
822
823
824
825
826
# File 'app/models/ci/build.rb', line 818

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_code=(value) ⇒ Object



461
462
463
464
465
# File 'app/models/ci/build.rb', line 461

def exit_code=(value)
  return unless value

  .exit_code = value.to_i.clamp(0, Gitlab::Database::MAX_SMALLINT_VALUE)
end

#exit_codes_defined?Boolean

Returns:

  • (Boolean)


1128
1129
1130
# File 'app/models/ci/build.rb', line 1128

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

#fallback_cache_keys_defined?Boolean

Returns:

  • (Boolean)


961
962
963
# File 'app/models/ci/build.rb', line 961

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

#featuresObject



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

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

#google_play_variablesObject



651
652
653
654
655
# File 'app/models/ci/build.rb', line 651

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



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

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)


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

def has_archived_trace?
  trace.archived?
end

#has_expired_locked_archive_artifacts?Boolean

Returns:

  • (Boolean)


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

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

#has_expiring_archive_artifacts?Boolean

Returns:

  • (Boolean)


897
898
899
# File 'app/models/ci/build.rb', line 897

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

#has_job_artifacts?Boolean

Returns:

  • (Boolean)


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

def has_job_artifacts?
  job_artifacts.any?
end

#has_live_trace?Boolean

Returns:

  • (Boolean)


714
715
716
# File 'app/models/ci/build.rb', line 714

def has_live_trace?
  trace.live?
end

#has_tags?Boolean

Returns:

  • (Boolean)


798
799
800
# File 'app/models/ci/build.rb', line 798

def has_tags?
  tag_list.any?
end

#has_terminal?Boolean

Returns:

  • (Boolean)


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

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

#has_test_reports?Boolean

Returns:

  • (Boolean)


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

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

#has_trace?Boolean

Returns:

  • (Boolean)


710
711
712
# File 'app/models/ci/build.rb', line 710

def has_trace?
  trace.exist?
end

#has_valid_build_dependencies?Boolean

Returns:

  • (Boolean)


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

def has_valid_build_dependencies?
  dependencies.valid?
end

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



1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
# File 'app/models/ci/build.rb', line 1010

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



927
928
929
# File 'app/models/ci/build.rb', line 927

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

#invalid_dependenciesObject



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

def invalid_dependencies
  dependencies.invalid_local
end

#job_artifact_typesObject



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

def job_artifact_types
  job_artifacts.map(&:file_type)
end

#job_variables_attributesObject



1154
1155
1156
1157
1158
1159
1160
1161
1162
# File 'app/models/ci/build.rb', line 1154

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



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

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

#locked_artifacts?Boolean

Returns:

  • (Boolean)


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

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

#max_test_cases_per_reportObject



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

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



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

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

#multi_build_steps?Boolean

Returns:

  • (Boolean)


1006
1007
1008
# File 'app/models/ci/build.rb', line 1006

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

#needs_touch?Boolean

Returns:

  • (Boolean)


773
774
775
# File 'app/models/ci/build.rb', line 773

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

#options_scheduled_atObject



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

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

#other_scheduled_actionsObject



477
478
479
# File 'app/models/ci/build.rb', line 477

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

#pagesObject

Overriden on EE rubocop:disable Gitlab/NoCodeCoverageComment – Fully tested in EE and tested in Foss through feature specs in spec/models/ci/build_spec.rb :nocov:



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

def pages
  {}
end

#pages_generator?Boolean

Returns:

  • (Boolean)


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

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

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

#pages_variablesObject



663
664
665
666
667
668
669
670
671
672
673
# File 'app/models/ci/build.rb', line 663

def pages_variables
  ::Gitlab::Ci::Variables::Collection.new.tap do |variables|
    next variables unless pages_generator? && Feature.enabled?(:fix_pages_ci_variables, project)

    pages_url_builder = ::Gitlab::Pages::UrlBuilder.new(project, pages)

    variables
      .append(key: 'CI_PAGES_HOSTNAME', value: pages_url_builder.hostname)
      .append(key: 'CI_PAGES_URL', value: pages_url_builder.pages_url)
  end
end

#persisted_environment_variablesObject



608
609
610
611
612
613
614
615
616
617
618
619
# File 'app/models/ci/build.rb', line 608

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



587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
# File 'app/models/ci/build.rb', line 587

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



539
540
541
542
543
# File 'app/models/ci/build.rb', line 539

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

#playable?Boolean

Returns:

  • (Boolean)


517
518
519
# File 'app/models/ci/build.rb', line 517

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

#prerequisitesObject



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

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

#publishes_artifacts_reports?Boolean

Returns:

  • (Boolean)


998
999
1000
# File 'app/models/ci/build.rb', line 998

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

#remove_pending_state!Object



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

def remove_pending_state!
  pending_state.try(:delete)
end

#remove_token!Object



786
787
788
# File 'app/models/ci/build.rb', line 786

def remove_token!
  update!(token_encrypted: nil)
end

#repo_urlObject



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

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



1061
1062
1063
# File 'app/models/ci/build.rb', line 1061

def report_artifacts
  job_artifacts.all_reports
end

#retries_countObject



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

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

#run_on_status_commit(&block) ⇒ Object



1104
1105
1106
# File 'app/models/ci/build.rb', line 1104

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

#runnable?Boolean

rubocop:enable Gitlab/NoCodeCoverageComment

Returns:

  • (Boolean)


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

def runnable?
  true
end

#runner_required_feature_namesObject



984
985
986
987
988
989
990
# File 'app/models/ci/build.rb', line 984

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



923
924
925
# File 'app/models/ci/build.rb', line 923

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

#schedulable?Boolean

Returns:

  • (Boolean)


521
522
523
# File 'app/models/ci/build.rb', line 521

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

#serializable_hash(options = {}) ⇒ Object



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

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

#servicesObject



931
932
933
# File 'app/models/ci/build.rb', line 931

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

#shared_runner_build?Boolean

Returns:

  • (Boolean)


1150
1151
1152
# File 'app/models/ci/build.rb', line 1150

def shared_runner_build?
  runner&.instance_type?
end

#sourceObject



1211
1212
1213
# File 'app/models/ci/build.rb', line 1211

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

#stepsObject



917
918
919
920
921
# File 'app/models/ci/build.rb', line 917

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)


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

def stuck?
  pending? && !any_runners_online?
end

#supported_runner?(features) ⇒ Boolean

Returns:

  • (Boolean)


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

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

#supports_artifacts_exclude?Boolean

Returns:

  • (Boolean)


1002
1003
1004
# File 'app/models/ci/build.rb', line 1002

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)


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

def supports_canceling?
  cancel_gracefully?
end

#tag_listObject



790
791
792
793
794
795
796
# File 'app/models/ci/build.rb', line 790

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

#tags_ids_relationObject



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

def tags_ids_relation
  simple_tags
end

#test_suite_nameObject



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

def test_suite_name
  if matrix_build?
    name
  else
    group_name
  end
end

#time_in_queue_secondsObject



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

def time_in_queue_seconds
  return if queued_at.nil?

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

#to_partial_pathObject

Can be removed in Rails 7.1. Related to: Gitlab.next_rails?



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

def to_partial_path
  'jobs/job'
end

#tokenObject



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

def token
  return encoded_jwt if user&.has_composite_identity? || Feature.enabled?(:ci_job_token_jwt, user)

  super
end

#traceObject



706
707
708
# File 'app/models/ci/build.rb', line 706

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

#triggered_by?(current_user) ⇒ Boolean

Returns:

  • (Boolean)


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

def triggered_by?(current_user)
  user == current_user
end

#update_coverageObject



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

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

#valid_dependency?Boolean

Returns:

  • (Boolean)


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

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

  true
end

#valid_token?(token) ⇒ Boolean

Returns:

  • (Boolean)


777
778
779
780
781
782
783
784
# File 'app/models/ci/build.rb', line 777

def valid_token?(token)
  jwt = ::Ci::JobToken::Jwt.decode(token)
  if jwt
    jwt.job == self
  else
    self.token && token.present? && ActiveSupport::SecurityUtils.secure_compare(token, self.token)
  end
end

#variablesObject

All variables, including persisted environment variables.



574
575
576
577
578
579
580
581
582
583
584
585
# File 'app/models/ci/build.rb', line 574

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(pages_variables)
      .concat(job_variables)
      .concat(persisted_environment_variables)
  end
end