I am new to Terraform and am trying to provision my GitLab project using it. This works so far, but I am also creating keys that must not be reset with every run. Therefore, the GitLab CI variables should only be created if they do not already exist. Unfortunately, I have no idea how to formulate this in Terraform.
This is the relevant part of my configuration:
variable "env_scopes_all" {
type = list(object({
scope = string
env_type = string
}))
default = [
{ scope = "prod/live", env_type = "live" },
{ scope = "prod/staging", env_type = "staging" },
{ scope = "prod/review-*", env_type = "review" }
]
}
data "external" "vapid_keys" {
count = length(var.env_scopes_all)
program = ["/bin/sh", "-c", <<-EOF
pem=$(openssl ecparam -name prime256v1 -genkey -noout)
private_key=$(echo "$pem" | openssl ec -outform DER 2>/dev/null | tail -c +8 | head -c 32 | base64 -w 0 | tr -d '=' | tr '/+' '_-')
public_key=$(echo "$pem" | openssl ec -pubout -outform DER 2>/dev/null | tail -c 65 | base64 -w 0 | tr -d '=' | tr '/+' '_-')
printf '{"private_key":"%s","public_key":"%s"}\n' "$private_key" "$public_key"
EOF
]
}
resource "gitlab_project_variable" "VAPID_PRIVATE_KEY" {
count = length(var.env_scopes_all)
project = gitlab_project.elybird.id
key = "VAPID_PRIVATE_KEY"
value = data.external.vapid_keys[count.index].result.private_key
description = "Base64-encoded VAPID private key for push notifications"
environment_scope = var.env_scopes_all[count.index].scope
masked = false
protected = false
raw = true
variable_type = "env_var"
}
resource "gitlab_project_variable" "VAPID_PUBLIC_KEY" {
count = length(var.env_scopes_all)
project = gitlab_project.elybird.id
key = "VAPID_PUBLIC_KEY"
value = data.external.vapid_keys[count.index].result.public_key
description = "Base64-encoded VAPID public key for client push subscription"
environment_scope = var.env_scopes_all[count.index].scope
masked = false
protected = false
raw = true
variable_type = "env_var"
}
After having a discussion in the comments I am going to add this as an answer here around using ignore_changes
. I personally still dont feel like this is the best option for you. But thats up to you to decide.
I am using a simple example Since I dont have gitlab setup to demo this. The example uses a var in the external data source but this should be similar to your example
variable "custom_length" {
type = number
description = "Custom length for the random_pet resource"
}
data "external" "foo" {
program = ["powershell", "-Command", "echo '{\"counter\":\"${var.custom_length}\"}'"]
}
resource "random_pet" "foo" {
length = data.external.foo.result["counter"]
lifecycle {ignore_changes = [length]}
}
output "counter" {
value = data.external.foo.result["counter"]
}
output "pet_length" {
value = random_pet.foo.length
}
If we run this you can see that on the first apply it creates 1 resource. On the 2nd apply with a different value it does indeed ignore the change in the attribute after its created. It does not create or destroy or change any resources, only the output changes.
I have added the output only for reference so you can see the data block output is changing but terraform doesnt change the resources because we are ignoring that attributes value.
First Create
$ terraform apply --var=custom_length=3 --auto-approve
data.external.foo: Reading...
data.external.foo: Read complete after 0s [id=-]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# random_pet.foo will be created
+ resource "random_pet" "foo" {
+ id = (known after apply)
+ length = 3
+ separator = "-"
}
Plan: 1 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ counter = "3"
+ pet_length = 3
random_pet.foo: Creating...
random_pet.foo: Creation complete after 0s [id=needlessly-wanted-hare]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
counter = "3"
pet_length = 3
Second Apply (Changed value)
$ terraform apply --var=custom_length=5 --auto-approve
data.external.foo: Reading...
data.external.foo: Read complete after 0s [id=-]
random_pet.foo: Refreshing state... [id=needlessly-wanted-hare]
Changes to Outputs:
~ counter = "3" -> "5"
You can apply this plan to save these new output values to the Terraform state, without changing any real infrastructure.
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
counter = "5"
pet_length = 3
You can see in the 2nd apply there is no changes in the infra, the outputs show that the external resource is returning 5, but the random_pet keeps its value as 3 and terraform decides there is no change to be made in the resource.