April 18, 2019·4 min read·engineeringterraformstatehcl

Terraform beta 2 makes state addresses more specific

Terraform 0.12.0-beta2 aligns state commands with resource addressing elsewhere and preserves state when a provider reports a failed deletion.

An address that identifies a resource in the root module should not also identify similarly named resources in every descendant module. Terraform's state commands have not consistently honored that distinction. The 0.12.0-beta2 release on April 18 changes their matching behavior to agree with resource addressing elsewhere in Terraform.

This is a practical part of the language upgrade. A richer expression system needs a consistent account of which object an address denotes. Administrative commands also need that account, because a mistaken match can change the relationship between configuration and existing infrastructure. The release notes explicitly warn that existing automation may now match fewer objects.

I regard predictable resource identity as a requirement for Terraform's reusable abstractions. The same resource name can appear inside several module instances without those resources becoming interchangeable. State operations need to preserve that distinction as carefully as configuration evaluation does.

Removing an older filter

Martin Atkins explains the implementation in pull request 20719. Earlier refactoring adapted state commands to the new address and state types but retained part of the older StateFilter abstraction. Other Terraform behavior no longer used that abstraction. Its adaptation was incomplete, leaving bugs that the existing tests did not cover.

The fix removes the old filter and introduces functions tailored to the state subcommands. One corrected behavior concerns aws_instance.foo. Previously it could match resources with that type and name in descendant modules as well as the root module. The pull request identifies this as a preexisting 0.11 problem, so it is not simply a regression introduced by the beta.

Under the corrected interpretation, the unqualified resource address identifies the root module's resource. A child module resource needs its module path. This allows the command's address interpretation to agree with the place the resource occupies in configuration.

The distinction can be inspected through the beta's dry-run support. These illustrative commands select different addresses and assume that both exist in the state being inspected.

terraform state rm -dry-run aws_instance.foo
terraform state rm -dry-run module.web.aws_instance.foo

The release recommends using dry runs when verifying existing state automation. Their purpose here is to inspect the selection without performing the removal. A script that previously depended on the broader match may need to enumerate the intended module addresses explicitly.

Moving resources and instances

The same pull request repairs state mv handling of whole resource addresses and indexed instance addresses. The new internal representation distinguishes these categories, while the older logic did not need the same distinction. That difference caused moves between a single resource and an indexed address to fail in cases the older command could handle.

The patch allows a resource without an instance collection to be treated as an instance for this operation. This supports moving the state association when configuration changes between an uncounted resource and an explicitly indexed instance. It is a compatibility rule for state movement, not a claim that every resource address means an individual instance in every expression.

The implementation also strengthens protection against moving between resource types or resource modes. These checks matter because changing a state address does not convert the remote object into another kind of object. A resource type defines how a provider interprets its state and operates on its corresponding API resource.

The versioned state removal documentation makes the underlying effect clear. Removing an item from state does not physically destroy it. Terraform stops tracking that object through the removed association. That is why address selection matters even though this administrative command is not itself a provider deletion request.

Failed deletion must retain an object

Another state correction in beta 2 concerns an operation that really does ask a provider to delete something. In pull request 21033, Atkins explains that providers may return a null new value when an operation fails. Terraform had retained the old value for other failed operations but made an exception for deletion.

That exception could remove Terraform's record even when the provider returned an error and the remote object might still exist. The patch retains the previous object when deletion returns null alongside errors. A subsequent run can refresh it to determine what happened.

This does not mean Terraform ignores every result from a failed delete. The explanation allows a provider to return a partial new object when part of the operation completed. In that case Terraform can use the returned state. The corrected default applies when the provider supplies no replacement value and reports failure.

There is an important difference between an explicit state removal and an unsuccessful provider deletion. The first is an operator's instruction to stop tracking an address. The second supplies insufficient evidence that the object disappeared. Preserving the old state in the second case keeps information available for reconciliation.

Consistency across the configuration boundary

I find these fixes more consequential than the beta label alone suggests. Terraform has to interpret identity consistently in configuration expressions, administrative commands, and the results returned by a provider. An improvement in one interpreter does not automatically correct the others.

Beta 2 also changes coalesce to skip null values and fixes its handling of empty strings, according to the release notes. That is another example of making the richer value model behave consistently, although it affects expression evaluation instead of resource identity. The state changes address a different part of the same upgrade effort.

Existing automation now has a specific compatibility question to answer. A root module address selects the root module resource, and module paths identify resources below it. When a deletion fails without a new state value, Terraform retains its previous record so that the next refresh can resolve the object's actual condition.