Gitlab TF Backend Reusable Yaml

guides/terraform/cover.png

How to setup a Gitlab as a remote state backend for Terraform with Reusable Templates

Pre Setup

Lets create a new repo in gitlab that is called pipelines and create a folder called terraform.

Lets create Reusable Jobs that are configured from environment variables

Standards

  • All files in pipelines project must end in .yml

Main Export

We are going to make a tf.yml that contains the full template that can be pulled into an applications .gitlab-ci.yml

1include:
2    - local: 'tf/main/plan.yml'
3    - local: 'tf/main/apply.yml'

Main Jobs

All the primary scripts will go in main/

init.yml

Create a reusable terraform init script to run at the beginning of each terraform stage

We will create a variable check that runs at the very beginning to catch any configuration errors form the Environment variables

You can store the username and password in the gitlab ci variables or in vault.

FieldDesc
usernamegitlab username
passwordEither a gitlab token or actual password
 1tf_init_var_check:
 2    stage: .pre 
 3    script:
 4      - if [ -z "${TF_ADDRESS}"]; then echo "ERROR - TF_ADDRESS does not exist, add to .gitlab-ci.yml"; exit 1; fi
 5      - if [ -z "${TF_USERNAME}"]; then echo "ERROR - TF_USERNAME does not exist, add to .gitlab-ci.yml"; exit 1; fi
 6      - if [ -z "${TF_PASSWORD}"]; then echo "ERROR - TF_PASSWORD does not exist, add to .gitlab-ci.yml"; exit 1; fi
 7
 8.terraform_init:
 9  script:
10  - echo $GITLAB_USER_NAME
11  - rm -rf .terraform
12  - terraform --version
13  - |
14      terraform init \
15      -backend-config=address=${TF_ADDRESS} \
16      -backend-config=lock_address=${TF_ADDRESS}/lock \
17      -backend-config=unlock_address=${TF_ADDRESS}/lock \
18      -backend-config=username=${TF_USERNAME} \
19      -backend-config=password=${TF_PASSWORD} \
20      -backend-config=lock_method=POST \
21      -backend-config=unlock_method=DELETE \
22      -backend-config=retry_wait_min=5      

config.yml

This sets the Image for all jobs and creates a cache for .terraform

 1tf_base_config_var_check:
 2    stage: .pre 
 3    script:
 4      - if [ -z "${TF_IMAGE}"]; then echo "ERROR - TF_IMAGE does not exist, add to .gitlab-ci.yml"; exit 1; fi
 5      - if [ -z "${TF_ROOT}"]; then echo "ERROR - TF_ROOT does not exist, add to .gitlab-ci.yml"; exit 1; fi
 6
 7.tf_base_config:
 8    image: ${TF_IMAGE}
 9    cache:
10      key: "${TF_ROOT}"
11      paths:
12        - ${TF_ROOT}/.terraform/

plan.yml

This uses the config and init script to plan and save a plan artifact named "plans"

 1include:
 2    - local: 'tf/main/init.yml'
 3    - local: 'tf/main/config.yml'
 4
 5plan:
 6  stage: build
 7  extends:
 8      - .tf_base_config
 9  script:
10    - !reference [.terraform_init, script]
11    - terraform plan -out "plans"
12  artifacts:
13    paths:
14      - plans
15    expire_in: 20m

apply.yml

This uses the config and init script to apply the artifact named "plans" only on the master branch

 1include:
 2    - local: 'tf/main/init.yml'
 3    - local: 'tf/main/config.yml'
 4
 5
 6deploy:
 7  stage: deploy
 8  extends:
 9      - .tf_base_config
10  script:
11    - !reference [.terraform_init, script]
12    - terraform apply -input=false "plans"
13  only:
14    - master

Use Template

Add this to your gitlab-ci.yml

 1include:
 2    - project: 'groupName/pipelines'
 3      file:
 4          - tf/tf.yml
 5
 6
 7variables:
 8  TF_ROOT: $CI_PROJECT_DIR  # The relative path to the root directory of the Terraform project
 9  TF_STATE_NAME: STATE_NAME  # The name of the state file used by the GitLab Managed Terraform state backend
10  TF_IMAGE: registry.gitlab.com/gitlab-org/terraform-images/releases/terraform:1.1.9
11  ARTIFACT_VERSION: "1.0.0"
12  REALEASE_VERSION: $ARTIFACT_VERSION
13  TF_ADDRESS: "https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/terraform/state/${TF_STATE_NAME}"
14
15
16default:
17    tags:
18        - your
19        - runner
20        - tags

Enjoy Quickly Adding Gitlab as your Remote state backend

To use add this to main.tf

1terraform{
2    
3    backend "http" {
4
5    }
6}