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

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

Constants included from Partitionable

Ci::Partitionable::MUTEX

Constants inherited from ApplicationRecord

ApplicationRecord::MAX_PLUCK

Constants included from ResetOnUnionError

ResetOnUnionError::MAX_RESET_PERIOD

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, namespace_init, project_init, scope_attrs, scope_usage

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?, #incomplete?, #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



47
48
49
# File 'app/models/ci/pipeline.rb', line 47

def 
  @config_metadata
end

Class Method Details

.auto_devops_pipelines_completed_totalObject



560
561
562
# File 'app/models/ci/pipeline.rb', line 560

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



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

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

.builds_count_in_alive_pipelinesObject



513
514
515
# File 'app/models/ci/pipeline.rb', line 513

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

.current_partition_valueObject



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

def self.current_partition_value
  100
end

.internal_sourcesObject



552
553
554
# File 'app/models/ci/pipeline.rb', line 552

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

.jobs_count_in_alive_pipelinesObject



509
510
511
# File 'app/models/ci/pipeline.rb', line 509

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

.last_finished_for_ref_id(ci_ref_id) ⇒ Object



540
541
542
# File 'app/models/ci/pipeline.rb', line 540

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



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

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.


526
527
528
529
530
531
532
533
534
# File 'app/models/ci/pipeline.rb', line 526

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



501
502
503
# File 'app/models/ci/pipeline.rb', line 501

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

.latest_status(ref = nil) ⇒ Object



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

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

.latest_successful_for_ref(ref) ⇒ Object



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

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

.latest_successful_for_refs(refs) ⇒ Object



489
490
491
492
493
494
495
496
497
498
499
# File 'app/models/ci/pipeline.rb', line 489

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



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

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

.latest_successful_ids_per_projectObject



536
537
538
# File 'app/models/ci/pipeline.rb', line 536

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.



464
465
466
467
468
469
470
471
472
473
474
475
# File 'app/models/ci/pipeline.rb', line 464

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

.object_hierarchy(relation, options = {}) ⇒ Object



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

def self.object_hierarchy(relation, options = {})
  ::Gitlab::Ci::PipelineObjectHierarchy.new(relation, options: options)
end

.total_durationObject



548
549
550
# File 'app/models/ci/pipeline.rb', line 548

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

.truncate_sha(sha) ⇒ Object



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

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

Instance Method Details

#accessibility_reportsObject



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

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

#add_error_message(content) ⇒ Object



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

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

#add_warning_message(content) ⇒ Object



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

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

#age_in_minutesObject



1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
# File 'app/models/ci/pipeline.rb', line 1351

def age_in_minutes
  return 0 unless persisted?

  unless has_attribute?(:created_at)
    raise ArgumentError, 'pipeline not fully loaded'
  end

  return 0 unless created_at

  (Time.current - created_at).ceil / 60
end

#all_child_pipelinesObject

With only parent-child pipelines



1010
1011
1012
# File 'app/models/ci/pipeline.rb', line 1010

def all_child_pipelines
  object_hierarchy(project_condition: :same).descendants
end

#all_merge_requestsObject

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



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

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



912
913
914
# File 'app/models/ci/pipeline.rb', line 912

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

#all_worktree_pathsObject



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

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

#auto_canceled?Boolean

Returns:

  • (Boolean)


689
690
691
# File 'app/models/ci/pipeline.rb', line 689

def auto_canceled?
  canceled? && auto_canceled_by_id?
end

#batch_lookup_report_artifact_for_file_type(file_type) ⇒ Object



732
733
734
# File 'app/models/ci/pipeline.rb', line 732

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



736
737
738
739
740
741
742
743
744
745
# File 'app/models/ci/pipeline.rb', line 736

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



661
662
663
# File 'app/models/ci/pipeline.rb', line 661

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

#branch_updated?Boolean

Returns:

  • (Boolean)


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

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

#bridge_triggered?Boolean

Returns:

  • (Boolean)


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

def bridge_triggered?
  source_bridge.present?
end

#bridge_waiting?Boolean

Returns:

  • (Boolean)


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

def bridge_waiting?
  source_bridge&.dependent?
end

#bridges_in_self_and_project_descendantsObject



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

def bridges_in_self_and_project_descendants
  Ci::Bridge.latest.where(pipeline: self_and_project_descendants)
