@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,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The zip of an attested module archive (chant#4605, terragucci#1299 ruling
|
|
3
|
+
* A). Artifactory takes a module only as a zip and Nexus recommends one, so
|
|
4
|
+
* their targets upload the same files the bucket registry's `.tar.gz`
|
|
5
|
+
* carries, at the same paths with the same modes, repacked as a zip. Nothing
|
|
6
|
+
* is added to or dropped from the module.
|
|
7
|
+
*
|
|
8
|
+
* The repack is deterministic: entries keep the tarball's order and mtimes,
|
|
9
|
+
* so one archive always gives the same zip bytes. The zip's archive comment
|
|
10
|
+
* carries the attested content digest and the revision
|
|
11
|
+
* (`chant-module content=sha256:<hex> revision=<sha>`), which is how a
|
|
12
|
+
* target's `held` tells, from the zip a host gives back, which attested
|
|
13
|
+
* archive it was made from. A zip with no such comment (one uploaded by
|
|
14
|
+
* hand) is judged by its own digest, so it never passes for the release's.
|
|
15
|
+
*
|
|
16
|
+
* No zip64: an archive past 4 GiB or 65535 entries is refused, far above
|
|
17
|
+
* either host's limit (Nexus takes 100 MB per module).
|
|
18
|
+
*/
|
|
19
|
+
import { deflateRawSync, gunzipSync, inflateRawSync } from "node:zlib";
|
|
20
|
+
import { PublishTargetError } from "@intentius/chant/lifecycle/release-publish";
|
|
21
|
+
|
|
22
|
+
/** One entry of a module tarball. */
|
|
23
|
+
export interface ModuleArchiveEntry {
|
|
24
|
+
path: string;
|
|
25
|
+
type: "file" | "dir";
|
|
26
|
+
mode: number;
|
|
27
|
+
/** Seconds since the epoch. */
|
|
28
|
+
mtime: number;
|
|
29
|
+
data: Buffer;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const field = (b: Buffer, at: number, len: number): string => {
|
|
33
|
+
const raw = b.subarray(at, at + len);
|
|
34
|
+
const end = raw.indexOf(0);
|
|
35
|
+
return raw.subarray(0, end < 0 ? raw.length : end).toString("utf-8");
|
|
36
|
+
};
|
|
37
|
+
const octal = (b: Buffer, at: number, len: number): number => parseInt(field(b, at, len).trim() || "0", 8);
|
|
38
|
+
|
|
39
|
+
function paxRecords(data: Buffer): Record<string, string> {
|
|
40
|
+
const out: Record<string, string> = {};
|
|
41
|
+
let at = 0;
|
|
42
|
+
while (at < data.length) {
|
|
43
|
+
const space = data.indexOf(0x20, at);
|
|
44
|
+
if (space < 0) break;
|
|
45
|
+
const len = parseInt(data.subarray(at, space).toString("utf-8"), 10);
|
|
46
|
+
if (!Number.isFinite(len) || len <= 0) break;
|
|
47
|
+
const record = data.subarray(space + 1, at + len - 1).toString("utf-8");
|
|
48
|
+
const eq = record.indexOf("=");
|
|
49
|
+
if (eq > 0) out[record.slice(0, eq)] = record.slice(eq + 1);
|
|
50
|
+
at += len;
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const invalid = (message: string) => new PublishTargetError("publish-archive-invalid", message, "Publish the .tar.gz the bucket registry serves, with the module's files at its root.");
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The entries of a module's gzip tarball (ustar, pax and GNU long names), with
|
|
59
|
+
* `./` cut from each path. A link or a device in the archive is refused: a
|
|
60
|
+
* zip of the module could not carry it as the tarball does.
|
|
61
|
+
*/
|
|
62
|
+
export function moduleArchiveEntries(archive: Uint8Array): ModuleArchiveEntry[] {
|
|
63
|
+
let tar: Buffer;
|
|
64
|
+
try {
|
|
65
|
+
tar = gunzipSync(archive);
|
|
66
|
+
} catch {
|
|
67
|
+
throw invalid("the module archive is not a gzip tarball");
|
|
68
|
+
}
|
|
69
|
+
const out: ModuleArchiveEntry[] = [];
|
|
70
|
+
let at = 0;
|
|
71
|
+
let pax: Record<string, string> = {};
|
|
72
|
+
let longName: string | undefined;
|
|
73
|
+
while (at + 512 <= tar.length) {
|
|
74
|
+
const header = tar.subarray(at, at + 512);
|
|
75
|
+
if (header.every((b) => b === 0)) break;
|
|
76
|
+
const size = octal(header, 124, 12);
|
|
77
|
+
const flag = String.fromCharCode(header[156] || 0x30);
|
|
78
|
+
const data = tar.subarray(at + 512, at + 512 + size);
|
|
79
|
+
at += 512 + Math.ceil(size / 512) * 512;
|
|
80
|
+
if (flag === "x") {
|
|
81
|
+
pax = paxRecords(data);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (flag === "L") {
|
|
85
|
+
longName = field(data, 0, data.length);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (flag === "g") continue;
|
|
89
|
+
const prefix = field(header, 345, 155);
|
|
90
|
+
const raw = pax.path ?? longName ?? (prefix ? `${prefix}/${field(header, 0, 100)}` : field(header, 0, 100));
|
|
91
|
+
const mtime = pax.mtime ? Math.floor(Number(pax.mtime)) : octal(header, 136, 12);
|
|
92
|
+
pax = {};
|
|
93
|
+
longName = undefined;
|
|
94
|
+
const path = raw.replace(/^(\.\/)+/, "").replace(/\/+$/, "");
|
|
95
|
+
if (!path || path === ".") continue;
|
|
96
|
+
if (path.startsWith("/") || path.split("/").includes("..")) throw invalid(`the module archive holds ${raw}, outside its root`);
|
|
97
|
+
const type = flag === "0" || flag === "\0" || flag === "7" ? "file" : flag === "5" ? "dir" : undefined;
|
|
98
|
+
if (!type) throw invalid(`the module archive holds ${path}, which is not a regular file or a directory; a zip of the module cannot carry it`);
|
|
99
|
+
out.push({ path, type, mode: octal(header, 100, 8) & 0o7777, mtime, data: type === "file" ? Buffer.from(data) : Buffer.alloc(0) });
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const CRC_TABLE = (() => {
|
|
105
|
+
const t = new Uint32Array(256);
|
|
106
|
+
for (let n = 0; n < 256; n++) {
|
|
107
|
+
let c = n;
|
|
108
|
+
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
109
|
+
t[n] = c >>> 0;
|
|
110
|
+
}
|
|
111
|
+
return t;
|
|
112
|
+
})();
|
|
113
|
+
|
|
114
|
+
/** The CRC-32 a zip entry carries. */
|
|
115
|
+
export function crc32(data: Uint8Array): number {
|
|
116
|
+
let c = 0xffffffff;
|
|
117
|
+
for (let i = 0; i < data.length; i++) c = CRC_TABLE[(c ^ data[i]!) & 0xff]! ^ (c >>> 8);
|
|
118
|
+
return (c ^ 0xffffffff) >>> 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** MS-DOS date and time, as a zip entry keeps them, in UTC and clamped to 1980. */
|
|
122
|
+
function dosTime(seconds: number): { time: number; date: number } {
|
|
123
|
+
const d = new Date(Math.max(seconds * 1000, Date.UTC(1980, 0, 1)));
|
|
124
|
+
return {
|
|
125
|
+
time: (d.getUTCHours() << 11) | (d.getUTCMinutes() << 5) | Math.floor(d.getUTCSeconds() / 2),
|
|
126
|
+
date: ((d.getUTCFullYear() - 1980) << 9) | ((d.getUTCMonth() + 1) << 5) | d.getUTCDate(),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** The archive comment that names the attested archive a zip was made from. */
|
|
131
|
+
export const ZIP_COMMENT_PREFIX = "chant-module";
|
|
132
|
+
|
|
133
|
+
export const moduleZipComment = (content: string, revision?: string): string => `${ZIP_COMMENT_PREFIX} content=${content}${revision ? ` revision=${revision}` : ""}`;
|
|
134
|
+
|
|
135
|
+
/** The content and revision a zip's archive comment names, when it is one {@link moduleZipComment} wrote. */
|
|
136
|
+
export function readModuleZipComment(comment: string): { content: string; revision?: string } | undefined {
|
|
137
|
+
if (!comment.startsWith(`${ZIP_COMMENT_PREFIX} `)) return undefined;
|
|
138
|
+
const content = /(?:^| )content=(sha256:[0-9a-f]{64})(?: |$)/.exec(comment)?.[1];
|
|
139
|
+
if (!content) return undefined;
|
|
140
|
+
const revision = /(?:^| )revision=([^ ]+)(?: |$)/.exec(comment)?.[1];
|
|
141
|
+
return { content, ...(revision ? { revision } : {}) };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** A zip of the given entries, with `comment` as its archive comment. Files are deflated. */
|
|
145
|
+
export function zipOf(entries: readonly ModuleArchiveEntry[], comment = ""): Buffer {
|
|
146
|
+
if (entries.length > 0xffff) throw invalid(`the module archive holds ${entries.length} entries; a zip without zip64 holds 65535`);
|
|
147
|
+
const locals: Buffer[] = [];
|
|
148
|
+
const centrals: Buffer[] = [];
|
|
149
|
+
let offset = 0;
|
|
150
|
+
for (const e of entries) {
|
|
151
|
+
const name = Buffer.from(e.type === "dir" ? `${e.path}/` : e.path, "utf-8");
|
|
152
|
+
const data = e.type === "file" ? e.data : Buffer.alloc(0);
|
|
153
|
+
const packed = e.type === "file" ? deflateRawSync(data, { level: 9 }) : Buffer.alloc(0);
|
|
154
|
+
const method = e.type === "file" ? 8 : 0;
|
|
155
|
+
const crc = crc32(data);
|
|
156
|
+
const { time, date } = dosTime(e.mtime);
|
|
157
|
+
if (packed.length > 0xfffffffe || data.length > 0xfffffffe || offset > 0xfffffffe) throw invalid("the module archive is past 4 GiB; a zip without zip64 holds less");
|
|
158
|
+
|
|
159
|
+
const local = Buffer.alloc(30);
|
|
160
|
+
local.writeUInt32LE(0x04034b50, 0);
|
|
161
|
+
local.writeUInt16LE(20, 4);
|
|
162
|
+
local.writeUInt16LE(0x0800, 6);
|
|
163
|
+
local.writeUInt16LE(method, 8);
|
|
164
|
+
local.writeUInt16LE(time, 10);
|
|
165
|
+
local.writeUInt16LE(date, 12);
|
|
166
|
+
local.writeUInt32LE(crc, 14);
|
|
167
|
+
local.writeUInt32LE(packed.length, 18);
|
|
168
|
+
local.writeUInt32LE(data.length, 22);
|
|
169
|
+
local.writeUInt16LE(name.length, 26);
|
|
170
|
+
local.writeUInt16LE(0, 28);
|
|
171
|
+
locals.push(local, name, packed);
|
|
172
|
+
|
|
173
|
+
const unixMode = (e.type === "dir" ? 0o040000 : 0o100000) | (e.mode & 0o7777 || (e.type === "dir" ? 0o755 : 0o644));
|
|
174
|
+
const central = Buffer.alloc(46);
|
|
175
|
+
central.writeUInt32LE(0x02014b50, 0);
|
|
176
|
+
central.writeUInt16LE((3 << 8) | 20, 4);
|
|
177
|
+
central.writeUInt16LE(20, 6);
|
|
178
|
+
central.writeUInt16LE(0x0800, 8);
|
|
179
|
+
central.writeUInt16LE(method, 10);
|
|
180
|
+
central.writeUInt16LE(time, 12);
|
|
181
|
+
central.writeUInt16LE(date, 14);
|
|
182
|
+
central.writeUInt32LE(crc, 16);
|
|
183
|
+
central.writeUInt32LE(packed.length, 20);
|
|
184
|
+
central.writeUInt32LE(data.length, 24);
|
|
185
|
+
central.writeUInt16LE(name.length, 28);
|
|
186
|
+
central.writeUInt16LE(0, 30);
|
|
187
|
+
central.writeUInt16LE(0, 32);
|
|
188
|
+
central.writeUInt16LE(0, 34);
|
|
189
|
+
central.writeUInt16LE(0, 36);
|
|
190
|
+
central.writeUInt32LE(((unixMode << 16) | (e.type === "dir" ? 0x10 : 0)) >>> 0, 38);
|
|
191
|
+
central.writeUInt32LE(offset, 42);
|
|
192
|
+
centrals.push(central, name);
|
|
193
|
+
offset += local.length + name.length + packed.length;
|
|
194
|
+
}
|
|
195
|
+
const cd = Buffer.concat(centrals);
|
|
196
|
+
const note = Buffer.from(comment, "utf-8");
|
|
197
|
+
if (note.length > 0xffff) throw invalid("the zip comment is longer than a zip carries");
|
|
198
|
+
const end = Buffer.alloc(22);
|
|
199
|
+
end.writeUInt32LE(0x06054b50, 0);
|
|
200
|
+
end.writeUInt16LE(0, 4);
|
|
201
|
+
end.writeUInt16LE(0, 6);
|
|
202
|
+
end.writeUInt16LE(entries.length, 8);
|
|
203
|
+
end.writeUInt16LE(entries.length, 10);
|
|
204
|
+
end.writeUInt32LE(cd.length, 12);
|
|
205
|
+
end.writeUInt32LE(offset, 16);
|
|
206
|
+
end.writeUInt16LE(note.length, 20);
|
|
207
|
+
return Buffer.concat([...locals, cd, end, note]);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* The zip a hosted target uploads for an attested archive: its entries,
|
|
212
|
+
* repacked, with the content digest and revision in the archive comment.
|
|
213
|
+
*/
|
|
214
|
+
export function moduleZip(archive: Uint8Array, content: string, revision?: string): Buffer {
|
|
215
|
+
return zipOf(moduleArchiveEntries(archive), moduleZipComment(content, revision));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** A zip read back: its archive comment and its entries' paths, modes and bytes. */
|
|
219
|
+
export interface ReadZip {
|
|
220
|
+
comment: string;
|
|
221
|
+
entries: { path: string; dir: boolean; mode: number; data: Buffer }[];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Reads a zip's central directory and entries. Throws on bytes that are not a zip. */
|
|
225
|
+
export function readZip(bytes: Uint8Array): ReadZip {
|
|
226
|
+
const b = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
227
|
+
let end = -1;
|
|
228
|
+
for (let i = b.length - 22; i >= Math.max(0, b.length - 22 - 0xffff); i--) {
|
|
229
|
+
if (b.readUInt32LE(i) === 0x06054b50) {
|
|
230
|
+
end = i;
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (end < 0) throw new Error("not a zip archive: no end of central directory");
|
|
235
|
+
const count = b.readUInt16LE(end + 10);
|
|
236
|
+
const cdOffset = b.readUInt32LE(end + 16);
|
|
237
|
+
const comment = b.subarray(end + 22, end + 22 + b.readUInt16LE(end + 20)).toString("utf-8");
|
|
238
|
+
const entries: ReadZip["entries"] = [];
|
|
239
|
+
let at = cdOffset;
|
|
240
|
+
for (let n = 0; n < count; n++) {
|
|
241
|
+
if (b.readUInt32LE(at) !== 0x02014b50) throw new Error("not a zip archive: a bad central directory entry");
|
|
242
|
+
const method = b.readUInt16LE(at + 10);
|
|
243
|
+
const csize = b.readUInt32LE(at + 20);
|
|
244
|
+
const nameLen = b.readUInt16LE(at + 28);
|
|
245
|
+
const extraLen = b.readUInt16LE(at + 30);
|
|
246
|
+
const commentLen = b.readUInt16LE(at + 32);
|
|
247
|
+
const external = b.readUInt32LE(at + 38);
|
|
248
|
+
const local = b.readUInt32LE(at + 42);
|
|
249
|
+
const name = b.subarray(at + 46, at + 46 + nameLen).toString("utf-8");
|
|
250
|
+
at += 46 + nameLen + extraLen + commentLen;
|
|
251
|
+
if (b.readUInt32LE(local) !== 0x04034b50) throw new Error(`not a zip archive: no local header for ${name}`);
|
|
252
|
+
const start = local + 30 + b.readUInt16LE(local + 26) + b.readUInt16LE(local + 28);
|
|
253
|
+
const packed = b.subarray(start, start + csize);
|
|
254
|
+
const data = method === 0 ? Buffer.from(packed) : method === 8 ? inflateRawSync(packed) : undefined;
|
|
255
|
+
if (!data) throw new Error(`${name}: zip method ${method} is not read here`);
|
|
256
|
+
const dir = name.endsWith("/");
|
|
257
|
+
entries.push({ path: dir ? name.slice(0, -1) : name, dir, mode: (external >>> 16) & 0o7777, data });
|
|
258
|
+
}
|
|
259
|
+
return { comment, entries };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const isGzip = (b: Uint8Array) => b.length >= 2 && b[0] === 0x1f && b[1] === 0x8b;
|
|
263
|
+
const isZip = (b: Uint8Array) => b.length >= 4 && b[0] === 0x50 && b[1] === 0x4b;
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* The content a module archive a host gave back carries, as `held` reports
|
|
267
|
+
* it: the attested digest a chant zip's comment names; for a gzip tarball,
|
|
268
|
+
* the digest of its uncompressed bytes, as the bucket registry keeps it; for
|
|
269
|
+
* any other bytes, their own digest.
|
|
270
|
+
*/
|
|
271
|
+
export function heldContentOf(bytes: Uint8Array, digest: (b: Uint8Array) => string): { content: string; revision?: string } {
|
|
272
|
+
if (isZip(bytes)) {
|
|
273
|
+
try {
|
|
274
|
+
const named = readModuleZipComment(readZip(bytes).comment);
|
|
275
|
+
if (named) return named;
|
|
276
|
+
} catch {
|
|
277
|
+
// Not a zip after all: judged by its own digest below.
|
|
278
|
+
}
|
|
279
|
+
} else if (isGzip(bytes)) {
|
|
280
|
+
try {
|
|
281
|
+
return { content: digest(gunzipSync(bytes)) };
|
|
282
|
+
} catch {
|
|
283
|
+
// A broken gzip: judged by its own digest below.
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return { content: digest(bytes) };
|
|
287
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The module registry protocol, written as files a bucket, a directory or a
|
|
3
|
+
* Pages site holds (https://opentofu.org/docs/internals/module-registry-protocol/):
|
|
4
|
+
*
|
|
5
|
+
* .well-known/terraform.json {"modules.v1": "/v1/modules/"}
|
|
6
|
+
* v1/modules/<ns>/<name>/<system>/versions every version, oldest first
|
|
7
|
+
* v1/modules/<ns>/<name>/<system>/<v>/download {"location": ...}
|
|
8
|
+
* v1/modules/<ns>/<name>/<system>/<v>/<name>-<v>.tar.gz a tarball download
|
|
9
|
+
* v1/modules/<ns>/<name>/<system>/<v>/release.json the commit and content digest
|
|
10
|
+
*
|
|
11
|
+
* The download answer the two CLIs need is a 200 with the JSON body and an
|
|
12
|
+
* `X-Terraform-Get` header naming the same location (terragucci#1287).
|
|
13
|
+
* Terraform reads only the header (hashicorp/terraform
|
|
14
|
+
* internal/registry/client.go:237-250 at v1.14.9 and v1.15.8). OpenTofu reads
|
|
15
|
+
* the body's `location` first and the header when it is empty
|
|
16
|
+
* (opentofu/opentofu internal/registry/client.go:205-237 at v1.12.5 and
|
|
17
|
+
* v1.13.1). A stored file carries no header, so the stored `download` file is
|
|
18
|
+
* the body, and whatever serves it (`downloadAnswer`, the loopback handler in
|
|
19
|
+
* ./serve, an edge function) adds the header from that body.
|
|
20
|
+
*
|
|
21
|
+
* The writer takes the store it writes to, so the bucket clients stay with
|
|
22
|
+
* whoever owns them (chant#4554 moves terragucci's into core).
|
|
23
|
+
*/
|
|
24
|
+
// The errors module alone, never the object-store index: its clients import Node built-ins, and the
|
|
25
|
+
// edge function (./edge) reaches this file for the download answer (chant#4589).
|
|
26
|
+
import { isStoreConflict, type StoreCondition } from "@intentius/chant/object-store/errors";
|
|
27
|
+
import { DISCOVERY, MODULES_V1, keyFor, type RegistryAddress } from "./address";
|
|
28
|
+
import { compareSemver, formatSemver, parseSemver, type Semver } from "./semver";
|
|
29
|
+
|
|
30
|
+
/** The header Terraform takes a module's location from. */
|
|
31
|
+
export const DOWNLOAD_HEADER = "X-Terraform-Get";
|
|
32
|
+
|
|
33
|
+
/** The files a registry is written to: a bucket client, a directory, or a test's map. */
|
|
34
|
+
export interface RegistryWriteStore {
|
|
35
|
+
/** Writes one object. With `when`, the write must fail with a conflict when the condition no longer holds. */
|
|
36
|
+
put(key: string, body: string | Buffer, contentType?: string, when?: StoreCondition): Promise<unknown>;
|
|
37
|
+
/** One object's body and version tag, or `{}` when there is none. */
|
|
38
|
+
read(key: string): Promise<{ body?: string; etag?: string }>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The service discovery document. */
|
|
42
|
+
export const discoveryDoc = (): string => `${JSON.stringify({ "modules.v1": MODULES_V1 })}\n`;
|
|
43
|
+
|
|
44
|
+
/** The protocol's versions answer, oldest first, duplicates and non-versions dropped. */
|
|
45
|
+
export function versionsDoc(versions: string[]): string {
|
|
46
|
+
// Dedupe after formatting, so "v1.0.0" and "1.0.0" are one version.
|
|
47
|
+
const parsed = versions.map((v) => parseSemver(v)).filter((v): v is Semver => !!v);
|
|
48
|
+
const sorted = [...new Set(parsed.sort(compareSemver).map(formatSemver))];
|
|
49
|
+
return `${JSON.stringify({ modules: [{ versions: sorted.map((version) => ({ version })) }] }, null, 2)}\n`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The versions a versions answer lists, or none when it is missing or not the protocol's JSON. */
|
|
53
|
+
export function readVersions(text: string | undefined): string[] {
|
|
54
|
+
if (!text) return [];
|
|
55
|
+
try {
|
|
56
|
+
const doc = JSON.parse(text) as { modules?: { versions?: { version?: unknown }[] }[] };
|
|
57
|
+
return (doc.modules?.[0]?.versions ?? []).map((v) => v.version).filter((v): v is string => typeof v === "string");
|
|
58
|
+
} catch {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** A version's download answer body: the JSON OpenTofu reads. */
|
|
64
|
+
export function downloadDoc(location: string): string {
|
|
65
|
+
return `${JSON.stringify({ location })}\n`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The location a download body names, or undefined when it names none. */
|
|
69
|
+
export function downloadLocation(body: string | undefined): string | undefined {
|
|
70
|
+
if (!body) return undefined;
|
|
71
|
+
try {
|
|
72
|
+
const location = (JSON.parse(body) as { location?: unknown }).location;
|
|
73
|
+
return typeof location === "string" && location ? location : undefined;
|
|
74
|
+
} catch {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** A download answer as a server sends it. */
|
|
80
|
+
export interface DownloadAnswer {
|
|
81
|
+
status: 200;
|
|
82
|
+
headers: Record<string, string>;
|
|
83
|
+
body: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The download answer both CLIs install from: status 200, the JSON body with
|
|
88
|
+
* `location`, and `X-Terraform-Get` naming the same location.
|
|
89
|
+
*/
|
|
90
|
+
export function downloadAnswer(location: string): DownloadAnswer {
|
|
91
|
+
return { status: 200, headers: { "content-type": "application/json", [DOWNLOAD_HEADER]: location }, body: downloadDoc(location) };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The headers a stored download body is served with, or undefined when the body names no location. */
|
|
95
|
+
export function downloadHeaders(body: string | undefined): Record<string, string> | undefined {
|
|
96
|
+
const location = downloadLocation(body);
|
|
97
|
+
return location ? downloadAnswer(location).headers : undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** True for a `.../v1/modules/<ns>/<name>/<system>/<version>/download` path, with or without a leading prefix. */
|
|
101
|
+
export const isDownloadPath = (path: string): boolean => /(^|\/)v1\/modules\/[^/]+\/[^/]+\/[^/]+\/[^/]+\/download$/.test(path.split("?")[0]!);
|
|
102
|
+
|
|
103
|
+
/** What the registry keeps beside a version, so the next publish knows what it holds. */
|
|
104
|
+
export interface ReleaseMeta {
|
|
105
|
+
module: string;
|
|
106
|
+
version: string;
|
|
107
|
+
revision: string;
|
|
108
|
+
content: string;
|
|
109
|
+
location: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The registry's files for one version, written in this order: the version is listed last, once all it points at is there. */
|
|
113
|
+
export interface VersionFiles {
|
|
114
|
+
tarball?: { key: string; body: Buffer };
|
|
115
|
+
download: { key: string; body: string };
|
|
116
|
+
meta: { key: string; body: string };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface WriteVersionOptions {
|
|
120
|
+
/** The registry's prefix in its store. */
|
|
121
|
+
prefix?: string;
|
|
122
|
+
/**
|
|
123
|
+
* Whether an error from `put` is a lost conditional write, to be retried.
|
|
124
|
+
* By default chant's `StoreConflict`, or any error named `StoreConflict`
|
|
125
|
+
* (a store from another package with a class of its own).
|
|
126
|
+
*/
|
|
127
|
+
isConflict?: (e: unknown) => boolean;
|
|
128
|
+
/** Conditional retries on the versions file before the conflict is thrown. Default 3. */
|
|
129
|
+
retries?: number;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Writes one version: discovery when it differs, the tarball, the download answer, release.json, then the versions list. */
|
|
133
|
+
export async function writeVersion(store: RegistryWriteStore, addr: RegistryAddress, version: Semver, files: VersionFiles, options: WriteVersionOptions = {}): Promise<void> {
|
|
134
|
+
const discovery = discoveryDoc();
|
|
135
|
+
const discoKey = keyFor(options.prefix, DISCOVERY);
|
|
136
|
+
const disco = await store.read(discoKey);
|
|
137
|
+
if (disco.body !== discovery) await store.put(discoKey, discovery, "application/json");
|
|
138
|
+
if (files.tarball) await store.put(files.tarball.key, files.tarball.body, "application/gzip");
|
|
139
|
+
await store.put(files.download.key, files.download.body, "application/json");
|
|
140
|
+
await store.put(files.meta.key, files.meta.body, "application/json");
|
|
141
|
+
const key = keyFor(options.prefix, `${addr.path}/versions`);
|
|
142
|
+
const isConflict = options.isConflict ?? isStoreConflict;
|
|
143
|
+
const retries = options.retries ?? 3;
|
|
144
|
+
// Read, add, write only over the copy read: two publishes at once cannot drop each other's version.
|
|
145
|
+
for (let attempt = 0; ; attempt++) {
|
|
146
|
+
const held = await store.read(key);
|
|
147
|
+
const when: StoreCondition | undefined = held.body === undefined ? { ifNoneMatch: "*" } : held.etag ? { ifMatch: held.etag } : undefined;
|
|
148
|
+
try {
|
|
149
|
+
await store.put(key, versionsDoc([...readVersions(held.body), formatSemver(version)]), "application/json", when);
|
|
150
|
+
return;
|
|
151
|
+
} catch (e) {
|
|
152
|
+
if (!isConflict(e) || attempt >= retries) throw e;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
package/src/report.test.ts
CHANGED
|
@@ -8,9 +8,13 @@
|
|
|
8
8
|
import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
|
9
9
|
import { tmpdir } from "node:os";
|
|
10
10
|
import { join } from "node:path";
|
|
11
|
+
import { contract } from "../../../packages/core/src/workspace/__fixtures__/contract-repo";
|
|
11
12
|
import { describe, expect, test } from "vitest";
|
|
13
|
+
import { verifyChangeSetDigest } from "@intentius/chant/change-set";
|
|
12
14
|
import { addToIndex, atmosOutputReads, atmosStackReads, dirStore, estateOfDir, httpStore, moduleEstates, runFactsFromEnv, slicePlan, terraformReport, topModule, writeReport } from "./report";
|
|
13
15
|
import { parseReportArgs } from "./commands";
|
|
16
|
+
import { generateReportFixture } from "./__fixtures__/report/generate";
|
|
17
|
+
import { readdirSync } from "node:fs";
|
|
14
18
|
|
|
15
19
|
const rc = (address: string, module: string, type: string, name: string, actions: string[], change: Record<string, unknown>) => ({
|
|
16
20
|
address, module_address: module, mode: "managed", type, name, provider_name: "registry.terraform.io/hashicorp/aws", change: { actions, ...change },
|
|
@@ -287,7 +291,7 @@ describe("writing", () => {
|
|
|
287
291
|
const store = dirStore(dir);
|
|
288
292
|
const plan = terraformReport({ ...base, stage: "tf-plan" });
|
|
289
293
|
const apply = terraformReport({ ...base, stage: "tf-apply", applied: AFTER, finished: "2026-10-24T10:05:00Z" });
|
|
290
|
-
expect(await writeReport(store, plan)).toEqual([`${RUN.project}/2026/10/abc123/tf-plan/report.json`, `${RUN.project}/index.json`, "index.json"]);
|
|
294
|
+
expect(await writeReport(store, plan)).toEqual([`${RUN.project}/2026/10/abc123/tf-plan/report.json`, `${RUN.project}/2026/10/abc123/tf-plan/change-set.json`, `${RUN.project}/index.json`, "index.json"]);
|
|
291
295
|
await writeReport(store, apply);
|
|
292
296
|
await writeReport(store, apply);
|
|
293
297
|
const project = JSON.parse(readFileSync(join(dir, RUN.project, "index.json"), "utf8"));
|
|
@@ -322,6 +326,83 @@ describe("writing", () => {
|
|
|
322
326
|
});
|
|
323
327
|
});
|
|
324
328
|
|
|
329
|
+
const SCHEMA_PATH = join(import.meta.dirname, "..", "..", "..", "packages", "core", "src", "workspace", "change-set.schema.json");
|
|
330
|
+
const { validate: validateChangeSet } = contract(JSON.parse(readFileSync(SCHEMA_PATH, "utf8")));
|
|
331
|
+
const FIXTURE_DIR = join(import.meta.dirname, "__fixtures__", "report");
|
|
332
|
+
|
|
333
|
+
/** The change set must validate against chant's schema and be the document the report's digest names. */
|
|
334
|
+
function expectChangeSet(doc: any, report: any): void {
|
|
335
|
+
expect(validateChangeSet(doc), JSON.stringify(validateChangeSet.errors, null, 2)).toBe(true);
|
|
336
|
+
expect(verifyChangeSetDigest(doc)).toBe(true);
|
|
337
|
+
expect(report.change_set).toBe(doc.digest);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
describe("change-set.json beside the report", () => {
|
|
341
|
+
const apply = { ...base, stage: "tf-apply" as const, applied: AFTER, finished: "2026-10-24T10:05:00Z" };
|
|
342
|
+
|
|
343
|
+
test.each([["tf-plan", { ...base, stage: "tf-plan" as const }], ["tf-apply", apply]])("%s: the document validates and its digest is the report's", (_stage, input) => {
|
|
344
|
+
const r = terraformReport(input);
|
|
345
|
+
expectChangeSet(r.changeSet, r.report);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
test("--out writes it next to report.json for both stages", async () => {
|
|
349
|
+
const dir = mkdtempSync(join(tmpdir(), "tf-report-cs-"));
|
|
350
|
+
const store = dirStore(dir);
|
|
351
|
+
for (const input of [{ ...base, stage: "tf-plan" as const }, apply]) {
|
|
352
|
+
const r = terraformReport(input);
|
|
353
|
+
await writeReport(store, r);
|
|
354
|
+
const at = join(dir, RUN.project, r.path);
|
|
355
|
+
expect(readdirSync(at).sort()).toEqual(["change-set.json", "report.json"]);
|
|
356
|
+
expectChangeSet(JSON.parse(readFileSync(join(at, "change-set.json"), "utf8")), JSON.parse(readFileSync(join(at, "report.json"), "utf8")));
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test("--to puts it next to report.json for both stages", async () => {
|
|
361
|
+
const objects = new Map<string, string>();
|
|
362
|
+
const fake = (async (url: string, init?: RequestInit) => {
|
|
363
|
+
if (init?.method === "PUT") {
|
|
364
|
+
objects.set(url, String(init.body));
|
|
365
|
+
return new Response("", { status: 200 });
|
|
366
|
+
}
|
|
367
|
+
return objects.has(url) ? new Response(objects.get(url)!) : new Response("", { status: 404 });
|
|
368
|
+
}) as unknown as typeof fetch;
|
|
369
|
+
const store = httpStore("http://floci:4566/demo-reports/demo", fake);
|
|
370
|
+
for (const input of [{ ...base, stage: "tf-plan" as const }, apply]) {
|
|
371
|
+
const r = terraformReport(input);
|
|
372
|
+
await writeReport(store, r);
|
|
373
|
+
const at = `http://floci:4566/demo-reports/demo/${encodeURIComponent("forgejo:3000").replace(/%3A/g, "%3A")}/admin/demo-terragrunt-choudoufu/${r.path}`;
|
|
374
|
+
expectChangeSet(JSON.parse(objects.get(`${at}/change-set.json`)!), JSON.parse(objects.get(`${at}/report.json`)!));
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
describe("the report fixture behold renders", () => {
|
|
380
|
+
const read = (name: string): any => JSON.parse(readFileSync(join(FIXTURE_DIR, name), "utf8"));
|
|
381
|
+
|
|
382
|
+
test("is what the generator writes now; regenerate with `npx tsx lexicons/terraform/src/__fixtures__/report/generate.ts`", async () => {
|
|
383
|
+
const files = await generateReportFixture();
|
|
384
|
+
expect(Object.keys(files).sort()).toEqual(["tf-apply/change-set.json", "tf-apply/report.json", "tf-plan/change-set.json", "tf-plan/report.json"]);
|
|
385
|
+
for (const [name, body] of Object.entries(files)) expect(readFileSync(join(FIXTURE_DIR, name), "utf8"), `${name} is stale`).toBe(body);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
test("both stages validate, and each digest is its report's", () => {
|
|
389
|
+
for (const stage of ["tf-plan", "tf-apply"]) expectChangeSet(read(`${stage}/change-set.json`), read(`${stage}/report.json`));
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
test("three members, two composed reads", () => {
|
|
393
|
+
const plan = read("tf-plan/report.json");
|
|
394
|
+
expect(read("tf-plan/change-set.json").members.map((m: any) => m.member)).toEqual(["estates/app", "estates/edge", "estates/network"]);
|
|
395
|
+
const reads = plan.roots.flatMap((r: any) => r.reads ?? []);
|
|
396
|
+
expect(reads.filter((r: any) => r.composed === true).length).toBeGreaterThanOrEqual(2);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test("an attribute unknown at plan has its value after the apply", () => {
|
|
400
|
+
const attr = (stage: string) => read(`${stage}/report.json`).roots.find((r: any) => r.path === "estates/network").changes.find((c: any) => c.address === "aws_subnet.new").attributes.find((a: any) => a.path === "id");
|
|
401
|
+
expect(attr("tf-plan")).toEqual({ path: "id", unknown: true });
|
|
402
|
+
expect(attr("tf-apply")).toEqual({ path: "id", after: "subnet-0a1b" });
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
|
|
325
406
|
describe("the run's facts and the verb's flags", () => {
|
|
326
407
|
test("a Forgejo environment names the project as terragucci does", () => {
|
|
327
408
|
const f = runFactsFromEnv({ GITHUB_REPOSITORY: "admin/demo", GITHUB_SERVER_URL: "http://forgejo:3000", GITHUB_SHA: "deadbeef", GITHUB_RUN_ID: "9", FORGEJO_ACTIONS: "true" }, { pullRequest: "12" });
|