Add empty_pipeline_behavior option to Pipeline Execution Policies

What does this MR do and why?

Adds configurable empty_pipeline_behavior to control policy application to empty pipelines, addressing duplicate pipeline issues.

This feature introduces a new option for Pipeline Execution Policies that gives administrators fine-grained control over when policies should apply jobs to pipelines. Previously, policies would always apply to empty pipelines, which could cause duplicate pipelines in certain scenarios. Now, administrators can choose from three behaviors:

Options:

  • always_apply (default): Always applies policies to empty pipelines (maintains current behavior)
  • apply_if_no_config: Only applies when the project has no CI configuration file
  • never_apply: Never applies policies to empty pipelines

Behavior when multiple policies are configured: When multiple policies have different empty_pipeline_behavior settings, the most restrictive setting applies (most limiting to project freedom):

  • always_apply > apply_if_no_config > never_apply

Implementation details: The option is configured via pipeline_config_strategy, which now accepts either:

  • A string (for backward compatibility): "inject_ci", "inject_policy", or "override_project_ci"
  • An object with type and optional empty_pipeline_behavior fields

Feature flag

This feature is controlled by the pipeline_execution_policy_empty_pipeline_behavior feature flag to control the rollout on GitLab.com. Additionally, the empty_pipeline_behavior_option experiment must be enabled in the security policy configuration YAML file (see setup instructions below).

References

#582196

Screenshots or screen recordings

With apply_if_no_config

Pipeline type Before (duplicate fallback pipeline) After (no fallback pipeline)
Branch Screenshot_2025-12-06_at_1.19.39_AM Screenshot_2025-12-06_at_1.15.37_AM
MR Screenshot_2025-12-06_at_1.19.46_AM Screenshot_2025-12-06_at_1.15.15_AM

How to set up and validate locally

1. Enable the feature flag

Enable the pipeline_execution_policy_empty_pipeline_behavior feature flag:

# In Rails console
Feature.enable(:pipeline_execution_policy_empty_pipeline_behavior)

2. Enable the experiment in the policy configuration

In your security policy project's .gitlab/security-policies/policy.yml file, add the experiment configuration at the top level:

# .gitlab/security-policies/policy.yml
experiments:
  empty_pipeline_behavior_option:
    enabled: true

pipeline_execution_policy:
- name: Enforce SAST on created pipelines
  description: ''
  enabled: true
  pipeline_config_strategy: 
    type: inject_policy
    empty_pipeline_behavior: apply_if_no_config  # Options: always_apply, apply_if_no_config, never_apply
  content:
    include:
    - project: pep-sast/pep-sast-security-policy-project
      file: .gitlab/ci/sast.yml
  skip_ci:
    allowed: false
  variables_override:
    allowed: false
    exceptions: []

3. Configure the policy CI file

Create the CI configuration file referenced in your policy:

# .gitlab/ci/sast.yml
workflow:
  rules:
    - when: always

enforced-security-scan:
  stage: .pipeline-policy-pre
  script:
    - echo "Running enforced security scan from pipeline execution policy"
    - echo "This job cannot be skipped by developers"
    - echo "Checking for security vulnerabilities..."
    - echo "Security scan completed successfully"

enforced-test-job:
  stage: test
  script:
    - echo "Running enforced test job in test stage"
    - echo "Creating test stage if it doesn't exist"
    - echo "Performing mandatory testing requirements..."
    - echo "Enforced tests completed successfully"

enforced-compliance-check:
  stage: .pipeline-policy-post
  script:
    - echo "Running enforced compliance check"
    - echo "Verifying pipeline compliance requirements"
    - echo "Compliance check passed"

4. Set up the downstream project

# .gitlab-ci.yml

workflow:
  rules:
  - if: $CI_MERGE_REQUEST_IID
    when: never
  - when: always

job:
  script: exit 0

5. Test the different behaviors

Test always_apply:

  • Set empty_pipeline_behavior: always_apply in the policy
  • Create a pipeline in a project with no .gitlab-ci.yml
  • Expected: Policy jobs are injected

Test apply_if_no_config:

  • Set empty_pipeline_behavior: apply_if_no_config in the policy
  • Create a pipeline in a project with no .gitlab-ci.yml
  • Expected: Policy jobs are injected
  • Add a .gitlab-ci.yml to the project
  • Expected: Policy jobs only inject if the project's pipeline is created

Test never_apply:

  • Set empty_pipeline_behavior: never_apply in the policy
  • Create a pipeline in a project with no .gitlab-ci.yml
  • Expected: No pipeline is created (policy jobs are not injected)

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Hordur Freyr Yngvason

Merge request reports

Loading