Today I Learned #03
If you use terraform, you know that HCL doesn’t work exactly like a programming language, so you might run into some problems with complex logic now and then.
I was working on an upgrade for a Kubernetes module we use to manage our environments. After some time, I realized that I should control a specific setting for a specific environment (dev cluster only). So, it should be something like with count to enable this resource, right?
Yes, it should be if this resource doesn’t already have a for_each
loop, so I need to use a different approach to achieve this.
Conditional Expression in Terraform
You can read this explanation about Conditional Expressions. https://developer.hashicorp.com/terraform/language/expressions/conditionals
A conditional expression uses the value of a boolean expression to select one of two values.
condition ? true_val : false_val
If condition is true then the result is
true_val
. If condition is false then the result isfalse_val
.
I had a code similar to this:
locals {
# use this for all roles
filenames = [
"not-so-random-name-01",
"not-so-random-name-02",
]
}
resource "local_file" "file" {
for_each = toset(local.filenames)
content = "Content of ${each.key}.txt"
filename = "${path.module}/${each.key}.txt"
}
When I try to add a count to apply this block, it fails with this result:
$ terraform plan
Error: Invalid combination of "count" and "for_each"
...
The "count" and "for_each" meta-arguments are mutually-exclusive, only one should
be used to be explicit about the number of resources to be created.
How to fix it?
It’s possible to combine the logic of the for_each
loop and also check for conditions. In this example, I check the condition var.env == dev
:
for_each = var.env == "dev" ? toset(local.filenames) : toset([])
I could also replace this comparison with a boolean variable.
for_each = var.enable == true ? toset(local.filenames) : toset([])
I could also check more than one condition:
for_each = var.env == "dev" || var.env == "stg" ? toset(local.filenames) : toset([])
Final code
With this extension of the original code, it was possible to do what I wanted, which was to apply it only in a desired environment, namely env = dev
.
variable "env" {
type = string
default = "dev"
}
locals {
filenames = [
"not-so-random-name-01",
"not-so-random-name-02",
]
}
resource "local_file" "file" {
for_each = var.env == "dev" ? toset(local.filenames) : toset([])
content = "Content of ${each.key}.txt"
filename = "${path.module}/${each.key}.txt"
}
Plan output
$ terraform plan
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:
# local_file.file["not-so-random-name-01"] will be created
+ resource "local_file" "file" {
+ content = "Content of not-so-random-name-01.txt"
+ directory_permission = "0777"
+ file_permission = "0777"
+ filename = "./not-so-random-name-01.txt"
+ id = (known after apply)
}
# local_file.file["not-so-random-name-02"] will be created
+ resource "local_file" "file" {
+ content = "Content of not-so-random-name-02.txt"
+ directory_permission = "0777"
+ file_permission = "0777"
+ filename = "./not-so-random-name-02.txt"
+ id = (known after apply)
}
Plan: 2 to add, 0 to change, 0 to destroy.
Now it’s possible to write more complex logic in your terraform modules. 😀