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


.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


.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


CI-templates.yml
# 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.






  • No labels