From 543b4fe6e80ff0921d08a64f412308c128708a4d Mon Sep 17 00:00:00 2001
From: Pierre Smeyers <pierre.smeyers@gmail.com>
Date: Mon, 20 Jun 2022 23:48:55 +0200
Subject: [PATCH] feat: adaptive pipeline rules

BREAKING CHANGE: change default workflow from Branch pipeline to MR pipeline
---
 templates/gitlab-ci-python.yml | 73 +++++++++++++++++++---------------
 1 file changed, 40 insertions(+), 33 deletions(-)

diff --git a/templates/gitlab-ci-python.yml b/templates/gitlab-ci-python.yml
index b8e87fe..a4ae4d8 100644
--- a/templates/gitlab-ci-python.yml
+++ b/templates/gitlab-ci-python.yml
@@ -16,11 +16,30 @@
 # default workflow rules: Merge Request pipelines
 workflow:
   rules:
-    - if: '$CI_MERGE_REQUEST_ID'
+    # prevent branch pipeline when an MR is open (prefer MR pipeline)
     - if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS'
       when: never
     - when: always
 
+# test job prototype: implement adaptive pipeline rules
+.test-policy:
+  rules:
+    # on tag: auto & failing
+    - if: $CI_COMMIT_TAG
+    # on ADAPTIVE_PIPELINE_DISABLED: auto & failing
+    - if: '$ADAPTIVE_PIPELINE_DISABLED == "true"'
+    # on production or integration branch(es): auto & failing
+    - if: '$CI_COMMIT_REF_NAME =~ $PROD_REF || $CI_COMMIT_REF_NAME =~ $INTEG_REF'
+    # early stage (dev branch, no MR): manual & non-failing
+    - if: '$CI_MERGE_REQUEST_ID == null && $CI_OPEN_MERGE_REQUESTS == null'
+      when: manual
+      allow_failure: true
+    # Draft MR: auto & non-failing
+    - if: '$CI_MERGE_REQUEST_TITLE =~ /^Draft:.*/'
+      allow_failure: true
+    # else (Ready MR): auto & failing
+    - when: on_success
+
 variables:
   # variabilized tracking image
   TBC_TRACKING_IMAGE: "$CI_REGISTRY/to-be-continuous/tools/tracking:master"
@@ -617,12 +636,7 @@ py-lint:
     # exclude if $PYLINT_ENABLED not set
     - if: '$PYLINT_ENABLED != "true"'
       when: never
-    # on non-production, non-integration branches: manual & non-blocking
-    - if: '$CI_COMMIT_REF_NAME !~ $PROD_REF && $CI_COMMIT_REF_NAME !~ $INTEG_REF'
-      when: manual
-      allow_failure: true
-    # else: manual & non-blocking
-    - when: always
+    - !reference [.test-policy, rules]
 
 py-compile:
   extends: .python-base
@@ -631,8 +645,10 @@ py-compile:
     - install_requirements
     - _python -m compileall $PYTHON_COMPILE_ARGS
   rules:
-    # on any branch: only when none of supported unit test framework is enabled
-    - if: '$UNITTEST_ENABLED != "true" && $PYTEST_ENABLED != "true" && $NOSETESTS_ENABLED != "true"'
+    # skip when one of unit test framework is enabled
+    - if: '$UNITTEST_ENABLED == "true" || $PYTEST_ENABLED == "true" || $NOSETESTS_ENABLED == "true"'
+      when: never
+    - !reference [.test-policy, rules]
 
 ###############################################################################################
 #                                      test stage                                             #
@@ -669,8 +685,10 @@ py-unittest:
     paths:
       - $PYTHON_PROJECT_DIR/reports/
   rules:
-    # on any branch: when $UNITTEST_ENABLED is set
-    - if: '$UNITTEST_ENABLED == "true"'
+    # skip if $UNITTEST_ENABLED not set
+    - if: '$UNITTEST_ENABLED != "true"'
+      when: never
+    - !reference [.test-policy, rules]
 
 py-pytest:
   extends: .python-base
@@ -699,8 +717,10 @@ py-pytest:
     paths:
       - $PYTHON_PROJECT_DIR/reports/
   rules:
-    # on any branch: when $PYTEST_ENABLED is set
-    - if: '$PYTEST_ENABLED == "true"'
+    # skip if $PYTEST_ENABLED not set
+    - if: '$PYTEST_ENABLED != "true"'
+      when: never
+    - !reference [.test-policy, rules]
 
 py-nosetests:
   extends: .python-base
@@ -728,8 +748,10 @@ py-nosetests:
     paths:
       - $PYTHON_PROJECT_DIR/reports/
   rules:
-    # on any branch: when $NOSETESTS_ENABLED is set
-    - if: '$NOSETESTS_ENABLED == "true"'
+    # skip if $NOSETESTS_ENABLED not set
+    - if: '$NOSETESTS_ENABLED != "true"'
+      when: never
+    - !reference [.test-policy, rules]
 
 # Bandit (SAST)
 py-bandit:
@@ -759,12 +781,7 @@ py-bandit:
     # exclude if $BANDIT_ENABLED not set
     - if: '$BANDIT_ENABLED != "true"'
       when: never
-    # on non-production, non-integration branches: manual & non-blocking
-    - if: '$CI_COMMIT_REF_NAME !~ $PROD_REF && $CI_COMMIT_REF_NAME !~ $INTEG_REF'
-      when: manual
-      allow_failure: true
-    # else: manual & non-blocking
-    - when: always
+    - !reference [.test-policy, rules]
 
 # Safety (dependency check)
 py-safety:
@@ -795,12 +812,7 @@ py-safety:
     # exclude if $SAFETY_ENABLED not set
     - if: '$SAFETY_ENABLED != "true"'
       when: never
-    # on non-production, non-integration branches: manual & non-blocking
-    - if: '$CI_COMMIT_REF_NAME !~ $PROD_REF && $CI_COMMIT_REF_NAME !~ $INTEG_REF'
-      when: manual
-      allow_failure: true
-    # else: manual & non-blocking
-    - when: always
+    - !reference [.test-policy, rules]
 
 # Trivy (dependency check)
 py-trivy:
@@ -854,12 +866,7 @@ py-trivy:
     # exclude if $PYTHON_TRIVY_ENABLED not set
     - if: '$PYTHON_TRIVY_ENABLED != "true"'
       when: never
-    # on non-production, non-integration branches: manual & non-blocking
-    - if: '$CI_COMMIT_REF_NAME !~ $PROD_REF && $CI_COMMIT_REF_NAME !~ $INTEG_REF'
-      when: manual
-      allow_failure: true
-    # else: manual & non-blocking
-    - when: always
+    - !reference [.test-policy, rules]
 
 # (manual from master branch): triggers a release (tag creation)
 py-release:
-- 
GitLab