terraform/website/docs/language/meta-arguments/for_each.html.md

219 lines
8.5 KiB
Markdown
Raw Normal View History

---
layout: "language"
page_title: "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](https://learn.hashicorp.com/tutorials/terraform/for-each?in=terraform/0-13&utm_source=WEBSITE&utm_medium=WEB_IO&utm_offer=ARTICLE_PAGE&utm_content=DOCS) tutorial on HashiCorp Learn.
By default, a [resource block](/docs/language/resources/syntax.html) configures one real
infrastructure object (and similarly, a
[module block](/docs/language/modules/syntax.html) includes a
2020-12-03 17:45:04 +01:00
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`](/docs/language/meta-arguments/count.html) 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.
Map:
```hcl
resource "azurerm_resource_group" "rg" {
for_each = {
a_group = "eastus"
another_group = "westus2"
}
name = each.key
location = each.value
}
```
Set of strings:
```hcl
resource "aws_iam_user" "the-accounts" {
for_each = toset( ["Todd", "James", "Alice", "Dottie"] )
name = each.key
}
```
Child module:
```hcl
# my_buckets.tf
module "bucket" {
for_each = toset(["assets", "media"])
source = "./publish_bucket"
name = "${each.key}_bucket"
}
```
```hcl
# 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 as `each.key`.)
## Limitations on values used in `for_each`
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.
Sensitive values, such as [sensitive input variables](https://www.terraform.io/docs/language/values/variables.html#suppressing-values-in-cli-output),
[sensitive outputs](https://www.terraform.io/docs/language/values/outputs.html#sensitive-suppressing-values-in-cli-output),
or [sensitive resource attributes](https://www.terraform.io/docs/language/expressions/references.html#sensitive-resource-attributes),
cannot be used as arguments to `for_each`. The value used in `for_each` is used
to identify the resource instance and will always be disclosed in UI output,
which is why sensitive values are not allowed.
Attempts to use sensitive values as `for_each` arguments will result in an error.
If you transform a value containing sensitive data into an argument to be used in `for_each`, be aware that
[most functions in Terraform will return a sensitive result if given an argument with any sensitive content](https://www.terraform.io/docs/language/expressions/function-calls.html#using-sensitive-data-as-function-arguments).
In many cases, you can achieve similar results to a function used for this purpose by
using a `for` expression. For example, if you would like to call `keys(local.map)`, where
`local.map` is an object with sensitive values (but non-sensitive keys), you can create a
value to pass to `for_each` with `toset([for k,v in local.map : k])`.
## Using Expressions in `for_each`
The `for_each` meta-argument accepts map or set [expressions](/docs/language/expressions/index.html).
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`](/docs/language/functions/toset.html)
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 the `flatten` function](/docs/language/functions/flatten.html#flattening-nested-structures-for-for_each).
* Produce an exhaustive list of combinations of elements from two or more
collections by
[using the `setproduct` function inside a `for` expression](/docs/language/functions/setproduct.html#finding-combinations-for-for_each).
## 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>` or `module.<NAME>` (for example, `azurerm_resource_group.rg`) refers to the block.
- `<TYPE>.<NAME>[<KEY>]` or `module.<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](/docs/language/expressions/type-constraints.html#collection-types), but you can use the `toset`
function to explicitly convert a list of strings to a set:
```hcl
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](/docs/language/values/variables.html) 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:
```hcl
variable "subnet_ids" {
type = set(string)
}
resource "aws_instance" "server" {
for_each = var.subnet_ids
# (and the other arguments as above)
}
```