Gitlab Gated Pipeline
Continuing from my last post about how I setup my website, this digs specifically into multi environment roll-out with a manual gate on Gitlab CI.
Where are we at
If you followed along from my previous post about my CI/CD pipeline, you will recall that we had two repositories and, therefore, two pipelines. For the gated release, we're going to extend the pipeline for the site layout repository and leave the content repository alone.
Here is what the layout YAML currently looks like:
stages:
- build
- deploy
build:
stage: build
image: dettmering/hugo-build
before_script:
- mkdir content
- "wget -O content.tar.gz https://gitlab.com/some-user/content/-/archive/master/content-master.tar.gz?private_token=${GITLAB_PRIVATE_TOKEN}"
- tar --strip-components=1 -zxf content.tar.gz -C content
- rm -rf content/.git content/.gitignore content/.gitlab-ci.yml
- rm content.tar.gz
- rm -rf /var/cache/apk/*
script:
- "hugo --config staging-config.toml"
artifacts:
expire_in: 1 week
paths:
- public
only:
- master
deploy:
stage: deploy
image: alpine:3.7
dependencies:
- build
before_script:
- apk update
- apk add git
- apk add rsync
- apk add openssh-client
- mkdir "${HOME}/.ssh"
- echo "$VPS_SSH_KEY" > "${HOME}/.ssh/id_rsa"
- chmod 600 "${HOME}/.ssh/id_rsa"
- eval "$(ssh-agent -s)"
- ssh-add "${HOME}/.ssh/id_rsa"
- echo $VPS_HOST >> "${HOME}/.ssh/known_hosts"
- rm -rf /var/cache/apk/*
script:
- rsync -hrvz --delete --exclude=_ public/ some-user@192.168.1.1:/var/www/my-site/
Multiple Environments
Ok, no one really likes going directly to Production. What would be really nice is to build and automatically deploy to a test or staging environment and then gate the production build/deploy. To do this, we need another two steps in our YAML file. These steps are just copies of what exist with small tweaks.
stages:
- build-stage
- deploy-stage
- build-prod
- deploy-prod
build-stage:
stage: build-stage
image: dettmering/hugo-build
before_script:
- mkdir content
- "wget -O content.tar.gz https://gitlab.com/some-user/content/-/archive/master/content-master.tar.gz?private_token=${GITLAB_PRIVATE_TOKEN}"
- tar --strip-components=1 -zxf content.tar.gz -C content
- rm -rf content/.git content/.gitignore content/.gitlab-ci.yml
- rm content.tar.gz
- rm -rf /var/cache/apk/*
script:
- "hugo --config staging-config.toml"
artifacts:
expire_in: 1 week
paths:
- public
only:
- master
deploy-stage:
stage: deploy-stage
image: alpine:3.7
dependencies:
- build
before_script:
- apk update
- apk add git
- apk add rsync
- apk add openssh-client
- mkdir "${HOME}/.ssh"
- echo "$VPS_SSH_KEY" > "${HOME}/.ssh/id_rsa"
- chmod 600 "${HOME}/.ssh/id_rsa"
- eval "$(ssh-agent -s)"
- ssh-add "${HOME}/.ssh/id_rsa"
- echo $VPS_HOST >> "${HOME}/.ssh/known_hosts"
- rm -rf /var/cache/apk/*
script:
- rsync -hrvz --delete --exclude=_ public/ some-user@192.168.1.1:/var/www/my-site-stg/
build-prod:
stage: build-prod
image: dettmering/hugo-build
before_script:
- mkdir content
- "wget -O content.tar.gz https://gitlab.com/some-user/content/-/archive/master/content-master.tar.gz?private_token=${GITLAB_PRIVATE_TOKEN}"
- tar --strip-components=1 -zxf content.tar.gz -C content
- rm -rf content/.git content/.gitignore content/.gitlab-ci.yml
- rm content.tar.gz
- rm -rf /var/cache/apk/*
script:
- "hugo --config production-config.toml"
artifacts:
expire_in: 1 week
paths:
- public
only:
- master
when: manual
allow_failure: false
deploy-prod:
stage: deploy-prod
image: alpine:3.7
dependencies:
- build
before_script:
- apk update
- apk add git
- apk add rsync
- apk add openssh-client
- mkdir "${HOME}/.ssh"
- echo "$VPS_SSH_KEY" > "${HOME}/.ssh/id_rsa"
- chmod 600 "${HOME}/.ssh/id_rsa"
- eval "$(ssh-agent -s)"
- ssh-add "${HOME}/.ssh/id_rsa"
- echo $VPS_HOST >> "${HOME}/.ssh/known_hosts"
- rm -rf /var/cache/apk/*
script:
- rsync -hrvz --delete --exclude=_ public/ some-user@192.168.1.1:/var/www/my-site/
And that is the worst thing ever. Stages are almost exactly the same except for two lines. That is bad because it is really hard to spot.
Gating
The new lines we added are for the gating process and go at the end of the production build stage.
# in the production build stage
when: manual
allow_failure: false
The manual setting makes sure the stage does not run without user input. Then the allow_failure
flag is set to false
so that stages after the current one do not run until this stage has completed.
That is the manual gating process.
Special features on Gitlab
Thankfully, we don't have to continue with this duplication madness. Gitlab provides two features that will simplify reading this file: hidden keys and anchors.
Combining these features allows us to create template stages and include them in regular stages.
Build template
The important parts that shouldn't change much are the before_script
, image
, and artifacts
. Let's pull those out and see what it looks like.
.build-template: &build-def
image: dettmering/hugo-build
before_script:
- mkdir content
- "wget -O content.tar.gz https://gitlab.com/some-user/content/-/archive/master/content-master.tar.gz?private_token=${GITLAB_PRIVATE_TOKEN}"
- tar --strip-components=1 -zxf content.tar.gz -C content
- rm -rf content/.git content/.gitignore content/.gitlab-ci.yml
- rm content.tar.gz
- rm -rf /var/cache/apk/*
artifacts:
expire_in: 1 week
paths:
- public
Next, we use the template in our build stages instead of being so verbose:
build-stage:
<<: *build-def
stage: build-stage
script:
- "hugo --config staging-config.toml"
only:
- master
build-prod:
<<: *build-def
stage: build-prod
script:
- "hugo --config production-config.toml"
only:
- master
when: manual
allow_failure: false
Deploy template
Thankfully the deploy stages look very similar so we can template them as well.
.deploy-tenplate: &deploy-def
image: alpine:3.7
before_script:
- apk update
- apk add git
- apk add rsync
- apk add openssh-client
- mkdir "${HOME}/.ssh"
- echo "$VPS_SSH_KEY" > "${HOME}/.ssh/id_rsa"
- chmod 600 "${HOME}/.ssh/id_rsa"
- eval "$(ssh-agent -s)"
- ssh-add "${HOME}/.ssh/id_rsa"
- echo $VPS_HOST >> "${HOME}/.ssh/known_hosts"
- rm -rf /var/cache/apk/*
# now the stages
deploy-stage:
<<: *deploy-def
stage: deploy-stage
dependencies:
- build-stage
script:
- rsync -hrvz --delete --exclude=_ public/ some-user@192.168.1.1:/var/www/my-site-test/
deploy-prod:
<<: *deploy-def
stage: deploy-prod
dependencies:
- build-prod
script:
- rsync -hrvz --delete --exclude=_ public/ some-user@192.168.1.1:/var/www/my-site/
Conclusion
I am sure that was a bit of a slog to read through. To be honest, that was kind of the point. To see the final version, you can download the full file from this website. In the final part of this series, I will cover testing and pre-processing of any files.