Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 509
Kound Avatar asked Jul 06 '20 13:07

Kound


People also ask

How do I force a push to a protected branch?

To enable force pushes on branches that are already protected: Go to your project and select Settings > Repository. Expand Protected branches. In the list of protected branches, next to the branch, turn on the Allowed to force push toggle.

What is Before_script in GitLab-CI?

These are scripts that you choose to be run before the job is executed or after the job is executed. These can also be defined at the top level of the YAML file (where jobs are defined) and they'll apply to all jobs in the . gitlab-ci. yml file.

What is protected branch in GitLab?

Protecting a branch can be done in GitLab by marking a branch as Protected. This means that people with the Developer permission level and lower are not able to push changes directly to that branch; they need to create a merge request to push changes to these branches.


1 Answers

Combining the workaround mentioned in the question with the new gitlab rule and workflow features I came up with an answer that seem 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 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 possibly shallow copies are removed).

Instead of using CI_COMMIT_REF_PROTECTED which could be true also for protected tags, I hardcoded the master branch 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 master
    - export IS_ON_MASTER=$(echo $CI_LOG_LINE | grep -qso "origin/master, " && echo 1 || echo 0)
    # var = 1 if current commit is on any remote commit that is part of masters history
    - export COMMIT_ON_MASTER=$(git branch -r --contains $CI_COMMIT_SHA | grep -Eq '^[ ]+origin/master$'  && echo 1 || echo 0)


stages:
  - check_update_environment

check_update_environment:
  stage: check_update_environment
  script:
    # Lets print some debug stuff
    - echo $CI_JOB_TRIGGERED
    - echo $CI_PIPELINE_SOURCE
    - echo $CI_COMMIT_SHA
    - echo $CI_COMMIT_REF_NAME
    - echo $CI_BUILD_REF
    - echo $CI_COMMIT_BRANCH
    - echo $CI_COMMIT_TAG
    # Get the information about the state of the current commit
    - git log --decorate=full| grep "^commit $CI_COMMIT_SHA[ ]" || echo "Failed???"
    - git status
    - git remote show
    # Show current branch --> normally fails - only for kept for reference
    - git symbolic-ref --short HEAD || echo "Doesn't work"
    # Some more possible debug information
    - git branch --contains $CI_BUILD_REF
    - git tag --contains $CI_BUILD_REF
    - env
    # **Finally the important part**
    # Exit if tag is given on none master branch early
    - if [[ ! -z "$CI_COMMIT_TAG" && $COMMIT_ON_MASTER != 1 ]]; then
         echo "Tags should never be applied to non master branches!" >&2;
         echo "We quit early! Please delete the tag, merge the branch to master 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 master
    - if: $CI_COMMIT_TAG
  dependencies:
    - test
like image 198
Kound Avatar answered Oct 07 '22 19:10

Kound