Class: Ci::Pipeline

Constant Summary collapse

PROJECT_ROUTE_AND_NAMESPACE_ROUTE =
{
  project: [:project_feature, :route, { namespace: :route }]
}.freeze
CONFIG_EXTENSION =
'.gitlab-ci.yml'
DEFAULT_CONFIG_PATH =
CONFIG_EXTENSION
BridgeStatusError =
Class.new(StandardError)

Constants included from HasStatus

HasStatus::ACTIVE_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::UnknownStatusError

Instance Attribute Summary

Attributes included from Importable

#imported, #importing

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Gitlab::Ci::Model

model_name, table_name_prefix

Methods included from HasRef

#branch?, #ref_slug

Methods included from AtomicInternalId

#internal_id_read_scope, #internal_id_scope_attrs, #internal_id_scope_usage

Methods included from Gitlab::Utils::StrongMemoize

#clear_memoization, #strong_memoize, #strong_memoized?

Methods included from Gitlab::OptimisticLocking

retry_lock

Methods included from Presentable

#present

Methods included from AfterCommitQueue

#run_after_commit, #run_after_commit_or_now

Methods included from HasStatus

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

Methods inherited from ApplicationRecord

at_most, id_in, id_not_in, iid_in, pluck_primary_key, primary_key_in, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, underscore, without_order

Class Method Details

.auto_devops_pipelines_completed_totalObject


401
402
403
# File 'app/models/ci/pipeline.rb', line 401

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


397
398
399
# File 'app/models/ci/pipeline.rb', line 397

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

.internal_sourcesObject


393
394
395
# File 'app/models/ci/pipeline.rb', line 393

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

.last_finished_for_ref_id(ci_ref_id) ⇒ Object


381
382
383
# File 'app/models/ci/pipeline.rb', line 381

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

352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
# File 'app/models/ci/pipeline.rb', line 352

def self.latest_pipeline_per_commit(commits, ref = nil)
  p1 = arel_table
  p2 = arel_table.alias

  # This LEFT JOIN will filter out all but the newest row for every
  # combination of (project_id, sha) or (project_id, sha, ref) if a ref is
  # given.
  cond = p1[:sha].eq(p2[:sha])
    .and(p1[:project_id].eq(p2[:project_id]))
    .and(p1[:id].lt(p2[:id]))

  cond = cond.and(p1[:ref].eq(p2[:ref])) if ref
  join = p1.join(p2, Arel::Nodes::OuterJoin).on(cond)

  relation = where(sha: commits)
    .where(p2[:id].eq(nil))
    .joins(join.join_sources)

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

  relation.each_with_object({}) do |pipeline, hash|
    hash[pipeline.sha] = pipeline
  end
end

.latest_status(ref = nil) ⇒ Object


323
324
325
# File 'app/models/ci/pipeline.rb', line 323

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

.latest_successful_for_ref(ref) ⇒ Object


327
328
329
# File 'app/models/ci/pipeline.rb', line 327

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

.latest_successful_for_refs(refs) ⇒ Object


335
336
337
338
339
340
341
# File 'app/models/ci/pipeline.rb', line 335

def self.latest_successful_for_refs(refs)
  relation = newest_first(ref: refs).success

  relation.each_with_object({}) do |pipeline, hash|
    hash[pipeline.ref] ||= pipeline
  end
end

.latest_successful_for_sha(sha) ⇒ Object


331
332
333
# File 'app/models/ci/pipeline.rb', line 331

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

.latest_successful_ids_per_projectObject


377
378
379
# File 'app/models/ci/pipeline.rb', line 377

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 mutliple SHAs) to limit the list of pipelines to. limit - This limits a backlog search, default to 100.


310
311
312
313
314
315
316
317
318
319
320
321
# File 'app/models/ci/pipeline.rb', line 310

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


389
390
391
# File 'app/models/ci/pipeline.rb', line 389

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

.truncate_sha(sha) ⇒ Object


385
386
387
# File 'app/models/ci/pipeline.rb', line 385

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

Instance Method Details

#accessibility_reportsObject


906
907
908
909
910
911
912
# File 'app/models/ci/pipeline.rb', line 906

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

#add_error_message(content) ⇒ Object


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

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

#add_warning_message(content) ⇒ Object


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

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


