This page describes how to implement basic Gitlab's CI/CD. If you had no experience in CI/CD practices, visit CI/CI-Introduction.
1. Using GitLab's integrated CI/CD
In this tutorial, we will describe the usage of Gitlab flavored CI/CD. You will be presented with two examples of pipelines, a simple one and a more complex one.
To use GitLab's CI/CD you have to create .gitlab-ci.yml file at the root of your git repository. In this file, we will define a simple pipeline that will test your project.
2. Simple example of .gitlab-ci.yml
stages: - Build - Test Build CPP: stage: Build image: ubuntu:20.04 script: - apt-get update - apt-get install -y cmake g++ - cd TDD-cpp && mkdir build && cd build - cmake -DCMAKE_INSTALL_PREFIX=../install .. - cmake --build . --target all - cmake --install . artifacts: paths: - TDD-cpp/build/ - TDD-cpp/install/ Test CPP: stage: Test image: ubuntu:20.04 script: - apt-get update - apt-get install -y cmake - cd TDD-cpp/build && ctest dependencies: - Build CPP Test Python: stage: Test image: ubuntu:20.04 script: - apt-get update - apt-get -y install python3 python3-pip - pip3 install pytest - pytest TDD-python/
Let's go over this example step by step.
2.1. Stages
This line defines groups of jobs.
- Jobs in the same stage run in parallel.
- Jobs in the next stage run after the jobs from the previous stage complete successfully.
2.2. Jobs
First, we define the name of our job. In our case, we have
- Build CPP
- Test CPP
- Test Python
In the next step, we will explain each keyword in these jobs.
2.2.1. Stage
Define in which stage the given job will run.
2.2.2. Image
Define an image that will be used to build your image. Apart from using Docker Hub, you can also use images from your own image registry.
image: "registry.example.com/my/image:latest"
In this example, we use Ubuntu 20.04 that is pulled from Docker Hub.
With Gitlab, you can get a free image registry inside your project. You can use it to build custom testing environments by i.e already including dependencies of your application. This is considered a good testing practice.
2.2.3. Script
These are shell commands that will run inside your container. Your working directory is set on the root of your repository. In this example, the script installs the required dependencies and runs the build system or runs tests.
2.2.4. Artifacts
Artifacts are files or directories that are saved after a job succeeds, fails, or always. By default, it only saves artifacts on success. Very useful option is to make artifacts save always.
job_name: artifacts: when: always
This will help troubleshoot failing tests.
2.2.5. Paths
Path to artifacts to save. Can be a file or a folder. All paths are relative to the root of the project.
2.2.6. Dependencies
By default, all artifacts from previous stages are passed to each job. However, you can use the dependencies keyword to define a limited list of jobs to fetch artifacts from. You can also set a job to download no artifacts at all. To prevent a job from downloading artifacts, define an empty array.
3. A more complex example of .gitlab-ci.yml
include: - local: "/CICD-gitlab/CI-templates.yml" stages: - Test Ubuntu: stage: Test parallel: matrix: - DISTRIBUTION: ubuntu VERSION: ["21.04", "20.10", "20.04"] image: $DISTRIBUTION:$VERSION before_script: !reference [.dependencies_ubuntu, before_script] script: - !reference [.build_CPP] - !reference [.test_CPP] - !reference [.test_Python] artifacts: when: always reports: junit: TDD-python/$DISTRIBUTION-$VERSION-pyreport.xml paths: - TDD-cpp/build/ - TDD-cpp/install/ - TDD-python/$DISTRIBUTION-$VERSION-pyreport.xml CentOS: stage: Test parallel: matrix: - DISTRIBUTION: centos VERSION: [8] image: $DISTRIBUTION:$VERSION before_script: !reference [.dependencies_centos, before_script] script: - !reference [.build_CPP] - !reference [.test_CPP] - !reference [.test_Python] artifacts: when: always reports: junit: TDD-python/$DISTRIBUTION-$VERSION-pyreport.xml paths: - TDD-cpp/build/ - TDD-cpp/install/ - TDD-python/$DISTRIBUTION-$VERSION-pyreport.xml
# This file is used to store build instructions templates # for particular systems like Ubuntu or CentOS. # It also generalizes testing instructions .build_CPP: - cd TDD-cpp && mkdir build && cd build - $CMAKE -DCMAKE_INSTALL_PREFIX=../install .. - $CMAKE --build . --target all - $CMAKE --install . - cd ../.. .test_CPP: - cd TDD-cpp/build && ctest - cd ../.. .test_Python: - pytest TDD-python/ --junitxml=TDD-python/$DISTRIBUTION-$VERSION-pyreport.xml .dependencies_ubuntu: before_script: - export CMAKE=cmake - export TZ=Europe/Warsaw - ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone - apt-get update -y - apt-get install -y cmake g++ python3 python3-pip - pip3 install pytest .dependencies_centos: before_script: - export CMAKE=cmake3 - yum update -y - yum --enablerepo=extras install -y epel-release - yum groupinstall -y "Development tools" - yum install -y cmake3 python3 python3-pip - pip3 install pytest
This example is a little bit different from the base one. Base example focused on building and testing your code just to check if tests are passing. Complex example extends this to check if your code also works on different platforms.
This can be used, for example, when your code is an MPI program that's supposed to run on HPC systems. Given that these systems are very heterogenous, testing different platforms may be very beneficial to you in terms of finding bugs early.
3.1.1. Include
Use include to include external YAML files in your CI/CD configuration. You can break down one long gitlab-ci.yml file into multiple files to increase readability, or reduce duplication of the same configuration in multiple places. In this example, we used CI-templates.yml located in /CICD-gitlab/CI-templates.yml. In this file, we wrote specific build dependencies instructions for CentOS and Ubuntu. Then, we wrote universal instructions to build and test our code. Finally, we use these instructions using reference keyword in the main .gitlab-ci.yml file.
3.1.2. Parallel & Matrix
This is the bread and butter of our pipeline. It runs a job multiple times in parallel in a single pipeline, but with different variable values for each instance of the job. In our example, we use it to change the base image, but the possibilities are endless. For example, you can use it to build your code in multiple configurations using multiple images. General example below
parallel: matrix: - DISTRIBUTION: [alpine/edge, devuan/ascii, devuan/beowulf] ARCH: [amd64, i386] COMPILER: [gcc, clang] BUILD_TYPE: [Debug, Release] - DISTRIBUTION: [centos/7] ARCH: [amd64] COMPILER: [gcc, clang] BUILD_TYPE: [Debug, Release]
Each job will have one of every environment variable available to use in your script or before_script.
3.1.3. Reports
Use this keyword to collect test reports, code quality reports, and security reports from jobs. It also exposes these reports in the GitLab UI (merge requests, pipeline views, and security dashboards) as shown in the screenshot above. It uses junit keyword which signals that report will be in JUnit XML format. There are many more keywords available, for example for test code coverage. This document will help you set everything up. It also has examples in multiple programming languages.