@intentius/chant-lexicon-terraform 0.101.0 → 0.104.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/__fixtures__/terragrunt/affected-cases.d.ts +23 -0
- package/dist/__fixtures__/terragrunt/affected-cases.d.ts.map +1 -0
- package/dist/__fixtures__/terragrunt/record.d.ts +17 -0
- package/dist/__fixtures__/terragrunt/record.d.ts.map +1 -0
- package/dist/change-set.d.ts +67 -0
- package/dist/change-set.d.ts.map +1 -0
- package/dist/changed-roots.d.ts +30 -0
- package/dist/changed-roots.d.ts.map +1 -0
- package/dist/codegen/package.d.ts +1 -1
- package/dist/commands.d.ts +26 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/components/capability-plugin.d.ts +54 -0
- package/dist/components/capability-plugin.d.ts.map +1 -0
- package/dist/composites/terraform-pin-rollout-op.d.ts +44 -0
- package/dist/composites/terraform-pin-rollout-op.d.ts.map +1 -0
- package/dist/config.d.ts +3 -3
- package/dist/hcl/descend.d.ts +1 -1
- package/dist/hcl/descend.d.ts.map +1 -1
- package/dist/hcl/edges.d.ts +2 -2
- package/dist/hcl/edges.d.ts.map +1 -1
- package/dist/hcl/jsonencode.d.ts +106 -0
- package/dist/hcl/jsonencode.d.ts.map +1 -0
- package/dist/hcl/marker.d.ts.map +1 -1
- package/dist/hcl/parse.d.ts +29 -1
- package/dist/hcl/parse.d.ts.map +1 -1
- package/dist/hcl/roots.d.ts +1 -1
- package/dist/hcl/roots.d.ts.map +1 -1
- package/dist/hcl/suppressions.d.ts.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +22 -4
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/audit-lineage.d.ts +1 -1
- package/dist/lint/audit-lineage.d.ts.map +1 -1
- package/dist/lint/post-synth/flat-attributes.d.ts +69 -0
- package/dist/lint/post-synth/flat-attributes.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 +27 -1
- package/dist/lint/post-synth/module-source.d.ts.map +1 -1
- package/dist/lint/post-synth/terragrunt.d.ts +37 -0
- package/dist/lint/post-synth/terragrunt.d.ts.map +1 -0
- package/dist/lint/post-synth/tf002.d.ts +3 -1
- package/dist/lint/post-synth/tf002.d.ts.map +1 -1
- package/dist/lint/post-synth/tf030.d.ts +51 -0
- package/dist/lint/post-synth/tf030.d.ts.map +1 -0
- package/dist/lint/post-synth/tf031.d.ts +55 -0
- package/dist/lint/post-synth/tf031.d.ts.map +1 -0
- package/dist/lint/post-synth/tf032.d.ts +71 -0
- package/dist/lint/post-synth/tf032.d.ts.map +1 -0
- package/dist/lint/post-synth/tf033.d.ts +50 -0
- package/dist/lint/post-synth/tf033.d.ts.map +1 -0
- package/dist/lint/post-synth/tf034.d.ts +25 -0
- package/dist/lint/post-synth/tf034.d.ts.map +1 -0
- package/dist/lint/post-synth/tf035.d.ts +33 -0
- package/dist/lint/post-synth/tf035.d.ts.map +1 -0
- package/dist/lint/post-synth/tf036.d.ts +33 -0
- package/dist/lint/post-synth/tf036.d.ts.map +1 -0
- package/dist/lint/post-synth/tf037.d.ts +31 -0
- package/dist/lint/post-synth/tf037.d.ts.map +1 -0
- package/dist/lint/post-synth/tf038.d.ts +22 -0
- package/dist/lint/post-synth/tf038.d.ts.map +1 -0
- package/dist/lint/post-synth/tf039.d.ts +23 -0
- package/dist/lint/post-synth/tf039.d.ts.map +1 -0
- package/dist/lint/post-synth/tf040.d.ts +30 -0
- package/dist/lint/post-synth/tf040.d.ts.map +1 -0
- package/dist/lint/post-synth/tf041.d.ts +20 -0
- package/dist/lint/post-synth/tf041.d.ts.map +1 -0
- package/dist/lint/post-synth/tf042.d.ts +18 -0
- package/dist/lint/post-synth/tf042.d.ts.map +1 -0
- package/dist/lint/post-synth/tf043.d.ts +17 -0
- package/dist/lint/post-synth/tf043.d.ts.map +1 -0
- package/dist/lint/post-synth/tf044.d.ts +17 -0
- package/dist/lint/post-synth/tf044.d.ts.map +1 -0
- package/dist/lint/post-synth/tf045.d.ts +22 -0
- package/dist/lint/post-synth/tf045.d.ts.map +1 -0
- package/dist/manifest.json +1 -1
- package/dist/okf/index.md +16 -0
- package/dist/okf/rules/TF030.md +11 -0
- package/dist/okf/rules/TF031.md +11 -0
- package/dist/okf/rules/TF032.md +11 -0
- package/dist/okf/rules/TF033.md +11 -0
- package/dist/okf/rules/TF034.md +11 -0
- package/dist/okf/rules/TF035.md +11 -0
- package/dist/okf/rules/TF036.md +11 -0
- package/dist/okf/rules/TF037.md +11 -0
- package/dist/okf/rules/TF038.md +11 -0
- package/dist/okf/rules/TF039.md +11 -0
- package/dist/okf/rules/TF040.md +11 -0
- package/dist/okf/rules/TF041.md +11 -0
- package/dist/okf/rules/TF042.md +11 -0
- package/dist/okf/rules/TF043.md +11 -0
- package/dist/okf/rules/TF044.md +11 -0
- package/dist/okf/rules/TF045.md +11 -0
- package/dist/op/activities/index.d.ts +4 -0
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/pin-rollout.d.ts +46 -0
- package/dist/op/activities/pin-rollout.d.ts.map +1 -0
- package/dist/op/activities/terraform.d.ts +24 -50
- package/dist/op/activities/terraform.d.ts.map +1 -1
- package/dist/op/activity-contracts.d.ts +66 -18
- package/dist/op/activity-contracts.d.ts.map +1 -1
- package/dist/op/builders.d.ts +9 -0
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/pin/edit-ts.d.ts +21 -0
- package/dist/pin/edit-ts.d.ts.map +1 -0
- package/dist/pin/edit.d.ts +52 -0
- package/dist/pin/edit.d.ts.map +1 -0
- package/dist/pin/forge.d.ts +39 -0
- package/dist/pin/forge.d.ts.map +1 -0
- package/dist/pin/index.d.ts +22 -0
- package/dist/pin/index.d.ts.map +1 -0
- package/dist/pin/rollout.d.ts +116 -0
- package/dist/pin/rollout.d.ts.map +1 -0
- package/dist/pin/scan.d.ts +54 -0
- package/dist/pin/scan.d.ts.map +1 -0
- package/dist/pin/source.d.ts +93 -0
- package/dist/pin/source.d.ts.map +1 -0
- package/dist/pin/waves.d.ts +66 -0
- package/dist/pin/waves.d.ts.map +1 -0
- package/dist/plan-digest.d.ts +59 -0
- package/dist/plan-digest.d.ts.map +1 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/rules/flat-attributes.ts +146 -0
- package/dist/rules/module-source.ts +56 -1
- package/dist/rules/terragrunt.ts +76 -0
- package/dist/rules/tf002.ts +1 -1
- package/dist/rules/tf030.ts +290 -0
- package/dist/rules/tf031.ts +370 -0
- package/dist/rules/tf032.ts +226 -0
- package/dist/rules/tf033.ts +160 -0
- package/dist/rules/tf034.ts +63 -0
- package/dist/rules/tf035.ts +74 -0
- package/dist/rules/tf036.ts +119 -0
- package/dist/rules/tf037.ts +101 -0
- package/dist/rules/tf038.ts +71 -0
- package/dist/rules/tf039.ts +64 -0
- package/dist/rules/tf040.ts +76 -0
- package/dist/rules/tf041.ts +56 -0
- package/dist/rules/tf042.ts +42 -0
- package/dist/rules/tf043.ts +49 -0
- package/dist/rules/tf044.ts +51 -0
- package/dist/rules/tf045.ts +47 -0
- package/dist/terragrunt/affected.d.ts +119 -0
- package/dist/terragrunt/affected.d.ts.map +1 -0
- package/dist/terragrunt/index.d.ts +11 -0
- package/dist/terragrunt/index.d.ts.map +1 -0
- package/dist/terragrunt/mocks.d.ts +147 -0
- package/dist/terragrunt/mocks.d.ts.map +1 -0
- package/dist/terragrunt/run.d.ts +130 -0
- package/dist/terragrunt/run.d.ts.map +1 -0
- package/dist/terragrunt/units.d.ts +103 -0
- package/dist/terragrunt/units.d.ts.map +1 -0
- package/dist/terragrunt/wave.d.ts +89 -0
- package/dist/terragrunt/wave.d.ts.map +1 -0
- package/package.json +13 -3
- package/src/__fixtures__/change-set/choudoufu-set-plan.change-set.golden.json +95 -0
- package/src/__fixtures__/change-set/choudoufu-set-plan.json +76 -0
- package/src/__fixtures__/change-set/choudoufu.part.golden.json +173 -0
- package/src/__fixtures__/change-set/choudoufu.plan.json +610 -0
- package/src/__fixtures__/change-set/terraform.import-forget.plan.json +81 -0
- package/src/__fixtures__/change-set/terraform.part.golden.json +173 -0
- package/src/__fixtures__/change-set/terraform.plan.json +589 -0
- package/src/__fixtures__/change-set/tofu.part.golden.json +173 -0
- package/src/__fixtures__/change-set/tofu.plan.json +588 -0
- package/src/__fixtures__/change-set/v1.tf.txt +21 -0
- package/src/__fixtures__/change-set/v2-module-app.tf.txt +4 -0
- package/src/__fixtures__/change-set/v2.tf.txt +21 -0
- package/src/__fixtures__/pin/choudoufu-live-waves-n5.json +83 -0
- package/src/__fixtures__/pin/largeset-oci-n5/apply-order.txt +5 -0
- package/src/__fixtures__/pin/largeset-oci-n5/estates/e01/estate.chdf.hcl +6 -0
- package/src/__fixtures__/pin/largeset-oci-n5/estates/e01/main.tf +33 -0
- package/src/__fixtures__/pin/largeset-oci-n5/estates/e02/estate.chdf.hcl +6 -0
- package/src/__fixtures__/pin/largeset-oci-n5/estates/e02/main.tf +45 -0
- package/src/__fixtures__/pin/largeset-oci-n5/estates/e03/estate.chdf.hcl +6 -0
- package/src/__fixtures__/pin/largeset-oci-n5/estates/e03/main.tf +47 -0
- package/src/__fixtures__/pin/largeset-oci-n5/estates/e04/estate.chdf.hcl +6 -0
- package/src/__fixtures__/pin/largeset-oci-n5/estates/e04/main.tf +31 -0
- package/src/__fixtures__/pin/largeset-oci-n5/estates/e05/estate.chdf.hcl +6 -0
- package/src/__fixtures__/pin/largeset-oci-n5/estates/e05/main.tf +27 -0
- package/src/__fixtures__/pin/largeset-oci-n5/fixture.json +77 -0
- package/src/__fixtures__/plan-summary/largeset-n5-bump.json +2071 -0
- package/src/__fixtures__/plan-summary/largeset-n5.summary.golden.json +95 -0
- package/src/__fixtures__/plan-summary/largeset-n5.summary.golden.md +22 -0
- package/src/__fixtures__/plan-summary/largeset-n5.summary.golden.txt +12 -0
- package/src/__fixtures__/terragrunt/affected/catalog/units/web/terragrunt.hcl +3 -0
- package/src/__fixtures__/terragrunt/affected/gitignore +3 -0
- package/src/__fixtures__/terragrunt/affected/live/dev/app/terragrunt.hcl +3 -0
- package/src/__fixtures__/terragrunt/affected/live/dev/vpc/terragrunt.hcl +3 -0
- package/src/__fixtures__/terragrunt/affected/live/prod/app/terragrunt.hcl +3 -0
- package/src/__fixtures__/terragrunt/affected/live/stg/terragrunt.stack.hcl +5 -0
- package/src/__fixtures__/terragrunt/affected/modules/app/main.tf +6 -0
- package/src/__fixtures__/terragrunt/affected/modules/app/policy.json +1 -0
- package/src/__fixtures__/terragrunt/affected/modules/net/main.tf +2 -0
- package/src/__fixtures__/terragrunt/affected/modules/thing/main.tf +3 -0
- package/src/__fixtures__/terragrunt/affected/root.hcl +5 -0
- package/src/__fixtures__/terragrunt/affected-cases.ts +126 -0
- package/src/__fixtures__/terragrunt/five-units/catalog/units/thing/terragrunt.hcl +3 -0
- package/src/__fixtures__/terragrunt/five-units/live/dev/app/terragrunt.hcl +13 -0
- package/src/__fixtures__/terragrunt/five-units/live/dev/db/terragrunt.hcl +8 -0
- package/src/__fixtures__/terragrunt/five-units/live/dev/vpc/terragrunt.hcl +3 -0
- package/src/__fixtures__/terragrunt/five-units/live/prod/app/terragrunt.hcl +8 -0
- package/src/__fixtures__/terragrunt/five-units/live/prod/vpc/terragrunt.hcl +3 -0
- package/src/__fixtures__/terragrunt/five-units/modules/thing/main.tf +7 -0
- package/src/__fixtures__/terragrunt/five-units/root.hcl +5 -0
- package/src/__fixtures__/terragrunt/record.ts +56 -0
- package/src/__fixtures__/terragrunt/recorded/affected-find.json +50 -0
- package/src/__fixtures__/terragrunt/recorded/failed-plan-report.json +36 -0
- package/src/__fixtures__/terragrunt/recorded/find.json +67 -0
- package/src/__fixtures__/terragrunt/recorded/version.txt +1 -0
- package/src/__fixtures__/terragrunt/recorded/wave-1/apply-report.json +22 -0
- package/src/__fixtures__/terragrunt/recorded/wave-1/json/live/dev/vpc/tfplan.json +1 -0
- package/src/__fixtures__/terragrunt/recorded/wave-1/json/live/prod/vpc/tfplan.json +1 -0
- package/src/__fixtures__/terragrunt/recorded/wave-1/plan-report.json +22 -0
- package/src/__fixtures__/terragrunt/recorded/wave-2/json/live/dev/db/tfplan.json +1 -0
- package/src/__fixtures__/terragrunt/recorded/wave-2/json/live/prod/app/tfplan.json +1 -0
- package/src/__fixtures__/terragrunt/recorded/wave-2/plan-report.json +22 -0
- package/src/change-set-combined-run.test.ts +156 -0
- package/src/change-set.test.ts +151 -0
- package/src/change-set.ts +273 -0
- package/src/changed-roots.test.ts +58 -0
- package/src/changed-roots.ts +102 -0
- package/src/commands.ts +112 -0
- package/src/components/capability-plugin.test.ts +91 -0
- package/src/components/capability-plugin.ts +131 -0
- package/src/components/pr-loop.tofu.test.ts +244 -0
- package/src/composites/terraform-pin-rollout-op.test.ts +69 -0
- package/src/composites/terraform-pin-rollout-op.ts +60 -0
- package/src/hcl/descend.ts +1 -1
- package/src/hcl/edges.ts +2 -2
- package/src/hcl/jsonencode.test.ts +226 -0
- package/src/hcl/jsonencode.ts +454 -0
- package/src/hcl/parse.test.ts +25 -0
- package/src/hcl/parse.ts +91 -6
- package/src/hcl/roots.ts +1 -1
- package/src/hcl/suppressions.ts +8 -2
- package/src/index.ts +18 -0
- package/src/lint/audit-catalog.ts +230 -0
- package/src/lint/audit-lineage.ts +155 -1
- package/src/lint/audit.test.ts +3 -1
- package/src/lint/post-synth/estate-2284.test.ts +68 -0
- package/src/lint/post-synth/fixtures/TF030/negative-rules.tf +45 -0
- package/src/lint/post-synth/fixtures/TF030/negative.tf +55 -0
- package/src/lint/post-synth/fixtures/TF030/not-determined-port.tf +28 -0
- package/src/lint/post-synth/fixtures/TF030/not-determined-standalone.tf +32 -0
- package/src/lint/post-synth/fixtures/TF030/not-determined-vpc-rule.tf +21 -0
- package/src/lint/post-synth/fixtures/TF030/not-determined.tf +39 -0
- package/src/lint/post-synth/fixtures/TF030/positive-ipv6.tf +44 -0
- package/src/lint/post-synth/fixtures/TF030/positive-mixed.tf +36 -0
- package/src/lint/post-synth/fixtures/TF030/positive-standalone.tf +25 -0
- package/src/lint/post-synth/fixtures/TF030/positive.tf +43 -0
- package/src/lint/post-synth/fixtures/TF031/negative.tf +89 -0
- package/src/lint/post-synth/fixtures/TF031/not-determined.tf +139 -0
- package/src/lint/post-synth/fixtures/TF031/positive-data-source.tf +57 -0
- package/src/lint/post-synth/fixtures/TF031/positive-heredoc.tf +149 -0
- package/src/lint/post-synth/fixtures/TF031/positive-jsonencode.tf +115 -0
- package/src/lint/post-synth/fixtures/TF032/negative-heredoc.tf +20 -0
- package/src/lint/post-synth/fixtures/TF032/negative-references.tf +214 -0
- package/src/lint/post-synth/fixtures/TF032/negative.tf +99 -0
- package/src/lint/post-synth/fixtures/TF032/positive-heredoc.tf +22 -0
- package/src/lint/post-synth/fixtures/TF032/positive-sidecar.tf +197 -0
- package/src/lint/post-synth/fixtures/TF032/positive.tf +54 -0
- package/src/lint/post-synth/fixtures/TF033/negative.tf +39 -0
- package/src/lint/post-synth/fixtures/TF033/not-determined-aurora.tf +25 -0
- package/src/lint/post-synth/fixtures/TF033/positive-absent.tf +93 -0
- package/src/lint/post-synth/fixtures/TF033/positive.tf +50 -0
- package/src/lint/post-synth/fixtures/TF034/negative.tf +24 -0
- package/src/lint/post-synth/fixtures/TF034/positive-absent.tf +36 -0
- package/src/lint/post-synth/fixtures/TF034/positive.tf +14 -0
- package/src/lint/post-synth/fixtures/TF035/negative-absent.tf +43 -0
- package/src/lint/post-synth/fixtures/TF035/negative.tf +54 -0
- package/src/lint/post-synth/fixtures/TF035/positive.tf +37 -0
- package/src/lint/post-synth/fixtures/TF036/negative.tf +61 -0
- package/src/lint/post-synth/fixtures/TF036/not-determined.tf +28 -0
- package/src/lint/post-synth/fixtures/TF036/positive.tf +38 -0
- package/src/lint/post-synth/fixtures/TF037/negative-exclusion.tf +63 -0
- package/src/lint/post-synth/fixtures/TF037/negative.tf +27 -0
- package/src/lint/post-synth/fixtures/TF037/not-determined.tf +27 -0
- package/src/lint/post-synth/fixtures/TF037/positive-absent.tf +15 -0
- package/src/lint/post-synth/fixtures/TF037/positive.tf +26 -0
- package/src/lint/post-synth/fixtures/TF038/positive.tf +3 -0
- package/src/lint/post-synth/fixtures/TF039/positive.tf +4 -0
- package/src/lint/post-synth/fixtures/TF041/negative/terragrunt.hcl +12 -0
- package/src/lint/post-synth/fixtures/TF041/positive/terragrunt.hcl +7 -0
- package/src/lint/post-synth/fixtures/TF041/positive-apply/terragrunt.hcl +8 -0
- package/src/lint/post-synth/fixtures/TF042/negative/terragrunt.hcl +13 -0
- package/src/lint/post-synth/fixtures/TF042/positive/terragrunt.hcl +9 -0
- package/src/lint/post-synth/fixtures/TF043/negative/terragrunt.hcl +3 -0
- package/src/lint/post-synth/fixtures/TF043/negative-other/terragrunt.hcl +3 -0
- package/src/lint/post-synth/fixtures/TF043/positive-git/terragrunt.hcl +3 -0
- package/src/lint/post-synth/fixtures/TF043/positive-oci/terragrunt.hcl +3 -0
- package/src/lint/post-synth/fixtures/TF043/positive-tfr/terragrunt.hcl +3 -0
- package/src/lint/post-synth/fixtures/TF044/negative/terragrunt.hcl +9 -0
- package/src/lint/post-synth/fixtures/TF044/positive/terragrunt.hcl +7 -0
- package/src/lint/post-synth/fixtures/TF044/positive-generate/terragrunt.hcl +9 -0
- package/src/lint/post-synth/fixtures/TF045/negative/root.hcl +1 -0
- package/src/lint/post-synth/fixtures/TF045/negative/terragrunt.hcl +3 -0
- package/src/lint/post-synth/fixtures/TF045/positive/root.hcl +9 -0
- package/src/lint/post-synth/fixtures/TF045/positive/terragrunt.hcl +3 -0
- package/src/lint/post-synth/fixtures/TF045/unit-only/terragrunt.hcl +3 -0
- package/src/lint/post-synth/fixtures/estate-2284/clean/main.tf +132 -0
- package/src/lint/post-synth/fixtures/estate-2284/violating/main.tf +112 -0
- package/src/lint/post-synth/flat-attributes.ts +146 -0
- package/src/lint/post-synth/index.ts +32 -0
- package/src/lint/post-synth/module-source.test.ts +42 -1
- package/src/lint/post-synth/module-source.ts +56 -1
- package/src/lint/post-synth/terragrunt.ts +76 -0
- package/src/lint/post-synth/tf002.ts +1 -1
- package/src/lint/post-synth/tf030.test.ts +219 -0
- package/src/lint/post-synth/tf030.ts +290 -0
- package/src/lint/post-synth/tf031.test.ts +229 -0
- package/src/lint/post-synth/tf031.ts +370 -0
- package/src/lint/post-synth/tf032.test.ts +127 -0
- package/src/lint/post-synth/tf032.ts +226 -0
- package/src/lint/post-synth/tf033.test.ts +126 -0
- package/src/lint/post-synth/tf033.ts +160 -0
- package/src/lint/post-synth/tf034.test.ts +71 -0
- package/src/lint/post-synth/tf034.ts +63 -0
- package/src/lint/post-synth/tf035.test.ts +68 -0
- package/src/lint/post-synth/tf035.ts +74 -0
- package/src/lint/post-synth/tf036.test.ts +87 -0
- package/src/lint/post-synth/tf036.ts +119 -0
- package/src/lint/post-synth/tf037.test.ts +88 -0
- package/src/lint/post-synth/tf037.ts +101 -0
- package/src/lint/post-synth/tf038.test.ts +51 -0
- package/src/lint/post-synth/tf038.ts +71 -0
- package/src/lint/post-synth/tf039.test.ts +40 -0
- package/src/lint/post-synth/tf039.ts +64 -0
- package/src/lint/post-synth/tf040.test.ts +55 -0
- package/src/lint/post-synth/tf040.ts +76 -0
- package/src/lint/post-synth/tf041.test.ts +50 -0
- package/src/lint/post-synth/tf041.ts +56 -0
- package/src/lint/post-synth/tf042.test.ts +19 -0
- package/src/lint/post-synth/tf042.ts +42 -0
- package/src/lint/post-synth/tf043.test.ts +65 -0
- package/src/lint/post-synth/tf043.ts +49 -0
- package/src/lint/post-synth/tf044.test.ts +27 -0
- package/src/lint/post-synth/tf044.ts +51 -0
- package/src/lint/post-synth/tf045.test.ts +30 -0
- package/src/lint/post-synth/tf045.ts +47 -0
- package/src/op/activities/index.ts +4 -0
- package/src/op/activities/pin-rollout.ts +77 -0
- package/src/op/activities/terraform.test.ts +13 -0
- package/src/op/activities/terraform.ts +46 -86
- package/src/op/activity-contracts.test.ts +4 -1
- package/src/op/activity-contracts.ts +42 -0
- package/src/op/builders.ts +13 -0
- package/src/pin/bundle.test.ts +53 -0
- package/src/pin/edit-ts.test.ts +39 -0
- package/src/pin/edit-ts.ts +77 -0
- package/src/pin/edit.test.ts +199 -0
- package/src/pin/edit.ts +157 -0
- package/src/pin/forge.ts +85 -0
- package/src/pin/index.ts +22 -0
- package/src/pin/rollout.test.ts +232 -0
- package/src/pin/rollout.ts +451 -0
- package/src/pin/scan.ts +309 -0
- package/src/pin/source.ts +206 -0
- package/src/pin/waves.test.ts +101 -0
- package/src/pin/waves.ts +125 -0
- package/src/plan-digest.ts +99 -0
- package/src/plan-summary.test.ts +154 -0
- package/src/plugin.ts +25 -2
- package/src/terragrunt/affected.acceptance.test.ts +83 -0
- package/src/terragrunt/affected.test.ts +177 -0
- package/src/terragrunt/affected.ts +348 -0
- package/src/terragrunt/index.ts +11 -0
- package/src/terragrunt/mocks.acceptance.test.ts +105 -0
- package/src/terragrunt/mocks.test.ts +309 -0
- package/src/terragrunt/mocks.ts +324 -0
- package/src/terragrunt/run.ts +369 -0
- package/src/terragrunt/terragrunt.acceptance.test.ts +122 -0
- package/src/terragrunt/terragrunt.test.ts +332 -0
- package/src/terragrunt/units.ts +235 -0
- package/src/terragrunt/wave.ts +181 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Epic #2284 acceptance estate, violating root. Written for chant, not
|
|
2
|
+
# vendored: the fidelity probe's fixtures (docs/design/waw-hcl-fidelity-probe.md)
|
|
3
|
+
# were never committed, so this root carries one violation per rule, TF030 to
|
|
4
|
+
# TF037, each written the way a real estate writes it. `chant audit` over this
|
|
5
|
+
# directory reports every one of them (src/lint/post-synth/estate-2284.test.ts).
|
|
6
|
+
|
|
7
|
+
terraform {
|
|
8
|
+
required_version = ">= 1.5.0"
|
|
9
|
+
|
|
10
|
+
backend "s3" {
|
|
11
|
+
bucket = "acme-terraform-state"
|
|
12
|
+
key = "payments/terraform.tfstate"
|
|
13
|
+
region = "eu-west-1"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
required_providers {
|
|
17
|
+
aws = {
|
|
18
|
+
source = "hashicorp/aws"
|
|
19
|
+
version = "~> 6.0"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
provider "aws" {
|
|
25
|
+
region = "eu-west-1"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# TF030: SSH open to the world.
|
|
29
|
+
resource "aws_security_group" "bastion" {
|
|
30
|
+
name = "bastion"
|
|
31
|
+
vpc_id = "vpc-0a1b2c3d4e5f60718"
|
|
32
|
+
|
|
33
|
+
ingress {
|
|
34
|
+
from_port = 22
|
|
35
|
+
to_port = 22
|
|
36
|
+
protocol = "tcp"
|
|
37
|
+
cidr_blocks = ["0.0.0.0/0"]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# TF031: an admin policy.
|
|
42
|
+
resource "aws_iam_policy" "deployer" {
|
|
43
|
+
name = "deployer"
|
|
44
|
+
policy = jsonencode({
|
|
45
|
+
Version = "2012-10-17"
|
|
46
|
+
Statement = [
|
|
47
|
+
{
|
|
48
|
+
Effect = "Allow"
|
|
49
|
+
Action = "*"
|
|
50
|
+
Resource = "*"
|
|
51
|
+
},
|
|
52
|
+
]
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# TF032: a database password in a container's environment.
|
|
57
|
+
resource "aws_ecs_task_definition" "api" {
|
|
58
|
+
family = "payments-api"
|
|
59
|
+
requires_compatibilities = ["FARGATE"]
|
|
60
|
+
network_mode = "awsvpc"
|
|
61
|
+
cpu = "512"
|
|
62
|
+
memory = "1024"
|
|
63
|
+
|
|
64
|
+
container_definitions = jsonencode([{
|
|
65
|
+
name = "api"
|
|
66
|
+
image = "public.ecr.aws/acme/payments-api:1.4.2"
|
|
67
|
+
environment = [
|
|
68
|
+
{ name = "LOG_LEVEL", value = "info" },
|
|
69
|
+
{ name = "DB_PASSWORD", value = "Pm-Pr0d-Db-2026!" }
|
|
70
|
+
]
|
|
71
|
+
}])
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# TF033: storage encryption written off.
|
|
75
|
+
resource "aws_db_instance" "ledger" {
|
|
76
|
+
identifier = "ledger"
|
|
77
|
+
engine = "postgres"
|
|
78
|
+
instance_class = "db.t4g.medium"
|
|
79
|
+
allocated_storage = 50
|
|
80
|
+
username = "ledger"
|
|
81
|
+
password = var.db_password
|
|
82
|
+
storage_encrypted = false
|
|
83
|
+
skip_final_snapshot = true
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
variable "db_password" {
|
|
87
|
+
type = string
|
|
88
|
+
sensitive = true
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# TF034: a topic that names no key.
|
|
92
|
+
resource "aws_sns_topic" "alerts" {
|
|
93
|
+
name = "payments-alerts"
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# TF035: SSE-SQS turned off.
|
|
97
|
+
resource "aws_sqs_queue" "settlements" {
|
|
98
|
+
name = "settlements"
|
|
99
|
+
sqs_managed_sse_enabled = false
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# TF036: a volume that asks not to be encrypted.
|
|
103
|
+
resource "aws_ebs_volume" "scratch" {
|
|
104
|
+
availability_zone = "eu-west-1a"
|
|
105
|
+
size = 100
|
|
106
|
+
encrypted = false
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# TF037: a repository left at the provider default, MUTABLE.
|
|
110
|
+
resource "aws_ecr_repository" "api" {
|
|
111
|
+
name = "payments-api"
|
|
112
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared reading helpers for TF033-TF037, the five flat encryption and
|
|
3
|
+
* immutability rules (chant #2288, epic #2284).
|
|
4
|
+
*
|
|
5
|
+
* Each of those rules reads one or two scalar attributes of one resource type
|
|
6
|
+
* and answers a three-valued question: the literal proves the resource
|
|
7
|
+
* secure, the literal proves it insecure, or the parse cannot tell. This
|
|
8
|
+
* module is the reading half; each rule file holds its own answer for
|
|
9
|
+
* absence, because that answer differs per resource (see each rule's doc
|
|
10
|
+
* comment and its section of `docs/pages/lint-rules.mdx`). It exports no
|
|
11
|
+
* `PostSynthCheck`, so the generated barrel skips it.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
15
|
+
import { RESOURCE_TYPE, type BlockBody } from "../../hcl/parse";
|
|
16
|
+
import { attr } from "../../hcl/value";
|
|
17
|
+
import { blocksOfType, nestedBodies, type TerraformBlock } from "./blocks";
|
|
18
|
+
|
|
19
|
+
/** What one attribute read settles. `reason` says why a value could not be read. */
|
|
20
|
+
export type Read<T> = { kind: "known"; value: T } | { kind: "absent" } | { kind: "unknown"; reason: string };
|
|
21
|
+
|
|
22
|
+
/** Every `resource` block of one provider type (`aws_sqs_queue`), in entity order. */
|
|
23
|
+
export function resourcesOfType(ctx: PostSynthContext, type: string): TerraformBlock[] {
|
|
24
|
+
return blocksOfType(ctx, RESOURCE_TYPE).filter((b) => b.address.startsWith(`${type}.`));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** One expression, for a message: `var.encrypted`, or the template text. */
|
|
28
|
+
function describe(name: string, raw: unknown, refs: string[] | undefined, kind: "reference" | "template"): string {
|
|
29
|
+
return kind === "reference"
|
|
30
|
+
? `\`${name}\` is an expression (${refs?.[0] ?? String(raw)})`
|
|
31
|
+
: `\`${name}\` is a template string ("${String(raw)}")`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A bool attribute. Terraform converts the strings `"true"` and `"false"` to
|
|
36
|
+
* bool, so those read as the bool they convert to. Any other literal (a
|
|
37
|
+
* number, a list) is a plan-time type error, not this rule's concern, and
|
|
38
|
+
* reads as unknown.
|
|
39
|
+
*/
|
|
40
|
+
export function readBool(body: BlockBody, name: string): Read<boolean> {
|
|
41
|
+
const a = attr(body, name);
|
|
42
|
+
if (a.kind === "absent") return { kind: "absent" };
|
|
43
|
+
if (a.kind === "reference" || a.kind === "template") {
|
|
44
|
+
return { kind: "unknown", reason: describe(name, a.raw, a.refs, a.kind) };
|
|
45
|
+
}
|
|
46
|
+
if (typeof a.value === "boolean") return { kind: "known", value: a.value };
|
|
47
|
+
if (a.value === "true" || a.value === "false") return { kind: "known", value: a.value === "true" };
|
|
48
|
+
return { kind: "unknown", reason: `\`${name}\` is not a bool (${JSON.stringify(a.value)})` };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** A string attribute, literal or not. */
|
|
52
|
+
export function readString(body: BlockBody, name: string): Read<string> {
|
|
53
|
+
const a = attr(body, name);
|
|
54
|
+
if (a.kind === "absent") return { kind: "absent" };
|
|
55
|
+
if (a.kind === "reference" || a.kind === "template") {
|
|
56
|
+
return { kind: "unknown", reason: describe(name, a.raw, a.refs, a.kind) };
|
|
57
|
+
}
|
|
58
|
+
if (typeof a.value === "string") return { kind: "known", value: a.value };
|
|
59
|
+
return { kind: "unknown", reason: `\`${name}\` is not a string (${JSON.stringify(a.value)})` };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Expression roots that name an input or a computed value, not a managed object. */
|
|
63
|
+
const VALUE_ROOTS = new Set(["var", "local", "module", "each", "count", "path", "terraform", "self"]);
|
|
64
|
+
|
|
65
|
+
/** `aws_kms_key.sns.arn`, `data.aws_kms_alias.sqs.target_key_arn`, with an optional index or splat. */
|
|
66
|
+
const OBJECT_ATTRIBUTE = /^(?:data\.)?([a-z][a-z0-9_]*)\.[A-Za-z_][\w-]*(?:\[[^\]]*\])?(?:\.[A-Za-z_][\w-]*|\[[^\]]*\])+$/;
|
|
67
|
+
|
|
68
|
+
/** Every `${...}` span in a template, as `attr()` finds them. */
|
|
69
|
+
const SPANS = /\$\{(?:[^{}]|\{[^{}]*\})*\}/g;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Whether a key-like argument (`kms_master_key_id`) is set to something.
|
|
73
|
+
*
|
|
74
|
+
* - absent, or `null`: `absent`.
|
|
75
|
+
* - a literal: `known`, with the string (`""` is a value, and a rule decides
|
|
76
|
+
* what an empty key means).
|
|
77
|
+
* - an attribute of a managed resource or data source (`aws_kms_key.x.arn`):
|
|
78
|
+
* `known` and non-empty. The provider computes a key's id or ARN; it is
|
|
79
|
+
* never the empty string, so the key is set whatever its value turns out to
|
|
80
|
+
* be.
|
|
81
|
+
* - a template with literal text (`"alias/${var.env}-sns"`): `known` and
|
|
82
|
+
* non-empty, for the same reason.
|
|
83
|
+
* - anything else (`var.kms_key`, `local.key`, a conditional, a function
|
|
84
|
+
* call): `unknown`. A variable can be `null` or `""`.
|
|
85
|
+
*/
|
|
86
|
+
export function readKeySet(body: BlockBody, name: string): Read<string> {
|
|
87
|
+
const a = attr(body, name);
|
|
88
|
+
if (a.kind === "absent") return { kind: "absent" };
|
|
89
|
+
if (a.kind === "literal") {
|
|
90
|
+
return typeof a.value === "string"
|
|
91
|
+
? { kind: "known", value: a.value }
|
|
92
|
+
: { kind: "unknown", reason: `\`${name}\` is not a string (${JSON.stringify(a.value)})` };
|
|
93
|
+
}
|
|
94
|
+
const raw = String(a.raw);
|
|
95
|
+
if (a.kind === "template") {
|
|
96
|
+
if (raw.replace(SPANS, "").length > 0) return { kind: "known", value: raw };
|
|
97
|
+
return { kind: "unknown", reason: describe(name, a.raw, a.refs, "template") };
|
|
98
|
+
}
|
|
99
|
+
const expr = (a.refs?.[0] ?? "").trim();
|
|
100
|
+
const m = OBJECT_ATTRIBUTE.exec(expr);
|
|
101
|
+
if (m && !VALUE_ROOTS.has(m[1])) return { kind: "known", value: raw };
|
|
102
|
+
return { kind: "unknown", reason: describe(name, a.raw, a.refs, "reference") };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Whether an argument that names a source (`snapshot_identifier`,
|
|
107
|
+
* `replicate_source_db`) or a nested block (`restore_to_point_in_time`) is
|
|
108
|
+
* given. The provider reads these with `GetOk`, so a literal `""` is the same
|
|
109
|
+
* as leaving the argument out; anything else, a reference included, is given.
|
|
110
|
+
*/
|
|
111
|
+
export function isGiven(body: BlockBody, name: string): boolean {
|
|
112
|
+
const a = attr(body, name);
|
|
113
|
+
if (a.kind === "absent") return false;
|
|
114
|
+
if (a.kind === "literal") {
|
|
115
|
+
if (a.value === "" || a.value === false) return false;
|
|
116
|
+
if (Array.isArray(a.value)) return a.value.length > 0;
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** The bodies of a nested block, re-exported for the rule files. */
|
|
122
|
+
export { nestedBodies };
|
|
123
|
+
|
|
124
|
+
/** A finding the literal proves. */
|
|
125
|
+
export function finding(
|
|
126
|
+
checkId: string,
|
|
127
|
+
severity: "error" | "warning",
|
|
128
|
+
block: TerraformBlock,
|
|
129
|
+
message: string,
|
|
130
|
+
): PostSynthDiagnostic {
|
|
131
|
+
return { checkId, severity, message: `"${block.address}" ${message}`, entity: block.key, lexicon: "terraform" };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The one `info` diagnostic a rule emits when it cannot read the value it
|
|
136
|
+
* needs. The message starts `Not determined:`, as #2284 fixed for TF030-TF037.
|
|
137
|
+
*/
|
|
138
|
+
export function notDetermined(checkId: string, block: TerraformBlock, reason: string, tail: string): PostSynthDiagnostic {
|
|
139
|
+
return {
|
|
140
|
+
checkId,
|
|
141
|
+
severity: "info",
|
|
142
|
+
message: `Not determined: "${block.address}" ${reason}. ${tail}`,
|
|
143
|
+
entity: block.key,
|
|
144
|
+
lexicon: "terraform",
|
|
145
|
+
};
|
|
146
|
+
}
|
|
@@ -28,6 +28,22 @@ import { tf026 } from "./tf026";
|
|
|
28
28
|
import { tf027 } from "./tf027";
|
|
29
29
|
import { tf028 } from "./tf028";
|
|
30
30
|
import { tf029 } from "./tf029";
|
|
31
|
+
import { tf030 } from "./tf030";
|
|
32
|
+
import { tf031 } from "./tf031";
|
|
33
|
+
import { tf032 } from "./tf032";
|
|
34
|
+
import { tf033 } from "./tf033";
|
|
35
|
+
import { tf034 } from "./tf034";
|
|
36
|
+
import { tf035 } from "./tf035";
|
|
37
|
+
import { tf036 } from "./tf036";
|
|
38
|
+
import { tf037 } from "./tf037";
|
|
39
|
+
import { tf038 } from "./tf038";
|
|
40
|
+
import { tf039 } from "./tf039";
|
|
41
|
+
import { tf040 } from "./tf040";
|
|
42
|
+
import { tf041 } from "./tf041";
|
|
43
|
+
import { tf042 } from "./tf042";
|
|
44
|
+
import { tf043 } from "./tf043";
|
|
45
|
+
import { tf044 } from "./tf044";
|
|
46
|
+
import { tf045 } from "./tf045";
|
|
31
47
|
|
|
32
48
|
export const postSynthChecks: PostSynthCheck[] = [
|
|
33
49
|
tf001,
|
|
@@ -58,4 +74,20 @@ export const postSynthChecks: PostSynthCheck[] = [
|
|
|
58
74
|
tf027,
|
|
59
75
|
tf028,
|
|
60
76
|
tf029,
|
|
77
|
+
tf030,
|
|
78
|
+
tf031,
|
|
79
|
+
tf032,
|
|
80
|
+
tf033,
|
|
81
|
+
tf034,
|
|
82
|
+
tf035,
|
|
83
|
+
tf036,
|
|
84
|
+
tf037,
|
|
85
|
+
tf038,
|
|
86
|
+
tf039,
|
|
87
|
+
tf040,
|
|
88
|
+
tf041,
|
|
89
|
+
tf042,
|
|
90
|
+
tf043,
|
|
91
|
+
tf044,
|
|
92
|
+
tf045,
|
|
61
93
|
];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from "vitest";
|
|
2
|
-
import { classifyModuleSource, isDefaultBranch, isPinnedRef } from "./module-source";
|
|
2
|
+
import { classifyModuleSource, isDefaultBranch, isExactVersionConstraint, isMutableOciTag, isPinnedRef } from "./module-source";
|
|
3
3
|
|
|
4
4
|
describe("classifyModuleSource", () => {
|
|
5
5
|
test("local paths", () => {
|
|
@@ -73,3 +73,44 @@ describe("isDefaultBranch", () => {
|
|
|
73
73
|
expect(isDefaultBranch("some-feature")).toBe(false);
|
|
74
74
|
});
|
|
75
75
|
});
|
|
76
|
+
|
|
77
|
+
describe("classifyModuleSource: oci sources (#3190)", () => {
|
|
78
|
+
const cases: Array<[string, string, string | undefined, string | undefined]> = [
|
|
79
|
+
["tag query", "oci://example.com/org/mod?tag=v1.4.0", "v1.4.0", undefined],
|
|
80
|
+
["digest query", "oci://example.com/org/mod?digest=sha256:abc123", undefined, "sha256:abc123"],
|
|
81
|
+
["tag suffix", "oci://example.com/org/mod:1.4.0", "1.4.0", undefined],
|
|
82
|
+
["digest suffix", "oci://example.com/org/mod@sha256:abc123", undefined, "sha256:abc123"],
|
|
83
|
+
["tag and digest suffix", "oci://example.com/org/mod:1.4.0@sha256:abc123", "1.4.0", "sha256:abc123"],
|
|
84
|
+
["tag and digest query", "oci://example.com/org/mod?tag=1.4.0&digest=sha256:abc123", "1.4.0", "sha256:abc123"],
|
|
85
|
+
["neither", "oci://example.com/org/mod", undefined, undefined],
|
|
86
|
+
["host with a port, no tag", "oci://registry.example.com:5000/org/mod", undefined, undefined],
|
|
87
|
+
["host with a port and a tag", "oci://registry.example.com:5000/org/mod:2.0.1", "2.0.1", undefined],
|
|
88
|
+
["host with a port and a digest", "oci://registry.example.com:5000/org/mod@sha256:def", undefined, "sha256:def"],
|
|
89
|
+
["subdirectory before the query", "oci://example.com/org/mod//modules/vpc?tag=v1.0.0", "v1.0.0", undefined],
|
|
90
|
+
["subdirectory, no pin", "oci://example.com/org/mod//modules/vpc", undefined, undefined],
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
test.each(cases)("%s", (_name, source, tag, digest) => {
|
|
94
|
+
const c = classifyModuleSource(source);
|
|
95
|
+
expect(c.kind).toBe("oci");
|
|
96
|
+
expect(c.tag).toBe(tag);
|
|
97
|
+
expect(c.digest).toBe(digest);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("isMutableOciTag", () => {
|
|
102
|
+
test("names that move are mutable, versions are not", () => {
|
|
103
|
+
expect(isMutableOciTag("latest")).toBe(true);
|
|
104
|
+
expect(isMutableOciTag("LATEST")).toBe(true);
|
|
105
|
+
expect(isMutableOciTag("main")).toBe(true);
|
|
106
|
+
expect(isMutableOciTag("1.4.0")).toBe(false);
|
|
107
|
+
expect(isMutableOciTag("v1.4.0")).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("isExactVersionConstraint", () => {
|
|
112
|
+
test.each([["1.4.0", true], ["= 1.4.0", true], ["=1.4.0", true], ["1.4.0-rc1", true], ["~> 1.4", false], [">= 1.4", false], [">= 1.0, < 2.0", false], ["!= 1.4.0", false], ["< 2", false]])(
|
|
113
|
+
"%s",
|
|
114
|
+
(v, exact) => expect(isExactVersionConstraint(v as string)).toBe(exact),
|
|
115
|
+
);
|
|
116
|
+
});
|
|
@@ -18,14 +18,27 @@
|
|
|
18
18
|
* recognize, scp-style `git@host:path` addresses, and any source ending in
|
|
19
19
|
* `.git`. `local` is `./` or `../` (checkov's `UNKNOWN` case for module
|
|
20
20
|
* pinning: a local path cannot be pinned to anything and is never flagged).
|
|
21
|
+
* `oci` is an OpenTofu `oci://host/repo` source (#3190). OpenTofu selects a
|
|
22
|
+
* version with `?tag=` or `?digest=` (https://opentofu.org/docs/language/modules/sources/),
|
|
23
|
+
* and a source naming neither resolves the `latest` tag. The container-image
|
|
24
|
+
* shapes `repo:tag` and `repo@sha256:...` are read too, so a source written
|
|
25
|
+
* the way `docker pull` spells it is not misread as unpinned. A `:` inside the
|
|
26
|
+
* host (a registry port) is never a tag: only a colon in the last path
|
|
27
|
+
* segment is. The tag and digest rules were checked against the docs page
|
|
28
|
+
* above; the suffix forms are not documented there and are not verified
|
|
29
|
+
* against OpenTofu's parser.
|
|
21
30
|
*/
|
|
22
31
|
|
|
23
|
-
export type ModuleSourceKind = "local" | "registry" | "git" | "other";
|
|
32
|
+
export type ModuleSourceKind = "local" | "registry" | "git" | "oci" | "other";
|
|
24
33
|
|
|
25
34
|
export interface ModuleSourceClassification {
|
|
26
35
|
kind: ModuleSourceKind;
|
|
27
36
|
/** The `?ref=`/`&ref=` value (falling back to `rev`), when `kind` is `"git"`. */
|
|
28
37
|
ref?: string;
|
|
38
|
+
/** The tag an `oci://` source names, from `?tag=` or a `:tag` suffix on the repository. */
|
|
39
|
+
tag?: string;
|
|
40
|
+
/** The digest an `oci://` source names, from `?digest=` or an `@sha256:...` suffix. */
|
|
41
|
+
digest?: string;
|
|
29
42
|
}
|
|
30
43
|
|
|
31
44
|
const REGISTRY_NAME = /^[0-9A-Za-z](?:[0-9A-Za-z_-]{0,62}[0-9A-Za-z])?$/;
|
|
@@ -77,6 +90,25 @@ function isGitShaped(rest: string): boolean {
|
|
|
77
90
|
return false;
|
|
78
91
|
}
|
|
79
92
|
|
|
93
|
+
function classifyOci(base: string, query: string): ModuleSourceClassification {
|
|
94
|
+
const params = parseQuery(query);
|
|
95
|
+
let path = stripSubdir(base).replace(/^oci:\/\//i, "");
|
|
96
|
+
let tag = params.get("tag") || undefined;
|
|
97
|
+
let digest = params.get("digest") || undefined;
|
|
98
|
+
|
|
99
|
+
const at = path.indexOf("@");
|
|
100
|
+
if (at !== -1) {
|
|
101
|
+
digest = digest ?? (path.slice(at + 1) || undefined);
|
|
102
|
+
path = path.slice(0, at);
|
|
103
|
+
}
|
|
104
|
+
const lastSlash = path.lastIndexOf("/");
|
|
105
|
+
const colon = path.indexOf(":", lastSlash + 1); // a port sits before the first slash
|
|
106
|
+
if (colon !== -1) {
|
|
107
|
+
tag = tag ?? (path.slice(colon + 1) || undefined);
|
|
108
|
+
}
|
|
109
|
+
return { kind: "oci", ...(tag !== undefined ? { tag } : {}), ...(digest !== undefined ? { digest } : {}) };
|
|
110
|
+
}
|
|
111
|
+
|
|
80
112
|
export function classifyModuleSource(source: string): ModuleSourceClassification {
|
|
81
113
|
const trimmed = source.trim();
|
|
82
114
|
if (trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith(".\\") || trimmed.startsWith("..\\")) {
|
|
@@ -87,6 +119,8 @@ export function classifyModuleSource(source: string): ModuleSourceClassification
|
|
|
87
119
|
const base = queryIdx === -1 ? trimmed : trimmed.slice(0, queryIdx);
|
|
88
120
|
const query = queryIdx === -1 ? "" : trimmed.slice(queryIdx + 1);
|
|
89
121
|
|
|
122
|
+
if (/^oci:\/\//i.test(base)) return classifyOci(base, query);
|
|
123
|
+
|
|
90
124
|
const forced = /^(git|hg)::/i.exec(base);
|
|
91
125
|
const rest = forced ? base.slice(forced[0].length) : base;
|
|
92
126
|
|
|
@@ -119,3 +153,24 @@ export function isPinnedRef(ref: string): boolean {
|
|
|
119
153
|
export function isDefaultBranch(ref: string): boolean {
|
|
120
154
|
return DEFAULT_BRANCHES.has(ref);
|
|
121
155
|
}
|
|
156
|
+
|
|
157
|
+
const MUTABLE_TAGS = new Set(["latest", "main", "master", "develop", "dev", "stable", "edge", "nightly"]);
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Is `tag` a name that conventionally moves (`latest`, a branch-like name)
|
|
161
|
+
* rather than an exact version. Used by TF038. A tag is a mutable pointer in
|
|
162
|
+
* most registries whatever it looks like, so the rule is a warning and this
|
|
163
|
+
* only decides what reads as obviously floating.
|
|
164
|
+
*/
|
|
165
|
+
export function isMutableOciTag(tag: string): boolean {
|
|
166
|
+
return MUTABLE_TAGS.has(tag.toLowerCase());
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Is a registry `version` constraint an exact version (`1.4.0`, `= 1.4.0`,
|
|
171
|
+
* `=1.4.0`) rather than a range (`~> 1.4`, `>= 1.4`, `>= 1.0, < 2.0`). Used
|
|
172
|
+
* by TF039. A bare version is an exact constraint in Terraform.
|
|
173
|
+
*/
|
|
174
|
+
export function isExactVersionConstraint(version: string): boolean {
|
|
175
|
+
return /^(?:=\s*)?v?\d+(?:\.\d+){0,2}(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(version.trim()) && !version.includes(",");
|
|
176
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared reading for the Terragrunt rules TF041 to TF045 (#3417).
|
|
3
|
+
*
|
|
4
|
+
* These rules look at `terragrunt.hcl` and `root.hcl`, which the parse reads
|
|
5
|
+
* into their own entity types (`TERRAGRUNT_*_TYPE` in `../../hcl/parse.ts`)
|
|
6
|
+
* and never into the module types the other TF rules check. Like the module
|
|
7
|
+
* source rules, they read the literal values hcl2json hands back: an
|
|
8
|
+
* attribute built from a function or a `local.` reference arrives as a
|
|
9
|
+
* `"${...}"` string and is treated as not determined, never as a finding.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { isResourceDeclarable, type Declarable } from "@intentius/chant/declarable";
|
|
13
|
+
import type { BlockBody } from "../../hcl/parse";
|
|
14
|
+
import { classifyModuleSource } from "./module-source";
|
|
15
|
+
|
|
16
|
+
/** Entities of one Terragrunt type, with their keys, in map order. */
|
|
17
|
+
export function terragruntEntities(
|
|
18
|
+
entities: ReadonlyMap<string, Declarable>,
|
|
19
|
+
entityType: string,
|
|
20
|
+
): Array<{ key: string; address: string; file: string; body: BlockBody }> {
|
|
21
|
+
const out: Array<{ key: string; address: string; file: string; body: BlockBody }> = [];
|
|
22
|
+
for (const [key, entity] of entities) {
|
|
23
|
+
if (entity.entityType !== entityType || !isResourceDeclarable(entity)) continue;
|
|
24
|
+
const props = entity.props as { address?: unknown; file?: unknown; body?: unknown };
|
|
25
|
+
out.push({
|
|
26
|
+
key,
|
|
27
|
+
address: typeof props.address === "string" ? props.address : key,
|
|
28
|
+
file: typeof props.file === "string" ? props.file : "terragrunt.hcl",
|
|
29
|
+
body: (typeof props.body === "object" && props.body !== null ? props.body : {}) as BlockBody,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** An HCL expression hcl2json left as `"${...}"`: its value is not known without evaluating it. */
|
|
36
|
+
export function isExpression(value: unknown): boolean {
|
|
37
|
+
return typeof value === "string" && value.includes("${");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type TerragruntSourceKind = "local" | "git" | "tfr" | "oci" | "other";
|
|
41
|
+
|
|
42
|
+
export interface TerragruntSourceClassification {
|
|
43
|
+
kind: TerragruntSourceKind;
|
|
44
|
+
/** Whether the source names a version: `?ref=` for git, `?version=` for `tfr://`, a tag or digest for `oci://`. Undefined for `local` and `other`. */
|
|
45
|
+
pinned?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Classify a unit's `terraform.source`. Git and `oci://` sources go through
|
|
50
|
+
* {@link classifyModuleSource}, the reader TF005 and TF038 use. `tfr://` is
|
|
51
|
+
* Terragrunt's own scheme for registry modules and takes its version from the
|
|
52
|
+
* `?version=` query argument. A source that holds an interpolation, or that
|
|
53
|
+
* names an absolute path or an archive URL, is `other` or `local` and is
|
|
54
|
+
* never reported.
|
|
55
|
+
*/
|
|
56
|
+
export function classifyTerragruntSource(source: string): TerragruntSourceClassification {
|
|
57
|
+
const trimmed = source.trim();
|
|
58
|
+
if (trimmed.includes("${")) return { kind: "other" };
|
|
59
|
+
if (/^tfr:\/\//i.test(trimmed)) {
|
|
60
|
+
const query = trimmed.includes("?") ? trimmed.slice(trimmed.indexOf("?") + 1) : "";
|
|
61
|
+
const version = new URLSearchParams(query).get("version");
|
|
62
|
+
return { kind: "tfr", pinned: version !== null && version.trim() !== "" };
|
|
63
|
+
}
|
|
64
|
+
if (trimmed.startsWith("/")) return { kind: "local" };
|
|
65
|
+
const classified = classifyModuleSource(trimmed);
|
|
66
|
+
switch (classified.kind) {
|
|
67
|
+
case "local":
|
|
68
|
+
return { kind: "local" };
|
|
69
|
+
case "git":
|
|
70
|
+
return { kind: "git", pinned: classified.ref !== undefined };
|
|
71
|
+
case "oci":
|
|
72
|
+
return { kind: "oci", pinned: classified.tag !== undefined || classified.digest !== undefined };
|
|
73
|
+
default:
|
|
74
|
+
return { kind: "other" };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -51,7 +51,7 @@ function providerNameFromType(type: string): string {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/** One implied provider name to the entity key that first implied it in file order. */
|
|
54
|
-
function impliedProviders(entities: PostSynthContext["entities"], root: string): Map<string, string> {
|
|
54
|
+
export function impliedProviders(entities: PostSynthContext["entities"], root: string): Map<string, string> {
|
|
55
55
|
const implied = new Map<string, string>();
|
|
56
56
|
const fromProviderBlock = new Set<string>();
|
|
57
57
|
|