810
811
812
813
814
815
816
817
818
# File 'app/models/ci/pipeline.rb', line 810

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


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

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

#all_worktree_pathsObject


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

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

#auto_cancel_running(pipeline, retries: nil) ⇒ Object


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

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

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

#auto_canceled?Boolean

Returns:

  • (Boolean)

528
529
530
# File 'app/models/ci/pipeline.rb', line 528

def auto_canceled?
  canceled? && auto_canceled_by_id?
end

#base_and_ancestorsObject


1065
1066
1067
1068
1069
1070
1071
# File 'app/models/ci/pipeline.rb', line 1065

def base_and_ancestors
  # Without using `unscoped`, caller scope is also included into the query.
  # Using `unscoped` here will be redundant after Rails 6.1
  ::Gitlab::Ci::PipelineObjectHierarchy
    .new(self.class.unscoped.where(id: id))
    .base_and_ancestors
end

#batch_lookup_report_artifact_for_file_type(file_type) ⇒ Object


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

def batch_lookup_report_artifact_for_file_type(file_type)
  latest_report_artifacts
    .values_at(*::Ci::JobArtifact.associated_file_types_for(file_type.to_s))
    .flatten
    .compact
    .last
end

#before_shaObject


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

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

#branch_updated?Boolean

Returns:

  • (Boolean)

938
939
940
941
942
# File 'app/models/ci/pipeline.rb', line 938

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

#bridge_triggered?Boolean

Returns:

  • (Boolean)

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

def bridge_triggered?
  source_bridge.present?
end

#bridge_waiting?Boolean

Returns:

  • (Boolean)

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

def bridge_waiting?
  source_bridge&.dependent?
end

#builds_with_coverageObject


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

def builds_with_coverage
  builds.with_coverage
end

#cacheable?Boolean

Returns:

  • (Boolean)

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

def cacheable?
  !dangling?
end

#can_generate_coverage_reports?Boolean

Returns:

  • (Boolean)

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

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

#cancel_running(retries: nil) ⇒ Object


532
533
534
535
536
537
538
539
# File 'app/models/ci/pipeline.rb', line 532

def cancel_running(retries: nil)
  retry_optimistic_lock(cancelable_statuses, retries) do |cancelable|
    cancelable.find_each do |job|
      yield(job) if block_given?
      job.cancel
    end
  end
end

#cancelable?Boolean

Returns:

  • (Boolean)

524
525
526
# File 'app/models/ci/pipeline.rb', line 524

def cancelable?
  cancelable_statuses.any?
end

#child?Boolean

Returns:

  • (Boolean)

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

