Refactoring Terraform Configuration
1. Renaming Resources
| Resource Type | Old Address | New Address | Notes |
|---|---|---|---|
| aws_instance | aws_instance.old_web_server |
aws_instance.new_web_server |
Resource renamed in configuration |
| aws_s3_bucket | aws_s3_bucket.old_bucket |
aws_s3_bucket.new_bucket |
Resource renamed in configuration |
Example: Renaming a Resource in Configuration
resource "aws_instance" "old_web_server" {
ami = "ami-12345678"
instance_type = "t2.micro"
}
# After renaming
resource "aws_instance" "new_web_server" {
ami = "ami-12345678"
instance_type = "t2.micro"
}
Note: When renaming a resource, Terraform will detect it as a new resource and destroy the old one unless you use
terraform state mv to move the resource in state.
Warning: Renaming a resource without updating state will result in data loss.
Workflow: Renaming a Resource Safely
- Update the resource name in your configuration files
- Run
terraform planto see the changes - If the plan shows destruction and recreation, proceed with caution
- Use
terraform state mvto move the resource in state:
terraform state mv aws_instance.old_web_server aws_instance.new_web_server
- Re-run
terraform planto confirm no changes are expected - Apply the configuration with
terraform apply
2. Moving Resources Between Modules
| Resource | Old Module Path | New Module Path | Action |
|---|---|---|---|
| aws_instance.web | module.old_module.aws_instance.web |
module.new_module.aws_instance.web |
Move resource to new module |
Example: Moving a Resource
resource "aws_instance" "web" {
ami = "ami-12345678"
instance_type = "t2.micro"
tags = {
Name = "web-server"
}
}
Workflow: Migrating Resource to New Module
- Update module references in configuration files
- Run
terraform planto verify changes - Apply changes with
terraform apply
Note: Ensure all dependencies are updated in the new module to avoid breaking changes.
3. Splitting Modules
| Module | Description | Usage |
|---|---|---|
| module "name" | Defines a child module | module "vpc" { source = "./modules/vpc" } |
| source | Location of the module | source = "terraform-aws-modules/vpc/aws" |
| version | Version constraint for module | version = "~> 3.0" |
Example: Splitting Resources into Modules
module "web_server" {
source = "./modules/web-server"
instance_type = "t3.micro"
count = 2
}
Note: When splitting modules, ensure that dependencies between resources are properly handled using
depends_on or by passing outputs as inputs.
Best Practices for Module Splitting
- Keep modules focused on a single responsibility
- Use versioned modules for stability
- Pass inputs explicitly to avoid tight coupling
4. Consolidating Resources
| Resource Type | Description | Notes |
|---|---|---|
| aws_instance | Represents a single EC2 instance in AWS | Can be consolidated into a single resource block with multiple instances |
| aws_s3_bucket | Represents an S3 bucket in AWS | Multiple buckets can be defined within a single resource block using for_each |
| google_compute_instance | Represents a Compute Engine instance in GCP | Consolidation can be achieved using count or for_each |
Example: Consolidating S3 Buckets with for_each
locals {
buckets = {
"bucket1" = { name = "my-bucket-1", region = "us-west-2" }
"bucket2" = { name = "my-bucket-2", region = "us-east-1" }
}
}
resource "aws_s3_bucket" "buckets" {
for_each = local.buckets
bucket = each.value.name
region = each.value.region
}
Example: Consolidating EC2 Instances with count
locals {
instance_count = 3
instance_type = "t3.micro"
}
resource "aws_instance" "consolidated_instances" {
count = local.instance_count
ami = "ami-0c55b159cbfafe1d0"
instance_type = local.instance_type
tags = {
Name = "instance-${count.index + 1}"
}
}
Note: When consolidating resources, ensure that the new configuration maintains the same state and dependencies as the original setup.
5. Removing Unused Resources
| Resource Type | Description | Action Required |
|---|---|---|
| aws_instance | EC2 instance resource | Remove from config and run terraform destroy |
| aws_s3_bucket | S3 bucket resource | Remove from config and run terraform destroy |
| null_resource | Null resource for triggers | Remove from config and run terraform destroy |
Example: Removing an unused S3 bucket
# Before
resource "aws_s3_bucket" "old_bucket" {
bucket = "my-old-bucket"
}
# After
# Resource removed from config
Note: Always verify that the resource is no longer referenced in any other configurations or scripts before removing it.
Warning: Removing resources will delete the actual infrastructure. Ensure backups or exports are created if needed.
6. Updating Resource Addresses
| Resource Type | Old Address Format | New Address Format | Notes |
|---|---|---|---|
| aws_instance | aws_instance.web |
aws_instance.web |
Same resource type and name |
| aws_s3_bucket | aws_s3_bucket.mybucket |
aws_s3_bucket.mybucket |
Same resource type and name |
| null_resource | null_resource.test |
null_resource.test |
Same resource type and name |
Example: Updating Resource Address in State
terraform state mv aws_instance.old_name aws_instance.new_name
Example: Updating Resource Address in Configuration
resource "aws_instance" "new_name" {
ami = "ami-12345678"
instance_type = "t2.micro"
}
Note: When updating resource addresses, ensure that any references to the old address in other resources or data sources are also updated.
Warning: Changing resource addresses can cause Terraform to recreate resources if not handled carefully. Always backup your state before making changes.
7. Migrating from Count to For-Each
| Aspect | Count | For-Each |
|---|---|---|
| Resource Declaration | count = 3 |
for_each = var.instances |
| Accessing Attributes | aws_instance.example[count.index] |
aws_instance.example[key] |
| Resource Name | aws_instance.example.0 |
aws_instance.example["instance1"] |
| Indexing | Implicit indexing via count.index |
No implicit index; keys are explicit |
| State Management | Recreates resources if count changes | Updates resources without recreation |
Example: Migrating from Count to For-Each
// Before: Using count
resource "aws_instance" "web" {
count = 3
ami = "ami-12345678"
instance_type = "t2.micro"
tags = {
Name = "web-${count.index}"
}
}
// After: Using for_each
variable "instances" {
default = ["web1", "web2", "web3"]
}
resource "aws_instance" "web" {
for_each = var.instances
ami = "ami-12345678"
instance_type = "t2.micro"
tags = {
Name = "web-${each.key}"
}
}
Note: When migrating, ensure that the
for_each variable is a map or set of strings. If you're using a list with count, convert it to a map or set.
Warning: Changing from
count to for_each may change the resource identifiers in state, which can lead to recreation of resources if not handled carefully.
8. Replacing Deprecated Features
| Feature | Replacement | Notes |
|---|---|---|
| data "aws_ami" | data "aws_ami_ids" |
Use aws_ami_ids for listing AMIs by filters |
| provider "aws" | provider "aws" with region |
Deprecated region in provider block is now a top-level attribute |
| resource "aws_instance" | resource "aws_ec2_instance" |
Use aws_ec2_instance for new instances (deprecated aws_instance) |
| module "example" | module "example" with source |
Ensure all modules use the source attribute for clarity |
Example: Replacing data "aws_ami"
data "aws_ami_ids" "ubuntu" {
owners = ["099720109473"]
filters {
name = "ubuntu/images/ubuntu-focal-20.04-amd64-server-*"
values = ["*hvm-ssd*"]
}
}
Note: Always check the official AWS provider documentation for the latest deprecated features and their replacements.
9. Using State Commands
| Command | Description | Notes |
|---|---|---|
| terraform state list | Lists all resources in the state file | Useful for auditing and verifying resource presence |
| terraform state show | Shows details of a specific resource | Can be used with resource address or ID |
| terraform state mv | Moves a resource from one address to another | Useful for refactoring and renaming resources |
| terraform state rm | Removes a resource from the state file | Does not destroy the actual resource in the cloud |
| terraform state pull | Downloads the current state file locally | Useful for backup or manual inspection |
| terraform state push | Uploads a local state file to the remote backend | Useful for restoring from backup or manual changes |
Note: State commands should be used with caution as they can cause drift between the actual infrastructure and Terraform's state.
10. Validating Refactoring
| Command | Description | Notes |
|---|---|---|
| terraform validate | Checks that a configuration is valid | Runs without applying changes |
Note: Use
terraform validate after refactoring to ensure no syntax or semantic errors were introduced.