@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,598 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* terraform describeResources on a live root (#2104).
|
|
3
|
+
*
|
|
4
|
+
* The declared side is real: `renderTerraformRoots` parses
|
|
5
|
+
* `src/__fixtures__/live-estate/` under `binary: "choudoufu"` exactly as
|
|
6
|
+
* `buildRoots()` does, so every key matched here is a key a build produces,
|
|
7
|
+
* and the sidecar in that directory is what makes the root live.
|
|
8
|
+
*
|
|
9
|
+
* The live side is real too. `src/__fixtures__/live-plan.json` and
|
|
10
|
+
* `src/__fixtures__/live-ls.json` were recorded from that same configuration
|
|
11
|
+
* by a choudoufu built from source, running against choudoufu's own pinned
|
|
12
|
+
* floci emulator; `src/__fixtures__/live-estate/README.md` is the recording
|
|
13
|
+
* log, including the two ways the recording could not be a chant live root.
|
|
14
|
+
*
|
|
15
|
+
* Nothing here runs choudoufu: the activities are injected, and the stock
|
|
16
|
+
* `show` activity throws if the reader ever reaches for it.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { cpSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
20
|
+
import { tmpdir } from "node:os";
|
|
21
|
+
import { dirname, join } from "node:path";
|
|
22
|
+
import { fileURLToPath } from "node:url";
|
|
23
|
+
import { afterAll, describe, expect, it } from "vitest";
|
|
24
|
+
import { describeObservationConformance } from "@intentius/chant-test-utils";
|
|
25
|
+
import { normalizeObservation } from "@intentius/chant/observation";
|
|
26
|
+
import type { Declarable } from "@intentius/chant/declarable";
|
|
27
|
+
import {
|
|
28
|
+
ambientKinds,
|
|
29
|
+
describeResources,
|
|
30
|
+
indexLivePlan,
|
|
31
|
+
liveRootNames,
|
|
32
|
+
observeAmbient,
|
|
33
|
+
readLiveLs,
|
|
34
|
+
teardownOwned,
|
|
35
|
+
LIVE_PLAN_OMISSION_REASONS,
|
|
36
|
+
TERRAFORM_LIVE_MARKER_KEYS,
|
|
37
|
+
type TerraformReadDeps,
|
|
38
|
+
} from "./describe-resources";
|
|
39
|
+
import { renderTerraformRoots } from "./hcl/roots";
|
|
40
|
+
|
|
41
|
+
const fixtures = join(dirname(fileURLToPath(import.meta.url)), "__fixtures__");
|
|
42
|
+
const ROOT_DIR = join(fixtures, "live-estate");
|
|
43
|
+
const PLAN: unknown = JSON.parse(readFileSync(join(fixtures, "live-plan.json"), "utf-8"));
|
|
44
|
+
const LS: unknown = JSON.parse(readFileSync(join(fixtures, "live-ls.json"), "utf-8"));
|
|
45
|
+
const ESTATE = "stateless-e2e-block";
|
|
46
|
+
|
|
47
|
+
const workspaces: string[] = [];
|
|
48
|
+
afterAll(() => {
|
|
49
|
+
for (const dir of workspaces) rmSync(dir, { recursive: true, force: true });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
/** A real project on disk: the fixture root plus the config that makes it live. */
|
|
53
|
+
function project(): string {
|
|
54
|
+
const dir = mkdtempSync(join(tmpdir(), "chant-terraform-live-"));
|
|
55
|
+
workspaces.push(dir);
|
|
56
|
+
cpSync(ROOT_DIR, join(dir, "root"), { recursive: true });
|
|
57
|
+
writeFileSync(
|
|
58
|
+
join(dir, "chant.config.json"),
|
|
59
|
+
JSON.stringify({
|
|
60
|
+
lexicons: ["terraform"],
|
|
61
|
+
terraform: { binary: "choudoufu", roots: { estate: { dir: "./root" } } },
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
return dir;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** `buildRoots()`'s own render of the live fixture root, keyed `<root>/<address>`. */
|
|
68
|
+
async function declaredEntities(): Promise<Map<string, { entityType: string; props: Record<string, unknown> }>> {
|
|
69
|
+
const { entities } = await renderTerraformRoots({
|
|
70
|
+
projectRoot: fixtures,
|
|
71
|
+
roots: { estate: { dir: "./live-estate" } },
|
|
72
|
+
binary: "choudoufu",
|
|
73
|
+
});
|
|
74
|
+
const out = new Map<string, { entityType: string; props: Record<string, unknown> }>();
|
|
75
|
+
for (const [key, entity] of entities) {
|
|
76
|
+
out.set(key, {
|
|
77
|
+
entityType: entity.entityType,
|
|
78
|
+
props: (entity as Declarable & { props: Record<string, unknown> }).props,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** A stock activity that must never be reached on a live root. */
|
|
85
|
+
const neverStock = (() => {
|
|
86
|
+
throw new Error("terraform show was run on a live root: there is no state file to show");
|
|
87
|
+
}) as never;
|
|
88
|
+
|
|
89
|
+
function deps(overrides?: Partial<TerraformReadDeps>): TerraformReadDeps {
|
|
90
|
+
return {
|
|
91
|
+
init: neverStock,
|
|
92
|
+
show: neverStock,
|
|
93
|
+
livePlan: (async () => ({
|
|
94
|
+
drift: true,
|
|
95
|
+
json: PLAN,
|
|
96
|
+
text: "",
|
|
97
|
+
// The adoption ledger fields (#2105) are a projection of the same
|
|
98
|
+
// document; `describeResources` reads none of them, so the stub carries
|
|
99
|
+
// the empty shape rather than a second copy of PLAN's unowned rows.
|
|
100
|
+
ledger: "",
|
|
101
|
+
finding: "",
|
|
102
|
+
adoptions: [],
|
|
103
|
+
contested: [],
|
|
104
|
+
ambiguous: 0,
|
|
105
|
+
// Plan change counts (#2106), likewise unread here.
|
|
106
|
+
adds: 0,
|
|
107
|
+
changes: 0,
|
|
108
|
+
destroys: 0,
|
|
109
|
+
dir: ROOT_DIR,
|
|
110
|
+
documentPath: "chant.live-plan.json",
|
|
111
|
+
estate: ESTATE,
|
|
112
|
+
unowned: 2,
|
|
113
|
+
adoptable: 1,
|
|
114
|
+
})) as TerraformReadDeps["livePlan"],
|
|
115
|
+
liveLs: (async () => ({ json: LS, dir: ROOT_DIR, estate: ESTATE })) as TerraformReadDeps["liveLs"],
|
|
116
|
+
...overrides,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const failingPlan = (message: string): TerraformReadDeps["livePlan"] =>
|
|
121
|
+
(async () => {
|
|
122
|
+
throw new Error(message);
|
|
123
|
+
}) as TerraformReadDeps["livePlan"];
|
|
124
|
+
|
|
125
|
+
async function options(overrides?: { owned?: boolean; only?: string[]; extra?: Map<string, { entityType: string; props: Record<string, unknown> }> }) {
|
|
126
|
+
const entities = overrides?.extra ?? (await declaredEntities());
|
|
127
|
+
const entityNames = overrides?.only ?? [...entities.keys()].sort();
|
|
128
|
+
return {
|
|
129
|
+
environment: "prod",
|
|
130
|
+
buildOutput: "",
|
|
131
|
+
entityNames,
|
|
132
|
+
entities,
|
|
133
|
+
...(overrides?.owned ? { owned: true } : {}),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
describe("the live fixture root is what buildRoots() calls live (#2103)", () => {
|
|
138
|
+
it("stamps mode live and the estate on every entity of the root", async () => {
|
|
139
|
+
const entities = await declaredEntities();
|
|
140
|
+
expect(entities.size).toBeGreaterThan(0);
|
|
141
|
+
for (const entity of entities.values()) {
|
|
142
|
+
expect(entity.props.mode).toBe("live");
|
|
143
|
+
expect(entity.props.estate).toBe(ESTATE);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("keys the count block by its block address, which the document keys by instance", async () => {
|
|
148
|
+
const entities = await declaredEntities();
|
|
149
|
+
expect([...entities.keys()]).toContain("estate/aws_eip.pool");
|
|
150
|
+
expect([...entities.keys()]).not.toContain("estate/aws_eip.pool[0]");
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe("indexLivePlan (#2104)", () => {
|
|
155
|
+
const index = indexLivePlan(PLAN);
|
|
156
|
+
|
|
157
|
+
it("indexes every section of the recorded document by address", () => {
|
|
158
|
+
expect(index.estate).toBe(ESTATE);
|
|
159
|
+
expect([...index.bound.keys()].sort()).toEqual([
|
|
160
|
+
"aws_cloudwatch_log_group.app",
|
|
161
|
+
"aws_eip.pool[0]",
|
|
162
|
+
"aws_eip.pool[1]",
|
|
163
|
+
"aws_security_group.main",
|
|
164
|
+
"aws_subnet.app",
|
|
165
|
+
"aws_vpc.main",
|
|
166
|
+
]);
|
|
167
|
+
expect([...index.unowned.keys()].sort()).toEqual([
|
|
168
|
+
"aws_cloudwatch_log_group.adoptable",
|
|
169
|
+
"aws_cloudwatch_log_group.held_elsewhere",
|
|
170
|
+
]);
|
|
171
|
+
expect(index.omissions.get("aws_cloudwatch_log_group.never_applied")!.reason).toBe("ABSENT");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("separates an adoptable match from a resource another estate holds", () => {
|
|
175
|
+
expect(index.unowned.get("aws_cloudwatch_log_group.adoptable")).toMatchObject({
|
|
176
|
+
adoptEstate: ESTATE,
|
|
177
|
+
adoptAddress: "aws_cloudwatch_log_group.adoptable",
|
|
178
|
+
});
|
|
179
|
+
expect(index.unowned.get("aws_cloudwatch_log_group.held_elsewhere")).toMatchObject({
|
|
180
|
+
heldBy: "other-estate",
|
|
181
|
+
});
|
|
182
|
+
expect(index.unowned.get("aws_cloudwatch_log_group.held_elsewhere")!.adoptEstate).toBeUndefined();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("reads a document with no sections at all as empty, never as a throw", () => {
|
|
186
|
+
expect(indexLivePlan({}).addresses).toEqual([]);
|
|
187
|
+
expect(indexLivePlan(null).bound.size).toBe(0);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe("the omission reason table (#2104)", () => {
|
|
192
|
+
it("maps ABSENT to absence, because the provider was asked and answered", () => {
|
|
193
|
+
expect(LIVE_PLAN_OMISSION_REASONS.ABSENT).toBe("absent");
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("maps a failure to read-failed and an unreachable kind to unsupported-kind", () => {
|
|
197
|
+
expect(LIVE_PLAN_OMISSION_REASONS.FAILED).toBe("read-failed");
|
|
198
|
+
expect(LIVE_PLAN_OMISSION_REASONS.NEEDS_DISCOVERY).toBe("unsupported-kind");
|
|
199
|
+
expect(LIVE_PLAN_OMISSION_REASONS.UNREADABLE).toBe("unsupported-kind");
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe("terraform describeResources on a live root (#2104)", () => {
|
|
204
|
+
it("reads live-plan and never terraform show", async () => {
|
|
205
|
+
// `deps()`'s init and show throw. Reaching this line means neither ran.
|
|
206
|
+
const { resources } = normalizeObservation(await describeResources(await options(), deps()));
|
|
207
|
+
expect(Object.keys(resources).length).toBeGreaterThan(0);
|
|
208
|
+
const { queried } = normalizeObservation(await describeResources(await options(), deps()));
|
|
209
|
+
expect(queried["estate/aws_vpc.main"]).toBe(
|
|
210
|
+
`choudoufu live-plan -json (root "estate", estate "${ESTATE}", address "aws_vpc.main")`,
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("reports a marker-bound instance owned, and surfaces the estate as its marker", async () => {
|
|
215
|
+
const { resources } = normalizeObservation(await describeResources(await options(), deps()));
|
|
216
|
+
expect(resources["estate/aws_vpc.main"]).toMatchObject({
|
|
217
|
+
type: "Terraform::Resource",
|
|
218
|
+
physicalId: "vpc-c1733cf7",
|
|
219
|
+
status: "bound",
|
|
220
|
+
ownership: "owned",
|
|
221
|
+
marker: { stack: ESTATE },
|
|
222
|
+
});
|
|
223
|
+
expect(resources["estate/aws_vpc.main"].attributes).toMatchObject({
|
|
224
|
+
address: "aws_vpc.main",
|
|
225
|
+
root: "estate",
|
|
226
|
+
estate: ESTATE,
|
|
227
|
+
resourceType: "aws_vpc",
|
|
228
|
+
boundBy: "marker",
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("aggregates a count block over the instance addresses the document carries", async () => {
|
|
233
|
+
const { resources } = normalizeObservation(await describeResources(await options(), deps()));
|
|
234
|
+
const pool = resources["estate/aws_eip.pool"];
|
|
235
|
+
expect(pool).toMatchObject({ ownership: "owned", marker: { stack: ESTATE } });
|
|
236
|
+
expect(pool.attributes!.instances).toEqual(["aws_eip.pool[0]", "aws_eip.pool[1]"]);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("reports a declaration-carried bind owned by derivation, with no marker claimed", async () => {
|
|
240
|
+
const { resources } = normalizeObservation(await describeResources(await options(), deps()));
|
|
241
|
+
const log = resources["estate/aws_cloudwatch_log_group.app"];
|
|
242
|
+
expect(log).toMatchObject({ ownership: "owned", status: "bound" });
|
|
243
|
+
expect(log.attributes!.boundBy).toBe("derived");
|
|
244
|
+
// Nothing was read off this resource, so nothing is claimed about it.
|
|
245
|
+
expect(log.marker).toBeUndefined();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("reports an adoptable match unknown, carrying the exact tag write that claims it", async () => {
|
|
249
|
+
const { resources } = normalizeObservation(await describeResources(await options(), deps()));
|
|
250
|
+
const adoptable = resources["estate/aws_cloudwatch_log_group.adoptable"];
|
|
251
|
+
expect(adoptable).toMatchObject({
|
|
252
|
+
status: "adoptable",
|
|
253
|
+
ownership: "unknown",
|
|
254
|
+
physicalId: "/stateless-e2e-block/adoptable",
|
|
255
|
+
});
|
|
256
|
+
expect(adoptable.attributes).toMatchObject({
|
|
257
|
+
adoptTofuEstate: ESTATE,
|
|
258
|
+
adoptTofuAddress: "aws_cloudwatch_log_group.adoptable",
|
|
259
|
+
});
|
|
260
|
+
expect(adoptable.marker).toBeUndefined();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("reports an unowned resource at a declared identity foreign, naming who holds it", async () => {
|
|
264
|
+
const { resources } = normalizeObservation(await describeResources(await options(), deps()));
|
|
265
|
+
const held = resources["estate/aws_cloudwatch_log_group.held_elsewhere"];
|
|
266
|
+
expect(held).toMatchObject({ status: "unowned", ownership: "foreign" });
|
|
267
|
+
expect(held.attributes!.heldBy).toBe("other-estate");
|
|
268
|
+
expect(held.marker).toBeUndefined();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("reports an ABSENT omission as absent, so a create is still proposable", async () => {
|
|
272
|
+
const { resources, unobserved, queried } = normalizeObservation(
|
|
273
|
+
await describeResources(await options(), deps()),
|
|
274
|
+
);
|
|
275
|
+
for (const name of [
|
|
276
|
+
"estate/aws_cloudwatch_log_group.never_applied",
|
|
277
|
+
"estate/aws_security_group_rule.https",
|
|
278
|
+
]) {
|
|
279
|
+
expect(resources).not.toHaveProperty(name);
|
|
280
|
+
expect(unobserved).not.toHaveProperty(name);
|
|
281
|
+
expect(queried[name]).toContain("live-plan");
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("reports a data block unsupported-kind: the document is prior state for managed resources", async () => {
|
|
286
|
+
const entities = await declaredEntities();
|
|
287
|
+
entities.set("estate/data.aws_ami.base", {
|
|
288
|
+
entityType: "Terraform::Data",
|
|
289
|
+
props: { address: "data.aws_ami.base", root: "estate", mode: "live", estate: ESTATE, body: {}, file: "x.tf" },
|
|
290
|
+
});
|
|
291
|
+
const { unobserved } = normalizeObservation(
|
|
292
|
+
await describeResources(await options({ extra: entities, only: ["estate/data.aws_ami.base"] }), deps()),
|
|
293
|
+
);
|
|
294
|
+
expect(unobserved["estate/data.aws_ami.base"].reason).toBe("unsupported-kind");
|
|
295
|
+
expect(unobserved["estate/data.aws_ami.base"].detail).toContain("prior state");
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("reports the terraform and provider blocks unsupported-kind", async () => {
|
|
299
|
+
const { unobserved } = normalizeObservation(await describeResources(await options(), deps()));
|
|
300
|
+
for (const name of ["estate/terraform", "estate/provider.aws", "estate/live"]) {
|
|
301
|
+
expect(unobserved[name].reason).toBe("unsupported-kind");
|
|
302
|
+
expect(unobserved[name].detail).toContain("live-plan document");
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it("notes, once for the run, that ownership came from the marker rather than a state file", async () => {
|
|
307
|
+
const { notes } = normalizeObservation(await describeResources(await options(), deps()));
|
|
308
|
+
expect(notes.join("\n")).toContain("terraform.roots.estate is a live root");
|
|
309
|
+
expect(notes.join("\n")).toContain("tofu-estate");
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
describe("terraform describeResources live-root failures (#2104)", () => {
|
|
314
|
+
it("reports every declared entity of the root not-observed, never absent", async () => {
|
|
315
|
+
const opts = await options();
|
|
316
|
+
const { resources, unobserved } = normalizeObservation(
|
|
317
|
+
await describeResources(opts, deps({ livePlan: failingPlan("Error: No configuration files") })),
|
|
318
|
+
);
|
|
319
|
+
expect(Object.keys(resources)).toEqual([]);
|
|
320
|
+
expect(Object.keys(unobserved).sort()).toEqual([...opts.entityNames].sort());
|
|
321
|
+
for (const entry of Object.values(unobserved)) {
|
|
322
|
+
expect(entry.reason).toBe("read-failed");
|
|
323
|
+
expect(entry.detail).toContain("terraform.roots.estate");
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it("calls a credentials failure no-credentials, not read-failed", async () => {
|
|
328
|
+
const opts = await options();
|
|
329
|
+
const { unobserved } = normalizeObservation(
|
|
330
|
+
await describeResources(
|
|
331
|
+
opts,
|
|
332
|
+
deps({ livePlan: failingPlan("no valid credential sources for AWS Provider found") }),
|
|
333
|
+
),
|
|
334
|
+
);
|
|
335
|
+
for (const name of opts.entityNames) expect(unobserved[name].reason).toBe("no-credentials");
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it("calls a refused configuration read-failed, with choudoufu's own first line", async () => {
|
|
339
|
+
const opts = await options();
|
|
340
|
+
const { unobserved } = normalizeObservation(
|
|
341
|
+
await describeResources(
|
|
342
|
+
opts,
|
|
343
|
+
deps({
|
|
344
|
+
livePlan: failingPlan(
|
|
345
|
+
"choudoufu live-plan failed in /root (exit 1)\nEstate named by both the live block and -estate",
|
|
346
|
+
),
|
|
347
|
+
}),
|
|
348
|
+
),
|
|
349
|
+
);
|
|
350
|
+
expect(unobserved[opts.entityNames[0]!].detail).toContain("choudoufu live-plan failed");
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
describe("terraform describeResources --owned on a live root (#2104)", () => {
|
|
355
|
+
it("keeps the owned reads and withholds the foreign and adoptable ones as filtered", async () => {
|
|
356
|
+
const { resources, unobserved } = normalizeObservation(
|
|
357
|
+
await describeResources(await options({ owned: true }), deps()),
|
|
358
|
+
);
|
|
359
|
+
expect(Object.keys(resources).sort()).toEqual([
|
|
360
|
+
"estate/aws_cloudwatch_log_group.app",
|
|
361
|
+
"estate/aws_eip.pool",
|
|
362
|
+
"estate/aws_security_group.main",
|
|
363
|
+
"estate/aws_subnet.app",
|
|
364
|
+
"estate/aws_vpc.main",
|
|
365
|
+
]);
|
|
366
|
+
expect(unobserved["estate/aws_cloudwatch_log_group.adoptable"].reason).toBe("filtered");
|
|
367
|
+
expect(unobserved["estate/aws_cloudwatch_log_group.held_elsewhere"].reason).toBe("filtered");
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
describe("readLiveLs (#2104)", () => {
|
|
372
|
+
const listing = readLiveLs(LS);
|
|
373
|
+
|
|
374
|
+
it("parses the recorded listing's items, slots and gaps", () => {
|
|
375
|
+
expect(listing.estate).toBe(ESTATE);
|
|
376
|
+
expect(listing.items).toHaveLength(7);
|
|
377
|
+
expect(listing.items.find((i) => i.address === "aws_eip.pool[0]")!.slot).toBe("0");
|
|
378
|
+
expect(listing.items.find((i) => i.address === "aws_s3_bucket.data")!.declared).toBe(false);
|
|
379
|
+
expect(listing.gaps).toEqual([
|
|
380
|
+
{
|
|
381
|
+
address: "aws_security_group_rule.https",
|
|
382
|
+
type: "aws_security_group_rule",
|
|
383
|
+
rung: "declaration-carried",
|
|
384
|
+
detail: expect.stringContaining("no settable tags argument"),
|
|
385
|
+
},
|
|
386
|
+
]);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it("reads a listing with no items at all as empty, never as a throw", () => {
|
|
390
|
+
expect(readLiveLs({}).items).toEqual([]);
|
|
391
|
+
expect(readLiveLs(null).gaps).toEqual([]);
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
describe("the estate beyond the declaration (#1278, #2104)", () => {
|
|
396
|
+
it("enumerates the project's live roots out of terraform.roots", async () => {
|
|
397
|
+
expect(await liveRootNames(project())).toEqual([{ root: "estate", estate: ESTATE }]);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it("names resource blocks as the one ambient kind", () => {
|
|
401
|
+
expect(ambientKinds()).toEqual(["Terraform::Resource"]);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it("reports the estate's owned orphans, and only those", async () => {
|
|
405
|
+
const ambient = await observeAmbient(
|
|
406
|
+
{ environment: "prod", kinds: ["Terraform::Resource"], observed: {}, cwd: project() },
|
|
407
|
+
deps(),
|
|
408
|
+
);
|
|
409
|
+
expect(Object.keys(ambient)).toEqual(["estate/aws_s3_bucket.data"]);
|
|
410
|
+
expect(ambient["estate/aws_s3_bucket.data"]).toMatchObject({
|
|
411
|
+
type: "Terraform::Resource",
|
|
412
|
+
physicalId: "arn:aws:s3:::tofu-stateless-e2e-block-data",
|
|
413
|
+
status: "orphan",
|
|
414
|
+
ownership: "owned",
|
|
415
|
+
ambient: true,
|
|
416
|
+
marker: { stack: ESTATE },
|
|
417
|
+
});
|
|
418
|
+
expect(ambient["estate/aws_s3_bucket.data"].attributes!.tags).toMatchObject({
|
|
419
|
+
"tofu-estate": ESTATE,
|
|
420
|
+
"tofu-address": "aws_s3_bucket.data",
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it("excludes anything describeResources already observed", async () => {
|
|
425
|
+
const ambient = await observeAmbient(
|
|
426
|
+
{
|
|
427
|
+
environment: "prod",
|
|
428
|
+
kinds: ["Terraform::Resource"],
|
|
429
|
+
observed: { "estate/aws_s3_bucket.data": { type: "Terraform::Resource", status: "bound" } },
|
|
430
|
+
cwd: project(),
|
|
431
|
+
},
|
|
432
|
+
deps(),
|
|
433
|
+
);
|
|
434
|
+
expect(ambient).toEqual({});
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it("enumerates nothing when the project declares no resource blocks", async () => {
|
|
438
|
+
const ambient = await observeAmbient(
|
|
439
|
+
{ environment: "prod", kinds: ["Terraform::Module"], observed: {}, cwd: project() },
|
|
440
|
+
deps(),
|
|
441
|
+
);
|
|
442
|
+
expect(ambient).toEqual({});
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
describe("teardownOwned on a live root (#1222, #2104)", () => {
|
|
447
|
+
it("names the owned orphans choudoufu's own default policy would delete", async () => {
|
|
448
|
+
const marker = { stack: "chant", env: "prod" };
|
|
449
|
+
const enumeration = await teardownOwned({ environment: "prod", marker, cwd: project() }, deps());
|
|
450
|
+
expect(enumeration.candidates).toEqual([
|
|
451
|
+
{
|
|
452
|
+
name: "estate/aws_s3_bucket.data",
|
|
453
|
+
type: "Terraform::Resource",
|
|
454
|
+
physicalId: "arn:aws:s3:::tofu-stateless-e2e-block-data",
|
|
455
|
+
marker,
|
|
456
|
+
},
|
|
457
|
+
]);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it("turns a listing gap into a hole rather than silence", async () => {
|
|
461
|
+
const enumeration = await teardownOwned(
|
|
462
|
+
{ environment: "prod", marker: { stack: "chant", env: "prod" }, cwd: project() },
|
|
463
|
+
deps(),
|
|
464
|
+
);
|
|
465
|
+
expect(enumeration.holes).toEqual([
|
|
466
|
+
{
|
|
467
|
+
name: "estate/aws_security_group_rule.https",
|
|
468
|
+
type: "Terraform::Resource",
|
|
469
|
+
reason: "unsupported-kind",
|
|
470
|
+
detail: expect.stringContaining("declaration-carried"),
|
|
471
|
+
},
|
|
472
|
+
]);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it("turns a failed listing into a hole, never into an empty candidate list", async () => {
|
|
476
|
+
const enumeration = await teardownOwned(
|
|
477
|
+
{ environment: "prod", marker: { stack: "chant", env: "prod" }, cwd: project() },
|
|
478
|
+
deps({
|
|
479
|
+
liveLs: (async () => {
|
|
480
|
+
throw new Error("ThrottlingException: Rate exceeded");
|
|
481
|
+
}) as TerraformReadDeps["liveLs"],
|
|
482
|
+
}),
|
|
483
|
+
);
|
|
484
|
+
expect(enumeration.candidates).toEqual([]);
|
|
485
|
+
expect(enumeration.holes![0]).toMatchObject({
|
|
486
|
+
name: "terraform.roots.estate",
|
|
487
|
+
reason: "read-failed",
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
describe("the live ownership channel (#2104)", () => {
|
|
493
|
+
it("names choudoufu's two marker tags", () => {
|
|
494
|
+
expect(TERRAFORM_LIVE_MARKER_KEYS).toEqual({
|
|
495
|
+
managedBy: "tofu-estate",
|
|
496
|
+
stack: "tofu-estate",
|
|
497
|
+
env: "tofu-address",
|
|
498
|
+
});
|
|
499
|
+
});
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
describeObservationConformance({
|
|
503
|
+
lexicon: "terraform (live root)",
|
|
504
|
+
// The same declaration the plugin makes: this path resolves a real verdict,
|
|
505
|
+
// read here off choudoufu's markers rather than off a state file.
|
|
506
|
+
ownershipChannel: { keys: TERRAFORM_LIVE_MARKER_KEYS, reads: ["describeResources"] },
|
|
507
|
+
scenarios: [
|
|
508
|
+
{
|
|
509
|
+
name: "a marker-bound resource",
|
|
510
|
+
declared: ["estate/aws_vpc.main"],
|
|
511
|
+
expectPresent: ["estate/aws_vpc.main"],
|
|
512
|
+
expectMarker: { "estate/aws_vpc.main": { stack: ESTATE, env: undefined } },
|
|
513
|
+
run: async () => describeResources(await options({ only: ["estate/aws_vpc.main"] }), deps()),
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
name: "an adoptable match, which carries no marker of its own",
|
|
517
|
+
declared: ["estate/aws_cloudwatch_log_group.adoptable"],
|
|
518
|
+
expectPresent: ["estate/aws_cloudwatch_log_group.adoptable"],
|
|
519
|
+
expectNoMarker: ["estate/aws_cloudwatch_log_group.adoptable"],
|
|
520
|
+
run: async () =>
|
|
521
|
+
describeResources(await options({ only: ["estate/aws_cloudwatch_log_group.adoptable"] }), deps()),
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
name: "a resource another estate holds at a declared identity",
|
|
525
|
+
declared: ["estate/aws_cloudwatch_log_group.held_elsewhere"],
|
|
526
|
+
expectPresent: ["estate/aws_cloudwatch_log_group.held_elsewhere"],
|
|
527
|
+
expectNoMarker: ["estate/aws_cloudwatch_log_group.held_elsewhere"],
|
|
528
|
+
run: async () =>
|
|
529
|
+
describeResources(await options({ only: ["estate/aws_cloudwatch_log_group.held_elsewhere"] }), deps()),
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
name: "declared and never applied",
|
|
533
|
+
declared: ["estate/aws_cloudwatch_log_group.never_applied"],
|
|
534
|
+
expectAbsent: ["estate/aws_cloudwatch_log_group.never_applied"],
|
|
535
|
+
run: async () =>
|
|
536
|
+
describeResources(await options({ only: ["estate/aws_cloudwatch_log_group.never_applied"] }), deps()),
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
name: "live-plan itself failed",
|
|
540
|
+
declared: ["estate/aws_vpc.main", "estate/aws_eip.pool"],
|
|
541
|
+
expectUnobserved: ["estate/aws_vpc.main", "estate/aws_eip.pool"],
|
|
542
|
+
run: async () =>
|
|
543
|
+
describeResources(
|
|
544
|
+
await options({ only: ["estate/aws_vpc.main", "estate/aws_eip.pool"] }),
|
|
545
|
+
deps({ livePlan: failingPlan("no valid credential sources for AWS Provider found") }),
|
|
546
|
+
),
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
name: "a data block, which the document has no row for",
|
|
550
|
+
declared: ["estate/data.aws_ami.base"],
|
|
551
|
+
expectUnobserved: ["estate/data.aws_ami.base"],
|
|
552
|
+
run: async () => {
|
|
553
|
+
const entities = await declaredEntities();
|
|
554
|
+
entities.set("estate/data.aws_ami.base", {
|
|
555
|
+
entityType: "Terraform::Data",
|
|
556
|
+
props: {
|
|
557
|
+
address: "data.aws_ami.base",
|
|
558
|
+
root: "estate",
|
|
559
|
+
mode: "live",
|
|
560
|
+
estate: ESTATE,
|
|
561
|
+
body: {},
|
|
562
|
+
file: "x.tf",
|
|
563
|
+
},
|
|
564
|
+
});
|
|
565
|
+
return describeResources(
|
|
566
|
+
await options({ extra: entities, only: ["estate/data.aws_ami.base"] }),
|
|
567
|
+
deps(),
|
|
568
|
+
);
|
|
569
|
+
},
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
name: "owned read, with the adoptable and foreign rows filtered out",
|
|
573
|
+
declared: [
|
|
574
|
+
"estate/aws_vpc.main",
|
|
575
|
+
"estate/aws_cloudwatch_log_group.adoptable",
|
|
576
|
+
"estate/aws_cloudwatch_log_group.held_elsewhere",
|
|
577
|
+
],
|
|
578
|
+
owned: true,
|
|
579
|
+
expectPresent: ["estate/aws_vpc.main"],
|
|
580
|
+
expectUnobserved: [
|
|
581
|
+
"estate/aws_cloudwatch_log_group.adoptable",
|
|
582
|
+
"estate/aws_cloudwatch_log_group.held_elsewhere",
|
|
583
|
+
],
|
|
584
|
+
run: async () =>
|
|
585
|
+
describeResources(
|
|
586
|
+
await options({
|
|
587
|
+
owned: true,
|
|
588
|
+
only: [
|
|
589
|
+
"estate/aws_vpc.main",
|
|
590
|
+
"estate/aws_cloudwatch_log_group.adoptable",
|
|
591
|
+
"estate/aws_cloudwatch_log_group.held_elsewhere",
|
|
592
|
+
],
|
|
593
|
+
}),
|
|
594
|
+
deps(),
|
|
595
|
+
),
|
|
596
|
+
},
|
|
597
|
+
],
|
|
598
|
+
});
|