@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
|
@@ -15,7 +15,390 @@
|
|
|
15
15
|
* `terraform_workspace_remote` (a `terraform.workspace` compatibility check
|
|
16
16
|
* against remote execution, a different condition than "no backend is
|
|
17
17
|
* configured at all"). Ship with no lineage rather than invent a credit.
|
|
18
|
+
*
|
|
19
|
+
* TF101 (plan-before-apply, `lint/rules/plan-before-apply.ts`) is a source-level
|
|
20
|
+
* lint rule over chant's own Op builders, not a post-synth check, so it carries
|
|
21
|
+
* no `RuleMeta`/`Lineage` entry here at all: this file's `Record` only ever
|
|
22
|
+
* keys post-synth check ids. Its own page section (docs/pages/lint-rules.mdx)
|
|
23
|
+
* says the same thing directly: no tool anywhere models "this plan file traces
|
|
24
|
+
* back to a specific preceding plan step", because that is a question about
|
|
25
|
+
* chant's own DSL, not about a Terraform root.
|
|
26
|
+
*
|
|
27
|
+
* ## The survey registry
|
|
28
|
+
*
|
|
29
|
+
* #2107 ran three research passes over every serious Terraform lint, scan and
|
|
30
|
+
* policy tool (1,888 lines, 222 cited URLs, attached to that issue as
|
|
31
|
+
* comments). The tools and documents below are what the survey found worth
|
|
32
|
+
* crediting from across that research. #2108 added none of them to
|
|
33
|
+
* `PRIOR_ART`: `packages/core/src/audit/catalog.test.ts`'s "every registered
|
|
34
|
+
* tool is credited" test fails on an entry no shipped rule cites yet, and
|
|
35
|
+
* neither TF001 nor TF101 cites anything. #2109 (TF002-TF005) is the first to
|
|
36
|
+
* draw from this list: `tflint-ruleset-terraform`, `terraform-sentinel-policies`,
|
|
37
|
+
* `hashicorp-style-guide` and `aws-terraform-prescriptive-guidance` are now in
|
|
38
|
+
* `PRIOR_ART`, spelled exactly as below. `checkov` and `kics` were already
|
|
39
|
+
* registered (other lexicons cite them); TF004 and TF005 are the first
|
|
40
|
+
* Terraform rules to. Each later rule issue adds the remaining entries it
|
|
41
|
+
* needs the first time a shipped rule cites them: #2112 (TF014, TF015, TF020,
|
|
42
|
+
* TF021) added `gcp-terraform-best-practices`, the last row of the table below
|
|
43
|
+
* that no shipped rule had cited yet.
|
|
44
|
+
*
|
|
45
|
+
* Each row's Sweep note says whether `scripts/prior-art-sweep.ts` can index the
|
|
46
|
+
* source and, when it cannot, why (#2219). Five of these are indexed as of
|
|
47
|
+
* 2026-09-07: the three tflint rulesets, tfsec and the Sentinel policy set,
|
|
48
|
+
* 311 upstream rule ids in `scripts/prior-art/snapshot.json`. The rest are
|
|
49
|
+
* prose or code, so the tools they credit are re-read by hand when their
|
|
50
|
+
* documents change.
|
|
51
|
+
*
|
|
52
|
+
* | Key | Name | URL | Licence | Kind | Note |
|
|
53
|
+
* |---|---|---|---|---|---|
|
|
54
|
+
* | `tflint` | tflint | https://github.com/terraform-linters/tflint | MPL-2.0 (its `terraform` package is BUSL-1.1; binaries are bound by both) | scanner | The plugin host; ships no language rules of its own. Sweep: not registered in `PRIOR_ART` (no rule cites it), so it is not swept. |
|
|
55
|
+
* | `tflint-ruleset-terraform` | tflint-ruleset-terraform | https://github.com/terraform-linters/tflint-ruleset-terraform | MPL-2.0 | scanner | The official language-level ruleset, bundled into tflint. Sweep: indexed from `docs/rules/README.md`, whose table links each rule's page (20 rules). |
|
|
56
|
+
* | `tflint-ruleset-redeploy` | tflint-ruleset-redeploy | https://github.com/RedeployAB/tflint-ruleset-redeploy | 0BSD | scanner | Community ruleset; richest source of language-level rules outside the official set. Sweep: indexed from `docs/rules/README.md`, same table shape (33 rules). |
|
|
57
|
+
* | `tflint-ruleset-avm` | tflint-ruleset-avm | https://github.com/Azure/tflint-ruleset-avm | MIT | scanner | Azure Verified Modules conformance; two of its 33 rules are provider-agnostic. Sweep: indexed from the repository-root `RULES.md`, since `docs/` holds pages for only two rules (33 rules). |
|
|
58
|
+
* | `tfsec` | tfsec | https://github.com/aquasecurity/tfsec | MIT | scanner | Historical: not archived but effectively frozen (no release since 2025-05); its docs site 404s above v0.61.x, so cite pinned repository paths (`github.com/aquasecurity/tfsec/blob/master/docs/checks/...`), not the docs site. Sweep: indexed from the root `rules.md` on `master`, a flat table of all 152 check ids, because `docs/checks` nests a directory per provider, service and check. |
|
|
59
|
+
* | `trivy-checks` | trivy-checks | https://github.com/aquasecurity/trivy-checks | MIT (not the trivy scanner's own Apache-2.0; cite the checks repo's licence, not the engine's) | scanner | Where tfsec's rules live today (tfsec -> defsec -> trivy + trivy-checks). Cite rules by `long_id` (stable, human-readable); `AVD-*` ids are aliases now. Sweep: not registered in `PRIOR_ART` (no rule cites it yet) and its `long_id`s live in Rego metadata under `checks/<kind>/<provider>/<service>/`, with no flat index. |
|
|
60
|
+
* | `terraform-sentinel-policies` | HashiCorp reference Sentinel policies | https://github.com/hashicorp/terraform-sentinel-policies | MPL-2.0 | specification | HashiCorp's own example policy set; ships zero rules baked into HCP Terraform itself, so treat a cited policy as a documented pattern (`specification`) unless a future entry runs it as a real check (`scanner`); the `kind` union allows either. Sweep: indexed by listing the five per-cloud directories, whose `.sentinel` filenames are the policy ids credits cite (73 policies); the repository publishes no index page. |
|
|
61
|
+
* | `choudoufu` | choudoufu | https://github.com/INTENTIUS/choudoufu | MPL-2.0 | scanner | Its `internal/live/lint` package; experimental, AWS-only. Sweep: unsweepable, its rule ids are Go constants in `internal/live/lint/issue.go` and it publishes no rule index. |
|
|
62
|
+
* | `hashicorp-style-guide` | HashiCorp Terraform style guide | https://developer.hashicorp.com/terraform/language/style | n/a | specification | Sweep: unsweepable, one prose page of recommendations with no rule ids; a credit quotes the sentence and links its fragment. |
|
|
63
|
+
* | `gcp-terraform-best-practices` | Google Cloud Terraform best-practices series | https://docs.cloud.google.com/docs/terraform/best-practices/general-style-structure | n/a | specification | A multi-page series; cite the specific page's fragment. Sweep: unsweepable, prose across several pages with no rule ids. |
|
|
64
|
+
* | `aws-terraform-prescriptive-guidance` | AWS prescriptive guidance for Terraform | https://docs.aws.amazon.com/prescriptive-guidance/latest/terraform-aws-provider-best-practices/ | n/a | specification | Sweep: unsweepable, a prose guide whose sections carry headings rather than rule ids. |
|
|
65
|
+
*
|
|
66
|
+
* Deliberately excluded, per the survey: Terrascan and regula (both archived,
|
|
67
|
+
* do not build on dead projects); Snyk IaC (a product, not a rule source);
|
|
68
|
+
* semgrep (no SPDX id: its engine is LGPL-2.1 but its rules ship under the
|
|
69
|
+
* proprietary Semgrep Rules License v1.0, which forbids redistribution; cite
|
|
70
|
+
* it by name in prose if a rule's idea comes from it, never register it in
|
|
71
|
+
* `PRIOR_ART` with an SPDX licence). TF008 credits semgrep by name under that
|
|
72
|
+
* rule, so `PRIOR_ART` does carry the entry, with the licence spelled out
|
|
73
|
+
* rather than as an SPDX id. Sweep: unsweepable, the registry publishes no
|
|
74
|
+
* index and a rule id is the path of its YAML file under
|
|
75
|
+
* `terraform/<provider>/<category>/`, which no single directory listing covers.
|
|
76
|
+
*
|
|
77
|
+
* TF024 (#2103, live root declares a backend/cloud block) credits choudoufu's
|
|
78
|
+
* own `RuleStateBackend`, `internal/live/lint/issue.go` of
|
|
79
|
+
* github.com/INTENTIUS/choudoufu: the identical condition, checked the same
|
|
80
|
+
* way this fork checks it at `init` before any command runs. TF025 (a live
|
|
81
|
+
* root's non-default `terraform.workspace`) has no entry: its workspace half
|
|
82
|
+
* is refused at the CLI argument-parsing layer there
|
|
83
|
+
* (`internal/command/live_mode.go`), not by a dedicated lint rule under
|
|
84
|
+
* `internal/live/lint`, so there is no `Rule*` constant to point at.
|
|
85
|
+
*
|
|
86
|
+
* TF026 (#2106, a live root's `delete: "never"` against its `policy` block's
|
|
87
|
+
* `undeclared_tagged` verb) has no entry either, the same reason TF001 has
|
|
88
|
+
* none: choudoufu's own `internal/live/lint` package checks a policy block
|
|
89
|
+
* for internal validity (`RulePolicyVerb`: an unrecognized or wrong-quadrant
|
|
90
|
+
* verb; `RulePolicyScope`: `undeclared_untagged = "delete"` with no `scope`
|
|
91
|
+
* block), never against a `delete: "never" | "owned-only" | "gated"`
|
|
92
|
+
* classification, because choudoufu has no such classification — that
|
|
93
|
+
* vocabulary is chant's own, shared across lexicons (`lexicons/k8s/src/op/
|
|
94
|
+
* activities/kubectl.ts`'s `ApplyDeleteMode`, `packages/core/src/op/
|
|
95
|
+
* activities/apply.ts`'s `DeleteMode`), applied here to a tool that does not
|
|
96
|
+
* itself carry the concept. Ship with no lineage rather than invent a credit.
|
|
97
|
+
*
|
|
98
|
+
* TF027 (#2216, a live root's `policy` block setting `undeclared_untagged =
|
|
99
|
+
* "delete"`) does have one: choudoufu's `RulePolicyScope`
|
|
100
|
+
* (`internal/live/lint/policy.go`) refuses the same assignment, and issue #67
|
|
101
|
+
* there makes it a lint refusal rather than a default. The relation is
|
|
102
|
+
* `extends`, because choudoufu refuses it only when no `scope` block narrows
|
|
103
|
+
* the purge, while chant refuses it either way: a `scope` block bounds an
|
|
104
|
+
* account-wide sweep to a service, type or region, and every resource inside
|
|
105
|
+
* that boundary is still one this estate never marked. "Bounded" is not
|
|
106
|
+
* "owned", and owning it is the condition chant applies.
|
|
107
|
+
*
|
|
108
|
+
* TF028 (#2216, a live root watched by a `TerraformWatchOp` built without
|
|
109
|
+
* `live: true`) has no entry. It reads a chant Op's own configuration against
|
|
110
|
+
* a root's mode, and no surveyed tool has an Op model to read: choudoufu lints
|
|
111
|
+
* HCL, and every tflint ruleset, tfsec/trivy-checks and the Sentinel policies
|
|
112
|
+
* do the same. There is nothing upstream to credit.
|
|
18
113
|
*/
|
|
19
114
|
import type { Lineage } from "@intentius/chant/audit/catalog";
|
|
20
115
|
|
|
21
|
-
|
|
116
|
+
const TFSEC_V061 = "https://github.com/aquasecurity/tfsec/blob/v0.61.3/docs/checks/general/secrets";
|
|
117
|
+
/** tflint's official language ruleset. */
|
|
118
|
+
const TFLINT_RULES = "https://github.com/terraform-linters/tflint-ruleset-terraform/blob/main/docs/rules";
|
|
119
|
+
/** The community ruleset with the richest set of language rules outside the official one. */
|
|
120
|
+
const REDEPLOY_RULES = "https://github.com/RedeployAB/tflint-ruleset-redeploy/blob/main/docs/rules";
|
|
121
|
+
|
|
122
|
+
export const terraformAuditLineage: Record<string, Lineage[]> = {
|
|
123
|
+
TF027: [
|
|
124
|
+
{
|
|
125
|
+
tool: "choudoufu",
|
|
126
|
+
rule: "RulePolicyScope",
|
|
127
|
+
url: "https://github.com/INTENTIUS/choudoufu/blob/main/internal/live/lint/policy.go",
|
|
128
|
+
relation: "extends",
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
TF024: [
|
|
132
|
+
{
|
|
133
|
+
tool: "choudoufu",
|
|
134
|
+
rule: "RuleStateBackend",
|
|
135
|
+
url: "https://github.com/INTENTIUS/choudoufu/blob/main/internal/live/lint/issue.go",
|
|
136
|
+
relation: "equivalent",
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
TF002: [
|
|
140
|
+
{
|
|
141
|
+
tool: "tflint-ruleset-terraform",
|
|
142
|
+
rule: "terraform_required_providers",
|
|
143
|
+
url: "https://github.com/terraform-linters/tflint-ruleset-terraform/blob/main/docs/rules/terraform_required_providers.md",
|
|
144
|
+
relation: "equivalent",
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
tool: "terraform-sentinel-policies",
|
|
148
|
+
rule: "require-all-providers-have-version-constraint",
|
|
149
|
+
url: "https://github.com/hashicorp/terraform-sentinel-policies/tree/main/cloud-agnostic",
|
|
150
|
+
relation: "equivalent",
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
tool: "aws-terraform-prescriptive-guidance",
|
|
154
|
+
rule: "Add automated version checks",
|
|
155
|
+
url: "https://docs.aws.amazon.com/prescriptive-guidance/latest/terraform-aws-provider-best-practices/version.html#add-automated-version-checks",
|
|
156
|
+
relation: "equivalent",
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
// Sentinel's restrict-terraform-versions checks that required_version is
|
|
160
|
+
// within an allowed range, not that it is present at all: a narrower
|
|
161
|
+
// question than TF003's, so "overlaps" rather than "equivalent" (#2109).
|
|
162
|
+
TF003: [
|
|
163
|
+
{
|
|
164
|
+
tool: "tflint-ruleset-terraform",
|
|
165
|
+
rule: "terraform_required_version",
|
|
166
|
+
url: "https://github.com/terraform-linters/tflint-ruleset-terraform/blob/main/docs/rules/terraform_required_version.md",
|
|
167
|
+
relation: "equivalent",
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
tool: "terraform-sentinel-policies",
|
|
171
|
+
rule: "restrict-terraform-versions",
|
|
172
|
+
url: "https://github.com/hashicorp/terraform-sentinel-policies/tree/main/cloud-agnostic",
|
|
173
|
+
relation: "overlaps",
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
tool: "hashicorp-style-guide",
|
|
177
|
+
rule: "Version pinning",
|
|
178
|
+
url: "https://developer.hashicorp.com/terraform/language/style#version-pinning",
|
|
179
|
+
relation: "overlaps",
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
TF004: [
|
|
183
|
+
{
|
|
184
|
+
tool: "tflint-ruleset-terraform",
|
|
185
|
+
rule: "terraform_module_version",
|
|
186
|
+
url: "https://github.com/terraform-linters/tflint-ruleset-terraform/blob/main/docs/rules/terraform_module_version.md",
|
|
187
|
+
relation: "equivalent",
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
tool: "checkov",
|
|
191
|
+
rule: "CKV_TF_2",
|
|
192
|
+
url: "https://github.com/bridgecrewio/checkov/blob/main/checkov/terraform/checks/module/generic/RevisionVersionTag.py",
|
|
193
|
+
relation: "equivalent",
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
// checkov's CKV_TF_1 accepts any `?ref=`/`&ref=` matching `[?&](ref=).*(\d\.\d).*`,
|
|
197
|
+
// which a branch literally named `v1.2-dev` passes; TF005 requires a full
|
|
198
|
+
// semver tag or a 40-hex SHA, so "extends" (a strict superset), not
|
|
199
|
+
// "equivalent". KICS's query only matches the `git::` prefix, not the
|
|
200
|
+
// github.com/bitbucket.org shorthands or scp-style sources TF005 also
|
|
201
|
+
// covers, so "overlaps" (#2109).
|
|
202
|
+
TF005: [
|
|
203
|
+
{
|
|
204
|
+
tool: "tflint-ruleset-terraform",
|
|
205
|
+
rule: "terraform_module_pinned_source",
|
|
206
|
+
url: "https://github.com/terraform-linters/tflint-ruleset-terraform/blob/main/docs/rules/terraform_module_pinned_source.md",
|
|
207
|
+
relation: "equivalent",
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
tool: "checkov",
|
|
211
|
+
rule: "CKV_TF_1",
|
|
212
|
+
url: "https://github.com/bridgecrewio/checkov/blob/main/checkov/terraform/checks/module/generic/RevisionHash.py",
|
|
213
|
+
relation: "extends",
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
tool: "kics",
|
|
217
|
+
rule: "3a81fc06-566f-492a-91dd-7448e409e2cd",
|
|
218
|
+
url: "https://docs.kics.io/latest/queries/terraform-queries/3a81fc06-566f-492a-91dd-7448e409e2cd/",
|
|
219
|
+
relation: "overlaps",
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
TF006: [
|
|
223
|
+
{
|
|
224
|
+
tool: "tflint-ruleset-avm",
|
|
225
|
+
rule: "avm_terraform_sensitive_variable_default_disallowed",
|
|
226
|
+
url: "https://github.com/Azure/tflint-ruleset-avm/blob/main/docs/basic/avm_terraform_sensitive_variable_default_disallowed.md",
|
|
227
|
+
relation: "equivalent",
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
TF007: [
|
|
231
|
+
{ tool: "tfsec", rule: "general-secrets-sensitive-in-variable", url: `${TFSEC_V061}/sensitive-in-variable.md`, relation: "equivalent" },
|
|
232
|
+
{ tool: "tfsec", rule: "general-secrets-sensitive-in-local", url: `${TFSEC_V061}/sensitive-in-local.md`, relation: "equivalent" },
|
|
233
|
+
],
|
|
234
|
+
// #2107's rule table credits TF008 with Snyk `SNYK-CC-TF-74` ("Credentials
|
|
235
|
+
// are configured via provider attributes") alongside these three. That
|
|
236
|
+
// credit is deliberately absent here, and the absence is the epic's own
|
|
237
|
+
// decision rather than an oversight: Snyk IaC is a product, not a rule
|
|
238
|
+
// source, which is why it sits in this file's "deliberately excluded" list
|
|
239
|
+
// above and has no `PRIOR_ART` entry to cite. The rules have no open
|
|
240
|
+
// repository behind them, and the public index (security.snyk.io/rules/
|
|
241
|
+
// cloud) does not even enumerate the legacy `SNYK-CC-TF-*` ids, so a
|
|
242
|
+
// credit here could not be swept the way every other one in this file is
|
|
243
|
+
// (scripts/prior-art-sweep.ts). The condition loses nothing by it:
|
|
244
|
+
// `SNYK-CC-TF-74`, checkov's `CKV_AWS_41` and semgrep's
|
|
245
|
+
// `aws-provider-static-credentials` are the same check three times over,
|
|
246
|
+
// and the other two are credited below (#2220).
|
|
247
|
+
TF008: [
|
|
248
|
+
{ tool: "checkov", rule: "CKV_AWS_41", url: "https://www.checkov.io/5.Policy%20Index/terraform.html", relation: "overlaps" },
|
|
249
|
+
{ tool: "kics", rule: "d7b9d850-3e06-4a75-852f-c46c2e92240b", url: "https://docs.kics.io/latest/queries/terraform-queries/aws/d7b9d850-3e06-4a75-852f-c46c2e92240b/", relation: "overlaps" },
|
|
250
|
+
{
|
|
251
|
+
tool: "semgrep",
|
|
252
|
+
rule: "terraform.aws.security.aws-provider-static-credentials",
|
|
253
|
+
url: "https://github.com/semgrep/semgrep-rules/blob/develop/terraform/aws/security/aws-provider-static-credentials.yaml",
|
|
254
|
+
relation: "overlaps",
|
|
255
|
+
},
|
|
256
|
+
],
|
|
257
|
+
TF009: [
|
|
258
|
+
{
|
|
259
|
+
tool: "hashicorp-style-guide",
|
|
260
|
+
rule: "Variables: for sensitive variables, such as passwords and private keys, set the sensitive parameter to true",
|
|
261
|
+
url: "https://developer.hashicorp.com/terraform/language/style#variables",
|
|
262
|
+
relation: "equivalent",
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
TF010: [
|
|
266
|
+
{ tool: "tflint-ruleset-terraform", rule: "terraform_typed_variables", url: `${TFLINT_RULES}/terraform_typed_variables.md`, relation: "equivalent" },
|
|
267
|
+
{ tool: "kics", rule: "fc5109bf-01fd-49fb-8bde-4492b543c34a", url: "https://docs.kics.io/latest/queries/terraform-queries/fc5109bf-01fd-49fb-8bde-4492b543c34a/", relation: "equivalent" },
|
|
268
|
+
],
|
|
269
|
+
TF011: [
|
|
270
|
+
{ tool: "tflint-ruleset-terraform", rule: "terraform_documented_variables", url: `${TFLINT_RULES}/terraform_documented_variables.md`, relation: "equivalent" },
|
|
271
|
+
{ tool: "kics", rule: "2a153952-2544-4687-bcc9-cc8fea814a9b", url: "https://docs.kics.io/latest/queries/terraform-queries/2a153952-2544-4687-bcc9-cc8fea814a9b/", relation: "equivalent" },
|
|
272
|
+
{
|
|
273
|
+
tool: "terraform-sentinel-policies",
|
|
274
|
+
rule: "validate-variables-have-descriptions",
|
|
275
|
+
url: "https://github.com/hashicorp/terraform-sentinel-policies/blob/main/cloud-agnostic/validate-variables-have-descriptions.sentinel",
|
|
276
|
+
relation: "equivalent",
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
TF012: [
|
|
280
|
+
{ tool: "tflint-ruleset-terraform", rule: "terraform_documented_outputs", url: `${TFLINT_RULES}/terraform_documented_outputs.md`, relation: "equivalent" },
|
|
281
|
+
{ tool: "kics", rule: "59312e8a-a64e-41e7-a252-618533dd1ea8", url: "https://docs.kics.io/latest/queries/terraform-queries/59312e8a-a64e-41e7-a252-618533dd1ea8/", relation: "equivalent" },
|
|
282
|
+
],
|
|
283
|
+
TF013: [
|
|
284
|
+
{ tool: "tflint-ruleset-redeploy", rule: "terraform_ignore_changes_all", url: `${REDEPLOY_RULES}/terraform_ignore_changes_all.md`, relation: "equivalent" },
|
|
285
|
+
],
|
|
286
|
+
// The two vendor documents below (and the one on TF015, and the HashiCorp
|
|
287
|
+
// style guide on TF021) are `Lineage` entries, not `RuleMeta.authority`
|
|
288
|
+
// ones, though #2107's table calls them "authority". `authority` is not a
|
|
289
|
+
// free-standing "this document says so" field: an authority citation forces
|
|
290
|
+
// `category: "security"` (`packages/core/src/audit/catalog.ts`), and
|
|
291
|
+
// `catalog.test.ts` binds the two together in both directions, "an
|
|
292
|
+
// authority citation always means security" and "authority citations only
|
|
293
|
+
// attach to merge-worthy entries". A provider block in a child module is a
|
|
294
|
+
// structural rule, not a security finding, so citing these guides as
|
|
295
|
+
// authority would have relabelled three rules as security to get a citation
|
|
296
|
+
// rendered. Lineage carries the same URLs and never touches tier or
|
|
297
|
+
// category, so that is where they live (#2112, recorded here by #2220).
|
|
298
|
+
TF014: [
|
|
299
|
+
{
|
|
300
|
+
tool: "tflint-ruleset-avm",
|
|
301
|
+
rule: "avm_terraform_provider_block_disallowed",
|
|
302
|
+
url: "https://github.com/Azure/tflint-ruleset-avm/blob/main/docs/basic/avm_terraform_provider_block_disallowed.md",
|
|
303
|
+
relation: "equivalent",
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
tool: "gcp-terraform-best-practices",
|
|
307
|
+
rule: "Build reusable modules: modules must not configure providers",
|
|
308
|
+
url: "https://docs.cloud.google.com/docs/terraform/best-practices/general-style-structure#reusable-modules",
|
|
309
|
+
relation: "equivalent",
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
tool: "aws-terraform-prescriptive-guidance",
|
|
313
|
+
rule: "Structure: declare provider configurations in the root module",
|
|
314
|
+
url: "https://docs.aws.amazon.com/prescriptive-guidance/latest/terraform-aws-provider-best-practices/structure.html",
|
|
315
|
+
relation: "equivalent",
|
|
316
|
+
},
|
|
317
|
+
],
|
|
318
|
+
// TF015 has no scanner to credit: no ruleset in the survey checks for a
|
|
319
|
+
// backend or cloud block inside a child module.
|
|
320
|
+
// tflint-ruleset-terraform's index has nothing about backends at all,
|
|
321
|
+
// Redeploy's has nothing either, and AVM's module rules stop at the
|
|
322
|
+
// provider block TF014 credits. What states the rule is Google Cloud's
|
|
323
|
+
// reusable-modules guide, so that document is the single credit here, and
|
|
324
|
+
// the page section says plainly that the credit is a document rather than
|
|
325
|
+
// a tool (#2112). It is lineage rather than `authority` for the reason on
|
|
326
|
+
// TF014 above: an authority citation would force `category: "security"` on
|
|
327
|
+
// a rule about where state configuration belongs.
|
|
328
|
+
TF015: [
|
|
329
|
+
{
|
|
330
|
+
tool: "gcp-terraform-best-practices",
|
|
331
|
+
rule: "Build reusable modules: state configuration belongs to the root module",
|
|
332
|
+
url: "https://docs.cloud.google.com/docs/terraform/best-practices/general-style-structure#reusable-modules",
|
|
333
|
+
relation: "equivalent",
|
|
334
|
+
},
|
|
335
|
+
],
|
|
336
|
+
TF016: [
|
|
337
|
+
// `overlaps`, not `equivalent`: tflint reports the same `x = "${y}"` form
|
|
338
|
+
// and also the object-key case (`"${var.k}" = v`), which this rule leaves
|
|
339
|
+
// alone. See tf016.ts.
|
|
340
|
+
{ tool: "tflint-ruleset-terraform", rule: "terraform_deprecated_interpolation", url: `${TFLINT_RULES}/terraform_deprecated_interpolation.md`, relation: "overlaps" },
|
|
341
|
+
],
|
|
342
|
+
TF017: [
|
|
343
|
+
{ tool: "tflint-ruleset-redeploy", rule: "terraform_module_depends_on", url: `${REDEPLOY_RULES}/terraform_module_depends_on.md`, relation: "equivalent" },
|
|
344
|
+
],
|
|
345
|
+
TF018: [
|
|
346
|
+
{ tool: "tflint-ruleset-redeploy", rule: "terraform_output_resource", url: `${REDEPLOY_RULES}/terraform_output_resource.md`, relation: "equivalent" },
|
|
347
|
+
],
|
|
348
|
+
TF019: [
|
|
349
|
+
{ tool: "tflint-ruleset-redeploy", rule: "terraform_redundant_default", url: `${REDEPLOY_RULES}/terraform_redundant_default.md`, relation: "equivalent" },
|
|
350
|
+
],
|
|
351
|
+
TF020: [
|
|
352
|
+
// `overlaps`: tflint evaluates expressions and reports the same four
|
|
353
|
+
// kinds of declaration; chant's index is a string scan over hcl2json's
|
|
354
|
+
// preserved expression strings, so it is generous where tflint is exact
|
|
355
|
+
// (see hcl/references.ts and tf020.ts).
|
|
356
|
+
{
|
|
357
|
+
tool: "tflint-ruleset-terraform",
|
|
358
|
+
rule: "terraform_unused_declarations",
|
|
359
|
+
url: `${TFLINT_RULES}/terraform_unused_declarations.md`,
|
|
360
|
+
relation: "overlaps",
|
|
361
|
+
},
|
|
362
|
+
],
|
|
363
|
+
TF021: [
|
|
364
|
+
// Redeploy reports every `count` over a collection; TF021 reports only
|
|
365
|
+
// the ones that build an identity out of `count.index`, which is a
|
|
366
|
+
// strict subset, hence `overlaps` on both credits rather than
|
|
367
|
+
// `equivalent`. choudoufu's own rule is the survey's deepest reading of
|
|
368
|
+
// the identity-bearing question (#2112). The HashiCorp style guide entry
|
|
369
|
+
// is lineage rather than `authority` for the reason recorded on TF014: an
|
|
370
|
+
// authority citation forces `category: "security"`, and `count` versus
|
|
371
|
+
// `for_each` is not a security finding.
|
|
372
|
+
{
|
|
373
|
+
tool: "tflint-ruleset-redeploy",
|
|
374
|
+
rule: "terraform_prefer_for_each",
|
|
375
|
+
url: `${REDEPLOY_RULES}/terraform_prefer_for_each.md`,
|
|
376
|
+
relation: "overlaps",
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
tool: "choudoufu",
|
|
380
|
+
rule: "RuleCountIndex",
|
|
381
|
+
url: "https://github.com/INTENTIUS/choudoufu/blob/main/internal/live/lint/count_index.go",
|
|
382
|
+
relation: "overlaps",
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
tool: "hashicorp-style-guide",
|
|
386
|
+
rule: "Resources: use for_each for a dynamic resource count",
|
|
387
|
+
url: "https://developer.hashicorp.com/terraform/language/style#dynamic-resource-count",
|
|
388
|
+
relation: "overlaps",
|
|
389
|
+
},
|
|
390
|
+
],
|
|
391
|
+
TF022: [
|
|
392
|
+
// tfsec consolidated its three `general/secrets` checks into
|
|
393
|
+
// `no-plaintext-exposure` in v1, which is the one still in the repository's
|
|
394
|
+
// `master` docs; `overlaps` because that consolidation also covers the
|
|
395
|
+
// variable and locals cases TF007 reports separately.
|
|
396
|
+
{
|
|
397
|
+
tool: "tfsec",
|
|
398
|
+
rule: "general-secrets-no-plaintext-exposure",
|
|
399
|
+
url: "https://github.com/aquasecurity/tfsec/blob/master/docs/checks/general/secrets/no-plaintext-exposure/index.md",
|
|
400
|
+
relation: "overlaps",
|
|
401
|
+
},
|
|
402
|
+
{ tool: "kics", rule: "a88baa34-e2ad-44ea-ad6f-8cac87bc7c71", url: "https://docs.kics.io/latest/secrets/", relation: "overlaps" },
|
|
403
|
+
],
|
|
404
|
+
};
|
package/src/lint/audit.test.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { dirname, join } from "node:path";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { copyFileSync, mkdirSync, mkdtempSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
3
5
|
import { describe, expect, test } from "vitest";
|
|
4
|
-
import { auditFiles, type ChecksProvider, type EntitiesProvider } from "@intentius/chant/audit/core";
|
|
5
|
-
import {
|
|
6
|
+
import { auditFiles, CROSS_FILE, type AuditFinding, type ChecksProvider, type EntitiesProvider } from "@intentius/chant/audit/core";
|
|
7
|
+
import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { collectCandidates, discoverByDetection, type DetectPlugin } from "@intentius/chant/audit/discover";
|
|
9
|
+
import { auditTerraformState } from "@intentius/chant/audit/terraform-state";
|
|
6
10
|
import { terraformPlugin } from "../plugin";
|
|
11
|
+
import { AUDIT_ROOT_NAME } from "../hcl/parse";
|
|
12
|
+
import { renderTerraformRoots } from "../hcl/roots";
|
|
13
|
+
import { postSynthChecks } from "./post-synth";
|
|
7
14
|
|
|
8
15
|
/**
|
|
9
16
|
* End-to-end audit test (#2085 acceptance): `chant audit` (`auditFiles` on top
|
|
@@ -35,11 +42,211 @@ describe("chant audit against a discovered terraform root module", () => {
|
|
|
35
42
|
expect(tf001[0]!.lexicon).toBe("terraform");
|
|
36
43
|
});
|
|
37
44
|
|
|
38
|
-
|
|
45
|
+
// #2218: `backend "local"` is the local backend named rather than fallen
|
|
46
|
+
// back into, so it is the same finding as no backend at all. The audit path
|
|
47
|
+
// gets its own case because it parses joined file content rather than a
|
|
48
|
+
// directory, and the label has to survive that parse too.
|
|
49
|
+
test('reports TF001 for a root whose only backend is `backend "local"`', async () => {
|
|
39
50
|
const inputs = discoverByDetection(join(fixtures, "with-backend"), [terraformDetectPlugin]);
|
|
40
51
|
expect(inputs).toHaveLength(1);
|
|
41
52
|
|
|
53
|
+
const findings = await auditFiles(inputs, { checksProvider, entitiesProvider });
|
|
54
|
+
const tf001 = findings.filter((f) => f.checkId === "TF001");
|
|
55
|
+
expect(tf001).toHaveLength(1);
|
|
56
|
+
expect(tf001[0]!.message).toContain('the backend it declares is `backend "local"`');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("reports nothing for the remote-backend root", async () => {
|
|
60
|
+
const inputs = discoverByDetection(join(fixtures, "remote-backend"), [terraformDetectPlugin]);
|
|
61
|
+
expect(inputs).toHaveLength(1);
|
|
62
|
+
|
|
42
63
|
const findings = await auditFiles(inputs, { checksProvider, entitiesProvider });
|
|
43
64
|
expect(findings.filter((f) => f.checkId === "TF001")).toHaveLength(0);
|
|
44
65
|
});
|
|
45
66
|
});
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* TF023 (#2110) is audit-only: it reads the file list `chant audit` discovers,
|
|
70
|
+
* never a parsed root module, so it lives in core
|
|
71
|
+
* (`packages/core/src/audit/terraform-state.ts`) beside the other
|
|
72
|
+
* lexicon-independent families. These run the two halves it depends on, the
|
|
73
|
+
* real walk and the real check, over the committed fixture roots.
|
|
74
|
+
*/
|
|
75
|
+
describe("TF023: Terraform state committed to the repository", () => {
|
|
76
|
+
const tf023 = join(dirname(fileURLToPath(import.meta.url)), "post-synth", "fixtures", "TF023");
|
|
77
|
+
|
|
78
|
+
test("fires on a root whose state file is committed beside it", () => {
|
|
79
|
+
// The fixture ships its state as `state.json`: a tracked `terraform.tfstate`
|
|
80
|
+
// is exactly what this rule reports, and the repository's own push rules
|
|
81
|
+
// refuse the name. The test materialises the root the rule is written for.
|
|
82
|
+
const root = mkdtempSync(join(tmpdir(), "chant-tf023-"));
|
|
83
|
+
copyFileSync(join(tf023, "positive", "main.tf"), join(root, "main.tf"));
|
|
84
|
+
copyFileSync(join(tf023, "positive", "state.json"), join(root, "terraform.tfstate"));
|
|
85
|
+
const findings = auditTerraformState(collectCandidates(root));
|
|
86
|
+
rmSync(root, { recursive: true, force: true });
|
|
87
|
+
expect(findings).toHaveLength(1);
|
|
88
|
+
expect(findings[0].checkId).toBe("TF023");
|
|
89
|
+
expect(findings[0].file).toBe("terraform.tfstate");
|
|
90
|
+
expect(findings[0].lexicon).toBe("terraform");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("says nothing about a root that keeps its state remote", () => {
|
|
94
|
+
expect(auditTerraformState(collectCandidates(join(tf023, "negative")))).toEqual([]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("never runs during a build: it is not one of this lexicon's post-synth checks", () => {
|
|
98
|
+
expect(postSynthChecks.map((c) => c.id)).not.toContain("TF023");
|
|
99
|
+
expect(terraformPlugin.auditCatalog?.()).not.toHaveProperty("TF023");
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* #2217: `chant audit` used to hand the plugin one directory's joined text and
|
|
105
|
+
* no directory, so the parse stopped at the root module's own files and every
|
|
106
|
+
* discovered directory parsed under the same fixed scope name. A root and its
|
|
107
|
+
* `modules/*` then merged into one entity map, which produced a finding keyed
|
|
108
|
+
* `<scope>/<address>#2` against the file `(cross-file)` and left the two rules
|
|
109
|
+
* about child modules (TF014, TF015) with no child to see. These run the real
|
|
110
|
+
* discovery and the real audit over the tree fixtures those rules were written
|
|
111
|
+
* for.
|
|
112
|
+
*/
|
|
113
|
+
describe("chant audit descends a root module's local modules (#2217)", () => {
|
|
114
|
+
const treeFixtures = join(dirname(fileURLToPath(import.meta.url)), "post-synth", "fixtures");
|
|
115
|
+
|
|
116
|
+
async function audit(dir: string): Promise<AuditFinding[]> {
|
|
117
|
+
return auditFiles(discoverByDetection(dir, [terraformDetectPlugin]), { checksProvider, entitiesProvider });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
test("a directory the root calls as a module is not audited as a root of its own", () => {
|
|
121
|
+
const inputs = discoverByDetection(join(treeFixtures, "TF014", "positive"), [terraformDetectPlugin]);
|
|
122
|
+
expect(inputs.map((i) => i.path)).toEqual(["."]);
|
|
123
|
+
expect(inputs[0]!.dir).toBe(join(treeFixtures, "TF014", "positive"));
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("TF014 fires on the positive fixture, named by its call chain", async () => {
|
|
127
|
+
const findings = await audit(join(treeFixtures, "TF014", "positive"));
|
|
128
|
+
const tf014 = findings.filter((f) => f.checkId === "TF014");
|
|
129
|
+
expect(tf014).toHaveLength(1);
|
|
130
|
+
expect(tf014[0]!.entity).toBe("audit-root/module.cdn/provider.aws");
|
|
131
|
+
expect(tf014[0]!.message).toContain("Callers: audit-root -> module.cdn (main.tf:13).");
|
|
132
|
+
expect(tf014[0]!.file).toBe(".");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("nothing is attributed to a synthetic cross-file entity any more", async () => {
|
|
136
|
+
const findings = await audit(join(treeFixtures, "TF014", "positive"));
|
|
137
|
+
expect(findings.map((f) => f.file)).not.toContain(CROSS_FILE);
|
|
138
|
+
expect(findings.filter((f) => /#\d+$/.test(f.entity ?? ""))).toEqual([]);
|
|
139
|
+
// The root's own provider block is still the root's: TF002 reports it
|
|
140
|
+
// against the root scope, not against a merge of the two directories.
|
|
141
|
+
const tf002 = findings.filter((f) => f.checkId === "TF002");
|
|
142
|
+
expect(tf002.map((f) => f.entity)).toEqual(["audit-root/provider.aws"]);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("TF014 says nothing about the negative fixture's alias-only block", async () => {
|
|
146
|
+
const findings = await audit(join(treeFixtures, "TF014", "negative"));
|
|
147
|
+
expect(findings.filter((f) => f.checkId === "TF014")).toEqual([]);
|
|
148
|
+
expect(findings.map((f) => f.file)).not.toContain(CROSS_FILE);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("TF015 fires on a child module that declares a backend", async () => {
|
|
152
|
+
const findings = await audit(join(treeFixtures, "TF015", "positive"));
|
|
153
|
+
expect(findings.map((f) => `${f.checkId} ${f.entity}`)).toEqual(["TF015 audit-root/module.cdn/terraform"]);
|
|
154
|
+
expect(findings[0]!.message).toContain("Callers: audit-root -> module.cdn");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("TF015 leaves a child's required_version and required_providers alone", async () => {
|
|
158
|
+
expect(await audit(join(treeFixtures, "TF015", "negative"))).toEqual([]);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("TF001 stays root-only: the child module without a backend is not reported", async () => {
|
|
162
|
+
const findings = await audit(join(treeFixtures, "TF014", "positive"));
|
|
163
|
+
expect(findings.filter((f) => f.checkId === "TF001")).toEqual([]);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("two roots in one repository are two scopes, each named after its directory", async () => {
|
|
167
|
+
const repo = mkdtempSync(join(tmpdir(), "chant-2217-"));
|
|
168
|
+
for (const env of ["prod", "dev"]) {
|
|
169
|
+
mkdirSync(join(repo, "envs", env), { recursive: true });
|
|
170
|
+
writeFileSync(join(repo, "envs", env, "main.tf"), 'terraform {\n required_version = ">= 1.5.0"\n}\n');
|
|
171
|
+
}
|
|
172
|
+
const findings = await audit(repo);
|
|
173
|
+
rmSync(repo, { recursive: true, force: true });
|
|
174
|
+
|
|
175
|
+
const tf001 = findings.filter((f) => f.checkId === "TF001");
|
|
176
|
+
expect(tf001.map((f) => f.missing?.scope).sort()).toEqual(["envs.dev", "envs.prod"]);
|
|
177
|
+
expect(tf001.map((f) => f.file).sort()).toEqual(["envs/dev", "envs/prod"]);
|
|
178
|
+
expect(findings.map((f) => f.file)).not.toContain(CROSS_FILE);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The regression net for #2217: for every fixture root in the family, the
|
|
184
|
+
* verdict `chant audit` reaches through real discovery is the verdict
|
|
185
|
+
* `chant build` reaches through `renderTerraformRoots`. The two paths parse
|
|
186
|
+
* the same directories with the same checks, so any rule whose behaviour
|
|
187
|
+
* depends on scope (every module-descent rule, and every root-only rule that
|
|
188
|
+
* must NOT follow one) is pinned on both paths at once.
|
|
189
|
+
*
|
|
190
|
+
* The build side renders with no `binary`, so a `live` fixture (TF024 to
|
|
191
|
+
* TF026) is stock on both sides: the audit path does not detect live mode
|
|
192
|
+
* (#2103), and this asserts the two agree, not that either sees an estate.
|
|
193
|
+
*/
|
|
194
|
+
describe("every fixture root gets the same verdict from chant audit as from chant build (#2217)", () => {
|
|
195
|
+
const fixtureRoot = join(dirname(fileURLToPath(import.meta.url)), "post-synth", "fixtures");
|
|
196
|
+
|
|
197
|
+
function verdictOf(findings: Array<{ checkId: string; entity?: string }>): string[] {
|
|
198
|
+
return findings.map((f) => `${f.checkId} ${f.entity ?? ""}`).sort();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function buildVerdict(dir: string): Promise<string[]> {
|
|
202
|
+
const { entities, warnings } = await renderTerraformRoots({
|
|
203
|
+
projectRoot: dir,
|
|
204
|
+
roots: { [AUDIT_ROOT_NAME]: { dir } },
|
|
205
|
+
});
|
|
206
|
+
const ctx = {
|
|
207
|
+
outputs: new Map(),
|
|
208
|
+
entities,
|
|
209
|
+
buildResult: { outputs: new Map(), entities, warnings, errors: [], sourceFileCount: entities.size },
|
|
210
|
+
} as unknown as PostSynthContext;
|
|
211
|
+
return verdictOf((terraformPlugin.postSynthChecks?.() ?? []).flatMap((check) => check.check(ctx)));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function auditVerdict(dir: string): Promise<string[]> {
|
|
215
|
+
const inputs = discoverByDetection(dir, [terraformDetectPlugin]);
|
|
216
|
+
return verdictOf(await auditFiles(inputs, { checksProvider, entitiesProvider }));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Every fixture case: a `<id>/<case>/` tree as it stands, and a `<id>/<case>.tf` copied into a directory of its own. */
|
|
220
|
+
function cases(): Array<{ name: string; materialize: (into: string) => string }> {
|
|
221
|
+
const out: Array<{ name: string; materialize: (into: string) => string }> = [];
|
|
222
|
+
for (const id of readdirSync(fixtureRoot).filter((e) => statSync(join(fixtureRoot, e)).isDirectory()).sort()) {
|
|
223
|
+
for (const entry of readdirSync(join(fixtureRoot, id)).sort()) {
|
|
224
|
+
const full = join(fixtureRoot, id, entry);
|
|
225
|
+
if (statSync(full).isDirectory()) {
|
|
226
|
+
out.push({ name: `${id}/${entry}`, materialize: () => full });
|
|
227
|
+
} else if (entry.endsWith(".tf")) {
|
|
228
|
+
out.push({
|
|
229
|
+
name: `${id}/${entry}`,
|
|
230
|
+
materialize: (into) => {
|
|
231
|
+
copyFileSync(full, join(into, entry));
|
|
232
|
+
return into;
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return out;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
for (const { name, materialize } of cases()) {
|
|
242
|
+
test(name, async () => {
|
|
243
|
+
const scratch = mkdtempSync(join(tmpdir(), "chant-2217-fx-"));
|
|
244
|
+
const dir = materialize(scratch);
|
|
245
|
+
try {
|
|
246
|
+
expect(await auditVerdict(dir)).toEqual(await buildVerdict(dir));
|
|
247
|
+
} finally {
|
|
248
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
});
|