From ab1dd87540c84a288b2131f2b1a25dcdb210c6aa Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 4 Dec 2020 16:15:54 -0800 Subject: [PATCH] website: Elaborate the "expressions" sub-pages in the language section When we did the earlier documentation rework for Terraform v0.12 we still had one big "Expressions" page talking about the various operators and constructs, and so we had to be a bit economical with the details about some more complicated constructs in order to avoid the page becoming even more overwhelming. However, we've recently reorganized the language documentation again so that the expressions section is split across several separate pages, and that gives some freedom to go into some more detail about and show longer examples for certain features. My changes here are not intended to be an exhaustive rewrite but I did try to focus on some areas I've commonly seen questions about when helping in the community forum and elsewhere, and also to create a little more connectivity between the different content so readers can hopefully find what they are looking for more easily when they're not yet sure what terminology to look for. --- .../expressions/conditionals.html.md | 25 +++ .../expressions/dynamic-blocks.html.md | 67 +++++- .../configuration/expressions/for.html.md | 192 +++++++++++++++--- .../expressions/function-calls.html.md | 48 ++++- .../expressions/operators.html.md | 29 ++- .../expressions/references.html.md | 88 +++++++- .../configuration/expressions/splat.html.md | 86 ++++++-- 7 files changed, 467 insertions(+), 68 deletions(-) diff --git a/website/docs/configuration/expressions/conditionals.html.md b/website/docs/configuration/expressions/conditionals.html.md index 56edeac1e..1ef7baae2 100644 --- a/website/docs/configuration/expressions/conditionals.html.md +++ b/website/docs/configuration/expressions/conditionals.html.md @@ -41,3 +41,28 @@ The two result values may be of any type, but they must both be of the _same_ type so that Terraform can determine what type the whole conditional expression will return without knowing the condition value. +If the two result expressions don't produce the same type then Terraform will +attempt to find a type that they can both convert to, and make those +conversions automatically if so. + +For example, the following expression is valid and will always return a string, +because in Terraform all numbers can convert automatically to a string using +decimal digits: + +```hcl +var.example ? 12 : "hello" +``` + +Relying on this automatic conversion behavior can be confusing for those who +are not familiar with Terraform's conversion rules though, so we recommend +being explicit using type conversion functions in any situation where there may +be some uncertainty about the expected result type. + +The following example is contrived because it would be easier to write the +constant `"12"` instead of the type conversion in this case, but shows how to +use [`tostring`](../functions/tostring.html) to explicitly convert a number to +a string. + +```hcl +var.example ? tostring(12) : "hello" +``` diff --git a/website/docs/configuration/expressions/dynamic-blocks.html.md b/website/docs/configuration/expressions/dynamic-blocks.html.md index 5d521d9bc..2c87c427a 100644 --- a/website/docs/configuration/expressions/dynamic-blocks.html.md +++ b/website/docs/configuration/expressions/dynamic-blocks.html.md @@ -9,7 +9,8 @@ page_title: "Dynamic Blocks - Configuration Language" Within top-level block constructs like resources, expressions can usually be used only when assigning a value to an argument using the `name = expression` form. This covers many uses, but some resource types include repeatable _nested -blocks_ in their arguments, which do not accept expressions: +blocks_ in their arguments, which typically represent separate objects that +are related to (or embedded within) the containing object: ```hcl resource "aws_elastic_beanstalk_environment" "tfenvtest" { @@ -42,9 +43,10 @@ resource "aws_elastic_beanstalk_environment" "tfenvtest" { } ``` -A `dynamic` block acts much like a `for` expression, but produces nested blocks -instead of a complex typed value. It iterates over a given complex value, and -generates a nested block for each element of that complex value. +A `dynamic` block acts much like a [`for` expression](for.html), but produces +nested blocks instead of a complex typed value. It iterates over a given +complex value, and generates a nested block for each element of that complex +value. - The label of the dynamic block (`"setting"` in the example above) specifies what kind of nested block to generate. @@ -86,6 +88,56 @@ and [`setproduct`](/docs/configuration/functions/setproduct.html) functions. +## Multi-level Nested Block Structures + +Some providers define resource types that include multiple levels of blocks +nested inside one another. You can generate these nested structures dynamically +when necessary by nesting `dynamic` blocks in the `content` portion of other +`dynamic` blocks. + +For example, a module might accept a complex data structure like the following: + +```hcl +variable "load_balancer_origin_groups" { + type = map(object({ + origins = set(object({ + hostname = string + })) + })) +} +``` + +If you were defining a resource whose type expects a block for each origin +group and then nested blocks for each origin within a group, you could ask +Terraform to generate that dynamically using the following nested `dynamic` +blocks: + +```hcl + dynamic "origin_group" { + for_each = var.load_balancer_origin_groups + content { + name = origin_group.key + + dynamic "origin" { + for_each = origin_group.value.origins + content { + hostname = origin.value.hostname + } + } + } + } +``` + +When using nested `dynamic` blocks it's particularly important to pay attention +to the iterator symbol for each block. In the above example, +`origin_group.value` refers to the current element of the outer block, while +`origin.value` refers to the current element of the inner block. + +If a particular resource type defines nested blocks that have the same type +name as one of their parents, you can use the `iterator` argument in each of +`dynamic` blocks to choose a different iterator symbol that makes the two +easier to distinguish. + ## Best Practices for `dynamic` Blocks Overuse of `dynamic` blocks can make configuration hard to read and maintain, so @@ -93,3 +145,10 @@ we recommend using them only when you need to hide details in order to build a clean user interface for a re-usable module. Always write nested blocks out literally where possible. +If you find yourself defining most or all of a `resource` block's arguments and +nested blocks using directly-corresponding attributes from an input variable +then that might suggest that your module is not creating a useful abstraction. +It may be better for the calling module to define the resource itself then +pass information about it into your module. For more information on this design +tradeoff, see [When to Write a Module](/docs/modules/#when-to-write-a-module) +and [Module Composition](/docs/modules/composition.html). diff --git a/website/docs/configuration/expressions/for.html.md b/website/docs/configuration/expressions/for.html.md index 5e7badf5b..04ab3a0ab 100644 --- a/website/docs/configuration/expressions/for.html.md +++ b/website/docs/configuration/expressions/for.html.md @@ -10,8 +10,8 @@ another complex type value. Each element in the input value can correspond to either one or zero values in the result, and an arbitrary expression can be used to transform each input element into an output element. -For example, if `var.list` is a list of strings, then the following expression -produces a list of strings with all-uppercase letters: +For example, if `var.list` were a list of strings, then the following expression +would produce a tuple of strings with all-uppercase letters: ```hcl [for s in var.list : upper(s)] @@ -22,10 +22,41 @@ evaluates the expression `upper(s)` with `s` set to each respective element. It then builds a new tuple value with all of the results of executing that expression in the same order. +## Input Types + +A `for` expression's input (given after the `in` keyword) can be a list, +a set, a tuple, a map, or an object. + +The above example showed a `for` expression with only a single temporary +symbol `s`, but a `for` expression can optionally declare a pair of temporary +symbols in order to use the key or index of each item too: + +```hcl +[for k, v in var.map : length(k) + length(v)] +``` + +For a map or object type, like above, the `k` symbol refers to the key or +attribute name of the current element. You can also use the two-symbol form +with lists and tuples, in which case the additional symbol is the index +of each element starting from zero, which conventionally has the symbol name +`i` or `idx` unless it's helpful to choose a more specific name: + +```hcl +[for i, v in var.list : "${i} is ${v}"] +``` + +The index or key symbol is always optional. If you specify only a single +symbol after the `for` keyword then that symbol will always represent the +_value_ of each element of the input collection. + +## Result Types + The type of brackets around the `for` expression decide what type of result -it produces. The above example uses `[` and `]`, which produces a tuple. If -`{` and `}` are used instead, the result is an object, and two result -expressions must be provided separated by the `=>` symbol: +it produces. + +The above example uses `[` and `]`, which produces a tuple. If you use `{` and +`}` instead, the result is an object and you must provide two result +expressions that are separated by the `=>` symbol: ```hcl {for s in var.list : s => upper(s)} @@ -33,39 +64,146 @@ expressions must be provided separated by the `=>` symbol: This expression produces an object whose attributes are the original elements from `var.list` and their corresponding values are the uppercase versions. +For example, the resulting value might be as follows: + +```hcl +{ + foo = "FOO" + bar = "BAR" + baz = "BAZ" +} +``` + +A `for` expression alone can only produce either an object value or a tuple +value, but Terraform's automatic type conversion rules mean that you can +typically use the results in locations where lists, maps, and sets are expected. + +## Filtering Elements A `for` expression can also include an optional `if` clause to filter elements -from the source collection, which can produce a value with fewer elements than -the source: +from the source collection, producing a value with fewer elements than +the source value: ``` [for s in var.list : upper(s) if s != ""] ``` -The source value can also be an object or map value, in which case two -temporary variable names can be provided to access the keys and values -respectively: +One common reason for filtering collections in `for` expressions is to split +a single source collection into two separate collections based on some +criteria. For example, if the input `var.users` is a map of objects where the +objects each have an attribute `is_admin` then you may wish to produce separate +maps with admin vs non-admin objects: -``` -[for k, v in var.map : length(k) + length(v)] +```hcl +variable "users" { + type = map(object({ + is_admin = boolean + })) +} + +locals { + admin_users = { + for name, user in var.users : name => user + if user.is_admin + } + regular_users = { + for name, user in var.users : name => user + if !user.is_admin + } +} ``` -Finally, if the result type is an object (using `{` and `}` delimiters) then -the value result expression can be followed by the `...` symbol to group -together results that have a common key: +## Element Ordering -``` -{for s in var.list : substr(s, 0, 1) => s... if s != ""} +Because `for` expressions can convert from unordered types (maps, objects, sets) +to unordered types (lists, tuples), Terraform must choose an implied ordering +for the elements of an unordered collection. + +For maps and objects, Terraform sorts the elements by key or attribute name, +using lexical sorting. + +For sets of strings, Terraform sorts the elements by their value, using +lexical sorting. + +For sets of other types, Terraform uses an arbitrary ordering that may change +in future versions of Terraform. For that reason, we recommend converting the +result of such an expression to itself be a set so that it's clear elsewhere +in the configuration that the result is unordered. You can use +[the `toset` function](../functions/toset.html) +to concisely convert a `for` expression result to be of a set type. + +```hcl +toset([for e in var.set : e.example]) ``` -For expressions are particularly useful when combined with other language -features to combine collections together in various ways. For example, -the following two patterns are commonly used when constructing map values -to use with -[the `for_each` meta-argument](/docs/configuration/meta-arguments/for_each.html): +## Grouping Results -* 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). +If the result type is an object (using `{` and `}` delimiters) then normally +the given key expression must be unique across all elements in the result, +or Terraform will return an error. + +Sometimes the resulting keys are _not_ unique, and so to support that situation +Terraform supports a special _grouping mode_ which changes the result to support +multiple elements per key. + +To activate grouping mode, add the symbol `...` after the value expression. +For example: + +```hcl +variable "users" { + type = map(object({ + role = string + })) +} + +locals { + users_by_role = { + for name, user in var.users : user.role => name... + } +} +``` + +The above represents a situation where a module expects a map describing +various users who each have a single "role", where the map keys are usernames. +The usernames are guaranteed unique because they are map keys in the input, +but many users may all share a single role name. + +The `local.users_by_role` expression inverts the input map so that the keys +are the role names and the values are usernames, but the expression is in +grouping mode (due to the `...` after `name`) and so the result will be a +map of lists of strings, such as the following: + +```hcl +{ + "admin": [ + "ps", + ], + "maintainer": [ + "am", + "jb", + "kl", + "ma", + ], + "viewer": [ + "st", + "zq", + ], +} +``` + +Due to [the element ordering rules](#element-ordering), Terraform will sort +the users lexically by username as part of evaluating the `for` expression, +and so the usernames associated with each role will be lexically sorted +after grouping. + +## Repeated Configuration Blocks + +The `for` expressions mechanism is for constructing collection values from +other collection values within expressions, which you can then assign to +individual resource arguments that expect complex values. + +Some resource types also define _nested block types_, which typically represent +separate objects that belong to the containing resource in some way. You can't +dynamically generated nested blocks using `for` expressions, but you _can_ +generate nested blocks for a resource dynamically using +[`dynamic` blocks](dynamic-blocks.html). diff --git a/website/docs/configuration/expressions/function-calls.html.md b/website/docs/configuration/expressions/function-calls.html.md index 77b6f75ba..d65c0b3f1 100644 --- a/website/docs/configuration/expressions/function-calls.html.md +++ b/website/docs/configuration/expressions/function-calls.html.md @@ -30,6 +30,11 @@ min(55, 3453, 2) A function call expression evaluates to the function's return value. +## Available Functions + +For a full list of available functions, see +[the function reference](/docs/configuration/functions.html). + ## Expanding Function Arguments If the arguments to pass to a function are available in a list or tuple value, @@ -43,8 +48,45 @@ min([55, 2453, 2]...) The expansion symbol is three periods (`...`), not a Unicode ellipsis character (`…`). Expansion is a special syntax that is only available in function calls. -## Available Functions +## When Terraform Calls Functions -For a full list of available functions, see -[the function reference](/docs/configuration/functions.html). +Most of Terraform's built-in functions are, in programming language terms, +[pure functions](https://en.wikipedia.org/wiki/Pure_function). This means that +their result is based only on their arguments and so it doesn't make any +practical difference when Terraform would call them. +However, a small subset of functions interact with outside state and so for +those it can be helpful to know when Terraform will call them in relation to +other events that occur in a Terraform run. + +The small set of special functions includes +[`file`](../functions/file.html), +[`templatefile`](../functions/templatefile.html), +[`timestamp`](../functions/timestamp.html), +and [`uuid`](../functions/uuid.html). +If you are not working with these functions then you don't need +to read this section, although the information here may still be interesting +background information. + +The `file` and `templatefile` functions are intended for reading files that +are included as a static part of the configuration and so Terraform will +execute these functions as part of initial configuration validation, before +taking any other actions with the configuration. That means you cannot use +either function to read files that your configuration might generate +dynamically on disk as part of the plan or apply steps. + +The `timestamp` function returns a representation of the current system time +at the point when Terraform calls it, and the `uuid` function returns a random +result which differs on each call. Without any special behavior these would +would both cause the final configuration during the apply step not to match the +actions shown in the plan, which violates the Terraform execution model. + +For that reason, Terraform arranges for both of those functions to produce +[unknown value](references.html#values-not-yet-known) results during the +plan step, with the real result being decided only during the apply step. +For `timestamp` in particular, this means that the recorded time will be +the instant when Terraform began applying the change, rather than when +Terraform _planned_ the change. + +For more details on the behavior of these functions, refer to their own +documentation pages. diff --git a/website/docs/configuration/expressions/operators.html.md b/website/docs/configuration/expressions/operators.html.md index 37906831c..330573590 100644 --- a/website/docs/configuration/expressions/operators.html.md +++ b/website/docs/configuration/expressions/operators.html.md @@ -30,15 +30,15 @@ in the following order of operations: 1. `&&` 1. `||` -Parentheses can be used to override the default order of operations. Without -parentheses, higher levels are evaluated first, so `1 + 2 * 3` is interpreted -as `1 + (2 * 3)` and _not_ as `(1 + 2) * 3`. +Use parentheses to override the default order of operations. Without +parentheses, higher levels will be evaluated first, so Terraform will interpret +`1 + 2 * 3` as `1 + (2 * 3)` and _not_ as `(1 + 2) * 3`. The different operators can be gathered into a few different groups with similar behavior, as described below. Each group of operators expects its given values to be of a particular type. Terraform will attempt to convert values to the required type automatically, or will produce an error message -if this automatic conversion is not possible. +if automatic conversion is impossible. ## Arithmetic Operators @@ -53,6 +53,11 @@ as results: generally useful only when used with whole numbers. * `-a` returns the result of multiplying `a` by `-1`. +Terraform supports some other less-common numeric operations as +[functions](function-calls.html). For example, you can calculate exponents +using +[the `pow` function](../functions/pow.html). + ## Equality Operators The equality operators both take two values of any type and produce boolean @@ -62,6 +67,18 @@ values as results. value, or `false` otherwise. * `a != b` is the opposite of `a == b`. +Because the equality operators require both arguments to be of exactly the +same type in order to decide equality, we recommend using these operators only +with values of primitive types or using explicit type conversion functions +to indicate which type you are intending to use for comparison. + +Comparisons between structural types may produce surprising results if you +are not sure about the types of each of the arguments. For example, +`var.list == []` may seem like it would return `true` if `var.list` were an +empty list, but `[]` actually builds a value of type `tuple([])` and so the +two values can never match. In this situation it's often clearer to write +`length(var.list) == 0` instead. + ## Comparison Operators The comparison operators all expect number values and produce boolean values @@ -80,3 +97,7 @@ The logical operators all expect bool values and produce bool values as results. * `a || b` returns `true` if either `a` or `b` is `true`, or `false` if both are `false`. * `a && b` returns `true` if both `a` and `b` are `true`, or `false` if either one is `false`. * `!a` returns `true` if `a` is `false`, and `false` if `a` is `true`. + +Terraform does not have an operator for the "exclusive OR" operation. If you +know that both operators are boolean values then exclusive OR is equivalent +to the `!=` ("not equal") operator. diff --git a/website/docs/configuration/expressions/references.html.md b/website/docs/configuration/expressions/references.html.md index 586d3869f..59d2b413e 100644 --- a/website/docs/configuration/expressions/references.html.md +++ b/website/docs/configuration/expressions/references.html.md @@ -60,24 +60,58 @@ For more information about how to use resource references, see `var.` is the value of the [input variable](/docs/configuration/variables.html) of the given name. +If the variable has a type constraint (`type` argument) as part of its +declaration, Terraform will automatically convert the caller's given value +to conform to the type constraint. + +For that reason, you can safely assume that a reference using `var.` will +always produce a value that conforms to the type constraint, even if the caller +provided a value of a different type that was automatically converted. + +In particular, note that if you define a variable as being of an object type +with particular attributes then only _those specific attributes_ will be +available in expressions elsewhere in the module, even if the caller actually +passed in a value with additional attributes. You must define in the type +constraint all of the attributes you intend to use elsewhere in your module. + ### Local Values `local.` is the value of the [local value](/docs/configuration/locals.html) of the given name. +Local values can refer to other local values, even within the same `locals` +block, as long as you don't introduce circular dependencies. + ### Child Module Outputs -* `module..` is the value of the specified - [output value](/docs/configuration/outputs.html) from a - [child module](/docs/configuration/blocks/modules/index.html) called by the - current module. +`module.` is an value representing the results of +[a `module` block](../blocks/modules/). + +If the corresponding `module` block does not have either `count` nor `for_each` +set then the value will be an object with one attribute for each output value +defined in the child module. To access one of the module's +[output values](../outputs.html), use `module..`. + +If the corresponding `module` uses `for_each` then the value will be a map +of objects whose keys correspond with the keys in the `for_each` expression, +and whose values are each objects with one attribute for each output value +defined in the child module, each representing one module instance. + +If the corresponding module uses `count` then the result is similar to for +`for_each` except that the value is a _list_ with the requested number of +elements, each one representing one module instance. ### Data Sources -* `data..` is an object representing a - [data resource](/docs/configuration/data-sources.html) of the given data - source type and name. If the resource has the `count` argument set, the value - is a list of objects representing its instances. If the resource has the `for_each` - argument set, the value is a map of objects representing its instances. +`data..` is an object representing a +[data resource](/docs/configuration/data-sources.html) of the given data +source type and name. If the resource has the `count` argument set, the value +is a list of objects representing its instances. If the resource has the `for_each` +argument set, the value is a map of objects representing its instances. + +For more information, see +[References to Resource Attributes](#references-to-resource-attributes), which +also applies to data resources aside from the addition of the `data.` prefix +to mark the reference as for a data resource. ### Filesystem and Workspace Info @@ -91,6 +125,36 @@ For more information about how to use resource references, see * `terraform.workspace` is the name of the currently selected [workspace](/docs/state/workspaces.html). +Use the values in this section carefully, because they include information +about the context in which a configuration is being applied and so may +inadvertently hurt the portability or composability of a module. + +For example, if you use `path.cwd` directly to populate a path into a resource +argument then later applying the same configuration from a different directory +or on a different computer with a different directory structure will cause +the provider to consider the change of path to be a change to be applied, even +if the path still refers to the same file. + +Similarly, if you use any of these values as a form of namespacing in a shared +module, such as using `terraform.workspace` as a prefix for globally-unique +object names, it may not be possible to call your module more than once in +the same configuration. + +Aside from `path.module`, we recommend using the values in this section only +in the root module of your configuration. If you are writing a shared module +which needs a prefix to help create unique names, define an input variable +for your module and allow the calling module to define the prefix. The +calling module can then use `terraform.workspace` to define it if appropriate, +or some other value if not: + +```hcl +module "example" { + # ... + + name_prefix = "app-${terraform-workspace}" +} +``` + ### Block-Local Values Within the bodies of certain blocks, or in some other specific contexts, @@ -110,6 +174,12 @@ _temporary variables_ in their documentation. These are not [input variables](/docs/configuration/variables.html); they are just arbitrary names that temporarily represent a value. +The names in this section relate to top-level configuration blocks only. +If you use [`dynamic` blocks](dynamic-blocks.html) to dynamically generate +resource-type-specific _nested_ blocks within `resource` and `data` blocks then +you'll refer to the key and value of each element differently. See the +`dynamic` blocks documentation for details. + ## Named Values and Dependencies Constructs like resources and module calls often use references to named values diff --git a/website/docs/configuration/expressions/splat.html.md b/website/docs/configuration/expressions/splat.html.md index ce3b18d24..6ae8017f7 100644 --- a/website/docs/configuration/expressions/splat.html.md +++ b/website/docs/configuration/expressions/splat.html.md @@ -39,34 +39,75 @@ The above expression is equivalent to the following `for` expression: [for o in var.list : o.interfaces[0].name] ``` -Splat expressions are for lists only (and thus cannot be used [to reference resources -created with `for_each`](/docs/configuration/meta-arguments/for_each.html#referring-to-instances), -which are represented as maps in Terraform). However, if a splat expression is applied -to a value that is _not_ a list or tuple then the value is automatically wrapped in -a single-element list before processing. +## Splat Expressions with Maps -For example, `var.single_object[*].id` is equivalent to `[var.single_object][*].id`, -or effectively `[var.single_object.id]`. This behavior is not interesting in most cases, -but it is particularly useful when referring to resources that may or may -not have `count` set, and thus may or may not produce a tuple value: +The splat expression patterns shown above apply only to lists, sets, and +tuples. To get a similar result with a map or object value you must use +[`for` expressions](for.html). -```hcl -aws_instance.example[*].id +Resources that use the `for_each` argument will appear in expressions as a map +of objects, so you can't use splat expressions with those resources. +For more information, see +[Referring to Resource Instances](/docs/configuration/meta-arguments/for_each.html#referring-to-instances). + +## Single Values as Lists + +Splat expressions have a special behavior when you apply them to a value that +isn't a list, set, or tuple. + +If the value is anything other than a null value then the splat expression will +transform it into a single-element list, or more accurately a single-element +tuple value. If the value is _null_ then the splat expression will return an +empty tuple. + +This special behavior can be useful for modules that accept optional input +variables whose default value is `null` to represent the absense of any value, +to adapt the variable value to work with other Terraform language features that +are designed to work with collections. For example: + +``` +variable "website" { + type = object({ + index_document = string + error_document = string + }) + default = null +} + +resource "aws_s3_bucket" "example" { + # ... + + dynamic "website" { + for_each = var.website[*] + content { + index_document = website.value.index_document + error_document = website.value.error_document + } + } +} ``` -The above will produce a list of ids whether `aws_instance.example` has -`count` set or not, avoiding the need to revise various other expressions -in the configuration when a particular resource switches to and from -having `count` set. +The above example uses a [`dynamic` block](dynamic-blocks.html), which +generates zero or more nested blocks based on a collection value. The input +variable `var.website` is defined as a single object that might be null, +so the `dynamic` block's `for_each` expression uses `[*]` to ensure that +there will be one block if the module caller sets the website argument, or +zero blocks if the caller leaves it set to null. + +This special behavior of splat expressions is not obvious to an unfamiliar +reader, so we recommend using it only in `for_each` arguments and similar +situations where the context implies working with a collection. Otherwise, +the meaning of the expression may be unclear to future readers. ## Legacy (Attribute-only) Splat Expressions -An older variant of the splat expression is available for compatibility with -code written in older versions of the Terraform language. This is a less useful -version of the splat expression, and should be avoided in new configurations. +Earlier versions of the Terraform language had a slightly different version +of splat expressions, which Terraform continues to support for backward +compatibility. This older variant is less useful than the modern form described +above, and so we recommend against using it in new configurations. -An "attribute-only" splat expression is indicated by the sequence `.*` (instead -of `[*]`): +The legacy "attribute-only" splat expressions use the sequence `.*`, instead of +`[*]`: ``` var.list.*.interfaces[0].name @@ -81,4 +122,7 @@ This form has a subtly different behavior, equivalent to the following Notice that with the attribute-only splat expression the index operation `[0]` is applied to the result of the iteration, rather than as part of -the iteration itself. +the iteration itself. Only the attribute lookups apply to each element of +the input. This limitation was confusing some people using older versions of +Terraform and so we recommend always using the new-style splat expressions, +with `[*]`, to get the more consistent behavior.