diff options
Diffstat (limited to 'app/models/ci/pipeline.rb')
-rw-r--r-- | app/models/ci/pipeline.rb | 130 |
1 files changed, 117 insertions, 13 deletions
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 8707d635e03..5e5f51d776f 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -7,6 +7,7 @@ module Ci include Importable include AfterCommitQueue include Presentable + include Gitlab::Allowable include Gitlab::OptimisticLocking include Gitlab::Utils::StrongMemoize include AtomicInternalId @@ -16,6 +17,8 @@ module Ci include FromUnion include UpdatedAtFilterable + MAX_OPEN_MERGE_REQUESTS_REFS = 4 + PROJECT_ROUTE_AND_NAMESPACE_ROUTE = { project: [:project_feature, :route, { namespace: :route }] }.freeze @@ -104,7 +107,6 @@ module Ci accepts_nested_attributes_for :variables, reject_if: :persisted? - delegate :id, to: :project, prefix: true delegate :full_path, to: :project, prefix: true validates :sha, presence: { unless: :importing? } @@ -259,6 +261,22 @@ module Ci end end + after_transition any => any do |pipeline| + next unless Feature.enabled?(:jira_sync_builds, pipeline.project) + + pipeline.run_after_commit do + # Passing the seq-id ensures this is idempotent + seq_id = ::Atlassian::JiraConnect::Client.generate_update_sequence_id + ::JiraConnect::SyncBuildsWorker.perform_async(pipeline.id, seq_id) + end + end + + after_transition any => ::Ci::Pipeline.completed_statuses do |pipeline| + pipeline.run_after_commit do + ::Ci::TestFailureHistoryService.new(pipeline).async.perform_if_needed # rubocop: disable CodeReuse/ServiceClass + end + end + after_transition any => [:success, :failed] do |pipeline| ref_status = pipeline.ci_ref&.update_status_by!(pipeline) @@ -277,15 +295,17 @@ module Ci scope :internal, -> { where(source: internal_sources) } scope :no_child, -> { where.not(source: :parent_pipeline) } scope :ci_sources, -> { where(source: Enums::Ci::Pipeline.ci_sources.values) } + scope :ci_branch_sources, -> { where(source: Enums::Ci::Pipeline.ci_branch_sources.values) } scope :ci_and_parent_sources, -> { where(source: Enums::Ci::Pipeline.ci_and_parent_sources.values) } scope :for_user, -> (user) { where(user: user) } scope :for_sha, -> (sha) { where(sha: sha) } scope :for_source_sha, -> (source_sha) { where(source_sha: source_sha) } scope :for_sha_or_source_sha, -> (sha) { for_sha(sha).or(for_source_sha(sha)) } scope :for_ref, -> (ref) { where(ref: ref) } + scope :for_branch, -> (branch) { for_ref(branch).where(tag: false) } scope :for_id, -> (id) { where(id: id) } scope :for_iid, -> (iid) { where(iid: iid) } - scope :for_project, -> (project) { where(project: project) } + scope :for_project, -> (project_id) { where(project_id: project_id) } 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) } @@ -310,9 +330,9 @@ module Ci # 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]) + 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 @@ -774,9 +794,20 @@ module Ci variables.append(key: 'CI_MERGE_REQUEST_EVENT_TYPE', value: merge_request_event_type.to_s) variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', value: source_sha.to_s) variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA', value: target_sha.to_s) + + diff = self.merge_request_diff + if diff.present? + variables.append(key: 'CI_MERGE_REQUEST_DIFF_ID', value: diff.id.to_s) + variables.append(key: 'CI_MERGE_REQUEST_DIFF_BASE_SHA', value: diff.base_commit_sha) + end + variables.concat(merge_request.predefined_variables) end + if Gitlab::Ci::Features.pipeline_open_merge_requests?(project) && open_merge_requests_refs.any? + variables.append(key: 'CI_OPEN_MERGE_REQUESTS', value: open_merge_requests_refs.join(',')) + end + variables.append(key: 'CI_KUBERNETES_ACTIVE', value: 'true') if has_kubernetes_active? variables.append(key: 'CI_DEPLOY_FREEZE', value: 'true') if freeze_period? @@ -824,9 +855,8 @@ module Ci end def execute_hooks - data = pipeline_data - project.execute_hooks(data, :pipeline_hooks) - project.execute_services(data, :pipeline_hooks) + project.execute_hooks(pipeline_data, :pipeline_hooks) if project.has_active_hooks?(:pipeline_hooks) + project.execute_services(pipeline_data, :pipeline_hooks) if project.has_active_services?(:pipeline_hooks) end # All the merge requests for which the current pipeline runs/ran against @@ -844,9 +874,39 @@ module Ci all_merge_requests.order(id: :desc) end + # This returns a list of MRs that point + # to the same source project/branch + 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 + + # We cannot use `all_merge_requests`, due to race condition + # This returns a list of at most 4 open MRs + 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 + def same_family_pipeline_ids ::Gitlab::Ci::PipelineObjectHierarchy.new( - base_and_ancestors(same_project: true), options: { same_project: true } + self.class.where(id: root_ancestor), options: { same_project: true } ).base_and_descendants.select(:id) end @@ -869,6 +929,15 @@ module Ci .base_and_descendants end + def root_ancestor + return self unless child? + + Gitlab::Ci::PipelineObjectHierarchy + .new(self.class.unscoped.where(id: id), options: { same_project: true }) + .base_and_ancestors(hierarchy_order: :desc) + .first + end + def bridge_triggered? source_bridge.present? end @@ -878,7 +947,8 @@ module Ci end def child? - parent_pipeline.present? + parent_pipeline? && # child pipelines have `parent_pipeline` source + parent_pipeline.present? end def parent? @@ -910,10 +980,18 @@ module Ci builds.latest.with_reports(reports_scope) end + def latest_test_report_builds + latest_report_builds(Ci::JobArtifact.test_reports).preload(:project) + end + def builds_with_coverage builds.latest.with_coverage end + def builds_with_failed_tests(limit: nil) + latest_test_report_builds.failed.limit(limit) + end + def has_reports?(reports_scope) complete? && latest_report_builds(reports_scope).exists? end @@ -934,7 +1012,7 @@ module Ci 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| + latest_test_report_builds.find_each do |build| build.collect_test_reports!(test_reports) end end @@ -950,12 +1028,20 @@ module Ci def coverage_reports Gitlab::Ci::Reports::CoverageReports.new.tap do |coverage_reports| - latest_report_builds(Ci::JobArtifact.coverage_reports).each do |build| + latest_report_builds(Ci::JobArtifact.coverage_reports).includes(:project).find_each do |build| build.collect_coverage_reports!(coverage_reports) end end end + def codequality_reports + Gitlab::Ci::Reports::CodequalityReports.new.tap do |codequality_reports| + latest_report_builds(Ci::JobArtifact.codequality_reports).each do |build| + build.collect_codequality_reports!(codequality_reports) + end + end + end + def terraform_reports ::Gitlab::Ci::Reports::TerraformReports.new.tap do |terraform_reports| latest_report_builds(::Ci::JobArtifact.terraform_reports).each do |build| @@ -1128,7 +1214,25 @@ module Ci end def pipeline_data - Gitlab::DataBuilder::Pipeline.build(self) + strong_memoize(:pipeline_data) do + Gitlab::DataBuilder::Pipeline.build(self) + end + end + + def merge_request_diff_sha + return unless merge_request? + + if merge_request_pipeline? + source_sha + else + sha + end + end + + def merge_request_diff + return unless merge_request? + + merge_request.merge_request_diff_for(merge_request_diff_sha) end def push_details |