Class: Ci::Pipeline

Constant Summary collapse

MAX_OPEN_MERGE_REQUESTS_REFS =
4
PROJECT_ROUTE_AND_NAMESPACE_ROUTE =
{
  project: [:project_feature, :route, { namespace: :route }]
}.freeze
CONFIG_EXTENSION =
'.gitlab-ci.yml'
DEFAULT_CONFIG_PATH =
CONFIG_EXTENSION
CANCELABLE_STATUSES =
(Ci::HasStatus::CANCELABLE_STATUSES + ['manual']).freeze
BridgeStatusError =
Class.new(StandardError)

Constants included from AtomicInternalId

AtomicInternalId::MissingValueError

Constants included from Gitlab::OptimisticLocking

Gitlab::OptimisticLocking::MAX_RETRIES

Constants included from HasStatus

HasStatus::ACTIVE_STATUSES, HasStatus::ALIVE_STATUSES, HasStatus::AVAILABLE_STATUSES, HasStatus::BLOCKED_STATUS, HasStatus::COMPLETED_STATUSES, HasStatus::DEFAULT_STATUS, HasStatus::EXCLUDE_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

Instance Attribute Summary collapse

Attributes included from Importable

#imported, #importing

Class Method Summary collapse

Instance Method Summary collapse

Methods included from FastDestroyAll::Helpers

#perform_fast_destroy

Methods included from AfterCommitQueue

#run_after_commit, #run_after_commit_or_now

Methods included from HasRef

#branch?, #ref_slug

Methods included from AtomicInternalId

group_init, #internal_id_read_scope, #internal_id_scope_attrs, #internal_id_scope_usage, project_init, scope_attrs, scope_usage

Methods included from Gitlab::Utils::StrongMemoize

#clear_memoization, #strong_memoize, #strong_memoized?

Methods included from Gitlab::OptimisticLocking

log_optimistic_lock_retries, retry_lock, retry_lock_histogram, retry_lock_logger

Methods included from Gitlab::Allowable

#can?

Methods included from Presentable

#present

Methods included from HasStatus

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

Methods inherited from ApplicationRecord

model_name, 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

Methods included from SensitiveSerializableHash

#serializable_hash

Instance Attribute Details

#config_metadataObject

Ci::CreatePipelineService returns Ci::Pipeline so this is the only place where we can pass additional information from the service. This accessor is used for storing the processed metadata for linting purposes. There is an open issue to address this: gitlab.com/gitlab-org/gitlab/-/issues/259010


42
43
44
# File 'app/models/ci/pipeline.rb', line 42

def 
  @config_metadata
end

Class Method Details

.auto_devops_pipelines_completed_totalObject


462
463
464
# File 'app/models/ci/pipeline.rb', line 462

def self.auto_devops_pipelines_completed_total
  @auto_devops_pipelines_completed_total ||= Gitlab::Metrics.counter(:auto_devops_pipelines_completed_total, 'Number of completed auto devops pipelines')
end

.bridgeable_statusesObject


458
459
460
# File 'app/models/ci/pipeline.rb', line 458

def self.bridgeable_statuses
  ::Ci::Pipeline::AVAILABLE_STATUSES - %w[created waiting_for_resource preparing pending]
end

.internal_sourcesObject


454
455
456
# File 'app/models/ci/pipeline.rb', line 454

def self.internal_sources
  sources.reject { |source| source == "external" }.values
end

.jobs_count_in_alive_pipelinesObject


415
416
417
# File 'app/models/ci/pipeline.rb', line 415

def self.jobs_count_in_alive_pipelines
  created_after(24.hours.ago).alive.joins(:builds).count
end

.last_finished_for_ref_id(ci_ref_id) ⇒ Object


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

def self.last_finished_for_ref_id(ci_ref_id)
  where(ci_ref_id: ci_ref_id).ci_sources.finished.order(id: :desc).select(:id).take
end

.latest_failed_for_ref(ref) ⇒ Object


411
412
413
# File 'app/models/ci/pipeline.rb', line 411

def self.latest_failed_for_ref(ref)
  newest_first(ref: ref).failed.take
end

.latest_pipeline_per_commit(commits, ref = nil) ⇒ Object

Returns a Hash containing the latest pipeline for every given commit.

The keys of this Hash are the commit SHAs, the values the pipelines.

commits - The list of commit SHAs to get the pipelines for. ref - The ref to scope the data to (e.g. “master”). If the ref is not

given we simply get the latest pipelines for the commits, regardless
of what refs the pipelines belong to.

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

def self.latest_pipeline_per_commit(commits, ref = nil)
  sql = select('DISTINCT ON (sha) *')
          .where(sha: commits)
          .order(:sha, id: :desc)

  sql = sql.where(ref: ref) if ref

  sql.index_by(&:sha)
end

.latest_running_for_ref(ref) ⇒ Object


407
408
409
# File 'app/models/ci/pipeline.rb', line 407

def self.latest_running_for_ref(ref)
  newest_first(ref: ref).running.take
end

.latest_status(ref = nil) ⇒ Object


383
384
385
# File 'app/models/ci/pipeline.rb', line 383

def self.latest_status(ref = nil)
  newest_first(ref: ref).pluck(:status).first
end

.latest_successful_for_ref(ref) ⇒ Object


387
388
389
# File 'app/models/ci/pipeline.rb', line 387

def self.latest_successful_for_ref(ref)
  newest_first(ref: ref).success.take
end

.latest_successful_for_refs(refs) ⇒ Object


395
396
397
398
399
400
401
402
403
404
405
# File 'app/models/ci/pipeline.rb', line 395

