7.2 KiB
layout | page_title |
---|---|
language | The for_each Meta-Argument - Configuration Language |
The for_each
Meta-Argument
-> Version note: for_each
was added in Terraform 0.12.6. Module support
for for_each
was added in Terraform 0.13, and previous versions can only use
it with resources.
-> Note: A given resource or module block cannot use both count
and for_each
.
Hands-on: Try the Manage Similar Resources With For Each tutorial on HashiCorp Learn.
By default, a resource block configures one real
infrastructure object (and similarly, a
module block includes a
child module's contents into the configuration one time).
However, sometimes you want to manage several similar objects (like a fixed
pool of compute instances) without writing a separate block for each one.
Terraform has two ways to do this:
count
and for_each
.
If a resource or module block includes a for_each
argument whose value is a map or
a set of strings, Terraform will create one instance for each member of
that map or set.
Basic Syntax
for_each
is a meta-argument defined by the Terraform language. It can be used
with modules and with every resource type.
The for_each
meta-argument accepts a map or a set of strings, and creates an
instance for each item in that map or set. Each instance has a distinct
infrastructure object associated with it, and each is separately created,
updated, or destroyed when the configuration is applied.
-> Note: The keys of the map (or all the values in the case of a set of strings) must
be known values, or you will get an error message that for_each
has dependencies
that cannot be determined before apply, and a -target
may be needed. for_each
keys
cannot be the result (or rely on the result of) of impure functions, including uuid
, bcrypt
,
or timestamp
, as their evaluation is deferred during the main evaluation step.
Map:
resource "azurerm_resource_group" "rg" {
for_each = {
a_group = "eastus"
another_group = "westus2"
}
name = each.key
location = each.value
}
Set of strings:
resource "aws_iam_user" "the-accounts" {
for_each = toset( ["Todd", "James", "Alice", "Dottie"] )
name = each.key
}
Child module:
# my_buckets.tf
module "bucket" {
for_each = toset(["assets", "media"])
source = "./publish_bucket"
name = "${each.key}_bucket"
}
# publish_bucket/bucket-and-cloudfront.tf
variable "name" {} # this is the input parameter of the module
resource "aws_s3_bucket" "example" {
# Because var.name includes each.key in the calling
# module block, its value will be different for
# each instance of this module.
bucket = var.name
# ...
}
resource "aws_iam_user" "deploy_user" {
# ...
}
The each
Object
In blocks where for_each
is set, an additional each
object is
available in expressions, so you can modify the configuration of each instance.
This object has two attributes:
each.key
— The map key (or set member) corresponding to this instance.each.value
— The map value corresponding to this instance. (If a set was provided, this is the same aseach.key
.)
Using Expressions in for_each
The for_each
meta-argument accepts map or set expressions.
However, unlike most arguments, the for_each
value must be known
before Terraform performs any remote resource actions. This means for_each
can't refer to any resource attributes that aren't known until after a
configuration is applied (such as a unique ID generated by the remote API when
an object is created).
The for_each
value must be a map or set with one element per desired
resource instance. When providing a set, you must use an expression that
explicitly returns a set value, like the toset
function; to prevent unwanted surprises during conversion, the for_each
argument does not implicitly convert lists or tuples to sets.
If you need to declare resource instances based on a nested
data structure or combinations of elements from multiple data structures you
can use Terraform expressions and functions to derive a suitable value.
For example:
- Transform a multi-level nested structure into a flat list by
using nested
for
expressions with theflatten
function. - Produce an exhaustive list of combinations of elements from two or more
collections by
using the
setproduct
function inside afor
expression.
Referring to Instances
When for_each
is set, Terraform distinguishes between the block itself
and the multiple resource or module instances associated with it. Instances are
identified by a map key (or set member) from the value provided to for_each
.
<TYPE>.<NAME>
ormodule.<NAME>
(for example,azurerm_resource_group.rg
) refers to the block.<TYPE>.<NAME>[<KEY>]
ormodule.<NAME>[<KEY>]
(for example,azurerm_resource_group.rg["a_group"]
,azurerm_resource_group.rg["another_group"]
, etc.) refers to individual instances.
This is different from resources and modules without count
or for_each
, which can be
referenced without an index or key.
Similarly, resources from child modules with multiple instances are prefixed
with module.<NAME>[<KEY>]
when displayed in plan output and elsewhere in the UI.
For a module without count
or for_each
, the address will not contain
the module index as the module's name suffices to reference the module.
-> Note: Within nested provisioner
or connection
blocks, the special
self
object refers to the current resource instance, not the resource block
as a whole.
Using Sets
The Terraform language doesn't have a literal syntax for
set values, but you can use the toset
function to explicitly convert a list of strings to a set:
locals {
subnet_ids = toset([
"subnet-abcdef",
"subnet-012345",
])
}
resource "aws_instance" "server" {
for_each = local.subnet_ids
ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
subnet_id = each.key # note: each.key and each.value are the same for a set
tags = {
Name = "Server ${each.key}"
}
}
Conversion from list to set discards the ordering of the items in the list and
removes any duplicate elements. toset(["b", "a", "b"])
will produce a set
containing only "a"
and "b"
in no particular order; the second "b"
is
discarded.
If you are writing a module with an input variable that
will be used as a set of strings for for_each
, you can set its type to
set(string)
to avoid the need for an explicit type conversion:
variable "subnet_ids" {
type = set(string)
}
resource "aws_instance" "server" {
for_each = var.subnet_ids
# (and the other arguments as above)
}