@intentius/chant-lexicon-terraform 0.57.0 → 0.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/composites/terraform-adopt-op.d.ts +126 -0
- package/dist/composites/terraform-adopt-op.d.ts.map +1 -0
- package/dist/composites/terraform-apply-op.d.ts +63 -16
- package/dist/composites/terraform-apply-op.d.ts.map +1 -1
- package/dist/composites/terraform-watch-op.d.ts +80 -36
- package/dist/composites/terraform-watch-op.d.ts.map +1 -1
- package/dist/config.d.ts +49 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/describe-resources.d.ts +256 -16
- package/dist/describe-resources.d.ts.map +1 -1
- package/dist/hcl/descend.d.ts +98 -0
- package/dist/hcl/descend.d.ts.map +1 -0
- package/dist/hcl/marker.d.ts +130 -0
- package/dist/hcl/marker.d.ts.map +1 -0
- package/dist/hcl/parse.d.ts +142 -9
- package/dist/hcl/parse.d.ts.map +1 -1
- package/dist/hcl/references.d.ts +76 -0
- package/dist/hcl/references.d.ts.map +1 -0
- package/dist/hcl/roots.d.ts +22 -3
- package/dist/hcl/roots.d.ts.map +1 -1
- package/dist/hcl/suppressions.d.ts +54 -0
- package/dist/hcl/suppressions.d.ts.map +1 -0
- package/dist/hcl/value.d.ts +77 -0
- package/dist/hcl/value.d.ts.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +32 -5
- package/dist/lint/audit-catalog.d.ts +3 -2
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/audit-lineage.d.ts +68 -0
- package/dist/lint/audit-lineage.d.ts.map +1 -1
- package/dist/lint/post-synth/blocks.d.ts +53 -0
- package/dist/lint/post-synth/blocks.d.ts.map +1 -0
- package/dist/lint/post-synth/fixtures/load.d.ts +37 -0
- package/dist/lint/post-synth/fixtures/load.d.ts.map +1 -0
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/lint/post-synth/module-source.d.ts +38 -0
- package/dist/lint/post-synth/module-source.d.ts.map +1 -0
- package/dist/lint/post-synth/scope.d.ts +65 -0
- package/dist/lint/post-synth/scope.d.ts.map +1 -0
- package/dist/lint/post-synth/tf001.d.ts +18 -0
- package/dist/lint/post-synth/tf001.d.ts.map +1 -1
- package/dist/lint/post-synth/tf002.d.ts +32 -0
- package/dist/lint/post-synth/tf002.d.ts.map +1 -0
- package/dist/lint/post-synth/tf003.d.ts +22 -0
- package/dist/lint/post-synth/tf003.d.ts.map +1 -0
- package/dist/lint/post-synth/tf004.d.ts +22 -0
- package/dist/lint/post-synth/tf004.d.ts.map +1 -0
- package/dist/lint/post-synth/tf005.d.ts +28 -0
- package/dist/lint/post-synth/tf005.d.ts.map +1 -0
- package/dist/lint/post-synth/tf006.d.ts +21 -0
- package/dist/lint/post-synth/tf006.d.ts.map +1 -0
- package/dist/lint/post-synth/tf007.d.ts +21 -0
- package/dist/lint/post-synth/tf007.d.ts.map +1 -0
- package/dist/lint/post-synth/tf008.d.ts +22 -0
- package/dist/lint/post-synth/tf008.d.ts.map +1 -0
- package/dist/lint/post-synth/tf009.d.ts +24 -0
- package/dist/lint/post-synth/tf009.d.ts.map +1 -0
- package/dist/lint/post-synth/tf010.d.ts +19 -0
- package/dist/lint/post-synth/tf010.d.ts.map +1 -0
- package/dist/lint/post-synth/tf011.d.ts +18 -0
- package/dist/lint/post-synth/tf011.d.ts.map +1 -0
- package/dist/lint/post-synth/tf012.d.ts +17 -0
- package/dist/lint/post-synth/tf012.d.ts.map +1 -0
- package/dist/lint/post-synth/tf013.d.ts +21 -0
- package/dist/lint/post-synth/tf013.d.ts.map +1 -0
- package/dist/lint/post-synth/tf014.d.ts +25 -0
- package/dist/lint/post-synth/tf014.d.ts.map +1 -0
- package/dist/lint/post-synth/tf015.d.ts +21 -0
- package/dist/lint/post-synth/tf015.d.ts.map +1 -0
- package/dist/lint/post-synth/tf016.d.ts +30 -0
- package/dist/lint/post-synth/tf016.d.ts.map +1 -0
- package/dist/lint/post-synth/tf017.d.ts +19 -0
- package/dist/lint/post-synth/tf017.d.ts.map +1 -0
- package/dist/lint/post-synth/tf018.d.ts +20 -0
- package/dist/lint/post-synth/tf018.d.ts.map +1 -0
- package/dist/lint/post-synth/tf019.d.ts +25 -0
- package/dist/lint/post-synth/tf019.d.ts.map +1 -0
- package/dist/lint/post-synth/tf020.d.ts +38 -0
- package/dist/lint/post-synth/tf020.d.ts.map +1 -0
- package/dist/lint/post-synth/tf021.d.ts +41 -0
- package/dist/lint/post-synth/tf021.d.ts.map +1 -0
- package/dist/lint/post-synth/tf022.d.ts +24 -0
- package/dist/lint/post-synth/tf022.d.ts.map +1 -0
- package/dist/lint/post-synth/tf024.d.ts +24 -0
- package/dist/lint/post-synth/tf024.d.ts.map +1 -0
- package/dist/lint/post-synth/tf025.d.ts +29 -0
- package/dist/lint/post-synth/tf025.d.ts.map +1 -0
- package/dist/lint/post-synth/tf026.d.ts +21 -0
- package/dist/lint/post-synth/tf026.d.ts.map +1 -0
- package/dist/lint/rules/fixtures/load.d.ts +13 -0
- package/dist/lint/rules/fixtures/load.d.ts.map +1 -0
- package/dist/lint/rules/plan-before-apply.d.ts +11 -0
- package/dist/lint/rules/plan-before-apply.d.ts.map +1 -1
- package/dist/lint/secret-shape.d.ts +68 -0
- package/dist/lint/secret-shape.d.ts.map +1 -0
- package/dist/live-ownership.d.ts +55 -0
- package/dist/live-ownership.d.ts.map +1 -0
- package/dist/lsp/option-keys.d.ts.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/okf/index.md +24 -0
- package/dist/okf/rules/TF002.md +11 -0
- package/dist/okf/rules/TF003.md +11 -0
- package/dist/okf/rules/TF004.md +11 -0
- package/dist/okf/rules/TF005.md +11 -0
- package/dist/okf/rules/TF006.md +11 -0
- package/dist/okf/rules/TF007.md +11 -0
- package/dist/okf/rules/TF008.md +11 -0
- package/dist/okf/rules/TF009.md +11 -0
- package/dist/okf/rules/TF010.md +11 -0
- package/dist/okf/rules/TF011.md +11 -0
- package/dist/okf/rules/TF012.md +11 -0
- package/dist/okf/rules/TF013.md +11 -0
- package/dist/okf/rules/TF014.md +11 -0
- package/dist/okf/rules/TF015.md +11 -0
- package/dist/okf/rules/TF016.md +11 -0
- package/dist/okf/rules/TF017.md +11 -0
- package/dist/okf/rules/TF018.md +11 -0
- package/dist/okf/rules/TF019.md +11 -0
- package/dist/okf/rules/TF020.md +11 -0
- package/dist/okf/rules/TF021.md +11 -0
- package/dist/okf/rules/TF022.md +11 -0
- package/dist/okf/rules/TF024.md +11 -0
- package/dist/okf/rules/TF025.md +11 -0
- package/dist/okf/rules/TF026.md +11 -0
- package/dist/op/activities/index.d.ts +13 -6
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/live-detect.d.ts +51 -0
- package/dist/op/activities/live-detect.d.ts.map +1 -0
- package/dist/op/activities/terraform.d.ts +445 -17
- package/dist/op/activities/terraform.d.ts.map +1 -1
- package/dist/op/activity-contracts.d.ts +148 -0
- package/dist/op/activity-contracts.d.ts.map +1 -0
- package/dist/op/adoption.d.ts +118 -0
- package/dist/op/adoption.d.ts.map +1 -0
- package/dist/op/builders.d.ts +46 -6
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/resolve-root-mode.d.ts +45 -0
- package/dist/op/resolve-root-mode.d.ts.map +1 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/rules/blocks.ts +109 -0
- package/dist/rules/module-source.ts +121 -0
- package/dist/rules/plan-before-apply.ts +11 -0
- package/dist/rules/scope.ts +107 -0
- package/dist/rules/tf001.ts +32 -8
- package/dist/rules/tf002.ts +151 -0
- package/dist/rules/tf003.ts +71 -0
- package/dist/rules/tf004.ts +63 -0
- package/dist/rules/tf005.ts +76 -0
- package/dist/rules/tf006.ts +53 -0
- package/dist/rules/tf007.ts +78 -0
- package/dist/rules/tf008.ts +75 -0
- package/dist/rules/tf009.ts +59 -0
- package/dist/rules/tf010.ts +51 -0
- package/dist/rules/tf011.ts +49 -0
- package/dist/rules/tf012.ts +47 -0
- package/dist/rules/tf013.ts +63 -0
- package/dist/rules/tf014.ts +72 -0
- package/dist/rules/tf015.ts +63 -0
- package/dist/rules/tf016.ts +75 -0
- package/dist/rules/tf017.ts +50 -0
- package/dist/rules/tf018.ts +72 -0
- package/dist/rules/tf019.ts +72 -0
- package/dist/rules/tf020.ts +123 -0
- package/dist/rules/tf021.ts +126 -0
- package/dist/rules/tf022.ts +61 -0
- package/dist/rules/tf024.ts +70 -0
- package/dist/rules/tf025.ts +101 -0
- package/dist/rules/tf026.ts +80 -0
- package/dist/skills/chant-terraform.md +1 -1
- package/package.json +7 -2
- package/src/__fixtures__/live/main.tf +28 -0
- package/src/__fixtures__/live-adopt/estate.chdf.hcl +6 -0
- package/src/__fixtures__/live-adopt/main.tf +21 -0
- package/src/__fixtures__/live-estate/README.md +58 -0
- package/src/__fixtures__/live-estate/compute.tf +10 -0
- package/src/__fixtures__/live-estate/estate.chdf.hcl +1 -0
- package/src/__fixtures__/live-estate/logs.tf +8 -0
- package/src/__fixtures__/live-estate/network.tf +24 -0
- package/src/__fixtures__/live-estate/rule.tf +11 -0
- package/src/__fixtures__/live-estate/unowned.tf +21 -0
- package/src/__fixtures__/live-estate/versions.tf +31 -0
- package/src/__fixtures__/live-ls.json +98 -0
- package/src/__fixtures__/live-plan.json +285 -0
- package/src/__fixtures__/live-sidecar/estate.chdf.hcl +3 -0
- package/src/__fixtures__/live-sidecar/main.tf +24 -0
- package/src/__fixtures__/module-tree/README.md +16 -0
- package/src/__fixtures__/module-tree/cycle/main.tf +3 -0
- package/src/__fixtures__/module-tree/cycle/modules/a/main.tf +9 -0
- package/src/__fixtures__/module-tree/cycle/modules/b/main.tf +9 -0
- package/src/__fixtures__/module-tree/missing/main.tf +9 -0
- package/src/__fixtures__/module-tree/outside/root/main.tf +9 -0
- package/src/__fixtures__/module-tree/root/main.tf +28 -0
- package/src/__fixtures__/module-tree/root/modules/cdn/main.tf +15 -0
- package/src/__fixtures__/module-tree/root/modules/cdn/modules/bucket/main.tf +11 -0
- package/src/__fixtures__/module-tree/shared/main.tf +5 -0
- package/src/__fixtures__/references/main.tf +56 -0
- package/src/codegen/lint-rules-page.test.ts +28 -0
- package/src/composites/terraform-adopt-op.acceptance.test.ts +166 -0
- package/src/composites/terraform-adopt-op.test.ts +170 -0
- package/src/composites/terraform-adopt-op.ts +197 -0
- package/src/composites/terraform-apply-op.acceptance.test.ts +183 -5
- package/src/composites/terraform-apply-op.test.ts +167 -15
- package/src/composites/terraform-apply-op.ts +105 -28
- package/src/composites/terraform-watch-op.test.ts +85 -26
- package/src/composites/terraform-watch-op.ts +130 -73
- package/src/config.ts +32 -3
- package/src/describe-resources.live.test.ts +598 -0
- package/src/describe-resources.test.ts +53 -0
- package/src/describe-resources.ts +811 -28
- package/src/hcl/descend.test.ts +162 -0
- package/src/hcl/descend.ts +273 -0
- package/src/hcl/marker.test.ts +147 -0
- package/src/hcl/marker.ts +235 -0
- package/src/hcl/parse.test.ts +69 -0
- package/src/hcl/parse.ts +249 -27
- package/src/hcl/references.test.ts +116 -0
- package/src/hcl/references.ts +172 -0
- package/src/hcl/roots.ts +59 -5
- package/src/hcl/suppressions.test.ts +147 -0
- package/src/hcl/suppressions.ts +204 -0
- package/src/hcl/value.test.ts +73 -0
- package/src/hcl/value.ts +106 -0
- package/src/index.ts +31 -1
- package/src/lint/audit-catalog.ts +264 -3
- package/src/lint/audit-lineage.ts +319 -1
- package/src/lint/audit.test.ts +40 -1
- package/src/lint/post-synth/blocks.ts +109 -0
- package/src/lint/post-synth/fixtures/TF001/negative.tf +28 -0
- package/src/lint/post-synth/fixtures/TF001/positive.tf +24 -0
- package/src/lint/post-synth/fixtures/TF002/negative.tf +17 -0
- package/src/lint/post-synth/fixtures/TF002/positive-legacy.tf +10 -0
- package/src/lint/post-synth/fixtures/TF002/positive-no-version.tf +12 -0
- package/src/lint/post-synth/fixtures/TF002/positive.tf +7 -0
- package/src/lint/post-synth/fixtures/TF003/negative.tf +10 -0
- package/src/lint/post-synth/fixtures/TF003/positive.tf +8 -0
- package/src/lint/post-synth/fixtures/TF004/negative-git.tf +3 -0
- package/src/lint/post-synth/fixtures/TF004/negative-local.tf +3 -0
- package/src/lint/post-synth/fixtures/TF004/negative.tf +4 -0
- package/src/lint/post-synth/fixtures/TF004/positive.tf +3 -0
- package/src/lint/post-synth/fixtures/TF005/negative-local.tf +3 -0
- package/src/lint/post-synth/fixtures/TF005/negative-registry.tf +3 -0
- package/src/lint/post-synth/fixtures/TF005/negative-sha.tf +3 -0
- package/src/lint/post-synth/fixtures/TF005/negative.tf +3 -0
- package/src/lint/post-synth/fixtures/TF005/positive-branch.tf +3 -0
- package/src/lint/post-synth/fixtures/TF005/positive-non-semver.tf +3 -0
- package/src/lint/post-synth/fixtures/TF005/positive.tf +3 -0
- package/src/lint/post-synth/fixtures/TF006/negative.tf +5 -0
- package/src/lint/post-synth/fixtures/TF006/positive-empty-default.tf +11 -0
- package/src/lint/post-synth/fixtures/TF006/positive.tf +6 -0
- package/src/lint/post-synth/fixtures/TF007/negative.tf +10 -0
- package/src/lint/post-synth/fixtures/TF007/positive.tf +9 -0
- package/src/lint/post-synth/fixtures/TF008/negative-variable.tf +10 -0
- package/src/lint/post-synth/fixtures/TF008/negative.tf +8 -0
- package/src/lint/post-synth/fixtures/TF008/positive-other-provider.tf +10 -0
- package/src/lint/post-synth/fixtures/TF008/positive.tf +5 -0
- package/src/lint/post-synth/fixtures/TF009/negative.tf +10 -0
- package/src/lint/post-synth/fixtures/TF009/positive.tf +4 -0
- package/src/lint/post-synth/fixtures/TF010/negative.tf +5 -0
- package/src/lint/post-synth/fixtures/TF010/positive.tf +4 -0
- package/src/lint/post-synth/fixtures/TF011/negative.tf +5 -0
- package/src/lint/post-synth/fixtures/TF011/positive-blank.tf +5 -0
- package/src/lint/post-synth/fixtures/TF011/positive.tf +4 -0
- package/src/lint/post-synth/fixtures/TF012/negative.tf +4 -0
- package/src/lint/post-synth/fixtures/TF012/positive.tf +3 -0
- package/src/lint/post-synth/fixtures/TF013/negative.tf +8 -0
- package/src/lint/post-synth/fixtures/TF013/positive-data.tf +8 -0
- package/src/lint/post-synth/fixtures/TF013/positive.tf +8 -0
- package/src/lint/post-synth/fixtures/TF014/negative/main.tf +26 -0
- package/src/lint/post-synth/fixtures/TF014/negative/modules/cdn/main.tf +19 -0
- package/src/lint/post-synth/fixtures/TF014/positive/main.tf +16 -0
- package/src/lint/post-synth/fixtures/TF014/positive/modules/cdn/main.tf +15 -0
- package/src/lint/post-synth/fixtures/TF015/negative/main.tf +13 -0
- package/src/lint/post-synth/fixtures/TF015/negative/modules/cdn/main.tf +16 -0
- package/src/lint/post-synth/fixtures/TF015/positive/main.tf +13 -0
- package/src/lint/post-synth/fixtures/TF015/positive/modules/cdn/main.tf +14 -0
- package/src/lint/post-synth/fixtures/TF016/negative-template.tf +7 -0
- package/src/lint/post-synth/fixtures/TF016/negative.tf +16 -0
- package/src/lint/post-synth/fixtures/TF016/positive-shared-file.tf +12 -0
- package/src/lint/post-synth/fixtures/TF016/positive.tf +13 -0
- package/src/lint/post-synth/fixtures/TF017/negative.tf +4 -0
- package/src/lint/post-synth/fixtures/TF017/positive.tf +5 -0
- package/src/lint/post-synth/fixtures/TF018/negative.tf +9 -0
- package/src/lint/post-synth/fixtures/TF018/positive-data.tf +9 -0
- package/src/lint/post-synth/fixtures/TF018/positive.tf +4 -0
- package/src/lint/post-synth/fixtures/TF019/negative.tf +14 -0
- package/src/lint/post-synth/fixtures/TF019/positive-output.tf +5 -0
- package/src/lint/post-synth/fixtures/TF019/positive.tf +16 -0
- package/src/lint/post-synth/fixtures/TF020/negative.tf +43 -0
- package/src/lint/post-synth/fixtures/TF020/positive.tf +32 -0
- package/src/lint/post-synth/fixtures/TF020/scoped/main.tf +21 -0
- package/src/lint/post-synth/fixtures/TF020/scoped/modules/cdn/main.tf +13 -0
- package/src/lint/post-synth/fixtures/TF021/negative.tf +31 -0
- package/src/lint/post-synth/fixtures/TF021/positive-length.tf +8 -0
- package/src/lint/post-synth/fixtures/TF021/positive.tf +8 -0
- package/src/lint/post-synth/fixtures/TF022/negative-locator.tf +7 -0
- package/src/lint/post-synth/fixtures/TF022/negative.tf +10 -0
- package/src/lint/post-synth/fixtures/TF022/positive-nested.tf +10 -0
- package/src/lint/post-synth/fixtures/TF022/positive.tf +6 -0
- package/src/lint/post-synth/fixtures/TF023/negative/main.tf +15 -0
- package/src/lint/post-synth/fixtures/TF023/positive/main.tf +9 -0
- package/src/lint/post-synth/fixtures/TF023/positive/state.json +24 -0
- package/src/lint/post-synth/fixtures/TF024/live-clean/main.tf +9 -0
- package/src/lint/post-synth/fixtures/TF024/live-with-backend/main.tf +14 -0
- package/src/lint/post-synth/fixtures/TF024/live-with-cloud/main.tf +16 -0
- package/src/lint/post-synth/fixtures/TF025/live-clean/main.tf +13 -0
- package/src/lint/post-synth/fixtures/TF025/live-with-workspace-ref/main.tf +13 -0
- package/src/lint/post-synth/fixtures/TF026/live-default/main.tf +9 -0
- package/src/lint/post-synth/fixtures/TF026/live-explicit-delete/main.tf +13 -0
- package/src/lint/post-synth/fixtures/TF026/live-keep/main.tf +13 -0
- package/src/lint/post-synth/fixtures/load.ts +108 -0
- package/src/lint/post-synth/index.ts +48 -0
- package/src/lint/post-synth/module-rules.test.ts +153 -0
- package/src/lint/post-synth/module-source.test.ts +75 -0
- package/src/lint/post-synth/module-source.ts +121 -0
- package/src/lint/post-synth/post-synth.test.ts +14 -15
- package/src/lint/post-synth/scope.ts +107 -0
- package/src/lint/post-synth/single-block.test.ts +263 -0
- package/src/lint/post-synth/suppression.test.ts +110 -0
- package/src/lint/post-synth/tf001.ts +32 -8
- package/src/lint/post-synth/tf002.test.ts +93 -0
- package/src/lint/post-synth/tf002.ts +151 -0
- package/src/lint/post-synth/tf003.test.ts +75 -0
- package/src/lint/post-synth/tf003.ts +71 -0
- package/src/lint/post-synth/tf004.test.ts +47 -0
- package/src/lint/post-synth/tf004.ts +63 -0
- package/src/lint/post-synth/tf005.test.ts +57 -0
- package/src/lint/post-synth/tf005.ts +76 -0
- package/src/lint/post-synth/tf006.ts +53 -0
- package/src/lint/post-synth/tf007.ts +78 -0
- package/src/lint/post-synth/tf008.ts +75 -0
- package/src/lint/post-synth/tf009.ts +59 -0
- package/src/lint/post-synth/tf010.ts +51 -0
- package/src/lint/post-synth/tf011.ts +49 -0
- package/src/lint/post-synth/tf012.ts +47 -0
- package/src/lint/post-synth/tf013.ts +63 -0
- package/src/lint/post-synth/tf014.ts +72 -0
- package/src/lint/post-synth/tf015.ts +63 -0
- package/src/lint/post-synth/tf016.ts +75 -0
- package/src/lint/post-synth/tf017.ts +50 -0
- package/src/lint/post-synth/tf018.ts +72 -0
- package/src/lint/post-synth/tf019.ts +72 -0
- package/src/lint/post-synth/tf020.ts +123 -0
- package/src/lint/post-synth/tf021.ts +126 -0
- package/src/lint/post-synth/tf022.ts +61 -0
- package/src/lint/post-synth/tf024.test.ts +42 -0
- package/src/lint/post-synth/tf024.ts +70 -0
- package/src/lint/post-synth/tf025.test.ts +46 -0
- package/src/lint/post-synth/tf025.ts +101 -0
- package/src/lint/post-synth/tf026.test.ts +56 -0
- package/src/lint/post-synth/tf026.ts +80 -0
- package/src/lint/presets.test.ts +72 -0
- package/src/lint/rules/fixtures/TF101/negative-op.ts +2 -0
- package/src/lint/rules/fixtures/TF101/positive-op.ts +2 -0
- package/src/lint/rules/fixtures/load.ts +53 -0
- package/src/lint/rules/plan-before-apply.test.ts +112 -7
- package/src/lint/rules/plan-before-apply.ts +11 -0
- package/src/lint/secret-shape.test.ts +150 -0
- package/src/lint/secret-shape.ts +159 -0
- package/src/live-ownership.ts +60 -0
- package/src/lsp/completions.test.ts +3 -2
- package/src/lsp/option-keys.ts +10 -5
- package/src/op/activities/choudoufu.acceptance.test.ts +143 -0
- package/src/op/activities/choudoufu.test.ts +693 -0
- package/src/op/activities/index.ts +40 -4
- package/src/op/activities/live-detect.test.ts +143 -0
- package/src/op/activities/live-detect.ts +162 -0
- package/src/op/activities/registry.test.ts +11 -3
- package/src/op/activities/terraform.test.ts +7 -3
- package/src/op/activities/terraform.ts +823 -69
- package/src/op/activity-contracts.test.ts +147 -0
- package/src/op/activity-contracts.ts +178 -0
- package/src/op/adoption.test.ts +170 -0
- package/src/op/adoption.ts +241 -0
- package/src/op/builders.test.ts +60 -5
- package/src/op/builders.ts +93 -5
- package/src/op/resolve-root-mode.test.ts +85 -0
- package/src/op/resolve-root-mode.ts +72 -0
- package/src/plugin.test.ts +68 -0
- package/src/plugin.ts +75 -9
- package/src/skills/chant-terraform.md +1 -1
package/src/hcl/parse.ts
CHANGED
|
@@ -18,14 +18,20 @@
|
|
|
18
18
|
* changed in `packages/core/package.json` for this.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import { readdirSync, readFileSync } from "node:fs";
|
|
21
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
22
22
|
import { join } from "node:path";
|
|
23
23
|
import { loadHcl2json, type Hcl2Json } from "@intentius/chant/terraform/parse";
|
|
24
24
|
import { DECLARABLE_MARKER, type Declarable } from "@intentius/chant/declarable";
|
|
25
|
+
import type { SuppressionDirective } from "@intentius/chant/lint/suppressions";
|
|
26
|
+
import type { TerraformDeleteMode } from "../config";
|
|
27
|
+
import { scanSuppressions, directivesFor, type FileScan } from "./suppressions";
|
|
25
28
|
|
|
26
29
|
/** A parsed HCL block body, as `@cdktf/hcl2json` encodes it. */
|
|
27
30
|
export type BlockBody = Record<string, unknown>;
|
|
28
31
|
|
|
32
|
+
/** Whether a root runs under choudoufu with a declared estate (#2103). */
|
|
33
|
+
export type TerraformRootMode = "live" | "state";
|
|
34
|
+
|
|
29
35
|
/** The entity every block becomes. `props` is what post-synth checks read. */
|
|
30
36
|
export interface TerraformEntity extends Declarable {
|
|
31
37
|
readonly lexicon: "terraform";
|
|
@@ -39,7 +45,70 @@ export interface TerraformEntity extends Declarable {
|
|
|
39
45
|
readonly file: string;
|
|
40
46
|
/** Configured root name this block belongs to. */
|
|
41
47
|
readonly root: string;
|
|
48
|
+
/**
|
|
49
|
+
* The chain of `module.<name>` calls that reached this block, outermost
|
|
50
|
+
* first (chant #2112). Empty or absent on a block declared in the root
|
|
51
|
+
* module itself; `["module.cdn"]` on a block of the module `./modules/cdn`
|
|
52
|
+
* called as `cdn` from the root; `["module.cdn", "module.bucket"]` one
|
|
53
|
+
* level deeper. The same chain is in the entity key, which is
|
|
54
|
+
* `<root>/module.cdn/<address>` for the first case, so a reader can tell
|
|
55
|
+
* root scope from child scope from either.
|
|
56
|
+
*
|
|
57
|
+
* The names are the CALL names (`module.<label>`), not the directory the
|
|
58
|
+
* source points at, matching `terraform show -json`'s `child_modules[]`
|
|
59
|
+
* addresses, choudoufu's marker grammar, and tflint's `Callers:` chain.
|
|
60
|
+
*/
|
|
61
|
+
readonly callers?: readonly string[];
|
|
62
|
+
/**
|
|
63
|
+
* Whether this root is live: `terraform.binary` is `"choudoufu"` and an
|
|
64
|
+
* estate is declared (a `live` block or an `estate.chdf.hcl` sidecar).
|
|
65
|
+
* Set on every entity of the root, not just the `Terraform::Live` one, so
|
|
66
|
+
* a post-synth check or `describeResources()` reads it without
|
|
67
|
+
* re-parsing (#2103). Absent when the caller supplied no `binary` (the
|
|
68
|
+
* `chant audit` content path, which has no project config to read).
|
|
69
|
+
*/
|
|
70
|
+
readonly mode?: TerraformRootMode;
|
|
71
|
+
/** The declared estate name. Present only when `mode` is `"live"`. */
|
|
72
|
+
readonly estate?: string;
|
|
73
|
+
/** `terraform.roots.<name>.workspace`, recorded so TF025 can flag a non-default one on a live root. */
|
|
74
|
+
readonly workspace?: string;
|
|
75
|
+
/**
|
|
76
|
+
* `terraform.roots.<name>.delete` (#2106), recorded so TF026 can check a
|
|
77
|
+
* live root declaring `"never"` against its `policy` block's
|
|
78
|
+
* `undeclared_tagged` setting. Present regardless of mode, same as
|
|
79
|
+
* `workspace`; only a live root's TF026 reads it.
|
|
80
|
+
*/
|
|
81
|
+
readonly delete?: TerraformDeleteMode;
|
|
82
|
+
/**
|
|
83
|
+
* 1-based line the block's header starts on, from a line scan over the
|
|
84
|
+
* raw source (chant #2111): hcl2json exposes no ranges to read this from
|
|
85
|
+
* instead (see `../hcl/suppressions.ts`). Undefined when the scan's block
|
|
86
|
+
* regex didn't recognize the header (unusual formatting); a suppression
|
|
87
|
+
* comment simply can't anchor to that block then.
|
|
88
|
+
*/
|
|
89
|
+
readonly line?: number;
|
|
90
|
+
/**
|
|
91
|
+
* The raw text of the file this block came from, verbatim.
|
|
92
|
+
*
|
|
93
|
+
* Every other field here is post-`hcl2json`, and that parse is lossy in
|
|
94
|
+
* one way that matters: it renders a bare reference (`value = var.x`) and
|
|
95
|
+
* a quoted interpolation (`value = "${var.x}"`) as the same string,
|
|
96
|
+
* `"${var.x}"`. The second is the deprecated pre-0.12 form TF016 reports
|
|
97
|
+
* and the first is idiomatic, so a check written against `body` alone
|
|
98
|
+
* would flag every reference in the root. The source text is where the
|
|
99
|
+
* quotes still exist. Empty when a caller built the entity by hand.
|
|
100
|
+
*/
|
|
101
|
+
readonly source: string;
|
|
42
102
|
};
|
|
103
|
+
/**
|
|
104
|
+
* `# chant-ignore`/`chant-ignore-file`/`chant-ignore-block` directives that
|
|
105
|
+
* apply to this entity (chant #2111): the ones anchored to its own block,
|
|
106
|
+
* plus any file-level one. Read generically by
|
|
107
|
+
* `@intentius/chant/lint/suppressions`'s `entitySuppressions()`; see that
|
|
108
|
+
* module's doc comment for why this is a plain duck-typed field rather than
|
|
109
|
+
* a `PostSynthContext` addition.
|
|
110
|
+
*/
|
|
111
|
+
readonly suppressions?: readonly SuppressionDirective[];
|
|
43
112
|
}
|
|
44
113
|
|
|
45
114
|
/** `entityType` per block kind. */
|
|
@@ -51,6 +120,11 @@ export const MODULE_TYPE = "Terraform::Module";
|
|
|
51
120
|
export const VARIABLE_TYPE = "Terraform::Variable";
|
|
52
121
|
export const OUTPUT_TYPE = "Terraform::Output";
|
|
53
122
|
export const LOCALS_TYPE = "Terraform::Locals";
|
|
123
|
+
/** A `live { estate = "..." }` block, or an `estate.chdf.hcl` sidecar's content (#2103). */
|
|
124
|
+
export const LIVE_TYPE = "Terraform::Live";
|
|
125
|
+
|
|
126
|
+
/** The sidecar filename choudoufu reads an estate declaration from when no in-block `live { }` is used. */
|
|
127
|
+
export const LIVE_SIDECAR_FILENAME = "estate.chdf.hcl";
|
|
54
128
|
|
|
55
129
|
/**
|
|
56
130
|
* Build one entity. Written out rather than run through `createResource`
|
|
@@ -66,13 +140,30 @@ export function terraformEntity(
|
|
|
66
140
|
body: BlockBody,
|
|
67
141
|
file: string,
|
|
68
142
|
root: string,
|
|
143
|
+
extra?: { mode?: TerraformRootMode; estate?: string; workspace?: string; delete?: TerraformDeleteMode; callers?: readonly string[] },
|
|
144
|
+
source: string = "",
|
|
145
|
+
line?: number,
|
|
146
|
+
suppressions?: readonly SuppressionDirective[],
|
|
69
147
|
): TerraformEntity {
|
|
70
148
|
return {
|
|
71
149
|
[DECLARABLE_MARKER]: true,
|
|
72
150
|
lexicon: "terraform",
|
|
73
151
|
entityType,
|
|
74
152
|
kind: "resource",
|
|
75
|
-
props: {
|
|
153
|
+
props: {
|
|
154
|
+
address,
|
|
155
|
+
body,
|
|
156
|
+
file,
|
|
157
|
+
root,
|
|
158
|
+
source,
|
|
159
|
+
line,
|
|
160
|
+
...(extra?.mode !== undefined ? { mode: extra.mode } : {}),
|
|
161
|
+
...(extra?.estate !== undefined ? { estate: extra.estate } : {}),
|
|
162
|
+
...(extra?.workspace !== undefined ? { workspace: extra.workspace } : {}),
|
|
163
|
+
...(extra?.delete !== undefined ? { delete: extra.delete } : {}),
|
|
164
|
+
...(extra?.callers !== undefined && extra.callers.length > 0 ? { callers: extra.callers } : {}),
|
|
165
|
+
},
|
|
166
|
+
suppressions,
|
|
76
167
|
};
|
|
77
168
|
}
|
|
78
169
|
|
|
@@ -115,6 +206,18 @@ export function listTerraformFiles(dir: string): string[] {
|
|
|
115
206
|
.sort();
|
|
116
207
|
}
|
|
117
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Read the `estate.chdf.hcl` sidecar beside `dir`'s `.tf` files, if present.
|
|
211
|
+
* Sibling to {@link listTerraformFiles}: the sidecar is plain HCL (a bare
|
|
212
|
+
* `estate = "..."` attribute, no wrapper block) and is never a `.tf` file, so
|
|
213
|
+
* it falls outside that glob and needs its own read.
|
|
214
|
+
*/
|
|
215
|
+
export function readLiveSidecarFile(dir: string): TerraformFile | undefined {
|
|
216
|
+
const path = join(dir, LIVE_SIDECAR_FILENAME);
|
|
217
|
+
if (!existsSync(path)) return undefined;
|
|
218
|
+
return { name: LIVE_SIDECAR_FILENAME, source: readFileSync(path, "utf-8") };
|
|
219
|
+
}
|
|
220
|
+
|
|
118
221
|
function asArray(value: unknown): unknown[] {
|
|
119
222
|
return Array.isArray(value) ? value : [];
|
|
120
223
|
}
|
|
@@ -125,29 +228,85 @@ function asRecord(value: unknown): Record<string, unknown> {
|
|
|
125
228
|
: {};
|
|
126
229
|
}
|
|
127
230
|
|
|
231
|
+
/** Every `live { ... }` block nested inside every `terraform { ... }` block of `tree`. */
|
|
232
|
+
function liveBlocksIn(tree: Record<string, unknown>): BlockBody[] {
|
|
233
|
+
const out: BlockBody[] = [];
|
|
234
|
+
for (const tfBody of asArray(tree["terraform"])) {
|
|
235
|
+
const body = asRecord(tfBody);
|
|
236
|
+
for (const liveBody of asArray(body["live"])) out.push(asRecord(liveBody));
|
|
237
|
+
}
|
|
238
|
+
return out;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Options threading the config-level facts a root's mode depends on into the
|
|
243
|
+
* parse, so `blocksToEntities` can stamp `mode`/`estate`/`workspace` onto
|
|
244
|
+
* every entity without a second pass over the project config (#2103).
|
|
245
|
+
*/
|
|
246
|
+
export interface TerraformRootModeOptions {
|
|
247
|
+
/** `terraform.binary`. A root is live only when this is `"choudoufu"` and an estate is declared. */
|
|
248
|
+
binary?: string;
|
|
249
|
+
/** `terraform.roots.<name>.workspace`, recorded verbatim regardless of mode. */
|
|
250
|
+
workspace?: string;
|
|
251
|
+
/** `terraform.roots.<name>.delete`, recorded verbatim regardless of mode (#2106). */
|
|
252
|
+
delete?: TerraformDeleteMode;
|
|
253
|
+
}
|
|
254
|
+
|
|
128
255
|
/**
|
|
129
256
|
* Parse a set of files into entities keyed `<root>/<address>`. Two blocks that
|
|
130
257
|
* genuinely share an address (two `locals` blocks, or the same address in two
|
|
131
258
|
* files of one root) are numbered `~2`, `~3` rather than overwriting.
|
|
259
|
+
*
|
|
260
|
+
* Recognises both ways an estate is declared: a `live { }` block nested in a
|
|
261
|
+
* `terraform { }` block of any file here, and, when `sidecar` is given, the
|
|
262
|
+
* `estate.chdf.hcl` sidecar's bare `estate = "..."` attribute. Either becomes
|
|
263
|
+
* a {@link LIVE_TYPE} entity, and the root is live (`mode: "live"`) exactly
|
|
264
|
+
* when `modeOptions.binary` is `"choudoufu"` and one of the two named an
|
|
265
|
+
* estate; that verdict, and the estate name when live, is then stamped onto
|
|
266
|
+
* every entity this call returns, sidecar and in-block declaration alike so
|
|
267
|
+
* a post-synth check or `describeResources()` reads it without re-parsing.
|
|
268
|
+
* When both forms are present the sidecar wins, matching choudoufu's own
|
|
269
|
+
* "the sidecar is the leading one" (choudoufu refuses the combination
|
|
270
|
+
* outright; this lexicon does not police that here, see TF024/TF025 for
|
|
271
|
+
* what it does police on a live root).
|
|
272
|
+
*
|
|
273
|
+
* `callers` is the module-call chain these files were reached through
|
|
274
|
+
* (chant #2112), empty for a root module. When it is non-empty the keys
|
|
275
|
+
* become `<root>/module.<name>/<address>` (one segment per caller) and every
|
|
276
|
+
* entity carries the chain on `props.callers`. `./descend.ts` is what fills
|
|
277
|
+
* it in; nothing else should pass it.
|
|
132
278
|
*/
|
|
133
279
|
export async function blocksToEntities(
|
|
134
280
|
files: readonly TerraformFile[],
|
|
135
281
|
root: string,
|
|
136
282
|
hcl2json?: Hcl2Json,
|
|
283
|
+
modeOptions?: TerraformRootModeOptions,
|
|
284
|
+
sidecar?: TerraformFile,
|
|
285
|
+
callers: readonly string[] = [],
|
|
137
286
|
): Promise<Map<string, Declarable>> {
|
|
138
287
|
const parser = hcl2json ?? (await loadHcl2json());
|
|
139
288
|
const entities = new Map<string, Declarable>();
|
|
289
|
+
const prefix = callers.length > 0 ? `${root}/${callers.join("/")}` : root;
|
|
140
290
|
|
|
141
|
-
const add = (entityType: string, address: string, body: unknown, file:
|
|
291
|
+
const add = (entityType: string, address: string, body: unknown, file: TerraformFile, scan: FileScan): void => {
|
|
292
|
+
const { line, suppressions } = directivesFor(scan, address);
|
|
142
293
|
const entity = terraformEntity(
|
|
143
294
|
entityType,
|
|
144
295
|
address,
|
|
145
296
|
(typeof body === "object" && body !== null ? body : {}) as BlockBody,
|
|
146
|
-
file,
|
|
297
|
+
file.name,
|
|
147
298
|
root,
|
|
299
|
+
{
|
|
300
|
+
...(modeOptions?.workspace !== undefined ? { workspace: modeOptions.workspace } : {}),
|
|
301
|
+
...(modeOptions?.delete !== undefined ? { delete: modeOptions.delete } : {}),
|
|
302
|
+
...(callers.length > 0 ? { callers } : {}),
|
|
303
|
+
},
|
|
304
|
+
file.source,
|
|
305
|
+
line,
|
|
306
|
+
suppressions,
|
|
148
307
|
);
|
|
149
|
-
let key = `${
|
|
150
|
-
for (let n = 2; entities.has(key); n++) key = `${
|
|
308
|
+
let key = `${prefix}/${address}`;
|
|
309
|
+
for (let n = 2; entities.has(key); n++) key = `${prefix}/${address}~${n}`;
|
|
151
310
|
entities.set(key, entity);
|
|
152
311
|
};
|
|
153
312
|
|
|
@@ -156,9 +315,10 @@ export async function blocksToEntities(
|
|
|
156
315
|
tree: Record<string, unknown>,
|
|
157
316
|
section: string,
|
|
158
317
|
entityType: string,
|
|
159
|
-
file:
|
|
318
|
+
file: TerraformFile,
|
|
319
|
+
scan: FileScan,
|
|
160
320
|
): void => {
|
|
161
|
-
for (const body of asArray(tree[section])) add(entityType, section, body, file);
|
|
321
|
+
for (const body of asArray(tree[section])) add(entityType, section, body, file, scan);
|
|
162
322
|
};
|
|
163
323
|
|
|
164
324
|
/** `provider`, `module`, `variable`, `output`: one label, so `Record<name, body[]>`. */
|
|
@@ -167,10 +327,11 @@ export async function blocksToEntities(
|
|
|
167
327
|
section: string,
|
|
168
328
|
entityType: string,
|
|
169
329
|
address: (name: string) => string,
|
|
170
|
-
file:
|
|
330
|
+
file: TerraformFile,
|
|
331
|
+
scan: FileScan,
|
|
171
332
|
): void => {
|
|
172
333
|
for (const [name, bodies] of Object.entries(asRecord(tree[section]))) {
|
|
173
|
-
for (const body of asArray(bodies)) add(entityType, address(name), body, file);
|
|
334
|
+
for (const body of asArray(bodies)) add(entityType, address(name), body, file, scan);
|
|
174
335
|
}
|
|
175
336
|
};
|
|
176
337
|
|
|
@@ -180,56 +341,117 @@ export async function blocksToEntities(
|
|
|
180
341
|
section: string,
|
|
181
342
|
entityType: string,
|
|
182
343
|
address: (type: string, name: string) => string,
|
|
183
|
-
file:
|
|
344
|
+
file: TerraformFile,
|
|
345
|
+
scan: FileScan,
|
|
184
346
|
): void => {
|
|
185
347
|
for (const [type, named] of Object.entries(asRecord(tree[section]))) {
|
|
186
348
|
for (const [name, bodies] of Object.entries(asRecord(named))) {
|
|
187
|
-
for (const body of asArray(bodies)) add(entityType, address(type, name), body, file);
|
|
349
|
+
for (const body of asArray(bodies)) add(entityType, address(type, name), body, file, scan);
|
|
188
350
|
}
|
|
189
351
|
}
|
|
190
352
|
};
|
|
191
353
|
|
|
192
354
|
for (const file of files) {
|
|
193
355
|
const tree = (await parser.parse(file.name, file.source)) as Record<string, unknown>;
|
|
194
|
-
|
|
195
|
-
unlabelled(tree, "
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
oneLabel(tree, "
|
|
199
|
-
oneLabel(tree, "
|
|
200
|
-
|
|
201
|
-
|
|
356
|
+
const scan = scanSuppressions(file.name, file.source);
|
|
357
|
+
unlabelled(tree, "terraform", TERRAFORM_TYPE, file, scan);
|
|
358
|
+
for (const liveBody of liveBlocksIn(tree)) add(LIVE_TYPE, "live", liveBody, file, scan);
|
|
359
|
+
unlabelled(tree, "locals", LOCALS_TYPE, file, scan);
|
|
360
|
+
oneLabel(tree, "provider", PROVIDER_TYPE, (n) => `provider.${n}`, file, scan);
|
|
361
|
+
oneLabel(tree, "module", MODULE_TYPE, (n) => `module.${n}`, file, scan);
|
|
362
|
+
oneLabel(tree, "variable", VARIABLE_TYPE, (n) => `var.${n}`, file, scan);
|
|
363
|
+
oneLabel(tree, "output", OUTPUT_TYPE, (n) => `output.${n}`, file, scan);
|
|
364
|
+
twoLabels(tree, "resource", RESOURCE_TYPE, (t, n) => `${t}.${n}`, file, scan);
|
|
365
|
+
twoLabels(tree, "data", DATA_TYPE, (t, n) => `data.${t}.${n}`, file, scan);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (sidecar) {
|
|
369
|
+
const tree = (await parser.parse(sidecar.name, sidecar.source)) as Record<string, unknown>;
|
|
370
|
+
if (typeof tree["estate"] === "string") add(LIVE_TYPE, "live", tree, sidecar, scanSuppressions(sidecar.name, sidecar.source));
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Which of the (at most two) Live entities names the estate, preferring the
|
|
374
|
+
// sidecar over an in-block declaration when both are present.
|
|
375
|
+
let estate: string | undefined;
|
|
376
|
+
for (const entity of entities.values()) {
|
|
377
|
+
if (entity.entityType !== LIVE_TYPE) continue;
|
|
378
|
+
const props = (entity as TerraformEntity).props;
|
|
379
|
+
const name = typeof props.body["estate"] === "string" ? (props.body["estate"] as string) : undefined;
|
|
380
|
+
if (name === undefined) continue;
|
|
381
|
+
if (estate === undefined || props.file === LIVE_SIDECAR_FILENAME) estate = name;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const mode: TerraformRootMode = modeOptions?.binary === "choudoufu" && estate !== undefined ? "live" : "state";
|
|
385
|
+
for (const [key, entity] of entities) {
|
|
386
|
+
const te = entity as TerraformEntity;
|
|
387
|
+
const stamped: TerraformEntity = {
|
|
388
|
+
...te,
|
|
389
|
+
props: { ...te.props, mode, ...(mode === "live" ? { estate } : {}) },
|
|
390
|
+
};
|
|
391
|
+
entities.set(key, stamped);
|
|
202
392
|
}
|
|
203
393
|
|
|
204
394
|
return entities;
|
|
205
395
|
}
|
|
206
396
|
|
|
207
397
|
/**
|
|
208
|
-
* Parse a root module directory. Reads every `.tf` directly under `dir
|
|
209
|
-
*
|
|
210
|
-
* that becomes a
|
|
398
|
+
* Parse a root module directory. Reads every `.tf` directly under `dir`, plus
|
|
399
|
+
* the `estate.chdf.hcl` sidecar beside them when present. Throws whatever the
|
|
400
|
+
* parser throws for malformed HCL; `buildRoots()` is where that becomes a
|
|
401
|
+
* warning.
|
|
211
402
|
*/
|
|
212
403
|
export async function parseTerraformRootDir(
|
|
213
404
|
dir: string,
|
|
214
405
|
root: string,
|
|
215
406
|
hcl2json?: Hcl2Json,
|
|
407
|
+
modeOptions?: TerraformRootModeOptions,
|
|
408
|
+
callers: readonly string[] = [],
|
|
216
409
|
): Promise<Map<string, Declarable>> {
|
|
217
410
|
const files = listTerraformFiles(dir).map((name) => ({
|
|
218
411
|
name,
|
|
219
412
|
source: readFileSync(join(dir, name), "utf-8"),
|
|
220
413
|
}));
|
|
221
|
-
|
|
414
|
+
// A child module gets no sidecar read: `estate.chdf.hcl` declares the
|
|
415
|
+
// estate a ROOT runs against, and choudoufu reads it from the root's own
|
|
416
|
+
// directory only. See `./descend.ts`.
|
|
417
|
+
const sidecar = callers.length === 0 ? readLiveSidecarFile(dir) : undefined;
|
|
418
|
+
return blocksToEntities(files, root, hcl2json, modeOptions, sidecar, callers);
|
|
222
419
|
}
|
|
223
420
|
|
|
224
421
|
/**
|
|
225
422
|
* Parse the joined-file string form `chant audit` produces for a discovered
|
|
226
|
-
* root module (`AuditInput.content`).
|
|
227
|
-
*
|
|
423
|
+
* root module (`AuditInput.content`). `auditEntities()` is what calls it; that
|
|
424
|
+
* hook's single-argument contract carries no project config, so `modeOptions`
|
|
425
|
+
* is left undefined there and every entity gets `mode: "state"`; the audit
|
|
426
|
+
* path does not detect live mode (#2103). `modeOptions` exists here mainly for
|
|
427
|
+
* tests exercising the mode-stamping behaviour directly against inline HCL,
|
|
428
|
+
* the same way `parseTerraformRootDir`'s callers exercise it against files.
|
|
228
429
|
*/
|
|
229
430
|
export async function parseTerraformRootContent(
|
|
230
431
|
content: string,
|
|
231
432
|
root: string,
|
|
232
433
|
hcl2json?: Hcl2Json,
|
|
434
|
+
modeOptions?: TerraformRootModeOptions,
|
|
233
435
|
): Promise<Map<string, Declarable>> {
|
|
234
|
-
return blocksToEntities(splitBundleContent(content), root, hcl2json);
|
|
436
|
+
return blocksToEntities(splitBundleContent(content), root, hcl2json, modeOptions);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* The module scope an entity key names: the part before the address.
|
|
441
|
+
*
|
|
442
|
+
* `"app/aws_s3_bucket.assets"` is scope `"app"` (the root module);
|
|
443
|
+
* `"app/module.cdn/aws_s3_bucket.assets"` is scope `"app/module.cdn"`. Keys
|
|
444
|
+
* are built here (see {@link blocksToEntities}), so the split lives here too:
|
|
445
|
+
* the reference index (`./references.ts`) and the module-scoped checks read
|
|
446
|
+
* scopes through it rather than re-deriving the key shape.
|
|
447
|
+
*/
|
|
448
|
+
export function scopeOfKey(key: string): string {
|
|
449
|
+
const slash = key.lastIndexOf("/");
|
|
450
|
+
return slash === -1 ? "" : key.slice(0, slash);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/** The `module.<name>` chain in an entity key, outermost first. Empty for a root-module block. */
|
|
454
|
+
export function callersOfKey(key: string): string[] {
|
|
455
|
+
const parts = key.split("/");
|
|
456
|
+
return parts.length <= 2 ? [] : parts.slice(1, -1);
|
|
235
457
|
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The reference index (chant #2112), against
|
|
3
|
+
* `src/__fixtures__/references/main.tf`, which carries every form the index
|
|
4
|
+
* collects exactly once.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { beforeAll, describe, expect, it } from "vitest";
|
|
11
|
+
import type { Declarable } from "@intentius/chant/declarable";
|
|
12
|
+
import { blocksToEntities } from "./parse";
|
|
13
|
+
import {
|
|
14
|
+
buildReferenceIndex,
|
|
15
|
+
collectReferences,
|
|
16
|
+
isReferenced,
|
|
17
|
+
providerReference,
|
|
18
|
+
referencesInBody,
|
|
19
|
+
referencesTo,
|
|
20
|
+
type ReferenceIndex,
|
|
21
|
+
} from "./references";
|
|
22
|
+
|
|
23
|
+
const FIXTURE = join(dirname(fileURLToPath(import.meta.url)), "..", "__fixtures__", "references", "main.tf");
|
|
24
|
+
|
|
25
|
+
let entities: Map<string, Declarable>;
|
|
26
|
+
let index: ReferenceIndex;
|
|
27
|
+
|
|
28
|
+
beforeAll(async () => {
|
|
29
|
+
entities = await blocksToEntities([{ name: "main.tf", source: readFileSync(FIXTURE, "utf-8") }], "app");
|
|
30
|
+
index = buildReferenceIndex(entities);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("collectReferences", () => {
|
|
34
|
+
it("reads each reference form out of one expression string", () => {
|
|
35
|
+
expect(collectReferences("${var.region}").sort()).toEqual(["var.region"]);
|
|
36
|
+
expect(collectReferences("${local.tags}").sort()).toEqual(["local.tags"]);
|
|
37
|
+
expect(collectReferences("${data.aws_ami.ubuntu.id}").sort()).toEqual(["data.aws_ami.ubuntu"]);
|
|
38
|
+
expect(collectReferences("${module.cdn.url}").sort()).toEqual(["module.cdn"]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("does not read a data source as a bare type reference", () => {
|
|
42
|
+
expect(collectReferences("${data.aws_ami.ubuntu.id}")).not.toContain("var.aws_ami");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("reads several references out of one interpolated string", () => {
|
|
46
|
+
expect(collectReferences("assets-${var.region}-${local.suffix}").sort()).toEqual([
|
|
47
|
+
"local.suffix",
|
|
48
|
+
"var.region",
|
|
49
|
+
]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("finds nothing in a string that names nothing", () => {
|
|
53
|
+
expect(collectReferences("a plain value")).toEqual([]);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("providerReference", () => {
|
|
58
|
+
it("reads the provider meta-argument's own form", () => {
|
|
59
|
+
expect(providerReference("${aws.replica}")).toBe("provider.aws.replica");
|
|
60
|
+
expect(providerReference("aws.replica")).toBe("provider.aws.replica");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("is not fooled by an expression that merely contains a dot", () => {
|
|
64
|
+
expect(providerReference("${var.region}.example.com")).toBeUndefined();
|
|
65
|
+
expect(providerReference(42)).toBeUndefined();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("referencesInBody", () => {
|
|
70
|
+
it("reads a provider reference from the meta-argument and from a providers map", () => {
|
|
71
|
+
expect(referencesInBody({ provider: "${aws.replica}" })).toContain("provider.aws.replica");
|
|
72
|
+
expect(referencesInBody({ providers: { aws: "${aws.replica}" } })).toContain("provider.aws.replica");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("descends through nested blocks", () => {
|
|
76
|
+
expect(referencesInBody({ tags: { Name: "${var.name}" } })).toContain("var.name");
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe("buildReferenceIndex over the fixture", () => {
|
|
81
|
+
it("collects every reference form in the root scope", () => {
|
|
82
|
+
const refs = [...(index.scopes.get("app") ?? new Map()).keys()].sort();
|
|
83
|
+
expect(refs).toEqual([
|
|
84
|
+
"data.aws_ami.ubuntu",
|
|
85
|
+
"local.bucket_name",
|
|
86
|
+
"local.tags",
|
|
87
|
+
"module.cdn",
|
|
88
|
+
"provider.aws.replica",
|
|
89
|
+
"var.region",
|
|
90
|
+
"var.subnets",
|
|
91
|
+
]);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("records which block each reference came from", () => {
|
|
95
|
+
expect([...referencesTo(index, "app", "module.cdn")]).toEqual(["app/output.cdn_url"]);
|
|
96
|
+
expect([...referencesTo(index, "app", "data.aws_ami.ubuntu")]).toEqual(["app/aws_instance.web"]);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("counts a reference from the declaration's own body only when asked to", () => {
|
|
100
|
+
// `local.bucket_name` is built from `var.region` inside the same `locals`
|
|
101
|
+
// block, and read again by `aws_s3_bucket.replica`.
|
|
102
|
+
expect([...referencesTo(index, "app", "var.region")]).toEqual(["app/locals"]);
|
|
103
|
+
expect(isReferenced(index, "app", "var.region")).toBe(true);
|
|
104
|
+
expect(isReferenced(index, "app", "var.region", "app/locals")).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("knows nothing about a token nobody names", () => {
|
|
108
|
+
expect(isReferenced(index, "app", "var.absent")).toBe(false);
|
|
109
|
+
expect(referencesTo(index, "app", "var.absent").size).toBe(0);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("keeps scopes apart", () => {
|
|
113
|
+
expect(index.scopes.has("app")).toBe(true);
|
|
114
|
+
expect(isReferenced(index, "app/module.cdn", "var.region")).toBe(false);
|
|
115
|
+
});
|
|
116
|
+
});
|