Multiple Approvers in GitLab CI for Change Management

Editor's Note

The author of this article Chris Timberlake is an employee of GitLab. This article is not written in any affiliation with GitLab Inc. In addition, the actions in this article may not be officially supported by GitLab Support.

Introduction

In a previous article we wrote about how to use an external bot to obtain approvals. This method is one take on that problem. It has it’s advantages, such as having the bot being able to be tied into third party services. It can handle logging, it can collect information at approval time to pass to the pipeline. There’s alot of flexibility there. The downside being that it’s an external bot, and everything that comes with that.

Today, we’re going to show how to do Multiple Approvals in a GitLab CI pipeline, with just GitLab and it’s tools. We’re going to be using Protected Environments, Manual CI Jobs, and Approvers. The way this works is we have multiple stages in a CI Pipeline that require approvers for each. Without an approval - that stage does not progress.

Make an Environment.

The first step in this is to go to Operations -> Environments and create a new environment. I named mine ctimberlake-approval with my username being first, then approval as second. This makes it easy to know what it’s for at a glance. Leave the URL Blank. You’ll want to do this for every person who needs to approve the job.

Environment Setup

Once that’s done, and you have the environments. You need to protect them. From here you go to Settings -> CI/CD -> Protected Environments. You click the environment of the user, then select the user.

Protected Environment Setup

After this is done - You’re process should look like this. If it does, The setup is done for environments.

Protected Environment Setup

The Pipeline

Now that we have the setup done, we need to set this up to work properly. Below is our pipeline, I’m going to explain it simply. First, We define the stages. We have a build stage, This could be anything you want. But then we have an approvals stage. Everything before this approvals stage will run without delay. However, then it gets to approvals. The pipeline stops solid. The final step is the release step - Which only fires if we have approvals.

The magic here works because we’ve specified the approval job with (3) three things.

  1. when: manual - This will prevent the pipeline from proceeding without manual intervention.
  2. allow_failure: false - This will prevent the release stage from running by itself. Without it the pipeline wont stop at the approvals.
  3. environment: name: ctimberlake-approval - Because this environment is protected, The only person who can trigger this job are those that have access to the environment.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
stages:
  - build
  - approvals
  - release

build-1:
  stage: build
  script:
    - echo "We're building here"

ctimberlake-approval:
  stage: approvals
  when: manual
  allow_failure: false
  environment:
    name: ctimberlake-approval
  script:
    - echo "We're just doing a simple echo statement here. But a curl request could be made to mark the approval to a cental compliance tool."

versable-approval:
  stage: approvals
  when: manual
  allow_failure: false
  environment:
    name: versable-approval
  script:
    - echo "We're just doing a simple echo statement here. But a curl request could be made to mark the approval to a cental compliance tool."

release-1:
  stage: release
  script:
    - echo "We're releasing stuff here"

If you did everything right, You’ll be met with a pipeline like below. Where it shows that I can approve one job (and I did). However, the release stage has not triggered yet because Michael hasn’t hit the button. When he does, Release will then fire.

Pipeline Result

How to protect the pipeline.

If you take this approach. Then it means that the .gitlab-ci.yml is limiting your pipeline from running through and requiring approval for the end steps. What is to prevent someone from just editing that file, removing the approval steps, and then rogue releasing?

Simple answer, We lock the .gitlab-ci.yml file.

Create in the root of your gitlab directory a file named CODEOWNERS. In this file, add a line .gitlab-ci.yml @your-username-here. Now, commit it. Anytime someone wants to edit the pipeline they’ll need specific approval from someone in the codeowners file that’s listed there. Now Michael can’t edit the gitlab-ci.yml file to remove my approvals! Muhaha.

If you succeeded, you should now see the following when you look at .gitlab-ci.yml

Codeowner Result