def self.latest_successful_for_refs(refs)
  return Ci::Pipeline.none if refs.empty?

  refs_values = refs.map { |ref| "(#{connection.quote(ref)})" }.join(",")
  join_query = success.where("refs_values.ref = ci_pipelines.ref").order(id: :desc).limit(1)

  Ci::Pipeline
    .from("(VALUES #{refs_values}) refs_values (ref)")
    .joins("INNER JOIN LATERAL (#{join_query.to_sql}) #{Ci::Pipeline.table_name} ON TRUE")
    .index_by(&:ref)
end

.latest_successful_for_sha(sha) ⇒ Object


391
392
393
# File 'app/models/ci/pipeline.rb', line 391

def self.latest_successful_for_sha(sha)
  newest_first(sha: sha).success.take
end

.latest_successful_ids_per_projectObject


438
439
440
# File 'app/models/ci/pipeline.rb', line 438

def self.latest_successful_ids_per_project
  success.group(:project_id).select('max(id) as id')
end

.newest_first(ref: nil, sha: nil, limit: 100) ⇒ Object

Returns the pipelines in descending order (= newest first), optionally limited to a number of references.

ref - The name (or names) of the branch(es)/tag(s) to limit the list of

pipelines to.

sha - The commit SHA (or multiple SHAs) to limit the list of pipelines to. limit - This limits a backlog search, default to 100.


370
371
372
373
374
375
376
377
378
379
380
381
# File 'app/models/ci/pipeline.rb', line 370

def self.newest_first(ref: nil, sha: nil, limit: 100)
  relation = order(id: :desc)
  relation = relation.where(ref: ref) if ref
  relation = relation.where(sha: sha) if sha

  if limit
    ids = relation.limit(limit).select(:id)
    relation = relation.where(id: ids)
  end

  relation
end

.total_durationObject


450
451
452
# File 'app/models/ci/pipeline.rb', line 450

def self.total_duration
  where.not(duration: nil).sum(:duration)
end

.truncate_sha(sha) ⇒ Object


446
447
448
# File 'app/models/ci/pipeline.rb', line 446

def self.truncate_sha(sha)
  sha[0...8]
end

Instance Method Details

#accessibility_reportsObject


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

def accessibility_reports
  Gitlab::Ci::Reports::AccessibilityReports.new.tap do |accessibility_reports|
    latest_report_builds(Ci::JobArtifact.accessibility_reports).each do |build|
      build.collect_accessibility_reports!(accessibility_reports)
    end
  end
end

#add_error_message(content) ⇒ Object


744
745
746
# File 'app/models/ci/pipeline.rb', line 744

def add_error_message(content)
  add_message(:error, content)
end

#add_warning_message(content) ⇒ Object


748
749
750
# File 'app/models/ci/pipeline.rb', line 748

def add_warning_message(content)
  add_message(:warning, content)
end

#all_merge_requestsObject

All the merge requests for which the current pipeline runs/ran against


911
912
913
914
915
916
917
918
919
# File 'app/models/ci/pipeline.rb', line 911

def all_merge_requests
  @all_merge_requests ||=
    if merge_request?
      MergeRequest.where(id: merge_request_id)
    else
      MergeRequest.where(source_project_id: project_id, source_branch: ref)
        .by_commit_sha(sha)
    end
end

#all_merge_requests_by_recencyObject


921
922
923
# File 'app/models/ci/pipeline.rb', line 921

def all_merge_requests_by_recency
  all_merge_requests.order(id: :desc)
end

#all_pipelines_in_hierarchyObject

With multi-project and parent-child pipelines


991
992
993
# File 'app/models/ci/pipeline.rb', line 991

def all_pipelines_in_hierarchy
  object_hierarchy.all_objects
end

#all_worktree_pathsObject


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

def all_worktree_paths
  strong_memoize(:all_worktree_paths) do
    project.repository.ls_files(sha)
  end
end

#authorized_cluster_agentsObject


1310
1311
1312
1313
1314
# File 'app/models/ci/pipeline.rb', line 1310

def authorized_cluster_agents
  strong_memoize(:authorized_cluster_agents) do
    ::Clusters::AgentAuthorizationsFinder.new(project).execute.map(&:agent)
  end
end

#auto_cancel_running(pipeline, retries: 1) ⇒ Object


626
627
628
629
630
631
632
# File 'app/models/ci/pipeline.rb', line 626

def auto_cancel_running(pipeline, retries: 1)
  update(auto_canceled_by: pipeline)

  cancel_running(retries: retries) do |job|
    job.auto_canceled_by = pipeline
  end
end

#auto_canceled?Boolean

Returns:

  • (Boolean)

607
608
609
# File 'app/models/ci/pipeline.rb', line 607

def auto_canceled?
  canceled? && auto_canceled_by_id?
end

#batch_lookup_report_artifact_for_file_type(file_type) ⇒ Object


673
674
675
# File 'app/models/ci/pipeline.rb', line 673

def batch_lookup_report_artifact_for_file_type(file_type)
  batch_lookup_report_artifact_for_file_types([file_type])
end

#batch_lookup_report_artifact_for_file_types(file_types) ⇒ Object


677
678
679
680
681
682
683
684
685
686
# File 'app/models/ci/pipeline.rb', line 677

def batch_lookup_report_artifact_for_file_types(file_types)
  file_types_to_search = []
  file_types.each { |file_type| file_types_to_search.append(*::Ci::JobArtifact.associated_file_types_for(file_type.to_s)) }

  latest_report_artifacts
    .values_at(*file_types_to_search.uniq)
    .flatten
    .compact
    .last
end

#before_shaObject


579
580
581
# File 'app/models/ci/pipeline.rb', line 579

def before_sha
  super || Gitlab::Git::BLANK_SHA
end

#branch_updated?Boolean

Returns:

  • (Boolean)

1148
1149
1150
1151
1152
# File 'app/models/ci/pipeline.rb', line 1148

def branch_updated?
  strong_memoize(:branch_updated) do
    push_details.branch_updated?
  end
end

