website: Align `count` and `for_each` sections

- Make these descriptions more similar, since they do basically the same thing.
- Add some subheaders to break up the wall of text and make it more skimmable.
- Nudge people more firmly toward `for_each` if they need to actually
  incorporate data from a variable into their instances.
- Add version note so you know whether you can use this yet.
This commit is contained in:
Nick Fagerlund 2019-08-07 16:47:42 -07:00 committed by Nick Fagerlund
parent d3dc1263bf
commit 979a2fa6d1
1 changed files with 102 additions and 50 deletions

View File

@ -226,11 +226,18 @@ maintainers understand the purpose of the additional dependency.
[inpage-count]: #count-multiple-resource-instances-by-count
By default, a single `resource` block corresponds to only one real
infrastructure object. Sometimes it is desirable to instead manage a set
of _similar_ objects of the same type, such as a fixed pool of compute
instances. You can achieve this by using the `count` meta-argument,
which is allowed in all `resource` blocks:
-> **Note:** A given resource block cannot use both `count` and `for_each`.
By default, a `resource` block configures one real infrastructure object.
However, sometimes you want to manage several similar objects, such as a fixed
pool of compute instances. Terraform has two ways to do this:
`count` and [`for_each`][inpage-for_each].
The `count` meta-argument accepts a whole number, and creates that many
instances of the resource. Each instance has a distinct infrastructure object
associated with it (as described above in
[Resource Behavior](#resource-behavior)), and each is separately created,
updated, or destroyed when the configuration is applied.
```hcl
resource "aws_instance" "server" {
@ -245,32 +252,45 @@ resource "aws_instance" "server" {
}
```
When the `count` meta-argument is present, a distinction exists between
the resource block itself — identified as `aws_instance.server`
and the multiple _resource instances_ associated with it, identified as
`aws_instance.server[0]`, `aws_instance.server[1]`, etc. Each instance has a
distinct infrastructure object associated with it (as described above in
[Resource Behavior](#resource-behavior)), and each is separately created,
updated, or destroyed when the configuration is applied.
#### The `count` Object
When `count` is _not_ present, a resource block has only a single resource
instance, which has no associated index.
In resource blocks where `count` is set, an additional `count` object is
available in expressions, so you can modify the configuration of each instance.
This object has one attribute:
Within resource blocks where `count` is set, an additional `count` object is
available for use in expressions so you can modify the configuration of each
instance. This object has one attribute, `count.index`, which provides the
distinct index number (starting with `0`) for each instance.
- `count.index` The distinct index number (starting with `0`) corresponding
to this instance.
The `count` meta-argument accepts [expressions](./expressions.html)
in its value, similar to the resource-type-specific arguments for a resource.
However, Terraform must interpret the `count` argument _before_ any actions
are taken from remote resources, and so (unlike the resource-type-specifc arguments)
the `count` expressions may not refer to any resource attributes that are
not known until after a configuration is applied, such as a unique id
generated by the remote API when an object is created.
#### Referring to Instances
For example, `count` can be used with an input variable that carries a list
value, to create one instance for each element of the list:
When `count` is set, Terraform distinguishes between the resource block itself
and the multiple _resource instances_ associated with it. Instances are
identified by an index number, starting with `0`.
- `<TYPE>.<NAME>` (for example, `aws_instance.server`) refers to the resource block.
- `<TYPE>.<NAME>[<INDEX>]` (for example, `aws_instance.server[0]`,
`aws_instance.server[1]`, etc.) refers to individual instances.
This is different from resources without `count` or `for_each`, which can be
referenced without an index or key.
#### Using Expressions in `count`
The `count` meta-argument accepts numeric [expressions](./expressions.html).
However, unlike most resource arguments, the `count` value must be known
_before_ Terraform performs any remote resource actions. This means `count`
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).
#### When to Use `for_each` Instead of `count`
If your resource instances are almost identical, `count` is appropriate. If some
of their arguments need distinct values that can't be directly derived from an
integer, it's safer to use `for_each`.
Before `for_each` was available, it was common to derive `count` from the
length of a list and use `count.index` to look up the original list value:
```hcl
variable "subnet_ids" {
@ -291,25 +311,30 @@ resource "aws_instance" "server" {
}
```
Note that the separate resource instances created by `count` are still
identified by their _index_, and not by the string values in the given
list. This means that if an element is removed from the middle of the list,
all of the indexed instances _after_ it will see their `subnet_id` values
change, which will cause more remote object changes than were probably
intended. The practice of generating multiple instances from lists should
be used sparingly, and with due care given to what will happen if the list is
changed later.
This was fragile, because the resource instances were still identified by their
_index_ instead of the string values in the list. If an element was removed from
the middle of the list, every instance _after_ that element would see its
`subnet_id` value change, resulting in more remote object changes than intended.
Using `for_each` gives the same flexibility without the extra churn.
### `for_each`: Multiple Resource Instances Defined By a Map, or Set of Strings
[inpage-for_each]: #for_each-multiple-resource-instances-defined-by-a-map-or-set-of-strings
When the `for_each` meta-argument is present, Terraform will create instances
based on the keys and values present in a provided map, or set of strings, and expose the values
of the map to the resource for its configuration.
-> **Version note:** `for_each` was added in Terraform 0.12.6.
The keys and values of the map, or strings in the case of a set, are exposed via the `each` attribute,
which can only be used in blocks with a `for_each` argument set.
-> **Note:** A given resource block cannot use both `count` and `for_each`.
By default, a `resource` block configures one real infrastructure object.
However, sometimes you want to manage several similar objects, such as a fixed
pool of compute instances. Terraform has two ways to do this:
[`count`][inpage-count] and `for_each`.
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 (as described above in
[Resource Behavior](#resource-behavior)), and each is separately created,
updated, or destroyed when the configuration is applied.
```hcl
resource "azurerm_resource_group" "rg" {
@ -322,16 +347,34 @@ resource "azurerm_resource_group" "rg" {
}
```
Resources created by `for_each` are identified by the key associated with the instance -
that is, if we have `azurerm_resource_group.rg` as above, the instances will be `azurerm_resource_group.rg["a_group"]`
and `azurerm_resource_group.rg["another_group"]`, as those are the keys in the map provided
to the `for_each` argument.
#### The `each` Object
The `for_each` argument also supports a set of strings in addition to maps; convert a list
to a set using the `toset` function. As such, we can take the example
in `count` and make it safer to use, as we can change items in our set
and because the string keys are used to identify the instances,
we will only change the items we intend to:
In resource 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`.)
#### Referring to Instances
When `for_each` is set, Terraform distinguishes between the resource block itself
and the multiple _resource instances_ associated with it. Instances are
identified by a map key (or set member) from the value provided to `for_each`.
- `<TYPE>.<NAME>` (for example, `azurerm_resource_group.rg`) refers to the resource block.
- `<TYPE>.<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 without `count` or `for_each`, which can be
referenced without an index or key.
#### Using Sets
The Terraform language doesn't have a literal syntax for
[sets](./types.html#collection-types), but you can use the `toset` function to
convert a list of strings to a set:
```hcl
variable "subnet_ids" {
@ -343,7 +386,7 @@ resource "aws_instance" "server" {
ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
subnet_id = each.key # note, each.key and each.value will be the same on a set
subnet_id = each.key # note: each.key and each.value are the same for a set
tags {
Name = "Server ${each.key}"
@ -351,6 +394,15 @@ resource "aws_instance" "server" {
}
```
#### Using Expressions in `for_each`
The `for_each` meta-argument accepts map or set [expressions](./expressions.html).
However, unlike most resource 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).
### `provider`: Selecting a Non-default Provider Configuration
[inpage-provider]: #provider-selecting-a-non-default-provider-configuration