diff options
Diffstat (limited to 'app/models/ci/pipeline.rb')
-rw-r--r-- | app/models/ci/pipeline.rb | 144 |
1 files changed, 132 insertions, 12 deletions
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 497e1a4d74a..d4b439d648f 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -3,7 +3,7 @@ module Ci class Pipeline < ApplicationRecord extend Gitlab::Ci::Model - include HasStatus + include Ci::HasStatus include Importable include AfterCommitQueue include Presentable @@ -51,6 +51,8 @@ module Ci has_many :latest_builds, -> { latest }, foreign_key: :commit_id, inverse_of: :pipeline, class_name: 'Ci::Build' has_many :downloadable_artifacts, -> { not_expired.downloadable }, through: :latest_builds, source: :job_artifacts + has_many :messages, class_name: 'Ci::PipelineMessage', inverse_of: :pipeline + # Merge requests for which the current pipeline is running against # the merge request's latest commit. has_many :merge_requests_as_head_pipeline, foreign_key: "head_pipeline_id", class_name: 'MergeRequest' @@ -80,6 +82,7 @@ module Ci has_one :pipeline_config, class_name: 'Ci::PipelineConfig', inverse_of: :pipeline has_many :daily_build_group_report_results, class_name: 'Ci::DailyBuildGroupReportResult', foreign_key: :last_pipeline_id + has_many :latest_builds_report_results, through: :latest_builds, source: :report_results accepts_nested_attributes_for :variables, reject_if: :persisted? @@ -110,6 +113,8 @@ module Ci # extend this `Hash` with new values. enum failure_reason: ::Ci::PipelineEnums.failure_reasons + enum locked: { unlocked: 0, artifacts_locked: 1 } + state_machine :status, initial: :created do event :enqueue do transition [:created, :manual, :waiting_for_resource, :preparing, :skipped, :scheduled] => :pending @@ -244,6 +249,14 @@ module Ci pipeline.run_after_commit { AutoDevops::DisableWorker.perform_async(pipeline.id) } end + + after_transition any => [:success] do |pipeline| + next unless Gitlab::Ci::Features.keep_latest_artifacts_for_ref_enabled?(pipeline.project) + + pipeline.run_after_commit do + Ci::PipelineSuccessUnlockArtifactsWorker.perform_async(pipeline.id) + end + end end scope :internal, -> { where(source: internal_sources) } @@ -256,7 +269,14 @@ module Ci scope :for_ref, -> (ref) { where(ref: ref) } scope :for_id, -> (id) { where(id: id) } scope :for_iid, -> (iid) { where(iid: iid) } + scope :for_project, -> (project) { where(project: project) } scope :created_after, -> (time) { where('ci_pipelines.created_at > ?', time) } + scope :created_before_id, -> (id) { where('ci_pipelines.id < ?', id) } + scope :before_pipeline, -> (pipeline) { created_before_id(pipeline.id).outside_pipeline_family(pipeline) } + + scope :outside_pipeline_family, ->(pipeline) do + where.not(id: pipeline.same_family_pipeline_ids) + end scope :with_reports, -> (reports_scope) do where('EXISTS (?)', ::Ci::Build.latest.with_reports(reports_scope).where('ci_pipelines.id=ci_builds.commit_id').select(1)) @@ -270,6 +290,15 @@ module Ci ) end + # Returns the pipelines that associated with the given merge request. + # In general, please use `Ci::PipelinesForMergeRequestFinder` instead, + # for checking permission of the actor. + scope :triggered_by_merge_request, -> (merge_request) do + ci_sources.where(source: :merge_request_event, + merge_request: merge_request, + project: [merge_request.source_project, merge_request.target_project]) + end + # Returns the pipelines in descending order (= newest first), optionally # limited to a number of references. # @@ -348,6 +377,10 @@ module Ci success.group(:project_id).select('max(id) as id') end + 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 + def self.truncate_sha(sha) sha[0...8] end @@ -440,6 +473,10 @@ module Ci end end + def triggered_pipelines_with_preloads + triggered_pipelines.preload(:source_job) + end + def legacy_stages if ::Gitlab::Ci::Features.composite_status?(project) legacy_stages_using_composite_status @@ -552,10 +589,28 @@ module Ci end end + 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 + def latest? return false unless git_ref && commit.present? - project.commit(git_ref) == commit + 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 def retried @@ -569,10 +624,46 @@ module Ci end end + 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 + + # 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 + 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 + def has_kubernetes_active? project.deployment_platform&.active? end + def freeze_period? + Ci::FreezePeriodStatus.new(project: project).execute + end + def has_warnings? number_of_warnings.positive? end @@ -607,6 +698,25 @@ module Ci yaml_errors.present? end + def add_error_message(content) + add_message(:error, content) + end + + def add_warning_message(content) + add_message(:warning, content) + end + + # 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. + def error_messages + messages.select(&:error?) + end + + def warning_messages + messages.select(&:warning?) + end + # 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 @@ -639,7 +749,7 @@ module Ci when 'manual' then block when 'scheduled' then delay else - raise HasStatus::UnknownStatusError, + raise Ci::HasStatus::UnknownStatusError, "Unknown status `#{new_status}`" end end @@ -683,6 +793,7 @@ module Ci 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) @@ -748,13 +859,10 @@ module Ci end # If pipeline is a child of another pipeline, include the parent - # and the siblings, otherwise return only itself. + # and the siblings, otherwise return only itself and children. def same_family_pipeline_ids - if (parent = parent_pipeline) - [parent.id] + parent.child_pipelines.pluck(:id) - else - [self.id] - end + parent = parent_pipeline || self + [parent.id] + parent.child_pipelines.pluck(:id) end def bridge_triggered? @@ -802,6 +910,10 @@ module Ci complete? && latest_report_builds(reports_scope).exists? end + def test_report_summary + Gitlab::Ci::Reports::TestReportSummary.new(latest_builds_report_results) + end + 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| @@ -840,6 +952,10 @@ module Ci end end + def has_archive_artifacts? + complete? && builds.latest.with_existing_job_artifacts(Ci::JobArtifact.archive.or(Ci::JobArtifact.metadata)).exists? + end + def has_exposed_artifacts? complete? && builds.latest.with_exposed_artifacts.exists? end @@ -925,7 +1041,7 @@ module Ci stages.find_by!(name: name) end - def error_messages + def full_error_messages errors ? errors.full_messages.to_sentence : "" end @@ -964,8 +1080,6 @@ module Ci # Set scheduling type of processables if they were created before scheduling_type # data was deployed (https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22246). def ensure_scheduling_type! - return unless ::Gitlab::Ci::Features.ensure_scheduling_type_enabled? - processables.populate_scheduling_type! end @@ -977,6 +1091,12 @@ module Ci private + def add_message(severity, content) + return unless Gitlab::Ci::Features.store_pipeline_messages?(project) + + messages.build(severity: severity, content: content) + end + def pipeline_data Gitlab::DataBuilder::Pipeline.build(self) end |