Is there a way to have a nested for loop without having to create a new variable in locals?

Viewed 211

On terraform v0.14.4

My variable looks like this:

variable "my_config" {
  type = object({
    instances = set(string)
    locations = set(string)
  })
  default = {
    locations = [
      "us",
      "asia"
    ]
    instances = [
      "instance1",
      "instance2"
    ]
  }

I want to loop over this var in a resource and create an instance of the resource for each location + instance. The "name" field of the resource will be "<LOCATION>_<INSTANCE>" as well.

I could create a new var in locals that reads the my_config var and generates a new var that looks like this:

[
  "us_instance1",
  "us_instance2",
  "asia_instance1",
  "asia_instance2",
]

I would prefer to not generate a new terraform var from this existing var though. Is it possible in a foreach loop to aggregate these two lists directly in a resource definition? Or is the only way to create a new data structure in locals?

EDIT

I cannot get the flatten example in answer provided to work inside a resource definition. I get this error: The given "for_each" argument value is unsuitable: the "for_each" argument must be a map, or set of strings, and you have provided a value of type tuple. This error happens if the type is set(string) or list(string).

# This works
output "test" {
  value = flatten(
    [
      for location in var.my_config.locations : [
        for instance in var.my_config.instances : "${location}_${instance}"
      ]
    ]
  )
}

# This throws the error
resource "null_resource" "test" {
  for_each = flatten(
    [
      for location in var.my_config.locations : [
        for instance in var.my_config.instances : "${location}_${instance}"
      ]
    ]
  )
  provisioner "local-exec" {
    command = "echo test"
  }
}
1 Answers

To achieve the return value of:

[
  "us_instance1",
  "us_instance2",
  "asia_instance1",
  "asia_instance2",
]

with the input of the variable my_config, you could:

flatten([for location in var.my_config.locations : [
  for instance in var.my_config.instances : "${location}_${instance}"
]])

Whether or not you define this in a locals block is up to you. If you plan on re-using this value multiple times, then it would be more efficient to define it as a local. If you plan on on only using it once, then it would certainly make more sense to not define it in locals.

Note this also assumes my_config type is object(list(string)). The type was not given in the question, but if the type were otherwise then the code becomes much more obfuscated.

For the additional question about using this value as a for_each meta-argument value at the resource scope, it would need to be converted to type set(string). This can be done easily with the toset function:

resource "resource" "this" {
  for_each = toset(<expression above or variable with return value of above assigned to it>)
}
Related