@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,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The terraform side of op waves through capabilities (#4545): lock
|
|
3
|
+
* timeouts, choudoufu cone plans, reads from another root's state and the
|
|
4
|
+
* cost hold preset, moved here from terragucci's wave callbacks.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, expect, test } from "vitest";
|
|
7
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { DEFAULT_LOCK_TIMEOUT, isLockTimeout, lockTimeoutArgs } from "./lock-timeout";
|
|
11
|
+
import { coneArgs, conePlanDigest, coneRecordPath, coneText, readPlanCone } from "./cone";
|
|
12
|
+
import { atmosStateReads, fillStateReads, readOutputValue, stateReadsProblems, waitingReadsReason } from "./state-reads";
|
|
13
|
+
import { INFRACOST_ARGV, terraformCostHold } from "./cost-hold";
|
|
14
|
+
import { terraformApplyCommand, terraformPlanCommand } from "./op/activities/terraform";
|
|
15
|
+
|
|
16
|
+
describe("lock timeout (#4545)", () => {
|
|
17
|
+
test("adds -lock-timeout unless the job sets one", () => {
|
|
18
|
+
expect(lockTimeoutArgs("plan", DEFAULT_LOCK_TIMEOUT, {})).toEqual(["-lock-timeout=5m"]);
|
|
19
|
+
expect(lockTimeoutArgs("apply", "30s", { TF_CLI_ARGS: "-no-color" })).toEqual(["-lock-timeout=30s"]);
|
|
20
|
+
expect(lockTimeoutArgs("plan", "5m", { TF_CLI_ARGS_plan: "-lock-timeout=1m" })).toEqual([]);
|
|
21
|
+
expect(lockTimeoutArgs("apply", "5m", { TF_CLI_ARGS: "--lock-timeout 2m" })).toEqual([]);
|
|
22
|
+
expect(lockTimeoutArgs("plan", false, {})).toEqual([]);
|
|
23
|
+
expect(() => lockTimeoutArgs("plan", "five", {})).toThrow(/not a duration/);
|
|
24
|
+
expect(isLockTimeout("1h30m")).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("the plan and apply commands carry the extra flags", () => {
|
|
28
|
+
expect(terraformPlanCommand({ binary: "tofu", planFile: "chant.tfplan", extra: ["-cone=origin/main", "-lock-timeout=5m"] })).toBe(
|
|
29
|
+
"tofu plan -input=false -detailed-exitcode -cone=origin/main -lock-timeout=5m -out=chant.tfplan",
|
|
30
|
+
);
|
|
31
|
+
expect(terraformApplyCommand({ binary: "tofu", planFile: "chant.tfplan", extra: ["-lock-timeout=5m"] })).toBe("tofu apply -input=false -lock-timeout=5m chant.tfplan");
|
|
32
|
+
expect(terraformApplyCommand({ binary: "tofu", planFile: "chant.tfplan" })).toBe("tofu apply -input=false chant.tfplan");
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("cone plans (#4545)", () => {
|
|
37
|
+
test("only choudoufu on a live root, and never beside -target or -destroy", () => {
|
|
38
|
+
expect(coneArgs("choudoufu", "origin/main", [], true)).toEqual(["-cone=origin/main"]);
|
|
39
|
+
expect(coneArgs("/usr/local/bin/choudoufu", "origin/main", [], true)).toEqual(["-cone=origin/main"]);
|
|
40
|
+
expect(coneArgs("tofu", "origin/main", [], true)).toEqual([]);
|
|
41
|
+
expect(coneArgs("choudoufu", "origin/main", [], false)).toEqual([]);
|
|
42
|
+
expect(coneArgs("choudoufu", "origin/main", ["-target"], true)).toEqual([]);
|
|
43
|
+
expect(coneArgs("choudoufu", undefined, [], true)).toEqual([]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("reads the record beside the plan, or the guard's fallback", () => {
|
|
47
|
+
const dir = mkdtempSync(join(tmpdir(), "cone-"));
|
|
48
|
+
const plan = join(dir, "chant.tfplan");
|
|
49
|
+
writeFileSync(coneRecordPath(plan), JSON.stringify({ base: "origin/main", base_sha: "abc", targets: ["b.x", "a.y"] }));
|
|
50
|
+
const cone = readPlanCone("Warning: Cone plan\n\nThis plan covers the cone: 2 of the estate's 40 blocks\n\nPlan: 1 to add", plan, "origin/main");
|
|
51
|
+
expect(cone).toEqual({ base: "origin/main", blocks: 2, of: 40, record: { base: "origin/main", base_sha: "abc", targets: ["a.y", "b.x"] } });
|
|
52
|
+
expect(coneText(cone)).toBe("the cone of the change since origin/main, 2 of the estate's 40 blocks");
|
|
53
|
+
const fell = readPlanCone("Warning: Cone plan fell back to a full plan\n\nThis plan covers the whole estate (guard lockfile): the lock file changed.\n", plan, "origin/main");
|
|
54
|
+
expect(fell).toEqual({ base: "origin/main", fallback: { kind: "lockfile", reason: "the lock file changed" } });
|
|
55
|
+
rmSync(dir, { recursive: true, force: true });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("the cone's targets join the digest; a full plan keeps its own", () => {
|
|
59
|
+
const d = `sha256:${"1".repeat(64)}`;
|
|
60
|
+
expect(conePlanDigest(d, undefined)).toBe(d);
|
|
61
|
+
expect(conePlanDigest(d, { base: "origin/main", fallback: { kind: "x", reason: "y" } })).toBe(d);
|
|
62
|
+
const a = conePlanDigest(d, { base: "origin/main", record: { base: "origin/main", base_sha: "", targets: ["a"] } });
|
|
63
|
+
const b = conePlanDigest(d, { base: "origin/main", record: { base: "origin/main", base_sha: "", targets: ["b"] } });
|
|
64
|
+
expect(a).not.toBe(d);
|
|
65
|
+
expect(a).not.toBe(b);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("reads from another root's state (#4545)", () => {
|
|
70
|
+
test("fills each read once per upstream, and waits on a missing output", async () => {
|
|
71
|
+
let reads = 0;
|
|
72
|
+
const filled = await fillStateReads(
|
|
73
|
+
[
|
|
74
|
+
{ var: "vpc_id", root: "network", output: ["vpc", "id"] },
|
|
75
|
+
{ var: "subnets", root: "network", output: ["subnets"] },
|
|
76
|
+
{ var: "db", root: "data", output: ["endpoint"] },
|
|
77
|
+
],
|
|
78
|
+
async (root) => {
|
|
79
|
+
reads++;
|
|
80
|
+
return root === "network" ? { vpc: { id: "vpc-1" }, subnets: ["a", "b"] } : {};
|
|
81
|
+
},
|
|
82
|
+
);
|
|
83
|
+
expect(reads).toBe(2);
|
|
84
|
+
expect(filled.vars).toEqual({ vpc_id: "vpc-1", subnets: ["a", "b"] });
|
|
85
|
+
expect(filled.waiting.map((w) => w.why)).toEqual(["has no state yet"]);
|
|
86
|
+
expect(waitingReadsReason(filled.waiting)).toBe("not planned: it reads db from data, which has no state yet; apply data first");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("a state that cannot be read is an error, not a wait", async () => {
|
|
90
|
+
const filled = await fillStateReads([{ var: "x", root: "up", output: ["x"] }], async () => {
|
|
91
|
+
throw new Error("init failed");
|
|
92
|
+
});
|
|
93
|
+
expect(filled.errors).toEqual(["it reads x from up, whose state could not be read: init failed"]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("checks the reads, reads a nested output, and maps Atmos edges", () => {
|
|
97
|
+
expect(stateReadsProblems([{ var: "a", root: "r", output: ["o"] }, { var: "a", root: "", output: [] }])).toHaveLength(3);
|
|
98
|
+
expect(readOutputValue({ o: { k: 1 } }, ["o", "k"])).toEqual({ value: 1 });
|
|
99
|
+
expect(readOutputValue({ o: { k: 1 } }, ["o", "j"])).toBeUndefined();
|
|
100
|
+
expect(atmosStateReads({ reads: [{ var: "v", upstream: "dev/vpc", output: ["id"], function: "!terraform.state vpc id" }] }, (u) => u.replace("/", "-"))).toEqual([{ var: "v", root: "dev-vpc", output: ["id"] }]);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe("cost hold preset (#4545)", () => {
|
|
105
|
+
test("prices each root's plan document with infracost", () => {
|
|
106
|
+
expect(terraformCostHold({ approveAbove: 100 })).toEqual({ command: [...INFRACOST_ARGV], approveAbove: 100, format: "infracost" });
|
|
107
|
+
expect(() => terraformCostHold({ approveAbove: 1, command: ["infracost"] })).toThrow(/\{planJson\}/);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module "vpc" {
|
|
2
|
+
source = "registry.acme.dev/acme/vpc/aws"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
module "eks" {
|
|
7
|
+
source = "acme/eks/aws"
|
|
8
|
+
version = "~> 2.0"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module "net" {
|
|
12
|
+
source = "git::https://git.acme.dev/acme/net.git?ref=v1.2.0"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module "cache" {
|
|
16
|
+
source = "oci://registry.acme.dev/modules/cache?tag=3.1.0"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module "local" {
|
|
20
|
+
source = "./modules/local"
|
|
21
|
+
}
|
package/src/pin/bundle.test.ts
CHANGED
|
@@ -38,6 +38,8 @@ describe("the pin bundle", () => {
|
|
|
38
38
|
expect(inputs.filter((i) => pattern.test(i)), String(pattern)).toEqual([]);
|
|
39
39
|
}
|
|
40
40
|
expect(inputs.map((i) => i.replace(/^.*?(packages|lexicons)\//, "$1/")).sort()).toEqual([
|
|
41
|
+
// The one config-file lister (chant#4457): node built-ins only, kept apart from the HCL parser.
|
|
42
|
+
"lexicons/terraform/src/hcl/files.ts",
|
|
41
43
|
"lexicons/terraform/src/pin/edit.ts",
|
|
42
44
|
"lexicons/terraform/src/pin/forge.ts",
|
|
43
45
|
"lexicons/terraform/src/pin/index.ts",
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The consumer scan (chant#4599): registry, git ref and OCI tag pins, a
|
|
3
|
+
* constraint left unpinned, a Terragrunt unit's `terraform { source }`, and
|
|
4
|
+
* local calls left out, all read from source files.
|
|
5
|
+
*/
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { describe, expect, test } from "vitest";
|
|
8
|
+
import { isLocalSource, moduleConsumers, normalizeModuleSource } from "./consumers";
|
|
9
|
+
|
|
10
|
+
const REPO = join(import.meta.dirname, "__fixtures__", "consumers");
|
|
11
|
+
|
|
12
|
+
describe("moduleConsumers", () => {
|
|
13
|
+
test("lists every published module call in the roots, sorted", async () => {
|
|
14
|
+
const calls = await moduleConsumers(REPO, ["live/prod", "live/dev", "units/vpc"]);
|
|
15
|
+
expect(calls.map((c) => [c.root, c.address, c.module, c.pin])).toEqual([
|
|
16
|
+
["live/dev", "module.vpc", "registry.acme.dev/acme/vpc/aws", "v2.0.0"],
|
|
17
|
+
["live/prod", "module.cache", "oci://registry.acme.dev/modules/cache", "3.1.0"],
|
|
18
|
+
["live/prod", "module.eks", "registry.terraform.io/acme/eks/aws", null],
|
|
19
|
+
["live/prod", "module.net", "git::https://git.acme.dev/acme/net.git", "v1.2.0"],
|
|
20
|
+
["live/prod", "module.vpc", "registry.acme.dev/acme/vpc/aws", "1.0.0"],
|
|
21
|
+
["units/vpc", "terragrunt.terraform", "registry.acme.dev/acme/vpc/aws", "1.1.0"],
|
|
22
|
+
]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("keeps where each pin sits and why a call has none", async () => {
|
|
26
|
+
const calls = await moduleConsumers(REPO, ["live/prod", "units/vpc"]);
|
|
27
|
+
const by = (a: string, root = "live/prod") => calls.find((c) => c.address === a && c.root === root)!;
|
|
28
|
+
expect(by("module.vpc")).toMatchObject({ kind: "terraform", file: "live/prod/main.tf", at: "version", source: "registry.acme.dev/acme/vpc/aws" });
|
|
29
|
+
expect(by("module.net")).toMatchObject({ at: "source", param: "ref" });
|
|
30
|
+
expect(by("module.cache")).toMatchObject({ at: "source", param: "tag" });
|
|
31
|
+
expect(by("module.eks").unpinned).toContain("constraint");
|
|
32
|
+
expect(by("terragrunt.terraform", "units/vpc")).toMatchObject({ kind: "terragrunt", file: "units/vpc/terragrunt.hcl", at: "source", param: "version" });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("leaves local calls out", async () => {
|
|
36
|
+
const calls = await moduleConsumers(REPO, ["live/prod"]);
|
|
37
|
+
expect(calls.find((c) => c.address === "module.local")).toBeUndefined();
|
|
38
|
+
expect(isLocalSource("./modules/local")).toBe(true);
|
|
39
|
+
expect(isLocalSource("../shared")).toBe(true);
|
|
40
|
+
expect(isLocalSource("acme/vpc/aws")).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("normalizeModuleSource", () => {
|
|
45
|
+
test.each([
|
|
46
|
+
["acme/vpc/aws", "registry.terraform.io/acme/vpc/aws"],
|
|
47
|
+
["acme/vpc/aws//modules/x", "registry.terraform.io/acme/vpc/aws//modules/x"],
|
|
48
|
+
["Registry.Acme.dev/acme/vpc/aws", "registry.acme.dev/acme/vpc/aws"],
|
|
49
|
+
["tfr:///terraform-aws-modules/vpc/aws", "registry.terraform.io/terraform-aws-modules/vpc/aws"],
|
|
50
|
+
["tfr://registry.acme.dev/acme/vpc/aws", "registry.acme.dev/acme/vpc/aws"],
|
|
51
|
+
["github.com/acme/vpc", "github.com/acme/vpc"],
|
|
52
|
+
["git::https://git.acme.dev/acme/net.git", "git::https://git.acme.dev/acme/net.git"],
|
|
53
|
+
["oci://registry.acme.dev/modules/cache", "oci://registry.acme.dev/modules/cache"],
|
|
54
|
+
])("%s is %s", (from, to) => {
|
|
55
|
+
expect(normalizeModuleSource(from)).toBe(to);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a repo calls its modules (chant#4599, terragucci#1289 bundle B): one
|
|
3
|
+
* entry per module call in the roots given, Terragrunt units included, read
|
|
4
|
+
* from the source files with chant's HCL parse and never from a plan, so the
|
|
5
|
+
* scan runs in a check job before anything is planned.
|
|
6
|
+
*
|
|
7
|
+
* Each call carries its root, file and block path, the source as written,
|
|
8
|
+
* the module it names with the pin taken out and normalised
|
|
9
|
+
* ({@link normalizeModuleSource}), and the pin `readModulePin` (./source.ts)
|
|
10
|
+
* reads. A local call (`./`, `../`) names no published module and is left
|
|
11
|
+
* out. The release catalog lists these as a version's consumers, and
|
|
12
|
+
* `deprecatedPins` (../registry/deprecated.ts) checks them against it.
|
|
13
|
+
*
|
|
14
|
+
* The `terragucci_module_pin` metric keeps reading the plan; this is the
|
|
15
|
+
* source-side view of the same calls. The normal form is chant#3924's
|
|
16
|
+
* (../registry/source-forms.ts), shared with `usedSources` (../sources.ts).
|
|
17
|
+
*/
|
|
18
|
+
import { join, relative } from "node:path";
|
|
19
|
+
import type { Hcl2Json } from "@intentius/chant/terraform/hcl2json";
|
|
20
|
+
import { MODULE_TYPE, TERRAGRUNT_SOURCE_TYPE, parseTerraformRootDir, type TerraformEntity } from "../hcl/parse";
|
|
21
|
+
import { normalizeModuleSource, PUBLIC_REGISTRY_HOST } from "../registry/source-forms";
|
|
22
|
+
import { readModulePin, type ModuleCallPin } from "./source";
|
|
23
|
+
|
|
24
|
+
export { normalizeModuleSource, PUBLIC_REGISTRY_HOST };
|
|
25
|
+
|
|
26
|
+
/** One module call in a consuming repo. */
|
|
27
|
+
export interface ModuleConsumer {
|
|
28
|
+
/** The root (or Terragrunt unit) directory, from the repo's top, `/`-separated. */
|
|
29
|
+
root: string;
|
|
30
|
+
/** The file holding the call, from the repo's top. */
|
|
31
|
+
file: string;
|
|
32
|
+
/** The block: `module.<name>`, or `terragrunt.terraform` for a unit's source. */
|
|
33
|
+
address: string;
|
|
34
|
+
/** `terraform` for a module block, `terragrunt` for a unit's `terraform { source }`. */
|
|
35
|
+
kind: "terraform" | "terragrunt";
|
|
36
|
+
/** The `source` as written. */
|
|
37
|
+
source: string;
|
|
38
|
+
/** The module the call names: the source with the pin taken out, normalised. */
|
|
39
|
+
module: string;
|
|
40
|
+
/** The pinned version, tag, digest or ref, or null when the call pins none. */
|
|
41
|
+
pin: string | null;
|
|
42
|
+
/** Where the pin is, as `readModulePin` reads it. */
|
|
43
|
+
at: ModuleCallPin["at"];
|
|
44
|
+
param?: ModuleCallPin["param"];
|
|
45
|
+
/** Why there is no pin, when `pin` is null. */
|
|
46
|
+
unpinned?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** A source names a local directory, not a published module. */
|
|
50
|
+
export function isLocalSource(source: string): boolean {
|
|
51
|
+
return source.startsWith("./") || source.startsWith("../");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function str(v: unknown): string | undefined {
|
|
55
|
+
return typeof v === "string" ? v : undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Every module call in `roots` (directories from `repo`), sorted by root,
|
|
60
|
+
* file and address. A root whose files cannot be parsed throws, naming the
|
|
61
|
+
* root, so a check job fails loudly instead of listing fewer consumers.
|
|
62
|
+
*/
|
|
63
|
+
export async function moduleConsumers(repo: string, roots: readonly string[], parser?: Hcl2Json): Promise<ModuleConsumer[]> {
|
|
64
|
+
const out: ModuleConsumer[] = [];
|
|
65
|
+
for (const root of [...new Set(roots)].sort()) {
|
|
66
|
+
const dir = join(repo, root);
|
|
67
|
+
const rel = relative(repo, dir).split("\\").join("/") || ".";
|
|
68
|
+
let entities: TerraformEntity[];
|
|
69
|
+
try {
|
|
70
|
+
entities = [...(await parseTerraformRootDir(dir, "consumers", parser)).values()] as TerraformEntity[];
|
|
71
|
+
} catch (e) {
|
|
72
|
+
throw new Error(`${rel}: ${(e as Error).message}`);
|
|
73
|
+
}
|
|
74
|
+
for (const e of entities) {
|
|
75
|
+
if (e.entityType !== MODULE_TYPE && e.entityType !== TERRAGRUNT_SOURCE_TYPE) continue;
|
|
76
|
+
const source = str(e.props.body.source);
|
|
77
|
+
if (source === undefined || isLocalSource(source)) continue;
|
|
78
|
+
const version = e.entityType === MODULE_TYPE ? str(e.props.body.version) : undefined;
|
|
79
|
+
const pin = readModulePin(source, version);
|
|
80
|
+
out.push({
|
|
81
|
+
root: rel,
|
|
82
|
+
file: rel === "." ? e.props.file : `${rel}/${e.props.file}`,
|
|
83
|
+
address: e.props.address,
|
|
84
|
+
kind: e.entityType === MODULE_TYPE ? "terraform" : "terragrunt",
|
|
85
|
+
source,
|
|
86
|
+
module: normalizeModuleSource(pin.module),
|
|
87
|
+
pin: pin.pin,
|
|
88
|
+
at: pin.at,
|
|
89
|
+
...(pin.param ? { param: pin.param } : {}),
|
|
90
|
+
...(pin.unpinned ? { unpinned: pin.unpinned } : {}),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return out.sort((a, b) => a.root.localeCompare(b.root) || a.file.localeCompare(b.file) || a.address.localeCompare(b.address));
|
|
95
|
+
}
|
package/src/pin/edit.ts
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
import type { Hcl2Json } from "@intentius/chant/terraform/hcl2json";
|
|
26
26
|
import { movePin, type PinMove } from "./source";
|
|
27
|
+
import { isJsonConfigFile, jsonConfigTree } from "../hcl/files";
|
|
27
28
|
import { escapeHcl, scanBlocks, unescapeHcl, type ScannedAttribute, type ScannedBlock } from "./scan";
|
|
28
29
|
|
|
29
30
|
/** What to move: a module, named by its source without the pin, from one pin to another. */
|
|
@@ -99,6 +100,7 @@ interface Splice {
|
|
|
99
100
|
* is reported with its reason and left as it was.
|
|
100
101
|
*/
|
|
101
102
|
export async function editPins(text: string, file: string, request: PinRequest, parser: Hcl2Json): Promise<PinEditResult> {
|
|
103
|
+
if (isJsonConfigFile(file)) return jsonPins(text, file, request);
|
|
102
104
|
const terragrunt = isTerragruntFile(file);
|
|
103
105
|
const tree = (await parser.parse(file, text)) as Tree;
|
|
104
106
|
const blocks = scanBlocks(text, file);
|
|
@@ -155,3 +157,31 @@ export async function editPins(text: string, file: string, request: PinRequest,
|
|
|
155
157
|
function rewriteLiteral(raw: string, was: string, value: string): string {
|
|
156
158
|
return raw === was ? value : escapeHcl(value);
|
|
157
159
|
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* The calls of the module in a `.tf.json` or `.tofu.json` file (chant#4457).
|
|
163
|
+
* The pin edit splices HCL text and has no splice for JSON syntax, so a call
|
|
164
|
+
* that would move is refused by name and the file is left as it is; a call
|
|
165
|
+
* already at the new pin, or at another one, reads as it does in HCL.
|
|
166
|
+
*/
|
|
167
|
+
function jsonPins(text: string, file: string, request: PinRequest): PinEditResult {
|
|
168
|
+
const calls: PinCallResult[] = [];
|
|
169
|
+
let tree: Record<string, unknown>;
|
|
170
|
+
try {
|
|
171
|
+
tree = jsonConfigTree(text);
|
|
172
|
+
} catch (err) {
|
|
173
|
+
return { content: text, calls: [{ outcome: "refused", reason: `not JSON the binary reads: ${err instanceof Error ? err.message : String(err)}`, file, call: "(file)" }] };
|
|
174
|
+
}
|
|
175
|
+
const modules = (tree.module && typeof tree.module === "object" ? tree.module : {}) as Record<string, unknown>;
|
|
176
|
+
for (const [name, bodies] of Object.entries(modules)) {
|
|
177
|
+
for (const body of Array.isArray(bodies) ? (bodies as Record<string, unknown>[]) : []) {
|
|
178
|
+
if (typeof body.source !== "string") continue;
|
|
179
|
+
const move = movePin(body.source, typeof body.version === "string" ? body.version : undefined, request);
|
|
180
|
+
if (!move) continue;
|
|
181
|
+
const at = { file, call: `module.${name}` };
|
|
182
|
+
if (move.outcome === "moved") calls.push({ outcome: "refused", reason: "the call is in Terraform's JSON syntax, which the pin edit does not write; move its pin by hand", ...at });
|
|
183
|
+
else calls.push({ ...move, ...at });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return { content: text, calls };
|
|
187
|
+
}
|
package/src/pin/rollout.ts
CHANGED
|
@@ -48,6 +48,7 @@ import { editPins, type PinCallResult, type PinEditResult, type PinRequest } fro
|
|
|
48
48
|
import { checkPinRequest, moduleOf } from "./source";
|
|
49
49
|
import { planPinWaves, restrictWaves, terragruntDependencies, type PinRoot, type PinWave } from "./waves";
|
|
50
50
|
import { ghPinForge, type PinForge } from "./forge";
|
|
51
|
+
import { isConfigFileFor, listConfigFilesSafe } from "../hcl/files";
|
|
51
52
|
|
|
52
53
|
const execFileAsync = /* @__PURE__ */ promisify(execFile);
|
|
53
54
|
|
|
@@ -83,6 +84,8 @@ export interface PinRolloutOptions extends PinRequest {
|
|
|
83
84
|
editTs?: TsPinEditor;
|
|
84
85
|
/** Markdown appended to a wave's PR body, such as the grouped plan summary of #3188 once a caller has the wave's plans. */
|
|
85
86
|
planSummary?: (wave: PinWave) => Promise<string | undefined>;
|
|
87
|
+
/** The binary the roots run: `tofu` or `choudoufu` reads `.tofu` files too (chant#4457). Default `terraform`. */
|
|
88
|
+
binary?: string;
|
|
86
89
|
}
|
|
87
90
|
|
|
88
91
|
/** Where one root's pin stood on the base branch. */
|
|
@@ -169,22 +172,24 @@ export function pinWaveBranch(request: PinRequest, wave: number): string {
|
|
|
169
172
|
return `chant/pin/${slug(request.module)}/${slug(request.to)}/wave-${wave}`;
|
|
170
173
|
}
|
|
171
174
|
|
|
172
|
-
/**
|
|
173
|
-
|
|
175
|
+
/**
|
|
176
|
+
* The files the pin edit reads in a root directory: the configuration files
|
|
177
|
+
* the root's binary reads (the lexicon's one lister, chant#4457) and its
|
|
178
|
+
* `terragrunt.hcl`.
|
|
179
|
+
*/
|
|
180
|
+
function rootFiles(worktree: string, root: string, binary?: string): string[] {
|
|
174
181
|
const dir = join(worktree, root);
|
|
175
182
|
if (!existsSync(dir)) return [];
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
.sort()
|
|
179
|
-
.map((f) => posix.join(root, f));
|
|
183
|
+
const names = [...listConfigFilesSafe(dir, binary), ...(existsSync(join(dir, "terragrunt.hcl")) ? ["terragrunt.hcl"] : [])];
|
|
184
|
+
return names.sort().map((f) => posix.join(root, f));
|
|
180
185
|
}
|
|
181
186
|
|
|
182
|
-
/** Every directory under the worktree with a
|
|
183
|
-
function candidateRoots(worktree: string): string[] {
|
|
187
|
+
/** Every directory under the worktree with a configuration file the binary reads, or a `terragrunt.hcl`. */
|
|
188
|
+
function candidateRoots(worktree: string, binary?: string): string[] {
|
|
184
189
|
const out: string[] = [];
|
|
185
190
|
const walk = (rel: string) => {
|
|
186
191
|
const entries = readdirSync(join(worktree, rel), { withFileTypes: true });
|
|
187
|
-
if (entries.some((e) => e.isFile() && (e.name
|
|
192
|
+
if (entries.some((e) => e.isFile() && (isConfigFileFor(e.name, binary) || e.name === "terragrunt.hcl"))) out.push(rel || ".");
|
|
188
193
|
for (const e of entries) if (e.isDirectory() && !SKIP_DIRS.has(e.name)) walk(rel ? posix.join(rel, e.name) : e.name);
|
|
189
194
|
};
|
|
190
195
|
walk("");
|
|
@@ -201,7 +206,7 @@ async function editRoot(worktree: string, root: PinRoot, request: PinRequest, op
|
|
|
201
206
|
return { state: { root: root.root, state: "refused", files, calls, reason: `generated from ${root.tsSource}, and this caller has no TypeScript editor` }, edits };
|
|
202
207
|
}
|
|
203
208
|
} else {
|
|
204
|
-
files = rootFiles(worktree, root.root);
|
|
209
|
+
files = rootFiles(worktree, root.root, options.binary);
|
|
205
210
|
}
|
|
206
211
|
for (const file of files) {
|
|
207
212
|
const path = join(worktree, file);
|
|
@@ -302,7 +307,7 @@ export async function runPinRollout(options: PinRolloutOptions): Promise<PinRoll
|
|
|
302
307
|
await run("git", ["worktree", "add", "--quiet", "--detach", worktree, baseRef], repo);
|
|
303
308
|
try {
|
|
304
309
|
// 1. Every root's pin on the base branch.
|
|
305
|
-
const declared = (options.roots ?? candidateRoots(worktree).map((root): PinRoot => ({ root }))).map((r) => ({
|
|
310
|
+
const declared = (options.roots ?? candidateRoots(worktree, options.binary).map((root): PinRoot => ({ root }))).map((r) => ({
|
|
306
311
|
...r,
|
|
307
312
|
root: dirOf(r.root),
|
|
308
313
|
...(r.dependsOn ? { dependsOn: r.dependsOn.map(dirOf) } : {}),
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `terraformPlanShape` (#4436), against the recorded `show -json` plans of
|
|
3
|
+
* terraform, tofu and choudoufu in `__fixtures__/change-set/` (see
|
|
4
|
+
* change-set.test.ts for how they were recorded).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { describe, expect, test } from "vitest";
|
|
10
|
+
import { planShapeCovers, planShapeDigest, type PlanShapeEntry } from "@intentius/chant/components/covered-round";
|
|
11
|
+
import { terraformPlanShape } from "./plan-shape";
|
|
12
|
+
|
|
13
|
+
const DIR = join(import.meta.dirname, "__fixtures__", "change-set");
|
|
14
|
+
const read = (name: string): unknown => JSON.parse(readFileSync(join(DIR, name), "utf-8"));
|
|
15
|
+
|
|
16
|
+
const T = "terraform_data";
|
|
17
|
+
|
|
18
|
+
/** The fixture plan: a no-op, an update, an update of a sensitive value, a delete, a replace, two creates under a module. */
|
|
19
|
+
const FIXTURE_ENTRIES: PlanShapeEntry[] = [
|
|
20
|
+
{ logicalId: "module.app.terraform_data.instance[0]", action: "create", type: T, properties: ["id", "input", "output"], unknown: ["id", "output"] },
|
|
21
|
+
{ logicalId: "module.app.terraform_data.instance[1]", action: "create", type: T, properties: ["id", "input", "output"], unknown: ["id", "output"] },
|
|
22
|
+
{ logicalId: "terraform_data.config", action: "update", replacement: "false", type: T, properties: ["input", "output"], unknown: ["output"] },
|
|
23
|
+
{ logicalId: "terraform_data.old", action: "delete", type: T, properties: [], unknown: [] },
|
|
24
|
+
{ logicalId: "terraform_data.secret", action: "update", replacement: "false", type: T, properties: ["input", "output"], unknown: ["output"] },
|
|
25
|
+
{ logicalId: "terraform_data.worker", action: "replace", replacement: "true", type: T, properties: ["id", "output", "triggers_replace"], unknown: ["id", "output"] },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
describe("terraformPlanShape, on real plans", () => {
|
|
29
|
+
test.each(["terraform", "tofu", "choudoufu"] as const)("%s: one entry per change, none for the no-op", (planner) => {
|
|
30
|
+
expect(terraformPlanShape(read(`${planner}.plan.json`), "estate")).toEqual({ member: "estate", entries: FIXTURE_ENTRIES, unknownInputs: [] });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("a table of the actions, replacement and attributes the fixture gives", () => {
|
|
34
|
+
const shape = terraformPlanShape(read("terraform.plan.json"), "estate");
|
|
35
|
+
expect(shape.entries.map((e) => [e.logicalId, e.action, e.replacement, e.properties, e.unknown])).toEqual(
|
|
36
|
+
FIXTURE_ENTRIES.map((e) => [e.logicalId, e.action, e.replacement, e.properties, e.unknown]),
|
|
37
|
+
);
|
|
38
|
+
// terraform_data.keep is a no-op, so it has no entry.
|
|
39
|
+
expect(shape.entries.some((e) => e.logicalId === "terraform_data.keep")).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("the choudoufu set plan's embedded plan gives a shape too", () => {
|
|
43
|
+
const doc = read("choudoufu-set-plan.json") as { roots: Array<{ plan?: unknown }> };
|
|
44
|
+
expect(terraformPlanShape(doc.roots[0]!.plan, "estates/e01")).toEqual({
|
|
45
|
+
member: "estates/e01",
|
|
46
|
+
entries: [{ logicalId: "terraform_data.x", action: "create", properties: [], unknown: [] }],
|
|
47
|
+
unknownInputs: [],
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("a forget has an entry with no properties", () => {
|
|
52
|
+
const shape = terraformPlanShape(read("terraform.import-forget.plan.json"), "estate");
|
|
53
|
+
expect(shape.entries.find((e) => e.logicalId === "terraform_data.a")).toMatchObject({ action: "forget", properties: [], unknown: [] });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("the digest of the same plan JSON is equal across two runs", () => {
|
|
57
|
+
const one = planShapeDigest(terraformPlanShape(read("terraform.plan.json"), "estate"));
|
|
58
|
+
const two = planShapeDigest(terraformPlanShape(read("terraform.plan.json"), "estate"));
|
|
59
|
+
expect(one).toBe(two);
|
|
60
|
+
expect(planShapeDigest(terraformPlanShape(read("tofu.plan.json"), "estate"))).toBe(one);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("a shape is covered by itself, and not by a plan that adds a resource", () => {
|
|
64
|
+
const first = terraformPlanShape(read("terraform.plan.json"), "estate");
|
|
65
|
+
expect(planShapeCovers(first, terraformPlanShape(read("terraform.plan.json"), "estate"))).toBeUndefined();
|
|
66
|
+
const plan = read("terraform.plan.json") as { resource_changes: unknown[] };
|
|
67
|
+
const more = { ...plan, resource_changes: [...plan.resource_changes, { address: "terraform_data.extra", type: T, change: { actions: ["create"], before: null, after: { input: "x" }, after_unknown: {} } }] };
|
|
68
|
+
expect(planShapeCovers(first, terraformPlanShape(more, "estate"))).toContain("terraform_data.extra");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("unknown and known inputs are the caller's, and enter the digest", () => {
|
|
72
|
+
const plan = read("terraform.plan.json");
|
|
73
|
+
const shape = terraformPlanShape(plan, "app", { unknownInputs: ["a.id"], knownInputs: { "net.cidr": "10.0.0.0/16" } });
|
|
74
|
+
expect(shape.unknownInputs).toEqual(["a.id"]);
|
|
75
|
+
expect(shape.knownInputs).toEqual({ "net.cidr": "10.0.0.0/16" });
|
|
76
|
+
expect(planShapeDigest(shape)).not.toBe(planShapeDigest(terraformPlanShape(plan, "app")));
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
/** A plan with one update whose `replace_paths` and `after_unknown` are as given. */
|
|
81
|
+
function updatePlan(change: Record<string, unknown>, extra: Record<string, unknown> = {}): unknown {
|
|
82
|
+
return { resource_changes: [{ address: "aws_instance.web", type: "aws_instance", ...extra, change: { actions: ["update"], ...change } }] };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
describe("terraformPlanShape replacement", () => {
|
|
86
|
+
test("an update with a replace_paths entry replaces", () => {
|
|
87
|
+
const plan = updatePlan({ before: { ami: "a" }, after: { ami: "b" }, after_unknown: {}, replace_paths: [["ami"]] });
|
|
88
|
+
expect(terraformPlanShape(plan, "m").entries[0]).toMatchObject({ action: "update", replacement: "true", type: "aws_instance", properties: ["ami"] });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("an update whose replaced path is unknown after apply is conditional", () => {
|
|
92
|
+
const plan = updatePlan({ before: { ami: "a" }, after: {}, after_unknown: { ami: true }, replace_paths: [["ami"]] });
|
|
93
|
+
expect(terraformPlanShape(plan, "m").entries[0]).toMatchObject({ replacement: "conditional", properties: ["ami"], unknown: ["ami"] });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("a nested replaced path is unknown when its own leaf is", () => {
|
|
97
|
+
const plan = updatePlan({ before: { tags: { env: "a" } }, after: { tags: {} }, after_unknown: { tags: { env: true } }, replace_paths: [["tags", "env"]] });
|
|
98
|
+
expect(terraformPlanShape(plan, "m").entries[0]!.replacement).toBe("conditional");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("one known replaced path is enough to replace", () => {
|
|
102
|
+
const plan = updatePlan({
|
|
103
|
+
before: { ami: "a", zone: "x" },
|
|
104
|
+
after: { zone: "y" },
|
|
105
|
+
after_unknown: { ami: true },
|
|
106
|
+
replace_paths: [["ami"], ["zone"]],
|
|
107
|
+
});
|
|
108
|
+
expect(terraformPlanShape(plan, "m").entries[0]!.replacement).toBe("true");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("an update with no replace_paths replaces nothing", () => {
|
|
112
|
+
const plan = updatePlan({ before: { ami: "a" }, after: { ami: "b" }, after_unknown: {} });
|
|
113
|
+
expect(terraformPlanShape(plan, "m").entries[0]!.replacement).toBe("false");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("a delete and create pair with no replace_paths replaces", () => {
|
|
117
|
+
const plan = { resource_changes: [{ address: "x.y", type: "x", change: { actions: ["create", "delete"], before: { a: 1 }, after: { a: 2 }, after_unknown: {} } }] };
|
|
118
|
+
expect(terraformPlanShape(plan, "m").entries[0]).toMatchObject({ action: "replace", replacement: "true" });
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe("terraformPlanShape, edge cases", () => {
|
|
123
|
+
test("a deposed object's logical id carries its key", () => {
|
|
124
|
+
const plan = updatePlan({ before: { a: 1 }, after: null, actions: ["delete"] }, { deposed: "0123abcd" });
|
|
125
|
+
const [entry] = terraformPlanShape(plan, "m").entries;
|
|
126
|
+
expect(entry).toMatchObject({ logicalId: "aws_instance.web#0123abcd", action: "delete" });
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("read and no-op entries are left out", () => {
|
|
130
|
+
const plan = {
|
|
131
|
+
resource_changes: [
|
|
132
|
+
{ address: "data.x.a", type: "x", change: { actions: ["read"] } },
|
|
133
|
+
{ address: "x.b", type: "x", change: { actions: ["no-op"], before: {}, after: {} } },
|
|
134
|
+
],
|
|
135
|
+
};
|
|
136
|
+
expect(terraformPlanShape(plan, "m").entries).toEqual([]);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("anything that is not a plan has no entries", () => {
|
|
140
|
+
expect(terraformPlanShape(undefined, "m")).toEqual({ member: "m", entries: [], unknownInputs: [] });
|
|
141
|
+
expect(terraformPlanShape({}, "m").entries).toEqual([]);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("sensitive values never enter the shape", () => {
|
|
145
|
+
const shape = terraformPlanShape(read("terraform.plan.json"), "estate");
|
|
146
|
+
expect(JSON.stringify(shape)).not.toContain("s3cr3t");
|
|
147
|
+
});
|
|
148
|
+
});
|