From 979a2fa6d13b20d24ec58fd71544f6a9aadaf604 Mon Sep 17 00:00:00 2001 From: Nick Fagerlund Date: Wed, 7 Aug 2019 16:47:42 -0700 Subject: [PATCH] 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. --- website/docs/configuration/resources.html.md | 152 +++++++++++++------ 1 file changed, 102 insertions(+), 50 deletions(-) diff --git a/website/docs/configuration/resources.html.md b/website/docs/configuration/resources.html.md index 5c55439e6c..18d8da957e 100644 --- a/website/docs/configuration/resources.html.md +++ b/website/docs/configuration/resources.html.md @@ -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`. + +- `.` (for example, `aws_instance.server`) refers to the resource block. +- `.[]` (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`. + +- `.` (for example, `azurerm_resource_group.rg`) refers to the resource block. +- `.[]` (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