#bridge_triggered?Boolean

Returns:

  • (Boolean)

1020
1021
1022
# File 'app/models/ci/pipeline.rb', line 1020

def bridge_triggered?
  source_bridge.present?
end

#bridge_waiting?Boolean

Returns:

  • (Boolean)

1024
1025
1026
# File 'app/models/ci/pipeline.rb', line 1024

def bridge_waiting?
  source_bridge&.dependent?
end

#build_matchersObject


1306
1307
1308
# File 'app/models/ci/pipeline.rb', line 1306

def build_matchers
  self.builds.latest.build_matchers(project)
end

#build_with_artifacts_in_self_and_descendants(name) ⇒ Object


961
962
963
964
965
966
# File 'app/models/ci/pipeline.rb', line 961

def build_with_artifacts_in_self_and_descendants(name)
  builds_in_self_and_descendants
    .ordered_by_pipeline # find job in hierarchical order
    .with_downloadable_artifacts
    .find_by_name(name)
end

#builds_in_self_and_descendantsObject


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

def builds_in_self_and_descendants
  Ci::Build.latest.where(pipeline: self_and_descendants)
end

#builds_with_coverageObject


1066
1067
1068
# File 'app/models/ci/pipeline.rb', line 1066

def builds_with_coverage
  builds.latest.with_coverage
end

#builds_with_failed_tests(limit: nil) ⇒ Object


1070
1071
1072
# File 'app/models/ci/pipeline.rb', line 1070

def builds_with_failed_tests(limit: nil)
  latest_test_report_builds.failed.limit(limit)
end

#can_generate_codequality_reports?Boolean

Returns:

  • (Boolean)

1090
1091
1092
# File 'app/models/ci/pipeline.rb', line 1090

def can_generate_codequality_reports?
  has_reports?(Ci::JobArtifact.codequality_reports)
end

#can_generate_coverage_reports?Boolean

Returns:

  • (Boolean)

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

def can_generate_coverage_reports?
  has_reports?(Ci::JobArtifact.coverage_reports)
end

#cancel_running(retries: 1) ⇒ Object


611
612
613
614
615
616
617
618
619
620
621
622
623
624
# File 'app/models/ci/pipeline.rb', line 611

def cancel_running(retries: 1)
  preloaded_relations = [:project, :pipeline, :deployment, :taggings]

  retry_lock(cancelable_statuses, retries, name: 'ci_pipeline_cancel_running') do |cancelables|
    cancelables.find_in_batches do |batch|
      Preloaders::CommitStatusPreloader.new(batch).execute(preloaded_relations)

      batch.each do |job|
        yield(job) if block_given?
        job.cancel
      end
    end
  end
end

#cancelable?Boolean

Returns:

  • (Boolean)

603
604
605
# File 'app/models/ci/pipeline.rb', line 603

def cancelable?
  cancelable_statuses.any?
end

#child?Boolean

Returns:

  • (Boolean)

1028
1029
1030
1031
# File 'app/models/ci/pipeline.rb', line 1028

def child?
  parent_pipeline? && # child pipelines have `parent_pipeline` source
    parent_pipeline.present?
end

#codequality_reportsObject


1124
1125
1126
1127
1128
1129
1130
# File 'app/models/ci/pipeline.rb', line 1124

def codequality_reports
  Gitlab::Ci::Reports::CodequalityReports.new.tap do |codequality_reports|
    latest_report_builds(Ci::JobArtifact.codequality_reports).each do |build|
      build.collect_codequality_reports!(codequality_reports)
    end
  end
end

#commitObject

NOTE: This is loaded lazily and will never be nil, even if the commit cannot be found.

Use constructs like: `pipeline.commit.present?`


591
592
593
# File 'app/models/ci/pipeline.rb', line 591

def commit
  @commit ||= Commit.lazy(project, sha)
end

#coverageObject


662
663
664
665
666
667
# File 'app/models/ci/pipeline.rb', line 662

def coverage
  coverage_array = latest_statuses.map(&:coverage).compact
  if coverage_array.size >= 1
    coverage_array.sum / coverage_array.size
  end
end

#coverage_reportsObject


1116
1117
1118
1119
1120
1121
1122
# File 'app/models/ci/pipeline.rb', line 1116

def coverage_reports
  Gitlab::Ci::Reports::CoverageReports.new.tap do |coverage_reports|
    latest_report_builds(Ci::JobArtifact.coverage_reports).includes(:project).find_each do |build|
      build.collect_coverage_reports!(coverage_reports)
    end
  end
end

#created_successfully?Boolean

Returns:

  • (Boolean)

1037
1038
1039
# File 'app/models/ci/pipeline.rb', line 1037

def created_successfully?
  persisted? && failure_reason.blank?
end

#dangling?Boolean

Returns:

  • (Boolean)

1255
1256
1257
# File 'app/models/ci/pipeline.rb', line 1255

def dangling?
  Enums::Ci::Pipeline.dangling_sources.key?(source.to_sym)
end

#default_branch?Boolean

Returns:

  • (Boolean)

1183
1184
1185
# File 'app/models/ci/pipeline.rb', line 1183

def default_branch?
  ref == project.default_branch
end

#detached_merge_request_pipeline?Boolean

Returns:

  • (Boolean)

1195
1196
1197
# File 'app/models/ci/pipeline.rb', line 1195

def detached_merge_request_pipeline?
  merge_request? && target_sha.nil?
end

#detailed_status(current_user) ⇒ Object


1041
1042
1043
1044
1045
# File 'app/models/ci/pipeline.rb', line 1041

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

#distinct_tags_countObject


482
483
484
# File 'app/models/ci/pipeline.rb', line 482

def distinct_tags_count
  ActsAsTaggableOn::Tagging.where(taggable: builds).count('distinct(tag_id)')
end

#ensure_ci_ref!Object