end

#build_matchersObject



1335
1336
1337
# File 'app/models/ci/pipeline.rb', line 1335

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

#build_with_artifacts_in_self_and_project_descendants(name) ⇒ Object



952
953
954
955
956
957
# File 'app/models/ci/pipeline.rb', line 952

def build_with_artifacts_in_self_and_project_descendants(name)
  builds_in_self_and_project_descendants
    .ordered_by_pipeline # find job in hierarchical order
    .with_downloadable_artifacts
    .find_by_name(name)
end

#builds_in_self_and_project_descendantsObject



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

def builds_in_self_and_project_descendants
  Ci::Build.latest.where(pipeline: self_and_project_descendants)
end

#builds_with_coverageObject



1088
1089
1090
# File 'app/models/ci/pipeline.rb', line 1088

def builds_with_coverage
  builds.latest.with_coverage
end

#builds_with_failed_tests(limit: nil) ⇒ Object



1092
1093
1094
# File 'app/models/ci/pipeline.rb', line 1092

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

#can_generate_codequality_reports?Boolean

Returns:

  • (Boolean)


1116
1117
1118
# File 'app/models/ci/pipeline.rb', line 1116

def can_generate_codequality_reports?
  complete_and_has_reports?(Ci::JobArtifact.of_report_type(:codequality))
end

#cancelable?Boolean

Returns:

  • (Boolean)


685
686
687
# File 'app/models/ci/pipeline.rb', line 685

def cancelable?
  cancelable_statuses.any?
end

#child?Boolean

Returns:

  • (Boolean)


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

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

#cluster_agent_authorizationsObject



1339
1340
1341
1342
1343
# File 'app/models/ci/pipeline.rb', line 1339

def cluster_agent_authorizations
  strong_memoize(:cluster_agent_authorizations) do
    ::Clusters::Agents::Authorizations::CiAccess::Finder.new(project).execute
  end
end

#codequality_reportsObject



1142
1143
1144
1145
1146
1147
1148
# File 'app/models/ci/pipeline.rb', line 1142

