Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
stop-slop, taste-skill, terrashark had embedded .git dirs causing Woodpecker clone to fail on submodule update.
117 lines
2.7 KiB
Markdown
117 lines
2.7 KiB
Markdown
# Terraform Migration Playbooks
|
|
|
|
Five dedicated playbooks for safely migrating Terraform resources without causing identity churn, data loss, or downtime.
|
|
|
|
## Playbook 1: `count` to `for_each` Migration
|
|
|
|
**Goal:** Keep object identity stable during refactor.
|
|
|
|
### Steps
|
|
|
|
1. Define stable keys (not list indexes)
|
|
2. Add `for_each` implementation
|
|
3. Add `moved` mappings from old index addresses to new keyed addresses
|
|
4. Run plan and confirm move operations (not destroy/create)
|
|
5. Apply in low-risk environment first
|
|
|
|
### Example Mapping
|
|
|
|
```javascript
|
|
moved {
|
|
from = aws_subnet.app[0]
|
|
to = aws_subnet.app["a"]
|
|
}
|
|
|
|
moved {
|
|
from = aws_subnet.app[1]
|
|
to = aws_subnet.app["b"]
|
|
}
|
|
```
|
|
|
|
## Playbook 2: Resource/Module Rename
|
|
|
|
Use `moved` for address renames before any apply.
|
|
|
|
```javascript
|
|
moved {
|
|
from = module.edge_cache
|
|
to = module.cdn_edge
|
|
}
|
|
```
|
|
|
|
## Playbook 3: Import-First Adoption
|
|
|
|
When taking over manually created resources:
|
|
|
|
1. Confirm remote object exactly matches intended config shape
|
|
2. Import into correct address
|
|
3. Run plan and ensure no surprise replacements
|
|
|
|
### Declarative `import` Block (TF 1.5+ / OpenTofu 1.5+)
|
|
|
|
Prefer declarative `import` blocks over CLI `terraform import`:
|
|
|
|
```javascript
|
|
import {
|
|
to = aws_s3_bucket.logs
|
|
id = "my-existing-bucket-name"
|
|
}
|
|
|
|
resource "aws_s3_bucket" "logs" {
|
|
bucket = "my-existing-bucket-name"
|
|
}
|
|
```
|
|
|
|
### Bulk Import with `for_each`
|
|
|
|
```javascript
|
|
locals {
|
|
existing_buckets = {
|
|
logs = "prod-logs-bucket"
|
|
archive = "prod-archive-bucket"
|
|
}
|
|
}
|
|
|
|
import {
|
|
for_each = local.existing_buckets
|
|
to = aws_s3_bucket.managed[each.key]
|
|
id = each.value
|
|
}
|
|
|
|
resource "aws_s3_bucket" "managed" {
|
|
for_each = local.existing_buckets
|
|
bucket = each.value
|
|
}
|
|
```
|
|
|
|
### Post-Import Checklist
|
|
|
|
- Run `terraform plan` and verify zero changes (no-diff)
|
|
- If plan shows changes, align config with actual state before applying
|
|
- Remove `import` blocks after successful apply (they are one-time directives)
|
|
|
|
## Playbook 4: Secrets Remediation
|
|
|
|
If secrets are currently in state:
|
|
|
|
1. Create new secret path in managed secret store
|
|
2. Switch resources to reference external secret material
|
|
3. Rotate credentials after cutover
|
|
4. Remove old secret-generating Terraform resources where possible
|
|
|
|
## Playbook 5: Runtime/Provider Upgrade Flow
|
|
|
|
1. Bump constraints intentionally
|
|
2. Regenerate lockfile
|
|
3. Run full test tier for target risk
|
|
4. Inspect deprecations and behavior shifts
|
|
5. Ship upgrade independently from functional changes when possible
|
|
|
|
## Migration Red Flags
|
|
|
|
Watch for these warning signs during any migration:
|
|
|
|
- Plan shows broad replace for unrelated resources
|
|
- Key changes derived from unstable list order
|
|
- Unknown ownership of imported resources
|
|
- No rollback narrative for production apply
|