1273
1274
1275
# File 'app/models/ci/pipeline.rb', line 1273

def ensure_ci_ref!
  self.ci_ref = Ci::Ref.ensure_for(self)
end

#ensure_persistent_refObject


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

def ensure_persistent_ref
  return if persistent_ref.exist?

  persistent_ref.create
end

#ensure_scheduling_type!Object

Set scheduling type of processables if they were created before scheduling_type data was deployed (gitlab.com/gitlab-org/gitlab/-/merge_requests/22246).


1269
1270
1271
# File 'app/models/ci/pipeline.rb', line 1269

def ensure_scheduling_type!
  processables.populate_scheduling_type!
end

#environments_in_self_and_descendants(deployment_status: nil) ⇒ Object


972
973
974
975
976
977
978
979
980
981
982
983
# File 'app/models/ci/pipeline.rb', line 972

def environments_in_self_and_descendants(deployment_status: nil)
  # We limit to 100 unique environments for application safety.
  # See: https://gitlab.com/gitlab-org/gitlab/-/issues/340781#note_699114700
  expanded_environment_names =
    builds_in_self_and_descendants.joins(:metadata)
                                  .where.not('ci_builds_metadata.expanded_environment_name' => nil)
                                  .distinct('ci_builds_metadata.expanded_environment_name')
                                  .limit(100)
                                  .pluck(:expanded_environment_name)

  Environment.where(project: project, name: expanded_environment_names).with_deployment(sha, status: deployment_status)
end

#error_messagesObject

We can't use `messages.error` scope here because messages should also be read when the pipeline is not persisted. Using the scope will return no results as it would query persisted data.


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

def error_messages
  messages.select(&:error?)
end

#external_pull_request?Boolean

Returns:

  • (Boolean)

1191
1192
1193
# File 'app/models/ci/pipeline.rb', line 1191

def external_pull_request?
  external_pull_request_id.present?
end

#find_job_with_archive_artifacts(name) ⇒ Object


1047
1048
1049
# File 'app/models/ci/pipeline.rb', line 1047

def find_job_with_archive_artifacts(name)
  builds.latest.with_downloadable_artifacts.find_by_name(name)
end

#find_stage_by_name!(name) ⇒ Object


1231
1232
1233
# File 'app/models/ci/pipeline.rb', line 1231

def find_stage_by_name!(name)
  stages.find_by!(name: name)
end

#freeze_period?Boolean

Returns:

  • (Boolean)

712
713
714
715
716
# File 'app/models/ci/pipeline.rb', line 712

def freeze_period?
  strong_memoize(:freeze_period) do
    Ci::FreezePeriodStatus.new(project: project).execute
  end
end

#full_error_messagesObject


1235
1236
1237
# File 'app/models/ci/pipeline.rb', line 1235

def full_error_messages
  errors ? errors.full_messages.to_sentence : ""
end

#git_author_emailObject


537
538
539
540
541
# File 'app/models/ci/pipeline.rb', line 537

def git_author_email
  strong_memoize(:git_author_email) do
    commit.try(:author_email)
  end
end

#git_author_full_textObject


543
544
545
546
547
# File 'app/models/ci/pipeline.rb', line 543

def git_author_full_text
  strong_memoize(:git_author_full_text) do
    commit.try(:author_full_text)
  end
end

#git_author_nameObject


531
532
533
534
535
# File 'app/models/ci/pipeline.rb', line 531

def git_author_name
  strong_memoize(:git_author_name) do
    commit.try(:author_name)
  end
end

#git_commit_descriptionObject


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

def git_commit_description
  strong_memoize(:git_commit_description) do
    commit.try(:description)
  end
end

#git_commit_full_titleObject


561
562
563
564
565
# File 'app/models/ci/pipeline.rb', line 561

def git_commit_full_title
  strong_memoize(:git_commit_full_title) do
    commit.try(:full_title)
  end
end

#git_commit_messageObject


549
550
551
552
553
# File 'app/models/ci/pipeline.rb', line 549

def git_commit_message
  strong_memoize(:git_commit_message) do
    commit.try(:message)
  end
end

#git_commit_timestampObject


573
574
575
576
577
# File 'app/models/ci/pipeline.rb', line 573

def git_commit_timestamp
  strong_memoize(:git_commit_timestamp) do
    commit.try(:timestamp)
  end
end

#git_commit_titleObject


555
556
557
558
559
# File 'app/models/ci/pipeline.rb', line 555

def git_commit_title
  strong_memoize(:git_commit_title) do
    commit.try(:title)
  end
end

#has_archive_artifacts?Boolean

Returns:

  • (Boolean)

1140
1141
1142
# File 'app/models/ci/pipeline.rb', line 1140

def has_archive_artifacts?
  complete? && builds.latest.with_existing_job_artifacts(Ci::JobArtifact.archive.or(Ci::JobArtifact.)).exists?
end

#has_codequality_mr_diff_report?Boolean

Returns:

  • (Boolean)

1086
1087
1088
# File 'app/models/ci/pipeline.rb', line 1086

def has_codequality_mr_diff_report?
  pipeline_artifacts&.report_exists?(:code_quality_mr_diff)
end

#has_coverage_reports?Boolean

Returns:

  • (Boolean)

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

def has_coverage_reports?
  pipeline_artifacts&.report_exists?(:code_coverage)
end

#has_expired_test_reports?Boolean

Returns:

  • (Boolean)

1316
1317
1318
1319
1320
# File 'app/models/ci/pipeline.rb', line 1316

def has_expired_test_reports?
  strong_memoize(:artifacts_expired) do
    !has_reports?(::Ci::JobArtifact.test_reports.not_expired)
  end
end

#has_exposed_artifacts?Boolean

Returns:

  • (Boolean)

1144
1145
1146
# File 'app/models/ci/pipeline.rb', line 1144