def codequality_reports
  Gitlab::Ci::Reports::CodequalityReports.new.tap do |codequality_reports|
    latest_report_builds(Ci::JobArtifact.of_report_type(:codequality)).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?`



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

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

#complete_and_has_reports?(reports_scope) ⇒ Boolean

Returns:

  • (Boolean)


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

def complete_and_has_reports?(reports_scope)
  if Feature.enabled?(:mr_show_reports_immediately, project, type: :development)
    latest_report_builds(reports_scope).exists?
  else
    complete? && has_reports?(reports_scope)
  end
end

#complete_hierarchy_countObject

Applies to all parent-child and multi-project pipelines



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

def complete_hierarchy_count
  upstream_root.self_and_downstreams.count
end

#coverageObject



721
722
723
724
725
726
# File 'app/models/ci/pipeline.rb', line 721

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

#created_successfully?Boolean

Returns:

  • (Boolean)


1055
1056
1057
# File 'app/models/ci/pipeline.rb', line 1055

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

#dangling?Boolean

Returns:

  • (Boolean)


1281
1282
1283
# File 'app/models/ci/pipeline.rb', line 1281

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

#default_branch?Boolean

Returns:

  • (Boolean)


1209
1210
1211
# File 'app/models/ci/pipeline.rb', line 1209

def default_branch?
  ref == project.default_branch
end

#detached_merge_request_pipeline?Boolean

Returns:

  • (Boolean)


1221
1222
1223
# File 'app/models/ci/pipeline.rb', line 1221

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

#detailed_status(current_user) ⇒ Object



1059
1060
1061
1062
1063
# File 'app/models/ci/pipeline.rb', line 1059

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

#distinct_tags_countObject



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

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

#ensure_ci_ref!Object



1299
1300
1301
# File 'app/models/ci/pipeline.rb', line 1299

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

#ensure_persistent_refObject



1303
1304
1305
1306
1307
# File 'app/models/ci/pipeline.rb', line 1303

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).



1295
1296
1297
# File 'app/models/ci/pipeline.rb', line 1295

def ensure_scheduling_type!
  processables.populate_scheduling_type!
end

#environments_in_self_and_project_descendants(deployment_status: nil) ⇒ Object



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

def environments_in_self_and_project_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 =
    jobs_in_self_and_project_descendants.joins(:metadata)
                                  .where.not(Ci::BuildMetadata.table_name => { expanded_environment_name: nil })
                                  .distinct("#{Ci::BuildMetadata.quoted_table_name}.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.



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

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

#external_pull_request?Boolean

Returns:

  • (Boolean)


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

def external_pull_request?
  external_pull_request_id.present?
end

#find_job_with_archive_artifacts(name) ⇒ Object



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

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

#freeze_period?Boolean

Returns:

  • (Boolean)


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

def freeze_period?
  strong_memoize(:freeze_period) do
    project.freeze_periods.any?(&:active?)
  end
end

#full_error_messagesObject



1261
1262
1263
# File 'app/models/ci/pipeline.rb', line 1261

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

#git_author_emailObject



619
620
621
622
623
# File 'app/models/ci/pipeline.rb', line 619

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

#git_author_full_textObject



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

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

#git_author_nameObject



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

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

#git_commit_descriptionObject



649
650
651
652
653
# File 'app/models/ci/pipeline.rb', line 649

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

#git_commit_full_titleObject



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

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

#git_commit_messageObject



631
632
633
634
635
# File 'app/models/ci/pipeline.rb', line 631

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

#git_commit_timestampObject



655
656
657
658
659
# File 'app/models/ci/pipeline.rb', line 655

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

#git_commit_titleObject



637
638
639
640
641
# File 'app/models/ci/pipeline.rb', line 637

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

#has_archive_artifacts?Boolean

Returns:

  • (Boolean)


1158
1159
1160
# File 'app/models/ci/pipeline.rb', line 1158

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)


1112
1113
1114
# File 'app/models/ci/pipeline.rb', line 1112

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

#has_coverage_reports?Boolean

Returns:

  • (Boolean)


1108
1109
1110
# File 'app/models/ci/pipeline.rb', line 1108

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

#has_erasable_artifacts?Boolean

Returns:

  • (Boolean)


1166
1167
1168
# File 'app/models/ci/pipeline.rb', line 1166

def has_erasable_artifacts?
  complete? && builds.latest.with_erasable_artifacts.exists?
end

#has_exposed_artifacts?Boolean

Returns:

  • (Boolean)


1162
1163
1164
# File 'app/models/ci/pipeline.rb', line 1162

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

#has_kubernetes_active?Boolean

Returns:

  • (Boolean)


765
766
767
768
769
# File 'app/models/ci/pipeline.rb', line 765

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

#has_reports?(reports_scope) ⇒ Boolean

Returns:

  • (Boolean)


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

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

#has_test_reports?Boolean

Returns:

  • (Boolean)


1345
1346
1347
1348
1349
# File 'app/models/ci/pipeline.rb', line 1345

def has_test_reports?
  strong_memoize(:has_test_reports) do
    has_reports?(::Ci::JobArtifact.of_report_type(:test))
  end
end

#has_warnings?Boolean

Returns:

  • (Boolean)


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

def has_warnings?
  number_of_warnings > 0
end

#has_yaml_errors?Boolean

Returns:

  • (Boolean)


799
800
801
# File 'app/models/ci/pipeline.rb', line 799

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.



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

alias_method :jobs_git_ref, :git_ref

#jobs_in_self_and_project_descendantsObject



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

def jobs_in_self_and_project_descendants
  Ci::Processable.latest.where(pipeline: self_and_project_descendants)
end

#latest?Boolean

Returns:

  • (Boolean)


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

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



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

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



753
754
755
756
757
758
759
760
761
762
763
# File 'app/models/ci/pipeline.rb', line 753

def latest_report_artifacts
  ::Gitlab::SafeRequestStore.fetch("pipeline:#{self.id}:latest_report_artifacts") do
    ::Ci::JobArtifact.where(
      id: job_artifacts.all_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.all_reports) ⇒ Object



1076
1077
1078
# File 'app/models/ci/pipeline.rb', line 1076

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

#latest_report_builds_in_self_and_project_descendants(reports_scope = ::Ci::JobArtifact.all_reports) ⇒ Object



1084
1085
1086
# File 'app/models/ci/pipeline.rb', line 1084

def latest_report_builds_in_self_and_project_descendants(reports_scope = ::Ci::JobArtifact.all_reports)
  builds_in_self_and_project_descendants.with_artifacts(reports_scope)
end

#latest_test_report_buildsObject



1080
1081
1082
# File 'app/models/ci/pipeline.rb', line 1080

def latest_test_report_builds
  latest_report_builds(Ci::JobArtifact.of_report_type(:test)).preload(:project, :metadata)
end

#lazy_ref_commitObject

rubocop: enable CodeReuse/ServiceClass



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

def lazy_ref_commit
  BatchLoader.for(ref).batch(key: project.id) 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)


1225
1226
1227
# File 'app/models/ci/pipeline.rb', line 1225

def legacy_detached_merge_request_pipeline?
  detached_merge_request_pipeline? && !merge_request_ref?
end

#legacy_triggerObject



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

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

#matches_sha_or_source_sha?(sha) ⇒ Boolean

Returns:

  • (Boolean)


1237
1238
1239
# File 'app/models/ci/pipeline.rb', line 1237

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

#merge_request?Boolean

Returns:

  • (Boolean)


1213
1214
1215
# File 'app/models/ci/pipeline.rb', line 1213

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

#merge_request_diffObject



1363
1364
1365
1366
1367
# File 'app/models/ci/pipeline.rb', line 1363

def merge_request_diff
  return unless merge_request?

  merge_request.merge_request_diff_for(merge_request_diff_sha)
end

#merge_request_event_typeObject



1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
# File 'app/models/ci/pipeline.rb', line 1265

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)


1233
1234
1235
# File 'app/models/ci/pipeline.rb', line 1233

def merge_request_ref?
  MergeRequest.merge_request_ref?(ref)
end

#merge_train_pipeline?Boolean

EE-only

Returns:

  • (Boolean)


1320
1321
1322
# File 'app/models/ci/pipeline.rb', line 1320

def merge_train_pipeline?
  false
end

#merged_result_pipeline?Boolean

Returns:

  • (Boolean)


1229
1230
1231
# File 'app/models/ci/pipeline.rb', line 1229

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



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

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

#modified_paths_since(compare_to_sha) ⇒ Object



1193
1194
1195
# File 'app/models/ci/pipeline.rb', line 1193

def modified_paths_since(compare_to_sha)
  project.repository.diff_stats(project.repository.merge_base(compare_to_sha, sha), sha).paths
end

#needs_processing?Boolean

Returns:

  • (Boolean)


792
793
794
795
796
797
# File 'app/models/ci/pipeline.rb', line 792

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

#notesObject



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

def notes
  project.notes.for_commit_id(sha)
end

#notes=(notes_to_save) ⇒ 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.



828
829
830
831
832
833
834
835
836
837
838
839
840
841
# File 'app/models/ci/pipeline.rb', line 828

def notes=(notes_to_save)
  notes_to_save.reject! do |note_to_save|
    notes.any? do |note|
      [note_to_save.note, note_to_save.created_at.to_i] == [note.note, note.created_at.to_i]
    end
  end

  notes_to_save.each do |note|
    note[:id] = nil
    note[:commit_id] = sha
    note[:noteable_id] = self['id']
    note.save!
  end
end

#number_of_warningsObject



781
782
783
784
785
786
787
788
789
790
# File 'app/models/ci/pipeline.rb', line 781

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



933
934
935
936
937
938
939
940
941
942
943
944
# File 'app/models/ci/pipeline.rb', line 933

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)


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

def parent?
  child_pipelines.exists?
end

#persisted_variablesObject



879
880
881
882
883
884
885
886
# File 'app/models/ci/pipeline.rb', line 879

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



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

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

#protected_ref?Boolean

Returns:

  • (Boolean)


867
868
869
# File 'app/models/ci/pipeline.rb', line 867

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

#queued_durationObject



888
889
890
891
892
893
# File 'app/models/ci/pipeline.rb', line 888

def queued_duration
  return unless started_at

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

#ref_exists?Boolean

Returns:

  • (Boolean)


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

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



918
919
920
921
922
923
924
925
926
927
928
929
# File 'app/models/ci/pipeline.rb', line 918

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

For dependent bridge jobs we reset the upstream bridge recursively to reflect that a downstream pipeline is running again



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

def reset_source_bridge!(current_user)
  # break recursion when no source_pipeline bridge (first upstream pipeline)
  return unless bridge_waiting?
  return unless current_user.can?(:update_pipeline, source_bridge.pipeline)

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

#retriedObject



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

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

#retry_failed(current_user) ⇒ Object

rubocop: disable CodeReuse/ServiceClass



694
695
696
697
# File 'app/models/ci/pipeline.rb', line 694

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

#retryable?Boolean

Returns:

  • (Boolean)


681
682
683
# File 'app/models/ci/pipeline.rb', line 681

def retryable?
  retryable_builds.any?
end

#root_ancestorObject

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



1019
1020
1021
1022
1023
1024
1025
# File 'app/models/ci/pipeline.rb', line 1019

def root_ancestor
  return self unless child?

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

#same_family_pipeline_idsObject



946
947
948
949
950
# File 'app/models/ci/pipeline.rb', line 946

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



1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
# File 'app/models/ci/pipeline.rb', line 1324

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_in_self_and_project_descendants(reports_scope).includes(pipeline: { project: :route }).each do |build| # rubocop:disable Rails/FindEach
      build.collect_security_reports!(security_reports, report_types: types_to_collect)
    end
  end
end

#self_and_downstreamsObject

With multi-project and parent-child pipelines



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

def self_and_downstreams
  object_hierarchy.base_and_descendants
end

#self_and_project_ancestorsObject

With only parent-child pipelines



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

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

#self_and_project_descendantsObject

With only parent-child pipelines



1005
1006
1007
# File 'app/models/ci/pipeline.rb', line 1005

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

#self_and_project_descendants_complete?Boolean

Returns:

  • (Boolean)


1014
1015
1016
# File 'app/models/ci/pipeline.rb', line 1014

def self_and_project_descendants_complete?
  self_and_project_descendants.all?(&:complete?)
end

#self_and_upstreamsObject

With multi-project and parent-child pipelines



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

def self_and_upstreams
  object_hierarchy.base_and_ancestors
end

#set_status(new_status) ⇒ Object



847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
# File 'app/models/ci/pipeline.rb', line 847

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



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

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

#source_refObject



1245
1246
1247
1248
1249
1250
1251
# File 'app/models/ci/pipeline.rb', line 1245

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

#source_ref_pathObject



1285
1286
1287
1288
1289
1290
1291
# File 'app/models/ci/pipeline.rb', line 1285

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



1253
1254
1255
# File 'app/models/ci/pipeline.rb', line 1253

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

#stage(name) ⇒ Object



1257
1258
1259
# File 'app/models/ci/pipeline.rb', line 1257

def stage(name)
  stages.find_by(name: name)
end

#stages_countObject



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

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

#stages_namesObject



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

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

#stuck?Boolean

Returns:

  • (Boolean)


677
678
679
# File 'app/models/ci/pipeline.rb', line 677

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

#tags_countObject



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

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

#terraform_reportsObject



1150
1151
1152
1153
1154
1155
1156
# File 'app/models/ci/pipeline.rb', line 1150

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

#test_report_summaryObject



1120
1121
1122
1123
1124
# File 'app/models/ci/pipeline.rb', line 1120

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

#test_reportsObject



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

def test_reports
  Gitlab::Ci::Reports::TestReport.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



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

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

#total_sizeObject



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

def total_size
  statuses.count(:id)
end

#triggered_by?(current_user) ⇒ Boolean

Returns:

  • (Boolean)


1241
1242
1243
# File 'app/models/ci/pipeline.rb', line 1241

def triggered_by?(current_user)
  user == current_user
end

#triggered_pipelines_with_preloadsObject



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

def triggered_pipelines_with_preloads
  triggered_pipelines.preload(:source_job)
end

#update_builds_coverageObject



728
729
730
# File 'app/models/ci/pipeline.rb', line 728

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

#update_durationObject



895
896
897
898
899
# File 'app/models/ci/pipeline.rb', line 895

def update_duration
  return unless started_at

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

#upstream_and_all_downstreamsObject

With multi-project and parent-child pipelines



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

def upstream_and_all_downstreams
  object_hierarchy.all_objects
end

#upstream_rootObject

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



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

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

#uses_needs?Boolean

Returns:

  • (Boolean)


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

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

#valid_commit_shaObject



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

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

#variables_builderObject



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

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

#warning_messages(limit: nil) ⇒ Object



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

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