Skip to main content

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

  1. Update the resource name in your configuration files
  2. Run terraform plan to see the changes
  3. If the plan shows destruction and recreation, proceed with caution
  4. Use terraform state mv to move the resource in state:
terraform state mv aws_instance.old_web_server aws_instance.new_web_server
  1. Re-run terraform plan to confirm no changes are expected
  2. 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

  1. Update module references in configuration files
  2. Run terraform plan to verify changes
  3. 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

Example: Moving a resource

terraform state mv 'aws_instance.old_name' 'aws_instance.new_name'

Example: Removing a resource from state

terraform state rm 'aws_s3_bucket.example'
Note: State commands should be used with caution as they can cause drift between the actual infrastructure and Terraform's state.

10. Validating Refactoring

Example: Validate refactored configuration

terraform validate
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.

Example: Validate with specific configuration directory

terraform validate -config-dir=/path/to/refactored/config

Comparison: Validate vs Plan

  • validate: Checks syntax and configuration correctness
  • plan: Checks configuration and computes execution plan

Was this article helpful?