@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,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A JFrog Artifactory Terraform module repository as a module target
|
|
3
|
+
* (chant#4605), from the research on terragucci#1299 and its ruling.
|
|
4
|
+
*
|
|
5
|
+
* The repository is a local Terraform repository (Module/Provider registry
|
|
6
|
+
* type, layout `terraform-module-default`). The one write is the documented
|
|
7
|
+
* deploy (docs.jfrog.com, Terraform repositories):
|
|
8
|
+
*
|
|
9
|
+
* PUT <url>/<repo>/<namespace>/<name>/<system>/<version>.zip
|
|
10
|
+
*
|
|
11
|
+
* with basic `user:token` credentials, as the docs show them (ruling B), and
|
|
12
|
+
* the zip of the attested archive as the body (ruling A, ./zip.ts). The user
|
|
13
|
+
* is `JFROG_USER` (or the declaration's `user` or `userEnv`) and the token
|
|
14
|
+
* `JFROG_ACCESS_TOKEN` (or `tokenEnv`). `<url>` is the Artifactory base,
|
|
15
|
+
* `https://<host>/artifactory`; a URL with no path gets `/artifactory`.
|
|
16
|
+
*
|
|
17
|
+
* Consumers write `<host>/<repo>__<namespace>/<name>/<system>`: the
|
|
18
|
+
* repository key and the namespace joined by a double underscore. `versions`
|
|
19
|
+
* and `reveal` read that source through the lexicon's registry reader
|
|
20
|
+
* (discovery, versions, download) with `TF_TOKEN_<host>` (chant#4563), else
|
|
21
|
+
* the access token as a bearer token. Artifactory indexes a deploy after
|
|
22
|
+
* the PUT answers, so `reveal` waits for the version to be listed, through
|
|
23
|
+
* the contract's `sleep` and `now`, up to `waitMs` (default 30s), then
|
|
24
|
+
* downloads it from the location the registry gives and checks it is the
|
|
25
|
+
* zip of the attested archive.
|
|
26
|
+
*
|
|
27
|
+
* `held` GETs the zip at its deploy path. A chant zip names the attested
|
|
28
|
+
* content in its archive comment; a version deployed some other way is
|
|
29
|
+
* judged by its own bytes, so it never passes for the release's. A version
|
|
30
|
+
* already there with the same content comes back `present` with nothing
|
|
31
|
+
* sent; with other content it is refused. A 403 or 409 to the PUT (a
|
|
32
|
+
* repository whose deploy policy refuses a redeploy) is judged the same way
|
|
33
|
+
* by reading the version again.
|
|
34
|
+
*
|
|
35
|
+
* Not confirmed by the docs (terragucci#1299): which editions carry
|
|
36
|
+
* Terraform repositories, the PUT's `Content-Type` (this sends
|
|
37
|
+
* `application/zip`), whether a deploy takes a bearer access token in place
|
|
38
|
+
* of basic credentials, and the overwrite rule. Artifactory's Terraform
|
|
39
|
+
* docs show no call to deprecate a module version, so Artifactory shows no
|
|
40
|
+
* deprecation and the bucket catalog carries it.
|
|
41
|
+
*/
|
|
42
|
+
import { PublishTargetError, type PublishContextBase, type PublishItem, type StagedTarget } from "@intentius/chant/lifecycle/release-publish";
|
|
43
|
+
import { terraformCredential } from "@intentius/chant/terraform/credentials";
|
|
44
|
+
import { createHash } from "node:crypto";
|
|
45
|
+
import { RegistryError, registryLocation, registryVersions, type RegistryReadOptions } from "../reader";
|
|
46
|
+
import {
|
|
47
|
+
answerError,
|
|
48
|
+
checkModuleVersion,
|
|
49
|
+
contentDigest,
|
|
50
|
+
defaultPublishFetch,
|
|
51
|
+
versionHeldError,
|
|
52
|
+
type HeldVersion,
|
|
53
|
+
type ModuleAddress,
|
|
54
|
+
type ModulePublishTarget,
|
|
55
|
+
type ModuleTargetDeps,
|
|
56
|
+
type ModuleVersionContext,
|
|
57
|
+
} from "./module-target";
|
|
58
|
+
import {
|
|
59
|
+
HOSTED_LIST_WAIT_MS,
|
|
60
|
+
HOSTED_POLL_MS,
|
|
61
|
+
defaultRegistryFetch,
|
|
62
|
+
getBytes,
|
|
63
|
+
locationContent,
|
|
64
|
+
needBasicCredential,
|
|
65
|
+
readBasicCredential,
|
|
66
|
+
revealMismatch,
|
|
67
|
+
waitListed,
|
|
68
|
+
writeRefused,
|
|
69
|
+
type BasicCredentialEnv,
|
|
70
|
+
} from "./hosted";
|
|
71
|
+
import { heldContentOf, moduleZip } from "./zip";
|
|
72
|
+
|
|
73
|
+
/** How an Artifactory module target is declared. */
|
|
74
|
+
export interface ArtifactoryModuleTargetOptions {
|
|
75
|
+
/** The Artifactory base, such as `https://acme.jfrog.io/artifactory`. A URL with no path gets `/artifactory`. */
|
|
76
|
+
url: string;
|
|
77
|
+
/** The local Terraform repository's key, such as `terraform-local`. */
|
|
78
|
+
repo: string;
|
|
79
|
+
/** The variable the access token is read from. Default `JFROG_ACCESS_TOKEN`. */
|
|
80
|
+
tokenEnv?: string;
|
|
81
|
+
/** The variable the user name is read from. Default `JFROG_USER`. */
|
|
82
|
+
userEnv?: string;
|
|
83
|
+
/** The user name, in place of `userEnv`. */
|
|
84
|
+
user?: string;
|
|
85
|
+
/** How long `reveal` waits for Artifactory to list the version. Default 30s. */
|
|
86
|
+
waitMs?: number;
|
|
87
|
+
/** How often it asks while it waits. Default 2s. */
|
|
88
|
+
pollMs?: number;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const ARTIFACTORY_TOKEN_ENV = "JFROG_ACCESS_TOKEN";
|
|
92
|
+
export const ARTIFACTORY_USER_ENV = "JFROG_USER";
|
|
93
|
+
|
|
94
|
+
/** The Artifactory base a target deploys under, without a trailing slash. */
|
|
95
|
+
export function artifactoryBase(url: string): string {
|
|
96
|
+
let u: URL;
|
|
97
|
+
try {
|
|
98
|
+
u = new URL(url);
|
|
99
|
+
} catch {
|
|
100
|
+
throw new PublishTargetError("publish-config-invalid", `${url} is not a URL`, "Set url to the Artifactory base, such as https://acme.jfrog.io/artifactory.");
|
|
101
|
+
}
|
|
102
|
+
const path = u.pathname.replace(/\/+$/, "");
|
|
103
|
+
return `${u.origin}${path || "/artifactory"}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The source consumers write for a module in the repository: `<host>/<repo>__<namespace>/<name>/<system>`. */
|
|
107
|
+
export const artifactorySource = (host: string, repo: string, a: ModuleAddress): string => `${host}/${repo}__${a.namespace}/${a.name}/${a.system}`;
|
|
108
|
+
|
|
109
|
+
/** The deploy path of one version's zip. */
|
|
110
|
+
export function artifactoryDeployUrl(base: string, repo: string, a: ModuleAddress, version: string): string {
|
|
111
|
+
const seg = [repo, a.namespace, a.name, a.system].map(encodeURIComponent).join("/");
|
|
112
|
+
return `${base}/${seg}/${encodeURIComponent(version)}.zip`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const REPO_KEY = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
116
|
+
|
|
117
|
+
export function artifactoryModuleTarget(options: ArtifactoryModuleTargetOptions, deps: ModuleTargetDeps = {}): ModulePublishTarget<"artifactory"> {
|
|
118
|
+
const put = deps.fetch ?? defaultPublishFetch;
|
|
119
|
+
const get = deps.registryFetch ?? defaultRegistryFetch;
|
|
120
|
+
const names: BasicCredentialEnv = { tokenEnv: options.tokenEnv ?? ARTIFACTORY_TOKEN_ENV, userEnv: options.userEnv ?? ARTIFACTORY_USER_ENV, ...(options.user ? { user: options.user } : {}) };
|
|
121
|
+
const wait = {
|
|
122
|
+
waitMs: options.waitMs ?? HOSTED_LIST_WAIT_MS,
|
|
123
|
+
pollMs: options.pollMs ?? HOSTED_POLL_MS,
|
|
124
|
+
sleep: deps.sleep ?? ((ms: number) => new Promise<void>((r) => setTimeout(r, ms))),
|
|
125
|
+
now: deps.now ?? Date.now,
|
|
126
|
+
};
|
|
127
|
+
const key = `artifactory:${createHash("sha256")
|
|
128
|
+
.update(JSON.stringify({ url: options.url, repo: options.repo, tokenEnv: names.tokenEnv, userEnv: names.userEnv, user: options.user ?? null }))
|
|
129
|
+
.digest("hex")
|
|
130
|
+
.slice(0, 16)}`;
|
|
131
|
+
|
|
132
|
+
const where = () => {
|
|
133
|
+
if (!REPO_KEY.test(options.repo)) throw new PublishTargetError("publish-config-invalid", `${JSON.stringify(options.repo)} is not an Artifactory repository key`);
|
|
134
|
+
const base = artifactoryBase(options.url);
|
|
135
|
+
const u = new URL(base);
|
|
136
|
+
return { base, host: u.host.toLowerCase(), scheme: (u.protocol === "http:" ? "http" : "https") as "http" | "https" };
|
|
137
|
+
};
|
|
138
|
+
const fix = `Use an identity token or API key with Deploy permission on the repository ${options.repo}, with the user it belongs to.`;
|
|
139
|
+
const needCredential = (ctx: Pick<PublishContextBase, "env" | "redactor">) => needBasicCredential(ctx, names, "Artifactory", fix);
|
|
140
|
+
|
|
141
|
+
/** The reader's options: the CLIs' token for the host, else the access token as a bearer token. */
|
|
142
|
+
const readOptions = (ctx: Pick<PublishContextBase, "env" | "redactor">): RegistryReadOptions & { host: string } => {
|
|
143
|
+
const { host, scheme } = where();
|
|
144
|
+
const cli = terraformCredential(host, { env: ctx.env });
|
|
145
|
+
const token = cli?.token ?? ctx.env[names.tokenEnv] ?? undefined;
|
|
146
|
+
ctx.redactor.add(token);
|
|
147
|
+
return { host, scheme, token: token || false, fetch: get };
|
|
148
|
+
};
|
|
149
|
+
const read = async <T>(ctx: Pick<PublishContextBase, "redactor">, run: () => Promise<T>): Promise<T> => {
|
|
150
|
+
try {
|
|
151
|
+
return await run();
|
|
152
|
+
} catch (e) {
|
|
153
|
+
if (e instanceof RegistryError) throw answerError(ctx, "publish-read-failed", e.message);
|
|
154
|
+
throw e;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const versions = async (address: ModuleAddress, ctx: Pick<PublishContextBase, "env" | "redactor">): Promise<string[]> => {
|
|
159
|
+
const o = readOptions(ctx);
|
|
160
|
+
return read(ctx, () => registryVersions(artifactorySource(o.host, options.repo, address), o));
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const held = async (address: ModuleAddress, version: string, ctx: Pick<PublishContextBase, "env" | "redactor">): Promise<HeldVersion | undefined> => {
|
|
164
|
+
const { base } = where();
|
|
165
|
+
const url = artifactoryDeployUrl(base, options.repo, address, version);
|
|
166
|
+
const basic = readBasicCredential(ctx, names);
|
|
167
|
+
const bearer = readOptions(ctx).token;
|
|
168
|
+
const { status, bytes } = await getBytes(ctx, get, url, basic?.header ?? (bearer ? `Bearer ${bearer}` : undefined));
|
|
169
|
+
if (status === 404) return undefined;
|
|
170
|
+
if (status === 401 || status === 403) {
|
|
171
|
+
throw answerError(ctx, "publish-credentials-refused", `Artifactory answered ${status} to a read of ${url}`, `Set ${names.userEnv} and ${names.tokenEnv} to a user with Read permission on ${options.repo}.`);
|
|
172
|
+
}
|
|
173
|
+
if (status < 200 || status > 299) throw answerError(ctx, "publish-read-failed", `Artifactory answered ${status} to a read of ${url}`);
|
|
174
|
+
return heldContentOf(bytes, contentDigest);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const item = (ctx: ModuleVersionContext, present?: boolean): PublishItem => {
|
|
178
|
+
const { host } = where();
|
|
179
|
+
return {
|
|
180
|
+
name: `${options.repo}__${ctx.address.namespace}/${ctx.address.name}/${ctx.address.system} ${ctx.version}`,
|
|
181
|
+
destination: artifactorySource(host, options.repo, ctx.address),
|
|
182
|
+
digest: ctx.content,
|
|
183
|
+
...(present ? { present: true } : {}),
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
kind: "artifactory",
|
|
189
|
+
name: "Artifactory Terraform repository",
|
|
190
|
+
destination: `${options.url} ${options.repo}`,
|
|
191
|
+
credentialEnv: [names.tokenEnv, ...(options.user ? [] : [names.userEnv])],
|
|
192
|
+
key,
|
|
193
|
+
versions,
|
|
194
|
+
held,
|
|
195
|
+
|
|
196
|
+
async plan(ctx) {
|
|
197
|
+
checkModuleVersion(ctx);
|
|
198
|
+
where();
|
|
199
|
+
needCredential(ctx);
|
|
200
|
+
return [item(ctx)];
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
async stage(ctx): Promise<StagedTarget> {
|
|
204
|
+
checkModuleVersion(ctx);
|
|
205
|
+
const credential = needCredential(ctx);
|
|
206
|
+
const { base, host } = where();
|
|
207
|
+
const source = artifactorySource(host, options.repo, ctx.address);
|
|
208
|
+
const staged = (present: boolean): StagedTarget => ({ target: "artifactory", destination: source, items: [item(ctx, present)], state: { repo: options.repo, host, version: ctx.version } });
|
|
209
|
+
|
|
210
|
+
const before = await held(ctx.address, ctx.version, ctx);
|
|
211
|
+
if (before) {
|
|
212
|
+
if (before.content !== ctx.content) throw versionHeldError(source, ctx, before);
|
|
213
|
+
ctx.log(`${source} ${ctx.version} is already in ${options.repo} with this content; nothing sent`);
|
|
214
|
+
return staged(true);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const zip = moduleZip(ctx.archive, ctx.content, ctx.revision);
|
|
218
|
+
const url = artifactoryDeployUrl(base, options.repo, ctx.address, ctx.version);
|
|
219
|
+
let res: Awaited<ReturnType<typeof put>>;
|
|
220
|
+
try {
|
|
221
|
+
res = await put(url, { method: "PUT", headers: { authorization: credential.header, "content-type": "application/zip" }, body: zip });
|
|
222
|
+
} catch (e) {
|
|
223
|
+
throw answerError(ctx, "publish-upload-failed", `PUT ${url}: ${(e as Error).message}`);
|
|
224
|
+
}
|
|
225
|
+
const text = await res.text().catch(() => "");
|
|
226
|
+
if (res.ok) {
|
|
227
|
+
ctx.log(`deployed ${source} ${ctx.version} to ${options.repo}`);
|
|
228
|
+
return staged(false);
|
|
229
|
+
}
|
|
230
|
+
if (res.status === 403 || res.status === 409) {
|
|
231
|
+
// A deploy policy that refuses a redeploy, or another run deploying first: judge what the repository holds.
|
|
232
|
+
const now = await held(ctx.address, ctx.version, ctx).catch(() => undefined);
|
|
233
|
+
if (now && now.content === ctx.content) return staged(true);
|
|
234
|
+
if (now) throw versionHeldError(source, ctx, now);
|
|
235
|
+
}
|
|
236
|
+
if (res.status === 401 || res.status === 403) throw writeRefused(ctx, "Artifactory", res.status, `${source} ${ctx.version}`, credential, names, fix);
|
|
237
|
+
throw answerError(ctx, "publish-upload-failed", `Artifactory answered ${res.status} to the deploy of ${source} ${ctx.version}: ${text.slice(0, 300)}`);
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
async reveal(ctx, staged) {
|
|
241
|
+
const listed = await waitListed(() => versions(ctx.address, ctx), ctx.version, wait);
|
|
242
|
+
if (!listed) {
|
|
243
|
+
throw answerError(
|
|
244
|
+
ctx,
|
|
245
|
+
"publish-reveal-missing",
|
|
246
|
+
`${staged.destination} lists no ${ctx.version} ${Math.round(wait.waitMs / 1000)}s after the deploy`,
|
|
247
|
+
`Artifactory may not have indexed the deploy yet: an admin can run Recalculate Index on ${options.repo}, then run the publish again.`,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
const o = readOptions(ctx);
|
|
251
|
+
const location = await read(ctx, () => registryLocation(staged.destination, ctx.version, o));
|
|
252
|
+
const got = await locationContent(ctx, get, location, o.host, o.token ? `Bearer ${o.token}` : undefined);
|
|
253
|
+
if (got.content !== ctx.content) throw revealMismatch(ctx, staged.destination, ctx.version, got.content, ctx.content);
|
|
254
|
+
return { target: staged.target, destination: staged.destination, items: staged.items };
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An in-memory stand-in for a JFrog Artifactory Terraform module repository
|
|
3
|
+
* (chant#4605), for the Artifactory module target's tests here and in
|
|
4
|
+
* terragucci#1302. It is a fetch function: nothing listens on a port and
|
|
5
|
+
* nothing leaves the process.
|
|
6
|
+
*
|
|
7
|
+
* It answers the calls terragucci#1299's research found in JFrog's docs:
|
|
8
|
+
*
|
|
9
|
+
* GET /.well-known/terraform.json {"modules.v1": "/artifactory/api/terraform/v1/modules/"}
|
|
10
|
+
* PUT /artifactory/<repo>/<ns>/<name>/<system>/<version>.zip
|
|
11
|
+
* GET /artifactory/<repo>/<ns>/<name>/<system>/<version>.zip
|
|
12
|
+
* GET /artifactory/api/terraform/v1/modules/<repo>__<ns>/<name>/<system>/versions
|
|
13
|
+
* GET /artifactory/api/terraform/v1/modules/<repo>__<ns>/<name>/<system>/<version>/download 204, X-Terraform-Get
|
|
14
|
+
*
|
|
15
|
+
* The deploy takes basic `user:token` credentials only; the reads take those
|
|
16
|
+
* or a bearer token the fake knows. An unknown credential gets 401, and a
|
|
17
|
+
* user without deploy rights on the repository 403. A deploy body that is
|
|
18
|
+
* not a zip holding a `.tf` file gets 400. A redeploy of a version is
|
|
19
|
+
* refused with 409 unless `redeploy` is set. `indexAfter` keeps a new
|
|
20
|
+
* version out of the versions list for that many reads, as Artifactory's
|
|
21
|
+
* index recalculation does. `failNext` answers the next requests with a
|
|
22
|
+
* status (401, 403, 409, 5xx). Every request is kept, headers and body
|
|
23
|
+
* included.
|
|
24
|
+
*/
|
|
25
|
+
import { readZip } from "./zip";
|
|
26
|
+
import { basicOf, bearerOf, bodyOf, headersOf, jsonAnswer, type FakeFetch, type FakeRequest } from "./fake-http";
|
|
27
|
+
|
|
28
|
+
export interface FakeArtifactoryOptions {
|
|
29
|
+
/** The host, such as `acme.jfrog.io`. */
|
|
30
|
+
host: string;
|
|
31
|
+
/** The local Terraform repositories' keys. */
|
|
32
|
+
repos: string[];
|
|
33
|
+
/** Users and their access tokens. */
|
|
34
|
+
users: Record<string, string>;
|
|
35
|
+
/** The users who may deploy, per repository. Default: every user on every repository. */
|
|
36
|
+
deployers?: Record<string, string[]>;
|
|
37
|
+
/** Whether reads go without a credential (Artifactory's anonymous access). Default false. */
|
|
38
|
+
anonymousReads?: boolean;
|
|
39
|
+
/** Whether a deploy over an existing version is taken. Default false: 409. */
|
|
40
|
+
redeploy?: boolean;
|
|
41
|
+
/** How many versions reads a new version stays unlisted for. Default 0. */
|
|
42
|
+
indexAfter?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface FakeArtifactory {
|
|
46
|
+
fetch: FakeFetch;
|
|
47
|
+
requests: FakeRequest[];
|
|
48
|
+
/** What the repositories hold, keyed `<repo>/<ns>/<name>/<system>/<version>`. */
|
|
49
|
+
modules: Map<string, Uint8Array>;
|
|
50
|
+
/** Put a version in directly, as a deploy by hand would. */
|
|
51
|
+
seed(repo: string, ns: string, name: string, system: string, version: string, bytes: Uint8Array): void;
|
|
52
|
+
/** Answer the next `count` requests with `status`. */
|
|
53
|
+
failNext(status: number, count?: number): void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const FAKE_ARTIFACTORY_MODULES_V1 = "/artifactory/api/terraform/v1/modules/";
|
|
57
|
+
|
|
58
|
+
export function createFakeArtifactory(o: FakeArtifactoryOptions): FakeArtifactory {
|
|
59
|
+
const requests: FakeRequest[] = [];
|
|
60
|
+
const modules = new Map<string, Uint8Array>();
|
|
61
|
+
const unlisted = new Map<string, number>();
|
|
62
|
+
let failures: { status: number; count: number } | undefined;
|
|
63
|
+
const tokens = new Set(Object.values(o.users));
|
|
64
|
+
|
|
65
|
+
const userOf = (headers: Record<string, string>): string | undefined => {
|
|
66
|
+
const b = basicOf(headers.authorization);
|
|
67
|
+
return b && o.users[b.user] === b.password ? b.user : undefined;
|
|
68
|
+
};
|
|
69
|
+
const mayRead = (headers: Record<string, string>): boolean => {
|
|
70
|
+
if (o.anonymousReads) return true;
|
|
71
|
+
if (userOf(headers)) return true;
|
|
72
|
+
const bearer = bearerOf(headers.authorization);
|
|
73
|
+
return bearer !== undefined && tokens.has(bearer);
|
|
74
|
+
};
|
|
75
|
+
const mayDeploy = (user: string, repo: string) => !o.deployers || (o.deployers[repo] ?? []).includes(user);
|
|
76
|
+
|
|
77
|
+
const seed: FakeArtifactory["seed"] = (repo, ns, name, system, version, bytes) => void modules.set(`${repo}/${ns}/${name}/${system}/${version}`, bytes);
|
|
78
|
+
|
|
79
|
+
const fetch: FakeFetch = async (url, init = {}) => {
|
|
80
|
+
const method = (init.method ?? "GET").toUpperCase();
|
|
81
|
+
const headers = headersOf(init.headers);
|
|
82
|
+
const body = bodyOf(init.body);
|
|
83
|
+
requests.push({ method, url, headers, ...(body ? { body } : {}) });
|
|
84
|
+
const u = new URL(url);
|
|
85
|
+
if (u.host !== o.host) throw new TypeError(`fetch failed: the fake answers only ${o.host}, not ${u.host}`);
|
|
86
|
+
if (failures && failures.count > 0) {
|
|
87
|
+
failures.count--;
|
|
88
|
+
return jsonAnswer(failures.status, { errors: [{ status: failures.status, message: "set by failNext" }] });
|
|
89
|
+
}
|
|
90
|
+
const path = decodeURIComponent(u.pathname);
|
|
91
|
+
|
|
92
|
+
if (method === "GET" && path === "/.well-known/terraform.json") return jsonAnswer(200, { "modules.v1": FAKE_ARTIFACTORY_MODULES_V1 });
|
|
93
|
+
|
|
94
|
+
if (path.startsWith(FAKE_ARTIFACTORY_MODULES_V1)) {
|
|
95
|
+
if (method !== "GET") return jsonAnswer(405, { errors: [{ status: 405 }] });
|
|
96
|
+
if (!mayRead(headers)) return jsonAnswer(401, { errors: [{ status: 401, message: "Unauthorized" }] });
|
|
97
|
+
const rest = path.slice(FAKE_ARTIFACTORY_MODULES_V1.length).split("/");
|
|
98
|
+
const joined = /^(.+?)__(.+)$/.exec(rest[0] ?? "");
|
|
99
|
+
if (!joined || !o.repos.includes(joined[1]!)) return jsonAnswer(404, { errors: [{ status: 404 }] });
|
|
100
|
+
const prefix = `${joined[1]}/${joined[2]}/${rest[1]}/${rest[2]}/`;
|
|
101
|
+
if (rest.length === 4 && rest[3] === "versions") {
|
|
102
|
+
const listed = [...modules.keys()]
|
|
103
|
+
.filter((k) => k.startsWith(prefix))
|
|
104
|
+
.filter((k) => {
|
|
105
|
+
const left = unlisted.get(k) ?? 0;
|
|
106
|
+
if (left > 0) unlisted.set(k, left - 1);
|
|
107
|
+
return left === 0;
|
|
108
|
+
})
|
|
109
|
+
.map((k) => k.slice(prefix.length));
|
|
110
|
+
if (listed.length === 0) return jsonAnswer(404, { errors: [{ status: 404 }] });
|
|
111
|
+
return jsonAnswer(200, { modules: [{ versions: listed.map((version) => ({ version })) }] });
|
|
112
|
+
}
|
|
113
|
+
if (rest.length === 5 && rest[4] === "download") {
|
|
114
|
+
if (!modules.has(`${prefix}${rest[3]}`)) return jsonAnswer(404, { errors: [{ status: 404 }] });
|
|
115
|
+
return new Response(null, { status: 204, headers: { "X-Terraform-Get": `/artifactory/${prefix}${rest[3]}.zip` } });
|
|
116
|
+
}
|
|
117
|
+
return jsonAnswer(404, { errors: [{ status: 404 }] });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const deploy = /^\/artifactory\/([^/]+)\/([^/]+)\/([^/]+)\/([^/]+)\/([^/]+)\.zip$/.exec(path);
|
|
121
|
+
if (deploy) {
|
|
122
|
+
const [repo, ns, name, system, version] = deploy.slice(1) as [string, string, string, string, string];
|
|
123
|
+
const k = `${repo}/${ns}/${name}/${system}/${version}`;
|
|
124
|
+
if (method === "GET") {
|
|
125
|
+
if (!mayRead(headers)) return jsonAnswer(401, { errors: [{ status: 401, message: "Unauthorized" }] });
|
|
126
|
+
const stored = modules.get(k);
|
|
127
|
+
if (!stored || !o.repos.includes(repo)) return jsonAnswer(404, { errors: [{ status: 404, message: "Not Found" }] });
|
|
128
|
+
return new Response(new Uint8Array(stored), { status: 200, headers: { "content-type": "application/zip" } });
|
|
129
|
+
}
|
|
130
|
+
if (method !== "PUT") return jsonAnswer(405, { errors: [{ status: 405 }] });
|
|
131
|
+
const user = userOf(headers);
|
|
132
|
+
if (!user) return jsonAnswer(401, { errors: [{ status: 401, message: "Bad credentials" }] });
|
|
133
|
+
if (!o.repos.includes(repo)) return jsonAnswer(404, { errors: [{ status: 404, message: `repository ${repo} not found` }] });
|
|
134
|
+
if (!mayDeploy(user, repo)) return jsonAnswer(403, { errors: [{ status: 403, message: "Not enough permissions to deploy" }] });
|
|
135
|
+
if (!body || body.length === 0) return jsonAnswer(400, { errors: [{ status: 400, message: "empty body" }] });
|
|
136
|
+
try {
|
|
137
|
+
if (!readZip(body).entries.some((e) => !e.dir && e.path.endsWith(".tf"))) return jsonAnswer(400, { errors: [{ status: 400, message: "the zip holds no .tf file" }] });
|
|
138
|
+
} catch {
|
|
139
|
+
return jsonAnswer(400, { errors: [{ status: 400, message: "not a zip archive" }] });
|
|
140
|
+
}
|
|
141
|
+
if (modules.has(k) && !o.redeploy) return jsonAnswer(409, { errors: [{ status: 409, message: "Redeploy is not allowed" }] });
|
|
142
|
+
modules.set(k, body);
|
|
143
|
+
if (o.indexAfter) unlisted.set(k, o.indexAfter);
|
|
144
|
+
return jsonAnswer(201, { repo, path: `/${ns}/${name}/${system}/${version}.zip` });
|
|
145
|
+
}
|
|
146
|
+
return jsonAnswer(404, { errors: [{ status: 404 }] });
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
fetch,
|
|
151
|
+
requests,
|
|
152
|
+
modules,
|
|
153
|
+
seed,
|
|
154
|
+
failNext(status, count = 1) {
|
|
155
|
+
failures = { status, count };
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An in-memory stand-in for GitLab's Terraform module registry (chant#4603),
|
|
3
|
+
* for the GitLab module target's tests. It is a fetch function, so nothing
|
|
4
|
+
* listens on a port and nothing leaves the process: the target's upload and
|
|
5
|
+
* the registry reader's reads both go through it.
|
|
6
|
+
*
|
|
7
|
+
* It answers the calls GitLab documents (docs.gitlab.com/api/packages/terraform-modules/):
|
|
8
|
+
*
|
|
9
|
+
* GET /.well-known/terraform.json {"modules.v1": "/api/v4/packages/terraform/modules/v1/"}
|
|
10
|
+
* PUT /api/v4/projects/:id/packages/terraform/modules/:name/:system/:version/file
|
|
11
|
+
* GET /api/v4/packages/terraform/modules/v1/:ns/:name/:system/versions
|
|
12
|
+
* GET /api/v4/packages/terraform/modules/v1/:ns/:name/:system/:version/download 204, X-Terraform-Get
|
|
13
|
+
* GET /api/v4/packages/terraform/modules/v1/:ns/:name/:system/:version/file?token=...
|
|
14
|
+
*
|
|
15
|
+
* The upload takes `PRIVATE-TOKEN` or `JOB-TOKEN`; the reads take
|
|
16
|
+
* `Authorization: Bearer`. A token the fake does not know gets 401, and a
|
|
17
|
+
* known token without write access to the project gets 403. Uploading a
|
|
18
|
+
* version the project already holds gets 403 "Package already exists", as
|
|
19
|
+
* GitLab answers a duplicate package. Every request is kept, headers and
|
|
20
|
+
* body included, so a test can check them.
|
|
21
|
+
*/
|
|
22
|
+
import { gunzipSync } from "node:zlib";
|
|
23
|
+
|
|
24
|
+
/** One request the fake was sent. */
|
|
25
|
+
export interface FakeGitlabRequest {
|
|
26
|
+
method: string;
|
|
27
|
+
url: string;
|
|
28
|
+
/** Header names lower-cased. */
|
|
29
|
+
headers: Record<string, string>;
|
|
30
|
+
body?: Uint8Array;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface FakeGitlabOptions {
|
|
34
|
+
/** The host, such as `gitlab.example.com`. */
|
|
35
|
+
host: string;
|
|
36
|
+
/** Projects by id, each with its full path, whose top-level group is the namespace. */
|
|
37
|
+
projects: Record<string, string>;
|
|
38
|
+
/** Tokens the fake knows, each with the project ids it may upload to. */
|
|
39
|
+
tokens: Record<string, string[]>;
|
|
40
|
+
/** Whether reads need a bearer token. Default true, as for a private project. */
|
|
41
|
+
privateReads?: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface Stored {
|
|
45
|
+
archive: Uint8Array;
|
|
46
|
+
project: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The fetch both the target and the reader are given. */
|
|
50
|
+
export type FakeGitlabFetch = (url: string, init?: { method?: string; headers?: unknown; body?: unknown }) => Promise<Response>;
|
|
51
|
+
|
|
52
|
+
export interface FakeGitlab {
|
|
53
|
+
fetch: FakeGitlabFetch;
|
|
54
|
+
requests: FakeGitlabRequest[];
|
|
55
|
+
/** What the registry holds, keyed `<ns>/<name>/<system>/<version>`. */
|
|
56
|
+
modules: Map<string, Stored>;
|
|
57
|
+
/** Put a version in directly, as an upload by hand would. */
|
|
58
|
+
seed(project: string, name: string, system: string, version: string, archive: Uint8Array): void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const API = "/api/v4";
|
|
62
|
+
const MODULES_V1 = `${API}/packages/terraform/modules/v1/`;
|
|
63
|
+
const DOWNLOAD_TOKEN = "fake-download-grant";
|
|
64
|
+
|
|
65
|
+
function headersOf(h: unknown): Record<string, string> {
|
|
66
|
+
const out: Record<string, string> = {};
|
|
67
|
+
if (!h) return out;
|
|
68
|
+
if (typeof (h as Headers).forEach === "function" && typeof (h as Headers).get === "function") {
|
|
69
|
+
(h as Headers).forEach((v, k) => (out[k.toLowerCase()] = v));
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
for (const [k, v] of Object.entries(h as Record<string, string>)) out[k.toLowerCase()] = String(v);
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function bodyOf(b: unknown): Uint8Array | undefined {
|
|
77
|
+
if (b === undefined || b === null) return undefined;
|
|
78
|
+
if (typeof b === "string") return new TextEncoder().encode(b);
|
|
79
|
+
if (b instanceof Uint8Array) return b;
|
|
80
|
+
if (b instanceof ArrayBuffer) return new Uint8Array(b);
|
|
81
|
+
throw new Error("the fake takes a string or bytes as a body");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const json = (status: number, doc: unknown) => new Response(JSON.stringify(doc), { status, headers: { "content-type": "application/json" } });
|
|
85
|
+
|
|
86
|
+
export function createFakeGitlab(o: FakeGitlabOptions): FakeGitlab {
|
|
87
|
+
const requests: FakeGitlabRequest[] = [];
|
|
88
|
+
const modules = new Map<string, Stored>();
|
|
89
|
+
const namespaceOf = (project: string) => o.projects[project]!.split("/")[0]!;
|
|
90
|
+
const projectId = (raw: string): string | undefined => {
|
|
91
|
+
const id = decodeURIComponent(raw);
|
|
92
|
+
if (o.projects[id]) return id;
|
|
93
|
+
return Object.entries(o.projects).find(([, path]) => path === id)?.[0];
|
|
94
|
+
};
|
|
95
|
+
const seed = (project: string, name: string, system: string, version: string, archive: Uint8Array) => {
|
|
96
|
+
modules.set(`${namespaceOf(project)}/${name}/${system}/${version}`, { archive, project });
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const fetch: FakeGitlabFetch = async (url, init = {}) => {
|
|
100
|
+
const method = (init.method ?? "GET").toUpperCase();
|
|
101
|
+
const headers = headersOf(init.headers);
|
|
102
|
+
const body = bodyOf(init.body);
|
|
103
|
+
requests.push({ method, url, headers, ...(body ? { body } : {}) });
|
|
104
|
+
const u = new URL(url);
|
|
105
|
+
if (u.host !== o.host) throw new TypeError(`fetch failed: the fake answers only ${o.host}, not ${u.host}`);
|
|
106
|
+
const path = u.pathname;
|
|
107
|
+
|
|
108
|
+
if (method === "GET" && path === "/.well-known/terraform.json") return json(200, { "modules.v1": MODULES_V1 });
|
|
109
|
+
|
|
110
|
+
const upload = /^\/api\/v4\/projects\/([^/]+)\/packages\/terraform\/modules\/([^/]+)\/([^/]+)\/([^/]+)\/file$/.exec(path);
|
|
111
|
+
if (upload) {
|
|
112
|
+
if (method !== "PUT") return json(405, { message: "405 Method Not Allowed" });
|
|
113
|
+
const token = headers["private-token"] ?? headers["job-token"];
|
|
114
|
+
if (!token || !o.tokens[token]) return json(401, { message: "401 Unauthorized" });
|
|
115
|
+
const project = projectId(upload[1]!);
|
|
116
|
+
if (!project) return json(404, { message: "404 Project Not Found" });
|
|
117
|
+
if (!o.tokens[token]!.includes(project)) return json(403, { message: "403 Forbidden" });
|
|
118
|
+
if (!body || body.length === 0) return json(400, { message: "400 Bad request - file is missing" });
|
|
119
|
+
try {
|
|
120
|
+
gunzipSync(body);
|
|
121
|
+
} catch {
|
|
122
|
+
return json(400, { message: "400 Bad request - not a gzip archive" });
|
|
123
|
+
}
|
|
124
|
+
const [name, system, version] = [upload[2]!, upload[3]!, upload[4]!].map(decodeURIComponent) as [string, string, string];
|
|
125
|
+
const key = `${namespaceOf(project)}/${name}/${system}/${version}`;
|
|
126
|
+
if (modules.has(key)) return json(403, { message: "403 Forbidden - Package already exists" });
|
|
127
|
+
modules.set(key, { archive: body, project });
|
|
128
|
+
return json(201, { message: "201 Created" });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (method === "GET" && path.startsWith(MODULES_V1)) {
|
|
132
|
+
const rest = path.slice(MODULES_V1.length).split("/").map(decodeURIComponent);
|
|
133
|
+
const isFile = rest.length === 5 && rest[4] === "file";
|
|
134
|
+
if (isFile) {
|
|
135
|
+
if (u.searchParams.get("token") !== DOWNLOAD_TOKEN) return json(401, { message: "401 Unauthorized" });
|
|
136
|
+
} else if (o.privateReads !== false) {
|
|
137
|
+
const bearer = /^Bearer (.+)$/i.exec(headers.authorization ?? "")?.[1];
|
|
138
|
+
if (!bearer || !o.tokens[bearer]) return json(401, { message: "401 Unauthorized" });
|
|
139
|
+
}
|
|
140
|
+
const [ns, name, system] = rest;
|
|
141
|
+
const prefix = `${ns}/${name}/${system}/`;
|
|
142
|
+
if (rest.length === 4 && rest[3] === "versions") {
|
|
143
|
+
const versions = [...modules.keys()].filter((k) => k.startsWith(prefix)).map((k) => k.slice(prefix.length));
|
|
144
|
+
if (versions.length === 0) return json(404, { message: "404 Not Found" });
|
|
145
|
+
return json(200, { modules: [{ versions: versions.map((version) => ({ version, submodules: [], root: { dependencies: [], providers: [] } })), source: `${o.host}/${ns}/${name}/${system}` }] });
|
|
146
|
+
}
|
|
147
|
+
if (rest.length === 5 && rest[4] === "download") {
|
|
148
|
+
if (!modules.has(`${prefix}${rest[3]}`)) return json(404, { message: "404 Not Found" });
|
|
149
|
+
return new Response(null, { status: 204, headers: { "X-Terraform-Get": `${MODULES_V1}${prefix}${rest[3]}/file?token=${DOWNLOAD_TOKEN}&archive=tgz` } });
|
|
150
|
+
}
|
|
151
|
+
if (isFile) {
|
|
152
|
+
const stored = modules.get(`${prefix}${rest[3]}`);
|
|
153
|
+
if (!stored) return json(404, { message: "404 Not Found" });
|
|
154
|
+
return new Response(new Uint8Array(stored.archive), { status: 200, headers: { "content-type": "application/octet-stream" } });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return json(404, { message: "404 Not Found" });
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
return { fetch, requests, modules, seed };
|
|
161
|
+
}
|