In this short article, we will look at the flatten function in Terraform, showing some examples of its use with nested lists and structures.
We will cover:
flatten
is a built-in function provided by Terraform. It takes a list and replaces any elements that are lists with a flattened sequence of the list contents. flatten
only expects one argument, which should be passed in as a list (elements separated by commas within square brackets []
). In Terraform, lists are ordered collections of values.
A Terraform function is a built-in operation that you can use to manipulate, transform, or query data within your Terraform configuration. Functions allow you to perform various tasks, such as transforming data, filtering collections, performing calculations, and more. They are an essential part of the HashiCorp Configuration Language (HCL) that Terraform uses.
Functions in Terraform are designed to be used within expressions. Expressions are used to define values, calculations, and transformations in your Terraform configuration files. Functions take input arguments (such as strings, lists, maps, etc.), perform a specific operation, and return a result that can be used within your configuration.
To test the flatten
function, you can use the Terraform console. Simply type terraform console
and enter the expression you want to test.
In the example below, I have two lists of values:
flatten([["luke", "yoda", "obiwan"], ["darth", "palpantine"]])
The flatten function returns the flattened sequence of the lists contents:
You can also include empty lists ( []
).
flatten([["luke", "yoda"], [], ["darth"]])
To define a list in your Terraform configuration files, you can define a variable like the example below.
If you want to define a list of a specific type (e.g., a list of numbers or strings), you can specify the type in the list
type declaration.
variable "list_of_characters" {
type = list(list(string))
default = [["luke", "yoda"], ["darth", "palpantine"], ["c3po", "r2d2"]]
}
To use flatten
with this variable, your configuration file would reference it like the example below:
flatten(var.list_of_characters)
And would output the following:
Example 1: Flattening nested structures for for_each
Terraform for_each
requires a collection that has one element for each repetition.
Sometimes your data structure may not be suitable to use with the for_each
function because of this. This is where the flatten
function can be used to manipulate the data in the correct way for use with for_each
.
Consider we define a variable containing some lists of our network ranges and subnets:
variable "networks" {
type = map(object({
cidr_block = string
subnets = map(object({ cidr_block = string }))
}))
default = {
"private" = {
cidr_block = "192.168.1.0/24"
subnets = {
"sql1" = {
cidr_block = "192.168.1.0/25"
}
"cosmos1" = {
cidr_block = "192.168.1.128/25"
}
}
},
"public" = {
cidr_block = "192.168.2.0/24"
subnets = {
"app1" = {
cidr_block = "192.168.2.0/28"
}
"app2" = {
cidr_block = "192.168.2.16/28"
}
}
}
}
In the below example that shows the creation of an AWS VPC, when referencing the variable in our code, we can use the for_each
function to iterate through the list and pull out the value for each cidr_block
defined in the variable.
resource "aws_vpc" "example" {
for_each = var.networks
cidr_block = each.value.cidr_block
}
However, to reference the values for the subnet cidr_blocks
, we need to use flatten
first to produce a flat list of objects, rather than the list of lists that we have defined in our variable if we wanted to define multiple subnets using a single resource block of code. This creates a neat way to create multiple VNETs and Subnets with minimal code.
In the example below, we use locals to create a flat list and reference it as local.network_subnets.
The for_each
function is then used with the for
expression to project the list into a map where each key is unique. Network and subnet keys are combined to produce a single unique key per instance.
locals {
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" {
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
}
Example 2: Iterating through a nested list
If any of the nested lists also contain directly-nested lists, these, too are flattened recursively:
flatten([[["luke", "yoda"], []], ["darth"]])
Example 3: Iterating through a nested list with a map
Indirectly-nested lists, such as those in maps, are not flattened.
In the below example, I have a map (defined within curly braces {}
) that contains two key-value pairs.
flatten([[["luke", "yoda"], []], ["darth"], {key1 = "jedi", key2 = "sith"} ])
Using the flatten function on these flattens the contents of the lists, but not the map contents:
Example 4: Using the flatten function with the for
expression
For the below examples, we have a variable that defines a nested map:
variable "nested_map" {
type = map(any)
default = {
"group:jedi" = [
"luke",
"yoda"
],
"group:sith" = [
"darth",
"palpantine"
],
"group:everyone" = [
"luke",
"darth",
"c3po",
"r2d2"
],
}
}
The first example below shows how we could use the flatten
function in a Terraform for
expression to retrieve values from the map.
Note that “luke” and “darth” are retrieved twice because they are in two of the lists.
locals {
flattened_map = flatten([
for group, characters in var.nested_map : [
group
]
])
}
output "result" {
value = distinct(local.flattened_map)
}
---------------------------------------------------------------------------
Output:
+ result = [
+ "luke",
+ "yoda",
+ "darth",
+ "palpantine",
+ "luke",
+ "darth",
+ "c3po",
+ "r2d2"
]
The second example below shows how we could use the flatten
function in a loop in combination with the distinct
function to retrieve only distinct values from the map.
locals {
flattened_map = flatten([
for group, characters in var.nested_map : [
group
]
])
}
output "result" {
value = distinct(local.flattened_map)
}
---------------------------------------------------------------------------
Output:
+ result = [
+ "luke",
+ "yoda",
+ "darth",
+ "palpantine",
+ "c3po",
+ "r2d2"
]
We could also use the flatten
function with a for
expression to retrieve the values of the keys in the map:
locals {
flattened_map = flatten([
for group, characters in var.nested_map : [
group
]
])
}
output "result" {
value = local.flattened_map
}
----------------------------------------------------------------------------
Output:
+ result = [
+ "group:jedi",
+ "group:sith",
+ "group:everyone",
]
The flatten
function can be used in expressions in Terraform to manipulate the output of a data structure, and can be useful to present your data to the for_each
function to iterate through a data structure that naturally wouldn’t be suitable to use with it, as for_each
requires a collection that has one element for each repetition.
For easier Terraform management, you can also check out Spacelift – a sophisticated and compliant infrastructure delivery platform. Spacelift can help you with building more complex workflows based on Terraform, and has the flexibility to integrate with any third party tool you want. You can test drive it for free by going here and creating a trial account.
Note: New versions of Terraform are placed under the BUSL license, but everything created before version 1.5.x stays open-source. OpenTofu is an open-source version of Terraform that expands on Terraform’s existing concepts and offerings. It is a viable alternative to HashiCorp’s Terraform, being forked from Terraform version 1.5.6.
The most flexible management platform for Infrastructure as Code
Spacelift is a sophisticated SaaS product for Infrastructure as Code that helps DevOps develop and deploy new infrastructures or changes quickly and with confidence.