def has_exposed_artifacts?
  complete? && builds.latest.with_exposed_artifacts.exists?
end

#has_kubernetes_active?Boolean

Returns:

  • (Boolean)

706
707
708
709
710
# File 'app/models/ci/pipeline.rb', line 706

def has_kubernetes_active?
  strong_memoize(:has_kubernetes_active) do
    project.deployment_platform&.active?
  end
end

#has_reports?(reports_scope) ⇒ Boolean

Returns:

  • (Boolean)

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

def has_reports?(reports_scope)
  complete? && latest_report_builds(reports_scope).exists?
end

#has_warnings?Boolean

Returns:

  • (Boolean)

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

def has_warnings?
  number_of_warnings > 0
end

#has_yaml_errors?Boolean

Returns:

  • (Boolean)

740
741
742
# File 'app/models/ci/pipeline.rb', line 740

def has_yaml_errors?
  yaml_errors.present?
end

#jobs_git_refObject

This is used to retain access to the method defined by `Ci::HasRef` before being overridden in this class.


46
# File 'app/models/ci/pipeline.rb', line 46

alias_method :jobs_git_ref, :git_ref

#latest?Boolean

Returns:

  • (Boolean)

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

def latest?
  return false unless git_ref && commit.present?
  return false if lazy_ref_commit.nil?

  lazy_ref_commit.id == commit.id
end

#latest_builds_with_artifactsObject


1051
1052
1053
1054
1055
1056
# File 'app/models/ci/pipeline.rb', line 1051

def latest_builds_with_artifacts
  # We purposely cast the builds to an Array here. Because we always use the
  # rows if there are more than 0 this prevents us from having to run two
  # queries: one to get the count and one to get the rows.
  @latest_builds_with_artifacts ||= builds.latest.with_artifacts_not_expired.to_a
end

#latest_report_artifactsObject

This batch loads the latest reports for each CI job artifact type (e.g. sast, dast, etc.) in a single SQL query to eliminate the need to do N different `job_artifacts.where(file_type: X).last` calls.

Return a hash of file type => array of 1 job artifact


694
695
696
697
698
699
700
701
702
703
704
# File 'app/models/ci/pipeline.rb', line 694

def latest_report_artifacts
  ::Gitlab::SafeRequestStore.fetch("pipeline:#{self.id}:latest_report_artifacts") do
    ::Ci::JobArtifact.where(
      id: job_artifacts.with_reports
        .select('max(ci_job_artifacts.id) as id')
        .group(:file_type)
    )
      .preload(:job)
      .group_by(&:file_type)
  end
end

#latest_report_builds(reports_scope = ::Ci::JobArtifact.with_reports) ⇒ Object


1058
1059
1060
# File 'app/models/ci/pipeline.rb', line 1058

def latest_report_builds(reports_scope = ::Ci::JobArtifact.with_reports)
  builds.latest.with_reports(reports_scope)
end

#latest_test_report_buildsObject


1062
1063
1064
# File 'app/models/ci/pipeline.rb', line 1062

def latest_test_report_builds
  latest_report_builds(Ci::JobArtifact.test_reports).preload(:project)
end

#lazy_ref_commitObject

rubocop: enable CodeReuse/ServiceClass


641
642
643
644
645
646
647
648
649
# File 'app/models/ci/pipeline.rb', line 641

def lazy_ref_commit
  BatchLoader.for(ref).batch do |refs, loader|
    next unless project.repository_exists?

    project.repository.list_commits_by_ref_name(refs).then do |commits|
      commits.each { |key, commit| loader.call(key, commits[key]) }
    end
  end
end

#legacy_detached_merge_request_pipeline?Boolean

Returns:

  • (Boolean)

1199
1200
1201
# File 'app/models/ci/pipeline.rb', line 1199

def legacy_detached_merge_request_pipeline?
  detached_merge_request_pipeline? && !merge_request_ref?
end

#legacy_stage(name) ⇒ Object


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

def legacy_stage(name)
  stage = Ci::LegacyStage.new(self, name: name)
  stage unless stage.statuses_count == 0
end

#legacy_stagesObject

TODO: Remove usage of this method in templates


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

def legacy_stages
  legacy_stages_using_composite_status
end

#legacy_stages_using_composite_statusObject


502
503
504
505
506
507
508
509
510
511
512
513
514
# File 'app/models/ci/pipeline.rb', line 502

def legacy_stages_using_composite_status
  stages = latest_statuses_ordered_by_stage.group_by(&:stage)

  stages.map do |stage_name, jobs|
    composite_status = Gitlab::Ci::Status::Composite
      .new(jobs)

    Ci::LegacyStage.new(self,
      name: stage_name,
      status: composite_status.status,
      warnings: composite_status.warnings?)
  end
end

#legacy_triggerObject


807
808
809
# File 'app/models/ci/pipeline.rb', line 807

def legacy_trigger
  strong_memoize(:legacy_trigger) { trigger_requests.first }
end

#matches_sha_or_source_sha?(sha) ⇒ Boolean

Returns:

  • (Boolean)

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

def matches_sha_or_source_sha?(sha)
  self.sha == sha || self.source_sha == sha
end

#merge_request?Boolean

Returns:

  • (Boolean)

1187
1188
1189
# File 'app/models/ci/pipeline.rb', line 1187

def merge_request?
  merge_request_id.present? && merge_request.present?
end

#merge_request_event_typeObject


1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
# File 'app/models/ci/pipeline.rb', line 1239

def merge_request_event_type
  return unless merge_request?

  strong_memoize(:merge_request_event_type) do
    if merged_result_pipeline?
      :merged_result
    elsif detached_merge_request_pipeline?
      :detached
    end
  end
end

#merge_request_ref?Boolean

Returns:

  • (Boolean)

1207
1208
1209
# File 'app/models/ci/pipeline.rb', line 1207

