May 25, 2017·4 min read·engineeringterraformconfigurationtypes

Terraform removes redundant brackets around splat expressions

Terraform 0.9.6 relaxes a list syntax convention inherited from string-based interpolation. The change exposes the distinction between a collection's type and how much Terraform knows about its members.

Terraform's list syntax carries some history that becomes visible when a value is not yet known. A splat expression collects an attribute across resource instances. Before Terraform 0.9.6, assigning that expression to a resource attribute required surrounding list brackets even though the expression already represented a list. The May 25 release notes remove that requirement and recommend dropping the brackets.

This is more than a shorter spelling. The implementation explanation in pull request 14737 traces the convention to Terraform versions in which lists were strings containing a special delimiter. Brackets told the interpolator to split the string. First-class lists arrived in 0.7, but compatibility behavior preserved the older notation.

I value a reusable configuration language over infrastructure APIs. Collection handling is part of that language's usefulness because modules and resources exchange lists constantly. Syntax inherited from an older representation becomes difficult to defend when it changes how an unknown value behaves.

A compatibility rule became a type problem

When Terraform gained first-class lists, it preserved the bracket convention by flattening lists of lists by one level. That allowed an expression returning a list to sit inside another list without necessarily producing the nested collection that its spelling suggested. According to the pull request, this compatibility handling missed the case where the inner list was unknown.

A subsequent change stopped collapsing partially unknown lists into completely unknown untyped values. That exposed the missed case. Resource arguments still benefited from normalization in helper/schema, but other interpolation contexts, including module arguments, did not have the same correction.

The distinction is between a value's structure and its contents. A list may have useful structural information even when some of its members depend on resources that have not been created. Losing that structure can prevent Terraform from using information it already has. Preserving it requires the type-handling code to make compatible assumptions throughout the implementation.

The author describes attempts to restore selective flattening as too risky because assumptions were spread across the core. The chosen fix removes the special requirement for splats instead. Other expressions that return lists already worked without the extra brackets, so splats can follow the same rule.

The expression supplies the list

The release notes use an assignment of the following form to explain the new recommendation. This is a schematic resource argument, with the resource names standing for objects declared elsewhere.

foo = "${some_resource.foo.*.baz}"

The splat supplies the collection of baz attributes. Adding brackets around the interpolation is no longer necessary to persuade the resource argument to accept that collection. For resource attributes, the older bracketed form continues to work through compatibility normalization.

That compatibility qualification matters. Terraform 0.9.6 does not require every configuration to change its syntax immediately. It recommends a form with fewer historical exceptions while continuing to accept the common older spelling for resource arguments.

The change also has limits for module boundaries. Pull request 14737 says a partially unknown list passed through a module argument still becomes fully unknown on the other side. Removing the brackets avoids the error exposed in 0.9.5, but it does not preserve every kind of partial knowledge across expressions.

Describing the release as complete support for partially known lists would therefore overstate it. The fix makes splat assignment more consistent and repairs a particular failure. The explanation explicitly leaves broader type-handling work unresolved.

Provider behavior can change independently of syntax

The same release includes a different compatibility issue for AWS Lightsail. Terraform previously configured Lightsail resources to use us-east-1 even if the AWS provider selected another region. Version 0.9.6 allows Lightsail resources to follow the provider's region.

For an existing configuration whose AWS provider targets another region, that is a change in where Terraform looks for the Lightsail resources. The release notes say users need a provider alias to retain those resources in us-east-1. The tagged provider documentation explains that an aliased provider is selected on a resource using TYPE.ALIAS.

This is a useful parallel to the splat change. Both remove a special behavior that made a familiar declaration mean something less obvious than it appeared. A provider region should describe the API region used by its resources, and a list-valued expression should not need an extra list wrapper merely because it is a splat.

I prefer these more consistent rules, while treating their compatibility paths as part of the interface. Existing infrastructure can depend on an old exception even when the new rule is easier to explain.

For collection arguments, the resulting recommendation is precise. Write the list-valued splat directly as the interpolated attribute value. Resource normalization preserves the older form for now, and module arguments still have the documented limits around unknown members. Those details explain both what becomes simpler in 0.9.6 and why the change does not amount to a complete redesign of Terraform's collection types.