Moving from count --> for_each meta argument

In earlier versions of Terraform (before the inception of for_each), all of our resource definitions used the count meta_argument.

If we wanted to access a particular module that was sourcing one of our resources, that could be done with module.example.default[0] or module.example.default[1].

If we wanted to do the same for a resource that is now implementing the for_each meta argument - is the only way to do so to specify the actual name of key we’d like? I’m primarily concerned about the case where there are many keys to choose from and we’d like to select all of them, or perhaps a few specific ones.

Would we have to use some sort of for expression and use an if to filter out for something specific? Is there something similar to the splat operator that can be used - or the splat operator itself?

What if we’d like to pass in the module to depends_on - could we simply do that via depends_on = [module.example.default] as is?

Hi @mikek,

A key thing to know about count vs. for_each is that a resource with count set will appear as a list when you refer to it elsewhere in the module, while a resource with for_each appears as a map. In the for_each case, the map keys are the same as the keys in the map you pass to for_each. For example:

resource "null_resource" "example" {
  for_each = {
    "a" = 1
    "b" = 2
  }
}

In the above contrived example, null_resource.example elsewhere in the module would produce a map with the keys a and b and with the values set to objects representing each of the resource instances.

It sounds like you want your module to return an output value that summarizes the map of the resource instances for external consumption, using a map. If so, then a for expression is indeed the usual choice. The splat operator is essentially a special case of for expression that works only with lists and sets, but we can use a for expression with any collection or structure type.

Here’s a less-contrived example:

variable "subnet_cidr_blocks" {
  type = map(string)
}

resource "aws_subnet" "example" {
  for_each = var.subnet_cidr_blocks

  cidr_block = each.value
  # ...
}

output "subnet_ids" {
  value = { for k, s in aws_subnet.example : k => s.id }
}

From the perspective of the calling module then, the subnet ids map would be accessible as module.example.subnet_ids and the keys of that map would be the same keys the caller passed in the subnet_cidr_blocks map, allowing the caller to correlate them.

You can also produce a more complex result that aggregates together several different attributes, if you like:

output "subnets" {
  value = {
    for k, s in aws_subnet.example : k => {
      id                = s.id
      cidr_block        = s.cidr_block
      availability_zone = s.availability_zone
    }
  }
}

This would then appear to the caller as a map of objects, again with the same keys as given in the subnet_cidr_blocks variable.


I’m not sure how the question about depends_on relates to the rest of it, but indeed you can refer to a specific module output in a depends_on like that. If you do, the resource in question will depend indirectly on anything the output value depends on, which in my example above would be the aws_subnet resources inside the module.

Thank you for the clarification!

I was already familiar with the semantical difference of count using lists and for_each using maps, but didn’t quite understand the behavior of referencing a module by just name itself ( and if I understand correctly, the behavior - in the context of something like depends_on would mean that all of the elements inside of the map act as a dependency collectively - please let me know if this is the wrong idea).

As for the topic of outputs, if I was interested in referencing a specific key from an output - would the correct way to do that be to just specify the name of the key with square brackets?
Or alternatively, could I use an if clause (in the for loop) and generate a specific output with that item filtered out?

Each output block is a separate node in Terraform’s internal dependency tree. If you set the output value to a map, that’s still just a single node in the dependency tree, depending on everything that is referenced in the value or depends_on arguments inside the output block. Terraform doesn’t track a separate dependency node for each element of the map, because the elements of the map aren’t known until the value expression is evaluated, and Terraform must already know the dependencies before it can evaluate the expression.

(A similar thing is true for resource blocks, by the way: each block is a node in the dependency graph, so for a block using count or for_each there’s still only one node in the dependency graph representing all of the instances of that resource together, because the count or for_each expression can itself contain references, which create dependencies.)

In the calling module you can indeed use an output that is a map just like any other map in Terraform, using the square bracket index syntax:

module.example.subnets["foo"]

Terraform also allows references to the entire module at once, like module.subnets, which produces an object value with one attribute per output value defined in the module. In that situation, it automatically creates a dependency on each of the outputs of the module, because they all contribute to that composite object value.

Thank you for breaking this down even further!

Just wanted to quickly add that your responses are always stellar, Martin!