» Attributes as Blocks

In Terraform v0.12 and later, the language makes a distinction between argument syntax and nested block syntax within blocks:

  • Argument syntax sets a named argument for the containing object. If the attribute has a default value then an explicitly-specified value entirely overrides that default.

  • Nested block syntax represents a related child object of the container that has its own set of arguments. Where multiple such objects are possible, multiple blocks of the same type can be present. If the nested attributes themselves have default values, they are honored for each nested block separately, merging in with any explicitly-defined arguments.

The distinction between these is particularly important for JSON syntax because the same primitive JSON constructs (lists and objects) will be interpreted differently depending on whether a particular name is an argument or a nested block type.

However, in some cases existing provider features were relying on the conflation of these two concepts in the language of Terraform v0.11 and earlier, using nested block syntax in most cases but using argument syntax to represent explicitly the idea of removing all existing objects of that type, since the absense of any blocks was interpreted as "ignore any existing objects".

The information on this page only applies to certain special arguments that were relying on this usage pattern prior to Terraform v0.12. The documentation for each of those features links to this page for details of the special usage patterns that apply. In all other cases, use either argument or nested block syntax as directed by the examples in the documentation for a particular resource type.

» Defining a Fixed Object Collection Value

When working with resource type arguments that behave in this way, it is valid and we recommend to use the nested block syntax whenever defining a fixed collection of objects:

example {
  foo = "bar"
}
example {
  foo = "baz"
}

The above implicitly specifies a two-element list of objects assigned to the example argument, treating it as if it were a nested block type.

If you need to explicitly call for zero example objects, you must use the argument syntax with an empty list:

example = []

These two forms cannot be mixed; there cannot be both explicitly zero example objects and explicit single example blocks declared at the same time.

For true nested blocks where this special behavior does not apply, assigning [] using argument syntax is not valid. The normal way to specify zero objects of a type is to write no nested blocks at all.

» Arbitrary Expressions with Argument Syntax

Although we recommend using block syntax for simple cases for readability, the names that work in this mode are defined as arguments, and so it is possible to use argument syntax to assign arbitrary dynamic expressions to them, as long as the expression has the expected result type:

example = [
  for name in var.names: {
    foo = name
  }
]
# Not recommended, but valid: a constant list-of-objects expression
example = [
  {
    foo = "bar"
  },
  {
    foo = "baz"
  },
]

Because of the rule that argument declarations like this fully override any default value, when creating a list-of-objects expression directly the usual handling of optional arguments does not apply, so all of the arguments must be assigned a value, even if it's an explicit null:

example = [
  {
    # Cannot omit foo in this case, even though it would be optional in the
    # nested block syntax.
    foo = null
  },
]

If you are writing a reusable module that allows callers to pass in a list of objects to assign to such an argument, you may wish to use the merge function to populate any attributes the user didn't explicitly set, in order to give the module user the effect of optional arguments:

example = [
  for ex in var.examples: merge({
    foo = null # (or any other suitable default value)
  }, ex)
]

For the arguments that use the attributes-as-blocks usage mode, the above is a better pattern than using dynamic blocks because the case where the caller provides an empty list will result in explicitly assigning an empty list value, rather than assigning no value at all and thus retaining and ignoring any existing objects. dynamic blocks are required for dynamically-generating normal nested blocks, though.

» In JSON syntax

Arguments that use this special mode are specified in JSON syntax always using the JSON expression mapping to produce a list of objects.

The interpretation of these values in JSON syntax is, therefore, equivalent to that described under Arbitrary Expressions with Argument Syntax above, but expressed in JSON syntax instead.

Due to the ambiguity of the JSON syntax, there is no way to distinguish based on the input alone between argument and nested block usage, so the JSON syntax cannot support the nested block processing mode for these arguments. This is, unfortunately, one necessary concession on the equivalence between native syntax and JSON syntax made pragmatically for compatibility with existing provider design patterns. Providers may phase out such patterns in future major releases.