1+ # GitLab CI configuration for OpenTofu/Terraform with GitOps approach
2+
3+ stages :
4+ - validate
5+ - plan
6+ - apply
7+ - cleanup
8+
9+ # Variables
10+ variables :
11+ TOFU_VERSION : " 1.6.0"
12+ VAULT_ADDR : ${VAULT_ADDR}
13+ # Non inserire qui valori sensibili, usare le variabili CI/CD di GitLab
14+
15+ # Cache terraform modules between runs
16+ cache :
17+ key : ${CI_COMMIT_REF_SLUG}
18+ paths :
19+ - .terraform
20+
21+ # Base container image for all jobs
22+ image : alpine:3.19
23+
24+ # Base configuration
25+ .tofu_base :
26+ before_script :
27+ # Install OpenTofu, curl, jq and other dependencies
28+ - apk add --no-cache curl unzip jq git bash ca-certificates
29+ # Install OpenTofu
30+ - |
31+ curl -SL "https://github.com/opentofu/opentofu/releases/download/v${TOFU_VERSION}/tofu_${TOFU_VERSION}_linux_amd64.zip" -o /tmp/tofu.zip
32+ unzip /tmp/tofu.zip -d /usr/local/bin/
33+ rm /tmp/tofu.zip
34+ # Configure OpenTofu state backend
35+ - |
36+ cat > backend.tf << EOF
37+ terraform {
38+ backend "http" {
39+ address = "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${CI_PROJECT_NAME}-${CI_ENVIRONMENT_NAME}"
40+ lock_address = "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${CI_PROJECT_NAME}-${CI_ENVIRONMENT_NAME}/lock"
41+ unlock_address = "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${CI_PROJECT_NAME}-${CI_ENVIRONMENT_NAME}/lock"
42+ username = "gitlab-ci-token"
43+ password = "${CI_JOB_TOKEN}"
44+ lock_method = "POST"
45+ unlock_method = "DELETE"
46+ retry_wait_min = 5
47+ }
48+ }
49+ EOF
50+ # Login in Vault e recupera i token necessari
51+ - |
52+ if [ -n "$VAULT_TOKEN" ]; then
53+ echo "Using provided VAULT_TOKEN"
54+ else
55+ echo "Logging into Vault with CI role"
56+ export VAULT_TOKEN=$(curl -s --request POST --data "{\"role\":\"ci-role\",\"jwt\":\"$CI_JOB_JWT\"}" "${VAULT_ADDR}/v1/auth/jwt/login" | jq -r '.auth.client_token')
57+ fi
58+ - tofu init
59+
60+ # Validate stage
61+ validate :
62+ extends : .tofu_base
63+ stage : validate
64+ script :
65+ - tofu validate
66+ rules :
67+ - if : $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
68+ when : always
69+
70+ # Plan stage
71+ plan :
72+ extends : .tofu_base
73+ stage : plan
74+ script :
75+ - tofu plan -out=plan.tfplan
76+ # Convert the plan to JSON for the approval
77+ - tofu show -json plan.tfplan > plan.json
78+ # Extract a readable summary for the MR comment
79+ - jq -r '.resource_changes[] | "\(.address) will be \(.change.actions[0])"' plan.json > plan_summary.txt
80+ artifacts :
81+ paths :
82+ - plan.tfplan
83+ - plan.json
84+ - plan_summary.txt
85+ expire_in : 1 week
86+ rules :
87+ - if : $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
88+ when : always
89+ - if : $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
90+ when : always
91+
92+ # Apply stage - only on the default branch
93+ apply :
94+ extends : .tofu_base
95+ stage : apply
96+ script :
97+ - |
98+ if [ -f plan.tfplan ]; then
99+ echo "Applying existing plan"
100+ tofu apply plan.tfplan
101+ else
102+ echo "No plan found, creating and applying"
103+ tofu apply -auto-approve
104+ fi
105+ artifacts :
106+ paths :
107+ - terraform.tfstate
108+ dependencies :
109+ - plan
110+ rules :
111+ - if : $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
112+ when : manual
113+
114+ # Clean cached files periodically
115+ cleanup :
116+ stage : cleanup
117+ script :
118+ - rm -rf .terraform
119+ - echo "Removed cached files"
120+ when : manual
121+ rules :
122+ - if : $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
123+ when : manual
124+ cache :
125+ key : ${CI_COMMIT_REF_SLUG}
126+ paths :
127+ - .terraform
128+ policy : pull
0 commit comments