Skip to content

fix(export): write a moved block when an exported resource is re-keyed - #200

Merged
2000game merged 2 commits into
mainfrom
fix/export-moved-on-rekey
Oct 16, 2026
Merged

2000game merged 2 commits into
mainfrom
fix/export-moved-on-rekey

Conversation

@2000game

Copy link
Copy Markdown
Member

Closes #199

What

When a resource keeps its id but gets a new ct key, ct export tf now compares each id with the label the id map recorded and writes a moved { from = <old>, to = <new> } block at the top of imports.tf. Before, the old address simply lost its config and tofu plan proposed destroying it (or failed on prevent_destroy).

Deviation from the issue: the import block stays

The issue suggested dropping the import block for a moved resource. That would plan a create (a duplicate) whenever the old address never reached tofu's state, e.g. an export that was never applied. Checked locally with OpenTofu 1.12.5 and random_id:

State Blocks tofu plan
old address in state moved + import moved, 0 to add, 0 to change, 0 to destroy
empty state moved + import 1 to import, 0 to add

So both blocks are written, and both cases come out right.

Limitation (documented)

The id map is the only record of the old label. A default export also rewrites the map, so a second export before applying loses the moved block. Documented in docs/opentofu-migration.md: apply on each host before re-exporting, or export with --no-ids and refresh with ct ids sync.

Checks

lint, typecheck, test (1276 passed), build all green locally; 4 new tests in tests/export/export-tf.test.ts.

The id map records the label each id was exported under. When the same
(type, id) now gets another label, imports.tf gets a moved block from the
old address to the new one, so tofu plan no longer proposes destroying it.

The import block for the new address stays: tofu moves first and the
import of the same id is then a no-op, and when the old address never
reached state the import still brings the resource in.

Closes #199
…eadable id map

A swap of two keys, or a new resource taking over a renamed one's old key,
produced a moved block whose source is still declared, which tofu rejects
("Moved object still exists"). Those moves are now left out with an
EXPORT_MOVE_CONFLICT warning.

The move lookup made every export read the id map, so a corrupt map failed
a --no-ids export that never needed it. A map that does not load now costs
only the moves, with an EXPORT_MOVES_SKIPPED warning.
@2000game

Copy link
Copy Markdown
Member Author

Review (/code-review high), each finding verified before acting:

  • Fixed — swap / key reuse. Reproduced with OpenTofu: a moved whose from is still declared fails plan with "Moved object still exists"; leaving it out instead updates the wrong object in place. Such moves are now skipped with an EXPORT_MOVE_CONFLICT warning (manual tofu state mv), documented.
  • Fixed — --no-ids now read the id map. On main a --no-ids export without references never loaded it; this branch made a corrupt map fatal. The move lookup now tolerates it (EXPORT_MOVES_SKIPPED); references that need the map still fail as before.
  • Not changed — second export loses the move. Already the documented limitation; keeping the old label in the map would point references and ct ids sync at a stale address. --only heals itself, since uncovered map entries carry over.
  • Not changed — shared tofu/ across hosts. imports.tf is already per-host-at-a-time (import ids differ between hosts); maps are per host, so each host's export derives its own moves.
  • Not changed — unvalidated map labels / repeated map reads. Labels come from hclLabel or tofu addresses via ct ids sync; the extra reads are minor.

3 regression tests (fail before, pass after). Ran the built CLI end to end: re-key → moved block, swap → warning and no block, corrupt map + --no-ids → exit 0 with warning.

@2000game
2000game merged commit d2df835 into main Oct 16, 2026
3 checks passed
@2000game
2000game deleted the fix/export-moved-on-rekey branch October 16, 2026 15:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

export tf: emit a moved block when an exported resource is re-keyed

1 participant