diff --git a/app/models/ci/pipeline_message.rb b/app/models/ci/pipeline_message.rb index 21f05258040be3c7900c84eb8dc453284965c503..273aa17a6ce6e0e51fbe0a1a7ef968741da44eb3 100644 --- a/app/models/ci/pipeline_message.rb +++ b/app/models/ci/pipeline_message.rb @@ -26,3 +26,5 @@ def truncate_long_content end end end + +Ci::PipelineMessage.prepend_mod_with('Ci::PipelineMessage') diff --git a/ee/app/models/ee/ci/pipeline.rb b/ee/app/models/ee/ci/pipeline.rb index 48acf7741e4bac9a6cd6b5c31bb5efeab3473e68..05e01c859dd1b5f3c8817211a0f6ba6c052657e7 100644 --- a/ee/app/models/ee/ci/pipeline.rb +++ b/ee/app/models/ee/ci/pipeline.rb @@ -375,7 +375,7 @@ def sbom_report_ingestion_errors_redis_key def should_audit_security_policy_pipeline_failure?(pipeline) return false if ::Feature.disabled?(:collect_security_policy_failed_pipelines_audit_events, pipeline.project) - pipeline_created_by_security_policies?(pipeline.source) || pipeline_with_security_policy_jobs? + pipeline_created_by_security_policies?(pipeline.source) || pipeline_with_security_policy_jobs? || pipeline_with_security_policy_errors?(pipeline) end def pipeline_created_by_security_policies?(source) @@ -385,6 +385,10 @@ def pipeline_created_by_security_policies?(source) def pipeline_with_security_policy_jobs? builds.joins(:build_source).where(p_ci_build_sources: { source: PIPELINE_EXECUTION_POLICIES_BUILD_SOURCES }).exists? end + + def pipeline_with_security_policy_errors?(pipeline) + pipeline.messages.pipeline_execution_policy_failure.exists? + end end end end diff --git a/ee/app/models/ee/ci/pipeline_message.rb b/ee/app/models/ee/ci/pipeline_message.rb new file mode 100644 index 0000000000000000000000000000000000000000..f2fab5584d15ccaed535a5e0eb983c04b84c6305 --- /dev/null +++ b/ee/app/models/ee/ci/pipeline_message.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module EE + module Ci + module PipelineMessage + extend ActiveSupport::Concern + + PIPELINE_EXECUTION_POLICY_ERROR = 'Pipeline execution policy error:' + + prepended do + scope :pipeline_execution_policy_failure, -> { + where("content LIKE ?", "#{sanitize_sql_like(PIPELINE_EXECUTION_POLICY_ERROR)}%").where(severity: :error) + } + end + end + end +end diff --git a/ee/spec/models/ci/pipeline_spec.rb b/ee/spec/models/ci/pipeline_spec.rb index 43844931e05c62af4456d51ef42fd79c4f85f150..e43a01075e88ccc206419c8badf2a7661202f8b7 100644 --- a/ee/spec/models/ci/pipeline_spec.rb +++ b/ee/spec/models/ci/pipeline_spec.rb @@ -778,10 +778,21 @@ context 'when the pipeline was created by a security policy' do let(:source) { :security_orchestration_policy } - it 'enqueues FailedPipelinesAuditWorker' do - expect(Security::Policies::FailedPipelinesAuditWorker).to receive(:perform_async).with(pipeline.id) + it_behaves_like 'enqueues FailedPipelinesAuditWorker' + end - transition_pipeline + context 'when the pipeline has errors messages' do + context 'when the errors are not related to security policies' do + let!(:error_message) { create(:ci_pipeline_message, pipeline: pipeline, content: 'error', severity: :error) } + + it_behaves_like 'does not enqueue FailedPipelinesAuditWorker' + end + + context 'when the errors are related to security policies' do + let(:security_policies_error_message) { 'Pipeline execution policy error: Cyclic dependencies detected when enforcing policies. Ensure stages across the project and policies are aligned.' } + let!(:error_message) { create(:ci_pipeline_message, pipeline: pipeline, content: security_policies_error_message, severity: :error) } + + it_behaves_like 'enqueues FailedPipelinesAuditWorker' end end end diff --git a/ee/spec/models/ee/ci/pipeline_message_spec.rb b/ee/spec/models/ee/ci/pipeline_message_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..dbf3ec099bf0527ae4f1f45eaae6ba1a6c00537f --- /dev/null +++ b/ee/spec/models/ee/ci/pipeline_message_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ci::PipelineMessage, feature_category: :continuous_integration do + let_it_be(:pipeline, refind: true) do + create(:ci_empty_pipeline) + end + + describe 'scopes' do + describe '.pipeline_execution_policy_failure' do + subject(:pipeline_execution_policy_failure) { described_class.pipeline_execution_policy_failure } + + let!(:warning_message) do + create(:ci_pipeline_message, pipeline: pipeline, content: 'Warning message', severity: :warning) + end + + let!(:non_pipeline_execution_policy_error_message) do + create(:ci_pipeline_message, pipeline: pipeline, content: 'Non Pipeline execution policy error', + severity: :error) + end + + context 'when content does not contains pipeline execution policy error' do + specify do + expect(pipeline_execution_policy_failure).to be_empty + end + end + + context 'when content contains pipeline execution policy error' do + let(:security_policies_error_message) do + 'Pipeline execution policy error: Cyclic dependencies detected when enforcing policies.' \ + 'Ensure stages across the project and policies are aligned.' + end + + let!(:pipeline_execution_policy_error_message) do + create(:ci_pipeline_message, pipeline: pipeline, content: security_policies_error_message, severity: :error) + end + + it 'returns the pipeline_execution_policy_error_message' do + expect(pipeline_execution_policy_failure).to contain_exactly(pipeline_execution_policy_error_message) + end + end + end + end +end