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_valIf 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. 😀
 
      
     
       
       
      