I have a requirement to set tags for the selection of GitLab runners where tags differ per environment. Therefore, I need the tag to be set according to the value of $CI_COMMIT_BRANCH
or $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
depending on context.
I have the following minimal example of the only pattern I have been able get working:
# Templates for rules
.rules__is_development:
tags:
- eks-development
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "development"
variables:
TF_WORKSPACE: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
- if: $CI_COMMIT_BRANCH == "development"
variables:
TF_WORKSPACE: $CI_COMMIT_BRANCH
.rules__is_staging:
tags:
- eks-staging
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "staging"
variables:
TF_WORKSPACE: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
- if: $CI_COMMIT_BRANCH == "staging"
variables:
TF_WORKSPACE: $CI_COMMIT_BRANCH
.rules__is_development__terraform_apply:
tags:
- eks-development
rules:
- if: $CI_COMMIT_BRANCH == "development"
.rules__is_staging__terraform_apply:
tags:
- eks-staging
rules:
- if: $CI_COMMIT_BRANCH == "staging"
variables:
ARTIFACTORY_DOCKER_PATH: artifactory.example.com/docker
stages:
- Quality
- Terraform
# Development Jobs
Terraform Format - Development:
extends: .rules__is_development
stage: Quality
when: always
image: $ARTIFACTORY_DOCKER_PATH/hashicorp/terraform:latest
script: terraform fmt -recursive -check -diff
Terraform Init - Development:
extends: .rules__is_development
stage: Terraform
image: $ARTIFACTORY_DOCKER_PATH/hashicorp/terraform:latest
script: terraform init -input=false
artifacts:
paths:
- .terraform
Terraform Apply - Development:
extends: .rules__is_development__terraform_apply
stage: Terraform
needs:
- job: Terraform Init - Development
artifacts: true
image: $ARTIFACTORY_DOCKER_PATH/hashicorp/terraform:latest
script: terraform apply -input=false
variables:
TF_WORKSPACE: $CI_COMMIT_BRANCH
# Staging Jobs
Terraform Format - Staging:
extends: .rules__is_staging
stage: Quality
when: always
image: $ARTIFACTORY_DOCKER_PATH/hashicorp/terraform:latest
script: terraform fmt -recursive -check -diff
Terraform Init - Staging:
extends: .rules__is_staging
stage: Terraform
image: $ARTIFACTORY_DOCKER_PATH/hashicorp/terraform:latest
script: terraform init -input=false
artifacts:
paths:
- .terraform
Terraform Apply - Staging:
extends: .rules__is_staging__terraform_apply
stage: Terraform
needs:
- job: Terraform Init - Staging
artifacts: true
image: $ARTIFACTORY_DOCKER_PATH/hashicorp/terraform:latest
script: terraform apply -input=false
variables:
TF_WORKSPACE: $CI_COMMIT_BRANCH
The configuration is very 'wet' - not DRY! The job definitions (Terraform Format, Terraform Init, Terraform Apply etc) are essentially duplicated for each environment, with the only differences being:
extends
field referencing environment-specific rules (e.g., .rules__is_development vs .rules__is_staging).needs
dependencies referencing environment-specific jobs (e.g., Terraform Init - Development vs Terraform Init - Staging).For the last 48 hours or so, just about everything I can think of using templates, conditionals, extends and includes. Ideally, I would have 3 layers of inheritance, a base template that defines the tag by environment, a second layer of templates that define each of the steps and includes the base tag template, and then a third layer that defines the pipeline itself.
In the end I found no way to do this without extreme duplication as shown above.
Does anyone know of a way of refactoring the above code in order to ideally avoid, or at least reduce, the duplication?
I am not sure if I have missed something in your post. But GitLab runner tags can be variables. So you could for example just set workflow rules to set the variables. This means you can set your TF_WORKSPACE and runner tag once.
see https://docs.gitlab.com/ci/runners/configure_runners/#use-cicd-variables-in-tags
In the .gitlab-ci.yml file, use CI/CD variables with tags for dynamic runner selection
You can then just define each job once and it will have the right values set based on the workflow rules. I have run an example of this using this structure. I cant use your exact values so I have two different types of Gitlabs online runners, I have also used the alpine image. but hopefully this illustrated the point.
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "development"
variables:
TF_WORKSPACE: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
RUNNER_TAG: saas-linux-small-amd64 #eks-development
- if: $CI_COMMIT_BRANCH == "development"
variables:
TF_WORKSPACE: $CI_COMMIT_BRANCH
RUNNER_TAG: saas-linux-small-amd64 #eks-development
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "staging"
variables:
TF_WORKSPACE: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
RUNNER_TAG: saas-linux-medium-amd64 #eks-staging
- if: $CI_COMMIT_BRANCH == "staging"
variables:
TF_WORKSPACE: $CI_COMMIT_BRANCH
RUNNER_TAG: saas-linux-medium-amd64 #eks-staging
default:
image: alpine:latest #artifactory.example.com/docker/hashicorp/terraform:latest
stages:
- Quality
- Terraform
Terraform Format:
tags:
- $RUNNER_TAG
stage: Quality
when: always
script:
- echo "$TF_WORKSPACE"
- echo "$RUNNER_TAG"
- terraform fmt -recursive -check -diff
Terraform Init:
tags:
- $RUNNER_TAG
stage: Terraform
script:
- echo "$TF_WORKSPACE"
- echo "$RUNNER_TAG"
- terraform init -input=false
artifacts:
paths:
- .terraform
Terraform Apply:
tags:
- $RUNNER_TAG
stage: Terraform
needs:
- job: Terraform Init
artifacts: true
script:
- echo "$TF_WORKSPACE"
- echo "$RUNNER_TAG"
- terraform apply -input=false
When run against the development branch
Running with gitlab-runner 18.3.0~pre.23.gb8a899e1 (b8a899e1)
on blue-2.saas-linux-medium-amd64.runners-manager.gitlab.com/default ZQ74L_riD, system ID: s_805fb2e5035f
Preparing the "docker+machine" executor
00:06
Using Docker executor with image alpine:latest ...
...
...
$ echo "$TF_WORKSPACE"
staging
$ echo "$RUNNER_TAG"
saas-linux-medium-amd64
When run against the staging branch
Running with gitlab-runner 18.3.0~pre.23.gb8a899e1 (b8a899e1)
on green-5.saas-linux-small-amd64.runners-manager.gitlab.com/default xS6Vzpvoq, system ID: s_6b1e4f06fcfd
Preparing the "docker+machine" executor
00:06
Using Docker executor with image alpine:latest ...
...
...
$ echo "$TF_WORKSPACE"
development
$ echo "$RUNNER_TAG"
saas-linux-small-amd64
You can see here that depending on the rules conditions it will run it on the related runner with the right TF_WORKSPACE.
If i have missed the point of something feel free to let me know.