@intentius/chant-lexicon-terraform 0.124.52 → 0.124.55
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 +14 -10
- package/dist/__fixtures__/pr-loop/estate.component.d.ts +7 -0
- package/dist/__fixtures__/pr-loop/estate.component.d.ts.map +1 -0
- package/dist/__fixtures__/pr-loop/estate.component.js +7 -0
- package/dist/__fixtures__/report/generate.d.ts +26 -0
- package/dist/__fixtures__/report/generate.d.ts.map +1 -0
- package/dist/__fixtures__/report/generate.js +58 -0
- package/dist/applied-state.d.ts +27 -0
- package/dist/applied-state.d.ts.map +1 -0
- package/dist/applied-state.js +44 -0
- package/dist/backend-config.d.ts +53 -0
- package/dist/backend-config.d.ts.map +1 -0
- package/dist/backend-config.js +136 -0
- package/dist/behaviour/kinds.d.ts.map +1 -1
- package/dist/behaviour/kinds.js +4 -0
- package/dist/behaviour/predict.d.ts.map +1 -1
- package/dist/behaviour/predict.js +6 -2
- package/dist/behaviour/reference-catalog.d.ts +16 -5
- package/dist/behaviour/reference-catalog.d.ts.map +1 -1
- package/dist/behaviour/reference-catalog.js +16 -5
- package/dist/behaviour/request.d.ts.map +1 -1
- package/dist/behaviour/request.js +3 -2
- package/dist/changed-roots.d.ts +6 -4
- package/dist/changed-roots.d.ts.map +1 -1
- package/dist/changed-roots.js +14 -18
- package/dist/ci-setup.d.ts +11 -0
- package/dist/ci-setup.d.ts.map +1 -0
- package/dist/ci-setup.js +41 -0
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +4 -3
- package/dist/components/capability-plugin.d.ts +105 -9
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/capability-plugin.js +357 -36
- package/dist/components/compose-group.d.ts +145 -0
- package/dist/components/compose-group.d.ts.map +1 -0
- package/dist/components/compose-group.js +407 -0
- package/dist/components/placeholder-preview.d.ts +78 -0
- package/dist/components/placeholder-preview.d.ts.map +1 -0
- package/dist/components/placeholder-preview.js +254 -0
- package/dist/components/release-inputs.d.ts +29 -0
- package/dist/components/release-inputs.d.ts.map +1 -0
- package/dist/components/release-inputs.js +57 -0
- package/dist/components/trace-facts.d.ts +16 -0
- package/dist/components/trace-facts.d.ts.map +1 -0
- package/dist/components/trace-facts.js +19 -0
- package/dist/compose/apply-events.d.ts +46 -0
- package/dist/compose/apply-events.d.ts.map +1 -0
- package/dist/compose/apply-events.js +77 -0
- package/dist/compose/estate.d.ts +194 -0
- package/dist/compose/estate.d.ts.map +1 -0
- package/dist/compose/estate.js +363 -0
- package/dist/compose/hcl.d.ts +99 -0
- package/dist/compose/hcl.d.ts.map +1 -0
- package/dist/compose/hcl.js +302 -0
- package/dist/compose/module-address.d.ts +24 -0
- package/dist/compose/module-address.d.ts.map +1 -0
- package/dist/compose/module-address.js +43 -0
- package/dist/compose/root.d.ts +127 -0
- package/dist/compose/root.d.ts.map +1 -0
- package/dist/compose/root.js +586 -0
- package/dist/compose/run.d.ts +183 -0
- package/dist/compose/run.d.ts.map +1 -0
- package/dist/compose/run.js +447 -0
- package/dist/compose/slice.d.ts +102 -0
- package/dist/compose/slice.d.ts.map +1 -0
- package/dist/compose/slice.js +249 -0
- package/dist/compose/state-address.d.ts +28 -0
- package/dist/compose/state-address.d.ts.map +1 -0
- package/dist/compose/state-address.js +154 -0
- package/dist/compose/state-merge.d.ts +52 -0
- package/dist/compose/state-merge.d.ts.map +1 -0
- package/dist/compose/state-merge.js +135 -0
- package/dist/compose.d.ts +179 -0
- package/dist/compose.d.ts.map +1 -0
- package/dist/compose.js +482 -0
- package/dist/composites/terraform-apply-op.d.ts.map +1 -1
- package/dist/composites/terraform-apply-op.js +7 -3
- package/dist/cone.d.ts +53 -0
- package/dist/cone.d.ts.map +1 -0
- package/dist/cone.js +119 -0
- package/dist/config.d.ts +59 -13
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +43 -9
- package/dist/cost-hold.d.ts +27 -0
- package/dist/cost-hold.d.ts.map +1 -0
- package/dist/cost-hold.js +21 -0
- package/dist/describe-resources.d.ts +74 -12
- package/dist/describe-resources.d.ts.map +1 -1
- package/dist/describe-resources.js +374 -66
- package/dist/drift-read.d.ts +33 -0
- package/dist/drift-read.d.ts.map +1 -0
- package/dist/drift-read.js +243 -0
- package/dist/drift-record.d.ts +147 -0
- package/dist/drift-record.d.ts.map +1 -0
- package/dist/drift-record.js +383 -0
- package/dist/drift-rows.d.ts +61 -0
- package/dist/drift-rows.d.ts.map +1 -0
- package/dist/drift-rows.js +108 -0
- package/dist/exit-codes.d.ts +55 -0
- package/dist/exit-codes.d.ts.map +1 -0
- package/dist/exit-codes.js +48 -0
- package/dist/hcl/edges.d.ts.map +1 -1
- package/dist/hcl/edges.js +24 -1
- package/dist/hcl/files.d.ts +61 -0
- package/dist/hcl/files.d.ts.map +1 -0
- package/dist/hcl/files.js +221 -0
- package/dist/hcl/instances.d.ts +82 -0
- package/dist/hcl/instances.d.ts.map +1 -0
- package/dist/hcl/instances.js +390 -0
- package/dist/hcl/parse.d.ts +81 -9
- package/dist/hcl/parse.d.ts.map +1 -1
- package/dist/hcl/parse.js +114 -16
- package/dist/hcl/roots.d.ts.map +1 -1
- package/dist/hcl/roots.js +5 -0
- package/dist/hcl/suppressions.d.ts.map +1 -1
- package/dist/hcl/suppressions.js +8 -2
- package/dist/index.d.ts +13 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -2
- package/dist/integrity.json +8 -5
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/audit-catalog.js +27 -3
- package/dist/lint/audit-lineage.d.ts +14 -2
- package/dist/lint/audit-lineage.d.ts.map +1 -1
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/lint/post-synth/index.js +4 -0
- package/dist/lint/post-synth/record-store.d.ts +54 -0
- package/dist/lint/post-synth/record-store.d.ts.map +1 -0
- package/dist/lint/post-synth/record-store.js +104 -0
- package/dist/lint/post-synth/tf001.d.ts +2 -2
- package/dist/lint/post-synth/tf001.js +2 -2
- package/dist/lint/post-synth/tf024.d.ts +35 -11
- package/dist/lint/post-synth/tf024.d.ts.map +1 -1
- package/dist/lint/post-synth/tf024.js +92 -20
- package/dist/lint/post-synth/tf046.d.ts +64 -0
- package/dist/lint/post-synth/tf046.d.ts.map +1 -0
- package/dist/lint/post-synth/tf046.js +175 -0
- package/dist/lint/post-synth/tf047.d.ts +36 -0
- package/dist/lint/post-synth/tf047.d.ts.map +1 -0
- package/dist/lint/post-synth/tf047.js +94 -0
- package/dist/live-plan-reasons.d.ts +21 -0
- package/dist/live-plan-reasons.d.ts.map +1 -0
- package/dist/live-plan-reasons.js +47 -0
- package/dist/lock-rows.d.ts +36 -0
- package/dist/lock-rows.d.ts.map +1 -0
- package/dist/lock-rows.js +60 -0
- package/dist/lock-timeout.d.ts +22 -0
- package/dist/lock-timeout.d.ts.map +1 -0
- package/dist/lock-timeout.js +32 -0
- package/dist/lsp/option-keys.d.ts.map +1 -1
- package/dist/lsp/option-keys.js +2 -0
- package/dist/manifest.json +1 -1
- package/dist/module-catalog/index.d.ts +69 -0
- package/dist/module-catalog/index.d.ts.map +1 -0
- package/dist/module-catalog/index.js +110 -0
- package/dist/module-catalog/refresh-cli.d.ts +4 -0
- package/dist/module-catalog/refresh-cli.d.ts.map +1 -0
- package/dist/module-catalog/refresh-cli.js +64 -0
- package/dist/module-catalog/static-tier.d.ts +6 -0
- package/dist/module-catalog/static-tier.d.ts.map +1 -0
- package/dist/module-catalog/static-tier.js +1650 -0
- package/dist/module-catalog/types.d.ts +65 -0
- package/dist/module-catalog/types.d.ts.map +1 -0
- package/dist/module-catalog/types.js +33 -0
- package/dist/okf/index.md +3 -1
- package/dist/okf/rules/TF024.md +2 -2
- package/dist/okf/rules/TF046.md +11 -0
- package/dist/okf/rules/TF047.md +11 -0
- package/dist/op/activities/index.d.ts +6 -2
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/index.js +5 -1
- package/dist/op/activities/live-detect.d.ts +4 -3
- package/dist/op/activities/live-detect.d.ts.map +1 -1
- package/dist/op/activities/live-detect.js +57 -32
- package/dist/op/activities/pin-rollout.d.ts +2 -0
- package/dist/op/activities/pin-rollout.d.ts.map +1 -1
- package/dist/op/activities/pin-rollout.js +1 -0
- package/dist/op/activities/terraform.d.ts +182 -5
- package/dist/op/activities/terraform.d.ts.map +1 -1
- package/dist/op/activities/terraform.js +224 -24
- package/dist/op/activity-contracts.d.ts +27 -0
- package/dist/op/activity-contracts.d.ts.map +1 -1
- package/dist/op/activity-contracts.js +19 -1
- package/dist/op/builders.d.ts +7 -1
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/builders.js +9 -0
- package/dist/pin/consumers.d.ts +35 -0
- package/dist/pin/consumers.d.ts.map +1 -0
- package/dist/pin/consumers.js +70 -0
- package/dist/pin/edit.d.ts.map +1 -1
- package/dist/pin/edit.js +35 -0
- package/dist/pin/rollout.d.ts +2 -0
- package/dist/pin/rollout.d.ts.map +1 -1
- package/dist/pin/rollout.js +14 -11
- package/dist/plan-shape.d.ts +30 -0
- package/dist/plan-shape.d.ts.map +1 -0
- package/dist/plan-shape.js +125 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +37 -18
- package/dist/registry/address.d.ts +63 -0
- package/dist/registry/address.d.ts.map +1 -0
- package/dist/registry/address.js +65 -0
- package/dist/registry/catalog-read.d.ts +14 -0
- package/dist/registry/catalog-read.d.ts.map +1 -0
- package/dist/registry/catalog-read.js +58 -0
- package/dist/registry/deprecated.d.ts +37 -0
- package/dist/registry/deprecated.d.ts.map +1 -0
- package/dist/registry/deprecated.js +43 -0
- package/dist/registry/docs.d.ts +96 -0
- package/dist/registry/docs.d.ts.map +1 -0
- package/dist/registry/docs.js +210 -0
- package/dist/registry/edge/edge.d.ts +46 -0
- package/dist/registry/edge/edge.d.ts.map +1 -0
- package/dist/registry/edge/edge.js +194 -0
- package/dist/registry/edge/index.d.ts +11 -0
- package/dist/registry/edge/index.d.ts.map +1 -0
- package/dist/registry/edge/index.js +10 -0
- package/dist/registry/edge/lambda-edge.d.ts +76 -0
- package/dist/registry/edge/lambda-edge.d.ts.map +1 -0
- package/dist/registry/edge/lambda-edge.js +95 -0
- package/dist/registry/edge/sigv4-web.d.ts +33 -0
- package/dist/registry/edge/sigv4-web.d.ts.map +1 -0
- package/dist/registry/edge/sigv4-web.js +56 -0
- package/dist/registry/edge/token-source.d.ts +73 -0
- package/dist/registry/edge/token-source.d.ts.map +1 -0
- package/dist/registry/edge/token-source.js +115 -0
- package/dist/registry/edge/worker.d.ts +45 -0
- package/dist/registry/edge/worker.d.ts.map +1 -0
- package/dist/registry/edge/worker.js +26 -0
- package/dist/registry/handler.d.ts +25 -0
- package/dist/registry/handler.d.ts.map +1 -0
- package/dist/registry/handler.js +75 -0
- package/dist/registry/headers-file.d.ts +21 -0
- package/dist/registry/headers-file.d.ts.map +1 -0
- package/dist/registry/headers-file.js +57 -0
- package/dist/registry/index.d.ts +31 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +30 -0
- package/dist/registry/mirror-paths.d.ts +9 -0
- package/dist/registry/mirror-paths.d.ts.map +1 -0
- package/dist/registry/mirror-paths.js +8 -0
- package/dist/registry/mirror.d.ts +130 -0
- package/dist/registry/mirror.d.ts.map +1 -0
- package/dist/registry/mirror.js +319 -0
- package/dist/registry/proxies.d.ts +73 -0
- package/dist/registry/proxies.d.ts.map +1 -0
- package/dist/registry/proxies.js +111 -0
- package/dist/registry/reader.d.ts +57 -0
- package/dist/registry/reader.d.ts.map +1 -0
- package/dist/registry/reader.js +198 -0
- package/dist/registry/semver.d.ts +16 -0
- package/dist/registry/semver.d.ts.map +1 -0
- package/dist/registry/semver.js +24 -0
- package/dist/registry/serve.d.ts +24 -0
- package/dist/registry/serve.d.ts.map +1 -0
- package/dist/registry/serve.js +64 -0
- package/dist/registry/source-forms.d.ts +79 -0
- package/dist/registry/source-forms.d.ts.map +1 -0
- package/dist/registry/source-forms.js +202 -0
- package/dist/registry/targets/artifactory.d.ts +28 -0
- package/dist/registry/targets/artifactory.d.ts.map +1 -0
- package/dist/registry/targets/artifactory.js +206 -0
- package/dist/registry/targets/fake-artifactory.d.ts +30 -0
- package/dist/registry/targets/fake-artifactory.d.ts.map +1 -0
- package/dist/registry/targets/fake-artifactory.js +142 -0
- package/dist/registry/targets/fake-gitlab.d.ts +39 -0
- package/dist/registry/targets/fake-gitlab.d.ts.map +1 -0
- package/dist/registry/targets/fake-gitlab.js +136 -0
- package/dist/registry/targets/fake-hcp.d.ts +50 -0
- package/dist/registry/targets/fake-hcp.d.ts.map +1 -0
- package/dist/registry/targets/fake-hcp.js +202 -0
- package/dist/registry/targets/fake-http.d.ts +31 -0
- package/dist/registry/targets/fake-http.d.ts.map +1 -0
- package/dist/registry/targets/fake-http.js +87 -0
- package/dist/registry/targets/fake-nexus.d.ts +27 -0
- package/dist/registry/targets/fake-nexus.d.ts.map +1 -0
- package/dist/registry/targets/fake-nexus.js +137 -0
- package/dist/registry/targets/gitlab.d.ts +53 -0
- package/dist/registry/targets/gitlab.d.ts.map +1 -0
- package/dist/registry/targets/gitlab.js +187 -0
- package/dist/registry/targets/hcp.d.ts +29 -0
- package/dist/registry/targets/hcp.d.ts.map +1 -0
- package/dist/registry/targets/hcp.js +310 -0
- package/dist/registry/targets/hosted.d.ts +62 -0
- package/dist/registry/targets/hosted.d.ts.map +1 -0
- package/dist/registry/targets/hosted.js +74 -0
- package/dist/registry/targets/index.d.ts +18 -0
- package/dist/registry/targets/index.d.ts.map +1 -0
- package/dist/registry/targets/index.js +17 -0
- package/dist/registry/targets/module-target.d.ts +68 -0
- package/dist/registry/targets/module-target.d.ts.map +1 -0
- package/dist/registry/targets/module-target.js +63 -0
- package/dist/registry/targets/nexus.d.ts +43 -0
- package/dist/registry/targets/nexus.d.ts.map +1 -0
- package/dist/registry/targets/nexus.js +250 -0
- package/dist/registry/targets/zip.d.ts +55 -0
- package/dist/registry/targets/zip.d.ts.map +1 -0
- package/dist/registry/targets/zip.js +278 -0
- package/dist/registry/writer.d.ts +101 -0
- package/dist/registry/writer.d.ts.map +1 -0
- package/dist/registry/writer.js +109 -0
- package/dist/report.d.ts +15 -6
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +28 -55
- package/dist/rules/record-store.ts +122 -0
- package/dist/rules/tf001.ts +2 -2
- package/dist/rules/tf024.ts +105 -22
- package/dist/rules/tf046.ts +204 -0
- package/dist/rules/tf047.ts +114 -0
- package/dist/skills/chant-terraform.md +1 -1
- package/dist/state/azure-blob.d.ts +9 -0
- package/dist/state/azure-blob.d.ts.map +1 -0
- package/dist/state/azure-blob.js +8 -0
- package/dist/state/backend.d.ts +158 -0
- package/dist/state/backend.d.ts.map +1 -0
- package/dist/state/backend.js +406 -0
- package/dist/state/exec.d.ts +39 -0
- package/dist/state/exec.d.ts.map +1 -0
- package/dist/state/exec.js +80 -0
- package/dist/state/gcs.d.ts +8 -0
- package/dist/state/gcs.d.ts.map +1 -0
- package/dist/state/gcs.js +7 -0
- package/dist/state/index.d.ts +15 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +14 -0
- package/dist/state/locks.d.ts +105 -0
- package/dist/state/locks.d.ts.map +1 -0
- package/dist/state/locks.js +130 -0
- package/dist/state/object-store.d.ts +8 -0
- package/dist/state/object-store.d.ts.map +1 -0
- package/dist/state/object-store.js +7 -0
- package/dist/state/root-states.d.ts +131 -0
- package/dist/state/root-states.d.ts.map +1 -0
- package/dist/state/root-states.js +241 -0
- package/dist/state/s3.d.ts +21 -0
- package/dist/state/s3.d.ts.map +1 -0
- package/dist/state/s3.js +19 -0
- package/dist/state/store.d.ts +51 -0
- package/dist/state/store.d.ts.map +1 -0
- package/dist/state/store.js +185 -0
- package/dist/state-ops.d.ts +255 -0
- package/dist/state-ops.d.ts.map +1 -0
- package/dist/state-ops.js +305 -0
- package/dist/state-reads.d.ts +57 -0
- package/dist/state-reads.d.ts.map +1 -0
- package/dist/state-reads.js +104 -0
- package/dist/terragrunt/affected.d.ts.map +1 -1
- package/dist/terragrunt/affected.js +2 -1
- package/dist/used-sources.d.ts +54 -0
- package/dist/used-sources.d.ts.map +1 -0
- package/dist/used-sources.js +157 -0
- package/package.json +24 -4
- package/src/__fixtures__/ACCEPTANCE.md +69 -7
- package/src/__fixtures__/compose/apply-failed-in-app.jsonl +12 -0
- package/src/__fixtures__/compose/state-app.json +66 -0
- package/src/__fixtures__/compose/state-edge.json +40 -0
- package/src/__fixtures__/compose/state-net.json +58 -0
- package/src/__fixtures__/compose/three-app.plan.json +184 -0
- package/src/__fixtures__/compose/three-composed.plan.json +509 -0
- package/src/__fixtures__/compose/three-edge.plan.json +144 -0
- package/src/__fixtures__/compose/three-net.plan.json +174 -0
- package/src/__fixtures__/compose/unknown-composed.plan.json +228 -0
- package/src/__fixtures__/compose/working-after-apply.json +145 -0
- package/src/__fixtures__/config-blocks/app/main.tf +61 -0
- package/src/__fixtures__/config-blocks/app/modules/network/main.tf +4 -0
- package/src/__fixtures__/config-blocks/app-json/main.tf.json +39 -0
- package/src/__fixtures__/config-files/shadow/main.tf +3 -0
- package/src/__fixtures__/config-files/shadow/main.tofu +3 -0
- package/src/__fixtures__/config-files/shadow/other.tf +3 -0
- package/src/__fixtures__/config-files/tf/main.tf +11 -0
- package/src/__fixtures__/config-files/tf-json/main.tf.json +18 -0
- package/src/__fixtures__/config-files/tofu/main.tofu +11 -0
- package/src/__fixtures__/config-files/tofu-json/main.tofu.json +18 -0
- package/src/__fixtures__/config-files/tofu-only/main.tofu +3 -0
- package/src/__fixtures__/drift-record/README.md +16 -0
- package/src/__fixtures__/drift-record/live-plan-sections.json +27 -0
- package/src/__fixtures__/drift-rows/live-root-plan.json +70 -0
- package/src/__fixtures__/drift-rows/refresh-plan.json +61 -0
- package/src/__fixtures__/instance-forms/app/main.tf +110 -0
- package/src/__fixtures__/instance-forms/app/modules/svc/main.tf +3 -0
- package/src/__fixtures__/instance-vars/app/main.tf +52 -0
- package/src/__fixtures__/instance-vars/app/modules/app/main.tf +12 -0
- package/src/__fixtures__/instance-vars/app/modules/edge/main.tf +3 -0
- package/src/__fixtures__/instance-vars/app/terraform.tfvars +1 -0
- package/src/__fixtures__/live-module-keyed/live-graph.json +259 -0
- package/src/__fixtures__/live-module-keyed/live-plan.json +87 -0
- package/src/__fixtures__/live-module-keyed/root/estate.chdf.hcl +1 -0
- package/src/__fixtures__/live-module-keyed/root/main.tf +21 -0
- package/src/__fixtures__/live-module-keyed/root/modules/app/main.tf +17 -0
- package/src/__fixtures__/live-module-keyed/root/modules/edge/main.tf +7 -0
- package/src/__fixtures__/live-module-keyed/root/versions.tf +30 -0
- package/src/__fixtures__/live-module-keyed/show-state.json +146 -0
- package/src/__fixtures__/pr-loop/README.md +50 -0
- package/src/__fixtures__/pr-loop/chant.config.json +13 -0
- package/src/__fixtures__/pr-loop/estate.component.ts +10 -0
- package/src/__fixtures__/pr-loop/roots/a/main.tf +11 -0
- package/src/__fixtures__/pr-loop/roots/app/main.tf +19 -0
- package/src/__fixtures__/pr-loop/roots/b/main.tf +7 -0
- package/src/__fixtures__/pr-loop/roots/dns/main.tf +3 -0
- package/src/__fixtures__/pr-loop/roots/net/main.tf +7 -0
- package/src/__fixtures__/report/applied.show.json +51 -0
- package/src/__fixtures__/report/generate.ts +63 -0
- package/src/__fixtures__/report/plan.show.json +200 -0
- package/src/__fixtures__/report/tf-apply/change-set.json +170 -0
- package/src/__fixtures__/report/tf-apply/report.json +338 -0
- package/src/__fixtures__/report/tf-plan/change-set.json +170 -0
- package/src/__fixtures__/report/tf-plan/report.json +320 -0
- package/src/__fixtures__/three-cloud-azure.graph.json +290 -0
- package/src/__fixtures__/used-sources/root/extra.tofu +1 -0
- package/src/__fixtures__/used-sources/root/main.tf +31 -0
- package/src/__fixtures__/used-sources/root/modules/net/main.tf +14 -0
- package/src/__fixtures__/used-sources/root/modules/net/versions.tf.json +7 -0
- package/src/__fixtures__/used-sources/unit/terragrunt.hcl +3 -0
- package/src/applied-state.ts +52 -0
- package/src/backend-config.test.ts +105 -0
- package/src/backend-config.ts +133 -0
- package/src/behaviour/kinds.ts +4 -0
- package/src/behaviour/predict.ts +5 -2
- package/src/behaviour/reference-catalog.plugin.test.ts +65 -0
- package/src/behaviour/reference-catalog.ts +16 -5
- package/src/behaviour/request.ts +3 -2
- package/src/change-set-instances.test.ts +98 -0
- package/src/changed-roots.ts +14 -16
- package/src/ci-setup.ts +42 -0
- package/src/commands.ts +4 -3
- package/src/components/capability-plugin.test.ts +198 -19
- package/src/components/capability-plugin.ts +383 -35
- package/src/components/change-set-graph.e2e.test.ts +11 -5
- package/src/components/compose-group.test.ts +486 -0
- package/src/components/compose-group.ts +489 -0
- package/src/components/demo-composed-roots.e2e.test.ts +440 -0
- package/src/components/member-outputs.test.ts +76 -0
- package/src/components/placeholder-preview.test.ts +147 -0
- package/src/components/placeholder-preview.ts +257 -0
- package/src/components/pr-loop-compose.tofu.e2e.test.ts +363 -0
- package/src/components/pr-loop.tofu.test.ts +113 -57
- package/src/components/release-inputs.ts +69 -0
- package/src/components/trace-facts.test.ts +21 -0
- package/src/components/trace-facts.ts +27 -0
- package/src/compose/apply-events.test.ts +62 -0
- package/src/compose/apply-events.ts +100 -0
- package/src/compose/estate.choudoufu.e2e.test.ts +112 -0
- package/src/compose/estate.test.ts +421 -0
- package/src/compose/estate.ts +443 -0
- package/src/compose/hcl.ts +311 -0
- package/src/compose/module-address.ts +49 -0
- package/src/compose/root.test.ts +416 -0
- package/src/compose/root.ts +592 -0
- package/src/compose/run.test.ts +440 -0
- package/src/compose/run.tofu.test.ts +176 -0
- package/src/compose/run.ts +527 -0
- package/src/compose/slice.test.ts +184 -0
- package/src/compose/slice.ts +290 -0
- package/src/compose/state-address.ts +157 -0
- package/src/compose/state-merge.test.ts +131 -0
- package/src/compose/state-merge.ts +148 -0
- package/src/compose.test.ts +248 -0
- package/src/compose.ts +627 -0
- package/src/composites/terraform-apply-op.test.ts +9 -0
- package/src/composites/terraform-apply-op.ts +7 -3
- package/src/cone.ts +138 -0
- package/src/config-file-listers.test.ts +265 -0
- package/src/config.ts +44 -9
- package/src/cost-hold.ts +29 -0
- package/src/describe-resources.engine-facts.test.ts +411 -0
- package/src/describe-resources.live-module-keyed.test.ts +290 -0
- package/src/describe-resources.ts +416 -70
- package/src/drift-read.ts +284 -0
- package/src/drift-record.test.ts +223 -0
- package/src/drift-record.ts +455 -0
- package/src/drift-rows.test.ts +135 -0
- package/src/drift-rows.ts +137 -0
- package/src/exit-codes.test.ts +23 -0
- package/src/exit-codes.ts +59 -0
- package/src/hcl/config-blocks.test.ts +174 -0
- package/src/hcl/edges.ts +21 -1
- package/src/hcl/files.ts +234 -0
- package/src/hcl/instance-forms.test.ts +94 -0
- package/src/hcl/instances.test.ts +137 -0
- package/src/hcl/instances.ts +422 -0
- package/src/hcl/parse.test.ts +139 -1
- package/src/hcl/parse.ts +149 -16
- package/src/hcl/roots.ts +6 -0
- package/src/hcl/suppressions.ts +8 -2
- package/src/index.ts +79 -0
- package/src/lint/audit-catalog.ts +29 -3
- package/src/lint/audit-lineage.ts +14 -2
- package/src/lint/post-synth/fixtures/TF024/live-with-record-store/main.tf +18 -0
- package/src/lint/post-synth/fixtures/TF024/sidecar-with-backend/estate.chdf.hcl +1 -0
- package/src/lint/post-synth/fixtures/TF024/sidecar-with-backend/main.tf +10 -0
- package/src/lint/post-synth/fixtures/TF046/azurerm-no-resource-group/main.tf +15 -0
- package/src/lint/post-synth/fixtures/TF046/backend-config-only/main.tf +14 -0
- package/src/lint/post-synth/fixtures/TF046/derived-s3/backend.tf +7 -0
- package/src/lint/post-synth/fixtures/TF046/derived-s3/main.tf +9 -0
- package/src/lint/post-synth/fixtures/TF046/explicit-local/main.tf +16 -0
- package/src/lint/post-synth/fixtures/TF046/explicit-s3-beside-local-backend/main.tf +15 -0
- package/src/lint/post-synth/fixtures/TF046/implied-local/main.tf +9 -0
- package/src/lint/post-synth/fixtures/TF046/sidecar-implied/estate.chdf.hcl +1 -0
- package/src/lint/post-synth/fixtures/TF046/sidecar-implied/main.tf +5 -0
- package/src/lint/post-synth/fixtures/TF047/clear/main.tf +12 -0
- package/src/lint/post-synth/fixtures/TF047/refused/main.tf +12 -0
- package/src/lint/post-synth/fixtures/TF047/unknown-module/main.tf +12 -0
- package/src/lint/post-synth/fixtures/TF047/unknown-version/main.tf +12 -0
- package/src/lint/post-synth/index.ts +4 -0
- package/src/lint/post-synth/record-store.ts +122 -0
- package/src/lint/post-synth/tf001.ts +2 -2
- package/src/lint/post-synth/tf024.test.ts +26 -5
- package/src/lint/post-synth/tf024.ts +105 -22
- package/src/lint/post-synth/tf046.test.ts +137 -0
- package/src/lint/post-synth/tf046.ts +204 -0
- package/src/lint/post-synth/tf047.test.ts +56 -0
- package/src/lint/post-synth/tf047.ts +114 -0
- package/src/live-plan-reasons.ts +54 -0
- package/src/lock-rows.test.ts +70 -0
- package/src/lock-rows.ts +82 -0
- package/src/lock-timeout.ts +34 -0
- package/src/lsp/completions.test.ts +1 -1
- package/src/lsp/option-keys.ts +2 -0
- package/src/module-catalog/index.test.ts +93 -0
- package/src/module-catalog/index.ts +159 -0
- package/src/module-catalog/refresh-cli.ts +68 -0
- package/src/module-catalog/static-tier.ts +1659 -0
- package/src/module-catalog/types.ts +88 -0
- package/src/op/activities/choudoufu-plan-set-op.test.ts +111 -0
- package/src/op/activities/index.ts +9 -0
- package/src/op/activities/live-detect.ts +55 -26
- package/src/op/activities/pin-rollout.ts +3 -0
- package/src/op/activities/registry.test.ts +2 -1
- package/src/op/activities/terraform.test.ts +55 -0
- package/src/op/activities/terraform.ts +312 -27
- package/src/op/activity-contracts.test.ts +1 -0
- package/src/op/activity-contracts.ts +24 -1
- package/src/op/builders.ts +14 -0
- package/src/op-wave-step.test.ts +109 -0
- package/src/pin/__fixtures__/consumers/live/dev/main.tf +4 -0
- package/src/pin/__fixtures__/consumers/live/prod/main.tf +21 -0
- package/src/pin/__fixtures__/consumers/units/vpc/terragrunt.hcl +7 -0
- package/src/pin/bundle.test.ts +2 -0
- package/src/pin/consumers.test.ts +57 -0
- package/src/pin/consumers.ts +95 -0
- package/src/pin/edit.ts +30 -0
- package/src/pin/rollout.ts +16 -11
- package/src/plan-shape.test.ts +148 -0
- package/src/plan-shape.ts +128 -0
- package/src/plugin.ts +39 -19
- package/src/registry/__fixtures__/docs/documented/.terraform-docs.yml +3 -0
- package/src/registry/__fixtures__/docs/documented/README.md +1 -0
- package/src/registry/__fixtures__/docs/documented/main.tf +8 -0
- package/src/registry/__fixtures__/docs/json/main.tf.json +14 -0
- package/src/registry/__fixtures__/docs/vpc/README.md +3 -0
- package/src/registry/__fixtures__/docs/vpc/examples/complete/main.tf +5 -0
- package/src/registry/__fixtures__/docs/vpc/examples/simple/README.md +1 -0
- package/src/registry/__fixtures__/docs/vpc/examples/simple/main.tf +4 -0
- package/src/registry/__fixtures__/docs/vpc/main.tf +14 -0
- package/src/registry/__fixtures__/docs/vpc/outputs.tf +9 -0
- package/src/registry/__fixtures__/docs/vpc/variables.tf +16 -0
- package/src/registry/__fixtures__/proxied-root/main.tf +12 -0
- package/src/registry/address.ts +100 -0
- package/src/registry/catalog-read.ts +56 -0
- package/src/registry/deprecated.test.ts +139 -0
- package/src/registry/deprecated.ts +76 -0
- package/src/registry/docs.test.ts +140 -0
- package/src/registry/docs.ts +299 -0
- package/src/registry/edge/bundle.test.ts +50 -0
- package/src/registry/edge/edge.test.ts +176 -0
- package/src/registry/edge/edge.ts +211 -0
- package/src/registry/edge/index.ts +10 -0
- package/src/registry/edge/lambda-edge.ts +127 -0
- package/src/registry/edge/sigv4-web.ts +79 -0
- package/src/registry/edge/token-source.test.ts +258 -0
- package/src/registry/edge/token-source.ts +169 -0
- package/src/registry/edge/worker.ts +61 -0
- package/src/registry/handler.ts +89 -0
- package/src/registry/headers-file.ts +61 -0
- package/src/registry/index.ts +31 -0
- package/src/registry/mirror-paths.ts +10 -0
- package/src/registry/mirror.test.ts +198 -0
- package/src/registry/mirror.ts +395 -0
- package/src/registry/proxies.test.ts +153 -0
- package/src/registry/proxies.ts +132 -0
- package/src/registry/reader.ts +225 -0
- package/src/registry/registry.test.ts +262 -0
- package/src/registry/semver.ts +34 -0
- package/src/registry/serve.ts +82 -0
- package/src/registry/source-forms.test.ts +87 -0
- package/src/registry/source-forms.ts +216 -0
- package/src/registry/targets/artifactory.test.ts +248 -0
- package/src/registry/targets/artifactory.ts +257 -0
- package/src/registry/targets/fake-artifactory.ts +158 -0
- package/src/registry/targets/fake-gitlab.ts +161 -0
- package/src/registry/targets/fake-hcp.ts +233 -0
- package/src/registry/targets/fake-http.ts +97 -0
- package/src/registry/targets/fake-nexus.ts +152 -0
- package/src/registry/targets/gitlab.test.ts +208 -0
- package/src/registry/targets/gitlab.ts +223 -0
- package/src/registry/targets/hcp.test.ts +230 -0
- package/src/registry/targets/hcp.ts +360 -0
- package/src/registry/targets/hosted.ts +117 -0
- package/src/registry/targets/index.ts +17 -0
- package/src/registry/targets/module-target.ts +131 -0
- package/src/registry/targets/nexus.test.ts +220 -0
- package/src/registry/targets/nexus.ts +314 -0
- package/src/registry/targets/zip.test.ts +65 -0
- package/src/registry/targets/zip.ts +287 -0
- package/src/registry/writer.ts +155 -0
- package/src/report.test.ts +82 -1
- package/src/report.ts +31 -54
- package/src/signal-only-root.test.ts +72 -0
- package/src/skills/chant-terraform.md +1 -1
- package/src/state/azure-blob.ts +8 -0
- package/src/state/backend-verdict.test.ts +187 -0
- package/src/state/backend.test.ts +127 -0
- package/src/state/backend.ts +488 -0
- package/src/state/exec.ts +111 -0
- package/src/state/gcs.ts +7 -0
- package/src/state/index.ts +15 -0
- package/src/state/locks.test.ts +84 -0
- package/src/state/locks.ts +201 -0
- package/src/state/object-store.ts +7 -0
- package/src/state/root-states-floci.e2e.test.ts +66 -0
- package/src/state/root-states.test.ts +462 -0
- package/src/state/root-states.ts +309 -0
- package/src/state/s3.ts +21 -0
- package/src/state/store.ts +225 -0
- package/src/state-ops.test.ts +182 -0
- package/src/state-ops.ts +462 -0
- package/src/state-reads.ts +116 -0
- package/src/terragrunt/affected.ts +2 -1
- package/src/used-sources.test.ts +75 -0
- package/src/used-sources.ts +204 -0
- package/src/workspace-kinds.test.ts +4 -17
- package/workspace-kinds.json +1 -24
- package/dist/workspace-member.d.ts +0 -30
- package/dist/workspace-member.d.ts.map +0 -1
- package/dist/workspace-member.js +0 -91
- package/schemas/pulumi.schema.json +0 -34
- package/src/testdata/pulumi-member/Pulumi.yaml +0 -2
- package/src/testdata/pulumi-member/stack-export.json +0 -53
- package/src/testdata/pulumi-unexported/Pulumi.yaml +0 -2
- package/src/workspace-member.test.ts +0 -89
- package/src/workspace-member.ts +0 -126
package/dist/report.js
CHANGED
|
@@ -32,6 +32,10 @@ import { dirname, join } from "node:path";
|
|
|
32
32
|
import { changeSetDigest, composeChangeSet } from "@intentius/chant/change-set";
|
|
33
33
|
import { groupChangeSet } from "@intentius/chant/plan-summary";
|
|
34
34
|
import { plannerForBinary, terraformChangeSetPart } from "./change-set.js";
|
|
35
|
+
import { moduleChanges, stripModule, topModule } from "./compose/module-address.js";
|
|
36
|
+
import { stateResources } from "./applied-state.js";
|
|
37
|
+
import { isJsonConfigFile, jsonLiveBlocks, listConfigFiles } from "./hcl/files.js";
|
|
38
|
+
export { topModule } from "./compose/module-address.js";
|
|
35
39
|
const isObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
36
40
|
export const REPORT_SCHEMA = "terragucci.report/v1";
|
|
37
41
|
export const INDEX_SCHEMA = "terragucci.report-index/v1";
|
|
@@ -66,39 +70,14 @@ export function runFactsFromEnv(env, over = {}) {
|
|
|
66
70
|
// ---------------------------------------------------------------------------
|
|
67
71
|
// Slicing the composed plan into one plan per module.
|
|
68
72
|
// ---------------------------------------------------------------------------
|
|
69
|
-
const TOP_MODULE = /^module\.([^.[\]]+)(?:\[(?:"(?:[^"\\]|\\.)*"|\d+)\])?(?:\.|$)/;
|
|
70
|
-
/** The top-level module an address or module address is in, or undefined for the composed root's own. */
|
|
71
|
-
export function topModule(address) {
|
|
72
|
-
return TOP_MODULE.exec(address)?.[1];
|
|
73
|
-
}
|
|
74
|
-
/** `address` with its leading `module.<m>` (and index) taken off. */
|
|
75
|
-
function unprefix(address) {
|
|
76
|
-
const m = TOP_MODULE.exec(address);
|
|
77
|
-
return m ? address.slice(m[0].length) : address;
|
|
78
|
-
}
|
|
79
73
|
/** The plan's `resource_changes` that belong to module `m` (undefined: the root's own), each with the module taken off its address. */
|
|
80
74
|
export function slicePlan(plan, m) {
|
|
81
75
|
const p = isObject(plan) ? plan : {};
|
|
82
|
-
|
|
83
|
-
const mine = list.filter((r) => isObject(r) && topModule(String(r.module_address ?? r.address ?? "")) === m);
|
|
84
|
-
const resource_changes = mine.map((r) => {
|
|
85
|
-
const rc = r;
|
|
86
|
-
const out = { ...rc, address: unprefix(String(rc.address)) };
|
|
87
|
-
if (typeof rc.previous_address === "string")
|
|
88
|
-
out.previous_address = unprefix(rc.previous_address);
|
|
89
|
-
if (typeof rc.module_address === "string") {
|
|
90
|
-
const rest = unprefix(rc.module_address);
|
|
91
|
-
if (rest === "" || rest === rc.module_address)
|
|
92
|
-
delete out.module_address;
|
|
93
|
-
else
|
|
94
|
-
out.module_address = rest;
|
|
95
|
-
}
|
|
96
|
-
return out;
|
|
97
|
-
});
|
|
98
|
-
return { ...(p.errored === true ? { errored: true } : {}), resource_changes };
|
|
76
|
+
return { ...(p.errored === true ? { errored: true } : {}), resource_changes: moduleChanges(p.resource_changes, m) };
|
|
99
77
|
}
|
|
100
78
|
// ---------------------------------------------------------------------------
|
|
101
|
-
// Applied values: the state after the apply gives the values the plan left unknown
|
|
79
|
+
// Applied values: the state after the apply gives the values the plan left unknown,
|
|
80
|
+
// read through `./applied-state`, as the composed estate run reads them.
|
|
102
81
|
// ---------------------------------------------------------------------------
|
|
103
82
|
/** Whether a sensitivity map marks anything under it `true`. */
|
|
104
83
|
function marks(v) {
|
|
@@ -110,27 +89,6 @@ function marks(v) {
|
|
|
110
89
|
return Object.values(v).some(marks);
|
|
111
90
|
return false;
|
|
112
91
|
}
|
|
113
|
-
/** Every resource of a `show -json` state, by address. A deposed object is left out. */
|
|
114
|
-
function stateResources(show) {
|
|
115
|
-
const out = new Map();
|
|
116
|
-
const walk = (mod) => {
|
|
117
|
-
if (!isObject(mod))
|
|
118
|
-
return;
|
|
119
|
-
for (const r of Array.isArray(mod.resources) ? mod.resources : []) {
|
|
120
|
-
if (!isObject(r) || typeof r.address !== "string" || r.deposed_key !== undefined)
|
|
121
|
-
continue;
|
|
122
|
-
out.set(r.address, { values: isObject(r.values) ? r.values : {}, sensitive: isObject(r.sensitive_values) ? r.sensitive_values : {} });
|
|
123
|
-
}
|
|
124
|
-
for (const c of Array.isArray(mod.child_modules) ? mod.child_modules : [])
|
|
125
|
-
walk(c);
|
|
126
|
-
};
|
|
127
|
-
const root = isObject(show) ? show : {};
|
|
128
|
-
// A plan's `prior_state` is the state; a `show -json` of state has `values` itself.
|
|
129
|
-
const state = isObject(root.prior_state) ? root.prior_state : root;
|
|
130
|
-
const values = isObject(state.values) ? state.values : {};
|
|
131
|
-
walk(values.root_module);
|
|
132
|
-
return out;
|
|
133
|
-
}
|
|
134
92
|
/** Each unknown attribute given the value the state holds. One the state lacks stays unknown; a sensitive one stays `sensitive`. */
|
|
135
93
|
function resolveUnknowns(attributes, res) {
|
|
136
94
|
if (!res || !attributes.some((a) => a.unknown))
|
|
@@ -147,16 +105,29 @@ function resolveUnknowns(attributes, res) {
|
|
|
147
105
|
return { ...rest, ...(a.before !== undefined ? { before: a.before } : {}), after: value };
|
|
148
106
|
});
|
|
149
107
|
}
|
|
150
|
-
/**
|
|
151
|
-
|
|
108
|
+
/**
|
|
109
|
+
* The estate named by the first `live { estate = "..." }` block in a
|
|
110
|
+
* directory's configuration files, in filename order, or undefined. A `live`
|
|
111
|
+
* block takes effect only under choudoufu, so the files are the ones
|
|
112
|
+
* choudoufu reads (`.tf`, `.tf.json`, `.tofu` and `.tofu.json`, chant#4457)
|
|
113
|
+
* unless `binary` names another. A JSON-syntax file's `live` block is the one
|
|
114
|
+
* nested in its `terraform` block (chant#4504).
|
|
115
|
+
*/
|
|
116
|
+
export function estateOfDir(dir, binary = "choudoufu") {
|
|
152
117
|
let files;
|
|
153
118
|
try {
|
|
154
|
-
files =
|
|
119
|
+
files = listConfigFiles(dir, binary);
|
|
155
120
|
}
|
|
156
121
|
catch {
|
|
157
122
|
return undefined;
|
|
158
123
|
}
|
|
159
124
|
for (const f of files) {
|
|
125
|
+
if (isJsonConfigFile(f)) {
|
|
126
|
+
const estate = jsonLiveBlocks(readFileSync(join(dir, f), "utf8")).map((b) => b.estate).find((e) => typeof e === "string" && e !== "");
|
|
127
|
+
if (estate)
|
|
128
|
+
return estate;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
160
131
|
const text = readFileSync(join(dir, f), "utf8").replace(/^\s*(#|\/\/).*$/gm, "");
|
|
161
132
|
const live = /\blive\s*\{/.exec(text);
|
|
162
133
|
if (!live)
|
|
@@ -240,7 +211,7 @@ function unknownOutputsOf(plan, u) {
|
|
|
240
211
|
const [type, rname, attr] = data ? [parts[1], parts[2], parts[3]] : [parts[0], parts[1], parts[2]];
|
|
241
212
|
if (!type || !rname)
|
|
242
213
|
continue;
|
|
243
|
-
const unprefixed = (c) =>
|
|
214
|
+
const unprefixed = (c) => stripModule(String(c.address)).replace(/\[[^\]]*\]$/, "");
|
|
244
215
|
for (const c of changes) {
|
|
245
216
|
if (unprefixed(c) !== `${data ? "data." : ""}${type}.${rname}`)
|
|
246
217
|
continue;
|
|
@@ -604,7 +575,7 @@ export function terraformReport(input) {
|
|
|
604
575
|
...(run.pull_request_url ? { pull_request_url: run.pull_request_url } : {}),
|
|
605
576
|
...(run.job_url ? { job_url: run.job_url } : {}),
|
|
606
577
|
};
|
|
607
|
-
return { report, path, row };
|
|
578
|
+
return { report, changeSet: doc, path, row };
|
|
608
579
|
}
|
|
609
580
|
// ---------------------------------------------------------------------------
|
|
610
581
|
// The indexes.
|
|
@@ -632,7 +603,8 @@ export function addToIndex(existing, row) {
|
|
|
632
603
|
return { schema: INDEX_SCHEMA, reports: reports.slice(0, INDEX_ROWS) };
|
|
633
604
|
}
|
|
634
605
|
/**
|
|
635
|
-
* Write the run's `report.json` and
|
|
606
|
+
* Write the run's `report.json` and `change-set.json` (the document whose
|
|
607
|
+
* digest the report names, as `chant change-set` writes it), and add its row to the project's index
|
|
636
608
|
* (`<project>/index.json`, paths relative to it) and to the prefix's
|
|
637
609
|
* (`index.json`, paths starting at the project), the two places terragucci
|
|
638
610
|
* writes. Returns the keys written.
|
|
@@ -645,6 +617,7 @@ export async function writeReport(store, r) {
|
|
|
645
617
|
keys.push(key);
|
|
646
618
|
};
|
|
647
619
|
await put(`${project}/${r.path}/report.json`, r.report);
|
|
620
|
+
await put(`${project}/${r.path}/change-set.json`, r.changeSet);
|
|
648
621
|
for (const [key, path] of [[`${project}/index.json`, r.path], ["index.json", `${project}/${r.path}`]]) {
|
|
649
622
|
await put(key, addToIndex(await store.read(key), { ...r.row, path }));
|
|
650
623
|
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The record store a live root resolves to, read from its HCL the way
|
|
3
|
+
* choudoufu resolves it (INTENTIUS/choudoufu#2293, `internal/configs/live_derive.go`
|
|
4
|
+
* at choudoufu 74ffde956bfd97f44b8ad444424584e23321adc2). Shared by TF024,
|
|
5
|
+
* which names where records go, and TF046, which guards a CI root that ends
|
|
6
|
+
* up on the local store, so the two never disagree.
|
|
7
|
+
*
|
|
8
|
+
* The order is choudoufu's:
|
|
9
|
+
*
|
|
10
|
+
* 1. A `record_store "<kind>"` block in the `live` block or the
|
|
11
|
+
* `estate.chdf.hcl` sidecar always wins (`source: "record_store"`).
|
|
12
|
+
* 2. Otherwise the backend block choudoufu ignores under live gives the store,
|
|
13
|
+
* s3 to s3, gcs to gcs, azurerm to azurerm (`source: "backend"`). s3 and gcs
|
|
14
|
+
* need a literal `bucket`; azurerm needs literal `storage_account_name`,
|
|
15
|
+
* `container_name` and `resource_group_name`. A value passed only with
|
|
16
|
+
* `-backend-config` is not in the configuration, so choudoufu does not see
|
|
17
|
+
* it either.
|
|
18
|
+
* 3. Otherwise the store is local (`source: "implied"`), with `note` saying
|
|
19
|
+
* why a backend that was there was not used.
|
|
20
|
+
*
|
|
21
|
+
* This mirrors `record_store.source` and `record_store.note` in
|
|
22
|
+
* `choudoufu live-check -json`. A literal here is a string hcl2json hands back
|
|
23
|
+
* with no `${...}` in it, the same "not determined" reading the Terragrunt
|
|
24
|
+
* rules use (`./terragrunt.ts`).
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import type { BlockBody } from "../../hcl/parse";
|
|
28
|
+
import { backendTypes } from "./tf001";
|
|
29
|
+
import { isExpression } from "./terragrunt";
|
|
30
|
+
|
|
31
|
+
export type RecordStoreSource = "record_store" | "backend" | "implied";
|
|
32
|
+
|
|
33
|
+
export interface ResolvedRecordStore {
|
|
34
|
+
/** `local`, `s3`, `gcs`, `azurerm` or `kubernetes`, or whatever label a `record_store` block carries. */
|
|
35
|
+
kind: string;
|
|
36
|
+
source: RecordStoreSource;
|
|
37
|
+
/** The backend type the store came from, or was not derived from, when there is a backend block. */
|
|
38
|
+
backend?: string;
|
|
39
|
+
/** Why a backend block that was present did not give a store. Set only with `source: "implied"`. */
|
|
40
|
+
note?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Backend types choudoufu derives a record store from (INTENTIUS/choudoufu#2293). */
|
|
44
|
+
export const DERIVABLE_BACKENDS: ReadonlySet<string> = new Set(["s3", "gcs", "azurerm"]);
|
|
45
|
+
|
|
46
|
+
const asRecord = (v: unknown): BlockBody =>
|
|
47
|
+
typeof v === "object" && v !== null && !Array.isArray(v) ? (v as BlockBody) : {};
|
|
48
|
+
|
|
49
|
+
const firstBody = (v: unknown): BlockBody => asRecord(Array.isArray(v) ? v[0] : v);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The label of a `record_store "<kind>"` block in a live declaration body, or
|
|
53
|
+
* undefined when it declares none. hcl2json nests the label one level down,
|
|
54
|
+
* as it does for `backend` (see `backendTypes` in `./tf001.ts`).
|
|
55
|
+
*/
|
|
56
|
+
export function recordStoreKind(liveBody: BlockBody): string | undefined {
|
|
57
|
+
const raw = liveBody["record_store"];
|
|
58
|
+
if (raw === undefined || raw === null) return undefined;
|
|
59
|
+
if (typeof raw !== "object" || Array.isArray(raw)) return undefined;
|
|
60
|
+
const labels = Object.keys(raw as BlockBody);
|
|
61
|
+
return labels[0];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** A top-level backend argument as a literal string, or undefined when it is absent, empty or an expression. */
|
|
65
|
+
function literal(body: BlockBody, name: string): string | undefined {
|
|
66
|
+
const v = body[name];
|
|
67
|
+
if (typeof v !== "string" || v === "" || isExpression(v)) return undefined;
|
|
68
|
+
return v;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Why a backend block gives no record store, or undefined when it gives one.
|
|
73
|
+
* The wording follows choudoufu's `Live.RecordStoreNote`; an empty string
|
|
74
|
+
* means no store and nothing to say (a `local` backend).
|
|
75
|
+
*/
|
|
76
|
+
function derivationNote(type: string, body: BlockBody): string | undefined {
|
|
77
|
+
const where = `backend "${type}"`;
|
|
78
|
+
switch (type) {
|
|
79
|
+
case "s3":
|
|
80
|
+
case "gcs":
|
|
81
|
+
return literal(body, "bucket") === undefined
|
|
82
|
+
? `${where} sets no literal bucket; values passed with -backend-config are not read`
|
|
83
|
+
: undefined;
|
|
84
|
+
case "azurerm":
|
|
85
|
+
for (const name of ["storage_account_name", "container_name", "resource_group_name"]) {
|
|
86
|
+
if (literal(body, name) === undefined) return `${where} sets no literal ${name}, which the azurerm record store needs`;
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
case "local":
|
|
90
|
+
// choudoufu leaves no note here: a local backend and the local store agree.
|
|
91
|
+
return "";
|
|
92
|
+
default:
|
|
93
|
+
return `${where} has no record store counterpart`;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Resolve one live root's record store.
|
|
99
|
+
*
|
|
100
|
+
* `terraformBodies` are the bodies of every `terraform` block in the root (a
|
|
101
|
+
* backend can sit in a separate `backend.tf`). `liveBodies` are the root's
|
|
102
|
+
* live declarations: its `live` block and its sidecar, if any. choudoufu
|
|
103
|
+
* refuses a root that has both, so the first `record_store` found is the one.
|
|
104
|
+
*/
|
|
105
|
+
export function resolveRecordStore(
|
|
106
|
+
terraformBodies: readonly BlockBody[],
|
|
107
|
+
liveBodies: readonly BlockBody[],
|
|
108
|
+
): ResolvedRecordStore {
|
|
109
|
+
for (const live of liveBodies) {
|
|
110
|
+
const kind = recordStoreKind(live);
|
|
111
|
+
if (kind !== undefined) return { kind, source: "record_store" };
|
|
112
|
+
}
|
|
113
|
+
for (const tf of terraformBodies) {
|
|
114
|
+
const type = backendTypes(tf)[0];
|
|
115
|
+
if (type === undefined) continue;
|
|
116
|
+
const body = firstBody(asRecord(tf["backend"])[type]);
|
|
117
|
+
const note = derivationNote(type, body);
|
|
118
|
+
if (note === undefined) return { kind: type, source: "backend", backend: type };
|
|
119
|
+
return { kind: "local", source: "implied", backend: type, ...(note ? { note } : {}) };
|
|
120
|
+
}
|
|
121
|
+
return { kind: "local", source: "implied" };
|
|
122
|
+
}
|
package/dist/rules/tf001.ts
CHANGED
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
* module's `terraform` block is skipped here rather than read as if it were
|
|
31
31
|
* the root's.
|
|
32
32
|
*
|
|
33
|
-
* Does not fire on a live root (#2103): a `backend` block
|
|
34
|
-
*
|
|
33
|
+
* Does not fire on a live root (#2103): choudoufu ignores a `backend` block
|
|
34
|
+
* there (TF024 reports what it still does with it), and the fallback to local
|
|
35
35
|
* state this check warns about does not apply, since a live root keeps no
|
|
36
36
|
* state file at all, local or remote.
|
|
37
37
|
*/
|
package/dist/rules/tf024.ts
CHANGED
|
@@ -1,18 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TF024: a live root declares a `backend` or `cloud` block.
|
|
3
3
|
*
|
|
4
|
-
* choudoufu
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
4
|
+
* choudoufu used to refuse this pair at `init` (#1170). Since
|
|
5
|
+
* INTENTIUS/choudoufu#2213 it accepts both and ignores them with a warning
|
|
6
|
+
* (`internal/configs/module.go`, `ignoreStateStoreUnderLive`), because
|
|
7
|
+
* Terragrunt's `remote_state` and Atmos's `backend.tf.json` write a backend
|
|
8
|
+
* beside the live block on every run. What each block still does:
|
|
9
|
+
*
|
|
10
|
+
* - `backend "<type>"`: no state is read or written there, and
|
|
11
|
+
* `init -migrate-state` is refused. choudoufu keeps the block as
|
|
12
|
+
* `IgnoredBackend`: an apply publishes the estate's non-sensitive outputs
|
|
13
|
+
* to the location it names, so `terraform_remote_state` readers keep
|
|
14
|
+
* working, and when the live declaration has no `record_store` the record
|
|
15
|
+
* store is derived from the backend's bucket, s3 to s3, gcs to gcs, azurerm
|
|
16
|
+
* to azurerm (INTENTIUS/choudoufu#2293), when the bucket is a literal in
|
|
17
|
+
* the configuration. An explicit `record_store` always wins. The resolution
|
|
18
|
+
* is shared with TF046 in `./record-store.ts`.
|
|
19
|
+
* - `cloud {}`: dropped entirely. Nothing is published to it and no store is
|
|
20
|
+
* derived from it.
|
|
21
|
+
*
|
|
22
|
+
* So the backend case is reported as `info` (the block is doing a job the
|
|
23
|
+
* author may want, and is the derivation source the maintainer ruled for),
|
|
24
|
+
* and the cloud case as `warning` (the block does nothing at all). Neither
|
|
25
|
+
* fails a build at default severity. The guard against a CI root that ends
|
|
26
|
+
* up on the local store is TF046 (chant#4561).
|
|
27
|
+
*
|
|
28
|
+
* The live marker is the root's `mode`, stamped from either a `live` block or
|
|
29
|
+
* an `estate.chdf.hcl` sidecar (`hcl/parse.ts`), so a sidecar estate is
|
|
30
|
+
* covered. A `record_store` is read from whichever `Terraform::Live` entity
|
|
31
|
+
* carries it: the `live` block, or the sidecar's top level.
|
|
11
32
|
*
|
|
12
33
|
* TF001 is the equivalent check for a stock root (no remote backend is
|
|
13
|
-
* configured); it
|
|
14
|
-
*
|
|
15
|
-
* at most one of them.
|
|
34
|
+
* configured); it does not fire on a live root. A root is flagged by at most
|
|
35
|
+
* one of them.
|
|
16
36
|
*
|
|
17
37
|
* Scope: root modules only (#2112). A child module's backend or cloud block
|
|
18
38
|
* is TF015's finding, whatever binary the root runs.
|
|
@@ -26,17 +46,74 @@ import type {
|
|
|
26
46
|
PostSynthDiagnostic,
|
|
27
47
|
} from "@intentius/chant/lint/post-synth";
|
|
28
48
|
import { isResourceDeclarable } from "@intentius/chant/declarable";
|
|
29
|
-
import { TERRAFORM_TYPE, type BlockBody } from "../../hcl/parse";
|
|
30
|
-
import { hasBlock } from "./tf001";
|
|
49
|
+
import { LIVE_TYPE, TERRAFORM_TYPE, type BlockBody } from "../../hcl/parse";
|
|
50
|
+
import { backendTypes, hasBlock } from "./tf001";
|
|
31
51
|
import { isRootScoped } from "./scope";
|
|
52
|
+
import { DERIVABLE_BACKENDS, resolveRecordStore, type ResolvedRecordStore } from "./record-store";
|
|
53
|
+
|
|
54
|
+
export { DERIVABLE_BACKENDS } from "./record-store";
|
|
55
|
+
|
|
56
|
+
const bodyOf = (props: { body?: unknown }): BlockBody =>
|
|
57
|
+
(typeof props.body === "object" && props.body !== null ? props.body : {}) as BlockBody;
|
|
58
|
+
|
|
59
|
+
/** Each root's live declarations (its `live` block and its sidecar), keyed by root. */
|
|
60
|
+
export function liveBodiesByRoot(ctx: PostSynthContext): Map<string, BlockBody[]> {
|
|
61
|
+
const roots = new Map<string, BlockBody[]>();
|
|
62
|
+
for (const entity of ctx.entities.values()) {
|
|
63
|
+
if (entity.entityType !== LIVE_TYPE) continue;
|
|
64
|
+
if (!isResourceDeclarable(entity) || !isRootScoped(entity)) continue;
|
|
65
|
+
const props = entity.props as { root?: unknown; body?: unknown };
|
|
66
|
+
const root = typeof props.root === "string" ? props.root : "";
|
|
67
|
+
const list = roots.get(root) ?? [];
|
|
68
|
+
list.push(bodyOf(props));
|
|
69
|
+
roots.set(root, list);
|
|
70
|
+
}
|
|
71
|
+
return roots;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function backendMessage(root: string, type: string, store: ResolvedRecordStore): string {
|
|
75
|
+
const label = type ? `backend "${type}"` : "backend";
|
|
76
|
+
const head =
|
|
77
|
+
`Root module "${root}" runs choudoufu with a declared estate and also declares a ${label} block. ` +
|
|
78
|
+
"choudoufu ignores it with a warning: no state is read or written there and `init -migrate-state` " +
|
|
79
|
+
"is refused. An apply publishes the estate's non-sensitive outputs to the location it names, so " +
|
|
80
|
+
"terraform_remote_state readers keep working.";
|
|
81
|
+
if (store.source === "record_store") {
|
|
82
|
+
return `${head} Records go to the record_store the live declaration names, which wins over the backend.`;
|
|
83
|
+
}
|
|
84
|
+
if (store.source === "backend") {
|
|
85
|
+
return (
|
|
86
|
+
`${head} With no record_store declared, choudoufu derives the record store from this backend's ` +
|
|
87
|
+
"bucket and prints it on every init and plan (INTENTIUS/choudoufu#2293). Declare record_store to " +
|
|
88
|
+
"put records somewhere else."
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
const why = DERIVABLE_BACKENDS.has(type) && store.note
|
|
92
|
+
? `No record store is derived from it: ${store.note}.`
|
|
93
|
+
: `No record store is derived from a ${type ? `"${type}"` : "backend of unknown type"} (only s3, gcs and azurerm are).`;
|
|
94
|
+
return (
|
|
95
|
+
`${head} ${why} No record_store is declared, so records stay in the local ` +
|
|
96
|
+
"store. Declare record_store if more than one machine runs this root (TF046 guards this in CI)."
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function cloudMessage(root: string): string {
|
|
101
|
+
return (
|
|
102
|
+
`Root module "${root}" runs choudoufu with a declared estate and also declares a cloud block. ` +
|
|
103
|
+
"choudoufu ignores it entirely: no state is read or written there, no outputs are published to it, " +
|
|
104
|
+
"and no record store is derived from it. Remove it, or use a backend block if " +
|
|
105
|
+
"terraform_remote_state readers need this estate's outputs."
|
|
106
|
+
);
|
|
107
|
+
}
|
|
32
108
|
|
|
33
109
|
export const tf024: PostSynthCheck = {
|
|
34
110
|
id: "TF024",
|
|
35
|
-
description: "Live root declares a backend or cloud block, which choudoufu
|
|
111
|
+
description: "Live root declares a backend or cloud block, which choudoufu ignores",
|
|
36
112
|
|
|
37
113
|
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
38
114
|
const diagnostics: PostSynthDiagnostic[] = [];
|
|
39
115
|
const flagged = new Set<string>();
|
|
116
|
+
let liveBodies: Map<string, BlockBody[]> | undefined;
|
|
40
117
|
|
|
41
118
|
for (const [name, entity] of ctx.entities) {
|
|
42
119
|
if (entity.entityType !== TERRAFORM_TYPE) continue;
|
|
@@ -46,20 +123,26 @@ export const tf024: PostSynthCheck = {
|
|
|
46
123
|
const root = typeof props.root === "string" ? props.root : "";
|
|
47
124
|
if (flagged.has(root)) continue;
|
|
48
125
|
|
|
49
|
-
const body = (
|
|
50
|
-
const hasBackend = hasBlock(body, "backend");
|
|
126
|
+
const body = bodyOf(props);
|
|
51
127
|
const hasCloud = hasBlock(body, "cloud");
|
|
128
|
+
const hasBackend = hasBlock(body, "backend");
|
|
52
129
|
if (!hasBackend && !hasCloud) continue;
|
|
53
|
-
|
|
54
130
|
flagged.add(root);
|
|
55
|
-
|
|
131
|
+
|
|
132
|
+
// A cloud block does nothing at all, so it is the one worth a warning
|
|
133
|
+
// when a (malformed) root somehow carries both.
|
|
134
|
+
if (hasCloud) {
|
|
135
|
+
diagnostics.push({ checkId: "TF024", severity: "warning", message: cloudMessage(root), entity: name, lexicon: "terraform" });
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
liveBodies ??= liveBodiesByRoot(ctx);
|
|
140
|
+
const type = backendTypes(body)[0] ?? "";
|
|
141
|
+
const store = resolveRecordStore([body], liveBodies.get(root) ?? []);
|
|
56
142
|
diagnostics.push({
|
|
57
143
|
checkId: "TF024",
|
|
58
|
-
severity: "
|
|
59
|
-
message:
|
|
60
|
-
`Root module "${root}" runs choudoufu with a declared estate and also declares a ` +
|
|
61
|
-
`${blockKind} block. choudoufu refuses this at init: "Both a ${blockKind} and a live ` +
|
|
62
|
-
`configuration are present." Remove the ${blockKind} block; a live root keeps no state to store.`,
|
|
144
|
+
severity: "info",
|
|
145
|
+
message: backendMessage(root, type, store),
|
|
63
146
|
entity: name,
|
|
64
147
|
lexicon: "terraform",
|
|
65
148
|
});
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF046: a live root's record store is local, and the lint runs in CI.
|
|
3
|
+
*
|
|
4
|
+
* A live root keeps its records (what binds a record-backed resource to the
|
|
5
|
+
* estate) in a record store. With nothing to say otherwise that store is a
|
|
6
|
+
* directory beside the module (choudoufu `live/STORAGE.md`), and on a CI
|
|
7
|
+
* runner the directory is empty on every run: an ordinary resource still binds
|
|
8
|
+
* by its marker, but a record-backed one is proposed for create again.
|
|
9
|
+
*
|
|
10
|
+
* "Local" is what choudoufu resolves, read here through `./record-store.ts`,
|
|
11
|
+
* the same resolution TF024 names in its message (INTENTIUS/choudoufu#2293):
|
|
12
|
+
*
|
|
13
|
+
* - an explicit `record_store "local"` fires;
|
|
14
|
+
* - an explicit `record_store` of any other kind is quiet, whatever backend
|
|
15
|
+
* sits beside it;
|
|
16
|
+
* - with no `record_store`, an ignored `backend "s3"`, `"gcs"` or `"azurerm"`
|
|
17
|
+
* block that names its bucket as a literal gives a derived store and is
|
|
18
|
+
* quiet;
|
|
19
|
+
* - with no `record_store` and no backend, or a backend with no counterpart
|
|
20
|
+
* (`local`, `consul`, `http`), or one whose bucket comes only from
|
|
21
|
+
* `-backend-config`, the store is implied local and the rule fires, naming
|
|
22
|
+
* the reason choudoufu would print.
|
|
23
|
+
*
|
|
24
|
+
* A root that is also a Terragrunt unit with a `remote_state`, a `generate`
|
|
25
|
+
* block that writes a backend, or an `include` of a parent config gets its
|
|
26
|
+
* backend file at run time, so what chant reads is not what choudoufu sees.
|
|
27
|
+
* The rule stays quiet there rather than guess; TF044 still reports a
|
|
28
|
+
* Terragrunt `backend = "local"`.
|
|
29
|
+
*
|
|
30
|
+
* CI is read from the environment chant runs in: `CI` set to anything but
|
|
31
|
+
* `false` or `0`, or `GITHUB_ACTIONS`, `GITLAB_CI`, `FORGEJO_ACTIONS`,
|
|
32
|
+
* `GITEA_ACTIONS` or `TF_BUILD` (Azure Pipelines) set to `true`. Outside CI the
|
|
33
|
+
* local store is a reasonable choice for one person or a demo, and the rule
|
|
34
|
+
* says nothing.
|
|
35
|
+
*
|
|
36
|
+
* Opt-outs, for emulator and test lanes that use the local store on purpose:
|
|
37
|
+
* `CHANT_ALLOW_LOCAL_RECORD_STORE=1` in the lane's environment, or
|
|
38
|
+
* `# chant-ignore-block: TF046` above the `terraform` block holding `live`
|
|
39
|
+
* for one root, or
|
|
40
|
+
* `lint.rules: { TF046: "off" }` for a whole project.
|
|
41
|
+
*
|
|
42
|
+
* The rule is `report-only` in the audit catalog, so it is off under the
|
|
43
|
+
* `recommended` preset: terragucci switches it on and holds the product
|
|
44
|
+
* wording (chant#4561 placement).
|
|
45
|
+
*
|
|
46
|
+
* One diagnostic per root. It fires from the `terraform` block that holds the
|
|
47
|
+
* `live` block, since suppression comments anchor on top-level blocks only,
|
|
48
|
+
* and from the sidecar's `Terraform::Live` entity for a sidecar estate, where
|
|
49
|
+
* `# chant-ignore-file: TF046` in `estate.chdf.hcl` suppresses it.
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
import type {
|
|
53
|
+
PostSynthCheck,
|
|
54
|
+
PostSynthContext,
|
|
55
|
+
PostSynthDiagnostic,
|
|
56
|
+
} from "@intentius/chant/lint/post-synth";
|
|
57
|
+
import { isResourceDeclarable, type ResourceDeclarable } from "@intentius/chant/declarable";
|
|
58
|
+
import {
|
|
59
|
+
LIVE_TYPE,
|
|
60
|
+
TERRAFORM_TYPE,
|
|
61
|
+
TERRAGRUNT_CONFIG_TYPE,
|
|
62
|
+
TERRAGRUNT_GENERATE_TYPE,
|
|
63
|
+
TERRAGRUNT_REMOTE_STATE_TYPE,
|
|
64
|
+
type BlockBody,
|
|
65
|
+
} from "../../hcl/parse";
|
|
66
|
+
import { isRootScoped } from "./scope";
|
|
67
|
+
import { resolveRecordStore, type ResolvedRecordStore } from "./record-store";
|
|
68
|
+
|
|
69
|
+
/** The environment variable an emulator or test lane sets to keep the local store without a finding. */
|
|
70
|
+
export const ALLOW_LOCAL_RECORD_STORE_ENV = "CHANT_ALLOW_LOCAL_RECORD_STORE";
|
|
71
|
+
|
|
72
|
+
type Env = Readonly<Record<string, string | undefined>>;
|
|
73
|
+
|
|
74
|
+
const truthy = (v: string | undefined): boolean => v !== undefined && v !== "" && v !== "0" && v.toLowerCase() !== "false";
|
|
75
|
+
|
|
76
|
+
/** Whether the environment is a CI job. */
|
|
77
|
+
export function isCiEnvironment(env: Env): boolean {
|
|
78
|
+
if (truthy(env.CI)) return true;
|
|
79
|
+
for (const name of ["GITHUB_ACTIONS", "GITLAB_CI", "FORGEJO_ACTIONS", "GITEA_ACTIONS", "TF_BUILD"]) {
|
|
80
|
+
if (env[name]?.toLowerCase() === "true") return true;
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const bodyOf = (entity: ResourceDeclarable): BlockBody => {
|
|
86
|
+
const props = entity.props as { body?: unknown };
|
|
87
|
+
return (typeof props.body === "object" && props.body !== null ? props.body : {}) as BlockBody;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const rootOf = (entity: ResourceDeclarable): string => {
|
|
91
|
+
const root = (entity.props as { root?: unknown }).root;
|
|
92
|
+
return typeof root === "string" ? root : "";
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
interface LiveRoot {
|
|
96
|
+
/** Entity key the finding anchors on: the live declaration's entity, until a `terraform` block holding it is seen. */
|
|
97
|
+
anchor: string;
|
|
98
|
+
liveBodies: BlockBody[];
|
|
99
|
+
terraformBodies: BlockBody[];
|
|
100
|
+
/** A Terragrunt unit whose backend file is written at run time. */
|
|
101
|
+
runtimeBackend: boolean;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Whether a Terragrunt block in this root writes a backend file at run time. */
|
|
105
|
+
function writesRuntimeBackend(entity: ResourceDeclarable): boolean {
|
|
106
|
+
const body = bodyOf(entity);
|
|
107
|
+
switch (entity.entityType) {
|
|
108
|
+
case TERRAGRUNT_REMOTE_STATE_TYPE:
|
|
109
|
+
return body.backend !== "local";
|
|
110
|
+
case TERRAGRUNT_GENERATE_TYPE: {
|
|
111
|
+
const contents = typeof body.contents === "string" ? body.contents : "";
|
|
112
|
+
return /\bbackend\s+"(?!local")/.test(contents);
|
|
113
|
+
}
|
|
114
|
+
case TERRAGRUNT_CONFIG_TYPE:
|
|
115
|
+
return Array.isArray(body.includes) && body.includes.length > 0;
|
|
116
|
+
default:
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function liveRoots(ctx: PostSynthContext): Map<string, LiveRoot> {
|
|
122
|
+
const roots = new Map<string, LiveRoot>();
|
|
123
|
+
const terraform = new Map<string, BlockBody[]>();
|
|
124
|
+
/** Per root, the key of the first `terraform` block that holds a `live` block. */
|
|
125
|
+
const holder = new Map<string, string>();
|
|
126
|
+
const runtime = new Set<string>();
|
|
127
|
+
|
|
128
|
+
for (const [key, entity] of ctx.entities) {
|
|
129
|
+
if (!isResourceDeclarable(entity) || !isRootScoped(entity)) continue;
|
|
130
|
+
const root = rootOf(entity);
|
|
131
|
+
if (entity.entityType === TERRAFORM_TYPE) {
|
|
132
|
+
const list = terraform.get(root) ?? [];
|
|
133
|
+
const body = bodyOf(entity);
|
|
134
|
+
list.push(body);
|
|
135
|
+
terraform.set(root, list);
|
|
136
|
+
if (body.live !== undefined && !holder.has(root)) holder.set(root, key);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (entity.entityType === LIVE_TYPE) {
|
|
140
|
+
if ((entity.props as { mode?: unknown }).mode !== "live") continue;
|
|
141
|
+
const found = roots.get(root);
|
|
142
|
+
if (found) found.liveBodies.push(bodyOf(entity));
|
|
143
|
+
else roots.set(root, { anchor: key, liveBodies: [bodyOf(entity)], terraformBodies: [], runtimeBackend: false });
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (writesRuntimeBackend(entity)) runtime.add(root);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
for (const [root, live] of roots) {
|
|
150
|
+
live.terraformBodies = terraform.get(root) ?? [];
|
|
151
|
+
live.anchor = holder.get(root) ?? live.anchor;
|
|
152
|
+
live.runtimeBackend = runtime.has(root);
|
|
153
|
+
}
|
|
154
|
+
return roots;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function message(root: string, store: ResolvedRecordStore): string {
|
|
158
|
+
const why =
|
|
159
|
+
store.source === "record_store"
|
|
160
|
+
? 'Its live declaration names record_store "local", a directory beside the module.'
|
|
161
|
+
: store.note
|
|
162
|
+
? `It declares no record_store, and no store is derived from its backend: ${store.note}.`
|
|
163
|
+
: store.backend
|
|
164
|
+
? `It declares no record_store, and its backend "${store.backend}" block keeps state on local disk, so choudoufu uses the local store.`
|
|
165
|
+
: "It declares no record_store and has no backend block to derive one from, so choudoufu uses the local store.";
|
|
166
|
+
return (
|
|
167
|
+
`Live root "${root}" keeps its records in the local store, and this is a CI run. ${why} ` +
|
|
168
|
+
"A CI runner starts with an empty directory every run, so each record-backed resource is proposed " +
|
|
169
|
+
"for create again. Keep or add a backend \"s3\", \"gcs\" or \"azurerm\" block with a literal bucket " +
|
|
170
|
+
"(choudoufu derives the store from it), or declare a record_store in the live block or " +
|
|
171
|
+
`estate.chdf.hcl. A lane that uses the local store on purpose sets ${ALLOW_LOCAL_RECORD_STORE_ENV}=1.`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* TF046 against an explicit environment. `tf046.check` passes `process.env`;
|
|
177
|
+
* tests pass their own.
|
|
178
|
+
*/
|
|
179
|
+
export function checkLocalRecordStore(ctx: PostSynthContext, env: Env): PostSynthDiagnostic[] {
|
|
180
|
+
if (!isCiEnvironment(env) || truthy(env[ALLOW_LOCAL_RECORD_STORE_ENV])) return [];
|
|
181
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
182
|
+
for (const [root, live] of liveRoots(ctx)) {
|
|
183
|
+
if (live.runtimeBackend) continue;
|
|
184
|
+
const store = resolveRecordStore(live.terraformBodies, live.liveBodies);
|
|
185
|
+
if (store.kind !== "local") continue;
|
|
186
|
+
diagnostics.push({
|
|
187
|
+
checkId: "TF046",
|
|
188
|
+
severity: "error",
|
|
189
|
+
message: message(root, store),
|
|
190
|
+
entity: live.anchor,
|
|
191
|
+
lexicon: "terraform",
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
return diagnostics;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export const tf046: PostSynthCheck = {
|
|
198
|
+
id: "TF046",
|
|
199
|
+
description: "Live root resolves to the local record store in a CI run",
|
|
200
|
+
|
|
201
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
202
|
+
return checkLocalRecordStore(ctx, process.env);
|
|
203
|
+
},
|
|
204
|
+
};
|