openlang

for_each keys are addresses, not labels

A for_each key isn't a label. It's half of the resource address, terraform_data.queue["prod-orders"], and the state tracks the object under that address. Change the key and the plan doesn't see a rename. It sees one object to destroy and another to create.

So I get nervous when the keys come out of an expression.

locals {
  queues = { for q in var.queues : "${q.env}-${q.name}" => q }
}

resource "terraform_data" "queue" {
  for_each = local.queues
  input    = each.value
}

Looks harmless. Then someone decides the key reads better as name first, edits one line in locals, and the review diff is one line long. The plan is where the replacement of every instance shows up, and the plan is the part people skim. My preference is keys written out in the file, or taken straight from one field that never changes, with any clever reshaping kept in the values.

后续讨论 6

顺着彼此的依据读下去
  1. 固定链接

    Could the key list itself become part of what the plan prints? My hunch is that the review problem is less the expression and more that nobody ever looks at the keys as a set. An output holding keys(local.queues) would put the whole list under the output changes in the plan, and a reformatted key would show up there as a list where every element changed, which is hard to skim past. Not a proof that nothing gets replaced, just a cheap thing to try on one module.

  2. 固定链接

    It would show, and I still wouldn't lean on it. The plan already lists every destroy and every create by address, so the information was never missing. What's missing is a reviewer who reads it, and an output adds a second place not to read. If you want something that fails, count the delete actions in the plan JSON in CI and fail the job when there are any and nobody marked the change as intended.

  3. 固定链接

    The question underneath is which fields are the identity of a queue and which are its attributes. If env decides where the queue lives, it belongs in the key. If it is a tag that may change, putting it in the key turns an attribute edit into a replacement. An expression is not the problem by itself. An expression that mixes the two is. Would you accept a computed key if it read only fields the module documents as never changing?

  4. 固定链接

    Also, "${q.env}-${q.name}" is a regex person's favourite bad habit, a join on a separator that can appear inside the parts. env = "eu-west" with name = "orders" and env = "eu" with name = "west-orders" both come out as eu-west-orders. The good news is that a for expression refuses duplicate keys instead of quietly keeping the last one, so you find out at plan time. Pick a separator your names can't contain, or put a validation block on the variable that rejects it.

  5. 固定链接

    That one is the kind I like. It stops the plan with an error. The quiet case is the reformat that still produces unique keys, where nothing fails and every instance gets replaced. If the format really has to change, a moved block per instance maps the old address to the new one and the plan shows a move instead of a destroy. Writing forty of those by hand is tedious, and that is most of why I keep key formats boring.

  6. 固定链接

    Maybe the expression can stay and just lose the ability to make keys. Keep the map literal in the file, keys typed out by hand, and let the expression build only the values from it. Then a key change is a visible edit in the review diff, and the reshaping dario dislikes can still live in the values, where a change usually stays an update in place unless that attribute itself forces replacement.

同一个问题的其他角度

  1. Launching the consumer on a stopped event

    Got a question about a supervisor rule that treats a stopped process as a finished one, and a tiny repro makes the problem ob…

    回复 17最近活动 bashpython
  2. What does a --hash line actually pin?

    A requirements file with --hash on every line installs cleanly on my laptop and fails with a hash mismatch in a fresh Linux c…

    回复 8最近活动 python
  3. After the Kotlin DSL move, subprojects can no longer find implementation

    Moving an old multi-project build from the Groovy DSL to the Kotlin DSL. The root build.gradle had this block. The root I con…

    回复 2最近活动 kotlingroovy