@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,202 @@
|
|
|
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). `gateName` 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
|
+
/** The gate's name. Default: `approve-<name>`, as `TerraformApplyOp` does. */
|
|
103
|
+
gateName?: string;
|
|
104
|
+
/**
|
|
105
|
+
* @deprecated The gate's name was `signalName` through 0.58.0 and is
|
|
106
|
+
* `gateName` since #2202, matching `TerraformApplyOp`. Removed in 0.60.0.
|
|
107
|
+
*/
|
|
108
|
+
signalName?: string;
|
|
109
|
+
/** How long a recorded pending gate stays valid, as a duration string. Default: core's own (48h). */
|
|
110
|
+
gateTimeout?: string;
|
|
111
|
+
/** Override the gate description shown to the approver. */
|
|
112
|
+
gateDescription?: string;
|
|
113
|
+
/**
|
|
114
|
+
* Directory each step starts the `chant.config.*` search from, which is what
|
|
115
|
+
* `terraform.roots` and the root's relative `dir` resolve against. Default:
|
|
116
|
+
* the running process's cwd.
|
|
117
|
+
*/
|
|
118
|
+
cwd?: string;
|
|
119
|
+
/**
|
|
120
|
+
* Saga-style rollback on a failed adoption, run as an `onFailure` phase.
|
|
121
|
+
*
|
|
122
|
+
* Refused without a command. Undoing an adoption is `untag` — removing this
|
|
123
|
+
* estate's marker from a live resource — which is a decision about the
|
|
124
|
+
* estate, and one this Op cannot even scope correctly: a resource that
|
|
125
|
+
* already carried the markers is not one this run claimed. So `true`, or an
|
|
126
|
+
* object with no `command`, throws when the Op is built, naming the Op,
|
|
127
|
+
* rather than warning at the moment a rollback is already wanted.
|
|
128
|
+
*/
|
|
129
|
+
compensate?: boolean | { command?: string };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface TerraformAdoptOpResources {
|
|
133
|
+
/** Op resource. Generates the Check/Ledger/Gate/Adopt workflow. */
|
|
134
|
+
op: InstanceType<typeof OpResource>;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function TerraformAdoptOp(config: TerraformAdoptOpConfig): TerraformAdoptOpResources {
|
|
138
|
+
const compensateCommand = typeof config.compensate === "object" ? config.compensate.command : undefined;
|
|
139
|
+
if (config.compensate !== undefined && config.compensate !== false && compensateCommand === undefined) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`TerraformAdoptOp "${config.name}": compensate is enabled, but there is no automatic undo for an ` +
|
|
142
|
+
`adoption — un-adopting is untag, removing this estate's ownership marker from a live resource, ` +
|
|
143
|
+
`which is a decision about the estate rather than something chant can synthesize, and which this ` +
|
|
144
|
+
`Op cannot scope for you (a resource that already carried the markers is not one this run ` +
|
|
145
|
+
`claimed). Either supply compensate: { command: "..." } with an undo of your own, or set ` +
|
|
146
|
+
`compensate: false.`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const where = config.cwd ? { cwd: config.cwd } : {};
|
|
151
|
+
const estate = config.estate ? { estate: config.estate } : {};
|
|
152
|
+
|
|
153
|
+
const check = checkStep(config.root, { ...where });
|
|
154
|
+
check.outcomeAttribute = { name: "Refused", from: "refused" };
|
|
155
|
+
|
|
156
|
+
// `id` is what makes `.out` legal. The Adopt step below reads two fields of
|
|
157
|
+
// this one: the adoptable matches, and the contested candidates it must
|
|
158
|
+
// report and never write.
|
|
159
|
+
const ledger = ledgerStep(config.root, { ...where, ...estate, adoptionOnly: true, id: "ledger" });
|
|
160
|
+
ledger.outcomeAttribute = [
|
|
161
|
+
{ name: "Adoptable", from: "adoptable" },
|
|
162
|
+
{ name: "Ambiguous", from: "ambiguous" },
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
const adopt = adoptStep(config.root, {
|
|
166
|
+
...where,
|
|
167
|
+
adoptions: ledger.out.adoptions,
|
|
168
|
+
contested: ledger.out.contested,
|
|
169
|
+
});
|
|
170
|
+
adopt.outcomeAttribute = { name: "Adopted", from: "adoptedCount" };
|
|
171
|
+
|
|
172
|
+
const op = Op({
|
|
173
|
+
name: config.name,
|
|
174
|
+
overview: `Adopt the live resources the "${config.root}" choudoufu estate already declares`,
|
|
175
|
+
labels: {
|
|
176
|
+
Adopt: "true",
|
|
177
|
+
TerraformRoot: config.root,
|
|
178
|
+
TerraformMode: "live",
|
|
179
|
+
},
|
|
180
|
+
phases: [
|
|
181
|
+
phase("Check", [check]),
|
|
182
|
+
phase("Ledger", [ledger]),
|
|
183
|
+
phase("Gate", [
|
|
184
|
+
gate(config.gateName ?? config.signalName ?? `approve-${config.name}`, {
|
|
185
|
+
...(config.gateTimeout ? { timeout: config.gateTimeout } : {}),
|
|
186
|
+
description:
|
|
187
|
+
config.gateDescription ??
|
|
188
|
+
`Approve adopting live resources into the "${config.root}" estate. The Ledger phase's ` +
|
|
189
|
+
`adoption ledger is what is being approved: every resource it lists as adoptable gets this ` +
|
|
190
|
+
`estate's tofu-estate and tofu-address tags written onto it. The Adoptable and Ambiguous ` +
|
|
191
|
+
`run outcomes from that phase are its counts; an ambiguous address is never adopted.`,
|
|
192
|
+
}),
|
|
193
|
+
]),
|
|
194
|
+
phase("Adopt", [adopt]),
|
|
195
|
+
],
|
|
196
|
+
...(compensateCommand
|
|
197
|
+
? { onFailure: [phase("Rollback", [activity("shellCmd", { cmd: compensateCommand })])] }
|
|
198
|
+
: {}),
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
return { op };
|
|
202
|
+
}
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Everything else in this lexicon's Op suite proves the shape of what chant
|
|
5
5
|
* emits. This proves the shape runs: build a `TerraformApplyOp` with
|
|
6
|
-
* `gate: "never"` (
|
|
6
|
+
* `gate: "never"` (a gate ends a local run as `gated` since #2119, so an
|
|
7
|
+
* acceptance run that must reach Apply declares none), hand
|
|
7
8
|
* it to `runOpLocally` with the activities the registry resolves by
|
|
8
9
|
* convention, and let a real `terraform` — or `tofu` — init, plan and apply
|
|
9
10
|
* the #2083 `with-backend` fixture root into a temp directory. Two
|
|
@@ -19,17 +20,29 @@
|
|
|
19
20
|
*
|
|
20
21
|
* Gating copied from `lexicons/k3s/src/serializer.acceptance.test.ts`, which
|
|
21
22
|
* skips the same way when Docker is absent.
|
|
23
|
+
*
|
|
24
|
+
* `../__fixtures__/ACCEPTANCE.md` records what both blocks in this file have
|
|
25
|
+
* last passed against, with the binary version and the date (#2220). Update
|
|
26
|
+
* it in the same change as a run.
|
|
22
27
|
*/
|
|
23
28
|
|
|
24
29
|
import { execSync } from "node:child_process";
|
|
25
30
|
import { lookup } from "node:dns/promises";
|
|
26
|
-
import { cpSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
31
|
+
import { cpSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
27
32
|
import { tmpdir } from "node:os";
|
|
28
33
|
import { join } from "node:path";
|
|
29
34
|
import { afterAll, describe, expect, it } from "vitest";
|
|
30
35
|
import { loadActivities, loadProfiles, runOpLocally, type OpConfig } from "@intentius/chant/op";
|
|
31
36
|
import { TerraformApplyOp } from "./terraform-apply-op";
|
|
32
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
MIN_CHOUDOUFU_VERSION,
|
|
39
|
+
isOlderVersion,
|
|
40
|
+
parseChoudoufuVersion,
|
|
41
|
+
terraformApply,
|
|
42
|
+
terraformInit,
|
|
43
|
+
terraformPlan,
|
|
44
|
+
terraformShow,
|
|
45
|
+
} from "../op/activities/terraform";
|
|
33
46
|
|
|
34
47
|
function onPath(cmd: string): boolean {
|
|
35
48
|
try {
|
|
@@ -113,7 +126,7 @@ describe.skipIf(skipReason !== "")(
|
|
|
113
126
|
await loadProfiles(),
|
|
114
127
|
);
|
|
115
128
|
|
|
116
|
-
expect(result.
|
|
129
|
+
expect(result.status).toBe("ok");
|
|
117
130
|
expect(result.records.map((r) => `${r.phase}:${r.status}`)).toEqual([
|
|
118
131
|
"Init:ok",
|
|
119
132
|
"Plan:ok",
|
|
@@ -121,7 +134,7 @@ describe.skipIf(skipReason !== "")(
|
|
|
121
134
|
]);
|
|
122
135
|
|
|
123
136
|
// The plan proposed two creates, and said so through the outcome the
|
|
124
|
-
// Plan step surfaces as the `Changed`
|
|
137
|
+
// Plan step surfaces as the `Changed` run outcome.
|
|
125
138
|
const plan = result.records.find((r) => r.phase === "Plan")!;
|
|
126
139
|
expect(plan.outcome).toEqual({ name: "Changed", value: true });
|
|
127
140
|
|
|
@@ -136,3 +149,173 @@ describe.skipIf(skipReason !== "")(
|
|
|
136
149
|
);
|
|
137
150
|
},
|
|
138
151
|
);
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* `TerraformApplyOp` on a live root, against choudoufu's own pinned emulator
|
|
155
|
+
* (#2106 follow-up). Two suites, gated the same way
|
|
156
|
+
* `../op/activities/choudoufu.acceptance.test.ts` is, plus a version floor:
|
|
157
|
+
*
|
|
158
|
+
* - no `choudoufu` on PATH,
|
|
159
|
+
* - a `choudoufu` older than {@link MIN_CHOUDOUFU_VERSION}: v0.13.0 shipped
|
|
160
|
+
* the approval artifact
|
|
161
|
+
* ([choudoufu #878](https://github.com/INTENTIUS/choudoufu/issues/878),
|
|
162
|
+
* PR 889), before which `plan -out` was refused under a live block and
|
|
163
|
+
* this Op could not be built the way it is built now, and v0.14.0 moved
|
|
164
|
+
* the floor again for the document the other two Ops read
|
|
165
|
+
* ([choudoufu #894](https://github.com/INTENTIUS/choudoufu/issues/894)),
|
|
166
|
+
* - `CHOUDOUFU_EMULATOR_ENDPOINT` unset (bring up choudoufu's `just smoke`
|
|
167
|
+
* docker compose stack and export `http://localhost:<mapped port>`).
|
|
168
|
+
*
|
|
169
|
+
* The #894 clause the first version of this suite carried is gone, and stayed
|
|
170
|
+
* gone: that issue was about `live-plan -json`, which the apply Op does not
|
|
171
|
+
* run at all, since the plan half is the stock `plan -out` path.
|
|
172
|
+
*
|
|
173
|
+
* The first test is the happy path end to end through `runOpLocally`. The
|
|
174
|
+
* second is the refusal, and it runs the activities directly rather than
|
|
175
|
+
* through the Op, because the world has to move between the Plan step and the
|
|
176
|
+
* Apply step and there is no seam inside a running Op to do that from.
|
|
177
|
+
*/
|
|
178
|
+
|
|
179
|
+
function versionOf(binary: string): string | undefined {
|
|
180
|
+
try {
|
|
181
|
+
return parseChoudoufuVersion(execSync(`${binary} version`, { encoding: "utf8" }));
|
|
182
|
+
} catch {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const LIVE_FIXTURE = join(import.meta.dirname, "..", "__fixtures__", "live");
|
|
188
|
+
|
|
189
|
+
/** A throwaway project holding a copy of the live fixture as its one root. */
|
|
190
|
+
function liveProject(): string {
|
|
191
|
+
const dir = mkdtempSync(join(tmpdir(), "chant-choudoufu-apply-accept-"));
|
|
192
|
+
workspaces.push(dir);
|
|
193
|
+
cpSync(LIVE_FIXTURE, join(dir, "root"), { recursive: true });
|
|
194
|
+
writeFileSync(
|
|
195
|
+
join(dir, "chant.config.json"),
|
|
196
|
+
JSON.stringify(
|
|
197
|
+
{ lexicons: ["terraform"], terraform: { binary: "choudoufu", roots: { estate: { dir: "./root" } } } },
|
|
198
|
+
null,
|
|
199
|
+
2,
|
|
200
|
+
),
|
|
201
|
+
);
|
|
202
|
+
return dir;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** The estate-wide marker sweep configures the AWS provider whatever the root declares. */
|
|
206
|
+
function pointAtTheEmulator(endpoint: string): void {
|
|
207
|
+
process.env.AWS_ENDPOINT_URL ??= endpoint;
|
|
208
|
+
process.env.AWS_ACCESS_KEY_ID ??= "choudoufu-emulator";
|
|
209
|
+
process.env.AWS_SECRET_ACCESS_KEY ??= "choudoufu-emulator";
|
|
210
|
+
process.env.AWS_DEFAULT_REGION ??= "us-east-1";
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const choudoufuVersion = onPath("choudoufu") ? versionOf("choudoufu") : undefined;
|
|
214
|
+
const emulatorEndpoint = process.env.CHOUDOUFU_EMULATOR_ENDPOINT;
|
|
215
|
+
|
|
216
|
+
const liveSkipReason: string = !onPath("choudoufu")
|
|
217
|
+
? "no choudoufu binary on PATH"
|
|
218
|
+
: choudoufuVersion === undefined
|
|
219
|
+
? "the choudoufu on PATH reports no release version (a dev build), so the approval artifact cannot be assumed"
|
|
220
|
+
: isOlderVersion(choudoufuVersion, MIN_CHOUDOUFU_VERSION)
|
|
221
|
+
? `choudoufu ${choudoufuVersion} is older than v${MIN_CHOUDOUFU_VERSION}, the lexicon's floor (choudoufu #878's approval artifact in v0.13.0, #894's -json document in v0.14.0)`
|
|
222
|
+
: !emulatorEndpoint
|
|
223
|
+
? "CHOUDOUFU_EMULATOR_ENDPOINT is not set (bring up choudoufu's `just smoke` emulator stack and export it)"
|
|
224
|
+
: "";
|
|
225
|
+
|
|
226
|
+
describe.skipIf(liveSkipReason !== "")(
|
|
227
|
+
`TerraformApplyOp applies a live root against choudoufu's emulator${liveSkipReason ? ` (skipped: ${liveSkipReason})` : ""}`,
|
|
228
|
+
() => {
|
|
229
|
+
it(
|
|
230
|
+
'inits, plans to a file, applies that file, gate: "never" — two null_resources applied',
|
|
231
|
+
{ timeout: 600_000 },
|
|
232
|
+
async () => {
|
|
233
|
+
pointAtTheEmulator(emulatorEndpoint!);
|
|
234
|
+
const project = liveProject();
|
|
235
|
+
|
|
236
|
+
const { op } = TerraformApplyOp({
|
|
237
|
+
name: "choudoufu-acceptance",
|
|
238
|
+
root: "estate",
|
|
239
|
+
gate: "never",
|
|
240
|
+
cwd: project,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// The plan file is what crosses the (skipped) gate, so the built Op
|
|
244
|
+
// has to name one even here, where nothing waits on it.
|
|
245
|
+
const applyStep = (op as unknown as { props: OpConfig }).props.phases
|
|
246
|
+
.find((p) => p.name === "Apply")!
|
|
247
|
+
.steps[0] as { args?: Record<string, unknown> };
|
|
248
|
+
expect(applyStep.args?.planFile).toBeDefined();
|
|
249
|
+
|
|
250
|
+
const activities = await loadActivities(["terraform"]);
|
|
251
|
+
const result = await runOpLocally(
|
|
252
|
+
(op as unknown as { props: OpConfig }).props,
|
|
253
|
+
activities,
|
|
254
|
+
await loadProfiles(),
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
expect(result.status).toBe("ok");
|
|
258
|
+
expect(result.records.map((r) => `${r.phase}:${r.status}`)).toEqual(["Init:ok", "Plan:ok", "Apply:ok"]);
|
|
259
|
+
|
|
260
|
+
// A live root has no state file to read back: the state cache is not
|
|
261
|
+
// stock's `terraform.tfstate` and `show` over it renders nothing. The
|
|
262
|
+
// estate's own answer is the next plan, which is built from the live
|
|
263
|
+
// system, so what was applied is read back the way choudoufu means it
|
|
264
|
+
// to be. Empty, and its prior state holds exactly the two resources.
|
|
265
|
+
const after = await terraformPlan({ root: "estate", cwd: project });
|
|
266
|
+
expect(after.changed).toBe(false);
|
|
267
|
+
expect({ adds: after.adds, changes: after.changes, destroys: after.destroys }).toEqual({
|
|
268
|
+
adds: 0,
|
|
269
|
+
changes: 0,
|
|
270
|
+
destroys: 0,
|
|
271
|
+
});
|
|
272
|
+
const applied = (
|
|
273
|
+
(after.json as { prior_state?: { values?: { root_module?: { resources?: Array<{ address?: string }> } } } })
|
|
274
|
+
.prior_state?.values?.root_module?.resources ?? []
|
|
275
|
+
).map((r) => r.address);
|
|
276
|
+
expect(applied.sort()).toEqual(["null_resource.first", "null_resource.second"]);
|
|
277
|
+
},
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
it(
|
|
281
|
+
"a plan file the live system has moved past comes back as a named refusal, not a throw",
|
|
282
|
+
{ timeout: 600_000 },
|
|
283
|
+
async () => {
|
|
284
|
+
pointAtTheEmulator(emulatorEndpoint!);
|
|
285
|
+
const project = liveProject();
|
|
286
|
+
const mainTf = join(project, "root", "main.tf");
|
|
287
|
+
const original = readFileSync(mainTf, "utf8");
|
|
288
|
+
|
|
289
|
+
await terraformInit({ root: "estate", cwd: project });
|
|
290
|
+
const plan = await terraformPlan({ root: "estate", cwd: project });
|
|
291
|
+
expect(plan.changed).toBe(true);
|
|
292
|
+
expect(plan.adds).toBe(2);
|
|
293
|
+
|
|
294
|
+
// The configuration moves after the approval: one of the two resources
|
|
295
|
+
// the approver read is gone, so the fresh plan the apply builds has a
|
|
296
|
+
// change the file does not, and the file has one the fresh plan does not.
|
|
297
|
+
writeFileSync(mainTf, original.replace(/resource "null_resource" "second" \{[\s\S]*?\n\}\n/, ""));
|
|
298
|
+
|
|
299
|
+
const refusedRun = await terraformApply({ root: "estate", cwd: project, planFile: plan.planFile });
|
|
300
|
+
expect(refusedRun.applied).toBe(false);
|
|
301
|
+
expect(refusedRun.refused).toBe("approval-mismatch");
|
|
302
|
+
expect(refusedRun.refusal).toContain("The approved plan no longer matches the live system");
|
|
303
|
+
expect(refusedRun.refusal).toContain("null_resource.second");
|
|
304
|
+
|
|
305
|
+
// The sibling refusal, from the same exit status: the file was produced
|
|
306
|
+
// against another estate than the one this directory now declares.
|
|
307
|
+
writeFileSync(mainTf, original.replace('estate = "fixture-estate"', 'estate = "renamed-estate"'));
|
|
308
|
+
const wrongEstate = await terraformApply({ root: "estate", cwd: project, planFile: plan.planFile });
|
|
309
|
+
expect(wrongEstate.applied).toBe(false);
|
|
310
|
+
expect(wrongEstate.refused).toBe("wrong-estate");
|
|
311
|
+
expect(wrongEstate.refusal).toContain("The approved plan belongs to a different estate");
|
|
312
|
+
|
|
313
|
+
// And with the world back where the approval found it, the same file applies.
|
|
314
|
+
writeFileSync(mainTf, original);
|
|
315
|
+
const applied = await terraformApply({ root: "estate", cwd: project, planFile: plan.planFile });
|
|
316
|
+
expect(applied.applied).toBe(true);
|
|
317
|
+
expect(applied.refused).toBeUndefined();
|
|
318
|
+
},
|
|
319
|
+
);
|
|
320
|
+
},
|
|
321
|
+
);
|