What Is New in Terraform 1.2
Terraform 1.2 introduces several key enhancements focused on improving the module development experience and providing finer control over data handling. The most significant additions are the replace_triggered_by lifecycle argument and the new moved configuration block.
| Category | Key Changes |
|---|---|
| New Features | moved block, replace_triggered_by lifecycle, optional object attributes |
| Enhancements | Cloud integration improvements, extended functions (startswith, endswith), terraform_data managed resource |
| Deprecations | terraform 0.11 and earlier compatibility, certain provisioner behaviors |
How does the moved block simplify refactoring?
The new moved block is a declarative way to record resource or module instance address changes in your configuration. This is a game-changer for refactoring because it tells Terraform to treat the new address as if it were always there, preserving the existing object in state instead of destroying and recreating it.
In practice, you add a moved block to your configuration to explain where a resource used to live. This is far cleaner than using the terraform state mv command, as the intent is now documented directly in the code for your whole team to see.
moved {
from = aws_instance.old_name
to = aws_instance.new_name
}
What can I control with replace_triggered_by?
The replace_triggered_by lifecycle argument lets you force a resource to be replaced based on changes in other parts of your infrastructure. You can point it to a list of references from attributes of other resources, a resource's self attributes, or even a list of instance addresses.
This is useful for managing implicit dependencies that Terraform can't automatically detect. For example, you can force an EC2 instance to be replaced whenever its underlying AMI is updated, even if the AMI ID is fetched dynamically by a data source.
lifecycle {
replace_triggered_by = [
data.aws_ami.example.id
]
}
How do optional object attributes improve modules?
Optional attributes in object type constraints make module input variable schemas much more flexible. You can now define an object type where certain attributes are optional instead of required, allowing users to omit them when calling your module.
This matters because it removes the need to specify dummy values for attributes you don't care about. It makes modules cleaner to use and more closely mirrors the optional arguments you find in resource blocks.
variable "example" {
type = object({
name = string
enabled = optional(bool, true)
tags = optional(map(string))
})
}
What cloud integrations were enhanced?
Terraform 1.2 includes deeper integration with Terraform Cloud, particularly for the CLI-driven run workflow. The authentication process for terraform login was improved to support automatically generating a credentials token when using TFC as a host.
These updates streamline the developer experience when working in a CI/CD pipeline or any automated environment where manual token handling is cumbersome. It's about reducing friction for teams that use Terraform Cloud as their collaboration backend.
FAQ
Does the moved block actually run a terraform state mv command?
No. The moved block is a configuration-time declaration, not a command. When you run terraform plan, it analyzes these blocks and internally updates the state file to reflect the new address before creating the execution plan.
Can I use replace_triggered_by with any expression?
No. The argument only accepts references to specific objects: resources, data sources, or a list of instance addresses. You cannot use arbitrary expressions, function calls, or literal values, as it needs to track specific dependencies.
What happens if I use both a moved block and terraform state mv?
You should generally choose one method. The moved block is the preferred, declarative approach. If you use both, Terraform will see the state was already moved by the command and the block may become a no-op, but it's best to avoid the confusion.
Are there any new functions besides startswith and endswith?
Not in this release. The main function additions are startswith and endswith, which provide convenient string prefix and suffix checks that were previously missing from the interpolation language.
Is the terraform_data resource a replacement for null_resource?
It serves a similar purpose but is integrated into the core lifecycle. terraform_data is a managed resource that stores a value, useful for triggering provisioners on other resources without the need for the null_resource community anti-pattern.