@intentius/chant-lexicon-terraform 0.57.0 → 0.59.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/README.md +9 -0
- package/dist/composites/terraform-adopt-op.d.ts +131 -0
- package/dist/composites/terraform-adopt-op.d.ts.map +1 -0
- package/dist/composites/terraform-apply-op.d.ts +70 -17
- package/dist/composites/terraform-apply-op.d.ts.map +1 -1
- package/dist/composites/terraform-watch-op.d.ts +104 -37
- 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 +156 -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 +34 -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 +95 -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 +58 -6
- 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 +30 -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 +28 -0
- package/dist/lint/post-synth/tf026.d.ts.map +1 -0
- package/dist/lint/post-synth/tf027.d.ts +30 -0
- package/dist/lint/post-synth/tf027.d.ts.map +1 -0
- package/dist/lint/post-synth/tf028.d.ts +33 -0
- package/dist/lint/post-synth/tf028.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 +26 -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/okf/rules/TF027.md +11 -0
- package/dist/okf/rules/TF028.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 +466 -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 +48 -6
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/resolve-root-mode.d.ts +59 -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 +98 -18
- 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 +72 -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 +84 -0
- package/dist/rules/tf027.ts +74 -0
- package/dist/rules/tf028.ts +96 -0
- package/dist/skills/chant-terraform.md +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/package.json +7 -2
- package/src/__fixtures__/ACCEPTANCE.md +181 -0
- 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/__fixtures__/remote-backend/main.tf +34 -0
- package/src/__fixtures__/with-backend/main.tf +7 -0
- package/src/codegen/lint-rules-page.test.ts +45 -0
- package/src/composites/terraform-adopt-op.acceptance.test.ts +193 -0
- package/src/composites/terraform-adopt-op.test.ts +175 -0
- package/src/composites/terraform-adopt-op.ts +202 -0
- package/src/composites/terraform-apply-op.acceptance.test.ts +188 -5
- package/src/composites/terraform-apply-op.test.ts +179 -19
- package/src/composites/terraform-apply-op.ts +123 -30
- package/src/composites/terraform-watch-op.test.ts +169 -29
- package/src/composites/terraform-watch-op.ts +183 -75
- 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 +269 -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 +288 -3
- package/src/lint/audit-lineage.ts +384 -1
- package/src/lint/audit.test.ts +210 -3
- package/src/lint/post-synth/blocks.ts +109 -0
- package/src/lint/post-synth/fixtures/TF001/negative-cloud.tf +28 -0
- package/src/lint/post-synth/fixtures/TF001/negative.tf +30 -0
- package/src/lint/post-synth/fixtures/TF001/positive-local.tf +31 -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/TF027/live-default/main.tf +9 -0
- package/src/lint/post-synth/fixtures/TF027/live-untagged-delete/main.tf +13 -0
- package/src/lint/post-synth/fixtures/TF027/live-untagged-delete-scoped/main.tf +17 -0
- package/src/lint/post-synth/fixtures/TF027/live-untagged-report/main.tf +13 -0
- package/src/lint/post-synth/fixtures/TF028/live/main.tf +9 -0
- package/src/lint/post-synth/fixtures/TF028/stock/main.tf +10 -0
- package/src/lint/post-synth/fixtures/load.ts +108 -0
- package/src/lint/post-synth/index.ts +52 -0
- package/src/lint/post-synth/live-refusals-config-format.test.ts +143 -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 +73 -16
- 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 +98 -18
- 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 +72 -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 +84 -0
- package/src/lint/post-synth/tf027.test.ts +62 -0
- package/src/lint/post-synth/tf027.ts +74 -0
- package/src/lint/post-synth/tf028.test.ts +92 -0
- package/src/lint/post-synth/tf028.ts +96 -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 +12 -6
- package/src/op/activities/choudoufu.acceptance.test.ts +127 -0
- package/src/op/activities/choudoufu.test.ts +725 -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 +861 -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 +95 -5
- package/src/op/resolve-root-mode.test.ts +85 -0
- package/src/op/resolve-root-mode.ts +86 -0
- package/src/plugin.test.ts +106 -0
- package/src/plugin.ts +186 -29
- package/src/skills/chant-terraform.md +1 -1
- package/src/validate.ts +10 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
terraform {
|
|
2
|
+
required_version = ">= 1.5.0"
|
|
3
|
+
|
|
4
|
+
backend "s3" {
|
|
5
|
+
bucket = "tfstate"
|
|
6
|
+
key = "app/terraform.tfstate"
|
|
7
|
+
region = "us-east-1"
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
variable "region" {
|
|
12
|
+
type = string
|
|
13
|
+
default = "us-east-1"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module "cdn" {
|
|
17
|
+
source = "./modules/cdn"
|
|
18
|
+
region = var.region
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module "vpc" {
|
|
22
|
+
source = "terraform-aws-modules/vpc/aws"
|
|
23
|
+
version = "5.1.2"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
resource "aws_s3_bucket" "root_bucket" {
|
|
27
|
+
bucket = "root-bucket"
|
|
28
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Every reference form the index collects, once each, so a test can assert the
|
|
2
|
+
# whole vocabulary against one parse (chant #2112).
|
|
3
|
+
#
|
|
4
|
+
# var.<name> region, subnets
|
|
5
|
+
# local.<name> bucket_name, tags
|
|
6
|
+
# data.<type>.<name> data.aws_ami.ubuntu
|
|
7
|
+
# module.<name> module.cdn
|
|
8
|
+
# provider = <type>.<alias> provider.aws.replica, through both the
|
|
9
|
+
# `provider` meta-argument and a module's
|
|
10
|
+
# `providers` map
|
|
11
|
+
|
|
12
|
+
variable "region" {
|
|
13
|
+
type = string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
variable "subnets" {
|
|
17
|
+
type = list(string)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
locals {
|
|
21
|
+
bucket_name = "assets-${var.region}"
|
|
22
|
+
tags = { Team = "platform" }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
data "aws_ami" "ubuntu" {
|
|
26
|
+
most_recent = true
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
provider "aws" {
|
|
30
|
+
alias = "replica"
|
|
31
|
+
region = "eu-west-1"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
resource "aws_instance" "web" {
|
|
35
|
+
count = length(var.subnets)
|
|
36
|
+
ami = data.aws_ami.ubuntu.id
|
|
37
|
+
subnet_id = var.subnets[count.index]
|
|
38
|
+
tags = local.tags
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
resource "aws_s3_bucket" "replica" {
|
|
42
|
+
provider = aws.replica
|
|
43
|
+
bucket = "${local.bucket_name}-replica"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module "cdn" {
|
|
47
|
+
source = "./modules/cdn"
|
|
48
|
+
|
|
49
|
+
providers = {
|
|
50
|
+
aws = aws.replica
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
output "cdn_url" {
|
|
55
|
+
value = module.cdn.url
|
|
56
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# The `with-backend` root next door keeps its state local on purpose, so
|
|
2
|
+
# TF001 reports it (#2218). This one is the same configuration with a remote
|
|
3
|
+
# backend, and is what a root TF001 says nothing about looks like. Nothing
|
|
4
|
+
# ever runs `terraform init` here, so the bucket need not exist.
|
|
5
|
+
terraform {
|
|
6
|
+
required_version = ">= 1.5.0"
|
|
7
|
+
|
|
8
|
+
backend "s3" {
|
|
9
|
+
bucket = "tfstate"
|
|
10
|
+
key = "app/terraform.tfstate"
|
|
11
|
+
region = "us-east-1"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
required_providers {
|
|
15
|
+
null = {
|
|
16
|
+
source = "hashicorp/null"
|
|
17
|
+
version = "~> 3.2"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
provider "null" {}
|
|
23
|
+
|
|
24
|
+
resource "null_resource" "first" {
|
|
25
|
+
triggers = {
|
|
26
|
+
name = "first"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
resource "null_resource" "second" {
|
|
31
|
+
triggers = {
|
|
32
|
+
name = "second"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# The `terraform-apply-op` acceptance test copies this root into a temp
|
|
2
|
+
# directory and runs a real `terraform init`, `plan` and `apply` against it,
|
|
3
|
+
# so the backend has to be one that works with no credentials and no network
|
|
4
|
+
# beyond the provider registry. That is `local`, which is why TF001 reports
|
|
5
|
+
# this root (#2218): the finding is correct, and the local state is the point
|
|
6
|
+
# of the fixture. `remote-backend/` next door is the same configuration with
|
|
7
|
+
# a backend TF001 says nothing about.
|
|
1
8
|
terraform {
|
|
2
9
|
required_version = ">= 1.5.0"
|
|
3
10
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { describe, expect, test } from "vitest";
|
|
5
|
+
import { scanRules } from "@intentius/chant/codegen/docs-rule-scanning";
|
|
6
|
+
import { RULE_CATALOG } from "@intentius/chant/audit/catalog";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `docs/pages/lint-rules.mdx` is authored by hand, one `##` section per rule
|
|
10
|
+
* (#2108). Nothing regenerates those sections, so nothing stops the page
|
|
11
|
+
* lagging the catalog the moment a new rule ships without one. This test
|
|
12
|
+
* reads the same rule scan the generated `rules` table (`All Rules`) is built
|
|
13
|
+
* from and fails the moment a scanned id has no matching heading on the
|
|
14
|
+
* authored page, so the page cannot silently fall behind.
|
|
15
|
+
*/
|
|
16
|
+
const pkgDir = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The TF ids core owns rather than this lexicon (#2220). `scanRules` walks
|
|
20
|
+
* this package's `src/`, so it cannot see them: TF023 (Terraform state
|
|
21
|
+
* committed to the repository) lives in `packages/core/src/audit/catalog.ts`
|
|
22
|
+
* because it reads the discovered file list rather than a parsed root module,
|
|
23
|
+
* and its page section here was the one TF section nothing guarded. Read off
|
|
24
|
+
* `RULE_CATALOG` rather than hardcoded, so a second core-owned TF id is
|
|
25
|
+
* covered on the day it lands.
|
|
26
|
+
*/
|
|
27
|
+
function coreOwnedTfIds(): string[] {
|
|
28
|
+
return Object.keys(RULE_CATALOG).filter((id) => /^TF\d+$/.test(id));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe("lint-rules.mdx stays in sync with the rule catalog", () => {
|
|
32
|
+
test("every rule id scanned from source has a ## section on the authored page", () => {
|
|
33
|
+
const core = coreOwnedTfIds();
|
|
34
|
+
expect(core, "core owns at least TF023").toContain("TF023");
|
|
35
|
+
|
|
36
|
+
const ids = [...new Set([...scanRules(join(pkgDir, "src")).map((r) => r.id), ...core])].sort();
|
|
37
|
+
expect(ids.length).toBeGreaterThan(0);
|
|
38
|
+
|
|
39
|
+
const page = readFileSync(join(pkgDir, "docs", "pages", "lint-rules.mdx"), "utf-8");
|
|
40
|
+
const headingIds = new Set([...page.matchAll(/^## (TF\d+)\b/gm)].map((m) => m[1]));
|
|
41
|
+
|
|
42
|
+
const missing = ids.filter((id) => !headingIds.has(id));
|
|
43
|
+
expect(missing, `lint-rules.mdx has no section for: ${missing.join(", ")}`).toEqual([]);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,193 @@
|
|
|
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 still skips on choudoufu v0.14.0
|
|
24
|
+
*
|
|
25
|
+
* Not #894 any more. v0.14.0 (choudoufu PR 915) made `live-plan -json`
|
|
26
|
+
* reachable on a configuration that names its own estate, and this suite was
|
|
27
|
+
* run against it on 2026-09-07 for the first time. It got as far as the
|
|
28
|
+
* document and stopped there: `ledger.adoptions` came back empty, because the
|
|
29
|
+
* #788 document carries no adoptable-by-content section at all.
|
|
30
|
+
*
|
|
31
|
+
* The document's `unowned[]` is the resources found at an identity the
|
|
32
|
+
* configuration itself declares. A `aws_cloudwatch_log_group` has one (its
|
|
33
|
+
* name is in the block), so an unmarked live one comes back in `unowned[]`
|
|
34
|
+
* with `adopt_tofu_estate`/`adopt_tofu_address` on it, which is exactly what
|
|
35
|
+
* `../__fixtures__/live-plan.json` recorded and what `readAdoptionLedger`
|
|
36
|
+
* reads. An `aws_vpc` has none: EC2 assigns the id, so the document reports
|
|
37
|
+
* `omissions[].reason = "NEEDS_DISCOVERY"` and leaves `unowned[]` empty. The
|
|
38
|
+
* VPC is matched instead by choudoufu's content matcher during the
|
|
39
|
+
* estate-wide unclaimed sweep, and that match is printed only in the human
|
|
40
|
+
* render's "Adoptable" section. `views.LivePlanDocument` has no field for it,
|
|
41
|
+
* `-adoption-only` is refused alongside `-json` ("-adoption-only and -json
|
|
42
|
+
* cannot be combined"), and `TOFU_LIVE_COLLECT_UNCLAIMED=1` on the `-json`
|
|
43
|
+
* run makes no difference: the text render then prints "Adoptable: 1 live
|
|
44
|
+
* resource matches a declared resource" and the document beside it still says
|
|
45
|
+
* `"unowned": []`.
|
|
46
|
+
*
|
|
47
|
+
* So `choudoufuLivePlan` cannot produce a ledger for a provider-assigned
|
|
48
|
+
* identity on any binary that exists today, and this suite is what would
|
|
49
|
+
* prove it can. Filed upstream as
|
|
50
|
+
* [choudoufu #962](https://github.com/INTENTIUS/choudoufu/issues/962); the
|
|
51
|
+
* measurements are there and on chant #2168.
|
|
52
|
+
*
|
|
53
|
+
* The gate below names that, alongside the three ordinary dependencies: a
|
|
54
|
+
* `choudoufu`, an `aws` CLI, and the emulator's endpoint.
|
|
55
|
+
*
|
|
56
|
+
* Gating copied from `./terraform-apply-op.acceptance.test.ts` (`onPath`),
|
|
57
|
+
* which in turn copies `lexicons/k3s/src/serializer.acceptance.test.ts`'s
|
|
58
|
+
* pattern of skipping with the reason named in the describe title rather than
|
|
59
|
+
* failing when the real dependency is absent.
|
|
60
|
+
*
|
|
61
|
+
* `../__fixtures__/ACCEPTANCE.md` records what this block has last passed
|
|
62
|
+
* against, which is still nothing, and why the reason changed.
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
import { execSync } from "node:child_process";
|
|
66
|
+
import { cpSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
67
|
+
import { tmpdir } from "node:os";
|
|
68
|
+
import { join } from "node:path";
|
|
69
|
+
import { afterAll, describe, expect, it } from "vitest";
|
|
70
|
+
import { choudoufuAdopt, choudoufuLivePlan, terraformInit } from "../op/activities/terraform";
|
|
71
|
+
|
|
72
|
+
function onPath(cmd: string): boolean {
|
|
73
|
+
try {
|
|
74
|
+
execSync(`command -v ${cmd}`, { stdio: "ignore" });
|
|
75
|
+
return true;
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* `true` while `live-plan -json`'s document carries no adoptable-by-content
|
|
83
|
+
* section, so a provider-assigned identity like an `aws_vpc` never reaches
|
|
84
|
+
* `readAdoptionLedger`. Filed upstream as
|
|
85
|
+
* [choudoufu #962](https://github.com/INTENTIUS/choudoufu/issues/962), with
|
|
86
|
+
* the measurements against the v0.14.0 release binary on 2026-09-07; flip to
|
|
87
|
+
* `false` when a choudoufu release puts the content matcher's "Adoptable"
|
|
88
|
+
* rows in the document, either in `unowned[]` or in a sibling array.
|
|
89
|
+
* choudoufu #894, which gated this block before, is fixed and gone.
|
|
90
|
+
*/
|
|
91
|
+
const CHOUDOUFU_ADOPTABLE_NOT_IN_DOCUMENT = true;
|
|
92
|
+
|
|
93
|
+
const emulatorEndpoint = process.env.CHOUDOUFU_EMULATOR_ENDPOINT;
|
|
94
|
+
|
|
95
|
+
const skipReason = !onPath("choudoufu")
|
|
96
|
+
? "no choudoufu binary on PATH"
|
|
97
|
+
: !onPath("aws")
|
|
98
|
+
? "no aws CLI on PATH (the unmarked resource is created with it, and adopted through it)"
|
|
99
|
+
: !emulatorEndpoint
|
|
100
|
+
? "CHOUDOUFU_EMULATOR_ENDPOINT is not set (bring up choudoufu's `just smoke` emulator stack and export it)"
|
|
101
|
+
: CHOUDOUFU_ADOPTABLE_NOT_IN_DOCUMENT
|
|
102
|
+
? "choudoufu#962: live-plan -json's document carries no adoptable-by-content section, so the " +
|
|
103
|
+
"fixture's unmarked aws_vpc reaches omissions[NEEDS_DISCOVERY] and never unowned[]; " +
|
|
104
|
+
"measured on choudoufu v0.14.0"
|
|
105
|
+
: "";
|
|
106
|
+
|
|
107
|
+
const FIXTURE = join(import.meta.dirname, "..", "__fixtures__", "live-adopt");
|
|
108
|
+
const ESTATE = "chant-adopt-fixture";
|
|
109
|
+
const CIDR = "10.77.0.0/16";
|
|
110
|
+
const workspaces: string[] = [];
|
|
111
|
+
|
|
112
|
+
afterAll(() => {
|
|
113
|
+
for (const dir of workspaces) rmSync(dir, { recursive: true, force: true });
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
/** A fresh copy of the live-adopt fixture, wired up with a real project config. */
|
|
117
|
+
function project(): string {
|
|
118
|
+
const dir = mkdtempSync(join(tmpdir(), "chant-adopt-accept-"));
|
|
119
|
+
workspaces.push(dir);
|
|
120
|
+
cpSync(FIXTURE, join(dir, "root"), { recursive: true });
|
|
121
|
+
writeFileSync(
|
|
122
|
+
join(dir, "chant.config.json"),
|
|
123
|
+
JSON.stringify(
|
|
124
|
+
{ lexicons: ["terraform"], terraform: { binary: "choudoufu", roots: { estate: { dir: "./root" } } } },
|
|
125
|
+
null,
|
|
126
|
+
2,
|
|
127
|
+
),
|
|
128
|
+
);
|
|
129
|
+
return dir;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
describe.skipIf(skipReason !== "")(
|
|
133
|
+
`TerraformAdoptOp adopts an unmarked live resource${skipReason ? ` (skipped: ${skipReason})` : ""}`,
|
|
134
|
+
() => {
|
|
135
|
+
it(
|
|
136
|
+
"ledgers one adoptable VPC, writes its two markers, and re-plans with it owned",
|
|
137
|
+
{ timeout: 600_000 },
|
|
138
|
+
async () => {
|
|
139
|
+
const dir = project();
|
|
140
|
+
|
|
141
|
+
process.env.AWS_ENDPOINT_URL ??= emulatorEndpoint;
|
|
142
|
+
process.env.AWS_ACCESS_KEY_ID ??= "choudoufu-emulator";
|
|
143
|
+
process.env.AWS_SECRET_ACCESS_KEY ??= "choudoufu-emulator";
|
|
144
|
+
process.env.AWS_DEFAULT_REGION ??= "us-east-1";
|
|
145
|
+
|
|
146
|
+
// The stock-created resource: a VPC at the cidr this root declares,
|
|
147
|
+
// carrying no ownership marker at all. Nothing about it says which
|
|
148
|
+
// configuration made it, which is exactly the migration situation.
|
|
149
|
+
execSync(
|
|
150
|
+
`aws ec2 create-vpc --cidr-block ${CIDR} --endpoint-url ${emulatorEndpoint} --region us-east-1`,
|
|
151
|
+
{ stdio: "pipe" },
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
await terraformInit({ root: "estate", cwd: dir });
|
|
155
|
+
|
|
156
|
+
const ledger = await choudoufuLivePlan({ root: "estate", cwd: dir, adoptionOnly: true });
|
|
157
|
+
expect(ledger.estate).toBe(ESTATE);
|
|
158
|
+
expect(ledger.contested).toEqual([]);
|
|
159
|
+
expect(ledger.adoptions).toHaveLength(1);
|
|
160
|
+
const [candidate] = ledger.adoptions;
|
|
161
|
+
expect(candidate.addr).toBe("aws_vpc.adoptable");
|
|
162
|
+
expect(candidate.markerEstate).toBe(ESTATE);
|
|
163
|
+
expect(candidate.markerAddress).toBe("aws_vpc.adoptable");
|
|
164
|
+
// A VPC is tagged through `ec2 create-tags`, which choudoufu prints a
|
|
165
|
+
// command for; a candidate with none would be refused below instead.
|
|
166
|
+
expect(candidate.command).toContain("create-tags");
|
|
167
|
+
expect(ledger.ledger).toContain("aws_vpc.adoptable <- aws_vpc vpc-");
|
|
168
|
+
|
|
169
|
+
const adopted = await choudoufuAdopt({
|
|
170
|
+
root: "estate",
|
|
171
|
+
cwd: dir,
|
|
172
|
+
adoptions: ledger.adoptions,
|
|
173
|
+
contested: ledger.contested,
|
|
174
|
+
});
|
|
175
|
+
expect(adopted.mechanism).toBe("tag-write");
|
|
176
|
+
expect(adopted.adopted).toEqual(["aws_vpc.adoptable"]);
|
|
177
|
+
expect(adopted.refused).toEqual([]);
|
|
178
|
+
|
|
179
|
+
// The markers are the whole ownership answer, so a re-plan binds the
|
|
180
|
+
// same live VPC instead of proposing a second one: nothing is left
|
|
181
|
+
// unowned at this estate's declared identity, and nothing adoptable.
|
|
182
|
+
const after = await choudoufuLivePlan({ root: "estate", cwd: dir });
|
|
183
|
+
expect(after.adoptable).toBe(0);
|
|
184
|
+
expect(after.unowned).toBe(0);
|
|
185
|
+
expect(after.adoptions).toEqual([]);
|
|
186
|
+
|
|
187
|
+
// And the estate now says so: the VPC is bound at the declared address.
|
|
188
|
+
const bound = (after.json as { bound?: Array<{ addr?: string; identity?: string }> }).bound ?? [];
|
|
189
|
+
expect(bound.map((b) => b.addr)).toContain("aws_vpc.adoptable");
|
|
190
|
+
},
|
|
191
|
+
);
|
|
192
|
+
},
|
|
193
|
+
);
|
|
@@ -0,0 +1,175 @@
|
|
|
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)).gate).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
|
+
// #2202: `gateName` is the option; `signalName` is read through 0.59.0.
|
|
95
|
+
test("the deprecated `signalName` option still names the gate", () => {
|
|
96
|
+
expect(gateOf(props({ ...base, signalName: "ok" })).gate).toBe("ok");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("gate name, timeout and description are overridable", () => {
|
|
100
|
+
const gate = gateOf(props({ ...base, gateName: "ok", gateTimeout: "2h", gateDescription: "mine" }));
|
|
101
|
+
expect(gate).toMatchObject({ gate: "ok", timeout: "2h", description: "mine" });
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("no timeout given means the gate carries none, so core's own default applies", () => {
|
|
105
|
+
expect(gateOf(props(base)).timeout).toBeUndefined();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("TerraformAdoptOp adopts the ledger's matches and nothing else (#2105)", () => {
|
|
110
|
+
const adoptStep = (op: OpConfig): ActivityStep => op.phases[3].steps[0] as ActivityStep;
|
|
111
|
+
|
|
112
|
+
test("the Adopt step takes the ledger's adoptable set by reference", () => {
|
|
113
|
+
const adopt = adoptStep(props(base));
|
|
114
|
+
expect(adopt.fn).toBe("choudoufuAdopt");
|
|
115
|
+
expect(isStepOutputRef(adopt.args?.adoptions)).toBe(true);
|
|
116
|
+
expect(adopt.args?.adoptions).toMatchObject({ step: "ledger", path: "adoptions" });
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("an ambiguous match is handed over as `contested`, which is a separate field from `adoptions`", () => {
|
|
120
|
+
// `readAdoptionLedger` never puts a contested candidate in `adoptions`, so
|
|
121
|
+
// the Adopt step cannot write one however it behaves; `contested` is
|
|
122
|
+
// passed so the Op's own result names what it declined and why.
|
|
123
|
+
const adopt = adoptStep(props(base));
|
|
124
|
+
expect(adopt.args?.contested).toMatchObject({ step: "ledger", path: "contested" });
|
|
125
|
+
expect(adopt.args?.contested).not.toEqual(adopt.args?.adoptions);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("the Adopt step reports how many resources it claimed", () => {
|
|
129
|
+
expect(adoptStep(props(base)).outcomeAttribute).toEqual({ name: "Adopted", from: "adoptedCount" });
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("the Op references exactly the two ledger fields and no document", () => {
|
|
133
|
+
const op = props(base);
|
|
134
|
+
const refs = op.phases.flatMap((p) => p.steps.filter(isActivity)).flatMap((s) => collectStepOutputRefs(s.args));
|
|
135
|
+
expect(refs.map((r) => r.path).sort()).toEqual(["adoptions", "contested"]);
|
|
136
|
+
expect(refs.every((r) => r.step === "ledger")).toBe(true);
|
|
137
|
+
// The bound/omissions/unowned document never leaves the Ledger step.
|
|
138
|
+
expect(JSON.stringify(op)).not.toContain('"json"');
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe("TerraformAdoptOp compensation (#2105)", () => {
|
|
143
|
+
test("compensate: true throws, because un-adopting is untag", () => {
|
|
144
|
+
expect(() => TerraformAdoptOp({ ...base, compensate: true })).toThrow(/un-adopting is untag/);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("an object with no command throws the same way", () => {
|
|
148
|
+
expect(() => TerraformAdoptOp({ ...base, compensate: {} })).toThrow(/TerraformAdoptOp "estate-adopt"/);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("a supplied command becomes an onFailure Rollback phase", () => {
|
|
152
|
+
const op = props({ ...base, compensate: { command: "./untag.sh" } });
|
|
153
|
+
expect(op.onFailure?.map((p) => p.name)).toEqual(["Rollback"]);
|
|
154
|
+
expect((op.onFailure![0].steps[0] as ActivityStep).args?.cmd).toBe("./untag.sh");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("compensate: false and omitting it both leave no onFailure", () => {
|
|
158
|
+
expect(props(base).onFailure).toBeUndefined();
|
|
159
|
+
expect(props({ ...base, compensate: false }).onFailure).toBeUndefined();
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe("TerraformAdoptOp labels (#2105, #2118)", () => {
|
|
164
|
+
test("marks the Op as an adoption on a live root", () => {
|
|
165
|
+
expect(props(base).labels).toEqual({
|
|
166
|
+
Adopt: "true",
|
|
167
|
+
TerraformRoot: "estate",
|
|
168
|
+
TerraformMode: "live",
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("no task queue rides on the Op (#2118)", () => {
|
|
173
|
+
expect(props(base)).not.toHaveProperty("taskQueue");
|
|
174
|
+
});
|
|
175
|
+
});
|