@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
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What applied, read from `show -json` of a plan made after the apply: its
|
|
3
|
+
* `prior_state` is the state the apply left (chant#4445). choudoufu refuses a
|
|
4
|
+
* bare `show` under a `live` block, since there is no state file to show, so
|
|
5
|
+
* a post-apply plan is the one place its applied values can be read.
|
|
6
|
+
*
|
|
7
|
+
* `chant terraform report` (`./report.ts`) and the composed estate run
|
|
8
|
+
* (`./compose/estate.ts`) both read through {@link priorStateValues}.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
type Json = Record<string, unknown>;
|
|
12
|
+
|
|
13
|
+
const isObject = (v: unknown): v is Json => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The `values` of the state a `show -json` holds: a plan's `prior_state`, or
|
|
17
|
+
* a state's own `values` when it is a `show -json` of state. Undefined when
|
|
18
|
+
* it holds neither.
|
|
19
|
+
*/
|
|
20
|
+
export function priorStateValues(show: unknown): Json | undefined {
|
|
21
|
+
const root = isObject(show) ? show : {};
|
|
22
|
+
const state = isObject(root.prior_state) ? root.prior_state : root;
|
|
23
|
+
return isObject(state.values) ? state.values : undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** One resource of a state: its values and its sensitivity map. */
|
|
27
|
+
export interface StateResourceValues {
|
|
28
|
+
values: Json;
|
|
29
|
+
sensitive: Json;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Every resource of the state a `show -json` holds, by address. A deposed object is left out. */
|
|
33
|
+
export function stateResources(show: unknown): Map<string, StateResourceValues> {
|
|
34
|
+
const out = new Map<string, StateResourceValues>();
|
|
35
|
+
const walk = (mod: unknown): void => {
|
|
36
|
+
if (!isObject(mod)) return;
|
|
37
|
+
for (const r of Array.isArray(mod.resources) ? mod.resources : []) {
|
|
38
|
+
if (!isObject(r) || typeof r.address !== "string" || r.deposed_key !== undefined) continue;
|
|
39
|
+
out.set(r.address, { values: isObject(r.values) ? r.values : {}, sensitive: isObject(r.sensitive_values) ? r.sensitive_values : {} });
|
|
40
|
+
}
|
|
41
|
+
for (const c of Array.isArray(mod.child_modules) ? mod.child_modules : []) walk(c);
|
|
42
|
+
};
|
|
43
|
+
walk(priorStateValues(show)?.root_module);
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The outputs of the state a `show -json` holds, by name, each its value. */
|
|
48
|
+
export function stateOutputs(show: unknown): Record<string, unknown> {
|
|
49
|
+
const outputs = priorStateValues(show)?.outputs;
|
|
50
|
+
if (!isObject(outputs)) return {};
|
|
51
|
+
return Object.fromEntries(Object.entries(outputs).map(([k, v]) => [k, isObject(v) && "value" in v ? v.value : v]));
|
|
52
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A root can name backend-config files (chant#4459): the config key, the
|
|
3
|
+
* flags init gets, where a relative path resolves, and the keys the
|
|
4
|
+
* composition reads from a file before init.
|
|
5
|
+
*/
|
|
6
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { afterAll, describe, expect, test } from "vitest";
|
|
10
|
+
import { terraformRootSchema } from "./config";
|
|
11
|
+
import { ROOT_ENTRY_KEYS } from "./lsp/option-keys";
|
|
12
|
+
import { backendConfigArgs, backendConfigFileKeys, backendConfigFilePath, readBackendConfigFile } from "./backend-config";
|
|
13
|
+
import type { DriverComponent } from "@intentius/chant/components/driver";
|
|
14
|
+
import { compositionFor } from "./compose";
|
|
15
|
+
import { rootState } from "./compose/hcl";
|
|
16
|
+
import { declaredBackend, lockRefusal, readBackend } from "./state/backend";
|
|
17
|
+
|
|
18
|
+
const made: string[] = [];
|
|
19
|
+
afterAll(() => {
|
|
20
|
+
for (const d of made) rmSync(d, { recursive: true, force: true });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function tree(files: Record<string, string>): string {
|
|
24
|
+
const dir = mkdtempSync(join(tmpdir(), "chant-4459-"));
|
|
25
|
+
made.push(dir);
|
|
26
|
+
for (const [path, text] of Object.entries(files)) {
|
|
27
|
+
mkdirSync(dirname(join(dir, path)), { recursive: true });
|
|
28
|
+
writeFileSync(join(dir, path), text);
|
|
29
|
+
}
|
|
30
|
+
return dir;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const S3_NO_LOCK = 'terraform {\n backend "s3" {\n bucket = "b"\n }\n}\n';
|
|
34
|
+
|
|
35
|
+
describe("terraform.roots.<name>.backendConfigFiles", () => {
|
|
36
|
+
test("the config schema takes a list of paths beside backendConfig, and refuses anything else", () => {
|
|
37
|
+
expect(terraformRootSchema.safeParse({ dir: ".", backendConfigFiles: ["backend.hcl"], backendConfig: { key: "k" } }).success).toBe(true);
|
|
38
|
+
expect(terraformRootSchema.safeParse({ dir: ".", backendConfigFiles: "backend.hcl" }).success).toBe(false);
|
|
39
|
+
expect(terraformRootSchema.safeParse({ dir: ".", backendConfigFiles: [1] }).success).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("the LSP offers the key on a root entry", () => {
|
|
43
|
+
expect(ROOT_ENTRY_KEYS.map((k) => k.key)).toContain("backendConfigFiles");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("init's flags: files in order, then the pairs", () => {
|
|
47
|
+
expect(backendConfigArgs({ backendConfigFiles: ["a.hcl", "b.hcl"], backendConfig: { key: "k" } })).toEqual(["-backend-config=a.hcl", "-backend-config=b.hcl", "-backend-config=key=k"]);
|
|
48
|
+
expect(backendConfigArgs({})).toEqual([]);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("a backend-config file's keys", () => {
|
|
53
|
+
test("a relative path resolves against the root's directory, as varFiles does; an absolute one stays", () => {
|
|
54
|
+
expect(backendConfigFilePath("/repo/roots/app", "backend.hcl")).toBe("/repo/roots/app/backend.hcl");
|
|
55
|
+
expect(backendConfigFilePath("/repo/roots/app", "../shared/backend.hcl")).toBe("/repo/roots/shared/backend.hcl");
|
|
56
|
+
expect(backendConfigFilePath("/repo/roots/app", "/etc/backend.hcl")).toBe("/etc/backend.hcl");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("a file's key = value lines, typed, comments and interpolations left out", () => {
|
|
60
|
+
const dir = tree({ "backend.hcl": '# shared\nbucket = "acme-state" // the bucket\nuse_lockfile = true\nmax_retries = 5\nkey = "${var.no}"\n' });
|
|
61
|
+
expect(readBackendConfigFile(join(dir, "backend.hcl"))).toEqual({ bucket: "acme-state", use_lockfile: true, max_retries: 5 });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("later files over earlier ones, read from the root's directory, and a missing file named", () => {
|
|
65
|
+
const dir = tree({ "roots/app/one.hcl": 'bucket = "one"\nkey = "k"\n', "roots/shared/two.hcl": 'bucket = "two"\n' });
|
|
66
|
+
expect(backendConfigFileKeys(join(dir, "roots/app"), ["one.hcl", "../shared/two.hcl", "gone.hcl"])).toEqual({ config: { bucket: "two", key: "k" }, unread: ["gone.hcl"] });
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("the composition reads the file's keys before init (ruled: yes)", () => {
|
|
71
|
+
test("an s3 root whose use_lockfile = true is only in the file is lockable", () => {
|
|
72
|
+
const dir = tree({ "main.tf": S3_NO_LOCK, "backend.hcl": 'key = "app.tfstate"\nuse_lockfile = true\n' });
|
|
73
|
+
expect(lockRefusal(readBackend(dir, { recorded: null }))).toMatchObject({ refused: "no-lockfile" });
|
|
74
|
+
expect(lockRefusal(readBackend(dir, { recorded: null, backendConfigFiles: ["backend.hcl"] }))).toBeUndefined();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("a pair overrides the file, and the file overrides the code", () => {
|
|
78
|
+
const dir = tree({ "main.tf": 'terraform {\n backend "s3" {\n bucket = "code"\n key = "code"\n }\n}\n', "backend.hcl": 'bucket = "file"\nkey = "file"\n' });
|
|
79
|
+
expect(declaredBackend(dir, { backendConfigFiles: ["backend.hcl"], backendConfig: { key: "pair" } })).toEqual({ type: "s3", config: { bucket: "file", key: "pair" } });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("a -backend-config file in TF_CLI_ARGS_init is read too", () => {
|
|
83
|
+
const dir = tree({ "main.tf": S3_NO_LOCK, "ci.hcl": "use_lockfile = true\n" });
|
|
84
|
+
expect(lockRefusal(readBackend(dir, { recorded: null, env: { TF_CLI_ARGS_init: "-backend-config=ci.hcl" } }))).toBeUndefined();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("rootState finds the state's address from a key only the file gives", () => {
|
|
88
|
+
const dir = tree({ "main.tf": S3_NO_LOCK });
|
|
89
|
+
expect(rootState(dir, "app", { env: {}, recorded: null, backendConfig: { key: "from-file.tfstate" } }).own).toMatchObject({ key: "from-file.tfstate" });
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("a composition lines a read up with a member whose state key is only in its file", () => {
|
|
93
|
+
const tf = (name: string): DriverComponent => ({ name, dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "terraform-apply", root: name }] }] });
|
|
94
|
+
const reader = 'data "terraform_remote_state" "a" {\n backend = "s3"\n config = {\n bucket = "b"\n key = "a.tfstate"\n }\n}\n\nresource "terraform_data" "x" {\n input = data.terraform_remote_state.a.outputs.id\n}\n';
|
|
95
|
+
const lockedS3 = (key?: string): string => `terraform {\n backend "s3" {\n bucket = "b"\n${key ? ` key = "${key}"\n` : ""} use_lockfile = true\n }\n}\n`;
|
|
96
|
+
const dir = tree({ "roots/a/main.tf": lockedS3(), "roots/a/backend.hcl": 'key = "a.tfstate"\n', "roots/b/main.tf": lockedS3("b.tfstate") + reader });
|
|
97
|
+
const c = compositionFor({
|
|
98
|
+
projectRoot: dir,
|
|
99
|
+
env: {},
|
|
100
|
+
components: [tf("a"), tf("b")],
|
|
101
|
+
terraform: { roots: { a: { dir: "roots/a", backendConfigFiles: ["backend.hcl"] }, b: { dir: "roots/b" } }, compose: { enabled: true } },
|
|
102
|
+
});
|
|
103
|
+
expect(c.reads.get("b")).toEqual([{ via: "remote-state", upstream: "a", name: "a" }]);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A root's `-backend-config` (chant#4459): the files it names in
|
|
3
|
+
* `terraform.roots.<name>.backendConfigFiles` and the pairs in
|
|
4
|
+
* `backendConfig`, as the flags every init the lexicon runs passes, and the
|
|
5
|
+
* keys a file holds, as the readers that find a root's state before init see
|
|
6
|
+
* them.
|
|
7
|
+
*
|
|
8
|
+
* Files go first and pairs after, because init applies later flags over
|
|
9
|
+
* earlier ones and a pair is the more specific of the two. A file's path is
|
|
10
|
+
* passed as written: init runs in the root's directory, so a relative path
|
|
11
|
+
* resolves against it, as `varFiles` does. A reader of the file resolves it
|
|
12
|
+
* the same way.
|
|
13
|
+
*
|
|
14
|
+
* Plain node: `op/activities/terraform.ts` imports this, and it stays out of
|
|
15
|
+
* the HCL parser's dependency graph.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { readFileSync } from "node:fs";
|
|
19
|
+
import { isAbsolute, resolve } from "node:path";
|
|
20
|
+
|
|
21
|
+
/** The two keys of a root that say what init's `-backend-config` flags are. */
|
|
22
|
+
export interface BackendConfigFlags {
|
|
23
|
+
backendConfigFiles?: readonly string[];
|
|
24
|
+
backendConfig?: Readonly<Record<string, string>>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The `-backend-config` values in the order init takes them: each file's path, then each `key=value` pair. Unquoted, one per flag. */
|
|
28
|
+
export function backendConfigValues(root: BackendConfigFlags): string[] {
|
|
29
|
+
return [...(root.backendConfigFiles ?? []), ...Object.entries(root.backendConfig ?? {}).map(([k, v]) => `${k}=${v}`)];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** {@link backendConfigValues} as argv: `-backend-config=<value>` each. */
|
|
33
|
+
export function backendConfigArgs(root: BackendConfigFlags): string[] {
|
|
34
|
+
return backendConfigValues(root).map((v) => `-backend-config=${v}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A backend-config file's path as init in `dir` reads it: an absolute path as it is, a relative one against `dir`. */
|
|
38
|
+
export function backendConfigFilePath(dir: string, file: string): string {
|
|
39
|
+
return isAbsolute(file) ? file : resolve(dir, file);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The text of `body` with everything nested deeper than `level` braces
|
|
44
|
+
* blanked, newlines kept, so only the attributes at that level are left to
|
|
45
|
+
* read. Strings are read through, so a brace in one does not count.
|
|
46
|
+
*/
|
|
47
|
+
export function blankBelow(body: string, level: number): string {
|
|
48
|
+
const out = body.split("");
|
|
49
|
+
let depth = 0;
|
|
50
|
+
let inString = false;
|
|
51
|
+
for (let i = 0; i < body.length; i++) {
|
|
52
|
+
const c = body[i]!;
|
|
53
|
+
if (depth > level && c !== "\n") out[i] = " ";
|
|
54
|
+
if (inString) {
|
|
55
|
+
if (c === "\\") {
|
|
56
|
+
if (depth > level && body[i + 1] !== undefined && body[i + 1] !== "\n") out[i + 1] = " ";
|
|
57
|
+
i++;
|
|
58
|
+
} else if (c === '"') inString = false;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (c === '"') inString = true;
|
|
62
|
+
else if (c === "{") depth++;
|
|
63
|
+
else if (c === "}") depth--;
|
|
64
|
+
}
|
|
65
|
+
return out.join("");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The literal attributes of HCL text at brace depth `level` (0 for a file of
|
|
70
|
+
* bare attributes, 1 for the body of a block given with its header): plain
|
|
71
|
+
* strings, booleans and numbers, typed as init records them. A string with an
|
|
72
|
+
* interpolation, and anything that is not one line of `name = literal`, is
|
|
73
|
+
* left out. Comments are left to the caller to blank.
|
|
74
|
+
*/
|
|
75
|
+
export function literalAttributes(text: string, level: number): Record<string, unknown> {
|
|
76
|
+
const out: Record<string, unknown> = {};
|
|
77
|
+
for (const m of blankBelow(text, level).matchAll(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*("(?:[^"\\]|\\.)*"|true|false|-?\d+(?:\.\d+)?)\s*$/gm)) {
|
|
78
|
+
const raw = m[2]!;
|
|
79
|
+
if (raw.startsWith('"')) {
|
|
80
|
+
const s = raw.slice(1, -1);
|
|
81
|
+
if (/\$\{|%\{/.test(s)) continue;
|
|
82
|
+
out[m[1]!] = s.replace(/\\(.)/g, "$1");
|
|
83
|
+
} else out[m[1]!] = raw === "true" ? true : raw === "false" ? false : Number(raw);
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** HCL text with its `#`, `//` and `/* *\/` comments blanked, strings read through. */
|
|
89
|
+
function withoutComments(text: string): string {
|
|
90
|
+
const out = text.split("");
|
|
91
|
+
let inString = false;
|
|
92
|
+
for (let i = 0; i < text.length; i++) {
|
|
93
|
+
const c = text[i]!;
|
|
94
|
+
if (inString) {
|
|
95
|
+
if (c === "\\") i++;
|
|
96
|
+
else if (c === '"') inString = false;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (c === '"') inString = true;
|
|
100
|
+
else if (c === "#" || (c === "/" && text[i + 1] === "/")) {
|
|
101
|
+
for (; i < text.length && text[i] !== "\n"; i++) out[i] = " ";
|
|
102
|
+
} else if (c === "/" && text[i + 1] === "*") {
|
|
103
|
+
const end = text.indexOf("*/", i + 2);
|
|
104
|
+
const stop = end < 0 ? text.length : end + 2;
|
|
105
|
+
for (; i < stop; i++) if (text[i] !== "\n") out[i] = " ";
|
|
106
|
+
i--;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return out.join("");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The keys one backend-config file sets: its top-level `key = "value"` lines. Throws when the file cannot be read. */
|
|
113
|
+
export function readBackendConfigFile(path: string): Record<string, unknown> {
|
|
114
|
+
return literalAttributes(withoutComments(readFileSync(path, "utf-8")), 0);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The keys a root's backend-config files set, later files over earlier ones,
|
|
119
|
+
* each path resolved against `dir`; and the files that could not be read,
|
|
120
|
+
* which init itself will refuse.
|
|
121
|
+
*/
|
|
122
|
+
export function backendConfigFileKeys(dir: string, files: readonly string[] = []): { config: Record<string, unknown>; unread: string[] } {
|
|
123
|
+
const config: Record<string, unknown> = {};
|
|
124
|
+
const unread: string[] = [];
|
|
125
|
+
for (const f of files) {
|
|
126
|
+
try {
|
|
127
|
+
Object.assign(config, readBackendConfigFile(backendConfigFilePath(dir, f)));
|
|
128
|
+
} catch {
|
|
129
|
+
unread.push(f);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return { config, unread };
|
|
133
|
+
}
|
package/src/behaviour/kinds.ts
CHANGED
|
@@ -138,6 +138,10 @@ const BLOCKS: Readonly<Record<string, string>> = {
|
|
|
138
138
|
"Terraform::Module": "a call to a child module; the blocks the call expands to are entities of their own and carry the figures",
|
|
139
139
|
"Terraform::Data": "a read of something that exists outside this root; whatever it reads is priced where it is declared",
|
|
140
140
|
"Terraform::Live": "choudoufu's estate declaration, which names the ownership marker every resource of the root carries and holds nothing itself",
|
|
141
|
+
"Terraform::Moved": "an instruction to rename an address in state; the resource it names carries the figures",
|
|
142
|
+
"Terraform::Import": "an instruction to adopt an existing object into state; the resource it names carries the figures",
|
|
143
|
+
"Terraform::Removed": "an instruction to drop an address from state; nothing is created by it",
|
|
144
|
+
"Terraform::Check": "an assertion terraform evaluates after a plan or apply, which holds nothing in any account",
|
|
141
145
|
};
|
|
142
146
|
|
|
143
147
|
/**
|
package/src/behaviour/predict.ts
CHANGED
|
@@ -163,10 +163,13 @@ export const readTerraformDeclaration: TerraformDeclarationReader = async (cwd)
|
|
|
163
163
|
const { config, configPath } = await loadChantConfigUpward(start);
|
|
164
164
|
const namespace = (config as { terraform?: TerraformConfig }).terraform;
|
|
165
165
|
const entities = new Map<string, TerraformBehaviourEntity>();
|
|
166
|
-
|
|
166
|
+
// A `signalOnly` root (#4484) builds nothing, as in `buildRoots`.
|
|
167
|
+
if (!namespace) return entities;
|
|
168
|
+
const roots = Object.fromEntries(Object.entries(namespace.roots ?? {}).filter(([, r]) => r.signalOnly !== true));
|
|
169
|
+
if (Object.keys(roots).length === 0) return entities;
|
|
167
170
|
const rendered = await renderTerraformRoots({
|
|
168
171
|
projectRoot: configPath ? dirname(configPath) : start,
|
|
169
|
-
roots
|
|
172
|
+
roots,
|
|
170
173
|
binary: namespace.binary,
|
|
171
174
|
callModuleType: namespace.callModuleType,
|
|
172
175
|
moduleRoot: namespace.moduleRoot,
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #4535: terraform's catalog is on the plugin, and what it reads there.
|
|
3
|
+
*
|
|
4
|
+
* A reader that asks each lexicon of a graph for its catalog (behold#1030)
|
|
5
|
+
* now finds terraform's. On a `chant graph` node (`Terraform::Resource`, no
|
|
6
|
+
* arguments in its attrs) no rule applies, so a declared terraform graph
|
|
7
|
+
* keeps exactly the edges its HCL references give it: on example-three-cloud's
|
|
8
|
+
* azure member that is also what behold's matcher added (nothing). On a node
|
|
9
|
+
* whose kind is the provider type and whose attrs are the block's arguments,
|
|
10
|
+
* each rule draws its edge.
|
|
11
|
+
*/
|
|
12
|
+
import { readFileSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { describe, expect, it } from "vitest";
|
|
15
|
+
import type { IRNode } from "@intentius/chant/graph-ir";
|
|
16
|
+
import { reconstructIndexedEdges, refRuleId } from "@intentius/chant/graph-refs";
|
|
17
|
+
import { TERRAFORM_REFERENCE_CATALOG } from "./reference-catalog";
|
|
18
|
+
import { terraformPlugin } from "../plugin";
|
|
19
|
+
|
|
20
|
+
const LEXICON = "terraform";
|
|
21
|
+
|
|
22
|
+
function atPath(path: string, value: string): Record<string, unknown> {
|
|
23
|
+
const parts = path.split(".");
|
|
24
|
+
const build = (i: number): unknown => {
|
|
25
|
+
if (i === parts.length) return value;
|
|
26
|
+
const part = parts[i];
|
|
27
|
+
const isArr = part.endsWith("[]");
|
|
28
|
+
const inner = build(i + 1);
|
|
29
|
+
return { [isArr ? part.slice(0, -2) : part]: isArr ? [inner] : inner };
|
|
30
|
+
};
|
|
31
|
+
return build(0) as Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe("terraform reference catalog on the plugin", () => {
|
|
35
|
+
it("is the plugin's referenceCatalog", () => {
|
|
36
|
+
expect(terraformPlugin.referenceCatalog).toBe(TERRAFORM_REFERENCE_CATALOG);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("each targeted rule draws its edge on provider-typed nodes", () => {
|
|
40
|
+
for (const [i, rule] of TERRAFORM_REFERENCE_CATALOG.refs.entries()) {
|
|
41
|
+
if (!rule.targetKind) continue;
|
|
42
|
+
it(`${rule.from} ${rule.path} -> ${rule.targetKind}`, () => {
|
|
43
|
+
const value = `id-${i}`;
|
|
44
|
+
const nodes: IRNode[] = [
|
|
45
|
+
{ id: `holder-${i}`, kind: rule.from, lexicon: LEXICON, attrs: atPath(rule.path, value) },
|
|
46
|
+
{ id: `target-${i}`, kind: rule.targetKind!, lexicon: LEXICON, attrs: {}, physicalId: value },
|
|
47
|
+
];
|
|
48
|
+
const out = reconstructIndexedEdges(nodes, [{ lexicon: LEXICON, catalog: TERRAFORM_REFERENCE_CATALOG }], () => ({ account: "declared" }));
|
|
49
|
+
expect(out.edges.find((e) => e.from === `holder-${i}` && e.to === `target-${i}`)?.rule).toBe(refRuleId(LEXICON, rule.from, rule.path));
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("example-three-cloud's azure member (parity with behold's matcher)", () => {
|
|
55
|
+
const graph = JSON.parse(readFileSync(join(import.meta.dirname, "..", "__fixtures__", "three-cloud-azure.graph.json"), "utf-8")) as { nodes: IRNode[] };
|
|
56
|
+
const out = reconstructIndexedEdges(graph.nodes, [{ lexicon: LEXICON, catalog: TERRAFORM_REFERENCE_CATALOG }], () => ({ account: "declared" }));
|
|
57
|
+
|
|
58
|
+
it("adds no edge to a declared block graph, as the matcher added none", () => {
|
|
59
|
+
expect(graph.nodes.some((n) => n.kind === "Terraform::Resource")).toBe(true);
|
|
60
|
+
expect(out.edges).toEqual([]);
|
|
61
|
+
expect(out.ambiguous).toEqual([]);
|
|
62
|
+
expect(out.dangling).toEqual([]);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -16,11 +16,22 @@
|
|
|
16
16
|
* Every terraform `resource` block is `Terraform::Resource` to chant. A rule
|
|
17
17
|
* keyed on that would apply to a VPC and an instance alike, so the nodes
|
|
18
18
|
* `./request.ts` builds for the resolver carry the **provider type** as their
|
|
19
|
-
* `kind` — `aws_subnet` — and the rules here are keyed the same way.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
19
|
+
* `kind` — `aws_subnet` — and the rules here are keyed the same way.
|
|
20
|
+
*
|
|
21
|
+
* ## On the plugin (#4535)
|
|
22
|
+
*
|
|
23
|
+
* The plugin declares it as `referenceCatalog` since #4535, so a reader that
|
|
24
|
+
* asks every lexicon of a graph for its catalog (behold#1030's gate, before
|
|
25
|
+
* behold#1005 retires behold's name matcher) finds terraform's. What it
|
|
26
|
+
* matches depends on the nodes. A declared or live `chant graph` node is
|
|
27
|
+
* `Terraform::Resource`, with its provider type in `attrs.type` and no
|
|
28
|
+
* arguments in `attrs`, so no rule here reads it: those graphs keep the edges
|
|
29
|
+
* the HCL references and the state dependencies give them, and gain none. A
|
|
30
|
+
* node whose `kind` is the provider type and whose `attrs` are the block's
|
|
31
|
+
* arguments (`./request.ts`'s, an engine's instance roster) is what the rules
|
|
32
|
+
* are for. `chant graph --live` merges a catalog's edges with the
|
|
33
|
+
* observation's own since #4535; before, any catalog replaced them, which is
|
|
34
|
+
* what kept this one off the plugin.
|
|
24
35
|
*
|
|
25
36
|
* ## Two relations, and why the split matters to an engine
|
|
26
37
|
*
|
package/src/behaviour/request.ts
CHANGED
|
@@ -329,8 +329,9 @@ function liveVerdictOf(address: string, plan: LivePlanIndex): LiveVerdict {
|
|
|
329
329
|
kind: "unobserved",
|
|
330
330
|
instances,
|
|
331
331
|
// `no-credentials` is an observation reason and not a prediction one;
|
|
332
|
-
// the credential wording stays in the detail.
|
|
333
|
-
|
|
332
|
+
// the credential wording stays in the detail. `not-reached` is a
|
|
333
|
+
// stream's reason (#4451) and a live-plan omission never gives it.
|
|
334
|
+
reason: unobserved.reason === "no-credentials" || unobserved.reason === "not-reached" ? "read-failed" : unobserved.reason,
|
|
334
335
|
detail: unobserved.detail,
|
|
335
336
|
};
|
|
336
337
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `count = 3` resource's stored change set carries three node ids (#4454).
|
|
3
|
+
*
|
|
4
|
+
* The graph is read in process the way `chant graph --format ir` builds it
|
|
5
|
+
* for a terraform root: the root's entities, `buildGraphIr`, then
|
|
6
|
+
* `withInstanceNodes`. The plan is a `show -json` shape with one
|
|
7
|
+
* `resource_changes` row per instance, as terraform prints a `count` block.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, expect, test } from "vitest";
|
|
11
|
+
import { composeChangeSet, verifyChangeSetDigest } from "@intentius/chant/change-set";
|
|
12
|
+
import { graphNodeFor, mapChangeSetToGraph } from "@intentius/chant/change-set-graph";
|
|
13
|
+
import { buildGraphIr, withInstanceNodes } from "@intentius/chant/graph-ir";
|
|
14
|
+
import { terraformChangeSetPart } from "./change-set";
|
|
15
|
+
import { literalInstanceKeys, parseTerraformRootContent } from "./hcl/parse";
|
|
16
|
+
|
|
17
|
+
const SOURCE = `resource "aws_eip" "pool" {
|
|
18
|
+
count = 3
|
|
19
|
+
domain = "vpc"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
resource "aws_s3_bucket" "logs" {
|
|
23
|
+
bucket = "logs"
|
|
24
|
+
}
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
const create = (address: string, index?: number) => ({
|
|
28
|
+
address,
|
|
29
|
+
mode: "managed",
|
|
30
|
+
type: address.split(".")[0],
|
|
31
|
+
name: address.split(".")[1]!.replace(/\[.*$/, ""),
|
|
32
|
+
...(index !== undefined ? { index } : {}),
|
|
33
|
+
change: { actions: ["create"], before: null, after: { domain: "vpc" }, after_unknown: { id: true } },
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const PLAN = {
|
|
37
|
+
format_version: "1.2",
|
|
38
|
+
resource_changes: [create("aws_eip.pool[0]", 0), create("aws_eip.pool[1]", 1), create("aws_eip.pool[2]", 2), create("aws_s3_bucket.logs")],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
describe("instance nodes for a count block", () => {
|
|
42
|
+
test("a literal count gives the block three instance keys", () => {
|
|
43
|
+
expect(literalInstanceKeys({ count: 3 })).toEqual(["[0]", "[1]", "[2]"]);
|
|
44
|
+
expect(literalInstanceKeys({ count: "${var.n}" })).toBeUndefined();
|
|
45
|
+
expect(literalInstanceKeys({ for_each: { b: 1, a: 2 } })).toEqual(['["a"]', '["b"]']);
|
|
46
|
+
expect(literalInstanceKeys({ for_each: '${toset(["x", "y"])}' })).toEqual(['["x"]', '["y"]']);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("the graph has the block node and one instance node per index, each naming the block", async () => {
|
|
50
|
+
const entities = await parseTerraformRootContent(SOURCE, "estate");
|
|
51
|
+
const ir = withInstanceNodes(buildGraphIr(entities), entities);
|
|
52
|
+
const pool = ir.nodes.filter((n) => n.id.startsWith("estate/aws_eip.pool"));
|
|
53
|
+
expect(pool.map((n) => [n.id, n.instanceOf ?? null, n.instanceKey ?? null])).toEqual([
|
|
54
|
+
["estate/aws_eip.pool", null, null],
|
|
55
|
+
["estate/aws_eip.pool[0]", "estate/aws_eip.pool", "[0]"],
|
|
56
|
+
["estate/aws_eip.pool[1]", "estate/aws_eip.pool", "[1]"],
|
|
57
|
+
["estate/aws_eip.pool[2]", "estate/aws_eip.pool", "[2]"],
|
|
58
|
+
]);
|
|
59
|
+
expect(ir.groups.byStack?.estate).toEqual(expect.arrayContaining(["estate/aws_eip.pool[0]", "estate/aws_eip.pool[2]"]));
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("the stored change set carries three node ids for the three instances", async () => {
|
|
63
|
+
const entities = await parseTerraformRootContent(SOURCE, "estate");
|
|
64
|
+
const ir = withInstanceNodes(buildGraphIr(entities), entities);
|
|
65
|
+
const doc = composeChangeSet([terraformChangeSetPart({ member: "estate", plan: PLAN })]);
|
|
66
|
+
const mapped = mapChangeSetToGraph(doc, { member: "infra", at: null, ids: ir.nodes.map((n) => n.id) });
|
|
67
|
+
|
|
68
|
+
const pool = mapped.entries.filter((e) => e.type === "aws_eip");
|
|
69
|
+
expect(pool.map((e) => [e.address, e.node, e.instanceOf])).toEqual([
|
|
70
|
+
["aws_eip.pool[0]", "infra/estate/aws_eip.pool[0]", "infra/estate/aws_eip.pool"],
|
|
71
|
+
["aws_eip.pool[1]", "infra/estate/aws_eip.pool[1]", "infra/estate/aws_eip.pool"],
|
|
72
|
+
["aws_eip.pool[2]", "infra/estate/aws_eip.pool[2]", "infra/estate/aws_eip.pool"],
|
|
73
|
+
]);
|
|
74
|
+
expect(new Set(pool.map((e) => e.node)).size).toBe(3);
|
|
75
|
+
const logs = mapped.entries.find((e) => e.address === "aws_s3_bucket.logs")!;
|
|
76
|
+
expect(logs.node).toBe("infra/estate/aws_s3_bucket.logs");
|
|
77
|
+
expect(logs.instanceOf).toBeUndefined();
|
|
78
|
+
// Node ids are not hashed.
|
|
79
|
+
expect(mapped.digest).toBe(doc.digest);
|
|
80
|
+
expect(verifyChangeSetDigest(mapped)).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("an instance the graph cannot count still gets its instance id, and a drift row the same", async () => {
|
|
84
|
+
const entities = await parseTerraformRootContent(SOURCE.replace("count = 3", "count = var.n"), "estate");
|
|
85
|
+
const ir = withInstanceNodes(buildGraphIr(entities), entities);
|
|
86
|
+
expect(ir.nodes.some((n) => n.instanceOf)).toBe(false);
|
|
87
|
+
const graph = { member: null, at: null, ids: ir.nodes.map((n) => n.id) };
|
|
88
|
+
expect(graphNodeFor({ member: "estate", planner: "terraform", address: "aws_eip.pool[1]" }, graph)).toMatchObject({
|
|
89
|
+
node: "estate/aws_eip.pool[1]",
|
|
90
|
+
instanceOf: "estate/aws_eip.pool",
|
|
91
|
+
});
|
|
92
|
+
// Under an expanded module call the module key sits on the call's segment (#4486).
|
|
93
|
+
expect(graphNodeFor({ member: "estate", planner: "terraform", address: "module.app[0].aws_eip.pool[1]" }, { ...graph, ids: ["estate/module.app/aws_eip.pool"] })).toEqual({
|
|
94
|
+
node: "estate/module.app[0]/aws_eip.pool[1]",
|
|
95
|
+
instanceOf: "estate/module.app/aws_eip.pool",
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
});
|
package/src/changed-roots.ts
CHANGED
|
@@ -9,16 +9,19 @@
|
|
|
9
9
|
* in a local module it calls (followed through modules that call modules),
|
|
10
10
|
* or is one of its var files.
|
|
11
11
|
*
|
|
12
|
-
* The module calls are read with a regex over the root's
|
|
12
|
+
* The module calls are read with a regex over the root's HCL configuration
|
|
13
13
|
* files, the way `./op/activities/live-detect.ts` reads a live block, so
|
|
14
|
-
* this stays out of the HCL parser's dependency graph.
|
|
14
|
+
* this stays out of the HCL parser's dependency graph. Which files those are
|
|
15
|
+
* follows the root's binary through the lexicon's one lister (chant#4457):
|
|
16
|
+
* `.tf`, plus `.tofu` for a tofu or choudoufu root. Only a `source` that
|
|
15
17
|
* starts with `./` or `../` is followed; a registry or git source is a
|
|
16
18
|
* version pin in the root's own files, which a change to it already touches.
|
|
17
19
|
*/
|
|
18
20
|
|
|
19
|
-
import { existsSync,
|
|
21
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
20
22
|
import { join, posix, relative, resolve, sep } from "node:path";
|
|
21
23
|
import type { TerraformRootConfig } from "./config";
|
|
24
|
+
import { isJsonConfigFile, listConfigFilesSafe } from "./hcl/files";
|
|
22
25
|
|
|
23
26
|
/** `source = "./..."` or `"../..."` inside a module block. Other sources are not on disk here. */
|
|
24
27
|
const LOCAL_SOURCE = /\bsource\s*=\s*"(\.{1,2}\/[^"]*)"/g;
|
|
@@ -31,17 +34,11 @@ function projectRelative(projectRoot: string, dir: string): string {
|
|
|
31
34
|
return rel === "" ? "." : posix.normalize(rel).replace(/\/+$/, "");
|
|
32
35
|
}
|
|
33
36
|
|
|
34
|
-
/** Local module directories `dir` calls, read from
|
|
35
|
-
function localModuleDirs(dir: string): string[] {
|
|
36
|
-
let names: string[];
|
|
37
|
-
try {
|
|
38
|
-
names = readdirSync(dir);
|
|
39
|
-
} catch {
|
|
40
|
-
return [];
|
|
41
|
-
}
|
|
37
|
+
/** Local module directories `dir` calls, read from the HCL configuration files `binary` reads. */
|
|
38
|
+
function localModuleDirs(dir: string, binary?: string): string[] {
|
|
42
39
|
const out: string[] = [];
|
|
43
|
-
for (const name of
|
|
44
|
-
if (
|
|
40
|
+
for (const name of listConfigFilesSafe(dir, binary)) {
|
|
41
|
+
if (isJsonConfigFile(name)) continue;
|
|
45
42
|
let text: string;
|
|
46
43
|
try {
|
|
47
44
|
text = readFileSync(join(dir, name), "utf8");
|
|
@@ -61,7 +58,7 @@ function localModuleDirs(dir: string): string[] {
|
|
|
61
58
|
* Every path, relative to `projectRoot`, whose change touches `root`: its
|
|
62
59
|
* directory, each local module it reaches, and its var files.
|
|
63
60
|
*/
|
|
64
|
-
export function rootWatchPaths(projectRoot: string, root: Pick<TerraformRootConfig, "dir" | "varFiles"
|
|
61
|
+
export function rootWatchPaths(projectRoot: string, root: Pick<TerraformRootConfig, "dir" | "varFiles">, binary?: string): string[] {
|
|
65
62
|
const rootDir = resolve(projectRoot, root.dir);
|
|
66
63
|
const seen = new Set<string>();
|
|
67
64
|
const queue = [rootDir];
|
|
@@ -69,7 +66,7 @@ export function rootWatchPaths(projectRoot: string, root: Pick<TerraformRootConf
|
|
|
69
66
|
const dir = queue.shift()!;
|
|
70
67
|
if (seen.has(dir)) continue;
|
|
71
68
|
seen.add(dir);
|
|
72
|
-
queue.push(...localModuleDirs(dir));
|
|
69
|
+
queue.push(...localModuleDirs(dir, binary));
|
|
73
70
|
}
|
|
74
71
|
const paths = [...seen].map((d) => projectRelative(projectRoot, d));
|
|
75
72
|
for (const file of root.varFiles ?? []) paths.push(projectRelative(projectRoot, resolve(rootDir, file)));
|
|
@@ -91,11 +88,12 @@ export function changedRoots(
|
|
|
91
88
|
projectRoot: string,
|
|
92
89
|
roots: Record<string, Pick<TerraformRootConfig, "dir" | "varFiles">>,
|
|
93
90
|
changedFiles: readonly string[],
|
|
91
|
+
binary?: string,
|
|
94
92
|
): string[] {
|
|
95
93
|
const files = changedFiles.map((f) => posix.normalize(f));
|
|
96
94
|
const touched: string[] = [];
|
|
97
95
|
for (const [name, root] of Object.entries(roots)) {
|
|
98
|
-
const watch = rootWatchPaths(projectRoot, root);
|
|
96
|
+
const watch = rootWatchPaths(projectRoot, root, binary);
|
|
99
97
|
if (files.some((f) => watch.some((p) => covers(p, f)))) touched.push(name);
|
|
100
98
|
}
|
|
101
99
|
return touched.sort();
|
package/src/ci-setup.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The engine install lines a Terraform project's pull request pipeline runs
|
|
3
|
+
* in chant's default image (#4550, `LexiconPlugin.ciSetup`): OpenTofu, and
|
|
4
|
+
* choudoufu when a root runs it. Each line skips the install when the binary
|
|
5
|
+
* is already on PATH.
|
|
6
|
+
*
|
|
7
|
+
* Whether a root runs choudoufu is read from the text of the project's
|
|
8
|
+
* `chant.config.ts` (`binary: "choudoufu"`), never by running it, as lexicon
|
|
9
|
+
* detection reads source.
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { PR_LOOP_SETUP } from "@intentius/chant/components/pr-pipeline";
|
|
14
|
+
|
|
15
|
+
/** The choudoufu release the default lines install. A product's images pin their own. */
|
|
16
|
+
export const CHOUDOUFU_CI_VERSION = "0.31.0";
|
|
17
|
+
|
|
18
|
+
/** choudoufu into /usr/local/bin from its release archive. */
|
|
19
|
+
export function choudoufuInstallLine(version = CHOUDOUFU_CI_VERSION): string {
|
|
20
|
+
const arch = `$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')`;
|
|
21
|
+
const url = `https://github.com/INTENTIUS/choudoufu/releases/download/v${version}/choudoufu_v${version}_linux_${arch}.tar.gz`;
|
|
22
|
+
return `command -v choudoufu >/dev/null 2>&1 || (mkdir -p /tmp/choudoufu && curl -fsSL "${url}" | tar -xz -C /tmp/choudoufu && install "$(find /tmp/choudoufu -type f -name choudoufu | head -n 1)" /usr/local/bin/choudoufu)`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Whether the project's config names choudoufu as a root's binary. */
|
|
26
|
+
export function usesChoudoufu(dir: string): boolean {
|
|
27
|
+
for (const name of ["chant.config.ts", "chant.config.mts", "chant.config.js"]) {
|
|
28
|
+
const file = join(dir, name);
|
|
29
|
+
if (!existsSync(file)) continue;
|
|
30
|
+
try {
|
|
31
|
+
if (/binary\s*:\s*["']choudoufu["']/.test(readFileSync(file, "utf-8"))) return true;
|
|
32
|
+
} catch {
|
|
33
|
+
// unreadable: no choudoufu line
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The lines for the project at `dir`. */
|
|
40
|
+
export function terraformCiSetup(ctx: { dir: string }): string[] {
|
|
41
|
+
return [...PR_LOOP_SETUP, ...(usesChoudoufu(ctx.dir) ? [choudoufuInstallLine()] : [])];
|
|
42
|
+
}
|