@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,488 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where an initialised root's state is, read from the backend the binary
|
|
3
|
+
* initialised: `<root>/.terraform/terraform.tfstate` (or
|
|
4
|
+
* `$TF_DATA_DIR/terraform.tfstate`), which `init` writes with the backend's
|
|
5
|
+
* type and its whole configuration, whether the root's code, a
|
|
6
|
+
* `-backend-config` file or the job's flags supplied it. Nothing here reads
|
|
7
|
+
* a state's contents.
|
|
8
|
+
*
|
|
9
|
+
* chant locks and writes four backends: s3 with `use_lockfile`, gcs, azurerm
|
|
10
|
+
* and local. Every other backend, and a root with a `cloud` block, comes back
|
|
11
|
+
* as a {@link StateRefusal} naming why, and is never locked.
|
|
12
|
+
*
|
|
13
|
+
* This is also the one place that answers which backend a root has and
|
|
14
|
+
* whether chant can lock it (chant#4478). Before `init` the answer comes
|
|
15
|
+
* from the root's code with the `-backend-config` pairs over it
|
|
16
|
+
* ({@link declaredBackend}); after, from what `init` recorded. When both
|
|
17
|
+
* exist and disagree the root is refused with a reason naming both
|
|
18
|
+
* ({@link readBackend}, {@link lockRefusal}). The composition's `backend`
|
|
19
|
+
* fallback and the state refusals both call {@link lockRefusal}, so one root
|
|
20
|
+
* gets one verdict.
|
|
21
|
+
*
|
|
22
|
+
* Lifted from terragucci's `backend.ts` (c493e0e): `initialisedBackend`,
|
|
23
|
+
* `dataDir`, `workspaceOf`, `s3StateKey`, `stateObject`, `isStored`,
|
|
24
|
+
* `stateLocation`, and the cloud block check of `migrate.ts`'s `refusal`.
|
|
25
|
+
*/
|
|
26
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
27
|
+
import { isAbsolute, join } from "node:path";
|
|
28
|
+
import { AZURE_AUTHORITY, type AzureOidc } from "./azure-blob";
|
|
29
|
+
import { GCS_ENDPOINT, type GcsTarget } from "./gcs";
|
|
30
|
+
import { s3FromEnv, type S3Target } from "./s3";
|
|
31
|
+
import { isJsonConfigFile, listConfigFilesSafe } from "../hcl/files";
|
|
32
|
+
import { blockBody, jsonBlocks, rootTexts } from "../compose/hcl";
|
|
33
|
+
import { backendConfigFileKeys, literalAttributes } from "../backend-config";
|
|
34
|
+
|
|
35
|
+
/** The backend `init` recorded: its type and configuration. */
|
|
36
|
+
export interface InitialisedBackend {
|
|
37
|
+
type: string;
|
|
38
|
+
config: Record<string, unknown>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A state in an S3 bucket. */
|
|
42
|
+
export interface S3State {
|
|
43
|
+
backend: "s3";
|
|
44
|
+
bucket: string;
|
|
45
|
+
key: string;
|
|
46
|
+
region: string;
|
|
47
|
+
endpoint?: string;
|
|
48
|
+
/** `use_lockfile = true`: the backend locks with `<key>.tflock` beside the state. */
|
|
49
|
+
lockfile: boolean;
|
|
50
|
+
target: S3Target;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A state in a GCS bucket: `<prefix>/<workspace>.tfstate`, locked by `<prefix>/<workspace>.tflock`. */
|
|
54
|
+
export interface GcsState {
|
|
55
|
+
backend: "gcs";
|
|
56
|
+
bucket: string;
|
|
57
|
+
key: string;
|
|
58
|
+
lock: string;
|
|
59
|
+
target: GcsTarget;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** How a request to Azure Blob Storage is authorized: an account key, a SAS, or the job's OIDC identity. */
|
|
63
|
+
export type AzureAuth = { accountKey: string } | { sas: string } | { oidc: AzureOidc };
|
|
64
|
+
|
|
65
|
+
/** A state blob in an Azure storage container, locked by a lease on it. */
|
|
66
|
+
export interface AzureState {
|
|
67
|
+
backend: "azurerm";
|
|
68
|
+
account: string;
|
|
69
|
+
container: string;
|
|
70
|
+
key: string;
|
|
71
|
+
/** The backend snapshots the blob before each write (`snapshot = true`). */
|
|
72
|
+
snapshot: boolean;
|
|
73
|
+
/** The account's blob endpoint. */
|
|
74
|
+
endpoint: string;
|
|
75
|
+
auth: AzureAuth;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** A local state file, its path relative to the root (or absolute). */
|
|
79
|
+
export interface LocalState {
|
|
80
|
+
backend: "local";
|
|
81
|
+
path: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Why chant does not lock or write a root's state. */
|
|
85
|
+
export type StateRefusalKind = "cloud" | "no-lockfile" | "backend" | "config" | "credentials" | "disagree";
|
|
86
|
+
|
|
87
|
+
export interface StateRefusal {
|
|
88
|
+
backend: string;
|
|
89
|
+
refused: StateRefusalKind;
|
|
90
|
+
reason: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Where a root's state lives, or why chant does not lock it. */
|
|
94
|
+
export type StateObject = S3State | GcsState | AzureState | LocalState | StateRefusal;
|
|
95
|
+
|
|
96
|
+
/** A state in a bucket or a container. */
|
|
97
|
+
export type StoredState = S3State | GcsState | AzureState;
|
|
98
|
+
|
|
99
|
+
/** A state chant locks and writes. */
|
|
100
|
+
export type LockableState = StoredState | LocalState;
|
|
101
|
+
|
|
102
|
+
export const isRefused = (o: StateObject): o is StateRefusal => "refused" in o;
|
|
103
|
+
|
|
104
|
+
/** Whether the state is in a bucket or a container: s3, gcs or azurerm. */
|
|
105
|
+
export const isStored = (o: StateObject): o is StoredState => !isRefused(o) && o.backend !== "local";
|
|
106
|
+
|
|
107
|
+
/** The backend types whose state chant locks and writes itself, s3 only with `use_lockfile = true` (#4437). The one list (chant#4478). */
|
|
108
|
+
export const LOCKABLE_BACKENDS: readonly string[] = ["s3", "gcs", "azurerm", "local"];
|
|
109
|
+
|
|
110
|
+
/** What a refusal says chant locks, spelled from {@link LOCKABLE_BACKENDS}. */
|
|
111
|
+
export const LOCKS = (() => {
|
|
112
|
+
const names = LOCKABLE_BACKENDS.map((b) => (b === "s3" ? "s3 (with use_lockfile = true)" : b));
|
|
113
|
+
return `chant locks and writes ${names.slice(0, -1).join(", ")} and ${names.at(-1)} states`;
|
|
114
|
+
})();
|
|
115
|
+
|
|
116
|
+
const str = (v: unknown): string | undefined => (typeof v === "string" && v !== "" ? v : undefined);
|
|
117
|
+
|
|
118
|
+
/** The root's data dir: `TF_DATA_DIR` (relative to the root, as the binary run with `-chdir` reads it), else `.terraform`. */
|
|
119
|
+
export function dataDir(dir: string, env: NodeJS.ProcessEnv = process.env): string {
|
|
120
|
+
const d = env.TF_DATA_DIR;
|
|
121
|
+
if (!d) return join(dir, ".terraform");
|
|
122
|
+
return isAbsolute(d) ? d : join(dir, d);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** The backend `init` recorded for the root, or undefined when it has not been initialised with one. */
|
|
126
|
+
export function initialisedBackend(dir: string, env: NodeJS.ProcessEnv = process.env): InitialisedBackend | undefined {
|
|
127
|
+
const file = join(dataDir(dir, env), "terraform.tfstate");
|
|
128
|
+
if (!existsSync(file)) return undefined;
|
|
129
|
+
try {
|
|
130
|
+
const parsed = JSON.parse(readFileSync(file, "utf-8")) as { backend?: { type?: unknown; config?: unknown } };
|
|
131
|
+
const type = str(parsed.backend?.type);
|
|
132
|
+
if (!type) return undefined;
|
|
133
|
+
const config = parsed.backend?.config && typeof parsed.backend.config === "object" ? (parsed.backend.config as Record<string, unknown>) : {};
|
|
134
|
+
return { type, config };
|
|
135
|
+
} catch {
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** The workspace the binary runs in: `TF_WORKSPACE`, else the one `workspace select` recorded, else `default`. */
|
|
141
|
+
export function workspaceOf(dir: string, env: NodeJS.ProcessEnv = process.env): string {
|
|
142
|
+
if (env.TF_WORKSPACE) return env.TF_WORKSPACE;
|
|
143
|
+
const file = join(dataDir(dir, env), "environment");
|
|
144
|
+
if (existsSync(file)) {
|
|
145
|
+
const ws = readFileSync(file, "utf-8").trim();
|
|
146
|
+
if (ws) return ws;
|
|
147
|
+
}
|
|
148
|
+
return "default";
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** The S3 key of a workspace's state, as the S3 backend names it: the key in `default`, else `<workspace_key_prefix>/<workspace>/<key>`. */
|
|
152
|
+
export function s3StateKey(key: string, workspace: string, prefix = "env:"): string {
|
|
153
|
+
return workspace === "default" ? key : `${prefix}/${workspace}/${key}`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Whether a root's JSON code gives a `terraform` block a `cloud` block, as `terraform.cloud` or a list of terraform blocks. */
|
|
157
|
+
function jsonCloud(doc: unknown): boolean {
|
|
158
|
+
const tf = doc && typeof doc === "object" ? (doc as Record<string, unknown>).terraform : undefined;
|
|
159
|
+
const blocks = Array.isArray(tf) ? tf : tf ? [tf] : [];
|
|
160
|
+
return blocks.some((b) => b && typeof b === "object" && "cloud" in (b as object));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Whether the root's code has a `cloud` block, over the configuration files
|
|
165
|
+
* `binary` reads: `.tf` and `.tf.json`, plus `.tofu` and `.tofu.json` for a
|
|
166
|
+
* tofu or choudoufu root, from the lexicon's one lister (chant#4457).
|
|
167
|
+
*/
|
|
168
|
+
export function hasCloudBlock(dir: string, binary?: string): boolean {
|
|
169
|
+
for (const f of listConfigFilesSafe(dir, binary)) {
|
|
170
|
+
let text: string;
|
|
171
|
+
try {
|
|
172
|
+
text = readFileSync(join(dir, f), "utf-8");
|
|
173
|
+
} catch {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (!isJsonConfigFile(f)) {
|
|
177
|
+
if (/^\s*cloud\s*\{/m.test(text)) return true;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
if (jsonCloud(JSON.parse(text))) return true;
|
|
182
|
+
} catch {
|
|
183
|
+
// Not JSON: the binary reports it.
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const CLOUD_REASON = "the root has a cloud block, whose state HCP Terraform keeps and locks";
|
|
190
|
+
|
|
191
|
+
/** Where a root's backend is read from, before `init` runs. */
|
|
192
|
+
export interface DeclaredBackendInput {
|
|
193
|
+
/** `terraform.roots.<name>.backendConfig`: the `-backend-config` pairs chant's own init passes, last. */
|
|
194
|
+
backendConfig?: Readonly<Record<string, string>>;
|
|
195
|
+
/** `terraform.roots.<name>.backendConfigFiles`: the backend-config files chant's own init passes before the pairs (chant#4459), relative to the root. */
|
|
196
|
+
backendConfigFiles?: readonly string[];
|
|
197
|
+
/** The job's environment: its `TF_CLI_ARGS` and `TF_CLI_ARGS_init` may carry `-backend-config` flags. */
|
|
198
|
+
env?: NodeJS.ProcessEnv;
|
|
199
|
+
/** `terraform.binary`, which says which of the root's files are its code (chant#4457). */
|
|
200
|
+
binary?: string;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** A `-backend-config` value as init would type it: `true` and `false` are booleans, anything else a string. */
|
|
204
|
+
const typed = (v: string): unknown => (v === "true" ? true : v === "false" ? false : v);
|
|
205
|
+
|
|
206
|
+
/** The `-backend-config` flags in `TF_CLI_ARGS` then `TF_CLI_ARGS_init`, in order: `key=value` pairs, and the paths of backend-config files. */
|
|
207
|
+
export function cliBackendConfig(env: NodeJS.ProcessEnv = {}): { pairs: Record<string, string>; files: string[]; ordered: Array<{ file: string } | { key: string; value: string }> } {
|
|
208
|
+
const pairs: Record<string, string> = {};
|
|
209
|
+
const files: string[] = [];
|
|
210
|
+
const ordered: Array<{ file: string } | { key: string; value: string }> = [];
|
|
211
|
+
const unquote = (t: string): string => t.replace(/^(["'])(.*)\1$/, "$2");
|
|
212
|
+
for (const line of [env.TF_CLI_ARGS, env.TF_CLI_ARGS_init]) {
|
|
213
|
+
const tokens = (line ?? "").match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) ?? [];
|
|
214
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
215
|
+
const t = unquote(tokens[i]!);
|
|
216
|
+
const flag = /^--?backend-config(?:=(.*))?$/.exec(t);
|
|
217
|
+
if (!flag) continue;
|
|
218
|
+
const value = flag[1] !== undefined ? unquote(flag[1]) : tokens[i + 1] !== undefined ? unquote(tokens[++i]!) : undefined;
|
|
219
|
+
if (value === undefined || value === "") continue;
|
|
220
|
+
const eq = value.indexOf("=");
|
|
221
|
+
if (eq > 0) {
|
|
222
|
+
pairs[value.slice(0, eq)] = value.slice(eq + 1);
|
|
223
|
+
ordered.push({ key: value.slice(0, eq), value: value.slice(eq + 1) });
|
|
224
|
+
} else {
|
|
225
|
+
files.push(value);
|
|
226
|
+
ordered.push({ file: value });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return { pairs, files, ordered };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const plain = (v: unknown): v is string | number | boolean => typeof v === "string" || typeof v === "number" || typeof v === "boolean";
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* The backend the root's code declares, with the `-backend-config` flags of
|
|
237
|
+
* `TF_CLI_ARGS`, `TF_CLI_ARGS_init` and then `backendConfig` over it, in the
|
|
238
|
+
* order init applies them: what `init` would record, as far as chant can read
|
|
239
|
+
* it before init runs. A `cloud` block is `{ type: "cloud" }`. Undefined when
|
|
240
|
+
* the code declares neither, which init records as no backend (local).
|
|
241
|
+
*/
|
|
242
|
+
export function declaredBackend(dir: string, input: DeclaredBackendInput = {}): InitialisedBackend | undefined {
|
|
243
|
+
let found: InitialisedBackend | undefined;
|
|
244
|
+
for (const { text, json } of rootTexts(dir, input.binary)) {
|
|
245
|
+
if (found) break;
|
|
246
|
+
if (json) {
|
|
247
|
+
let doc: unknown;
|
|
248
|
+
try {
|
|
249
|
+
doc = JSON.parse(text);
|
|
250
|
+
} catch {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
for (const t of jsonBlocks(doc, "terraform")) {
|
|
254
|
+
if (t.cloud !== undefined) {
|
|
255
|
+
found = { type: "cloud", config: {} };
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
const backends = t.backend && typeof t.backend === "object" && !Array.isArray(t.backend) ? (t.backend as Record<string, unknown>) : {};
|
|
259
|
+
const type = Object.keys(backends)[0];
|
|
260
|
+
if (!type) continue;
|
|
261
|
+
const raw = backends[type];
|
|
262
|
+
const body = (Array.isArray(raw) ? raw[0] : raw) as Record<string, unknown> | undefined;
|
|
263
|
+
found = { type, config: Object.fromEntries(Object.entries(body ?? {}).filter(([, v]) => plain(v) && !(typeof v === "string" && /\$\{/.test(v)))) };
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
const m = text.match(/\bbackend\s+"([^"]+)"\s*\{/);
|
|
269
|
+
if (m) {
|
|
270
|
+
found = { type: m[1]!, config: literalAttributes(blockBody(text, m.index!), 1) };
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
if (/^\s*cloud\s*\{/m.test(text)) found = { type: "cloud", config: {} };
|
|
274
|
+
}
|
|
275
|
+
if (!found || found.type === "cloud") return found;
|
|
276
|
+
// The order init applies them: the job's TF_CLI_ARGS flags first, then the
|
|
277
|
+
// flags chant's own init passes, its files before its pairs (chant#4459).
|
|
278
|
+
// A file's keys are read from it, relative to the root as init reads it.
|
|
279
|
+
const config = { ...found.config };
|
|
280
|
+
for (const flag of cliBackendConfig(input.env).ordered) {
|
|
281
|
+
if ("file" in flag) Object.assign(config, backendConfigFileKeys(dir, [flag.file]).config);
|
|
282
|
+
else config[flag.key] = typed(flag.value);
|
|
283
|
+
}
|
|
284
|
+
Object.assign(config, backendConfigFileKeys(dir, input.backendConfigFiles).config);
|
|
285
|
+
for (const [k, v] of Object.entries(input.backendConfig ?? {})) config[k] = typed(v);
|
|
286
|
+
return { type: found.type, config };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** Configuration keys whose values a reason names but never prints. */
|
|
290
|
+
const SECRET_KEYS = /(^|_)(key|secret|token|password|credentials|sas)($|_)/;
|
|
291
|
+
|
|
292
|
+
const shown = (k: string, v: unknown): string => (SECRET_KEYS.test(k) && k !== "key" ? `${k} (value hidden)` : `${k} = ${JSON.stringify(v)}`);
|
|
293
|
+
|
|
294
|
+
/** A root's backend as both sources give it, and the one the verdict reads. */
|
|
295
|
+
export interface BackendReading {
|
|
296
|
+
/** From the root's code and the `-backend-config` flags, before init. */
|
|
297
|
+
declared?: InitialisedBackend;
|
|
298
|
+
/** What `init` recorded. */
|
|
299
|
+
recorded?: InitialisedBackend;
|
|
300
|
+
/** The recorded backend when there is one, else the declared one, else local. */
|
|
301
|
+
backend: InitialisedBackend;
|
|
302
|
+
/** Why the two disagree, naming both, when both exist and they do. */
|
|
303
|
+
disagreement?: string;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** Where a root's backend is read from: {@link DeclaredBackendInput}, plus what `init` recorded, which the root's data dir holds unless given. */
|
|
307
|
+
export interface ReadBackendInput extends DeclaredBackendInput {
|
|
308
|
+
/** What `init` recorded; `null` for none. Read from the root's data dir when absent. */
|
|
309
|
+
recorded?: InitialisedBackend | null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* A root's backend from its code and flags and from what `init` recorded,
|
|
314
|
+
* compared (chant#4478). They disagree when both exist and name another type,
|
|
315
|
+
* or when a key both give a plain value has two values; a key only one side
|
|
316
|
+
* gives (a default init filled in, a value from a backend-config file chant
|
|
317
|
+
* did not read) is no disagreement. Code that declares no backend leaves the
|
|
318
|
+
* record to stand.
|
|
319
|
+
*/
|
|
320
|
+
export function readBackend(dir: string, input: ReadBackendInput = {}): BackendReading {
|
|
321
|
+
const env = input.env ?? process.env;
|
|
322
|
+
const declared = declaredBackend(dir, input);
|
|
323
|
+
const recorded = input.recorded === undefined ? initialisedBackend(dir, env) : (input.recorded ?? undefined);
|
|
324
|
+
const backend = recorded ?? declared ?? { type: "local", config: {} };
|
|
325
|
+
const out: BackendReading = { ...(declared ? { declared } : {}), ...(recorded ? { recorded } : {}), backend };
|
|
326
|
+
if (!declared || !recorded) return out;
|
|
327
|
+
if (declared.type !== recorded.type) {
|
|
328
|
+
out.disagreement = `the root's code names the ${declared.type} backend and init recorded ${recorded.type}`;
|
|
329
|
+
return out;
|
|
330
|
+
}
|
|
331
|
+
const differ: string[] = [];
|
|
332
|
+
for (const [k, d] of Object.entries(declared.config)) {
|
|
333
|
+
const r = recorded.config[k];
|
|
334
|
+
if (!plain(d) || !plain(r) || String(d) === String(r)) continue;
|
|
335
|
+
differ.push(`${shown(k, d)} in the code and flags, ${shown(k, r)} in what init recorded`);
|
|
336
|
+
}
|
|
337
|
+
if (differ.length > 0) out.disagreement = `the root's ${declared.type} backend has ${differ.join("; ")}`;
|
|
338
|
+
return out;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Whether chant can lock the root's state, from one {@link BackendReading}:
|
|
343
|
+
* undefined when it can, else the refusal and why. The one verdict the
|
|
344
|
+
* composition's `backend` fallback (#4438) and the state refusals (#4437)
|
|
345
|
+
* both give (chant#4478).
|
|
346
|
+
*/
|
|
347
|
+
export function lockRefusal(reading: BackendReading): StateRefusal | undefined {
|
|
348
|
+
const b = reading.backend;
|
|
349
|
+
if (reading.disagreement) return { backend: b.type, refused: "disagree", reason: `${reading.disagreement}, so chant cannot tell which state to lock; run init again with the configuration the root means; ${LOCKS}` };
|
|
350
|
+
if (b.type === "cloud") return { backend: b.type, refused: "cloud", reason: `${CLOUD_REASON}; ${LOCKS}` };
|
|
351
|
+
if (!LOCKABLE_BACKENDS.includes(b.type)) return { backend: b.type, refused: "backend", reason: `the root's backend is ${b.type}; ${LOCKS}` };
|
|
352
|
+
if (b.type === "s3" && b.config.use_lockfile !== true && b.config.use_lockfile !== "true") {
|
|
353
|
+
return { backend: "s3", refused: "no-lockfile", reason: `the s3 backend takes no lock file (use_lockfile = true is unset), so chant cannot hold its lock; ${LOCKS}` };
|
|
354
|
+
}
|
|
355
|
+
return undefined;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Where the root's state is, or why chant does not lock it, from
|
|
360
|
+
* {@link readBackend}: what `init` recorded (`backend` stands in for it),
|
|
361
|
+
* compared with the root's code, `backendConfigFiles` and `backendConfig`. A root neither declares
|
|
362
|
+
* nor records a backend for reads as a local one.
|
|
363
|
+
*/
|
|
364
|
+
export function stateObject(
|
|
365
|
+
dir: string,
|
|
366
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
367
|
+
backend: InitialisedBackend | undefined = initialisedBackend(dir, env),
|
|
368
|
+
binary?: string,
|
|
369
|
+
flags: Pick<DeclaredBackendInput, "backendConfig" | "backendConfigFiles"> = {},
|
|
370
|
+
): StateObject {
|
|
371
|
+
const reading = readBackend(dir, {
|
|
372
|
+
env,
|
|
373
|
+
recorded: backend ?? null,
|
|
374
|
+
...(binary ? { binary } : {}),
|
|
375
|
+
...(flags.backendConfig ? { backendConfig: flags.backendConfig } : {}),
|
|
376
|
+
...(flags.backendConfigFiles ? { backendConfigFiles: flags.backendConfigFiles } : {}),
|
|
377
|
+
});
|
|
378
|
+
const refusal = lockRefusal(reading);
|
|
379
|
+
if (refusal) return refusal;
|
|
380
|
+
const chosen = reading.backend;
|
|
381
|
+
const ws = workspaceOf(dir, env);
|
|
382
|
+
if (chosen.type === "local") {
|
|
383
|
+
const c = chosen.config;
|
|
384
|
+
const path = ws === "default" ? (str(c.path) ?? "terraform.tfstate") : join(str(c.workspace_dir) ?? "terraform.tfstate.d", ws, "terraform.tfstate");
|
|
385
|
+
return { backend: "local", path };
|
|
386
|
+
}
|
|
387
|
+
if (chosen.type === "gcs") return gcsObject(chosen.config, ws, env);
|
|
388
|
+
if (chosen.type === "azurerm") return azureObject(chosen.config, ws, env);
|
|
389
|
+
const c = chosen.config;
|
|
390
|
+
const bucket = str(c.bucket);
|
|
391
|
+
const key = str(c.key);
|
|
392
|
+
if (!bucket || !key) return { backend: "s3", refused: "config", reason: "the s3 backend's bucket or key is not in its configuration (the code and flags, or what init recorded)" };
|
|
393
|
+
const endpoints = c.endpoints && typeof c.endpoints === "object" ? (c.endpoints as Record<string, unknown>) : {};
|
|
394
|
+
const endpoint = str(endpoints.s3) ?? str(c.endpoint) ?? env.AWS_ENDPOINT_URL_S3 ?? env.AWS_ENDPOINT_URL;
|
|
395
|
+
const region = str(c.region) ?? env.AWS_REGION ?? env.AWS_DEFAULT_REGION ?? "us-east-1";
|
|
396
|
+
const access = str(c.access_key);
|
|
397
|
+
const secret = str(c.secret_key);
|
|
398
|
+
const jobEnv = { ...env, AWS_REGION: region, ...(access && secret ? { AWS_ACCESS_KEY_ID: access, AWS_SECRET_ACCESS_KEY: secret, AWS_SESSION_TOKEN: str(c.token) ?? "" } : {}) };
|
|
399
|
+
let target: S3Target;
|
|
400
|
+
try {
|
|
401
|
+
target = s3FromEnv({ bucket, ...(endpoint ? { endpoint } : {}) }, jobEnv);
|
|
402
|
+
} catch (e) {
|
|
403
|
+
return { backend: "s3", refused: "credentials", reason: `the job has no credentials to read s3://${bucket}: ${(e as Error).message}` };
|
|
404
|
+
}
|
|
405
|
+
return {
|
|
406
|
+
backend: "s3",
|
|
407
|
+
bucket,
|
|
408
|
+
key: s3StateKey(key, ws, str(c.workspace_key_prefix)),
|
|
409
|
+
region,
|
|
410
|
+
...(endpoint ? { endpoint: endpoint.replace(/\/+$/, "") } : {}),
|
|
411
|
+
lockfile: true,
|
|
412
|
+
target,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/** A gcs backend's custom endpoint (`storage_custom_endpoint`, `https://host/storage/v1/`) as the JSON API's address: no path, no trailing slash. */
|
|
417
|
+
const gcsEndpoint = (e: string): string => e.replace(/\/+$/, "").replace(/\/storage\/v1$/, "");
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* A gcs backend's state: `<prefix>/<workspace>.tfstate` and its lock beside
|
|
421
|
+
* it, with the credentials the backend would use, first match wins:
|
|
422
|
+
* `access_token` (or GOOGLE_OAUTH_ACCESS_TOKEN), `credentials` (a file or its
|
|
423
|
+
* JSON), GOOGLE_BACKEND_CREDENTIALS, GOOGLE_CREDENTIALS, and
|
|
424
|
+
* GOOGLE_APPLICATION_CREDENTIALS.
|
|
425
|
+
*/
|
|
426
|
+
function gcsObject(c: Record<string, unknown>, ws: string, env: NodeJS.ProcessEnv): StateObject {
|
|
427
|
+
const bucket = str(c.bucket);
|
|
428
|
+
if (!bucket) return { backend: "gcs", refused: "config", reason: "the gcs backend's bucket is not in the configuration init recorded" };
|
|
429
|
+
const raw = str(c.prefix) ?? "";
|
|
430
|
+
const prefix = raw === "" || raw.endsWith("/") ? raw : `${raw}/`;
|
|
431
|
+
const endpoint = gcsEndpoint(str(c.storage_custom_endpoint) ?? env.GOOGLE_BACKEND_STORAGE_CUSTOM_ENDPOINT ?? env.GOOGLE_STORAGE_CUSTOM_ENDPOINT ?? GCS_ENDPOINT);
|
|
432
|
+
const impersonate = str(c.impersonate_service_account) ?? env.GOOGLE_BACKEND_IMPERSONATE_SERVICE_ACCOUNT ?? env.GOOGLE_IMPERSONATE_SERVICE_ACCOUNT;
|
|
433
|
+
const where = { bucket, endpoint, ...(impersonate ? { impersonate } : {}) };
|
|
434
|
+
const token = str(c.access_token) ?? env.GOOGLE_OAUTH_ACCESS_TOKEN;
|
|
435
|
+
const creds = str(c.credentials) ?? env.GOOGLE_BACKEND_CREDENTIALS ?? env.GOOGLE_CREDENTIALS;
|
|
436
|
+
let target: GcsTarget;
|
|
437
|
+
if (token) target = { ...where, accessToken: token };
|
|
438
|
+
else if (creds) target = creds.trim().startsWith("{") ? { ...where, credentialsJson: creds } : { ...where, credentialsFile: creds };
|
|
439
|
+
else if (env.GOOGLE_APPLICATION_CREDENTIALS) target = { ...where, credentialsFile: env.GOOGLE_APPLICATION_CREDENTIALS };
|
|
440
|
+
else return { backend: "gcs", refused: "credentials", reason: `the job has no credentials to read gs://${bucket}: set GOOGLE_APPLICATION_CREDENTIALS, GOOGLE_CREDENTIALS or GOOGLE_OAUTH_ACCESS_TOKEN` };
|
|
441
|
+
return { backend: "gcs", bucket, key: `${prefix}${ws}.tfstate`, lock: `${prefix}${ws}.tflock`, target };
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/** Each Azure cloud's storage suffix and Entra ID, by the azurerm backend's `environment`. */
|
|
445
|
+
const CLOUD_ENDPOINTS: Record<string, { storage: string; authority: string }> = {
|
|
446
|
+
public: { storage: "core.windows.net", authority: AZURE_AUTHORITY },
|
|
447
|
+
china: { storage: "core.chinacloudapi.cn", authority: "https://login.chinacloudapi.cn" },
|
|
448
|
+
usgovernment: { storage: "core.usgovcloudapi.net", authority: "https://login.microsoftonline.us" },
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* An azurerm backend's state: the blob `key` in `container_name` of
|
|
453
|
+
* `storage_account_name`, `<key>env:<workspace>` in another workspace, with
|
|
454
|
+
* the credentials the backend would use: `access_key` (ARM_ACCESS_KEY),
|
|
455
|
+
* `sas_token` (ARM_SAS_TOKEN), else the job's OIDC identity. An
|
|
456
|
+
* `environment` other than public, china or usgovernment is refused: its
|
|
457
|
+
* storage suffix would need the metadata lookup terragucci makes and chant
|
|
458
|
+
* does not.
|
|
459
|
+
*/
|
|
460
|
+
function azureObject(c: Record<string, unknown>, ws: string, env: NodeJS.ProcessEnv): StateObject {
|
|
461
|
+
const account = str(c.storage_account_name);
|
|
462
|
+
const container = str(c.container_name);
|
|
463
|
+
const base = str(c.key);
|
|
464
|
+
if (!account || !container || !base) return { backend: "azurerm", refused: "config", reason: "the azurerm backend's storage_account_name, container_name or key is not in the configuration init recorded" };
|
|
465
|
+
const key = ws === "default" ? base : `${base}env:${ws}`;
|
|
466
|
+
const cloud = CLOUD_ENDPOINTS[str(c.environment) ?? env.ARM_ENVIRONMENT ?? "public"];
|
|
467
|
+
if (!cloud) return { backend: "azurerm", refused: "config", reason: `the azurerm backend's environment ${str(c.environment) ?? env.ARM_ENVIRONMENT} is not public, china or usgovernment` };
|
|
468
|
+
const accessKey = str(c.access_key) ?? env.ARM_ACCESS_KEY;
|
|
469
|
+
const sas = str(c.sas_token) ?? env.ARM_SAS_TOKEN;
|
|
470
|
+
const tenantId = str(c.tenant_id) ?? env.ARM_TENANT_ID;
|
|
471
|
+
const clientId = str(c.client_id) ?? env.ARM_CLIENT_ID;
|
|
472
|
+
const tokenFile = str(c.oidc_token_file_path) ?? env.ARM_OIDC_TOKEN_FILE_PATH;
|
|
473
|
+
let auth: AzureAuth;
|
|
474
|
+
if (accessKey) auth = { accountKey: accessKey };
|
|
475
|
+
else if (sas) auth = { sas };
|
|
476
|
+
else if (tenantId && clientId && tokenFile) auth = { oidc: { tenantId, clientId, tokenFile, authority: (env.AZURE_AUTHORITY_HOST || cloud.authority).replace(/\/+$/, "") } };
|
|
477
|
+
else return { backend: "azurerm", refused: "credentials", reason: `the job has no credentials to read the container ${container} of ${account}: set ARM_ACCESS_KEY, ARM_SAS_TOKEN, or ARM_TENANT_ID, ARM_CLIENT_ID and ARM_OIDC_TOKEN_FILE_PATH` };
|
|
478
|
+
return { backend: "azurerm", account, container, key, snapshot: c.snapshot === true || env.ARM_SNAPSHOT === "true", endpoint: `https://${account}.blob.${cloud.storage}`, auth };
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/** `s3://<bucket>/<key>`, `gs://<bucket>/<key>`, `az://<account>/<container>/<key>`, or the local file's path; undefined for a refusal. */
|
|
482
|
+
export function stateLocation(o: StateObject): string | undefined {
|
|
483
|
+
if (isRefused(o)) return undefined;
|
|
484
|
+
if (o.backend === "local") return o.path;
|
|
485
|
+
if (o.backend === "gcs") return `gs://${o.bucket}/${o.key}`;
|
|
486
|
+
if (o.backend === "azurerm") return `az://${o.account}/${o.container}/${o.key}`;
|
|
487
|
+
return `s3://${o.bucket}/${o.key}`;
|
|
488
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The binary a state is read and written through: `state pull` and
|
|
3
|
+
* `state push`. The exec is injectable, so a test runs none.
|
|
4
|
+
*
|
|
5
|
+
* Lifted from terragucci's `migrate.ts` (c493e0e): `Ran`, `BinaryIo`,
|
|
6
|
+
* `BinaryExec`, `runBinary`, `StateFile`, `StateResource` and `pullState`.
|
|
7
|
+
*/
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
9
|
+
|
|
10
|
+
export interface Ran {
|
|
11
|
+
code: number;
|
|
12
|
+
stdout: string;
|
|
13
|
+
/** stdout and stderr together, as they came. */
|
|
14
|
+
out: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** What a run of the binary hands back while it runs: `stderr` sees each line of stderr as it arrives, and a line it takes (true) is left out of `out`. */
|
|
18
|
+
export interface BinaryIo {
|
|
19
|
+
stderr?: (line: string) => boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** How the binary runs: in `dir`, with `env`. Injectable for tests. */
|
|
23
|
+
export type BinaryExec = (binary: string, args: string[], dir: string, env: NodeJS.ProcessEnv, io?: BinaryIo) => Promise<Ran>;
|
|
24
|
+
|
|
25
|
+
/** The binary run with `-chdir=<dir>` and `TF_IN_AUTOMATION=1`. */
|
|
26
|
+
export const runBinary: BinaryExec = (binary, args, dir, env, io) =>
|
|
27
|
+
new Promise((done) => {
|
|
28
|
+
const child = spawn(binary, [`-chdir=${dir}`, ...args], { env: { ...env, TF_IN_AUTOMATION: "1" }, stdio: ["ignore", "pipe", "pipe"] });
|
|
29
|
+
let stdout = "";
|
|
30
|
+
let out = "";
|
|
31
|
+
let partial = "";
|
|
32
|
+
const stderrLine = (line: string): void => {
|
|
33
|
+
let taken = false;
|
|
34
|
+
try {
|
|
35
|
+
taken = io!.stderr!(line);
|
|
36
|
+
} catch {
|
|
37
|
+
taken = false;
|
|
38
|
+
}
|
|
39
|
+
if (!taken) out += `${line}\n`;
|
|
40
|
+
};
|
|
41
|
+
child.stdout.on("data", (d) => {
|
|
42
|
+
stdout += d;
|
|
43
|
+
out += d;
|
|
44
|
+
});
|
|
45
|
+
// A line split across two chunks, a character too, is put back together before it is read.
|
|
46
|
+
if (io?.stderr) child.stderr.setEncoding("utf8");
|
|
47
|
+
child.stderr.on("data", (d) => {
|
|
48
|
+
if (!io?.stderr) {
|
|
49
|
+
out += d;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
partial += d;
|
|
53
|
+
const lines = partial.split("\n");
|
|
54
|
+
partial = lines.pop()!;
|
|
55
|
+
for (const l of lines) stderrLine(l);
|
|
56
|
+
});
|
|
57
|
+
const rest = (): void => {
|
|
58
|
+
if (io?.stderr && partial) stderrLine(partial);
|
|
59
|
+
partial = "";
|
|
60
|
+
};
|
|
61
|
+
child.on("error", (e) => {
|
|
62
|
+
rest();
|
|
63
|
+
done({ code: 127, stdout, out: `${out}${e.message}` });
|
|
64
|
+
});
|
|
65
|
+
child.on("close", (code) => {
|
|
66
|
+
rest();
|
|
67
|
+
done({ code: code ?? 1, stdout, out });
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
/** A state file as `state pull` prints it (format version 4); only what a run touches is named. */
|
|
72
|
+
export interface StateFile {
|
|
73
|
+
version: number;
|
|
74
|
+
terraform_version?: string;
|
|
75
|
+
serial: number;
|
|
76
|
+
lineage: string;
|
|
77
|
+
outputs?: Record<string, unknown>;
|
|
78
|
+
resources: StateResource[];
|
|
79
|
+
check_results?: unknown;
|
|
80
|
+
[k: string]: unknown;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface StateResource {
|
|
84
|
+
module?: string;
|
|
85
|
+
mode: string;
|
|
86
|
+
type: string;
|
|
87
|
+
name: string;
|
|
88
|
+
provider?: string;
|
|
89
|
+
instances?: unknown[];
|
|
90
|
+
[k: string]: unknown;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** The last lines of the binary's output, on one line, for an error. */
|
|
94
|
+
export const lastLines = (s: string, n = 4): string => s.trim().split("\n").slice(-n).join(" ").slice(0, 400);
|
|
95
|
+
|
|
96
|
+
/** A root's state as `state pull` gives it, or null when it has none. */
|
|
97
|
+
export async function pullState(exec: BinaryExec, binary: string, root: string, dir: string, env: NodeJS.ProcessEnv): Promise<StateFile | null> {
|
|
98
|
+
const pulled = await exec(binary, ["state", "pull"], dir, env);
|
|
99
|
+
if (pulled.code !== 0) throw new Error(`state pull in ${root} failed: ${lastLines(pulled.out)}`);
|
|
100
|
+
if (!pulled.stdout.trim()) return null;
|
|
101
|
+
let s: StateFile;
|
|
102
|
+
try {
|
|
103
|
+
s = JSON.parse(pulled.stdout) as StateFile;
|
|
104
|
+
} catch {
|
|
105
|
+
throw new Error(`state pull in ${root} printed something that is not a state`);
|
|
106
|
+
}
|
|
107
|
+
if (!Array.isArray(s.resources)) s.resources = [];
|
|
108
|
+
// A backend with no state object yet prints an empty state with no lineage: that is no state.
|
|
109
|
+
if (!s.lineage && !s.serial && s.resources.length === 0) return null;
|
|
110
|
+
return s;
|
|
111
|
+
}
|
package/src/state/gcs.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Google Cloud Storage client a gcs backend's state uses: chant core's
|
|
3
|
+
* (#4554, `@intentius/chant/object-store`), over the JSON API with `fetch`
|
|
4
|
+
* and `node:crypto`. Writes are conditional on a generation
|
|
5
|
+
* (`ifGenerationMatch`); a lost write is a 412, a StoreConflict.
|
|
6
|
+
*/
|
|
7
|
+
export { GCS_ENDPOINT, GcsClient, IAM_CREDENTIALS, selfSignedJwt, type GcsTarget } from "@intentius/chant/object-store";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A root's state for a composed run (#4437): where it is, its lock, its pull
|
|
3
|
+
* and its write, and the unwritten keep. Plain lexicon code, lifted from
|
|
4
|
+
* terragucci at c493e0e.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export * from "./object-store";
|
|
8
|
+
export * from "./backend";
|
|
9
|
+
export * from "./store";
|
|
10
|
+
export * from "./exec";
|
|
11
|
+
export * from "./locks";
|
|
12
|
+
export * from "./root-states";
|
|
13
|
+
export { S3Client, s3FromEnv, type S3Target } from "./s3";
|
|
14
|
+
export { GcsClient, type GcsTarget } from "./gcs";
|
|
15
|
+
export { AzureBlobClient, type AzureTarget } from "./azure-blob";
|