Using Terraform Moved Block to Refactor Resources
Terraform moved block, introduced in version 1.1.0, provides a straightforward way to refactor resources by explicitly mapping old resource addresses to new ones.
Join the DZone community and get the full member experience.
Join For FreeTerraform introduced the moved
block in version 1.1.0. This block provides a straightforward way to refactor resources by explicitly mapping old resource addresses to new ones. It significantly reduces the risk of losing state or manually managing imports during renames or moves.
In this article, we’ll explain what the moved
block is and how to use it to streamline resource restructuring for smoother and safer Terraform updates.
What Is the Terraform Moved Block?
A Terraform moved
block is used within a module’s moved
section to declare the migration of a resource or data source from one address to another. It is helpful for refactoring Terraform code while ensuring that state information is preserved, preventing Terraform from destroying and recreating resources unnecessarily.
The moved
block also handles resource renaming or movement across modules, making state management seamless.
The syntax of the Terraform moved
block is as follows:
moved {
from = "<old_address>"
to = "<new_address>"
}
Where:
from
is the original address of the resource or module in the previous configurationto
is the new address of the resource or module in the updated configuration
When to use the Terraform Moved Block?
The moved
block in Terraform is used when you need to refactor or rename resources in your configuration without destroying and recreating them. It can be useful for:
- Renaming resources: When you change the name of a resource within the configuration (e.g.,
aws_instance.old_name
toaws_instance.new_name
). - Reorganizing modules: If a resource is moved between modules or changed from a root-level resource to a module-managed resource (or vice versa), you can use the
moved
block to keep Terraform aware of the transition. - Refactoring module names: When you are renaming or reorganizing modules to align with new naming conventions or standards.
- Changing resource block types: If you change the type of a resource block (e.g., from
google_compute_instance
togoogle_compute_engine
), themoved
block maps the old resource to the new type. Note that this is possible only if the configurations are compatible and the resource’s state remains valid. - Splitting or consolidating configurations: When you split a configuration into multiple files or modules or consolidate it into fewer files, use the
moved
block to reflect these changes without resource re-creation.
How to Use the Terraform Moved Block
Let’s consider some examples:
Example 1: Renaming a Resource
In this example, we will rename a Terraform resource from aws_instance.old_name
to aws_instance.new_name
.
# Old Configuration
# This block is no longer present
resource "aws_instance" "old_name" {
ami = "ami-12345678"
instance_type = "t2.micro"
}
# New Configuration
resource "aws_instance" "new_name" {
ami = "ami-12345678"
instance_type = "t2.micro"
}
# Moved Block
moved {
from = "aws_instance.old_name"
to = "aws_instance.new_name"
}
Here, the moved block tells Terraform that the resource aws_instance.old_name
has been renamed to aws_instance.new_name
.
Note: After making these changes, make sure you run terraform plan
to verify that Terraform recognizes the migration correctly and that no unnecessary changes will be applied.
Example 2: Moving Resources Between Modules
In the next example, a resource is moved from the root module to a nested module.
We want the aws_s3_bucket.my_bucket
resource to be moved from the root module to a module named storage
:
# Old Configuration (Root Module)
# This block is no longer present
resource "aws_s3_bucket" "my_bucket" {
bucket = "example-bucket"
}
# New Configuration (Nested Module)
# modules/storage/main.tf
resource "aws_s3_bucket" "my_bucket" {
bucket = "example-bucket"
}
# Root Module Configuration
module "storage" {
source = "./modules/storage"
}
# Moved Block
moved {
from = "aws_s3_bucket.my_bucket"
to = "module.storage.aws_s3_bucket.my_bucket"
}
The moved
block updates the Terraform state to reflect the new location without destroying and recreating the resource.
Example 3: Using Moved With for_each
We will now move a resource to a new name using a for_each
loop, which creates multiple resources dynamically.
# Old Configuration
# This block is no longer present
resource "aws_security_group" "old_group" {
for_each = toset(["web", "db"])
name = "sg-${each.key}"
}
# New Configuration
resource "aws_security_group" "new_group" {
for_each = toset(["web", "db"])
name = "sg-${each.key}"
}
# Moved Block
moved {
from = "aws_security_group.old_group[\"web\"]"
to = "aws_security_group.new_group[\"web\"]"
}
moved {
from = "aws_security_group.old_group[\"db\"]"
to = "aws_security_group.new_group[\"db\"]"
}
Each resource in the old_group
is mapped to a corresponding resource in new_group
using moved blocks. This ensures a smooth state migration for all instances of the resources without unnecessary recreation.
Example 4: Moving a Resource With a Changed Identifier
In this example, a resource’s identifier changes because the for_each
key expression is modified.
We want to rename the for_each
keys from ["app", "db"]
to ["frontend", "database"]
. Without the moved
block, Terraform would see these as new resources and destroy the old ones.
# Old Configuration
resource "aws_security_group" "sg" {
for_each = toset(["app", "db"])
name = "sg-${each.key}"
}
# New Configuration
resource "aws_security_group" "sg" {
for_each = toset(["frontend", "database"]) # Changed identifiers
name = "sg-${each.key}"
}
# Moved Blocks
moved {
from = "aws_security_group.sg[\"app\"]"
to = "aws_security_group.sg[\"frontend\"]"
}
moved {
from = "aws_security_group.sg[\"db\"]"
to = "aws_security_group.sg[\"database\"]"
}
Each moved
block maps the old key (app
or db
) to the new key (frontend
or database
).
When you run terraform plan
and terraform apply
, Terraform updates the state to match the new identifiers, avoiding unnecessary destruction and re-creation of the security groups.
Example 5: Moving a Resource Between Providers
If we want to migrate a resource from one provider to another (e.g., from aws
to aws.other_region
), the Terraform moved
block ensures a seamless state transition.
In our example, the S3 bucket resource is initially in the us-west-1
region using the default aws
provider. We want to manage the same bucket in a different AWS region (e.g., us-east-1
) using a different provider alias.
# Old Configuration
provider "aws" {
region = "us-west-1"
}
resource "aws_s3_bucket" "example_bucket" {
bucket = "example-bucket"
}
# New Configuration
provider "aws" {
region = "us-west-1"
}
provider "aws" {
alias = "other_region"
region = "us-east-1"
}
resource "aws_s3_bucket" "example_bucket" {
provider = aws.other_region
bucket = "example-bucket"
}
# Moved Block
moved {
from = "aws_s3_bucket.example_bucket"
to = "aws_s3_bucket.example_bucket"
}
The moved
block ensures Terraform updates the state to associate the resource with the new provider configuration (aws.other_region
). The bucket itself remains intact in AWS and is not recreated, avoiding downtime or data loss.
Terraform Moved Block Limitations
The moved
block in Terraform is useful for handling resource renaming or moving across modules in a safe and structured manner, but it has some limitations:
- Manual specification. The
moved
block requires you to manually specify both the source and destination addresses, which can lead to human error if the information is not provided accurately. - Resource renames only. The
moved
block is specifically designed for renaming or moving resources within the state. Splitting or restructuring state might require manual state modifications or the use of tools liketerraform state mv
. - Static declaration. The
moved
block is static and does not support conditional logic. It cannot handle scenarios in which the movement depends on dynamic conditions. - Limited to planned changes. The
moved
block is only applied during theterraform plan
andapply
commands. If you modify the state file manually, themoved
block will not help reconcile those changes. - Requires state compatibility. The source and destination must be part of the same state file. This is particularly important for users working with remote backends.
- Only supported in Terraform 1.1+. Older versions of Terraform do not recognize the
moved
block, making it incompatible with legacy configurations or environments.
Key Points
The moved
block in Terraform is a practical tool for restructuring resources without disrupting infrastructure. It ensures seamless state updates, avoids downtime, and simplifies complex refactoring tasks, allowing you to reorganize code confidently and maintain operational stability — provided the mappings are accurate and proper planning is in place.
Published at DZone with permission of Mariusz Michalowski. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments