
HashiCorp has released Terraform 1.16, and it fills two gaps that many infrastructure teams have worked around for years. Actions can now run when a resource is destroyed, not just when it is created or updated. Import blocks can now sit inside child modules, so a module can bring existing infrastructure under management on its own. The release also adds smaller CLI improvements that make Terraform easier to script and document. Here is what changed and how to adopt it safely.
Destroy-time actions
Actions let a provider run an operation that is not itself a managed resource, such as triggering a backup or calling an external system. Until now there was no clean way to run that kind of step when a resource was being removed. Terraform 1.16 adds two new events to the action_trigger block in a resource’s lifecycle: before_destroy and after_destroy.
action "example_cleanup" "archive" {
config {
resource_id = caller.id
}
}
resource "example_service" "payments" {
name = "payments"
lifecycle {
action_trigger {
events = [before_destroy]
actions = [action.example_cleanup.archive]
}
}
}
HashiCorp suggests uses such as taking a final database backup before deletion, removing records from a CMDB, releasing reserved addresses and checking that backups exist. Until now, these steps usually lived in separate scripts or a runbook that someone had to remember to follow.
Rules to keep in mind
- The action’s configuration and trigger condition must be fully known at plan time.
- The trigger block must still be in your configuration when Terraform plans the destroy. If you delete the resource block and its trigger together, the action has nothing to run from.
- To retire a resource that has destroy actions, first set
count = 0and apply. Then remove the block in a later change. - You choose what happens when an action fails:
halt(the default),taintorcontinue. Usehaltfor steps such as a final backup, where it is safer to stop than to delete without one. - Actions come from providers. The provider you use must implement the action you want to call.
Import blocks in child modules
Before 1.16, import blocks had to live in the root module. To adopt existing infrastructure through a shared module, the caller had to know the module’s internal resource addresses and write imports against them. In 1.16, the module can contain the import itself, and the caller only supplies the identity of the existing object:
# modules/log-bucket/main.tf
variable "existing_bucket_name" {
type = string
}
resource "aws_s3_bucket" "this" {
bucket = var.existing_bucket_name
}
import {
to = aws_s3_bucket.this
id = var.existing_bucket_name
}
# root module
module "logs" {
source = "./modules/log-bucket"
existing_bucket_name = "platform-audit-logs"
}
Terraform evaluates the import separately for each module instance. That includes nested modules and module calls that use count or for_each. For teams bringing hand-built AWS resources into Terraform, this means one well-tested module can adopt dozens of existing buckets, security groups or DNS zones without separate import code in every root configuration.
Smaller improvements
terraform state show -jsonandterraform workspace list -jsonnow give machine-readable output, which is useful in CI pipelines and audit scripts.terraform graph -format=mermaidoutputs a Mermaid diagram that you can paste into Markdown documentation or a wiki.- The CLI now shows a summary of HCP Terraform policy evaluation results.
- Prebuilt binaries are now available for Linux on
s390x.
Upgrading to Terraform 1.16
Terraform minor releases are usually smooth, but treat the upgrade as a change in its own right and do not combine it with infrastructure changes.
- Read the changelog and upgrade guide for 1.16 before you start, especially if you use HCP Terraform or custom providers.
- Back up remote state, or confirm that your backend keeps versions, as S3 does with bucket versioning enabled.
- Upgrade the CLI in CI first. If you installed it from the HashiCorp package repository, use your normal package manager:
# RHEL, Rocky Linux, AlmaLinux sudo dnf upgrade terraform # Debian, Ubuntu sudo apt update && sudo apt install --only-upgrade terraform terraform version - Re-initialise and plan each workspace. A plan with no changes is the result you want before using any new features:
terraform init -upgrade terraform plan - Pin the version once you depend on the new features, so older CLIs fail early instead of misreading your configuration:
terraform { required_version = ">= 1.16.0" } - Adopt features one at a time. Try destroy-time actions first on a non-production resource, and check that a failing action does what your chosen failure mode says it will.
How TechProvidence can help
TechProvidence builds and maintains Terraform code for AWS environments. That includes bringing manually created resources under code, refactoring root configurations into reusable modules, setting up CI pipelines with plan review, and running Terraform upgrades without surprise changes. If you want help adopting 1.16 or getting your existing cloud setup into code, contact us.
Source: HashiCorp Blog