def merge_request_ref?
  MergeRequest.merge_request_ref?(ref)
end

#merge_train_pipeline?Boolean

EE-only

Returns:

  • (Boolean)

1291
1292
1293
# File 'app/models/ci/pipeline.rb', line 1291

def merge_train_pipeline?
  false
end

#merged_result_pipeline?Boolean

Returns:

  • (Boolean)

1203
1204
1205
# File 'app/models/ci/pipeline.rb', line 1203

def merged_result_pipeline?
  merge_request? && target_sha.present?
end

#modified_pathsObject

Returns the modified paths.

The returned value is

  • Array: List of modified paths that should be evaluated

  • nil: Modified path can not be evaluated


1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
# File 'app/models/ci/pipeline.rb', line 1159

def modified_paths
  strong_memoize(:modified_paths) do
    if merge_request?
      merge_request.modified_paths
    elsif branch_updated?
      push_details.modified_paths
    elsif external_pull_request?
      external_pull_request.modified_paths
    end
  end
end

#needs_processing?Boolean

Returns:

  • (Boolean)

733
734
735
736
737
738
# File 'app/models/ci/pipeline.rb', line 733

def needs_processing?
  statuses
    .where(processed: [false, nil])
    .latest
    .exists?
end

#notesObject


778
779
780
# File 'app/models/ci/pipeline.rb', line 778

def notes
  project.notes.for_commit_id(sha)
end

#notes=(notes) ⇒ Object

Manually set the notes for a Ci::Pipeline There is no ActiveRecord relation between Ci::Pipeline and notes as they are related to a commit sha. This method helps importing them using the Gitlab::ImportExport::Project::RelationFactory class.


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

def notes=(notes)
  notes.each do |note|
    note[:id] = nil
    note[:commit_id] = sha
    note[:noteable_id] = self['id']
    note.save!
  end
end

#number_of_warningsObject


722
723
724
725
726
727
728
729
730
731
# File 'app/models/ci/pipeline.rb', line 722

def number_of_warnings
  BatchLoader.for(id).batch(default_value: 0) do |pipeline_ids, loader|
    ::CommitStatus.where(commit_id: pipeline_ids)
      .latest
      .failed_but_allowed
      .group(:commit_id)
      .count
      .each { |id, amount| loader.call(id, amount) }
  end
end

#open_merge_requests_refsObject

We cannot use `all_merge_requests`, due to race condition This returns a list of at most 4 open MRs


942
943
944
945
946
947
948
949
950
951
952
953
# File 'app/models/ci/pipeline.rb', line 942

def open_merge_requests_refs
  strong_memoize(:open_merge_requests_refs) do
    # We ensure that triggering user can actually read the pipeline
    related_merge_requests
      .opened
      .limit(MAX_OPEN_MERGE_REQUESTS_REFS)
      .order(id: :desc)
      .preload(:target_project)
      .select { |mr| can?(user, :read_merge_request, mr) }
      .map { |mr| mr.to_reference(project, full: true) }
  end
end

#parent?Boolean

Returns:

  • (Boolean)

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

def parent?
  child_pipelines.exists?
end

#persisted_variablesObject


815
816
817
818
819
820
821
822
# File 'app/models/ci/pipeline.rb', line 815

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

    variables.append(key: 'CI_PIPELINE_ID', value: id.to_s)
    variables.append(key: 'CI_PIPELINE_URL', value: Gitlab::Routing.url_helpers.project_pipeline_url(project, self))
  end
end

#persistent_refObject


1251
1252
1253
# File 'app/models/ci/pipeline.rb', line 1251

def persistent_ref
  @persistent_ref ||= PersistentRef.new(pipeline: self)
end

#predefined_commit_variablesObject


848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
# File 'app/models/ci/pipeline.rb', line 848

def predefined_commit_variables
  strong_memoize(:predefined_commit_variables) do
    Gitlab::Ci::Variables::Collection.new.tap do |variables|
      next variables unless sha.present?

      variables.append(key: 'CI_COMMIT_SHA', value: sha)
      variables.append(key: 'CI_COMMIT_SHORT_SHA', value: short_sha)
      variables.append(key: 'CI_COMMIT_BEFORE_SHA', value: before_sha)
      variables.append(key: 'CI_COMMIT_REF_NAME', value: source_ref)
      variables.append(key: 'CI_COMMIT_REF_SLUG', value: source_ref_slug)
      variables.append(key: 'CI_COMMIT_BRANCH', value: ref) if branch?
      variables.append(key: 'CI_COMMIT_TAG', value: ref) if tag?
      variables.append(key: 'CI_COMMIT_MESSAGE', value: git_commit_message.to_s)
      variables.append(key: 'CI_COMMIT_TITLE', value: git_commit_full_title.to_s)
      variables.append(key: 'CI_COMMIT_DESCRIPTION', value: git_commit_description.to_s)
      variables.append(key: 'CI_COMMIT_REF_PROTECTED', value: (!!protected_ref?).to_s)
      variables.append(key: 'CI_COMMIT_TIMESTAMP', value: git_commit_timestamp.to_s)
      variables.append(key: 'CI_COMMIT_AUTHOR', value: git_author_full_text.to_s)

      # legacy variables
      variables.append(key: 'CI_BUILD_REF', value: sha)
      variables.append(key: 'CI_BUILD_BEFORE_SHA', value: before_sha)
      variables.append(key: 'CI_BUILD_REF_NAME', value: source_ref)
      variables.append(key: 'CI_BUILD_REF_SLUG', value: source_ref_slug)
      variables.append(key: 'CI_BUILD_TAG', value: ref) if tag?
    end
  end
end

#predefined_merge_request_variablesObject


877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
# File 'app/models/ci/pipeline.rb', line 877

