TT Lab
Get started
Learn Learning paths Courses

GitLab CI/CD

Tagging nightly shipped a release

Continue in TT Lab

Goal

By changing the situation, you confirm how jobs get into and drop out of the list depending on the branch, the tag, the existence of a file, and the changes against the remote, and you engrave the deployment policy into the configuration with the order of the rules and the variables the rules set.

Why it matters

rules is not a condition asked when a job starts but a rule that decides the list when the pipeline is created. Only the first match applies, so the order of entries is itself the policy, and if you put them in the wrong order, the deployment disappears without an error or a release goes out on the wrong tag. The result of changes varies depending on what it is compared with, and if you keep the condition and the value (the deployment target) apart, a day will come when the two come out of line. Such differences are hard to see just by reading the configuration, so you have to change the situation and run it.

Steps

  1. Make /root/glci-rules a git repository and, in .gitignore, put .gitlab-ci-local/. In .gitlab-ci.yml, put stages [build, deploy], a job unit with no condition (build; echo "unit log=$LOG_LEVEL"), and, created only when $CI_COMMIT_BRANCH == "main", deploy-staging (deploy; echo staging), and commit on the main branch. The grader switches to the feature/login branch in a copy and checks that deploy-staging disappears from the list.
  2. Add a job release-notes (deploy; echo notes) that is created only when $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/, and commit. gitlab-ci-local does not read git tags, so it imitates a tag pipeline with --variable CI_COMMIT_TAG=v1.2.0. With v1.2.0 it must be in the list, and with nightly or no tag it must not.
  3. Give the job publish (deploy; echo publish) three entries in rules: if there is a tag, when: on_success; if it is the main branch, when: manual and allow_failure: false; and otherwise when: never. Commit. In the list, it must be manual (allowFailure false) on main, on_success if the tag variable is present, and absent on a feature branch.
  4. Add a job docker-build (build; echo docker) with rules: - exists: [Dockerfile] and commit. Do not create a Dockerfile in this repository yet. The grader checks that the job appears only when a Dockerfile is put into a copy.
  5. Make a bare repository at /root/glci-rules-origin.git, register it as origin, push main, and then run git remote set-head origin main. Then add a job docs-build (build; echo docs) with rules: - changes: ["docs/**/*"], commit, and push again. The grader makes a branch in which something under docs was changed and a branch in which only other files were changed, in copies, and checks that docs-build appears only in the former case.
  6. Add a job deploy (deploy; echo "target=$DEPLOY_TARGET"). Its rules are variables: {DEPLOY_TARGET: production} for a tag and variables: {DEPLOY_TARGET: staging} for main. Commit and push. When run on main, target=staging must be printed, and if you give the tag variable, target=production must be printed.
  7. At the very top of .gitlab-ci.yml, put workflow: rules:. For main it is variables: {LOG_LEVEL: warn}, and for everything else (when: always) it is variables: {LOG_LEVEL: debug}. Commit and push. On main, the unit log must be unit log=warn, and on a feature branch it must be unit log=debug.

Notes

Create the staging deployment only on main

Make /root/glci-rules a git repository and, in .gitignore, put .gitlab-ci-local/. In .gitlab-ci.yml, put stages [build, deploy], a job unit with no condition (build; echo "unit log=$LOG_LEVEL"), and, created only when $CI_COMMIT_BRANCH == "main", deploy-staging (deploy; echo staging), and commit on the main branch. The grader switches to the feature/login branch in a copy and checks that deploy-staging disappears from the list.

rules is evaluated when the pipeline is created. If no condition matches, that job drops out of the list as 'never'. Change the branch and compare the when column with gitlab-ci-local --list-csv-all.

Look even at the shape of the tag name to create release notes

Add a job release-notes (deploy; echo notes) that is created only when $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/, and commit. gitlab-ci-local does not read git tags, so it imitates a tag pipeline with --variable CI_COMMIT_TAG=v1.2.0. With v1.2.0 it must be in the list, and with nightly or no tag it must not.

=~ is a regular expression comparison. The tag variable is filled only in a tag pipeline, so if you look only at its existence, a release goes out even for a temporary tag such as nightly. Wrap the regular expression in slashes.

Only the first match applies — the order is the policy

Give the job publish (deploy; echo publish) three entries in rules: if there is a tag, when: on_success; if it is the main branch, when: manual and allow_failure: false; and otherwise when: never. Commit. In the list, it must be manual (allowFailure false) on main, on_success if the tag variable is present, and absent on a feature branch.

It reads from the top and uses only the first entry that matches. If you put a when: never with no condition in the middle, the entries below it are never read, and no error appears. To use a manual job as a real approval gate, put allow_failure: false along with it.

Bake the image only in repositories that have a Dockerfile

Add a job docker-build (build; echo docker) with rules: - exists: [Dockerfile] and commit. Do not create a Dockerfile in this repository yet. The grader checks that the job appears only when a Dockerfile is put into a copy.

exists looks at whether a file at that path exists in the repository. When several repositories include the same template, you use it to turn on only the relevant jobs without editing the configuration of each repository.

Build the docs only on a branch where the docs changed

Make a bare repository at /root/glci-rules-origin.git, register it as origin, push main, and then run git remote set-head origin main. Then add a job docs-build (build; echo docs) with rules: - changes: ["docs/**/*"], commit, and push again. The grader makes a branch in which something under docs was changed and a branch in which only other files were changed, in copies, and checks that docs-build appears only in the former case.

For changes, the key is 'changed compared with what'. gitlab-ci-local compares with the remote default branch (origin/main), so a remote is needed. In GitLab, a branch pipeline compares with the previous push and a merge request pipeline compares with the target branch.

Which rule you matched decides the deployment target

Add a job deploy (deploy; echo "target=$DEPLOY_TARGET"). Its rules are variables: {DEPLOY_TARGET: production} for a tag and variables: {DEPLOY_TARGET: staging} for main. Commit and push. When run on main, target=staging must be printed, and if you give the tag variable, target=production must be printed.

The variables of a rules entry go into the job only when that entry matches. If you keep the condition and the value in one place, a mismatch such as "it was a tag but went out to staging" structurally disappears.

Values for the whole pipeline are decided in workflow

At the very top of .gitlab-ci.yml, put workflow: rules:. For main it is variables: {LOG_LEVEL: warn}, and for everything else (when: always) it is variables: {LOG_LEVEL: debug}. Commit and push. On main, the unit log must be unit log=warn, and on a feature branch it must be unit log=debug.

workflow:rules decides whether to create the pipeline and the variables that go into the whole pipeline. You do not have to repeat the same condition in every job. This is also the place to block merge request and branch pipelines from overlapping, but that behavior can be confirmed only on a GitLab server.