I am facing difficulties creating a proper setup for a CI/CD driven Terraform deployment strategy.
What I am looking for doesn't fit a classic "stages" approach and instead is more of a multiple production deployments of the same infrastructure components for different teams (organizational).
For example.. In my typical Software Engineering projects, the team has a CI/CD Pipeline where infrastructure is, depending on stage / job, deployed to the equivalent infrastructure stage for their software. They know how to do stuff and are YBI/YRI enabled.
In the Platform I have modules, again two stages (test and production) and a Repository glueing it together. Deployed via CI/CD. All good so far.
Now I have a project at hand where a lot of teams will require the same piece of infrastructure (a finished TF module) but do not use GIT / CI/CD / any modern method / automation. Training them is out of scope. Actually they are required to just "get the infrastructure" to use it by the organization.
On the other hand, I still want to be able to maintain the deployed infrastructure instead of "fire and forget". Therefor me and my team will provision, hand over and maintain the infrastructure. They are free to use it within the provided settings.
Requirements I want to achieve:
F.e.
./team1/main.tf
./team2/main.tf
./.../main.tf
The folders have a main.tf
using the module with team variable values and a providers.tf
with provider setup for the team (Azure Subscription).
Now that seems simple and where is the question. I want this to be executed with CI/CD. Colleagues should be able to create a MR with a new folder + terraform configuration and get the infrastructure that we can maintain from there but also hand it over to be used.
I haven't found anything really helpful while a lot of inputs are close.
I am pretty sure I am missing something very easy and just fail to google / ChatGPT the problem correctly.
Using GitLab-CI + Azure btw.
Thanks for your inputs.
Closest finding is CI/CD with Terragrunt/Terraform mono-repo
The following outline should accomplish what you want. This assumes you're using gitlab-managed terraform state, otherwise you'll need to adapt the solution for whatever system you use to manage your remote state.
Assume you have a structure like so:
.
├── projects
│ ├── team1
│ │ └── main.tf
│ ├── team2
│ │ └── main.tf
│ └── team3
│ └── main.tf
└── tf-autodeploy
This shell script tf-autodeploy
(described below) accepts one argument, which is the root directory containing subdirectories for each independent set of tf definitions (projects
in the above structure). It will traverse each directory, check if terraform state already exists; if it does not exist, it will terraform apply will be run.
Then you can have your ci configuration that (in relevant part) defines two jobs: one to apply automatic updates for new infrastructure deployments (not changes to existing state) and a job that can be run manually and specifying a variable of which project to update.
auto_tf_apply:
# automatically deploy _new_ infrastructure only once merged to default branch
needs: [init, validate]
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
changes: projects/**/*
script:
- tf-autoupdate "${CI_PROJECT_DIR}/projects"
tf_apply_manual:
when: manual
needs: [init, validate, plan]
variables:
PROJECT:
description: "The project to deploy changes to (e.g., 'team1')"
TF_ROOT: "$CI_PROJECT_DIR/projects/$PROJECT"
TF_STATE_NAME: "$PROJECT"
script:
- if [[ "$PROJECT" == "" ]]; then exit 1; fi
- cd $TF_ROOT
- gitlab-terraform apply
The tf-autoupdate
script run in the auto_tf_apply
job satisfies the requirements of "CI/CD deploys added Terraform configurations automatically on main merge" and "dosen't update already existing Terraform configurations"
The tf_apply_manual
job allows you to manually update existing configurations by running the manual job and specifying the variable value for the specific project (e.g., team1
) to be updated.
For additional context, see the examples in the gitlab documentation.
The contents of an example the tf-autodeploy
script
#!/usr/bin/env bash
# tf-autodeploy
root=$1
for project_name in "$root"/*; do
pushd "${root}/${project_name}"
export TF_STATE_NAME="${project_name}"
export TF_ROOT="${root}/${project_name}"
# check if state already exists
if ! gitlab-terraform state list
then
echo "creating new deployment for ${project_name}"
gitlab-terraform apply
else
echo "state already exists, skipping ${project_name}"
fi
popd
done