Terraform 27: Refactoring safely with state mv, import, and moved
easy⏱ 5 mincourseterraform
moved blocks: rename in-place, versioned
A moved block tells Terraform "the resource formerly at address A is now at address B — update state, don't recreate." It lives in your .tf, so the rename is code-reviewed and applied like any other change. Prefer it over the imperative CLI because it's declarative and reproducible for everyone who runs apply.
# You renamed the resource from "old" to "main" in config.
resource "azurerm_storage_account" "main" {
name = "myorgordersdev"
resource_group_name = "myorg-orders-dev-rg"
location = "westus2"
account_tier = "Standard"
account_replication_type = "LRS"
}
# This block updates state instead of destroy+create.
moved {
from = azurerm_storage_account.old
to = azurerm_storage_account.main
}
import blocks: adopt existing resources
When a resource already exists in Azure but not in state (created by hand or another tool), an import block (Terraform 1.5+) brings it under management. You give the target address and the cloud id; terraform plan shows what will be imported and apply records it in state — no destroy, no recreate. It's the declarative successor to the old terraform import command.
# Adopt a resource group that already exists in Azure.
import {
to = azurerm_resource_group.orders
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myorg-orders-dev-rg"
}
resource "azurerm_resource_group" "orders" {
name = "myorg-orders-dev-rg"
location = "westus2"
}
terraform state mv is the imperative escape hatch
Before moved blocks existed, you renamed with the CLI: terraform state mv <old> <new>. It's still handy for one-off surgery (e.g. moving a resource into a module: terraform state mv azurerm_storage_account.main module.storage.azurerm_storage_account.main). But it mutates state immediately with no PR trail — reach for moved blocks first, and always run terraform plan afterward to confirm zero destroys.
Emit a moved block
Implement movedBlock(from, to) so it returns the HCL text of a moved block. For movedBlock("azurerm_storage_account.old", "azurerm_storage_account.main") the output must contain moved {, a from = line with the old address, and a to line with the new address. The starter logs the call.