Refactoring: moved, import and removed

Terraform · Advanced · 12 min

Codebases evolve; without these tools every refactor or adoption risks destroying production resources.

Resource addresses are identities. Change one (rename, move into a module, switch from count to for_each) and Terraform sees a destroy plus a create, unless you tell it otherwise.

moved blocks record a rename in code:

moved { from = aws_s3_bucket.logs to = module.logs.aws_s3_bucket.this }

The next plan shows "has moved to" instead of destroy/create. Keep moved blocks for a while, as other environments may not have applied them yet. They are reviewable, unlike the older terraform state mv command.

import blocks adopt existing objects (made in the console or by another tool):

import { to = aws_s3_bucket.legacy id = "acme-legacy-bucket" }

Plan shows what will be imported and any differences from your configuration. terraform plan -generate-config-out=generated.tf even writes a starting configuration for you to clean up. for_each in import blocks adopts many at once. The ID format depends on the resource type; see each resource's Import section in the provider docs.

removed blocks take a resource out of state without destroying it, when another configuration should own it, or it should just be left alone:

removed { from = aws_instance.old lifecycle { destroy = false } }

All three are applied as part of a normal plan/apply, so they get the same review as any other change.

Example

# 1. Renamed a resource and moved it into a module
moved {
  from = aws_s3_bucket.logs
  to   = module.logs.aws_s3_bucket.this
}

# 2. Adopt a bucket created by hand
import {
  to = aws_s3_bucket.legacy
  id = "acme-legacy-bucket"
}
resource "aws_s3_bucket" "legacy" { bucket = "acme-legacy-bucket" }

# 3. Hand an instance over to another team's config without deleting it
removed {
  from = aws_instance.reporting
  lifecycle { destroy = false }
}
# terraform plan
#   # aws_s3_bucket.logs has moved to module.logs.aws_s3_bucket.this
#   # aws_s3_bucket.legacy will be imported
#   # aws_instance.reporting will no longer be managed by Terraform

Remember

  • moved: rename without recreate
  • import (+ -generate-config-out): adopt existing objects
  • removed with destroy = false: stop managing without deleting

Watch out for

  • Renaming a resource without a moved block and approving a plan that destroys it.

Check yourself

  1. You switch a resource from count to for_each. How do you avoid recreating the existing instance aws_iam_user.u[0] (user ana)?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: developer.hashicorp.com/terraform/language/modules/develop/refactoring