def predefined_merge_request_variables
  strong_memoize(:predefined_merge_request_variables) do
    Gitlab::Ci::Variables::Collection.new.tap do |variables|
      next variables unless merge_request?

      variables.append(key: 'CI_MERGE_REQUEST_EVENT_TYPE', value: merge_request_event_type.to_s)
      variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', value: source_sha.to_s)
      variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA', value: target_sha.to_s)

      diff = self.merge_request_diff
      if diff.present?
        variables.append(key: 'CI_MERGE_REQUEST_DIFF_ID', value: diff.id.to_s)
        variables.append(key: 'CI_MERGE_REQUEST_DIFF_BASE_SHA', value: diff.base_commit_sha)
      end

      variables.concat(merge_request.predefined_variables)
    end
  end
end

#predefined_variablesObject


824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
# File 'app/models/ci/pipeline.rb', line 824

def predefined_variables
  Gitlab::Ci::Variables::Collection.new.tap do |variables|
    variables.append(key: 'CI_PIPELINE_IID', value: iid.to_s)
    variables.append(key: 'CI_PIPELINE_SOURCE', value: source.to_s)
    variables.append(key: 'CI_PIPELINE_CREATED_AT', value: created_at&.iso8601)

    variables.concat(predefined_commit_variables)
    variables.concat(predefined_merge_request_variables)

    if open_merge_requests_refs.any?
      variables.append(key: 'CI_OPEN_MERGE_REQUESTS', value: open_merge_requests_refs.join(','))
    end

    variables.append(key: 'CI_GITLAB_FIPS_MODE', value: 'true') if Gitlab::FIPS.enabled?

    variables.append(key: 'CI_KUBERNETES_ACTIVE', value: 'true') if has_kubernetes_active?
    variables.append(key: 'CI_DEPLOY_FREEZE', value: 'true') if freeze_period?

    if external_pull_request_event? && external_pull_request
      variables.concat(external_pull_request.predefined_variables)
    end
  end
end

#protected_ref?Boolean

Returns:

  • (Boolean)

803
804
805
# File 'app/models/ci/pipeline.rb', line 803

def protected_ref?
  strong_memoize(:protected_ref) { project.protected_for?(git_ref) }
end

#queued_durationObject


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

def queued_duration
  return unless started_at

  seconds = (started_at - created_at).to_i
  seconds unless seconds == 0
end

#ref_exists?Boolean

Returns:

  • (Boolean)

496
497
498
499
500
# File 'app/models/ci/pipeline.rb', line 496

def ref_exists?
  project.repository.ref_exists?(git_ref)
rescue Gitlab::Git::Repository::NoRepository
  false
end

This returns a list of MRs that point to the same source project/branch


927
928
929
930
931
932
933
934
935
936
937
938
# File 'app/models/ci/pipeline.rb', line 927

def related_merge_requests
  if merge_request?
    # We look for all other MRs that this branch might be pointing to
    MergeRequest.where(
      source_project_id: merge_request.source_project_id,
      source_branch: merge_request.source_branch)
  else
    MergeRequest.where(
      source_project_id: project_id,
      source_branch: ref)
  end
end

#reset_source_bridge!(current_user) ⇒ Object


1283
1284
1285
1286
1287
1288
# File 'app/models/ci/pipeline.rb', line 1283

def reset_source_bridge!(current_user)
  return unless bridge_waiting?

  source_bridge.pending!
  Ci::AfterRequeueJobService.new(project, current_user).execute(source_bridge) # rubocop:disable CodeReuse/ServiceClass
end

#retriedObject


658
659
660
# File 'app/models/ci/pipeline.rb', line 658

def retried
  @retried ||= (statuses.order(id: :desc) - latest_statuses)
end

#retry_failed(current_user) ⇒ Object

rubocop: disable CodeReuse/ServiceClass


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

def retry_failed(current_user)
  Ci::RetryPipelineService.new(project, current_user)
    .execute(self)
end

#retryable?Boolean

Returns:

  • (Boolean)

599
600
601
# File 'app/models/ci/pipeline.rb', line 599

def retryable?
  retryable_builds.any?
end

#root_ancestorObject

Follow the parent-child relationships and return the top-level parent


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

def root_ancestor
  return self unless child?

  object_hierarchy(project_condition: :same)
    .base_and_ancestors(hierarchy_order: :desc)
    .first
end

#same_family_pipeline_idsObject


955
956
957
958
959
# File 'app/models/ci/pipeline.rb', line 955

def same_family_pipeline_ids
  ::Gitlab::Ci::PipelineObjectHierarchy.new(
    self.class.default_scoped.where(id: root_ancestor), options: { project_condition: :same }
  ).base_and_descendants.select(:id)
end

#security_reports(report_types: []) ⇒ Object


1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
# File 'app/models/ci/pipeline.rb', line 1295

def security_reports(report_types: [])
  reports_scope = report_types.empty? ? ::Ci::JobArtifact.security_reports : ::Ci::JobArtifact.security_reports(file_types: report_types)
  types_to_collect = report_types.empty? ? ::Ci::JobArtifact::SECURITY_REPORT_FILE_TYPES : report_types

  ::Gitlab::Ci::Reports::Security::Reports.new(self).tap do |security_reports|
    latest_report_builds(reports_scope).each do |build|
      build.collect_security_reports!(security_reports, report_types: types_to_collect)
    end
  end
end

#self_and_ancestorsObject

With only parent-child pipelines


996
997
998
# File 'app/models/ci/pipeline.rb', line 996

def self_and_ancestors
  object_hierarchy(project_condition: :same).base_and_ancestors
end

#self_and_descendantsObject

With only parent-child pipelines


1001
1002
1003
# File 'app/models/ci/pipeline.rb', line 1001

def self_and_descendants
  object_hierarchy(project_condition: :same).base_and_descendants
end

#self_and_upstreamsObject

With multi-project and parent-child pipelines


