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.
148 lines
3.8 KiB
Markdown
148 lines
3.8 KiB
Markdown
# Identity Churn
|
|
|
|
Use this guide when resource addresses or object identity can shift unexpectedly.
|
|
|
|
## Symptoms
|
|
|
|
- plan shows broad replace actions after small list edits
|
|
- renaming resources/modules triggers destroy/create
|
|
- refactor from `count` to `for_each` causes churn
|
|
- imported resources keep drifting because addressing is unstable
|
|
|
|
## Primary causes
|
|
|
|
- index-based identity (`count`) used for long-lived logical objects
|
|
- keys derived from unstable data (sorted lists, transient IDs)
|
|
- missing `moved` blocks during refactor
|
|
- hidden dependencies forcing replacement chains
|
|
- `for_each` keys derived from values unknown at plan time
|
|
|
|
## Prevention rules
|
|
|
|
- use `for_each` for long-lived identities
|
|
- choose stable keys from business identity (e.g., `zone-a`, `payments-api`)
|
|
- keep identity attributes separate from mutable attributes
|
|
- add `moved` blocks before first apply after rename/restructure
|
|
|
|
## Decision matrix: `count` vs `for_each`
|
|
|
|
Use `count` only when:
|
|
- resource is truly optional singleton (`0` or `1`)
|
|
- no downstream references depend on stable per-item addresses
|
|
|
|
Use `for_each` when:
|
|
- multiple logical instances are expected
|
|
- insertion/removal/reordering happens over time
|
|
- downstream references need stable keys
|
|
- keys are fully known during planning
|
|
|
|
If keys are unknown at plan time, `for_each` will fail planning. In that case:
|
|
- drive `for_each` from known input keys, or
|
|
- use `count` for conditional/singleton creation when key-stable `for_each` is not possible
|
|
|
|
## Safe migration playbook (`count` -> `for_each`)
|
|
|
|
1. define stable key map
|
|
2. refactor resource to `for_each`
|
|
3. add one `moved` block per old index
|
|
4. verify plan reports move operations, not replace
|
|
5. apply in lower environment first
|
|
|
|
Example:
|
|
|
|
```hcl
|
|
locals {
|
|
app_subnets = {
|
|
a = { cidr = "10.40.1.0/24", az = "us-east-1a" }
|
|
b = { cidr = "10.40.2.0/24", az = "us-east-1b" }
|
|
}
|
|
}
|
|
|
|
resource "aws_subnet" "app" {
|
|
for_each = local.app_subnets
|
|
vpc_id = aws_vpc.main.id
|
|
cidr_block = each.value.cidr
|
|
availability_zone = each.value.az
|
|
tags = {
|
|
Name = "app-${each.key}"
|
|
}
|
|
}
|
|
|
|
moved {
|
|
from = aws_subnet.app[0]
|
|
to = aws_subnet.app["a"]
|
|
}
|
|
|
|
moved {
|
|
from = aws_subnet.app[1]
|
|
to = aws_subnet.app["b"]
|
|
}
|
|
```
|
|
|
|
## Rename playbook
|
|
|
|
When renaming resource/module labels, add `moved` first:
|
|
|
|
```hcl
|
|
moved {
|
|
from = module.network_core
|
|
to = module.network_foundation
|
|
}
|
|
```
|
|
|
|
## LLM mistake checklist
|
|
|
|
Common model mistakes to correct:
|
|
- defaults to `count` for every collection
|
|
- omits `moved` blocks in refactors
|
|
- uses list index as identity key
|
|
- suggests `terraform state mv` in automation where `moved` is safer and reviewable
|
|
- builds `for_each` keys from computed IDs not known until apply
|
|
|
|
## Known-at-plan example (`for_each` failure pattern)
|
|
|
|
Bad (key depends on apply-time value):
|
|
|
|
```hcl
|
|
resource "aws_security_group_rule" "egress" {
|
|
for_each = toset([aws_security_group.ecs.id])
|
|
type = "egress"
|
|
from_port = 443
|
|
to_port = 443
|
|
protocol = "tcp"
|
|
security_group_id = each.value
|
|
cidr_blocks = ["0.0.0.0/0"]
|
|
}
|
|
```
|
|
|
|
Safer fallback for optional singleton behavior:
|
|
|
|
```hcl
|
|
resource "aws_security_group_rule" "egress" {
|
|
count = var.enable_egress_rule ? 1 : 0
|
|
type = "egress"
|
|
from_port = 443
|
|
to_port = 443
|
|
protocol = "tcp"
|
|
security_group_id = aws_security_group.ecs.id
|
|
cidr_blocks = ["0.0.0.0/0"]
|
|
}
|
|
```
|
|
|
|
## Verification commands
|
|
|
|
```bash
|
|
terraform fmt -check
|
|
terraform validate
|
|
terraform plan -out=plan.bin
|
|
terraform show plan.bin | grep -i moved
|
|
```
|
|
|
|
OpenTofu equivalent:
|
|
|
|
```bash
|
|
tofu fmt -check
|
|
tofu validate
|
|
tofu plan -out=plan.bin
|
|
tofu show plan.bin | grep -i moved
|
|
```
|