| || || || |
We need to instruct GitLab CI on how our pipeline is to be composed. This file will specifically address the requirements needed by our Terraform code base.
In this file you'll notice a lot of references to
terraform-gitlab. This is a "wrapper" that augment's the Terraform implementation GitLab providers so that all of the configuration that Terraform needs is provided in the environment. Just know that it's there to help us operate Terraform inside of GitLab CI pipelines.
Here is a breakdown of the Terraform pipeline configuration file
.gitlab-ci.yml. The file is a YAML file and is pretty straight forward. We'll break it down into keywords and explain what their purpose is.
This section is going to contain some repetition from the previous section. This is because we've broken down this part of the configuration already to explain how a pipeline is constructed. I've repeated the content for clarity.
Let's look at the very first few lines and checkout what's happening:
1 2 3 4 5 6 7 8 9 10 11 12
All of this is configuring GitLab CI to behave in a particular way and do some tasks for use ahead of each stage. I think we should go over each item (above)...
This configures the entire pipeline to run all
script: configurations (explained below) in a Docker container using a specific image:
This particular image is perfect for our needs not just because it provides Terraform but because it's suitable for us inside of GitLab CI pipelines due to some bootstrapping that's being done around Terraform. This will become more clear later on.
This configuration keyword allows us to define variables that are available for use across the entire pipeline, in all stages, and can be used for all kinds of things.
cache: keyword we can have the pipeline cache certain files and or directories between stages/jobs, and even across pipelines themselves. For us this is important because after we call
terraform init we need to copy the
.terraform/ to the other stages in the pipeline. If we didn't we would have to call
terraform init for every job.
In our stages we use the
script: keyword to define the functionality of each stage and actually get our work done. The
before_script: configuration is used to have a script execute before the script inside of each of our
script: blocks. We're using the GitLab CI provided Terraform Docker image, so we need to use this feature to move into the
Our pipeline's stages are as follows:
1 2 3 4 5
These stages are stepped through, one by one, in the order shown. We have four stages:
Let's explore each one.
1 2 3 4 5 6 7 8 9 10 11
What rules do we have in our
1 2 3
We're using an
exists: keyword to determine if a file (
.destroy) exists or not. If it does then the
when: keyword determines what should happen, and in this case
never means this stage should
never be included in the pipeline.
Finally we're asking GitLab CI to check for changes to a list of pattern matches. In our case we're looking for changes to any files that match
*.tf, or Terraform configuration files. In the event such changes do exist then this rule evaluates to true and the stage is included in the pipeline.
init the Terraform installation. Then we
validate that the syntax of the code is valid. If not then the stage will fail and the pipeline will come to a halt.
In the above script we're using the
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
artefacts: keyword we're telling GitLab CI to create two artefacts: the plan file for Terraform to use at later stages, and the JSON version that gets pushed into the back end of the GitLab CI Terraform solution.
As we need to generate a Terraform plan so that our
apply can do its job, we use the
artefacts: keyword to store it for later recovery.
We produce a normal Terraform plan, a file that is used by
apply to action changes after the plan has been approved.
We also produce a JSON version of the plan so that the Terraform back end built into GitLab CI and work its magic. This is background magic for GitLab internal workings. We don't have to worry about this part of the script all that much.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
plan.cache file is downloaded into this current job from the
plan job, and is used during the execution of
We're seeing something slightly new here:
This is an
if: statement just like we've seen before. This time, however, we checking a different variable:
CI_COMMIT_BRANCH. And we're checking to see if it equal to the contents of another variable,
CI_DEFAULT_BRANCH. What are these variables?
CI_COMMIT_BRANCH pre-defined variable tells us what Git branch this job is running against. When a developer pushes code into the GitLab repository they will do so against a particular branch. This variables contains that branch name.
CI_DEFAULT_BRANCH variable contains the default branch name for the repository. In the past this would have been
master, and this is what it'll be called in Git repository that were created over a year ago. But these days this tends to be called
main instead. Therefore the
CI_DEFAULT_BRANCH variable will very likely be
Going back to our
if:, we're asking GitLab CI to check of this particular job is being expected due to a push to the default branch. If true, then this job is included in the stage and thus the pipeline.
We're also doing something else that's interesting:
when: manual. This is configuring the job to only execute based on manual intervention from a human. So this job isn't fully automated and requires us to press a button in the GitLab CI UI. you'll see this being referred to as a "manual gate" in the wild and it's good practice to put sensitive parts of your pipelines behind such gates.
terraform apply, but using the GitLab CI pipeline provided executable. This will execute our actual Terraform code, based on the
plan.cache file (the Terraform plan) and build our network for us.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
We're inverting the
if: rule you'll find above in the
apply: job. Instead of having this job execute if we detect changes to
*.tf files in the current commit, we're instead saying we don't want to run this job if there are changes. We only want this job to run if there are no changes to
*.tf files and a file called
.destroy exists in the commit.
All the other rules we've seen before now, but also note how this job is also behind a manual gate.
terraform destroy with a
-auto-approve flag to prevent Terraform asking us to confirm the command. This will destroy all of our network and so the requirement to create and commit a
.destroy file helps us not do this accidentally.
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
Committing the Code¶
- Set your working directory to the
- Save the file as
git add .gitlab-ci.ymlto add it to the Git staging area
git commit -am 'providing a CI pipeline for our IAC'to commit the file to our repository
- Push the code to GitLab.com: