flatten Function
flatten
takes a list and replaces any elements that are lists with a
flattened sequence of the list contents.
Examples
> flatten([["a", "b"], [], ["c"]])["a", "b", "c"]
If any of the nested lists also contain directly-nested lists, these too are flattened recursively:
> flatten([[["a", "b"], []], ["c"]])["a", "b", "c"]
Indirectly-nested lists, such as those in maps, are not flattened.
Flattening nested structures for for_each
The
resource for_each
and
dynamic
block
language features both require a collection value that has one element for
each repetition.
Sometimes your input data structure isn't naturally in a suitable shape for
use in a for_each
argument, and flatten
can be a useful helper function
when reducing a nested data structure into a flat one.
For example, consider a module that declares a variable like the following:
variable "networks" { type = map(object({ cidr_block = string subnets = map(object({ cidr_block = string })) })) default = { "private" = { cidr_block = "10.1.0.0/16" subnets = { "db1" = { cidr_block = "10.1.0.1/16" } "db2" = { cidr_block = "10.1.0.2/16" } } }, "public" = { cidr_block = "10.1.1.0/16" subnets = { "webserver" = { cidr_block = "10.1.1.1/16" } "email_server" = { cidr_block = "10.1.1.2/16" } } } "dmz" = { cidr_block = "10.1.2.0/16" subnets = { "firewall" = { cidr_block = "10.1.2.1/16" } } } }}
The above is a reasonable way to model objects that naturally form a tree, such as top-level networks and their subnets. The repetition for the top-level networks can use this variable directly, because it's already in a form where the resulting instances match one-to-one with map elements:
resource "aws_vpc" "example" { for_each = var.networks cidr_block = each.value.cidr_block}
However, in order to declare all of the subnets with a single resource
block, we must first flatten the structure to produce a collection where each
top-level element represents a single subnet:
locals { # flatten ensures that this local value is a flat list of objects, rather # than a list of lists of objects. network_subnets = flatten([ for network_key, network in var.networks : [ for subnet_key, subnet in network.subnets : { network_key = network_key subnet_key = subnet_key network_id = aws_vpc.example[network_key].id cidr_block = subnet.cidr_block } ] ])} resource "aws_subnet" "example" { # local.network_subnets is a list, so we must now project it into a map # where each key is unique. We'll combine the network and subnet keys to # produce a single unique key per instance. for_each = { for subnet in local.network_subnets : "${subnet.network_key}.${subnet.subnet_key}" => subnet } vpc_id = each.value.network_id availability_zone = each.value.subnet_key cidr_block = each.value.cidr_block}
The above results in one subnet instance per subnet object, while retaining the associations between the subnets and their containing networks.
Related Functions
setproduct
finds all of the combinations of multiple lists or sets of values, which can also be useful when preparing collections for use withfor_each
constructs.