Home / AWS

AWSHow-toIntermediate

Transformation: bring console-built AWS resources under Terraform or CloudFormation

Adopt existing AWS resources into Infrastructure as Code without recreating them. Use Terraform import blocks with generated config, CloudFormation resource import or IaC generator, reach a no-change plan and add drift detection.

Deepak Bhardwaj2 min read

Applies toTerraform 1.5+ with the AWS provider, AWS CloudFormation (resource import and IaC generator), AWS CLI v2

Goal

Everything in production is described in code, changes go through pull requests, and the code matches reality. You get there by importing, not rebuilding.

1. Pick a scope

Start with one application or one shared layer (VPC, IAM baseline). List what's there:

aws resourcegroupstaggingapi get-resources --tag-filters Key=app,Values=orders \
  --query "ResourceTagMappingList[].ResourceARN" --output text | tr '\t' '\n'

Untagged resources won't show up — use AWS Config's resource inventory or service-specific describe calls.

2. Terraform: import blocks and generated config

import {
  to = aws_s3_bucket.invoices
  id = "prod-invoices"
}

import {
  to = aws_security_group.web
  id = "sg-0123456789abcdef0"
}
terraform plan -generate-config-out=generated.tf
terraform apply     # imports into state only
terraform plan      # goal: "No changes."

Then refactor: move generated resources into modules (use moved blocks so state follows), replace literals with variables and references.

Caution

If a plan shows must be replaced on a production resource, stop. Your code differs from reality on an immutable attribute; fix the code, never apply it.

3. CloudFormation: IaC generator and resource import

The CloudFormation IaC generator scans the account and builds a template from selected resources, which you can then import into a stack. Alternatively, import into a new or existing stack with a template you write:

aws cloudformation create-change-set --stack-name orders-data --change-set-name import-1 \
  --change-set-type IMPORT --template-body file://orders-data.yaml \
  --resources-to-import '[{"ResourceType":"AWS::S3::Bucket","LogicalResourceId":"InvoicesBucket","ResourceIdentifier":{"BucketName":"prod-invoices"}}]'
aws cloudformation execute-change-set --stack-name orders-data --change-set-name import-1

Imported resources need DeletionPolicy set in the template — use Retain for data stores.

4. Pipeline and state

  • Terraform state in S3 with versioning, encryption and locking (S3 native locking in recent Terraform versions, or DynamoDB).
  • Plan on pull request, apply on merge, manual approval for production.
  • Pipeline authenticates with OIDC to a deploy role, not access keys.

5. Detect and prevent drift

aws cloudformation detect-stack-drift --stack-name orders-data
aws cloudformation describe-stack-resource-drifts --stack-name orders-data --stack-resource-drift-status-filters MODIFIED DELETED

For Terraform, run terraform plan -detailed-exitcode nightly (exit code 2 = drift) and alert.

Then reduce console write access in production accounts: read-only permission sets for people, changes through the pipeline role.

Checklist

  • Scope inventoried, including untagged resources
  • Imported with a no-change plan / successful import change set
  • Data stores protected (prevent_destroy, DeletionPolicy: Retain)
  • Remote state secured; pipeline with OIDC
  • Nightly drift detection; console write access reduced