@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,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF014: a `provider` block inside a child module that configures anything.
|
|
3
|
+
*
|
|
4
|
+
* Scope: child modules only. A `provider` block in the ROOT module is where
|
|
5
|
+
* providers are supposed to be configured, and TF008 is what checks its
|
|
6
|
+
* contents.
|
|
7
|
+
*
|
|
8
|
+
* A child module that configures its own provider takes the decision away
|
|
9
|
+
* from its caller: the caller can no longer point the module at a different
|
|
10
|
+
* region, account or endpoint, and two calls of the module in one root cannot
|
|
11
|
+
* differ. Worse, Terraform cannot remove a module whose provider is declared
|
|
12
|
+
* inside it, because destroying the resources needs a provider configuration
|
|
13
|
+
* that the removal itself deletes. That is the "Provider configuration not
|
|
14
|
+
* present" failure that leaves a root unable to plan.
|
|
15
|
+
*
|
|
16
|
+
* The one legal form is a provider block that declares only `alias`, which
|
|
17
|
+
* names a slot the caller fills through `providers = { aws.replica =
|
|
18
|
+
* aws.west }`. That is a declaration, not a configuration, so a block whose
|
|
19
|
+
* body has nothing but `alias` is not reported. Everything else is: a
|
|
20
|
+
* `region`, a `profile`, an `assume_role` block, and `version` too, which has
|
|
21
|
+
* been deprecated in favour of `required_providers` since Terraform 0.13.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type {
|
|
25
|
+
PostSynthCheck,
|
|
26
|
+
PostSynthContext,
|
|
27
|
+
PostSynthDiagnostic,
|
|
28
|
+
} from "@intentius/chant/lint/post-synth";
|
|
29
|
+
import { isResourceDeclarable } from "@intentius/chant/declarable";
|
|
30
|
+
import { PROVIDER_TYPE, type BlockBody, type TerraformEntity } from "../../hcl/parse";
|
|
31
|
+
import { isChildScoped } from "./scope";
|
|
32
|
+
|
|
33
|
+
/** Everything in a provider body that is not the `alias` slot declaration. */
|
|
34
|
+
function configuredKeys(body: BlockBody): string[] {
|
|
35
|
+
return Object.keys(body).filter((key) => key !== "alias");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const tf014: PostSynthCheck = {
|
|
39
|
+
id: "TF014",
|
|
40
|
+
description: "Child module configures a provider block",
|
|
41
|
+
|
|
42
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
43
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
44
|
+
|
|
45
|
+
for (const [key, entity] of ctx.entities) {
|
|
46
|
+
if (entity.entityType !== PROVIDER_TYPE || !isResourceDeclarable(entity)) continue;
|
|
47
|
+
if (!isChildScoped(entity)) continue;
|
|
48
|
+
|
|
49
|
+
const props = (entity as TerraformEntity).props as Partial<TerraformEntity["props"]>;
|
|
50
|
+
const body = (typeof props.body === "object" && props.body !== null ? props.body : {}) as BlockBody;
|
|
51
|
+
const configured = configuredKeys(body);
|
|
52
|
+
if (configured.length === 0) continue;
|
|
53
|
+
|
|
54
|
+
const address = typeof props.address === "string" ? props.address : key;
|
|
55
|
+
diagnostics.push({
|
|
56
|
+
checkId: "TF014",
|
|
57
|
+
severity: "warning",
|
|
58
|
+
message:
|
|
59
|
+
`Child module block "${address}" configures a provider (${configured.sort().join(", ")}). ` +
|
|
60
|
+
"A module that configures its own provider cannot be pointed at another region or account by " +
|
|
61
|
+
"its caller, cannot be called twice with different settings, and cannot be removed, since " +
|
|
62
|
+
"destroying its resources needs the provider configuration the removal deletes. Configure the " +
|
|
63
|
+
"provider in the root module and pass it in with `providers = { ... }`, leaving at most an " +
|
|
64
|
+
"`alias`-only block here.",
|
|
65
|
+
entity: key,
|
|
66
|
+
lexicon: "terraform",
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return diagnostics;
|
|
71
|
+
},
|
|
72
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF015: a `backend` or `cloud` block inside a child module.
|
|
3
|
+
*
|
|
4
|
+
* Scope: child modules only. This is TF001's mirror image. TF001 wants a root
|
|
5
|
+
* module to declare a REMOTE backend, because a root without one keeps its
|
|
6
|
+
* state on local disk. A child module must declare neither: state belongs to
|
|
7
|
+
* the root,
|
|
8
|
+
* one state per root however many modules it calls, and Terraform says so
|
|
9
|
+
* directly, "A backend block can only appear in the root module" and the
|
|
10
|
+
* same for `cloud`. Depending on the version, the block is ignored with a
|
|
11
|
+
* warning or the root refuses to initialize, and either way the block is a
|
|
12
|
+
* claim about where state lives that the module does not get to make.
|
|
13
|
+
*
|
|
14
|
+
* The usual cause is a directory that used to be a root module and became a
|
|
15
|
+
* child when someone factored it out, with its `terraform` block carried
|
|
16
|
+
* along. The rest of that block is fine to keep: `required_version` and
|
|
17
|
+
* `required_providers` are meaningful in a child module and are not reported
|
|
18
|
+
* here.
|
|
19
|
+
*
|
|
20
|
+
* Label-blind on purpose (#2218). TF001 had to start reading the backend
|
|
21
|
+
* block's type label, since `local` is the state placement it warns about
|
|
22
|
+
* rather than a cure for it. Nothing of the sort applies here: what a child
|
|
23
|
+
* module may not do is name a backend at all, `local` included, and
|
|
24
|
+
* Terraform's own refusal ("A backend block can only appear in the root
|
|
25
|
+
* module") does not read the label either. `hasBlock` is the right test, and
|
|
26
|
+
* the `positive` fixture's child declares `backend "local"` for that reason.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import type {
|
|
30
|
+
PostSynthCheck,
|
|
31
|
+
PostSynthContext,
|
|
32
|
+
PostSynthDiagnostic,
|
|
33
|
+
} from "@intentius/chant/lint/post-synth";
|
|
34
|
+
import { isResourceDeclarable } from "@intentius/chant/declarable";
|
|
35
|
+
import { TERRAFORM_TYPE, type BlockBody, type TerraformEntity } from "../../hcl/parse";
|
|
36
|
+
import { hasBlock } from "./tf001";
|
|
37
|
+
import { isChildScoped } from "./scope";
|
|
38
|
+
|
|
39
|
+
export const tf015: PostSynthCheck = {
|
|
40
|
+
id: "TF015",
|
|
41
|
+
description: "Child module declares a backend or cloud block",
|
|
42
|
+
|
|
43
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
44
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
45
|
+
|
|
46
|
+
for (const [key, entity] of ctx.entities) {
|
|
47
|
+
if (entity.entityType !== TERRAFORM_TYPE || !isResourceDeclarable(entity)) continue;
|
|
48
|
+
if (!isChildScoped(entity)) continue;
|
|
49
|
+
|
|
50
|
+
const props = (entity as TerraformEntity).props as Partial<TerraformEntity["props"]>;
|
|
51
|
+
const body = (typeof props.body === "object" && props.body !== null ? props.body : {}) as BlockBody;
|
|
52
|
+
const backend = hasBlock(body, "backend");
|
|
53
|
+
const cloud = hasBlock(body, "cloud");
|
|
54
|
+
if (!backend && !cloud) continue;
|
|
55
|
+
|
|
56
|
+
const blockKind = backend ? "backend" : "cloud";
|
|
57
|
+
diagnostics.push({
|
|
58
|
+
checkId: "TF015",
|
|
59
|
+
severity: "warning",
|
|
60
|
+
message:
|
|
61
|
+
`A child module's terraform block declares a ${blockKind} block. Only a root module may: ` +
|
|
62
|
+
"state belongs to the root, one state file for every module it calls, so this block is either " +
|
|
63
|
+
`ignored with a warning or refused at init. Delete the ${blockKind} block and leave the state ` +
|
|
64
|
+
"configuration to the root module that calls this one.",
|
|
65
|
+
entity: key,
|
|
66
|
+
lexicon: "terraform",
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return diagnostics;
|
|
71
|
+
},
|
|
72
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF016: an attribute whose whole value is one quoted interpolation,
|
|
3
|
+
* `x = "${var.y}"`, the pre-0.12 style Terraform deprecated in 2019.
|
|
4
|
+
*
|
|
5
|
+
* The quotes buy nothing and cost readability: the expression is already an
|
|
6
|
+
* expression, and wrapping it in a template makes every value in the file look
|
|
7
|
+
* like a string whether it is one or not, which hides the actual type of an
|
|
8
|
+
* argument that takes a list or a bool.
|
|
9
|
+
*
|
|
10
|
+
* This is the one rule in the family that reads the block's SOURCE rather than
|
|
11
|
+
* its parsed body, and it has to. `@cdktf/hcl2json` renders both `x = var.y`
|
|
12
|
+
* and `x = "${var.y}"` as the string `"${var.y}"`, so a check over the parse
|
|
13
|
+
* cannot tell the deprecated form from the idiomatic one and would report every
|
|
14
|
+
* reference in the root. The quotes only survive in the text, so the text is
|
|
15
|
+
* what is scanned, one pass per file, through the same
|
|
16
|
+
* `interpolationOnlyLine` the deterministic fix uses, so the finding and the
|
|
17
|
+
* patch can never disagree.
|
|
18
|
+
*
|
|
19
|
+
* Out of reach, and deliberately not guessed at: tflint also reports the
|
|
20
|
+
* deprecated interpolation inside an object KEY (`"${var.k}" = "value"`).
|
|
21
|
+
* That is a different line shape and its fix is not a simple unquote, so this
|
|
22
|
+
* rule leaves it alone rather than emit a diff it cannot justify.
|
|
23
|
+
*
|
|
24
|
+
* Scope: root and child modules alike (#2112). The condition is a property
|
|
25
|
+
* of the block itself, so a descended module's block is read exactly as a
|
|
26
|
+
* root's is.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import type {
|
|
30
|
+
PostSynthCheck,
|
|
31
|
+
PostSynthContext,
|
|
32
|
+
PostSynthDiagnostic,
|
|
33
|
+
} from "@intentius/chant/lint/post-synth";
|
|
34
|
+
import { isResourceDeclarable } from "@intentius/chant/declarable";
|
|
35
|
+
import { interpolationOnlyLine } from "@intentius/chant/audit/proof";
|
|
36
|
+
import type { TerraformEntity } from "../../hcl/parse";
|
|
37
|
+
|
|
38
|
+
export const tf016: PostSynthCheck = {
|
|
39
|
+
id: "TF016",
|
|
40
|
+
description: "Attribute value is a quoted interpolation of a single expression",
|
|
41
|
+
|
|
42
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
43
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
44
|
+
/** `<root>/<file>` to that file's source, so a file is scanned once however many blocks it holds. */
|
|
45
|
+
const sources = new Map<string, string>();
|
|
46
|
+
|
|
47
|
+
for (const entity of ctx.entities.values()) {
|
|
48
|
+
if (entity.lexicon !== "terraform") continue;
|
|
49
|
+
if (!isResourceDeclarable(entity)) continue;
|
|
50
|
+
const props = (entity as TerraformEntity).props as Partial<TerraformEntity["props"]>;
|
|
51
|
+
if (typeof props.source !== "string" || props.source === "") continue;
|
|
52
|
+
sources.set(`${props.root ?? ""}/${props.file ?? ""}`, props.source);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (const [key, source] of sources) {
|
|
56
|
+
const lines = source.split("\n");
|
|
57
|
+
for (let i = 0; i < lines.length; i++) {
|
|
58
|
+
const parsed = interpolationOnlyLine(lines[i]);
|
|
59
|
+
if (!parsed) continue;
|
|
60
|
+
diagnostics.push({
|
|
61
|
+
checkId: "TF016",
|
|
62
|
+
severity: "info",
|
|
63
|
+
message:
|
|
64
|
+
`${key}:${i + 1}: \`${parsed.name}\` wraps a single expression in a template ` +
|
|
65
|
+
`("\${${parsed.expr}}"). Terraform deprecated that spelling in 0.12. Write ` +
|
|
66
|
+
`\`${parsed.name} = ${parsed.expr}\` instead, so the argument keeps its own type.`,
|
|
67
|
+
entity: `${key}:${i + 1}`,
|
|
68
|
+
lexicon: "terraform",
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return diagnostics;
|
|
74
|
+
},
|
|
75
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF017: a `module` block carries `depends_on`.
|
|
3
|
+
*
|
|
4
|
+
* `depends_on` on a module applies to every resource inside it, including the
|
|
5
|
+
* ones that had no reason to wait, so the whole module serializes behind the
|
|
6
|
+
* named dependency and a plan that could have run in parallel does not. It
|
|
7
|
+
* also makes the dependency invisible from inside the module, where the
|
|
8
|
+
* resource that actually needed the ordering lives.
|
|
9
|
+
*
|
|
10
|
+
* Passing an attribute of the dependency into a module input creates the same
|
|
11
|
+
* edge implicitly, only narrower and self-documenting.
|
|
12
|
+
*
|
|
13
|
+
* Scope: root and child modules alike (#2112). The condition is a property
|
|
14
|
+
* of the block itself, so a descended module's block is read exactly as a
|
|
15
|
+
* root's is.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type {
|
|
19
|
+
PostSynthCheck,
|
|
20
|
+
PostSynthContext,
|
|
21
|
+
PostSynthDiagnostic,
|
|
22
|
+
} from "@intentius/chant/lint/post-synth";
|
|
23
|
+
import { MODULE_TYPE } from "../../hcl/parse";
|
|
24
|
+
import { blocksOfType } from "./blocks";
|
|
25
|
+
|
|
26
|
+
export const tf017: PostSynthCheck = {
|
|
27
|
+
id: "TF017",
|
|
28
|
+
description: "Module block uses depends_on",
|
|
29
|
+
|
|
30
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
31
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
32
|
+
|
|
33
|
+
for (const block of blocksOfType(ctx, MODULE_TYPE)) {
|
|
34
|
+
if (!("depends_on" in block.body)) continue;
|
|
35
|
+
|
|
36
|
+
diagnostics.push({
|
|
37
|
+
checkId: "TF017",
|
|
38
|
+
severity: "info",
|
|
39
|
+
message:
|
|
40
|
+
`"${block.address}" uses \`depends_on\`, which orders every resource in the module behind ` +
|
|
41
|
+
"the dependency, not just the one that needs it. Pass an attribute of the dependency into " +
|
|
42
|
+
"a module input instead, so Terraform derives the edge itself.",
|
|
43
|
+
entity: block.key,
|
|
44
|
+
lexicon: "terraform",
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return diagnostics;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF018: an `output` returns a whole resource or data source.
|
|
3
|
+
*
|
|
4
|
+
* `value = aws_instance.web` exports every attribute the provider schema
|
|
5
|
+
* happens to have, which is a much larger promise than the module meant to
|
|
6
|
+
* make. Consumers reach into whatever they find, the module can no longer
|
|
7
|
+
* change any of it, and a provider upgrade that adds or renames an attribute
|
|
8
|
+
* moves the module's public interface without anyone editing it. Sensitive
|
|
9
|
+
* attributes ride along too.
|
|
10
|
+
*
|
|
11
|
+
* A bare `module.x` reference is not reported: a module's outputs are already
|
|
12
|
+
* a curated surface, which is the property this rule is asking for.
|
|
13
|
+
*
|
|
14
|
+
* Scope: root and child modules alike (#2112). The condition is a property
|
|
15
|
+
* of the block itself, so a descended module's block is read exactly as a
|
|
16
|
+
* root's is.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type {
|
|
20
|
+
PostSynthCheck,
|
|
21
|
+
PostSynthContext,
|
|
22
|
+
PostSynthDiagnostic,
|
|
23
|
+
} from "@intentius/chant/lint/post-synth";
|
|
24
|
+
import { OUTPUT_TYPE } from "../../hcl/parse";
|
|
25
|
+
import { blockName, blocksOfType } from "./blocks";
|
|
26
|
+
|
|
27
|
+
/** Reference prefixes that are not a managed resource type. */
|
|
28
|
+
const NOT_A_RESOURCE = new Set(["var", "local", "module", "each", "count", "self", "path", "terraform"]);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Is this value a reference to a whole resource (`type.name`) or a whole data
|
|
32
|
+
* source (`data.type.name`), with no attribute selected? Anything with a third
|
|
33
|
+
* segment, an index, or a function call around it has already narrowed the
|
|
34
|
+
* value down, which is the fix.
|
|
35
|
+
*/
|
|
36
|
+
function wholeBlockReference(value: unknown): string | undefined {
|
|
37
|
+
if (typeof value !== "string") return undefined;
|
|
38
|
+
const m = /^\$\{([A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_-]*)+)\}$/.exec(value);
|
|
39
|
+
if (!m) return undefined;
|
|
40
|
+
const parts = m[1].split(".");
|
|
41
|
+
if (parts[0] === "data") return parts.length === 3 ? m[1] : undefined;
|
|
42
|
+
if (NOT_A_RESOURCE.has(parts[0])) return undefined;
|
|
43
|
+
return parts.length === 2 ? m[1] : undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const tf018: PostSynthCheck = {
|
|
47
|
+
id: "TF018",
|
|
48
|
+
description: "Output value is a whole resource or data source",
|
|
49
|
+
|
|
50
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
51
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
52
|
+
|
|
53
|
+
for (const block of blocksOfType(ctx, OUTPUT_TYPE)) {
|
|
54
|
+
const reference = wholeBlockReference(block.body.value);
|
|
55
|
+
if (!reference) continue;
|
|
56
|
+
|
|
57
|
+
diagnostics.push({
|
|
58
|
+
checkId: "TF018",
|
|
59
|
+
severity: "info",
|
|
60
|
+
message:
|
|
61
|
+
`Output "${blockName(block.address)}" returns the whole of \`${reference}\`, so every ` +
|
|
62
|
+
"attribute the provider schema has becomes part of this module's public interface, " +
|
|
63
|
+
"including the ones a provider upgrade adds later. Return the attribute the caller needs " +
|
|
64
|
+
`(\`${reference}.id\`, \`${reference}.arn\`) instead.`,
|
|
65
|
+
entity: block.key,
|
|
66
|
+
lexicon: "terraform",
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return diagnostics;
|
|
71
|
+
},
|
|
72
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF019: a meta-argument is explicitly set to its own default, `false`.
|
|
3
|
+
*
|
|
4
|
+
* `sensitive` and `ephemeral` on a `variable` or an `output`, `prevent_destroy`
|
|
5
|
+
* and `create_before_destroy` inside a `lifecycle` block. All four default to
|
|
6
|
+
* false, so writing it changes nothing and reads as though someone considered
|
|
7
|
+
* the setting and turned it off, which is exactly the impression a reviewer
|
|
8
|
+
* should not get from a variable called `password`.
|
|
9
|
+
*
|
|
10
|
+
* The first TF rule with a mechanical fix: the remediation is the deletion of
|
|
11
|
+
* the line, so this ships `fixKind: "deterministic"` and `chant audit` renders
|
|
12
|
+
* the diff in its quick-wins section. `packages/core/src/audit/proof.ts` owns
|
|
13
|
+
* the patch, as it does for every other deterministic rule.
|
|
14
|
+
*
|
|
15
|
+
* Only a literal `false` counts. `prevent_destroy = var.protect` is a real
|
|
16
|
+
* decision expressed as a variable, and hcl2json leaves it a `"${var.protect}"`
|
|
17
|
+
* string rather than a boolean, so it never reaches the comparison.
|
|
18
|
+
*
|
|
19
|
+
* Scope: root and child modules alike (#2112). The condition is a property
|
|
20
|
+
* of the block itself, so a descended module's block is read exactly as a
|
|
21
|
+
* root's is.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type {
|
|
25
|
+
PostSynthCheck,
|
|
26
|
+
PostSynthContext,
|
|
27
|
+
PostSynthDiagnostic,
|
|
28
|
+
} from "@intentius/chant/lint/post-synth";
|
|
29
|
+
import { DATA_TYPE, OUTPUT_TYPE, RESOURCE_TYPE, VARIABLE_TYPE } from "../../hcl/parse";
|
|
30
|
+
import { blocksOfTypes, nestedBodies, type TerraformBlock } from "./blocks";
|
|
31
|
+
|
|
32
|
+
/** Meta-arguments that default to false on a `variable` or an `output`. */
|
|
33
|
+
const BLOCK_ARGS = ["sensitive", "ephemeral"] as const;
|
|
34
|
+
/** Meta-arguments that default to false inside a `lifecycle` block. */
|
|
35
|
+
const LIFECYCLE_ARGS = ["prevent_destroy", "create_before_destroy"] as const;
|
|
36
|
+
|
|
37
|
+
function redundant(block: TerraformBlock, arg: string, where: string): PostSynthDiagnostic {
|
|
38
|
+
return {
|
|
39
|
+
checkId: "TF019",
|
|
40
|
+
severity: "info",
|
|
41
|
+
message:
|
|
42
|
+
`"${block.address}" sets \`${arg} = false\`${where}, which is already the default. Declaring a ` +
|
|
43
|
+
"default reads as a deliberate decision to turn the behaviour off. Delete the line.",
|
|
44
|
+
entity: `${block.key}.${arg}`,
|
|
45
|
+
lexicon: "terraform",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const tf019: PostSynthCheck = {
|
|
50
|
+
id: "TF019",
|
|
51
|
+
description: "Meta-argument explicitly set to its default of false",
|
|
52
|
+
|
|
53
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
54
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
55
|
+
|
|
56
|
+
for (const block of blocksOfTypes(ctx, [VARIABLE_TYPE, OUTPUT_TYPE])) {
|
|
57
|
+
for (const arg of BLOCK_ARGS) {
|
|
58
|
+
if (block.body[arg] === false) diagnostics.push(redundant(block, arg, ""));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for (const block of blocksOfTypes(ctx, [RESOURCE_TYPE, DATA_TYPE])) {
|
|
63
|
+
for (const lifecycle of nestedBodies(block.body, "lifecycle")) {
|
|
64
|
+
for (const arg of LIFECYCLE_ARGS) {
|
|
65
|
+
if (lifecycle[arg] === false) diagnostics.push(redundant(block, arg, " in its `lifecycle` block"));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return diagnostics;
|
|
71
|
+
},
|
|
72
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF020: a `variable`, a `locals` entry, a `data` source or an aliased
|
|
3
|
+
* `provider` that nothing in its module scope references.
|
|
4
|
+
*
|
|
5
|
+
* Scope: root and child modules, each judged on its own. Terraform's
|
|
6
|
+
* namespaces are per module, so `var.region` inside `module.cdn` is the child
|
|
7
|
+
* module's variable and a reference to it in the root proves nothing about
|
|
8
|
+
* the child's. The reference index (`hcl/references.ts`) is keyed the same
|
|
9
|
+
* way, one scope per module, and this check never looks outside the scope the
|
|
10
|
+
* declaration is in.
|
|
11
|
+
*
|
|
12
|
+
* An unused declaration is dead weight rather than a defect, which is why
|
|
13
|
+
* this is report-only. It still earns a finding: a variable nobody reads is
|
|
14
|
+
* usually the leftover of a refactor, and the next reader has to prove that
|
|
15
|
+
* before deleting it. A data source is worse than dead weight, since it is
|
|
16
|
+
* read from the provider's API on every plan whether or not anything uses the
|
|
17
|
+
* result.
|
|
18
|
+
*
|
|
19
|
+
* Two deliberate gaps, both from the index being a string scan rather than an
|
|
20
|
+
* expression tree (see its module doc), and both the reason the credit to
|
|
21
|
+
* tflint's `terraform_unused_declarations` is `overlaps`:
|
|
22
|
+
*
|
|
23
|
+
* - A name mentioned in a string that is not an expression (a `description`
|
|
24
|
+
* that says "overrides var.region") reads as a reference, so the
|
|
25
|
+
* declaration is not reported. Quiet, and wrong in the safe direction.
|
|
26
|
+
* - A value reached only dynamically (`lookup(local.by_env, ...)` where the
|
|
27
|
+
* map is built elsewhere) is a real reference the scan does see, since the
|
|
28
|
+
* local is still named; but a variable consumed only through
|
|
29
|
+
* `TF_VAR_`-style external input is not, and a root's variables are
|
|
30
|
+
* frequently exactly that. Report-only keeps that from failing a build.
|
|
31
|
+
*
|
|
32
|
+
* A `provider` block with no `alias` is never reported: it is the default
|
|
33
|
+
* configuration for its type, used by every resource of that type without
|
|
34
|
+
* naming it. Only an aliased one has to be asked for by name.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import type {
|
|
38
|
+
PostSynthCheck,
|
|
39
|
+
PostSynthContext,
|
|
40
|
+
PostSynthDiagnostic,
|
|
41
|
+
} from "@intentius/chant/lint/post-synth";
|
|
42
|
+
import { isResourceDeclarable } from "@intentius/chant/declarable";
|
|
43
|
+
import {
|
|
44
|
+
DATA_TYPE,
|
|
45
|
+
LOCALS_TYPE,
|
|
46
|
+
PROVIDER_TYPE,
|
|
47
|
+
VARIABLE_TYPE,
|
|
48
|
+
scopeOfKey,
|
|
49
|
+
type BlockBody,
|
|
50
|
+
type TerraformEntity,
|
|
51
|
+
} from "../../hcl/parse";
|
|
52
|
+
import { buildReferenceIndex, isReferenced } from "../../hcl/references";
|
|
53
|
+
|
|
54
|
+
/** What a reader is being told to do about each kind of unused declaration. */
|
|
55
|
+
const ADVICE: Record<string, string> = {
|
|
56
|
+
variable: "Delete it, or reference it where it was meant to be used.",
|
|
57
|
+
local: "Delete it, or reference it where it was meant to be used.",
|
|
58
|
+
data:
|
|
59
|
+
"Delete it. A data source is read from the provider on every plan, so an unused one costs an API " +
|
|
60
|
+
"call and a permission for a value nothing consumes.",
|
|
61
|
+
provider: "Delete it, or pass it to the resources or modules that were meant to use it.",
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const tf020: PostSynthCheck = {
|
|
65
|
+
id: "TF020",
|
|
66
|
+
description: "Declaration is never referenced in its module scope",
|
|
67
|
+
|
|
68
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
69
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
70
|
+
const index = buildReferenceIndex(ctx.entities);
|
|
71
|
+
|
|
72
|
+
const report = (key: string, kind: keyof typeof ADVICE, what: string): void => {
|
|
73
|
+
diagnostics.push({
|
|
74
|
+
checkId: "TF020",
|
|
75
|
+
severity: "warning",
|
|
76
|
+
message: `${what} is declared and never referenced in its module. ${ADVICE[kind]}`,
|
|
77
|
+
entity: key,
|
|
78
|
+
lexicon: "terraform",
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
for (const [key, entity] of ctx.entities) {
|
|
83
|
+
if (!isResourceDeclarable(entity)) continue;
|
|
84
|
+
const props = (entity as TerraformEntity).props as Partial<TerraformEntity["props"]>;
|
|
85
|
+
const body = (typeof props.body === "object" && props.body !== null ? props.body : {}) as BlockBody;
|
|
86
|
+
const address = typeof props.address === "string" ? props.address : key;
|
|
87
|
+
const scope = scopeOfKey(key);
|
|
88
|
+
|
|
89
|
+
if (entity.entityType === VARIABLE_TYPE) {
|
|
90
|
+
// Excluding the variable's own key matters here: a `validation` block
|
|
91
|
+
// reads `var.<self>` in its condition, which would otherwise make
|
|
92
|
+
// every validated variable look used.
|
|
93
|
+
if (!isReferenced(index, scope, address, key)) report(key, "variable", `Variable "${address}"`);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (entity.entityType === LOCALS_TYPE) {
|
|
98
|
+
for (const name of Object.keys(body)) {
|
|
99
|
+
// No self-exclusion: one `locals` block is many declarations, and a
|
|
100
|
+
// local reading another local of the same block is a real use.
|
|
101
|
+
if (!isReferenced(index, scope, `local.${name}`)) report(key, "local", `Local value "local.${name}"`);
|
|
102
|
+
}
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (entity.entityType === DATA_TYPE) {
|
|
107
|
+
if (!isReferenced(index, scope, address, key)) report(key, "data", `Data source "${address}"`);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (entity.entityType === PROVIDER_TYPE) {
|
|
112
|
+
const alias = typeof body.alias === "string" ? body.alias : undefined;
|
|
113
|
+
if (alias === undefined || alias.trim() === "") continue;
|
|
114
|
+
const type = address.startsWith("provider.") ? address.slice("provider.".length) : address;
|
|
115
|
+
if (!isReferenced(index, scope, `provider.${type}.${alias}`, key)) {
|
|
116
|
+
report(key, "provider", `Aliased provider "${type}.${alias}"`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return diagnostics;
|
|
122
|
+
},
|
|
123
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF021: `count` used where `for_each` is safer, judged narrowly.
|
|
3
|
+
*
|
|
4
|
+
* Scope: root and child modules. A resource is a resource wherever it is
|
|
5
|
+
* declared, and the failure mode below does not depend on the scope.
|
|
6
|
+
*
|
|
7
|
+
* `count` addresses its instances by position: `aws_instance.web[0]`,
|
|
8
|
+
* `[1]`, `[2]`. Delete the middle element of the list the count was derived
|
|
9
|
+
* from and every instance after it shifts down one, so Terraform plans to
|
|
10
|
+
* destroy and recreate them all, in order, to move each one's identity onto
|
|
11
|
+
* the next index. `for_each` addresses by key instead, and removing one
|
|
12
|
+
* element removes exactly one instance.
|
|
13
|
+
*
|
|
14
|
+
* The narrow part is which counts get reported. Two conditions must both
|
|
15
|
+
* hold:
|
|
16
|
+
*
|
|
17
|
+
* 1. The count is plural and derived, not a switch. A numeric literal of two
|
|
18
|
+
* or more, or `length(var.x)` / `length(local.x)`. `count = 0`, `count =
|
|
19
|
+
* 1` and `count = var.enabled ? 1 : 0` are the conditional-resource idiom,
|
|
20
|
+
* which `for_each` does not replace, and they are never reported.
|
|
21
|
+
* 2. The resource uses `count.index` to build something that carries the
|
|
22
|
+
* instance's identity: a name, a bucket, a key, a `tags.Name`. That is what
|
|
23
|
+
* makes a shift destructive; a `count.index` used only to pick a subnet out
|
|
24
|
+
* of a list is reindexed silently and harmlessly.
|
|
25
|
+
*
|
|
26
|
+
* Redeploy's `terraform_prefer_for_each` reports every `count` over a
|
|
27
|
+
* collection, which is a broader rule and a noisier one, so the credit is
|
|
28
|
+
* `overlaps` rather than `equivalent`. choudoufu's `RuleCountIndex`
|
|
29
|
+
* (`internal/live/lint/count_index.go`) is the survey's deepest treatment of
|
|
30
|
+
* the identity-bearing question, and it is credited the same way. Report-only
|
|
31
|
+
* either way: a `count` with a stable list behind it is fine forever, and
|
|
32
|
+
* only the person who knows whether that list is stable can say.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import type {
|
|
36
|
+
PostSynthCheck,
|
|
37
|
+
PostSynthContext,
|
|
38
|
+
PostSynthDiagnostic,
|
|
39
|
+
} from "@intentius/chant/lint/post-synth";
|
|
40
|
+
import { isResourceDeclarable } from "@intentius/chant/declarable";
|
|
41
|
+
import { RESOURCE_TYPE, type BlockBody, type TerraformEntity } from "../../hcl/parse";
|
|
42
|
+
import { walkAttributes } from "./blocks";
|
|
43
|
+
|
|
44
|
+
/** `length(var.x)` or `length(local.x)`, as hcl2json renders the expression. */
|
|
45
|
+
const LENGTH_OF = /^\$\{?\s*length\(\s*(var|local)\.[A-Za-z_][A-Za-z0-9_-]*\s*\)\s*\}?$/;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Attribute names whose value IS the instance's identity to the provider: a
|
|
49
|
+
* rename destroys and recreates. Matched on the last path segment, so
|
|
50
|
+
* `tags.Name` and `labels.name` are covered without listing every container.
|
|
51
|
+
*/
|
|
52
|
+
const IDENTITY_ATTRIBUTES = new Set([
|
|
53
|
+
"name",
|
|
54
|
+
"bucket",
|
|
55
|
+
"key",
|
|
56
|
+
"identifier",
|
|
57
|
+
"hostname",
|
|
58
|
+
"domain",
|
|
59
|
+
"domain_name",
|
|
60
|
+
"display_name",
|
|
61
|
+
"resource_name",
|
|
62
|
+
"alias",
|
|
63
|
+
"prefix",
|
|
64
|
+
"path",
|
|
65
|
+
"topic",
|
|
66
|
+
"queue_name",
|
|
67
|
+
"function_name",
|
|
68
|
+
"db_name",
|
|
69
|
+
"repository",
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
/** The `count` value, when it is one this rule reports on. */
|
|
73
|
+
export function pluralCount(body: BlockBody): string | undefined {
|
|
74
|
+
const count = body.count;
|
|
75
|
+
if (typeof count === "number") return count >= 2 ? String(count) : undefined;
|
|
76
|
+
if (typeof count !== "string") return undefined;
|
|
77
|
+
if (LENGTH_OF.test(count.trim())) return count.replace(/^\$\{?/, "").replace(/\}$/, "");
|
|
78
|
+
const literal = /^\$\{?\s*(\d+)\s*\}?$/.exec(count.trim());
|
|
79
|
+
return literal && Number(literal[1]) >= 2 ? literal[1] : undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Identity-bearing attributes of this body whose value is built from `count.index`. */
|
|
83
|
+
export function identityUsesOfCountIndex(body: BlockBody): string[] {
|
|
84
|
+
const out: string[] = [];
|
|
85
|
+
for (const attr of walkAttributes(body)) {
|
|
86
|
+
if (typeof attr.value !== "string" || !attr.value.includes("count.index")) continue;
|
|
87
|
+
if (!IDENTITY_ATTRIBUTES.has(attr.name.toLowerCase())) continue;
|
|
88
|
+
out.push(attr.path);
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const tf021: PostSynthCheck = {
|
|
94
|
+
id: "TF021",
|
|
95
|
+
description: "count builds instance identities from count.index where for_each is safer",
|
|
96
|
+
|
|
97
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
98
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
99
|
+
|
|
100
|
+
for (const [key, entity] of ctx.entities) {
|
|
101
|
+
if (entity.entityType !== RESOURCE_TYPE || !isResourceDeclarable(entity)) continue;
|
|
102
|
+
const props = (entity as TerraformEntity).props as Partial<TerraformEntity["props"]>;
|
|
103
|
+
const body = (typeof props.body === "object" && props.body !== null ? props.body : {}) as BlockBody;
|
|
104
|
+
|
|
105
|
+
const count = pluralCount(body);
|
|
106
|
+
if (count === undefined) continue;
|
|
107
|
+
const uses = identityUsesOfCountIndex(body);
|
|
108
|
+
if (uses.length === 0) continue;
|
|
109
|
+
|
|
110
|
+
const address = typeof props.address === "string" ? props.address : key;
|
|
111
|
+
diagnostics.push({
|
|
112
|
+
checkId: "TF021",
|
|
113
|
+
severity: "warning",
|
|
114
|
+
message:
|
|
115
|
+
`Resource "${address}" has \`count = ${count}\` and builds ${uses.sort().join(", ")} from ` +
|
|
116
|
+
"`count.index`, so each instance's identity is its position in the list. Removing an element " +
|
|
117
|
+
"shifts every later instance down one index, and Terraform destroys and recreates all of them " +
|
|
118
|
+
"to match. Use `for_each` over a map or set so each instance is addressed by a stable key.",
|
|
119
|
+
entity: key,
|
|
120
|
+
lexicon: "terraform",
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return diagnostics;
|
|
125
|
+
},
|
|
126
|
+
};
|