@intentius/chant-lexicon-terraform 0.57.0 → 0.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/composites/terraform-adopt-op.d.ts +126 -0
- package/dist/composites/terraform-adopt-op.d.ts.map +1 -0
- package/dist/composites/terraform-apply-op.d.ts +63 -16
- package/dist/composites/terraform-apply-op.d.ts.map +1 -1
- package/dist/composites/terraform-watch-op.d.ts +80 -36
- package/dist/composites/terraform-watch-op.d.ts.map +1 -1
- package/dist/config.d.ts +49 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/describe-resources.d.ts +256 -16
- package/dist/describe-resources.d.ts.map +1 -1
- package/dist/hcl/descend.d.ts +98 -0
- package/dist/hcl/descend.d.ts.map +1 -0
- package/dist/hcl/marker.d.ts +130 -0
- package/dist/hcl/marker.d.ts.map +1 -0
- package/dist/hcl/parse.d.ts +142 -9
- package/dist/hcl/parse.d.ts.map +1 -1
- package/dist/hcl/references.d.ts +76 -0
- package/dist/hcl/references.d.ts.map +1 -0
- package/dist/hcl/roots.d.ts +22 -3
- package/dist/hcl/roots.d.ts.map +1 -1
- package/dist/hcl/suppressions.d.ts +54 -0
- package/dist/hcl/suppressions.d.ts.map +1 -0
- package/dist/hcl/value.d.ts +77 -0
- package/dist/hcl/value.d.ts.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +32 -5
- package/dist/lint/audit-catalog.d.ts +3 -2
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/audit-lineage.d.ts +68 -0
- package/dist/lint/audit-lineage.d.ts.map +1 -1
- package/dist/lint/post-synth/blocks.d.ts +53 -0
- package/dist/lint/post-synth/blocks.d.ts.map +1 -0
- package/dist/lint/post-synth/fixtures/load.d.ts +37 -0
- package/dist/lint/post-synth/fixtures/load.d.ts.map +1 -0
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/lint/post-synth/module-source.d.ts +38 -0
- package/dist/lint/post-synth/module-source.d.ts.map +1 -0
- package/dist/lint/post-synth/scope.d.ts +65 -0
- package/dist/lint/post-synth/scope.d.ts.map +1 -0
- package/dist/lint/post-synth/tf001.d.ts +18 -0
- package/dist/lint/post-synth/tf001.d.ts.map +1 -1
- package/dist/lint/post-synth/tf002.d.ts +32 -0
- package/dist/lint/post-synth/tf002.d.ts.map +1 -0
- package/dist/lint/post-synth/tf003.d.ts +22 -0
- package/dist/lint/post-synth/tf003.d.ts.map +1 -0
- package/dist/lint/post-synth/tf004.d.ts +22 -0
- package/dist/lint/post-synth/tf004.d.ts.map +1 -0
- package/dist/lint/post-synth/tf005.d.ts +28 -0
- package/dist/lint/post-synth/tf005.d.ts.map +1 -0
- package/dist/lint/post-synth/tf006.d.ts +21 -0
- package/dist/lint/post-synth/tf006.d.ts.map +1 -0
- package/dist/lint/post-synth/tf007.d.ts +21 -0
- package/dist/lint/post-synth/tf007.d.ts.map +1 -0
- package/dist/lint/post-synth/tf008.d.ts +22 -0
- package/dist/lint/post-synth/tf008.d.ts.map +1 -0
- package/dist/lint/post-synth/tf009.d.ts +24 -0
- package/dist/lint/post-synth/tf009.d.ts.map +1 -0
- package/dist/lint/post-synth/tf010.d.ts +19 -0
- package/dist/lint/post-synth/tf010.d.ts.map +1 -0
- package/dist/lint/post-synth/tf011.d.ts +18 -0
- package/dist/lint/post-synth/tf011.d.ts.map +1 -0
- package/dist/lint/post-synth/tf012.d.ts +17 -0
- package/dist/lint/post-synth/tf012.d.ts.map +1 -0
- package/dist/lint/post-synth/tf013.d.ts +21 -0
- package/dist/lint/post-synth/tf013.d.ts.map +1 -0
- package/dist/lint/post-synth/tf014.d.ts +25 -0
- package/dist/lint/post-synth/tf014.d.ts.map +1 -0
- package/dist/lint/post-synth/tf015.d.ts +21 -0
- package/dist/lint/post-synth/tf015.d.ts.map +1 -0
- package/dist/lint/post-synth/tf016.d.ts +30 -0
- package/dist/lint/post-synth/tf016.d.ts.map +1 -0
- package/dist/lint/post-synth/tf017.d.ts +19 -0
- package/dist/lint/post-synth/tf017.d.ts.map +1 -0
- package/dist/lint/post-synth/tf018.d.ts +20 -0
- package/dist/lint/post-synth/tf018.d.ts.map +1 -0
- package/dist/lint/post-synth/tf019.d.ts +25 -0
- package/dist/lint/post-synth/tf019.d.ts.map +1 -0
- package/dist/lint/post-synth/tf020.d.ts +38 -0
- package/dist/lint/post-synth/tf020.d.ts.map +1 -0
- package/dist/lint/post-synth/tf021.d.ts +41 -0
- package/dist/lint/post-synth/tf021.d.ts.map +1 -0
- package/dist/lint/post-synth/tf022.d.ts +24 -0
- package/dist/lint/post-synth/tf022.d.ts.map +1 -0
- package/dist/lint/post-synth/tf024.d.ts +24 -0
- package/dist/lint/post-synth/tf024.d.ts.map +1 -0
- package/dist/lint/post-synth/tf025.d.ts +29 -0
- package/dist/lint/post-synth/tf025.d.ts.map +1 -0
- package/dist/lint/post-synth/tf026.d.ts +21 -0
- package/dist/lint/post-synth/tf026.d.ts.map +1 -0
- package/dist/lint/rules/fixtures/load.d.ts +13 -0
- package/dist/lint/rules/fixtures/load.d.ts.map +1 -0
- package/dist/lint/rules/plan-before-apply.d.ts +11 -0
- package/dist/lint/rules/plan-before-apply.d.ts.map +1 -1
- package/dist/lint/secret-shape.d.ts +68 -0
- package/dist/lint/secret-shape.d.ts.map +1 -0
- package/dist/live-ownership.d.ts +55 -0
- package/dist/live-ownership.d.ts.map +1 -0
- package/dist/lsp/option-keys.d.ts.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/okf/index.md +24 -0
- package/dist/okf/rules/TF002.md +11 -0
- package/dist/okf/rules/TF003.md +11 -0
- package/dist/okf/rules/TF004.md +11 -0
- package/dist/okf/rules/TF005.md +11 -0
- package/dist/okf/rules/TF006.md +11 -0
- package/dist/okf/rules/TF007.md +11 -0
- package/dist/okf/rules/TF008.md +11 -0
- package/dist/okf/rules/TF009.md +11 -0
- package/dist/okf/rules/TF010.md +11 -0
- package/dist/okf/rules/TF011.md +11 -0
- package/dist/okf/rules/TF012.md +11 -0
- package/dist/okf/rules/TF013.md +11 -0
- package/dist/okf/rules/TF014.md +11 -0
- package/dist/okf/rules/TF015.md +11 -0
- package/dist/okf/rules/TF016.md +11 -0
- package/dist/okf/rules/TF017.md +11 -0
- package/dist/okf/rules/TF018.md +11 -0
- package/dist/okf/rules/TF019.md +11 -0
- package/dist/okf/rules/TF020.md +11 -0
- package/dist/okf/rules/TF021.md +11 -0
- package/dist/okf/rules/TF022.md +11 -0
- package/dist/okf/rules/TF024.md +11 -0
- package/dist/okf/rules/TF025.md +11 -0
- package/dist/okf/rules/TF026.md +11 -0
- package/dist/op/activities/index.d.ts +13 -6
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/live-detect.d.ts +51 -0
- package/dist/op/activities/live-detect.d.ts.map +1 -0
- package/dist/op/activities/terraform.d.ts +445 -17
- package/dist/op/activities/terraform.d.ts.map +1 -1
- package/dist/op/activity-contracts.d.ts +148 -0
- package/dist/op/activity-contracts.d.ts.map +1 -0
- package/dist/op/adoption.d.ts +118 -0
- package/dist/op/adoption.d.ts.map +1 -0
- package/dist/op/builders.d.ts +46 -6
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/resolve-root-mode.d.ts +45 -0
- package/dist/op/resolve-root-mode.d.ts.map +1 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/rules/blocks.ts +109 -0
- package/dist/rules/module-source.ts +121 -0
- package/dist/rules/plan-before-apply.ts +11 -0
- package/dist/rules/scope.ts +107 -0
- package/dist/rules/tf001.ts +32 -8
- package/dist/rules/tf002.ts +151 -0
- package/dist/rules/tf003.ts +71 -0
- package/dist/rules/tf004.ts +63 -0
- package/dist/rules/tf005.ts +76 -0
- package/dist/rules/tf006.ts +53 -0
- package/dist/rules/tf007.ts +78 -0
- package/dist/rules/tf008.ts +75 -0
- package/dist/rules/tf009.ts +59 -0
- package/dist/rules/tf010.ts +51 -0
- package/dist/rules/tf011.ts +49 -0
- package/dist/rules/tf012.ts +47 -0
- package/dist/rules/tf013.ts +63 -0
- package/dist/rules/tf014.ts +72 -0
- package/dist/rules/tf015.ts +63 -0
- package/dist/rules/tf016.ts +75 -0
- package/dist/rules/tf017.ts +50 -0
- package/dist/rules/tf018.ts +72 -0
- package/dist/rules/tf019.ts +72 -0
- package/dist/rules/tf020.ts +123 -0
- package/dist/rules/tf021.ts +126 -0
- package/dist/rules/tf022.ts +61 -0
- package/dist/rules/tf024.ts +70 -0
- package/dist/rules/tf025.ts +101 -0
- package/dist/rules/tf026.ts +80 -0
- package/dist/skills/chant-terraform.md +1 -1
- package/package.json +7 -2
- package/src/__fixtures__/live/main.tf +28 -0
- package/src/__fixtures__/live-adopt/estate.chdf.hcl +6 -0
- package/src/__fixtures__/live-adopt/main.tf +21 -0
- package/src/__fixtures__/live-estate/README.md +58 -0
- package/src/__fixtures__/live-estate/compute.tf +10 -0
- package/src/__fixtures__/live-estate/estate.chdf.hcl +1 -0
- package/src/__fixtures__/live-estate/logs.tf +8 -0
- package/src/__fixtures__/live-estate/network.tf +24 -0
- package/src/__fixtures__/live-estate/rule.tf +11 -0
- package/src/__fixtures__/live-estate/unowned.tf +21 -0
- package/src/__fixtures__/live-estate/versions.tf +31 -0
- package/src/__fixtures__/live-ls.json +98 -0
- package/src/__fixtures__/live-plan.json +285 -0
- package/src/__fixtures__/live-sidecar/estate.chdf.hcl +3 -0
- package/src/__fixtures__/live-sidecar/main.tf +24 -0
- package/src/__fixtures__/module-tree/README.md +16 -0
- package/src/__fixtures__/module-tree/cycle/main.tf +3 -0
- package/src/__fixtures__/module-tree/cycle/modules/a/main.tf +9 -0
- package/src/__fixtures__/module-tree/cycle/modules/b/main.tf +9 -0
- package/src/__fixtures__/module-tree/missing/main.tf +9 -0
- package/src/__fixtures__/module-tree/outside/root/main.tf +9 -0
- package/src/__fixtures__/module-tree/root/main.tf +28 -0
- package/src/__fixtures__/module-tree/root/modules/cdn/main.tf +15 -0
- package/src/__fixtures__/module-tree/root/modules/cdn/modules/bucket/main.tf +11 -0
- package/src/__fixtures__/module-tree/shared/main.tf +5 -0
- package/src/__fixtures__/references/main.tf +56 -0
- package/src/codegen/lint-rules-page.test.ts +28 -0
- package/src/composites/terraform-adopt-op.acceptance.test.ts +166 -0
- package/src/composites/terraform-adopt-op.test.ts +170 -0
- package/src/composites/terraform-adopt-op.ts +197 -0
- package/src/composites/terraform-apply-op.acceptance.test.ts +183 -5
- package/src/composites/terraform-apply-op.test.ts +167 -15
- package/src/composites/terraform-apply-op.ts +105 -28
- package/src/composites/terraform-watch-op.test.ts +85 -26
- package/src/composites/terraform-watch-op.ts +130 -73
- package/src/config.ts +32 -3
- package/src/describe-resources.live.test.ts +598 -0
- package/src/describe-resources.test.ts +53 -0
- package/src/describe-resources.ts +811 -28
- package/src/hcl/descend.test.ts +162 -0
- package/src/hcl/descend.ts +273 -0
- package/src/hcl/marker.test.ts +147 -0
- package/src/hcl/marker.ts +235 -0
- package/src/hcl/parse.test.ts +69 -0
- package/src/hcl/parse.ts +249 -27
- package/src/hcl/references.test.ts +116 -0
- package/src/hcl/references.ts +172 -0
- package/src/hcl/roots.ts +59 -5
- package/src/hcl/suppressions.test.ts +147 -0
- package/src/hcl/suppressions.ts +204 -0
- package/src/hcl/value.test.ts +73 -0
- package/src/hcl/value.ts +106 -0
- package/src/index.ts +31 -1
- package/src/lint/audit-catalog.ts +264 -3
- package/src/lint/audit-lineage.ts +319 -1
- package/src/lint/audit.test.ts +40 -1
- package/src/lint/post-synth/blocks.ts +109 -0
- package/src/lint/post-synth/fixtures/TF001/negative.tf +28 -0
- package/src/lint/post-synth/fixtures/TF001/positive.tf +24 -0
- package/src/lint/post-synth/fixtures/TF002/negative.tf +17 -0
- package/src/lint/post-synth/fixtures/TF002/positive-legacy.tf +10 -0
- package/src/lint/post-synth/fixtures/TF002/positive-no-version.tf +12 -0
- package/src/lint/post-synth/fixtures/TF002/positive.tf +7 -0
- package/src/lint/post-synth/fixtures/TF003/negative.tf +10 -0
- package/src/lint/post-synth/fixtures/TF003/positive.tf +8 -0
- package/src/lint/post-synth/fixtures/TF004/negative-git.tf +3 -0
- package/src/lint/post-synth/fixtures/TF004/negative-local.tf +3 -0
- package/src/lint/post-synth/fixtures/TF004/negative.tf +4 -0
- package/src/lint/post-synth/fixtures/TF004/positive.tf +3 -0
- package/src/lint/post-synth/fixtures/TF005/negative-local.tf +3 -0
- package/src/lint/post-synth/fixtures/TF005/negative-registry.tf +3 -0
- package/src/lint/post-synth/fixtures/TF005/negative-sha.tf +3 -0
- package/src/lint/post-synth/fixtures/TF005/negative.tf +3 -0
- package/src/lint/post-synth/fixtures/TF005/positive-branch.tf +3 -0
- package/src/lint/post-synth/fixtures/TF005/positive-non-semver.tf +3 -0
- package/src/lint/post-synth/fixtures/TF005/positive.tf +3 -0
- package/src/lint/post-synth/fixtures/TF006/negative.tf +5 -0
- package/src/lint/post-synth/fixtures/TF006/positive-empty-default.tf +11 -0
- package/src/lint/post-synth/fixtures/TF006/positive.tf +6 -0
- package/src/lint/post-synth/fixtures/TF007/negative.tf +10 -0
- package/src/lint/post-synth/fixtures/TF007/positive.tf +9 -0
- package/src/lint/post-synth/fixtures/TF008/negative-variable.tf +10 -0
- package/src/lint/post-synth/fixtures/TF008/negative.tf +8 -0
- package/src/lint/post-synth/fixtures/TF008/positive-other-provider.tf +10 -0
- package/src/lint/post-synth/fixtures/TF008/positive.tf +5 -0
- package/src/lint/post-synth/fixtures/TF009/negative.tf +10 -0
- package/src/lint/post-synth/fixtures/TF009/positive.tf +4 -0
- package/src/lint/post-synth/fixtures/TF010/negative.tf +5 -0
- package/src/lint/post-synth/fixtures/TF010/positive.tf +4 -0
- package/src/lint/post-synth/fixtures/TF011/negative.tf +5 -0
- package/src/lint/post-synth/fixtures/TF011/positive-blank.tf +5 -0
- package/src/lint/post-synth/fixtures/TF011/positive.tf +4 -0
- package/src/lint/post-synth/fixtures/TF012/negative.tf +4 -0
- package/src/lint/post-synth/fixtures/TF012/positive.tf +3 -0
- package/src/lint/post-synth/fixtures/TF013/negative.tf +8 -0
- package/src/lint/post-synth/fixtures/TF013/positive-data.tf +8 -0
- package/src/lint/post-synth/fixtures/TF013/positive.tf +8 -0
- package/src/lint/post-synth/fixtures/TF014/negative/main.tf +26 -0
- package/src/lint/post-synth/fixtures/TF014/negative/modules/cdn/main.tf +19 -0
- package/src/lint/post-synth/fixtures/TF014/positive/main.tf +16 -0
- package/src/lint/post-synth/fixtures/TF014/positive/modules/cdn/main.tf +15 -0
- package/src/lint/post-synth/fixtures/TF015/negative/main.tf +13 -0
- package/src/lint/post-synth/fixtures/TF015/negative/modules/cdn/main.tf +16 -0
- package/src/lint/post-synth/fixtures/TF015/positive/main.tf +13 -0
- package/src/lint/post-synth/fixtures/TF015/positive/modules/cdn/main.tf +14 -0
- package/src/lint/post-synth/fixtures/TF016/negative-template.tf +7 -0
- package/src/lint/post-synth/fixtures/TF016/negative.tf +16 -0
- package/src/lint/post-synth/fixtures/TF016/positive-shared-file.tf +12 -0
- package/src/lint/post-synth/fixtures/TF016/positive.tf +13 -0
- package/src/lint/post-synth/fixtures/TF017/negative.tf +4 -0
- package/src/lint/post-synth/fixtures/TF017/positive.tf +5 -0
- package/src/lint/post-synth/fixtures/TF018/negative.tf +9 -0
- package/src/lint/post-synth/fixtures/TF018/positive-data.tf +9 -0
- package/src/lint/post-synth/fixtures/TF018/positive.tf +4 -0
- package/src/lint/post-synth/fixtures/TF019/negative.tf +14 -0
- package/src/lint/post-synth/fixtures/TF019/positive-output.tf +5 -0
- package/src/lint/post-synth/fixtures/TF019/positive.tf +16 -0
- package/src/lint/post-synth/fixtures/TF020/negative.tf +43 -0
- package/src/lint/post-synth/fixtures/TF020/positive.tf +32 -0
- package/src/lint/post-synth/fixtures/TF020/scoped/main.tf +21 -0
- package/src/lint/post-synth/fixtures/TF020/scoped/modules/cdn/main.tf +13 -0
- package/src/lint/post-synth/fixtures/TF021/negative.tf +31 -0
- package/src/lint/post-synth/fixtures/TF021/positive-length.tf +8 -0
- package/src/lint/post-synth/fixtures/TF021/positive.tf +8 -0
- package/src/lint/post-synth/fixtures/TF022/negative-locator.tf +7 -0
- package/src/lint/post-synth/fixtures/TF022/negative.tf +10 -0
- package/src/lint/post-synth/fixtures/TF022/positive-nested.tf +10 -0
- package/src/lint/post-synth/fixtures/TF022/positive.tf +6 -0
- package/src/lint/post-synth/fixtures/TF023/negative/main.tf +15 -0
- package/src/lint/post-synth/fixtures/TF023/positive/main.tf +9 -0
- package/src/lint/post-synth/fixtures/TF023/positive/state.json +24 -0
- package/src/lint/post-synth/fixtures/TF024/live-clean/main.tf +9 -0
- package/src/lint/post-synth/fixtures/TF024/live-with-backend/main.tf +14 -0
- package/src/lint/post-synth/fixtures/TF024/live-with-cloud/main.tf +16 -0
- package/src/lint/post-synth/fixtures/TF025/live-clean/main.tf +13 -0
- package/src/lint/post-synth/fixtures/TF025/live-with-workspace-ref/main.tf +13 -0
- package/src/lint/post-synth/fixtures/TF026/live-default/main.tf +9 -0
- package/src/lint/post-synth/fixtures/TF026/live-explicit-delete/main.tf +13 -0
- package/src/lint/post-synth/fixtures/TF026/live-keep/main.tf +13 -0
- package/src/lint/post-synth/fixtures/load.ts +108 -0
- package/src/lint/post-synth/index.ts +48 -0
- package/src/lint/post-synth/module-rules.test.ts +153 -0
- package/src/lint/post-synth/module-source.test.ts +75 -0
- package/src/lint/post-synth/module-source.ts +121 -0
- package/src/lint/post-synth/post-synth.test.ts +14 -15
- package/src/lint/post-synth/scope.ts +107 -0
- package/src/lint/post-synth/single-block.test.ts +263 -0
- package/src/lint/post-synth/suppression.test.ts +110 -0
- package/src/lint/post-synth/tf001.ts +32 -8
- package/src/lint/post-synth/tf002.test.ts +93 -0
- package/src/lint/post-synth/tf002.ts +151 -0
- package/src/lint/post-synth/tf003.test.ts +75 -0
- package/src/lint/post-synth/tf003.ts +71 -0
- package/src/lint/post-synth/tf004.test.ts +47 -0
- package/src/lint/post-synth/tf004.ts +63 -0
- package/src/lint/post-synth/tf005.test.ts +57 -0
- package/src/lint/post-synth/tf005.ts +76 -0
- package/src/lint/post-synth/tf006.ts +53 -0
- package/src/lint/post-synth/tf007.ts +78 -0
- package/src/lint/post-synth/tf008.ts +75 -0
- package/src/lint/post-synth/tf009.ts +59 -0
- package/src/lint/post-synth/tf010.ts +51 -0
- package/src/lint/post-synth/tf011.ts +49 -0
- package/src/lint/post-synth/tf012.ts +47 -0
- package/src/lint/post-synth/tf013.ts +63 -0
- package/src/lint/post-synth/tf014.ts +72 -0
- package/src/lint/post-synth/tf015.ts +63 -0
- package/src/lint/post-synth/tf016.ts +75 -0
- package/src/lint/post-synth/tf017.ts +50 -0
- package/src/lint/post-synth/tf018.ts +72 -0
- package/src/lint/post-synth/tf019.ts +72 -0
- package/src/lint/post-synth/tf020.ts +123 -0
- package/src/lint/post-synth/tf021.ts +126 -0
- package/src/lint/post-synth/tf022.ts +61 -0
- package/src/lint/post-synth/tf024.test.ts +42 -0
- package/src/lint/post-synth/tf024.ts +70 -0
- package/src/lint/post-synth/tf025.test.ts +46 -0
- package/src/lint/post-synth/tf025.ts +101 -0
- package/src/lint/post-synth/tf026.test.ts +56 -0
- package/src/lint/post-synth/tf026.ts +80 -0
- package/src/lint/presets.test.ts +72 -0
- package/src/lint/rules/fixtures/TF101/negative-op.ts +2 -0
- package/src/lint/rules/fixtures/TF101/positive-op.ts +2 -0
- package/src/lint/rules/fixtures/load.ts +53 -0
- package/src/lint/rules/plan-before-apply.test.ts +112 -7
- package/src/lint/rules/plan-before-apply.ts +11 -0
- package/src/lint/secret-shape.test.ts +150 -0
- package/src/lint/secret-shape.ts +159 -0
- package/src/live-ownership.ts +60 -0
- package/src/lsp/completions.test.ts +3 -2
- package/src/lsp/option-keys.ts +10 -5
- package/src/op/activities/choudoufu.acceptance.test.ts +143 -0
- package/src/op/activities/choudoufu.test.ts +693 -0
- package/src/op/activities/index.ts +40 -4
- package/src/op/activities/live-detect.test.ts +143 -0
- package/src/op/activities/live-detect.ts +162 -0
- package/src/op/activities/registry.test.ts +11 -3
- package/src/op/activities/terraform.test.ts +7 -3
- package/src/op/activities/terraform.ts +823 -69
- package/src/op/activity-contracts.test.ts +147 -0
- package/src/op/activity-contracts.ts +178 -0
- package/src/op/adoption.test.ts +170 -0
- package/src/op/adoption.ts +241 -0
- package/src/op/builders.test.ts +60 -5
- package/src/op/builders.ts +93 -5
- package/src/op/resolve-root-mode.test.ts +85 -0
- package/src/op/resolve-root-mode.ts +72 -0
- package/src/plugin.test.ts +68 -0
- package/src/plugin.ts +75 -9
- package/src/skills/chant-terraform.md +1 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The acceptance test is choudoufu, not a fixture (#2105): adopt a live
|
|
3
|
+
* resource nothing has marked, end to end.
|
|
4
|
+
*
|
|
5
|
+
* `terraform-adopt-op.test.ts` next door proves the shape chant emits.
|
|
6
|
+
* `choudoufu.test.ts` proves the activities' contract against a stubbed child
|
|
7
|
+
* process. This proves the shape does the thing: create an unmarked VPC
|
|
8
|
+
* directly against choudoufu's pinned emulator, so it is a live resource this
|
|
9
|
+
* estate does not own at an identity this root's configuration declares; run
|
|
10
|
+
* the Ledger step and expect exactly one adoptable match with the two marker
|
|
11
|
+
* values on it; run the Adopt step and let it write them; re-plan and expect
|
|
12
|
+
* the estate to own the same VPC, with nothing left adoptable.
|
|
13
|
+
*
|
|
14
|
+
* The Op's phases are not run through `runOpLocally` here, deliberately.
|
|
15
|
+
* `TerraformAdoptOp` always emits a gate — adoption moves the estate's
|
|
16
|
+
* boundary, and there is no ungated form — so a local run would stop at the
|
|
17
|
+
* gate and record a pending fact rather than reach the Adopt phase
|
|
18
|
+
* (`packages/core/src/op/gate.ts`). So this drives the two activities the Op's
|
|
19
|
+
* Ledger and Adopt phases are built from, in the same order and with the same
|
|
20
|
+
* hand-off, which is the part a real binary can falsify. `choudoufu.acceptance.test.ts` takes the same approach for the same
|
|
21
|
+
* reason.
|
|
22
|
+
*
|
|
23
|
+
* ## Why this skips today
|
|
24
|
+
*
|
|
25
|
+
* choudoufu#894: `live-plan -json` is reachable only through the `-estate`
|
|
26
|
+
* form, and that form is refused on a configuration that names its own estate
|
|
27
|
+
* ("Estate named by both the live block and -estate"), while the same root
|
|
28
|
+
* without `-estate` refuses with "Machine-readable output is not available
|
|
29
|
+
* under live resource markers yet". The one configuration shape choudoufu's
|
|
30
|
+
* own docs lead with is the one shape the #788 document cannot be produced
|
|
31
|
+
* for, so `choudoufuLivePlan` throws on any real live root until that lands.
|
|
32
|
+
* Found by chant #2104 against a source build at HEAD.
|
|
33
|
+
*
|
|
34
|
+
* The gate below names it, alongside the ordinary two: no binary, and no
|
|
35
|
+
* emulator. When #894 ships, drop `CHOUDOUFU_894_OPEN` and this suite runs as
|
|
36
|
+
* written.
|
|
37
|
+
*
|
|
38
|
+
* Gating copied from `./terraform-apply-op.acceptance.test.ts` (`onPath`),
|
|
39
|
+
* which in turn copies `lexicons/k3s/src/serializer.acceptance.test.ts`'s
|
|
40
|
+
* pattern of skipping with the reason named in the describe title rather than
|
|
41
|
+
* failing when the real dependency is absent.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import { execSync } from "node:child_process";
|
|
45
|
+
import { cpSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
46
|
+
import { tmpdir } from "node:os";
|
|
47
|
+
import { join } from "node:path";
|
|
48
|
+
import { afterAll, describe, expect, it } from "vitest";
|
|
49
|
+
import { choudoufuAdopt, choudoufuLivePlan, terraformInit } from "../op/activities/terraform";
|
|
50
|
+
|
|
51
|
+
function onPath(cmd: string): boolean {
|
|
52
|
+
try {
|
|
53
|
+
execSync(`command -v ${cmd}`, { stdio: "ignore" });
|
|
54
|
+
return true;
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* https://github.com/INTENTIUS/choudoufu/issues/894. Flip to `false` when the
|
|
62
|
+
* `-json` document becomes reachable on a root that declares its own estate.
|
|
63
|
+
* chant #2168 tracks this and the other four reversals that unblock together.
|
|
64
|
+
*/
|
|
65
|
+
const CHOUDOUFU_894_OPEN = true;
|
|
66
|
+
|
|
67
|
+
const emulatorEndpoint = process.env.CHOUDOUFU_EMULATOR_ENDPOINT;
|
|
68
|
+
|
|
69
|
+
const skipReason = !onPath("choudoufu")
|
|
70
|
+
? "no choudoufu binary on PATH"
|
|
71
|
+
: !onPath("aws")
|
|
72
|
+
? "no aws CLI on PATH (the unmarked resource is created with it, and adopted through it)"
|
|
73
|
+
: !emulatorEndpoint
|
|
74
|
+
? "CHOUDOUFU_EMULATOR_ENDPOINT is not set (bring up choudoufu's `just smoke` emulator stack and export it)"
|
|
75
|
+
: CHOUDOUFU_894_OPEN
|
|
76
|
+
? "choudoufu#894: live-plan -json is refused on a configuration that declares its own estate, " +
|
|
77
|
+
"so there is no adoption ledger to act on yet"
|
|
78
|
+
: "";
|
|
79
|
+
|
|
80
|
+
const FIXTURE = join(import.meta.dirname, "..", "__fixtures__", "live-adopt");
|
|
81
|
+
const ESTATE = "chant-adopt-fixture";
|
|
82
|
+
const CIDR = "10.77.0.0/16";
|
|
83
|
+
const workspaces: string[] = [];
|
|
84
|
+
|
|
85
|
+
afterAll(() => {
|
|
86
|
+
for (const dir of workspaces) rmSync(dir, { recursive: true, force: true });
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
/** A fresh copy of the live-adopt fixture, wired up with a real project config. */
|
|
90
|
+
function project(): string {
|
|
91
|
+
const dir = mkdtempSync(join(tmpdir(), "chant-adopt-accept-"));
|
|
92
|
+
workspaces.push(dir);
|
|
93
|
+
cpSync(FIXTURE, join(dir, "root"), { recursive: true });
|
|
94
|
+
writeFileSync(
|
|
95
|
+
join(dir, "chant.config.json"),
|
|
96
|
+
JSON.stringify(
|
|
97
|
+
{ lexicons: ["terraform"], terraform: { binary: "choudoufu", roots: { estate: { dir: "./root" } } } },
|
|
98
|
+
null,
|
|
99
|
+
2,
|
|
100
|
+
),
|
|
101
|
+
);
|
|
102
|
+
return dir;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
describe.skipIf(skipReason !== "")(
|
|
106
|
+
`TerraformAdoptOp adopts an unmarked live resource${skipReason ? ` (skipped: ${skipReason})` : ""}`,
|
|
107
|
+
() => {
|
|
108
|
+
it(
|
|
109
|
+
"ledgers one adoptable VPC, writes its two markers, and re-plans with it owned",
|
|
110
|
+
{ timeout: 600_000 },
|
|
111
|
+
async () => {
|
|
112
|
+
const dir = project();
|
|
113
|
+
|
|
114
|
+
process.env.AWS_ENDPOINT_URL ??= emulatorEndpoint;
|
|
115
|
+
process.env.AWS_ACCESS_KEY_ID ??= "choudoufu-emulator";
|
|
116
|
+
process.env.AWS_SECRET_ACCESS_KEY ??= "choudoufu-emulator";
|
|
117
|
+
process.env.AWS_DEFAULT_REGION ??= "us-east-1";
|
|
118
|
+
|
|
119
|
+
// The stock-created resource: a VPC at the cidr this root declares,
|
|
120
|
+
// carrying no ownership marker at all. Nothing about it says which
|
|
121
|
+
// configuration made it, which is exactly the migration situation.
|
|
122
|
+
execSync(
|
|
123
|
+
`aws ec2 create-vpc --cidr-block ${CIDR} --endpoint-url ${emulatorEndpoint} --region us-east-1`,
|
|
124
|
+
{ stdio: "pipe" },
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
await terraformInit({ root: "estate", cwd: dir });
|
|
128
|
+
|
|
129
|
+
const ledger = await choudoufuLivePlan({ root: "estate", cwd: dir, adoptionOnly: true });
|
|
130
|
+
expect(ledger.estate).toBe(ESTATE);
|
|
131
|
+
expect(ledger.contested).toEqual([]);
|
|
132
|
+
expect(ledger.adoptions).toHaveLength(1);
|
|
133
|
+
const [candidate] = ledger.adoptions;
|
|
134
|
+
expect(candidate.addr).toBe("aws_vpc.adoptable");
|
|
135
|
+
expect(candidate.markerEstate).toBe(ESTATE);
|
|
136
|
+
expect(candidate.markerAddress).toBe("aws_vpc.adoptable");
|
|
137
|
+
// A VPC is tagged through `ec2 create-tags`, which choudoufu prints a
|
|
138
|
+
// command for; a candidate with none would be refused below instead.
|
|
139
|
+
expect(candidate.command).toContain("create-tags");
|
|
140
|
+
expect(ledger.ledger).toContain("aws_vpc.adoptable <- aws_vpc vpc-");
|
|
141
|
+
|
|
142
|
+
const adopted = await choudoufuAdopt({
|
|
143
|
+
root: "estate",
|
|
144
|
+
cwd: dir,
|
|
145
|
+
adoptions: ledger.adoptions,
|
|
146
|
+
contested: ledger.contested,
|
|
147
|
+
});
|
|
148
|
+
expect(adopted.mechanism).toBe("tag-write");
|
|
149
|
+
expect(adopted.adopted).toEqual(["aws_vpc.adoptable"]);
|
|
150
|
+
expect(adopted.refused).toEqual([]);
|
|
151
|
+
|
|
152
|
+
// The markers are the whole ownership answer, so a re-plan binds the
|
|
153
|
+
// same live VPC instead of proposing a second one: nothing is left
|
|
154
|
+
// unowned at this estate's declared identity, and nothing adoptable.
|
|
155
|
+
const after = await choudoufuLivePlan({ root: "estate", cwd: dir });
|
|
156
|
+
expect(after.adoptable).toBe(0);
|
|
157
|
+
expect(after.unowned).toBe(0);
|
|
158
|
+
expect(after.adoptions).toEqual([]);
|
|
159
|
+
|
|
160
|
+
// And the estate now says so: the VPC is bound at the declared address.
|
|
161
|
+
const bound = (after.json as { bound?: Array<{ addr?: string; identity?: string }> }).bound ?? [];
|
|
162
|
+
expect(bound.map((b) => b.addr)).toContain("aws_vpc.adoptable");
|
|
163
|
+
},
|
|
164
|
+
);
|
|
165
|
+
},
|
|
166
|
+
);
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TerraformAdoptOp composite tests (#2105): the four phases, the gate that is
|
|
3
|
+
* always there, the two references the Adopt step takes off the ledger, and
|
|
4
|
+
* the rule an adoption Op exists to keep — an ambiguous match is reported and
|
|
5
|
+
* never written.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, test, expect } from "vitest";
|
|
9
|
+
import {
|
|
10
|
+
collectStepOutputRefs,
|
|
11
|
+
isStepOutputRef,
|
|
12
|
+
type ActivityStep,
|
|
13
|
+
type GateStep,
|
|
14
|
+
type OpConfig,
|
|
15
|
+
type StepDefinition,
|
|
16
|
+
} from "@intentius/chant/op";
|
|
17
|
+
|
|
18
|
+
import { TerraformAdoptOp } from "./terraform-adopt-op";
|
|
19
|
+
|
|
20
|
+
function props(config: Parameters<typeof TerraformAdoptOp>[0]): OpConfig {
|
|
21
|
+
return (TerraformAdoptOp(config).op as unknown as { props: OpConfig }).props;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const phaseNames = (op: OpConfig): string[] => op.phases.map((p) => p.name);
|
|
25
|
+
const isActivity = (s: StepDefinition): s is ActivityStep => s.kind === "activity";
|
|
26
|
+
const steps = (op: OpConfig): ActivityStep[] => op.phases.flatMap((p) => p.steps.filter(isActivity));
|
|
27
|
+
const base = { name: "estate-adopt", root: "estate" };
|
|
28
|
+
|
|
29
|
+
describe("TerraformAdoptOp phases (#2105)", () => {
|
|
30
|
+
test("Check, Ledger, Gate, Adopt, in that order", () => {
|
|
31
|
+
const op = props(base);
|
|
32
|
+
expect(phaseNames(op)).toEqual(["Check", "Ledger", "Gate", "Adopt"]);
|
|
33
|
+
expect(steps(op).map((s) => s.fn)).toEqual(["choudoufuLiveCheck", "choudoufuLivePlan", "choudoufuAdopt"]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("Check runs live-check first, and reports whether the root was refused", () => {
|
|
37
|
+
const check = props(base).phases[0].steps[0] as ActivityStep;
|
|
38
|
+
expect(check.fn).toBe("choudoufuLiveCheck");
|
|
39
|
+
expect(check.outcomeAttribute).toEqual({ name: "Refused", from: "refused" });
|
|
40
|
+
// No cloud calls, so this is the cheapest place to learn the root cannot
|
|
41
|
+
// move under markers at all.
|
|
42
|
+
expect(check.profile).toBe("fastIdempotent");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("Ledger is the -adoption-only live plan, and publishes both counts", () => {
|
|
46
|
+
const ledger = props(base).phases[1].steps[0] as ActivityStep;
|
|
47
|
+
expect(ledger.fn).toBe("choudoufuLivePlan");
|
|
48
|
+
expect(ledger.id).toBe("ledger");
|
|
49
|
+
expect(ledger.args?.adoptionOnly).toBe(true);
|
|
50
|
+
expect(ledger.outcomeAttribute).toEqual([
|
|
51
|
+
{ name: "Adoptable", from: "adoptable" },
|
|
52
|
+
{ name: "Ambiguous", from: "ambiguous" },
|
|
53
|
+
]);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("every step names the configured root", () => {
|
|
57
|
+
for (const step of steps(props(base))) expect(step.args?.root).toBe("estate");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("cwd rides every step, and no cwd means no cwd arg", () => {
|
|
61
|
+
for (const step of steps(props({ ...base, cwd: "/repo" }))) expect(step.args?.cwd).toBe("/repo");
|
|
62
|
+
for (const step of steps(props(base))) expect(step.args && "cwd" in step.args).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("an explicit estate rides the Ledger step only", () => {
|
|
66
|
+
const op = props({ ...base, estate: "prod-networking" });
|
|
67
|
+
expect((op.phases[1].steps[0] as ActivityStep).args?.estate).toBe("prod-networking");
|
|
68
|
+
expect((op.phases[0].steps[0] as ActivityStep).args?.estate).toBeUndefined();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe("TerraformAdoptOp gates on the ledger (#2105)", () => {
|
|
73
|
+
const gateOf = (op: OpConfig): GateStep => {
|
|
74
|
+
const step = op.phases[2].steps[0];
|
|
75
|
+
expect(step.kind).toBe("gate");
|
|
76
|
+
return step as GateStep;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
test("the gate is always emitted: there is no ungated adoption", () => {
|
|
80
|
+
// Unlike TerraformApplyOp there is no `gate: "never"` here. Adoption moves
|
|
81
|
+
// the estate's boundary onto resources it did not own, which is not a
|
|
82
|
+
// thing to do unattended.
|
|
83
|
+
expect(gateOf(props(base)).signalName).toBe("approve-estate-adopt");
|
|
84
|
+
expect(Object.keys(TerraformAdoptOp(base))).toEqual(["op"]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("the description names the ledger as the approval subject", () => {
|
|
88
|
+
const description = gateOf(props(base)).description!;
|
|
89
|
+
expect(description).toContain("adoption ledger");
|
|
90
|
+
expect(description).toContain("tofu-estate and tofu-address");
|
|
91
|
+
expect(description).toContain("an ambiguous address is never adopted");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("signal name, timeout and description are overridable", () => {
|
|
95
|
+
const gate = gateOf(props({ ...base, signalName: "ok", gateTimeout: "2h", gateDescription: "mine" }));
|
|
96
|
+
expect(gate).toMatchObject({ signalName: "ok", timeout: "2h", description: "mine" });
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("no timeout given means the gate carries none, so core's own default applies", () => {
|
|
100
|
+
expect(gateOf(props(base)).timeout).toBeUndefined();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe("TerraformAdoptOp adopts the ledger's matches and nothing else (#2105)", () => {
|
|
105
|
+
const adoptStep = (op: OpConfig): ActivityStep => op.phases[3].steps[0] as ActivityStep;
|
|
106
|
+
|
|
107
|
+
test("the Adopt step takes the ledger's adoptable set by reference", () => {
|
|
108
|
+
const adopt = adoptStep(props(base));
|
|
109
|
+
expect(adopt.fn).toBe("choudoufuAdopt");
|
|
110
|
+
expect(isStepOutputRef(adopt.args?.adoptions)).toBe(true);
|
|
111
|
+
expect(adopt.args?.adoptions).toMatchObject({ step: "ledger", path: "adoptions" });
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("an ambiguous match is handed over as `contested`, which is a separate field from `adoptions`", () => {
|
|
115
|
+
// `readAdoptionLedger` never puts a contested candidate in `adoptions`, so
|
|
116
|
+
// the Adopt step cannot write one however it behaves; `contested` is
|
|
117
|
+
// passed so the Op's own result names what it declined and why.
|
|
118
|
+
const adopt = adoptStep(props(base));
|
|
119
|
+
expect(adopt.args?.contested).toMatchObject({ step: "ledger", path: "contested" });
|
|
120
|
+
expect(adopt.args?.contested).not.toEqual(adopt.args?.adoptions);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("the Adopt step reports how many resources it claimed", () => {
|
|
124
|
+
expect(adoptStep(props(base)).outcomeAttribute).toEqual({ name: "Adopted", from: "adoptedCount" });
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("the Op references exactly the two ledger fields and no document", () => {
|
|
128
|
+
const op = props(base);
|
|
129
|
+
const refs = op.phases.flatMap((p) => p.steps.filter(isActivity)).flatMap((s) => collectStepOutputRefs(s.args));
|
|
130
|
+
expect(refs.map((r) => r.path).sort()).toEqual(["adoptions", "contested"]);
|
|
131
|
+
expect(refs.every((r) => r.step === "ledger")).toBe(true);
|
|
132
|
+
// The bound/omissions/unowned document never leaves the Ledger step.
|
|
133
|
+
expect(JSON.stringify(op)).not.toContain('"json"');
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe("TerraformAdoptOp compensation (#2105)", () => {
|
|
138
|
+
test("compensate: true throws, because un-adopting is untag", () => {
|
|
139
|
+
expect(() => TerraformAdoptOp({ ...base, compensate: true })).toThrow(/un-adopting is untag/);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("an object with no command throws the same way", () => {
|
|
143
|
+
expect(() => TerraformAdoptOp({ ...base, compensate: {} })).toThrow(/TerraformAdoptOp "estate-adopt"/);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("a supplied command becomes an onFailure Rollback phase", () => {
|
|
147
|
+
const op = props({ ...base, compensate: { command: "./untag.sh" } });
|
|
148
|
+
expect(op.onFailure?.map((p) => p.name)).toEqual(["Rollback"]);
|
|
149
|
+
expect((op.onFailure![0].steps[0] as ActivityStep).args?.cmd).toBe("./untag.sh");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("compensate: false and omitting it both leave no onFailure", () => {
|
|
153
|
+
expect(props(base).onFailure).toBeUndefined();
|
|
154
|
+
expect(props({ ...base, compensate: false }).onFailure).toBeUndefined();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe("TerraformAdoptOp labels (#2105, #2118)", () => {
|
|
159
|
+
test("marks the Op as an adoption on a live root", () => {
|
|
160
|
+
expect(props(base).labels).toEqual({
|
|
161
|
+
Adopt: "true",
|
|
162
|
+
TerraformRoot: "estate",
|
|
163
|
+
TerraformMode: "live",
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("no task queue rides on the Op (#2118)", () => {
|
|
168
|
+
expect(props(base)).not.toHaveProperty("taskQueue");
|
|
169
|
+
});
|
|
170
|
+
});
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TerraformAdoptOp composite (#2105): claim the live resources a choudoufu
|
|
3
|
+
* estate's configuration already describes.
|
|
4
|
+
*
|
|
5
|
+
* The reconcile position on the lifecycle dial, for the one backend that has
|
|
6
|
+
* a typed path from live back to the configuration. chant #2089 deferred
|
|
7
|
+
* reconcile for the terraform lexicon because stock Terraform has none: a live
|
|
8
|
+
* resource nobody has imported is knowable only as HCL somebody writes, and
|
|
9
|
+
* generating that is a different problem. choudoufu changes the question
|
|
10
|
+
* rather than answering the old one. Ownership there is two tags on the
|
|
11
|
+
* resource (`live/MARKERS.md`), `live-plan` already names every live resource
|
|
12
|
+
* that exactly matches a declared block and carries no marker, and it prints
|
|
13
|
+
* the two values that would claim it. Cloud-to-code here is "claim what the
|
|
14
|
+
* configuration already describes" — a tag write, not a regeneration.
|
|
15
|
+
*
|
|
16
|
+
* Phases: Check, Ledger, Gate, Adopt.
|
|
17
|
+
*
|
|
18
|
+
* - **Check** runs `live-check`, which makes no cloud calls and says whether
|
|
19
|
+
* this configuration can move under markers at all. It runs first because
|
|
20
|
+
* a root that `live-check` refuses will produce an adoption ledger that
|
|
21
|
+
* reads as authoritative and is not, and the cheapest place to find that
|
|
22
|
+
* out is before the estate-wide sweep.
|
|
23
|
+
* - **Ledger** runs `live-plan` with `adoptionOnly`, so the run's human half
|
|
24
|
+
* is choudoufu's own adoption ledger (GitHub issue #587) and its machine
|
|
25
|
+
* half is the bound/omissions/unowned document (#788). The ledger is the
|
|
26
|
+
* approval subject; the document is what the Adopt step acts on.
|
|
27
|
+
* - **Gate** always. Adoption writes tags onto resources this estate does
|
|
28
|
+
* not yet own, which is the moment the estate's boundary moves, and no
|
|
29
|
+
* reading of that is routine enough to skip. There is no `gate: "never"`
|
|
30
|
+
* here, unlike `TerraformApplyOp`, so every run stops at the gate until
|
|
31
|
+
* someone has approved it. A gate is a fact on the gate ledger (#2119):
|
|
32
|
+
* `chant run` records that the run is waiting, ends with status `gated`
|
|
33
|
+
* and exits 3, `chant approve <op> <gate>` writes the resolution, and the
|
|
34
|
+
* next run reads it and walks through to the Adopt phase.
|
|
35
|
+
* - **Adopt** writes the markers. See `choudoufuAdopt`'s own doc for the
|
|
36
|
+
* mechanism and why it is the tag write rather than choudoufu's `adopt`
|
|
37
|
+
* policy verb.
|
|
38
|
+
*
|
|
39
|
+
* ## The adopt mechanism, and why it is not the policy verb
|
|
40
|
+
*
|
|
41
|
+
* choudoufu's `policy` block has an `adopt` verb for the declared-but-unmarked
|
|
42
|
+
* quadrant, and its migration guide names `policy { declared_untagged =
|
|
43
|
+
* "adopt" }` as the way to claim a whole estate at once. That is the smaller
|
|
44
|
+
* write, and #2105 tried to reach it from outside the configuration. It is not
|
|
45
|
+
* reachable: a `live` block in a terraform override file is dropped in silence
|
|
46
|
+
* (choudoufu's `Module.mergeFile` has no `Lives` case), a second primary file
|
|
47
|
+
* carrying one is refused as a duplicate, and a sidecar beside an in-block
|
|
48
|
+
* form is refused as two sources of truth. What is left is editing the root's
|
|
49
|
+
* own checked-in live configuration around an apply, which leaves an estate
|
|
50
|
+
* declaring "adopt everything declared and unmarked" if the run dies between
|
|
51
|
+
* the two edits.
|
|
52
|
+
*
|
|
53
|
+
* So the mechanism is the tag write, which choudoufu's migration guide calls
|
|
54
|
+
* the whole contract: "There is no `choudoufu adopt` command and no need for
|
|
55
|
+
* one. Two tags is the whole contract, so any tool that writes two tags can
|
|
56
|
+
* adopt a resource." Each write runs the paste-ready command choudoufu itself
|
|
57
|
+
* printed in the ledger, carrying its own provider configuration's region and
|
|
58
|
+
* endpoint. `choudoufuAdopt` has the measurements behind that ruling.
|
|
59
|
+
*
|
|
60
|
+
* ## Ambiguity is reported, never resolved
|
|
61
|
+
*
|
|
62
|
+
* Two live resources at one declared identity are two `unowned` entries with
|
|
63
|
+
* the same address, and no single tag write claims that address. Those
|
|
64
|
+
* candidates never enter the adoptable set; the Adopt step is handed them
|
|
65
|
+
* separately so the Op's own result names what it declined and why, beside
|
|
66
|
+
* what it wrote.
|
|
67
|
+
*
|
|
68
|
+
* ## Compensation
|
|
69
|
+
*
|
|
70
|
+
* Refused without a command, the same stance `TerraformApplyOp` takes.
|
|
71
|
+
* Un-adopting is `untag`: dropping this estate's marker from a live resource,
|
|
72
|
+
* which is a decision about the estate rather than something chant can
|
|
73
|
+
* synthesize. It is also not always the right undo — a resource that was
|
|
74
|
+
* already carrying the markers before this run is not one this run adopted.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* import { TerraformAdoptOp } from "@intentius/chant-lexicon-terraform";
|
|
79
|
+
*
|
|
80
|
+
* export const { op } = TerraformAdoptOp({ name: "estate-adopt", root: "estate" });
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
|
|
84
|
+
import { Op, phase, gate, activity, OpResource } from "@intentius/chant/op";
|
|
85
|
+
import {
|
|
86
|
+
choudoufuLiveCheck as checkStep,
|
|
87
|
+
choudoufuLivePlan as ledgerStep,
|
|
88
|
+
choudoufuAdopt as adoptStep,
|
|
89
|
+
} from "../op/builders";
|
|
90
|
+
|
|
91
|
+
export interface TerraformAdoptOpConfig {
|
|
92
|
+
/** Op name (kebab-case). `signalName` defaults to `approve-<name>`. */
|
|
93
|
+
name: string;
|
|
94
|
+
/** Key into the project's `terraform.roots`. Must be a live root: choudoufu, with a declared estate. */
|
|
95
|
+
root: string;
|
|
96
|
+
/**
|
|
97
|
+
* The estate whose markers this run looks for. Omitted, `choudoufuLivePlan`
|
|
98
|
+
* auto-detects it from the root's own `live` block or `estate.chdf.hcl`
|
|
99
|
+
* sidecar, which is the usual case.
|
|
100
|
+
*/
|
|
101
|
+
estate?: string;
|
|
102
|
+
/** Gate signal name. Default: `approve-<name>`, as `TerraformApplyOp` does. */
|
|
103
|
+
signalName?: string;
|
|
104
|
+
/** How long a recorded pending gate stays valid, as a duration string. Default: core's own (48h). */
|
|
105
|
+
gateTimeout?: string;
|
|
106
|
+
/** Override the gate description shown to the approver. */
|
|
107
|
+
gateDescription?: string;
|
|
108
|
+
/**
|
|
109
|
+
* Directory each step starts the `chant.config.*` search from, which is what
|
|
110
|
+
* `terraform.roots` and the root's relative `dir` resolve against. Default:
|
|
111
|
+
* the running process's cwd.
|
|
112
|
+
*/
|
|
113
|
+
cwd?: string;
|
|
114
|
+
/**
|
|
115
|
+
* Saga-style rollback on a failed adoption, run as an `onFailure` phase.
|
|
116
|
+
*
|
|
117
|
+
* Refused without a command. Undoing an adoption is `untag` — removing this
|
|
118
|
+
* estate's marker from a live resource — which is a decision about the
|
|
119
|
+
* estate, and one this Op cannot even scope correctly: a resource that
|
|
120
|
+
* already carried the markers is not one this run claimed. So `true`, or an
|
|
121
|
+
* object with no `command`, throws when the Op is built, naming the Op,
|
|
122
|
+
* rather than warning at the moment a rollback is already wanted.
|
|
123
|
+
*/
|
|
124
|
+
compensate?: boolean | { command?: string };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface TerraformAdoptOpResources {
|
|
128
|
+
/** Op resource. Generates the Check/Ledger/Gate/Adopt workflow. */
|
|
129
|
+
op: InstanceType<typeof OpResource>;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function TerraformAdoptOp(config: TerraformAdoptOpConfig): TerraformAdoptOpResources {
|
|
133
|
+
const compensateCommand = typeof config.compensate === "object" ? config.compensate.command : undefined;
|
|
134
|
+
if (config.compensate !== undefined && config.compensate !== false && compensateCommand === undefined) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`TerraformAdoptOp "${config.name}": compensate is enabled, but there is no automatic undo for an ` +
|
|
137
|
+
`adoption — un-adopting is untag, removing this estate's ownership marker from a live resource, ` +
|
|
138
|
+
`which is a decision about the estate rather than something chant can synthesize, and which this ` +
|
|
139
|
+
`Op cannot scope for you (a resource that already carried the markers is not one this run ` +
|
|
140
|
+
`claimed). Either supply compensate: { command: "..." } with an undo of your own, or set ` +
|
|
141
|
+
`compensate: false.`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const where = config.cwd ? { cwd: config.cwd } : {};
|
|
146
|
+
const estate = config.estate ? { estate: config.estate } : {};
|
|
147
|
+
|
|
148
|
+
const check = checkStep(config.root, { ...where });
|
|
149
|
+
check.outcomeAttribute = { name: "Refused", from: "refused" };
|
|
150
|
+
|
|
151
|
+
// `id` is what makes `.out` legal. The Adopt step below reads two fields of
|
|
152
|
+
// this one: the adoptable matches, and the contested candidates it must
|
|
153
|
+
// report and never write.
|
|
154
|
+
const ledger = ledgerStep(config.root, { ...where, ...estate, adoptionOnly: true, id: "ledger" });
|
|
155
|
+
ledger.outcomeAttribute = [
|
|
156
|
+
{ name: "Adoptable", from: "adoptable" },
|
|
157
|
+
{ name: "Ambiguous", from: "ambiguous" },
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
const adopt = adoptStep(config.root, {
|
|
161
|
+
...where,
|
|
162
|
+
adoptions: ledger.out.adoptions,
|
|
163
|
+
contested: ledger.out.contested,
|
|
164
|
+
});
|
|
165
|
+
adopt.outcomeAttribute = { name: "Adopted", from: "adoptedCount" };
|
|
166
|
+
|
|
167
|
+
const op = Op({
|
|
168
|
+
name: config.name,
|
|
169
|
+
overview: `Adopt the live resources the "${config.root}" choudoufu estate already declares`,
|
|
170
|
+
labels: {
|
|
171
|
+
Adopt: "true",
|
|
172
|
+
TerraformRoot: config.root,
|
|
173
|
+
TerraformMode: "live",
|
|
174
|
+
},
|
|
175
|
+
phases: [
|
|
176
|
+
phase("Check", [check]),
|
|
177
|
+
phase("Ledger", [ledger]),
|
|
178
|
+
phase("Gate", [
|
|
179
|
+
gate(config.signalName ?? `approve-${config.name}`, {
|
|
180
|
+
...(config.gateTimeout ? { timeout: config.gateTimeout } : {}),
|
|
181
|
+
description:
|
|
182
|
+
config.gateDescription ??
|
|
183
|
+
`Approve adopting live resources into the "${config.root}" estate. The Ledger phase's ` +
|
|
184
|
+
`adoption ledger is what is being approved: every resource it lists as adoptable gets this ` +
|
|
185
|
+
`estate's tofu-estate and tofu-address tags written onto it. The Adoptable and Ambiguous ` +
|
|
186
|
+
`run outcomes from that phase are its counts; an ambiguous address is never adopted.`,
|
|
187
|
+
}),
|
|
188
|
+
]),
|
|
189
|
+
phase("Adopt", [adopt]),
|
|
190
|
+
],
|
|
191
|
+
...(compensateCommand
|
|
192
|
+
? { onFailure: [phase("Rollback", [activity("shellCmd", { cmd: compensateCommand })])] }
|
|
193
|
+
: {}),
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
return { op };
|
|
197
|
+
}
|