CI Templates
The CI templates are a set of Gitlab CI job templates that can be
integrated into your .gitlab-ci.yml
file. These templates simplify
building and re-using container images for the project’s
registry.
To make use of the templates, you need to
The CI templates provide a set of templates per distribution and you need to include the one for the distribution(s) you want to use. The examples below use the Fedora templates.
Why use the CI templates?
With the CI templates you can easily build a persistent container image. Images are identified by a unique tag that allows for them to be used in the actual CI jobs.
So you can say “I want a Fedora 31 container with packages X, Y, Z
installed”, tag this with “2020-02-03.0” for the current date (best practice
but not required) and that image can be used from all tests. To build a
new container or rebuild the existing container with updated packages,
simply change the tag in your .gitlab-ci.yml
.
Merge request re-use the same container image for CI. The CI templates will take care of the rest. This gives you reproducible test results as all merge requests will use the same container images.
Where a merge request changes the tag, a new container is built for that merge request and this merge request runs CI with the new container image. All other merge requests are unaffected. Only once the merge request has been merged into the upstream project will future merge requests use the new container image.
All this is available with minimal boilerplate - you specify the distribution, version and the packages to install and the templates do the rest.
Including the CI templates
There are two ways of including a template in your project, depending on whether your project is hosted on gitlab.freedesktop.org or hosted elsewhere. In both cases, you should use a specific git sha of the files you want to include - the CI templates do not use version numbers.
Warning
You can use master
as git ref but we do not recommend this.
Using a specific git sha protects you from unplanned CI failures caused by
changes in the CI templates.
Projects hosted on gitlab.freedesktop.org
If your project
is hosted on gitlab.freedesktop.org
you can include it as follows:
.templates_sha: &templates_sha 123456deadbeef
include:
- project: 'freedesktop/ci-templates' # the project to include from
ref: *templates_sha # git ref of that project
file: '/templates/fedora.yml' # the actual file to include
- project: 'freedesktop/ci-templates'
ref: *templates_sha
file: '/templates/alpine.yml'
# rest of your .gitlab-ci.yml goes here
The above snippet first defines the git sha of the CI templates we want to include as a YAML anchor. It then includes the Fedora and Alpine templates from the CI templates repository of that specific sha. Using a YAML anchor is recommended to avoid duplication.
You can specify different shas for different files though it is not
something we recommend. You can specify any valid git ref (e.g.
master
) though we recommend that you use a specific sha.
For more information on the include:
statement, see the GitLab documentation
Projects not hosted on gitlab.freedesktop.org
If youre project is not hosted on gitlab.freedesktop.org
you can
use the CI templates as follows.
include:
- remote: 'https://gitlab.freedesktop.org/freedesktop/ci-templates/-/raw/123456deadbeef/templates/fedora.yml'
- remote: 'https://gitlab.freedesktop.org/freedesktop/ci-templates/-/raw/123456deadbeef/templates/alpine.yml'
# rest of your .gitlab-ci.yml goes here
The above snippets links to the files directly using the git sha.
You can specify different shas for different files though it is not
something we recommend. You can specify any valid git ref (e.g.
master
) though we recommend that you use a specific sha.
For more information on the include:
statement, see the GitLab documentation
Extending the template jobs
All jobs provided by the CI templates use a naming scheme in the form
.fdo.<type>@<distribution>
. They start with a dot (.
) and thus
do not get invoked by the GitLab CI runners. To make use of them, use
extends:
.
# include statements go here
myjob:
extends: .fdo.container-build@fedora
stage: somestage
variables:
FDO_DISTRIBUTION_VERSION: 31
FDO_DISTRIBUTION_PACKAGES: 'curl wget valgrind'
In the (incomplete) example above a job called myjob
is defined to be
invoked in the somestage
stage (user’s choice). It extends the
.fdo.container-build@fedora
template (see
Building container images). The variables
will be
used by the CI template to generate the correct container image.
For more information on the extends:
statement, see the GitLab documentation
Building container images
The CI templates provide two ways of building a container image:
container-build
for a normal container and qemu-build
for building an image that can be run through QEMU on a KVM-enabled host.
For projects without specific hardware interactions the
container-build
is sufficient.
As the name implies, this builds a container image if it does not already
exist. At runtime, it will check your image repository (i.e. the GitLab
username/project
) for the container image and then the upstream
repository.
If the image does not exist in your repository but it does exist upstream, the image is copied to your repository. Thus, even if upstream changes the image later, you have a copy of that image.
If the image does not exist upstream, it is built in your repository. Once your merge request is merged, the image will be rebuilt for the upstream repository.
Note
If you want to force a container build, set the
variable FDO_FORCE_REBUILD
.
Below is an example on how to build a container. To avoid repetition of boilerplate code, we define a template for the distribution we want to build on and re-use that template to fill in the variables for us where required.
include:
- project: 'freedesktop/ci-templates'
ref: 12345deadbeef
file: '/templates/fedora.yml'
# Let's define some stages to get the correct order of jobs.
stages:
- prep
- test
variables:
# The upstream repository path on gitlab.freedesktop.org to check
# for existing container images.
FDO_UPSTREAM_REPO: some/path
# A simple template so we only have one place where we need to
# define the Fedora version and the image tag
# Using a date as tag is best practice but it can be any string
# allowed by the GitLab registry.
.myproject.fedora:30:
variables:
FDO_DISTRIBUTION_VERSION: 31
FDO_DISTRIBUTION_TAG: '2020-03-10.0'
# A job to build a Fedora 31 container with valgrind and gcc
# installed.
#
# You must not define script: in this job, it is used by the
# container-build template.
build-fedora-container:
extends:
- .fdo.container-build@fedora # the CI template
- .myproject.fedora:30 # our template job above
stage: prep
variables:
# Packages to install on the container
FDO_DISTRIBUTION_PACKAGES: "valgrind gcc"
# The test job for your project. Extending from the CI Templates
# .fdo.distribution-image makes it use the same image we built above.
mytest:
extends:
- .fdo.distribution-image@fedora
- .myproject.fedora:30
stage: test
script:
# FDO_DISTRIBUTION_NAME is set by the distribution-image job.
# It should be considered read-only
- echo "Hello world in $FDO_DISTRIBUTION_NAME"
Warning
The .fdo.qemu-build
template uses an arch-specific
suffix. See Handling multi-arch images for important details.
Building QEMU-capable container images
Some tests need to run in a virtual machine instead of a container. For
those, qemu-capable images can be prepared with the CI templates. The
templates are identical to the ones shown above but use the term qemu
instead of container
. The above example thus becomes:
# A job to build a Fedora 31 qemu image with valgrind and gcc
# installed.
#
# You must not define script: in this job, it is used by the
# qemu-build template.
build-fedora-vm:
extends:
- .fdo.qemu-build@fedora@x86_64 # the CI template
- .myproject.fedora:30 # our template job above
stage: prep
variables:
# Packages to install on the vm
FDO_DISTRIBUTION_PACKAGES: "valgrind gcc"
Warning
The .fdo.qemu-build
template uses an arch-specific
suffix. See Handling multi-arch images for important details.
Once built, the container image provides the script /app/vmctl start
to start the virtual machine. The VM is configured to accept ssh connection
and aliased as host vm
. Commands can be run on the virtual machine with
the /app/vmctl exec
helper.
# The test job for your project. Extending from the CI Templates
# .fdo.distribution-image makes it use the same image we built above.
mytest:
extends:
- .fdo.distribution-image@fedora
- .myproject.fedora:30
stage: test
script:
# start the VM. This also sets up ssh/scp to connect to "vm"
# correctly.
- /app/vmctl start
# copy our workspace to the VM
# The quotes are required to stop the ':' from parsing as yaml
- scp -r $PWD "vm:"
# We don't want any failed commands to exit our script until VM
# cleanup has been completed.
- set +e
# run test-command on the VM and create the .success file if it
# succeeds
- /app/vmctl exec "cd $CI_PROJECT_NAME ; test-command" && touch .success
# copy any test results from the VM to our container so we can
# save them as artifacts
- scp -r vm:$CI_PROJECT_NAME/test-results.xml .
# shut down the VM
- /app/vmctl stop
# VM cleanup is complete, any command failures now should result in
# a CI failed job
- set -e
# our CI script exit code should match the test command exit status
- test -e .success || exit 1
Noteworthy in the above is that the actual test command and subsequent VM
cleanup are wrapped by a set +e
and set -e
call. This ensures that
any failed call does not immediately terminate the CI job but allows for the
proper cleanup of the VM. The .success
file is what determines the
actual success status of the CI job.
Image labels
Images built with the CI templates have a number of labels set that can be used by jobs or the GitLab setup itself. The example below shows how to access the labels from a CI job using skopeo and jq:
check label:
extends:
- .myproject.fedora:30 # our template job above
- .fdo.distribution_image@fedora
# We don't want/need to use the actual image we built earlier, we can use
# any image that has skopeo and jq, or install those as part of
# script:
image: any-image-with-skopeo-and-jq
script:
# FDO_DISTRIBUTION_IMAGE still has indirections
- DISTRO_IMAGE=$(eval echo ${FDO_DISTRIBUTION_IMAGE})
# retrieve the infos from the registry (once)
- JSON_IMAGE=$(skopeo inspect docker://$DISTRO_IMAGE)
# Parse the the pipeline_id label
- IMAGE_PIPELINE_ID=$(echo $JSON_IMAGE | jq -r '.Labels["fdo.pipeline_id"]')
# If the image was built as part of this pipeline, the image's pipeline
# ID is the same as the current pipeline ID.
# This can be used to poke other projects to rebuild dependent images.
- if [[ x"$IMAGE_PIPELINE_ID" == x"$CI_PIPELINE_ID" ]]; then
echo "Image was built in this pipeline"
fi
Note
Extending from .fdo.distribution_image@fedora
provides
FDO_DISTRIBUTION_IMAGE
. We do not actually use the image
itself, any image with skopeo
and jq
will work here.
The labels currently set by the CI templates are as follows:
Image Label |
Value |
|
|
|
|
|
|
|
|
|
|
|
|
For the values starting with CI_
, see the GitLab
environment variables documentation
As in the example above, the image’s fdo.pipeline_id
can be used to
check if an image was built as part of the current pipeline.
Deleting container images
Unfortunately, deleting container images is nontrivial with templates and it requires extra authentication tokens. Use the ci-fairy tool for this task:
delete-containers:
extends:
- .fdo.distribution-image@fedora
- .myproject.fedora:30
stage: cleanup
image: golang:alpine
before_script:
- apk add python3 git
- pip3 install git+https://gitlab.freedesktop.org/freedesktop/ci-templates
script:
# Go to your Profile, Settings, Access Tokens
# Create a personal token with 'api' scope, copy the value.
# Go to CI/CD, Schedules, schedule a new monthly job (or edit the existing one)
# Define a variable of type File named AUTHFILE. Content is that token
# value.
#
# This example assumes that you want to delete all but the current tag
- ci-fairy -v --authfile $AUTHFILE delete-image
--repository $FDO_DISTRIBUTION_NAME/$FDO_DISTRIBUTION_VERSION
--exclude-tag $FDO_DISTRIBUTION_TAG
only:
- schedules
This is a job to run container cleanup on a schedule job. We get
$FDO_DISTRIBUTION_NAME
by extending .fdo.distribution-image@fedora
but since we only need to run a simple python tool, we can just run off a
golang:alpine
image.
All other variables are courtesy of .myproject.fedora:30
(see
Extending the template jobs)
Because of restrictions in GitLab, this can only be run with an API token,
CI_JOB_TOKEN
does not have permissions to delete images.
The ci-fairy
command as run here will delete all images in the
fedora/30
image repository, excluding the one with the tag 2020-03-10.0
.
Handling multi-arch images
The .fdo.container-build
and .fdo.qemu-build
templates use an
arch-specific suffix (@x86_64
or @aarch64
) to build for the
appropriate architecture. This arch-specific suffix is not encoded in the
image name, and a potential pitfall when building identical images for
multiple architectures is that those images overwrite each other. This
example illustrates the problem:
# DO NOT USE THIS SNIPPET. This example illustrates a bug
.fedora:
variables:
FDO_DISTRIBUTION_VERSION: 32
FDO_DISTRIBUTION_TAG: '2020-11-12.0'
# uses distribution name, version and tag to store the image in the
# registry
build-x86:
stage: prep
extends:
- .fdo.container-build@fedora
- .fedora
# uses the same distribution name, version and tag to store the image
# in the registry, potentially overwriting the other image.
# if this job runs after the build-x86 job completed, it does nothing
# because an image with the given tag is already in the registry
build-arm:
stage: prep
extends:
- .fdo.container-build@fedora
- .fedora
tags:
- aarch64
# this job runs on whichever image got stored in the registry
run-image:
stage: build
extends:
- .fdo.distribution-image@fedora
- .fedora
script:
- echo "I don't know which image I'm running on"
Both build-
jobs produce the same image tag and which image ends up in
the registry depends on the (non-deterministic) order the jobs are run
and/or completed.
Where multi-arch jobs are required, the arch must be encoded in the image
name using FDO_DISTRIBUTION_TAG
(or FDO_REPO_SUFFIX
):
.fedora:
variables:
FDO_DISTRIBUTION_VERSION: 32
BASE_TAG: "2020-11-12.0"
.fedora-x86:
extends:
- .fedora
variables:
FDO_DISTRIBUTION_TAG: "x86_64-$BASE_TAG"
.fedora-arm:
extends:
- .fedora
tags:
- aarch64
variables:
FDO_DISTRIBUTION_TAG: "aarch64-$BASE_TAG"
build-x86:
stage: prep
extends:
- .fdo.container-build@fedora
- .fedora-x86
build-arm:
stage: prep
extends:
- .fdo.container-build@fedora
- .fedora-arm
.run-image:
stage: build
extends:
- .fdo.distribution-image@fedora
script:
- echo "I'm running on $FDO_DISTRIBUTION_TAG"
run-x86:
extends:
- .run-image
- .fedora-x86
run-arm:
extends:
- .run-image
- .fedora-arm
Templating the .gitlab-ci.yml can ease the maintenance of such pipelines.