986
987
988
# File 'app/models/ci/pipeline.rb', line 986

def self_and_upstreams
  object_hierarchy.base_and_ancestors
end

#set_status(new_status) ⇒ Object


782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
# File 'app/models/ci/pipeline.rb', line 782

def set_status(new_status)
  retry_optimistic_lock(self, name: 'ci_pipeline_set_status') do
    case new_status
    when 'created' then nil
    when 'waiting_for_resource' then request_resource
    when 'preparing' then prepare
    when 'pending' then enqueue
    when 'running' then run
    when 'success' then succeed
    when 'failed' then drop
    when 'canceled' then cancel
    when 'skipped' then skip
    when 'manual' then block
    when 'scheduled' then delay
    else
      raise Ci::HasStatus::UnknownStatusError,
            "Unknown status `#{new_status}`"
    end
  end
end

#short_shaObject


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

def short_sha
  Ci::Pipeline.truncate_sha(sha)
end

#source_refObject


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

def source_ref
  if merge_request?
    merge_request.source_branch
  else
    ref
  end
end

#source_ref_pathObject


1259
1260
1261
1262
1263
1264
1265
# File 'app/models/ci/pipeline.rb', line 1259

def source_ref_path
  if branch? || merge_request?
    Gitlab::Git::BRANCH_REF_PREFIX + source_ref.to_s
  elsif tag?
    Gitlab::Git::TAG_REF_PREFIX + source_ref.to_s
  end
end

#source_ref_slugObject


1227
1228
1229
# File 'app/models/ci/pipeline.rb', line 1227

def source_ref_slug
  Gitlab::Utils.slugify(source_ref.to_s)
end

#stages_countObject


470
471
472
# File 'app/models/ci/pipeline.rb', line 470

def stages_count
  statuses.select(:stage).distinct.count
end

#stages_namesObject


486
487
488
489
# File 'app/models/ci/pipeline.rb', line 486

def stages_names
  statuses.order(:stage_idx).distinct
    .pluck(:stage, :stage_idx).map(&:first)
end

#stuck?Boolean

Returns:

  • (Boolean)

595
596
597
# File 'app/models/ci/pipeline.rb', line 595

def stuck?
  pending_builds.any?(&:stuck?)
end

#tags_countObject


478
479
480
# File 'app/models/ci/pipeline.rb', line 478

def tags_count
  ActsAsTaggableOn::Tagging.where(taggable: builds).count
end

#terraform_reportsObject


1132
1133
1134
1135
1136
1137
1138
# File 'app/models/ci/pipeline.rb', line 1132

def terraform_reports
  ::Gitlab::Ci::Reports::TerraformReports.new.tap do |terraform_reports|
    latest_report_builds(::Ci::JobArtifact.terraform_reports).each do |build|
      build.collect_terraform_reports!(terraform_reports)
    end
  end
end

#test_report_summaryObject


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

def test_report_summary
  strong_memoize(:test_report_summary) do
    Gitlab::Ci::Reports::TestReportSummary.new(latest_builds_report_results)
  end
end

#test_reportsObject


1100
1101
1102
1103
1104
1105
1106
# File 'app/models/ci/pipeline.rb', line 1100

def test_reports
  Gitlab::Ci::Reports::TestReports.new.tap do |test_reports|
    latest_test_report_builds.find_each do |build|
      build.collect_test_reports!(test_reports)
    end
  end
end

#top_level_worktree_pathsObject


1177
1178
1179
1180
1181
# File 'app/models/ci/pipeline.rb', line 1177

def top_level_worktree_paths
  strong_memoize(:top_level_worktree_paths) do
    project.repository.tree(sha).blobs.map(&:path)
  end
end

#total_sizeObject


474
475
476
# File 'app/models/ci/pipeline.rb', line 474

def total_size
  statuses.count(:id)
end

#triggered_by?(current_user) ⇒ Boolean

Returns:

  • (Boolean)

1215
1216
1217
# File 'app/models/ci/pipeline.rb', line 1215

def triggered_by?(current_user)
  user == current_user
end

#triggered_pipelines_with_preloadsObject


516
517
518
# File 'app/models/ci/pipeline.rb', line 516

def triggered_pipelines_with_preloads
  triggered_pipelines.preload(:source_job)
end

#update_builds_coverageObject


669
670
671
# File 'app/models/ci/pipeline.rb', line 669

def update_builds_coverage
  builds.with_coverage_regex.without_coverage.each(&:update_coverage)
end

#update_durationObject


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

def update_duration
  return unless started_at

  self.duration = Gitlab::Ci::Pipeline::Duration.from_pipeline(self)
end

#upstream_rootObject

Follow the upstream pipeline relationships, regardless of multi-project or parent-child, and return the top-level ancestor.


1016
1017
1018
# File 'app/models/ci/pipeline.rb', line 1016

def upstream_root
  object_hierarchy.base_and_ancestors(hierarchy_order: :desc).first
end

#uses_needs?Boolean

Returns:

  • (Boolean)

466
467
468
# File 'app/models/ci/pipeline.rb', line 466

def uses_needs?
  builds.where(scheduling_type: :dag).any?
end

#valid_commit_shaObject


525
526
527
528
529
# File 'app/models/ci/pipeline.rb', line 525

def valid_commit_sha
  if self.sha == Gitlab::Git::BLANK_SHA
    self.errors.add(:sha, " cant be 00000000 (branch removal)")
  end
end

#variables_builderObject


811
812
813
# File 'app/models/ci/pipeline.rb', line 811

def variables_builder
  @variables_builder ||= ::Gitlab::Ci::Variables::Builder.new(self)
end

#warning_messages(limit: nil) ⇒ Object


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

def warning_messages(limit: nil)
  messages.select(&:warning?).tap do |warnings|
    break warnings.take(limit) if limit
  end
end