@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
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF008: a `provider` block configures a credential inline.
|
|
3
|
+
*
|
|
4
|
+
* The most corroborated rule in #2107's survey: checkov (`CKV_AWS_41` and its
|
|
5
|
+
* `credentials.py` siblings), KICS, Snyk and semgrep all ship a version of it,
|
|
6
|
+
* and it is the only Terraform-language-shaped check any of Snyk or semgrep
|
|
7
|
+
* ships at all. Every one of them is written per provider. This one is written
|
|
8
|
+
* against attribute NAMES, so it covers the providers those tools cover and
|
|
9
|
+
* the several hundred they do not.
|
|
10
|
+
*
|
|
11
|
+
* A credential in a provider block is used by every resource that provider
|
|
12
|
+
* manages, so it is the highest-value one in the root, and it is read from the
|
|
13
|
+
* repository by everyone who can clone it. Providers all accept the same
|
|
14
|
+
* credential through the environment or a shared config file instead.
|
|
15
|
+
*
|
|
16
|
+
* Scope: root and child modules alike (#2112). The condition is a property
|
|
17
|
+
* of the block itself, so a descended module's block is read exactly as a
|
|
18
|
+
* root's is.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type {
|
|
22
|
+
PostSynthCheck,
|
|
23
|
+
PostSynthContext,
|
|
24
|
+
PostSynthDiagnostic,
|
|
25
|
+
} from "@intentius/chant/lint/post-synth";
|
|
26
|
+
import { PROVIDER_TYPE } from "../../hcl/parse";
|
|
27
|
+
import { isLiteralString, isPlaceholderValue, isSecretName } from "../secret-shape";
|
|
28
|
+
import { blockName, blocksOfType, walkAttributes } from "./blocks";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Credential attributes the name heuristic in `../secret-shape.ts` does not
|
|
32
|
+
* cover on its own. `client_secret` and the `*_key`/`*_token` names it does
|
|
33
|
+
* cover are not repeated here.
|
|
34
|
+
*/
|
|
35
|
+
const PROVIDER_CREDENTIAL_ATTRS = new Set(["credentials", "client_certificate", "client_key", "shared_credentials_file"]);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A path, not a credential: `credentials = "keys/service-account.json"` and
|
|
39
|
+
* `shared_credentials_file = "~/.aws/credentials"` name where the secret lives,
|
|
40
|
+
* which is what the fix looks like.
|
|
41
|
+
*/
|
|
42
|
+
const FILE_PATH_RE = /^[~./]?[\w./\\-]*\.(json|pem|key|p12|pfx|crt|cer|ini|conf|credentials)$/i;
|
|
43
|
+
|
|
44
|
+
export const tf008: PostSynthCheck = {
|
|
45
|
+
id: "TF008",
|
|
46
|
+
description: "Provider block configures a hardcoded credential",
|
|
47
|
+
|
|
48
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
49
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
50
|
+
|
|
51
|
+
for (const block of blocksOfType(ctx, PROVIDER_TYPE)) {
|
|
52
|
+
for (const attr of walkAttributes(block.body)) {
|
|
53
|
+
if (!PROVIDER_CREDENTIAL_ATTRS.has(attr.name) && !isSecretName(attr.name)) continue;
|
|
54
|
+
if (!isLiteralString(attr.value)) continue;
|
|
55
|
+
const value = attr.value.trim();
|
|
56
|
+
if (value === "" || isPlaceholderValue(value) || FILE_PATH_RE.test(value)) continue;
|
|
57
|
+
|
|
58
|
+
diagnostics.push({
|
|
59
|
+
checkId: "TF008",
|
|
60
|
+
severity: "error",
|
|
61
|
+
message:
|
|
62
|
+
`Provider "${blockName(block.address)}" sets \`${attr.path}\` to a literal value ` +
|
|
63
|
+
`(${value.length} characters, redacted here). A credential in a provider block is ` +
|
|
64
|
+
"committed to the repository and used by every resource the provider manages. Remove it " +
|
|
65
|
+
"and let the provider read the credential from the environment, a shared config file, or " +
|
|
66
|
+
"a sensitive variable.",
|
|
67
|
+
entity: `${block.key}.${attr.path}`,
|
|
68
|
+
lexicon: "terraform",
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return diagnostics;
|
|
74
|
+
},
|
|
75
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF009: a credential-named variable is not marked `sensitive`.
|
|
3
|
+
*
|
|
4
|
+
* No tool in #2107's survey checks this. The authority is HashiCorp's own style
|
|
5
|
+
* guide, whose variables section says plainly: "For sensitive variables, such
|
|
6
|
+
* as passwords and private keys, set the sensitive parameter to true."
|
|
7
|
+
*
|
|
8
|
+
* Without the flag the value is echoed in `terraform plan` and
|
|
9
|
+
* `terraform apply` output, which means it lands in CI logs, in the pull
|
|
10
|
+
* request comment a plan bot posts, and in every terminal scrollback. The flag
|
|
11
|
+
* does not keep the value out of state (nothing does), which is why the
|
|
12
|
+
* remediation says both things.
|
|
13
|
+
*
|
|
14
|
+
* Only the name is read. A variable with no default and no value in the
|
|
15
|
+
* repository is still flagged, because the finding is about where the value
|
|
16
|
+
* goes at run time, not about what is committed. That is TF007's question.
|
|
17
|
+
*
|
|
18
|
+
* Scope: root and child modules alike (#2112). The condition is a property
|
|
19
|
+
* of the block itself, so a descended module's block is read exactly as a
|
|
20
|
+
* root's is.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type {
|
|
24
|
+
PostSynthCheck,
|
|
25
|
+
PostSynthContext,
|
|
26
|
+
PostSynthDiagnostic,
|
|
27
|
+
} from "@intentius/chant/lint/post-synth";
|
|
28
|
+
import { VARIABLE_TYPE } from "../../hcl/parse";
|
|
29
|
+
import { isSecretName } from "../secret-shape";
|
|
30
|
+
import { blockName, blocksOfType } from "./blocks";
|
|
31
|
+
|
|
32
|
+
export const tf009: PostSynthCheck = {
|
|
33
|
+
id: "TF009",
|
|
34
|
+
description: "Credential-named variable is not marked sensitive",
|
|
35
|
+
|
|
36
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
37
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
38
|
+
|
|
39
|
+
for (const block of blocksOfType(ctx, VARIABLE_TYPE)) {
|
|
40
|
+
const name = blockName(block.address);
|
|
41
|
+
if (!isSecretName(name)) continue;
|
|
42
|
+
if (block.body.sensitive === true) continue;
|
|
43
|
+
|
|
44
|
+
diagnostics.push({
|
|
45
|
+
checkId: "TF009",
|
|
46
|
+
severity: "error",
|
|
47
|
+
message:
|
|
48
|
+
`Variable "${name}" is named like a credential but is not marked \`sensitive = true\`, so ` +
|
|
49
|
+
"Terraform prints its value in plan and apply output, and from there into CI logs and plan " +
|
|
50
|
+
"comments. Add `sensitive = true`, and remember the value is still stored in plaintext in " +
|
|
51
|
+
"state either way.",
|
|
52
|
+
entity: block.key,
|
|
53
|
+
lexicon: "terraform",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return diagnostics;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF010: a `variable` declares no `type`.
|
|
3
|
+
*
|
|
4
|
+
* Without a type constraint Terraform accepts whatever it is given and infers
|
|
5
|
+
* the type from the value, so a variable meant to be a list arrives as a
|
|
6
|
+
* string from a `TF_VAR_` environment variable, or a number arrives as `"3"`
|
|
7
|
+
* from a tfvars file, and the failure surfaces deep inside a resource
|
|
8
|
+
* argument rather than at the module boundary where it was introduced.
|
|
9
|
+
*
|
|
10
|
+
* A blank `type` counts as absent, matching KICS, which fails a `type` that
|
|
11
|
+
* trims to empty.
|
|
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 { VARIABLE_TYPE } from "../../hcl/parse";
|
|
24
|
+
import { blockName, blocksOfType, isBlank } from "./blocks";
|
|
25
|
+
|
|
26
|
+
export const tf010: PostSynthCheck = {
|
|
27
|
+
id: "TF010",
|
|
28
|
+
description: "Variable declares no type constraint",
|
|
29
|
+
|
|
30
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
31
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
32
|
+
|
|
33
|
+
for (const block of blocksOfType(ctx, VARIABLE_TYPE)) {
|
|
34
|
+
if (!isBlank(block.body.type)) continue;
|
|
35
|
+
|
|
36
|
+
diagnostics.push({
|
|
37
|
+
checkId: "TF010",
|
|
38
|
+
severity: "info",
|
|
39
|
+
message:
|
|
40
|
+
`Variable "${blockName(block.address)}" declares no \`type\`, so Terraform accepts any ` +
|
|
41
|
+
"value and infers the type from whatever it is given. Add a type constraint " +
|
|
42
|
+
"(`string`, `number`, `bool`, `list(string)`, an `object({...})`) so a wrong value is " +
|
|
43
|
+
"rejected at the module boundary.",
|
|
44
|
+
entity: block.key,
|
|
45
|
+
lexicon: "terraform",
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return diagnostics;
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF011: a `variable` declares no `description`.
|
|
3
|
+
*
|
|
4
|
+
* A module's variables are its API. The description is what `terraform-docs`
|
|
5
|
+
* renders into the README, what the HCP Terraform and Terraform Cloud variable
|
|
6
|
+
* UI shows next to the input box, and what a caller reads before deciding what
|
|
7
|
+
* to pass. Without it the only documentation is the variable's name.
|
|
8
|
+
*
|
|
9
|
+
* Report-only: this is documentation hygiene, filed the way KICS files it (as
|
|
10
|
+
* INFO) and the way tflint files it (in the `all` preset, not `recommended`).
|
|
11
|
+
*
|
|
12
|
+
* Scope: root and child modules alike (#2112). The condition is a property
|
|
13
|
+
* of the block itself, so a descended module's block is read exactly as a
|
|
14
|
+
* root's is.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type {
|
|
18
|
+
PostSynthCheck,
|
|
19
|
+
PostSynthContext,
|
|
20
|
+
PostSynthDiagnostic,
|
|
21
|
+
} from "@intentius/chant/lint/post-synth";
|
|
22
|
+
import { VARIABLE_TYPE } from "../../hcl/parse";
|
|
23
|
+
import { blockName, blocksOfType, isBlank } from "./blocks";
|
|
24
|
+
|
|
25
|
+
export const tf011: PostSynthCheck = {
|
|
26
|
+
id: "TF011",
|
|
27
|
+
description: "Variable declares no description",
|
|
28
|
+
|
|
29
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
30
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
31
|
+
|
|
32
|
+
for (const block of blocksOfType(ctx, VARIABLE_TYPE)) {
|
|
33
|
+
if (!isBlank(block.body.description)) continue;
|
|
34
|
+
|
|
35
|
+
diagnostics.push({
|
|
36
|
+
checkId: "TF011",
|
|
37
|
+
severity: "info",
|
|
38
|
+
message:
|
|
39
|
+
`Variable "${blockName(block.address)}" declares no \`description\`, so the module's ` +
|
|
40
|
+
"generated documentation and the variable prompt have nothing to show but the name. Add a " +
|
|
41
|
+
"description saying what the value is for and what a valid one looks like.",
|
|
42
|
+
entity: block.key,
|
|
43
|
+
lexicon: "terraform",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return diagnostics;
|
|
48
|
+
},
|
|
49
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF012: an `output` declares no `description`.
|
|
3
|
+
*
|
|
4
|
+
* The other half of a module's API. An output is what a caller wires into the
|
|
5
|
+
* next module, and the description is the only place to say what the value
|
|
6
|
+
* actually is (an id or an ARN, a full URL or a bare hostname) and whether it
|
|
7
|
+
* is stable enough to depend on.
|
|
8
|
+
*
|
|
9
|
+
* Report-only, for the same reason TF011 is.
|
|
10
|
+
*
|
|
11
|
+
* Scope: root and child modules alike (#2112). The condition is a property
|
|
12
|
+
* of the block itself, so a descended module's block is read exactly as a
|
|
13
|
+
* root's is.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type {
|
|
17
|
+
PostSynthCheck,
|
|
18
|
+
PostSynthContext,
|
|
19
|
+
PostSynthDiagnostic,
|
|
20
|
+
} from "@intentius/chant/lint/post-synth";
|
|
21
|
+
import { OUTPUT_TYPE } from "../../hcl/parse";
|
|
22
|
+
import { blockName, blocksOfType, isBlank } from "./blocks";
|
|
23
|
+
|
|
24
|
+
export const tf012: PostSynthCheck = {
|
|
25
|
+
id: "TF012",
|
|
26
|
+
description: "Output declares no description",
|
|
27
|
+
|
|
28
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
29
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
30
|
+
|
|
31
|
+
for (const block of blocksOfType(ctx, OUTPUT_TYPE)) {
|
|
32
|
+
if (!isBlank(block.body.description)) continue;
|
|
33
|
+
|
|
34
|
+
diagnostics.push({
|
|
35
|
+
checkId: "TF012",
|
|
36
|
+
severity: "info",
|
|
37
|
+
message:
|
|
38
|
+
`Output "${blockName(block.address)}" declares no \`description\`. Outputs are a module's ` +
|
|
39
|
+
"public interface, so say what the value is and what a caller can rely on it for.",
|
|
40
|
+
entity: block.key,
|
|
41
|
+
lexicon: "terraform",
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return diagnostics;
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF013: `lifecycle { ignore_changes = all }`.
|
|
3
|
+
*
|
|
4
|
+
* `all` tells Terraform to ignore drift on every attribute of the resource
|
|
5
|
+
* after it is created. Nothing about that resource is ever reconciled again:
|
|
6
|
+
* a manual console edit, a deleted security-group rule, a downgraded instance
|
|
7
|
+
* class, all of it plans clean forever. It is also silent, because a plan that
|
|
8
|
+
* finds nothing looks exactly like a plan that was told to look at nothing.
|
|
9
|
+
*
|
|
10
|
+
* That is the observe half of chant's lifecycle switched off, so this is
|
|
11
|
+
* merge-worthy rather than hygiene. A list of specific attributes is fine and
|
|
12
|
+
* is not reported: ignoring `tags` an external tool writes is a real need, and
|
|
13
|
+
* naming them is what makes the intent auditable.
|
|
14
|
+
*
|
|
15
|
+
* Scope: root and child modules alike (#2112). The condition is a property
|
|
16
|
+
* of the block itself, so a descended module's block is read exactly as a
|
|
17
|
+
* root's is.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type {
|
|
21
|
+
PostSynthCheck,
|
|
22
|
+
PostSynthContext,
|
|
23
|
+
PostSynthDiagnostic,
|
|
24
|
+
} from "@intentius/chant/lint/post-synth";
|
|
25
|
+
import { DATA_TYPE, RESOURCE_TYPE } from "../../hcl/parse";
|
|
26
|
+
import { blocksOfTypes, nestedBodies } from "./blocks";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* `ignore_changes = all` parses to the string `"${all}"`: hcl2json renders the
|
|
30
|
+
* bare keyword as an expression template. A list of attributes parses to an
|
|
31
|
+
* array, so an array is only a match if `all` is the whole of it.
|
|
32
|
+
*/
|
|
33
|
+
function ignoresEverything(value: unknown): boolean {
|
|
34
|
+
if (value === "${all}") return true;
|
|
35
|
+
return Array.isArray(value) && value.length === 1 && value[0] === "${all}";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const tf013: PostSynthCheck = {
|
|
39
|
+
id: "TF013",
|
|
40
|
+
description: "lifecycle ignore_changes is set to all",
|
|
41
|
+
|
|
42
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
43
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
44
|
+
|
|
45
|
+
for (const block of blocksOfTypes(ctx, [RESOURCE_TYPE, DATA_TYPE])) {
|
|
46
|
+
for (const lifecycle of nestedBodies(block.body, "lifecycle")) {
|
|
47
|
+
if (!ignoresEverything(lifecycle.ignore_changes)) continue;
|
|
48
|
+
diagnostics.push({
|
|
49
|
+
checkId: "TF013",
|
|
50
|
+
severity: "warning",
|
|
51
|
+
message:
|
|
52
|
+
`"${block.address}" sets \`ignore_changes = all\`, so Terraform stops reconciling every ` +
|
|
53
|
+
"attribute of the resource after it is created. Drift becomes invisible: a manual change " +
|
|
54
|
+
"in the console plans clean forever. List the specific attributes to ignore instead.",
|
|
55
|
+
entity: block.key,
|
|
56
|
+
lexicon: "terraform",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return diagnostics;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
@@ -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,63 @@
|
|
|
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 backend, because a root without one keeps its state on
|
|
6
|
+
* local disk. A child module must declare neither: state belongs to the root,
|
|
7
|
+
* one state per root however many modules it calls, and Terraform says so
|
|
8
|
+
* directly, "A backend block can only appear in the root module" and the
|
|
9
|
+
* same for `cloud`. Depending on the version, the block is ignored with a
|
|
10
|
+
* warning or the root refuses to initialize, and either way the block is a
|
|
11
|
+
* claim about where state lives that the module does not get to make.
|
|
12
|
+
*
|
|
13
|
+
* The usual cause is a directory that used to be a root module and became a
|
|
14
|
+
* child when someone factored it out, with its `terraform` block carried
|
|
15
|
+
* along. The rest of that block is fine to keep: `required_version` and
|
|
16
|
+
* `required_providers` are meaningful in a child module and are not reported
|
|
17
|
+
* here.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type {
|
|
21
|
+
PostSynthCheck,
|
|
22
|
+
PostSynthContext,
|
|
23
|
+
PostSynthDiagnostic,
|
|
24
|
+
} from "@intentius/chant/lint/post-synth";
|
|
25
|
+
import { isResourceDeclarable } from "@intentius/chant/declarable";
|
|
26
|
+
import { TERRAFORM_TYPE, type BlockBody, type TerraformEntity } from "../../hcl/parse";
|
|
27
|
+
import { hasBlock } from "./tf001";
|
|
28
|
+
import { isChildScoped } from "./scope";
|
|
29
|
+
|
|
30
|
+
export const tf015: PostSynthCheck = {
|
|
31
|
+
id: "TF015",
|
|
32
|
+
description: "Child module declares a backend or cloud block",
|
|
33
|
+
|
|
34
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
35
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
36
|
+
|
|
37
|
+
for (const [key, entity] of ctx.entities) {
|
|
38
|
+
if (entity.entityType !== TERRAFORM_TYPE || !isResourceDeclarable(entity)) continue;
|
|
39
|
+
if (!isChildScoped(entity)) continue;
|
|
40
|
+
|
|
41
|
+
const props = (entity as TerraformEntity).props as Partial<TerraformEntity["props"]>;
|
|
42
|
+
const body = (typeof props.body === "object" && props.body !== null ? props.body : {}) as BlockBody;
|
|
43
|
+
const backend = hasBlock(body, "backend");
|
|
44
|
+
const cloud = hasBlock(body, "cloud");
|
|
45
|
+
if (!backend && !cloud) continue;
|
|
46
|
+
|
|
47
|
+
const blockKind = backend ? "backend" : "cloud";
|
|
48
|
+
diagnostics.push({
|
|
49
|
+
checkId: "TF015",
|
|
50
|
+
severity: "warning",
|
|
51
|
+
message:
|
|
52
|
+
`A child module's terraform block declares a ${blockKind} block. Only a root module may: ` +
|
|
53
|
+
"state belongs to the root, one state file for every module it calls, so this block is either " +
|
|
54
|
+
`ignored with a warning or refused at init. Delete the ${blockKind} block and leave the state ` +
|
|
55
|
+
"configuration to the root module that calls this one.",
|
|
56
|
+
entity: key,
|
|
57
|
+
lexicon: "terraform",
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return diagnostics;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
@@ -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
|
+
};
|