Search code examples
tagsconditional-statementsgitlab-ci

Gitlab CI: Run Pipeline job only for tagged commits that exist on protected branches


I would like to create a pipeline that is only run if both of the following conditions are met:

  • A tag refers to the given commit
  • The commit exists on any protected branch (i.e. master)
  • Optional: The job should be run whenever a tagged unprotected branch is merge (with a merge request) into a protected branch or if a tag is added to a protected branch.

I've tried:

publish:
  stage: publish
  script:
    - echo "Publish!"
  rules:
    # Only publish if tag given and commit is present on a protected branch
    - if: '$CI_COMMIT_TAG && $CI_COMMIT_REF_PROTECTED == "true"'

Which does not work as either the $CI_COMMIT_TAG is set or the $CI_COMMIT_REF_PROTECTED is set to true.

I am aware of the similar Questions: Gitlab ci run job on master with release tag only and How to run a gitlab-ci.yml job only on a tagged branch?.

Also I know there is/was a wide discussion in the issues from gitlab, with some solution (or something close to this) like this.

The general problem seems to be that it is not possible in gitlab to determine reliable if a commit if on a given branch as the information (git history) for this is not given.

This question is to keep track of a proper solution within gitlab CI for this common use case.


Solution

  • Combining the workaround mentioned in the question with the new gitlab rule and workflow features I came up with an answer that seems satisfying for me.

    The person originally posting the workaround mentioned that there are cases in which git branch contains does not give the correct results. So I made sure, that git fetch does not make a shallow copy (note for the beginning it could be useful to change the GIT_STRATEGY to clone, so that old possible shallow copies are removed).

    Instead of using CI_COMMIT_REF_PROTECTED which could be true also for protected tags, I hardcoded the $CI_DEFAULT_BRANCH (thx to [Gostega) as protected.

    # Be quite strict in what can trigger a pipeline, actually only pushes of
    # branches or version tags should trigger anything - otherwise we need to catch
    # too many edge cases.
    workflow:
      rules:
        # Do no allow manually triggered pipelines to prevent duplicates!
        # Instead rerun the pipeline created with the last push
        - if: $CI_PIPELINE_SOURCE != "push"
          when: never
        # Only execute when a valid version tag like v1.0, 2.3 or similar is given
        # Required is always one point like 1.0
        - if: $CI_COMMIT_TAG =~ /^v?[0-9]+[.][0-9]+([.][0-9]+)?$/
        - if: $CI_COMMIT_BRANCH
    variables:
      # Make sure we don't get a shallow copy
      GIT_DEPTH: 0
      # Fetch is default just to make clear what is used
      GIT_STRATEGY: fetch
      # make sure we fetch everything and also see what is happening
      GIT_FETCH_EXTRA_FLAGS: -f --tags --prune --update-head-ok
    
    default:
      before_script:
        - export CI_LOG_LINE=$(git log --decorate=full| grep "^commit $CI_COMMIT_SHA[ ]")
        # var = 1 if the current commit is the **latest** on the default branch
        - export IS_ON_MAIN=$(echo $CI_LOG_LINE | grep -qso "origin/${CI_DEFAULT_BRANCH}, " && echo 1 || echo 0)
        # var = 1 if current commit is on any remote commit that is part of default branchs history
        - export COMMIT_ON_MAIN=$(git branch -r --contains $CI_COMMIT_SHA | grep -Eq "^[ ]+origin/${CI_DEFAULT_BRANCH}$"  && echo 1 || echo 0)
    
    
    stages:
      - check_update_environment
      - test
      - publish
    
    check_update_environment:
      stage: check_update_environment
      script:
        # Exit if tag is given on none main branch early
        # Check for 
        - if [[ ! -z "$CI_COMMIT_TAG" && $COMMIT_ON_MAIN != 1 ]]; then
             echo "Tags should never be applied to non main branches!" >&2;
             echo "We quit early! Please delete the tag, merge the branch to main and recreate the tag to continue" >&2;
             exit 1;
          fi
    
    test:
      stage: test
      script:
        - echo "Doing testing..."
      dependencies:
        - check_update_environment
    
    publish:
      stage: publish
      script:
        - echo "Publishing..."
      rules:
        # Run always if there is version tag. The version tag is defined
        #   in the workflow rules
        # Due to the fail early in the environment check this is never done for
        # branches that aren't the default branch
        - if: $CI_COMMIT_TAG
      dependencies:
        - test