200 lines
7.2 KiB
Markdown
200 lines
7.2 KiB
Markdown
|
---
|
|||
|
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/configuration/blocks/resources/syntax.html) configures one real
|
|||
|
infrastructure object. (Similarly, a
|
|||
|
[module block](/docs/configuration/blocks/modules/syntax.html) 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`](/docs/configuration/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.
|
|||
|
|
|||
|
-> **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:
|
|||
|
|
|||
|
```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`.)
|
|||
|
|
|||
|
## Using Expressions in `for_each`
|
|||
|
|
|||
|
The `for_each` meta-argument accepts map or set [expressions](/docs/configuration/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/configuration/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/configuration/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/configuration/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/configuration/types.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/configuration/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)
|
|||
|
}
|
|||
|
```
|