Class: Ci::Pipeline
- Inherits:
-
ApplicationRecord
show all
- Includes:
- AfterCommitQueue, AtomicInternalId, HasCompletionReason, HasRef, HasStatus, Partitionable, EachBatch, FastDestroyAll::Helpers, FromUnion, Gitlab::Allowable, Gitlab::OptimisticLocking, Gitlab::Utils::StrongMemoize, Importable, Presentable, ShaAttribute, UpdatedAtFilterable
- Defined in:
- app/models/ci/pipeline.rb
Constant Summary
collapse
- MAX_OPEN_MERGE_REQUESTS_REFS =
4
- PROJECT_ROUTE_AND_NAMESPACE_ROUTE =
{
project: [:project_feature, :route, { namespace: :route }]
}.freeze
- DEFAULT_CONFIG_PATH =
'.gitlab-ci.yml'
- CANCELABLE_STATUSES =
(Ci::HasStatus::CANCELABLE_STATUSES + ['manual']).freeze
- UNLOCKABLE_STATUSES =
(Ci::Pipeline.completed_statuses + [:manual]).freeze
- COUNT_FAILED_JOBS_LIMIT =
UI only shows 100+. TODO: pass constant to UI for SSoT
101
AtomicInternalId::MissingValueError
Gitlab::OptimisticLocking::MAX_RETRIES
Constants included
from HasStatus
HasStatus::ACTIVE_STATUSES, HasStatus::ALIVE_STATUSES, HasStatus::AVAILABLE_STATUSES, HasStatus::BLOCKED_STATUS, 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
ApplicationRecord::MAX_PLUCK
HasCheckConstraints::NOT_NULL_CHECK_PATTERN
ResetOnColumnErrors::MAX_RESET_PERIOD
Instance Attribute Summary collapse
-
#config_metadata ⇒ Object
Ci::CreatePipelineService returns Ci::Pipeline so this is the only place where we can pass additional information from the service.
Attributes included from Importable
#importing, #user_contributions
Class Method Summary
collapse
Instance Method Summary
collapse
#perform_fast_destroy
#run_after_commit, #run_after_commit_or_now
Methods included from HasRef
#branch?, #ref_slug
group_init, #internal_id_read_scope, #internal_id_scope_attrs, #internal_id_scope_usage, namespace_init, project_init, scope_attrs, scope_usage
log_optimistic_lock_retries, retry_lock, retry_lock_histogram, retry_lock_logger
#can?, #can_all?, #can_any?
#present
Methods included from HasStatus
#active?, #blocked?, #complete?, #complete_or_manual?, #incomplete?, #started?
registered_models
model_name, table_name_prefix
===, 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
#reset_on_union_error, #reset_on_unknown_attribute_error
#serializable_hash
Instance Attribute Details
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
51
52
53
|
# File 'app/models/ci/pipeline.rb', line 51
def config_metadata
@config_metadata
end
|
Class Method Details
.auto_devops_pipelines_completed_total ⇒ Object
588
589
590
|
# File 'app/models/ci/pipeline.rb', line 588
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_statuses ⇒ Object
584
585
586
|
# File 'app/models/ci/pipeline.rb', line 584
def self.bridgeable_statuses
::Ci::Pipeline::AVAILABLE_STATUSES - %w[created waiting_for_resource waiting_for_callback preparing pending]
end
|
.builds_count_in_alive_pipelines ⇒ Object
541
542
543
|
# File 'app/models/ci/pipeline.rb', line 541
def self.builds_count_in_alive_pipelines
created_after(24.hours.ago).alive.joins(:builds).count
end
|
.current_partition_value ⇒ Object
.internal_id_scope_usage ⇒ Object
602
603
604
|
# File 'app/models/ci/pipeline.rb', line 602
def self.internal_id_scope_usage
:ci_pipelines
end
|
.internal_sources ⇒ Object
580
581
582
|
# File 'app/models/ci/pipeline.rb', line 580
def self.internal_sources
sources.reject { |source| source == "external" }.values
end
|
.jobs_count_in_alive_pipelines ⇒ Object
537
538
539
|
# File 'app/models/ci/pipeline.rb', line 537
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
568
569
570
|
# File 'app/models/ci/pipeline.rb', line 568
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
533
534
535
|
# File 'app/models/ci/pipeline.rb', line 533
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.
554
555
556
557
558
559
560
561
562
|
# File 'app/models/ci/pipeline.rb', line 554
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
529
530
531
|
# File 'app/models/ci/pipeline.rb', line 529
def self.latest_running_for_ref(ref)
newest_first(ref: ref).running.take
end
|
.latest_status(ref = nil) ⇒ Object
504
505
506
|
# File 'app/models/ci/pipeline.rb', line 504
def self.latest_status(ref = nil)
newest_first(ref: ref).pick(:status)
end
|
.latest_successful_for_ref(ref) ⇒ Object
508
509
510
|
# File 'app/models/ci/pipeline.rb', line 508
def self.latest_successful_for_ref(ref)
newest_first(ref: ref).success.take
end
|
.latest_successful_for_refs(refs) ⇒ Object
516
517
518
519
520
521
522
523
524
525
526
527
|
# File 'app/models/ci/pipeline.rb', line 516
def self.latest_successful_for_refs(refs)
return Ci::Pipeline.none if refs.empty?
refs_values = refs.map { |ref| "(#{connection.quote(ref)})" }.join(",")
query = Arel.sql(sanitize_sql_array(["refs_values.ref = #{quoted_table_name}.ref"]))
join_query = success.where(query).order(id: :desc).limit(1)
Ci::Pipeline
.from("(VALUES #{refs_values}) refs_values (ref)")
.joins("INNER JOIN LATERAL (#{join_query.to_sql}) #{quoted_table_name} ON TRUE")
.index_by(&:ref)
end
|
.latest_successful_for_sha(sha) ⇒ Object
512
513
514
|
# File 'app/models/ci/pipeline.rb', line 512
def self.latest_successful_for_sha(sha)
newest_first(sha: sha).success.take
end
|
.latest_successful_ids_per_project ⇒ Object
564
565
566
|
# File 'app/models/ci/pipeline.rb', line 564
def self.latest_successful_ids_per_project
success.group(:project_id).select('max(id) as id')
end
|
.newest_first(ref: nil, sha: nil, limit: nil) ⇒ 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 - Number of pipelines to return. Chaining with sampling methods (#pick, #take)
will cause unnecessary subqueries.
491
492
493
494
495
496
497
498
499
500
501
502
|
# File 'app/models/ci/pipeline.rb', line 491
def self.newest_first(ref: nil, sha: nil, limit: nil)
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
598
599
600
|
# File 'app/models/ci/pipeline.rb', line 598
def self.object_hierarchy(relation, options = {})
::Gitlab::Ci::PipelineObjectHierarchy.new(relation, options: options)
end
|
.total_duration ⇒ Object
576
577
578
|
# File 'app/models/ci/pipeline.rb', line 576
def self.total_duration
where.not(duration: nil).sum(:duration)
end
|
.truncate_sha(sha) ⇒ Object
572
573
574
|
# File 'app/models/ci/pipeline.rb', line 572
def self.truncate_sha(sha)
sha[0...8]
end
|
Instance Method Details
#accessibility_reports ⇒ Object
1187
1188
1189
1190
1191
1192
1193
|
# File 'app/models/ci/pipeline.rb', line 1187
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
833
834
835
|
# File 'app/models/ci/pipeline.rb', line 833
def add_error_message(content)
add_message(:error, content)
end
|
#add_warning_message(content) ⇒ Object
837
838
839
|
# File 'app/models/ci/pipeline.rb', line 837
def add_warning_message(content)
add_message(:warning, content)
end
|
#age_in_minutes ⇒ Object
1395
1396
1397
1398
1399
1400
1401
1402
1403
|
# File 'app/models/ci/pipeline.rb', line 1395
def age_in_minutes
return 0 unless persisted?
raise ArgumentError, 'pipeline not fully loaded' unless has_attribute?(:created_at)
return 0 unless created_at
(Time.current - created_at).ceil / 60
end
|
#all_child_pipelines ⇒ Object
With only parent-child pipelines
1051
1052
1053
|
# File 'app/models/ci/pipeline.rb', line 1051
def all_child_pipelines
object_hierarchy(project_condition: :same).descendants
end
|
#all_merge_requests ⇒ Object
All the merge requests for which the current pipeline runs/ran against
943
944
945
946
947
948
949
950
951
|
# File 'app/models/ci/pipeline.rb', line 943
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_recency ⇒ Object
953
954
955
|
# File 'app/models/ci/pipeline.rb', line 953
def all_merge_requests_by_recency
all_merge_requests.order(id: :desc)
end
|
#all_worktree_paths ⇒ Object
1252
1253
1254
1255
1256
|
# File 'app/models/ci/pipeline.rb', line 1252
def all_worktree_paths
strong_memoize(:all_worktree_paths) do
project.repository.ls_files(sha)
end
end
|
#auto_cancel_on_job_failure ⇒ Object
1411
1412
1413
|
# File 'app/models/ci/pipeline.rb', line 1411
def auto_cancel_on_job_failure
pipeline_metadata&.auto_cancel_on_job_failure || 'none'
end
|
#auto_cancel_on_new_commit ⇒ Object
1415
1416
1417
|
# File 'app/models/ci/pipeline.rb', line 1415
def auto_cancel_on_new_commit
pipeline_metadata&.auto_cancel_on_new_commit || 'conservative'
end
|
#auto_canceled? ⇒ Boolean
721
722
723
|
# File 'app/models/ci/pipeline.rb', line 721
def auto_canceled?
canceled? && auto_canceled_by_id?
end
|
#batch_lookup_report_artifact_for_file_type(file_type) ⇒ Object
762
763
764
|
# File 'app/models/ci/pipeline.rb', line 762
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
766
767
768
769
770
771
772
773
774
775
|
# File 'app/models/ci/pipeline.rb', line 766
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_sha ⇒ Object
693
694
695
|
# File 'app/models/ci/pipeline.rb', line 693
def before_sha
super || project.repository.blank_ref
end
|
#branch_updated? ⇒ Boolean
1223
1224
1225
1226
1227
|
# File 'app/models/ci/pipeline.rb', line 1223
def branch_updated?
strong_memoize(:branch_updated) do
push_details.branch_updated?
end
end
|
#bridge_triggered? ⇒ Boolean
1079
1080
1081
|
# File 'app/models/ci/pipeline.rb', line 1079
def bridge_triggered?
source_bridge.present?
end
|
#bridge_waiting? ⇒ Boolean
1083
1084
1085
|
# File 'app/models/ci/pipeline.rb', line 1083
def bridge_waiting?
source_bridge&.dependent?
end
|
#bridges_in_self_and_project_descendants ⇒ Object
1004
1005
1006
|
# File 'app/models/ci/pipeline.rb', line 1004
def bridges_in_self_and_project_descendants
Ci::Bridge.in_partition(self).latest.where(pipeline: self_and_project_descendants)
end
|
#build_matchers ⇒ Object
1379
1380
1381
|
# File 'app/models/ci/pipeline.rb', line 1379
def build_matchers
self.builds.latest.build_matchers(project)
end
|
#build_with_artifacts_in_self_and_project_descendants(name) ⇒ Object
993
994
995
996
997
998
|
# File 'app/models/ci/pipeline.rb', line 993
def build_with_artifacts_in_self_and_project_descendants(name)
builds_in_self_and_project_descendants
.ordered_by_pipeline .with_downloadable_artifacts
.find_by_name(name)
end
|
#builds_in_self_and_project_descendants ⇒ Object
1000
1001
1002
|
# File 'app/models/ci/pipeline.rb', line 1000
def builds_in_self_and_project_descendants
Ci::Build.in_partition(self).latest.where(pipeline: self_and_project_descendants)
end
|
#builds_with_coverage ⇒ Object
1133
1134
1135
|
# File 'app/models/ci/pipeline.rb', line 1133
def builds_with_coverage
builds.latest.with_coverage
end
|
#builds_with_failed_tests(limit: nil) ⇒ Object
1137
1138
1139
|
# File 'app/models/ci/pipeline.rb', line 1137
def builds_with_failed_tests(limit: nil)
latest_test_report_builds.failed.limit(limit)
end
|
#can_generate_codequality_reports? ⇒ Boolean
1169
1170
1171
|
# File 'app/models/ci/pipeline.rb', line 1169
def can_generate_codequality_reports?
complete_and_has_reports?(Ci::JobArtifact.of_report_type(:codequality))
end
|
#cancel_async_on_job_failure ⇒ Object
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
|
# File 'app/models/ci/pipeline.rb', line 1419
def cancel_async_on_job_failure
case auto_cancel_on_job_failure
when 'none'
when 'all'
::Ci::UserCancelPipelineWorker.perform_async(id, id, user.id)
else
raise ArgumentError,
"Unknown auto_cancel_on_job_failure value: #{auto_cancel_on_job_failure}"
end
end
|
#cancelable? ⇒ Boolean
717
718
719
|
# File 'app/models/ci/pipeline.rb', line 717
def cancelable?
cancelable_statuses.any?
end
|
#child? ⇒ Boolean
1087
1088
1089
1090
|
# File 'app/models/ci/pipeline.rb', line 1087
def child?
parent_pipeline? && parent_pipeline.present?
end
|
#cluster_agent_authorizations ⇒ Object
#codequality_reports ⇒ Object
1195
1196
1197
1198
1199
1200
1201
|
# File 'app/models/ci/pipeline.rb', line 1195
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
|
#commit ⇒ Object
NOTE: This is loaded lazily and will never be nil, even if the commit cannot be found.
Use constructs like: ‘pipeline.commit.present?`
705
706
707
|
# File 'app/models/ci/pipeline.rb', line 705
def commit
@commit ||= Commit.lazy(project, sha)
end
|
#complete_and_has_reports?(reports_scope) ⇒ Boolean
1145
1146
1147
1148
1149
1150
1151
|
# File 'app/models/ci/pipeline.rb', line 1145
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_count ⇒ Object
Applies to all parent-child and multi-project pipelines
1075
1076
1077
|
# File 'app/models/ci/pipeline.rb', line 1075
def complete_hierarchy_count
upstream_root.self_and_downstreams.count
end
|
#complete_or_manual_and_has_reports?(reports_scope) ⇒ Boolean
1153
1154
1155
1156
1157
1158
1159
|
# File 'app/models/ci/pipeline.rb', line 1153
def complete_or_manual_and_has_reports?(reports_scope)
if Feature.enabled?(:mr_show_reports_immediately, project, type: :development)
latest_report_builds(reports_scope).exists?
else
complete_or_manual? && has_reports?(reports_scope)
end
end
|
#coverage ⇒ Object
753
754
755
756
|
# File 'app/models/ci/pipeline.rb', line 753
def coverage
coverage_array = latest_statuses.map(&:coverage).compact
coverage_array.sum / coverage_array.size if coverage_array.size >= 1
end
|
#created_successfully? ⇒ Boolean
1096
1097
1098
|
# File 'app/models/ci/pipeline.rb', line 1096
def created_successfully?
persisted? && failure_reason.blank?
end
|
#default_branch? ⇒ Boolean
1264
1265
1266
|
# File 'app/models/ci/pipeline.rb', line 1264
def default_branch?
ref == project.default_branch
end
|
#detached_merge_request_pipeline? ⇒ Boolean
1276
1277
1278
|
# File 'app/models/ci/pipeline.rb', line 1276
def detached_merge_request_pipeline?
merge_request? && target_sha.nil?
end
|
#detailed_status(current_user) ⇒ Object
1104
1105
1106
1107
1108
|
# File 'app/models/ci/pipeline.rb', line 1104
def detailed_status(current_user)
Gitlab::Ci::Status::Pipeline::Factory
.new(self.present, current_user)
.fabricate!
end
|
622
623
624
|
# File 'app/models/ci/pipeline.rb', line 622
def distinct_tags_count
Ci::Tagging.where(taggable: builds).count('distinct(tag_id)')
end
|
#ensure_ci_ref! ⇒ Object
1354
1355
1356
|
# File 'app/models/ci/pipeline.rb', line 1354
def ensure_ci_ref!
self.ci_ref = Ci::Ref.ensure_for(self)
end
|
#ensure_persistent_ref ⇒ Object
1358
1359
1360
1361
1362
|
# File 'app/models/ci/pipeline.rb', line 1358
def ensure_persistent_ref
return if persistent_ref.exist?
persistent_ref.create
end
|
#ensure_scheduling_type! ⇒ Object
1350
1351
1352
|
# File 'app/models/ci/pipeline.rb', line 1350
def ensure_scheduling_type!
processables.populate_scheduling_type!
end
|
#environments_in_self_and_project_descendants(deployment_status: nil) ⇒ Object
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
|
# File 'app/models/ci/pipeline.rb', line 1012
def environments_in_self_and_project_descendants(deployment_status: nil)
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_messages ⇒ Object
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.
851
852
853
|
# File 'app/models/ci/pipeline.rb', line 851
def error_messages
messages.select(&:error?)
end
|
#external_pull_request? ⇒ Boolean
1272
1273
1274
|
# File 'app/models/ci/pipeline.rb', line 1272
def external_pull_request?
external_pull_request_id.present?
end
|
#filtered_as_empty? ⇒ Boolean
1100
1101
1102
|
# File 'app/models/ci/pipeline.rb', line 1100
def filtered_as_empty?
filtered_by_rules? || filtered_by_workflow_rules?
end
|
#find_job_with_archive_artifacts(name) ⇒ Object
1110
1111
1112
|
# File 'app/models/ci/pipeline.rb', line 1110
def find_job_with_archive_artifacts(name)
builds.latest.with_downloadable_artifacts.find_by_name(name)
end
|
#freeze_period? ⇒ Boolean
801
802
803
804
805
|
# File 'app/models/ci/pipeline.rb', line 801
def freeze_period?
strong_memoize(:freeze_period) do
project.freeze_periods.any?(&:active?)
end
end
|
#full_error_messages ⇒ Object
1316
1317
1318
|
# File 'app/models/ci/pipeline.rb', line 1316
def full_error_messages
errors ? errors.full_messages.to_sentence : ""
end
|
#git_author_email ⇒ Object
651
652
653
654
655
|
# File 'app/models/ci/pipeline.rb', line 651
def git_author_email
strong_memoize(:git_author_email) do
commit.try(:author_email)
end
end
|
#git_author_full_text ⇒ Object
657
658
659
660
661
|
# File 'app/models/ci/pipeline.rb', line 657
def git_author_full_text
strong_memoize(:git_author_full_text) do
commit.try(:author_full_text)
end
end
|
#git_author_name ⇒ Object
645
646
647
648
649
|
# File 'app/models/ci/pipeline.rb', line 645
def git_author_name
strong_memoize(:git_author_name) do
commit.try(:author_name)
end
end
|
#git_commit_description ⇒ Object
681
682
683
684
685
|
# File 'app/models/ci/pipeline.rb', line 681
def git_commit_description
strong_memoize(:git_commit_description) do
commit.try(:description)
end
end
|
#git_commit_full_title ⇒ Object
675
676
677
678
679
|
# File 'app/models/ci/pipeline.rb', line 675
def git_commit_full_title
strong_memoize(:git_commit_full_title) do
commit.try(:full_title)
end
end
|
#git_commit_message ⇒ Object
663
664
665
666
667
|
# File 'app/models/ci/pipeline.rb', line 663
def git_commit_message
strong_memoize(:git_commit_message) do
commit.try(:message)
end
end
|
#git_commit_timestamp ⇒ Object
687
688
689
690
691
|
# File 'app/models/ci/pipeline.rb', line 687
def git_commit_timestamp
strong_memoize(:git_commit_timestamp) do
commit.try(:timestamp)
end
end
|
#git_commit_title ⇒ Object
669
670
671
672
673
|
# File 'app/models/ci/pipeline.rb', line 669
def git_commit_title
strong_memoize(:git_commit_title) do
commit.try(:title)
end
end
|
#has_archive_artifacts? ⇒ Boolean
1211
1212
1213
|
# File 'app/models/ci/pipeline.rb', line 1211
def has_archive_artifacts?
complete? && builds.latest.with_existing_job_artifacts(Ci::JobArtifact.archive.or(Ci::JobArtifact.metadata)).exists?
end
|
#has_codequality_mr_diff_report? ⇒ Boolean
1165
1166
1167
|
# File 'app/models/ci/pipeline.rb', line 1165
def has_codequality_mr_diff_report?
pipeline_artifacts&.report_exists?(:code_quality_mr_diff)
end
|
#has_coverage_reports? ⇒ Boolean
1161
1162
1163
|
# File 'app/models/ci/pipeline.rb', line 1161
def has_coverage_reports?
pipeline_artifacts&.report_exists?(:code_coverage)
end
|
#has_erasable_artifacts? ⇒ Boolean
1219
1220
1221
|
# File 'app/models/ci/pipeline.rb', line 1219
def has_erasable_artifacts?
complete? && builds.latest.with_erasable_artifacts.exists?
end
|
#has_exposed_artifacts? ⇒ Boolean
1215
1216
1217
|
# File 'app/models/ci/pipeline.rb', line 1215
def has_exposed_artifacts?
complete? && builds.latest.with_exposed_artifacts.exists?
end
|
#has_kubernetes_active? ⇒ Boolean
795
796
797
798
799
|
# File 'app/models/ci/pipeline.rb', line 795
def has_kubernetes_active?
strong_memoize(:has_kubernetes_active) do
project.deployment_platform&.active?
end
end
|
#has_reports?(reports_scope) ⇒ Boolean
1141
1142
1143
|
# File 'app/models/ci/pipeline.rb', line 1141
def has_reports?(reports_scope)
latest_report_builds(reports_scope).exists?
end
|
#has_test_reports? ⇒ Boolean
1389
1390
1391
1392
1393
|
# File 'app/models/ci/pipeline.rb', line 1389
def has_test_reports?
strong_memoize(:has_test_reports) do
has_reports?(::Ci::JobArtifact.of_report_type(:test))
end
end
|
#has_warnings? ⇒ Boolean
807
808
809
|
# File 'app/models/ci/pipeline.rb', line 807
def has_warnings?
number_of_warnings > 0
end
|
#has_yaml_errors? ⇒ Boolean
829
830
831
|
# File 'app/models/ci/pipeline.rb', line 829
def has_yaml_errors?
yaml_errors.present?
end
|
#jobs_git_ref ⇒ Object
This is used to retain access to the method defined by ‘Ci::HasRef` before being overridden in this class.
55
|
# File 'app/models/ci/pipeline.rb', line 55
alias_method :jobs_git_ref, :git_ref
|
#jobs_in_self_and_project_descendants ⇒ Object
1008
1009
1010
|
# File 'app/models/ci/pipeline.rb', line 1008
def jobs_in_self_and_project_descendants
Ci::Processable.in_partition(self).latest.where(pipeline: self_and_project_descendants)
end
|
#latest? ⇒ Boolean
742
743
744
745
746
747
|
# File 'app/models/ci/pipeline.rb', line 742
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_artifacts ⇒ Object
1114
1115
1116
1117
1118
1119
|
# File 'app/models/ci/pipeline.rb', line 1114
def latest_builds_with_artifacts
@latest_builds_with_artifacts ||= builds.latest.with_artifacts_not_expired.to_a
end
|
#latest_report_artifacts ⇒ Object
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
783
784
785
786
787
788
789
790
791
792
793
|
# File 'app/models/ci/pipeline.rb', line 783
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::JobArtifact.quoted_table_name}.id) as id")
.group(:file_type)
)
.preload(:job)
.group_by(&:file_type)
end
end
|
#latest_report_builds(reports_scope = ::Ci::JobArtifact.all_reports) ⇒ Object
1121
1122
1123
|
# File 'app/models/ci/pipeline.rb', line 1121
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
1129
1130
1131
|
# File 'app/models/ci/pipeline.rb', line 1129
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_builds ⇒ Object
1125
1126
1127
|
# File 'app/models/ci/pipeline.rb', line 1125
def latest_test_report_builds
latest_report_builds(Ci::JobArtifact.of_report_type(:test)).preload(:project, :metadata, job_artifacts: :artifact_report)
end
|
#lazy_ref_commit ⇒ Object
rubocop: enable CodeReuse/ServiceClass
732
733
734
735
736
737
738
739
740
|
# File 'app/models/ci/pipeline.rb', line 732
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
1280
1281
1282
|
# File 'app/models/ci/pipeline.rb', line 1280
def legacy_detached_merge_request_pipeline?
detached_merge_request_pipeline? && !merge_request_ref?
end
|
#legacy_trigger ⇒ Object
912
913
914
|
# File 'app/models/ci/pipeline.rb', line 912
def legacy_trigger
strong_memoize(:legacy_trigger) { trigger_requests.first }
end
|
#matches_sha_or_source_sha?(sha) ⇒ Boolean
1292
1293
1294
|
# File 'app/models/ci/pipeline.rb', line 1292
def matches_sha_or_source_sha?(sha)
self.sha == sha || self.source_sha == sha
end
|
#merge_request? ⇒ Boolean
1268
1269
1270
|
# File 'app/models/ci/pipeline.rb', line 1268
def merge_request?
merge_request_id.present? && merge_request.present?
end
|
#merge_request_diff ⇒ Object
1405
1406
1407
1408
1409
|
# File 'app/models/ci/pipeline.rb', line 1405
def merge_request_diff
return unless merge_request?
merge_request.merge_request_diff_for(merge_request_diff_sha)
end
|
#merge_request_event_type ⇒ Object
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
|
# File 'app/models/ci/pipeline.rb', line 1320
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
#merge_train_pipeline? ⇒ Boolean
1375
1376
1377
|
# File 'app/models/ci/pipeline.rb', line 1375
def merge_train_pipeline?
false
end
|
#merged_result_pipeline? ⇒ Boolean
1284
1285
1286
|
# File 'app/models/ci/pipeline.rb', line 1284
def merged_result_pipeline?
merge_request? && target_sha.present?
end
|
#modified_paths ⇒ Object
Returns the modified paths.
The returned value is
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
|
# File 'app/models/ci/pipeline.rb', line 1234
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
1246
1247
1248
1249
1250
|
# File 'app/models/ci/pipeline.rb', line 1246
def modified_paths_since(compare_to_sha)
strong_memoize_with(:modified_paths_since, compare_to_sha) do
project.repository.diff_stats(project.repository.merge_base(compare_to_sha, sha), sha).paths
end
end
|
#needs_processing? ⇒ Boolean
822
823
824
825
826
827
|
# File 'app/models/ci/pipeline.rb', line 822
def needs_processing?
statuses
.where(processed: [false, nil])
.latest
.exists?
end
|
#notes ⇒ Object
880
881
882
|
# File 'app/models/ci/pipeline.rb', line 880
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.
865
866
867
868
869
870
871
872
873
874
875
876
877
878
|
# File 'app/models/ci/pipeline.rb', line 865
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_warnings ⇒ Object
811
812
813
814
815
816
817
818
819
820
|
# File 'app/models/ci/pipeline.rb', line 811
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_refs ⇒ Object
We cannot use ‘all_merge_requests`, due to race condition This returns a list of at most 4 open MRs
974
975
976
977
978
979
980
981
982
983
984
985
|
# File 'app/models/ci/pipeline.rb', line 974
def open_merge_requests_refs
strong_memoize(:open_merge_requests_refs) do
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
1092
1093
1094
|
# File 'app/models/ci/pipeline.rb', line 1092
def parent?
child_pipelines.exists?
end
|
#persisted_variables ⇒ Object
920
921
922
923
924
925
926
927
|
# File 'app/models/ci/pipeline.rb', line 920
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_ref ⇒ Object
1332
1333
1334
|
# File 'app/models/ci/pipeline.rb', line 1332
def persistent_ref
@persistent_ref ||= PersistentRef.new(pipeline: self)
end
|
#protected_ref? ⇒ Boolean
rubocop: enable Metrics/CyclomaticComplexity
908
909
910
|
# File 'app/models/ci/pipeline.rb', line 908
def protected_ref?
strong_memoize(:protected_ref) { project.protected_for?(git_ref) }
end
|
#queued_duration ⇒ Object
929
930
931
932
933
934
|
# File 'app/models/ci/pipeline.rb', line 929
def queued_duration
return unless started_at
seconds = (started_at - created_at).to_i
seconds unless seconds == 0
end
|
#ref_exists? ⇒ Boolean
631
632
633
634
635
|
# File 'app/models/ci/pipeline.rb', line 631
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
959
960
961
962
963
964
965
966
967
968
969
970
|
# File 'app/models/ci/pipeline.rb', line 959
def related_merge_requests
if merge_request?
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
1366
1367
1368
1369
1370
1371
1372
|
# File 'app/models/ci/pipeline.rb', line 1366
def reset_source_bridge!(current_user)
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!) end
|
#retried ⇒ Object
749
750
751
|
# File 'app/models/ci/pipeline.rb', line 749
def retried
@retried ||= (statuses.order(id: :desc) - latest_statuses)
end
|
#retry_failed(current_user) ⇒ Object
rubocop: disable CodeReuse/ServiceClass
726
727
728
729
|
# File 'app/models/ci/pipeline.rb', line 726
def retry_failed(current_user)
Ci::RetryPipelineService.new(project, current_user)
.execute(self)
end
|
#retryable? ⇒ Boolean
713
714
715
|
# File 'app/models/ci/pipeline.rb', line 713
def retryable?
retryable_builds.any?
end
|
#root_ancestor ⇒ Object
Follow the parent-child relationships and return the top-level parent
1060
1061
1062
1063
1064
1065
1066
|
# File 'app/models/ci/pipeline.rb', line 1060
def root_ancestor
return self unless child?
object_hierarchy(project_condition: :same)
.base_and_ancestors(hierarchy_order: :desc)
.first
end
|
#same_family_pipeline_ids ⇒ Object
#self_and_downstreams ⇒ Object
With multi-project and parent-child pipelines
1031
1032
1033
|
# File 'app/models/ci/pipeline.rb', line 1031
def self_and_downstreams
object_hierarchy.base_and_descendants
end
|
#self_and_project_ancestors ⇒ Object
With only parent-child pipelines
1041
1042
1043
|
# File 'app/models/ci/pipeline.rb', line 1041
def self_and_project_ancestors
object_hierarchy(project_condition: :same).base_and_ancestors
end
|
#self_and_project_descendants ⇒ Object
With only parent-child pipelines
1046
1047
1048
|
# File 'app/models/ci/pipeline.rb', line 1046
def self_and_project_descendants
object_hierarchy(project_condition: :same).base_and_descendants
end
|
#self_and_project_descendants_complete? ⇒ Boolean
1055
1056
1057
|
# File 'app/models/ci/pipeline.rb', line 1055
def self_and_project_descendants_complete?
self_and_project_descendants.all?(&:complete?)
end
|
#self_and_upstreams ⇒ Object
With multi-project and parent-child pipelines
1026
1027
1028
|
# File 'app/models/ci/pipeline.rb', line 1026
def self_and_upstreams
object_hierarchy.base_and_ancestors
end
|
#set_failed(failure_reason) ⇒ Object
Like #drop!, but does not persist the pipeline nor trigger any state machine callbacks.
843
844
845
846
|
# File 'app/models/ci/pipeline.rb', line 843
def set_failed(failure_reason)
self.failure_reason = failure_reason.to_s
self.status = 'failed'
end
|
#set_status(new_status) ⇒ Object
rubocop: disable Metrics/CyclomaticComplexity – breaking apart hurts readability
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
|
# File 'app/models/ci/pipeline.rb', line 885
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 'waiting_for_callback' then wait_for_callback
when 'pending' then enqueue
when 'running' then run
when 'success' then succeed
when 'failed' then drop
when 'canceling' then start_cancel
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
|
#source_ref ⇒ Object
1300
1301
1302
1303
1304
1305
1306
|
# File 'app/models/ci/pipeline.rb', line 1300
def source_ref
if merge_request?
merge_request.source_branch
else
ref
end
end
|
#source_ref_path ⇒ Object
#source_ref_slug ⇒ Object
1308
1309
1310
|
# File 'app/models/ci/pipeline.rb', line 1308
def source_ref_slug
Gitlab::Utils.slugify(source_ref.to_s)
end
|
#stage(name) ⇒ Object
1312
1313
1314
|
# File 'app/models/ci/pipeline.rb', line 1312
def stage(name)
stages.find_by(name: name)
end
|
#stages_count ⇒ Object
610
611
612
|
# File 'app/models/ci/pipeline.rb', line 610
def stages_count
statuses.select(:stage).distinct.count
end
|
#stages_names ⇒ Object
626
627
628
629
|
# File 'app/models/ci/pipeline.rb', line 626
def stages_names
statuses.order(:stage_idx).distinct
.pluck(:stage, :stage_idx).map(&:first)
end
|
#stuck? ⇒ Boolean
709
710
711
|
# File 'app/models/ci/pipeline.rb', line 709
def stuck?
pending_builds.any?(&:stuck?)
end
|
618
619
620
|
# File 'app/models/ci/pipeline.rb', line 618
def tags_count
Ci::Tagging.where(taggable: builds).count
end
|
1203
1204
1205
1206
1207
1208
1209
|
# File 'app/models/ci/pipeline.rb', line 1203
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_summary ⇒ Object
1173
1174
1175
1176
1177
|
# File 'app/models/ci/pipeline.rb', line 1173
def test_report_summary
strong_memoize(:test_report_summary) do
Gitlab::Ci::Reports::TestReportSummary.new(latest_builds_report_results)
end
end
|
#test_reports ⇒ Object
1179
1180
1181
1182
1183
1184
1185
|
# File 'app/models/ci/pipeline.rb', line 1179
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_paths ⇒ Object
1258
1259
1260
1261
1262
|
# File 'app/models/ci/pipeline.rb', line 1258
def top_level_worktree_paths
strong_memoize(:top_level_worktree_paths) do
project.repository.tree(sha).blobs.map(&:path)
end
end
|
#total_size ⇒ Object
614
615
616
|
# File 'app/models/ci/pipeline.rb', line 614
def total_size
statuses.count(:id)
end
|
#triggered_by?(current_user) ⇒ Boolean
1296
1297
1298
|
# File 'app/models/ci/pipeline.rb', line 1296
def triggered_by?(current_user)
user == current_user
end
|
#triggered_pipelines_with_preloads ⇒ Object
637
638
639
|
# File 'app/models/ci/pipeline.rb', line 637
def triggered_pipelines_with_preloads
triggered_pipelines.preload(:source_job)
end
|
#update_builds_coverage ⇒ Object
758
759
760
|
# File 'app/models/ci/pipeline.rb', line 758
def update_builds_coverage
builds.with_coverage_regex.without_coverage.each(&:update_coverage)
end
|
#update_duration ⇒ Object
#upstream_and_all_downstreams ⇒ Object
With multi-project and parent-child pipelines
1036
1037
1038
|
# File 'app/models/ci/pipeline.rb', line 1036
def upstream_and_all_downstreams
object_hierarchy.all_objects
end
|
#upstream_root ⇒ Object
Follow the upstream pipeline relationships, regardless of multi-project or parent-child, and return the top-level ancestor.
1070
1071
1072
|
# File 'app/models/ci/pipeline.rb', line 1070
def upstream_root
@upstream_root ||= object_hierarchy.base_and_ancestors(hierarchy_order: :desc).first
end
|
#uses_needs? ⇒ Boolean
606
607
608
|
# File 'app/models/ci/pipeline.rb', line 606
def uses_needs?
processables.where(scheduling_type: :dag).any?
end
|
#valid_commit_sha ⇒ Object
641
642
643
|
# File 'app/models/ci/pipeline.rb', line 641
def valid_commit_sha
self.errors.add(:sha, "can't be 00000000 (branch removal)") if Gitlab::Git.blank_ref?(self.sha)
end
|
#variables_builder ⇒ Object
916
917
918
|
# File 'app/models/ci/pipeline.rb', line 916
def variables_builder
@variables_builder ||= ::Gitlab::Ci::Variables::Builder.new(self)
end
|
#warning_messages(limit: nil) ⇒ Object
855
856
857
858
859
|
# File 'app/models/ci/pipeline.rb', line 855
def warning_messages(limit: nil)
messages.select(&:warning?).tap do |warnings|
break warnings.take(limit) if limit
end
end
|