def child?
  parent_pipeline.present?
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?`


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

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

#config_pathObject

TODO: this logic is duplicate with Pipeline::Chain::Config::Content we should persist this is `ci_pipelines.config_path`


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

def config_path
  return unless repository_source? || unknown_source?

  project.ci_config_path_or_default
end

#coverageObject


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

def coverage
  coverage_array = statuses.latest.map(&:coverage).compact
  if coverage_array.size >= 1
    '%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
  end
end

#coverage_reportsObject


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

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

#created_successfully?Boolean

Returns:

  • (Boolean)

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

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

#dangling?Boolean

Returns:

  • (Boolean)

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

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

#default_branch?Boolean

Returns:

  • (Boolean)

971
972
973
# File 'app/models/ci/pipeline.rb', line 971

def default_branch?
  ref == project.default_branch
end

#detached_merge_request_pipeline?Boolean

Returns:

  • (Boolean)

979
980
981
# File 'app/models/ci/pipeline.rb', line 979

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

#detailed_status(current_user) ⇒ Object


855
856
857
858
859
# File 'app/models/ci/pipeline.rb', line 855

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

#ensure_ci_ref!Object


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

def ensure_ci_ref!
  self.ci_ref = Ci::Ref.ensure_for(self)
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).


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

def ensure_scheduling_type!
  processables.populate_scheduling_type!
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.


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

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

#execute_hooksObject


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

def execute_hooks
  data = pipeline_data
  project.execute_hooks(data, :pipeline_hooks)
  project.execute_services(data, :pipeline_hooks)
end

#find_job_with_archive_artifacts(name) ⇒ Object


861
862
863
# File 'app/models/ci/pipeline.rb', line 861

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

#find_stage_by_name!(name) ⇒ Object


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

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

#freeze_period?Boolean

Returns:

  • (Boolean)

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

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

#full_error_messagesObject


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

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

#git_author_emailObject


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

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

#git_author_nameObject


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

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

#git_commit_descriptionObject


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

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

#git_commit_full_titleObject


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

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

#git_commit_messageObject


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

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

#git_commit_timestampObject


494
495
496
497
498
# File 'app/models/ci/pipeline.rb', line 494

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

#git_commit_titleObject


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

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

#has_archive_artifacts?Boolean

Returns:

  • (Boolean)

930
931
932
# File 'app/models/ci/pipeline.rb', line 930

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

#has_coverage_reports?Boolean

Returns:

  • (Boolean)

884
885
886
# File 'app/models/ci/pipeline.rb', line 884

def has_coverage_reports?
  pipeline_artifacts&.has_code_coverage?
end

#has_exposed_artifacts?Boolean

Returns:

  • (Boolean)

934
935
936
# File 'app/models/ci/pipeline.rb', line 934

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

#has_kubernetes_active?Boolean

Returns:

  • (Boolean)

623
624
625
# File 'app/models/ci/pipeline.rb', line 623

def has_kubernetes_active?
  project.deployment_platform&.active?
end

#has_reports?(reports_scope) ⇒ Boolean

Returns:

  • (Boolean)

880
881
882
# File 'app/models/ci/pipeline.rb', line 880

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

#has_warnings?Boolean

Returns:

  • (Boolean)

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

def has_warnings?
  number_of_warnings > 0
end

#has_yaml_errors?Boolean

Returns:

  • (Boolean)

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

def has_yaml_errors?
  yaml_errors.present?
end

#latest?Boolean

Returns:

  • (Boolean)

568
569
570
571
572
573
574
575
576
577
578
# File 'app/models/ci/pipeline.rb', line 568

def latest?
  return false unless git_ref && commit.present?

  unless ::Gitlab::Ci::Features.pipeline_latest?
    return project.commit(git_ref) == commit
  end

  return false if lazy_ref_commit.nil?

  lazy_ref_commit.id == commit.id
end

#latest_builds_with_artifactsObject


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

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


605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
# File 'app/models/ci/pipeline.rb', line 605

def latest_report_artifacts
  ::Gitlab::SafeRequestStore.fetch("pipeline:#{self.id}:latest_report_artifacts") do
    # Note we use read_attribute(:project_id) to read the project
    # ID instead of self.project_id. The latter appears to load
    # the Project model. This extra filter doesn't appear to
    # affect query plan but included to ensure we don't leak the
    # wrong informaiton.
    ::Ci::JobArtifact.where(
      id: job_artifacts.with_reports
        .select('max(ci_job_artifacts.id) as id')
        .where(project_id: self.read_attribute(:project_id))
        .group(:file_type)
    )
      .preload(:job)
      .group_by(&:file_type)
  end
end

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


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

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

#lazy_ref_commitObject

rubocop: enable CodeReuse/ServiceClass


556
557
558
559
560
561
562
563
564
565
566
# File 'app/models/ci/pipeline.rb', line 556

def lazy_ref_commit
  return unless ::Gitlab::Ci::Features.pipeline_latest?

  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)

983
984
985
# File 'app/models/ci/pipeline.rb', line 983

def legacy_detached_merge_request_pipeline?
  detached_merge_request_pipeline? && !merge_request_ref?
end

#legacy_stage(name) ⇒ Object


418
419
420
421
# File 'app/models/ci/pipeline.rb', line 418

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


448
449
450
# File 'app/models/ci/pipeline.rb', line 448

def legacy_stages
  legacy_stages_using_composite_status
end

#legacy_stages_using_composite_statusObject


429
430
431
432
433
434
435
436
437
438
439
440
441
# File 'app/models/ci/pipeline.rb', line 429

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


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

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

#matches_sha_or_source_sha?(sha) ⇒ Boolean

Returns:

  • (Boolean)

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

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

#merge_request?Boolean

Returns:

  • (Boolean)

975
976
977
# File 'app/models/ci/pipeline.rb', line 975

def merge_request?
  merge_request_id.present?
end

#merge_request_event_typeObject


1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
# File 'app/models/ci/pipeline.rb', line 1023

def merge_request_event_type
  return unless merge_request?

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

#merge_request_pipeline?Boolean

Returns:

  • (Boolean)

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

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

#merge_request_ref?Boolean

Returns:

  • (Boolean)

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

def merge_request_ref?
  MergeRequest.merge_request_ref?(ref)
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


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

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

#needs_processing?Boolean

Returns:

  • (Boolean)

646
647
648
649
650
651
# File 'app/models/ci/pipeline.rb', line 646

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

#notesObject


699
700
701
# File 'app/models/ci/pipeline.rb', line 699

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.


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

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


635
636
637
638
639
640
641
642
643
644
# File 'app/models/ci/pipeline.rb', line 635

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

#parent?Boolean

Returns:

  • (Boolean)

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

def parent?
  child_pipelines.exists?
end

#persisted_variablesObject


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

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


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

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

#predefined_commit_variablesObject


766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
# File 'app/models/ci/pipeline.rb', line 766

def predefined_commit_variables
  Gitlab::Ci::Variables::Collection.new.tap do |variables|
    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)

    # 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

#predefined_variablesObject


741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
# File 'app/models/ci/pipeline.rb', line 741

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_CONFIG_PATH', value: config_path)

    variables.concat(predefined_commit_variables)

    if 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)
      variables.concat(merge_request.predefined_variables)
    end

    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)

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

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

#queued_durationObject


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

def queued_duration
  return unless started_at

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

#ref_exists?Boolean

Returns:

  • (Boolean)

423
424
425
426
427
# File 'app/models/ci/pipeline.rb', line 423

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

#retriedObject


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

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

#retry_failed(current_user) ⇒ Object

rubocop: disable CodeReuse/ServiceClass


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

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

#retryable?Boolean

Returns:

  • (Boolean)

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

def retryable?
  retryable_builds.any?
end

#same_family_pipeline_idsObject


824
825
826
827
828
829
830
831
832
833
# File 'app/models/ci/pipeline.rb', line 824

def same_family_pipeline_ids
  if ::Gitlab::Ci::Features.child_of_child_pipeline_enabled?(project)
    ::Gitlab::Ci::PipelineObjectHierarchy.new(base_and_ancestors).base_and_descendants.select(:id)
  else
    # If pipeline is a child of another pipeline, include the parent
    # and the siblings, otherwise return only itself and children.
    parent = parent_pipeline || self
    [parent.id] + parent.child_pipelines.pluck(:id)
  end
end

#set_status(new_status) ⇒ Object


703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
# File 'app/models/ci/pipeline.rb', line 703

def set_status(new_status)
  retry_optimistic_lock(self) 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


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

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

#source_refObject


1003
1004
1005
1006
1007
1008
1009
# File 'app/models/ci/pipeline.rb', line 1003

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

#source_ref_pathObject


1047
1048
1049
1050
1051
1052
1053
# File 'app/models/ci/pipeline.rb', line 1047

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


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

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

#stages_countObject


405
406
407
# File 'app/models/ci/pipeline.rb', line 405

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

#stages_namesObject


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

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

#stuck?Boolean

Returns:

  • (Boolean)

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

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

#terraform_reportsObject


922
923
924
925
926
927
928
# File 'app/models/ci/pipeline.rb', line 922

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


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

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

#test_reportsObject


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

def test_reports
  Gitlab::Ci::Reports::TestReports.new.tap do |test_reports|
    latest_report_builds(Ci::JobArtifact.test_reports).preload(:project).find_each do |build|
      build.collect_test_reports!(test_reports)
    end
  end
end

#top_level_worktree_pathsObject


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

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

#total_sizeObject


409
410
411
# File 'app/models/ci/pipeline.rb', line 409

def total_size
  statuses.count(:id)
end

#triggered_by?(current_user) ⇒ Boolean

Returns:

  • (Boolean)

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

def triggered_by?(current_user)
  user == current_user
end

#triggered_pipelines_with_preloadsObject


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

def triggered_pipelines_with_preloads
  triggered_pipelines.preload(:source_job)
end

#update_durationObject


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

def update_duration
  return unless started_at

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

#valid_commit_shaObject


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

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

#warning_messages(limit: nil) ⇒ Object


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

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