for
Expressions
A for
expression creates a complex type value by transforming
another complex type value. Each element in the input value
can correspond to either one or zero values in the result, and an arbitrary
expression can be used to transform each input element into an output element.
For example, if var.list
were a list of strings, then the following expression
would produce a tuple of strings with all-uppercase letters:
[for s in var.list : upper(s)]
This for
expression iterates over each element of var.list
, and then
evaluates the expression upper(s)
with s
set to each respective element.
It then builds a new tuple value with all of the results of executing that
expression in the same order.
Input Types
A for
expression's input (given after the in
keyword) can be a list,
a set, a tuple, a map, or an object.
The above example showed a for
expression with only a single temporary
symbol s
, but a for
expression can optionally declare a pair of temporary
symbols in order to use the key or index of each item too:
[for k, v in var.map : length(k) + length(v)]
For a map or object type, like above, the k
symbol refers to the key or
attribute name of the current element. You can also use the two-symbol form
with lists and tuples, in which case the additional symbol is the index
of each element starting from zero, which conventionally has the symbol name
i
or idx
unless it's helpful to choose a more specific name:
[for i, v in var.list : "${i} is ${v}"]
The index or key symbol is always optional. If you specify only a single
symbol after the for
keyword then that symbol will always represent the
value of each element of the input collection.
Result Types
The type of brackets around the for
expression decide what type of result
it produces.
The above example uses [
and ]
, which produces a tuple. If you use {
and
}
instead, the result is an object and you must provide two result
expressions that are separated by the =>
symbol:
{for s in var.list : s => upper(s)}
This expression produces an object whose attributes are the original elements
from var.list
and their corresponding values are the uppercase versions.
For example, the resulting value might be as follows:
{
foo = "FOO"
bar = "BAR"
baz = "BAZ"
}
A for
expression alone can only produce either an object value or a tuple
value, but OpenTF's automatic type conversion rules mean that you can
typically use the results in locations where lists, maps, and sets are expected.
Filtering Elements
A for
expression can also include an optional if
clause to filter elements
from the source collection, producing a value with fewer elements than
the source value:
[for s in var.list : upper(s) if s != ""]
One common reason for filtering collections in for
expressions is to split
a single source collection into two separate collections based on some
criteria. For example, if the input var.users
is a map of objects where the
objects each have an attribute is_admin
then you may wish to produce separate
maps with admin vs non-admin objects:
variable "users" {
type = map(object({
is_admin = bool
}))
}
locals {
admin_users = {
for name, user in var.users : name => user
if user.is_admin
}
regular_users = {
for name, user in var.users : name => user
if !user.is_admin
}
}
Element Ordering
Because for
expressions can convert from unordered types (maps, objects, sets)
to ordered types (lists, tuples), OpenTF must choose an implied ordering
for the elements of an unordered collection.
For maps and objects, OpenTF sorts the elements by key or attribute name, using lexical sorting.
For sets of strings, OpenTF sorts the elements by their value, using lexical sorting.
For sets of other types, OpenTF uses an arbitrary ordering that may change in future versions. We recommend converting the expression result into a set to make it clear elsewhere in the configuration that the result is unordered. You can use the toset
function
to concisely convert a for
expression result to be of a set type.
toset([for e in var.set : e.example])
Grouping Results
If the result type is an object (using {
and }
delimiters) then normally
the given key expression must be unique across all elements in the result,
or OpenTF will return an error.
Sometimes the resulting keys are not unique, and so to support that situation OpenTF supports a special grouping mode which changes the result to support multiple elements per key.
To activate grouping mode, add the symbol ...
after the value expression.
For example:
variable "users" {
type = map(object({
role = string
}))
}
locals {
users_by_role = {
for name, user in var.users : user.role => name...
}
}
The above represents a situation where a module expects a map describing various users who each have a single "role", where the map keys are usernames. The usernames are guaranteed unique because they are map keys in the input, but many users may all share a single role name.
The local.users_by_role
expression inverts the input map so that the keys
are the role names and the values are usernames, but the expression is in
grouping mode (due to the ...
after name
) and so the result will be a
map of lists of strings, such as the following:
{
"admin": [
"ps",
],
"maintainer": [
"am",
"jb",
"kl",
"ma",
],
"viewer": [
"st",
"zq",
],
}
Due to the element ordering rules, OpenTF will sort
the users lexically by username as part of evaluating the for
expression,
and so the usernames associated with each role will be lexically sorted
after grouping.
Repeated Configuration Blocks
The for
expressions mechanism is for constructing collection values from
other collection values within expressions, which you can then assign to
individual resource arguments that expect complex values.
Some resource types also define nested block types, which typically represent
separate objects that belong to the containing resource in some way. You can't
dynamically generate nested blocks using for
expressions, but you can
generate nested blocks for a resource dynamically using
dynamic
blocks.