Dieser Blogpost ist auch auf Deutsch verfügbar

Pipeline setup

When providing a GitLab CI/CD pipeline as a service or base to multiple development teams the pipeline code itself often resides in a separate repository. The development teams then include the pipeline in their .gitlab-ci.yml and use it. The example assumes the use of the pipeline in its entirety but the testing strategy could also be used to test single jobs in a more unit-testish way.

Our example consists of a pipeline repository and an application repository as shown below.

devops/ # group containing devops related projects
  pipeline # the pipeline project
apps/
  some-application # an application using the pipeline via include
GitLab group / project structure
pipeline/
  jobs/
    build.yml
    test.yml
    quality.yml
  pipeline.yml
Directory layout of the devops/pipeline repository
include:
  - local: 'pipeline/jobs/build.yml'
  - local: 'pipeline/jobs/test.yml'
  - local: 'pipeline/jobs/quality.yml'

stages:
  - build
  - test
  - quality
Simplified contents of pipeline.yml
include:
  - project: 'devops/pipeline'
    file: 'pipeline/pipeline.yml'
    ref: '1.0.0'
Contents of the application's .gitlab-ci.yml in the apps/some-application repository

Testing the pipeline

If you now want to develop a feature in the pipeline, how do you make sure that the pipeline still works without rolling it out to your customers and having them discover you completely messed up the functionality?

The simple solution is to create a test project that uses your pipeline to validate it still works. This would look something like the following code.

devops/
  pipeline
  spring-boot-test-app # the new test app
apps/
  some-application
Updated GitLab group / project structure
include:
  - project: 'devops/pipeline'
    file: 'pipeline/pipeline.yml'
    ref: 'main'
Contents of our test application .gitlab-ci.yml in the devops/spring-boot-test-app repository

As you have noticed we changed the ref property from 1.0.0 which is referencing a git tag to main assuming this is the default branch in the pipeline repository.

Let’s automate running the test project’s pipeline whenever the main branch in the pipeline changes. We can easily do so by adding a pipeline to the pipeline repository that makes use of GitLabs downstream pipelines feature and the trigger keyword. This leaves us with the code below.

pipeline/
  jobs/
    build.yml
    test.yml
    quality.yml
  pipeline.yml
.gitlab-ci.yml # <- the pipeline within the pipeline repository
Directory layout of the devops/pipeline repository
stages:
  - test-pipeline

test-spring-boot-app:
  stage: test-pipeline
  trigger:
    project: 'devops/spring-boot-test-app'
    strategy: depend
  # we only run on changes of our main branch
  rules:
    - if: $CI_COMMIT_BRANCH == 'main'
Contents of our pipeline .gitlab-ci.yml in the devops/pipeline repository

The flow now looks like this

GitLab pipeline test flow

Every time a commit on the main branch in the pipeline repository is made a CI/CD pipeline in the devops/spring-boot-test-app is triggered, which in turn makes use of the pipeline code in devops/pipeline on the main branch. The depend strategy in the trigger will even fail the upstream pipeline if the downstream pipeline fails.

Improving the idea

If we only test the main branch of the pipeline we still might encounter failures only after we have already added a new feature. So what we actually would like to do is to test a feature branch before we merge it to the main branch. This is surprisingly easy since we have the branch information at hand in predefined GitLab variables like CI_COMMIT_BRANCH and CI_COMMIT_REF_NAME and we can use a variable in the ref property of the include as well. There are some limitations to which variables can be used in includes but we can work with a project or group variable and - this is important - a trigger variable which we will call PIPELINE_REF_NAME.

We will have to declare the PIPELINE_REF_NAME as a project variable for the test app in devops/spring-boot-test-app setting it to main so by default the main branch of devops/pipeline will be included. Then we will pass the the pipelines current branch name to the downstream in the trigger.

stages:
  - test-pipeline

test-spring-boot-app:
  stage: test-pipeline
  variables:
    PIPELINE_REF_NAME: $CI_COMMIT_REF_NAME # <- we pass the current branch name to the downstream pipeline
  trigger:
    project: 'devops/spring-boot-test-app'
    strategy: depend
Updated contents of our pipeline .gitlab-ci.yml in the devops/pipeline repository
include:
  - project: 'devops/pipeline'
    file: 'pipeline/pipeline.yml'
    ref: $PIPELINE_REF_NAME # <- will use the variable passed by the trigger or the default of the project variable
Updated contents of our test application .gitlab-ci.yml in the devops/spring-boot-test-app repository

Now the include in the downstream pipeline of the test project is dynamically resolved depending on the upstream branch name allowing us to test our changed pipeline code prior to merging it.

Conclusion

This obviously does not guard against everything and is still time and resource consuming but it will allow you to create some test projects for common cases and help to catch some bugs early on. The automation makes it easy to incorporate testing in your pipeline development flow just like you would do with other pieces of software as well and likely improves the quality of your product.