@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,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitLab's Terraform module registry as a module target (chant#4603).
|
|
3
|
+
*
|
|
4
|
+
* The one write is GitLab's documented upload
|
|
5
|
+
* (docs.gitlab.com/api/packages/terraform-modules/):
|
|
6
|
+
*
|
|
7
|
+
* PUT /projects/:id/packages/terraform/modules/:name/:system/:version/file
|
|
8
|
+
*
|
|
9
|
+
* with the archive as the body. The token follows chant's forge target
|
|
10
|
+
* (core lifecycle/publish-forge.ts `forgeTokenEnv`): the variable the
|
|
11
|
+
* declaration names, else `GITLAB_TOKEN`, then `CI_JOB_TOKEN`. A job token
|
|
12
|
+
* goes in `JOB-TOKEN` and any other in `PRIVATE-TOKEN`. The API root is the
|
|
13
|
+
* declaration's, else `CI_API_V4_URL`, else gitlab.com's; the project is the
|
|
14
|
+
* declaration's (an id or a full path), else `CI_PROJECT_ID`.
|
|
15
|
+
*
|
|
16
|
+
* GitLab lists a version as soon as it is uploaded, so there is no staging
|
|
17
|
+
* step to hide it: `stage` uploads, and `reveal` reads the version back the
|
|
18
|
+
* way Terraform and OpenTofu do (discovery, versions, download) with the
|
|
19
|
+
* lexicon's registry reader, sending `TF_TOKEN_<host>` (chant#4563), else the
|
|
20
|
+
* upload token, which GitLab also takes as a bearer token on these reads.
|
|
21
|
+
* `held` downloads the version's archive and compares its content digest, so
|
|
22
|
+
* a version holding other content is refused and the same content comes back
|
|
23
|
+
* `present` with nothing sent.
|
|
24
|
+
*
|
|
25
|
+
* GitLab's module API has no call to deprecate a version, so a deprecation
|
|
26
|
+
* stays in the bucket catalog and GitLab shows none. The namespace a GitLab
|
|
27
|
+
* source names is the project's top-level group (`CI_PROJECT_ROOT_NAMESPACE`
|
|
28
|
+
* in CI): the declaration's `namespace`, else the context's.
|
|
29
|
+
*/
|
|
30
|
+
import { readCredential } from "@intentius/chant/lifecycle/publish-credentials";
|
|
31
|
+
import { PublishTargetError } from "@intentius/chant/lifecycle/release-publish";
|
|
32
|
+
import { terraformCredential } from "@intentius/chant/terraform/credentials";
|
|
33
|
+
import { createHash } from "node:crypto";
|
|
34
|
+
import { RegistryError, fetchTarball, registryLocation, registryVersions } from "../reader.js";
|
|
35
|
+
import { answerError, checkModuleVersion, contentDigest, defaultPublishFetch, versionHeldError, } from "./module-target.js";
|
|
36
|
+
export const GITLAB_DEFAULT_API = "https://gitlab.com/api/v4";
|
|
37
|
+
/** The variables the upload token is read from, in order. */
|
|
38
|
+
export const gitlabTokenEnv = (o) => (o.tokenEnv ? [o.tokenEnv] : ["GITLAB_TOKEN", "CI_JOB_TOKEN"]);
|
|
39
|
+
/** The API root a target uploads to, without a trailing slash. */
|
|
40
|
+
export const gitlabApi = (o, env) => (o.api ?? env.CI_API_V4_URL ?? GITLAB_DEFAULT_API).replace(/\/+$/, "");
|
|
41
|
+
/** The headers a GitLab token goes in: `JOB-TOKEN` for a CI job token, `PRIVATE-TOKEN` for any other. */
|
|
42
|
+
export const gitlabAuthHeader = (token) => (token.from === "CI_JOB_TOKEN" ? { "job-token": token.value } : { "private-token": token.value });
|
|
43
|
+
/** The upload URL for one version. */
|
|
44
|
+
export function gitlabUploadUrl(api, project, address, version) {
|
|
45
|
+
const id = /^\d+$/.test(project) ? project : encodeURIComponent(project);
|
|
46
|
+
return `${api}/projects/${id}/packages/terraform/modules/${encodeURIComponent(address.name)}/${encodeURIComponent(address.system)}/${encodeURIComponent(version)}/file`;
|
|
47
|
+
}
|
|
48
|
+
/** A signed URL's query (GitLab's download link carries a token in it) cut from a message. */
|
|
49
|
+
const withoutQueries = (text) => text.replace(/(https?:\/\/[^\s?]+)\?[^\s)"']*/g, "$1?[query]");
|
|
50
|
+
export function gitlabModuleTarget(options = {}, deps = {}) {
|
|
51
|
+
const fetch = deps.fetch ?? defaultPublishFetch;
|
|
52
|
+
const tokenEnv = gitlabTokenEnv(options);
|
|
53
|
+
const key = `gitlab:${createHash("sha256")
|
|
54
|
+
.update(JSON.stringify({ project: options.project ?? null, api: options.api ?? null, tokenEnv: options.tokenEnv ?? null, namespace: options.namespace ?? null }))
|
|
55
|
+
.digest("hex")
|
|
56
|
+
.slice(0, 16)}`;
|
|
57
|
+
const where = (env) => {
|
|
58
|
+
const api = gitlabApi(options, env);
|
|
59
|
+
let u;
|
|
60
|
+
try {
|
|
61
|
+
u = new URL(api);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
throw new PublishTargetError("publish-config-invalid", `${api} is not a URL`, "Set the declaration's api, or CI_API_V4_URL, to the API root, such as https://gitlab.example.com/api/v4.");
|
|
65
|
+
}
|
|
66
|
+
return { api, host: u.host.toLowerCase(), scheme: (u.protocol === "http:" ? "http" : "https") };
|
|
67
|
+
};
|
|
68
|
+
const addressOf = (address) => ({ ...address, namespace: options.namespace ?? address.namespace });
|
|
69
|
+
const sourceOf = (host, a) => `${host}/${a.namespace}/${a.name}/${a.system}`;
|
|
70
|
+
const uploadToken = (ctx) => readCredential(ctx.env, tokenEnv, ctx.redactor);
|
|
71
|
+
const needToken = (ctx) => {
|
|
72
|
+
const t = uploadToken(ctx);
|
|
73
|
+
if (!t)
|
|
74
|
+
throw answerError(ctx, "publish-credentials-missing", `no GitLab token: set ${tokenEnv.join(" or ")}`, "In GitLab CI the job's CI_JOB_TOKEN can upload to its own project; elsewhere use a token with the api scope.");
|
|
75
|
+
return t;
|
|
76
|
+
};
|
|
77
|
+
const projectOf = (env) => {
|
|
78
|
+
const p = options.project ?? env.CI_PROJECT_ID;
|
|
79
|
+
if (!p)
|
|
80
|
+
throw new PublishTargetError("publish-config-invalid", "no GitLab project to upload to", "Name the project (its id or full path) in the declaration, or run in GitLab CI, which sets CI_PROJECT_ID.");
|
|
81
|
+
return p;
|
|
82
|
+
};
|
|
83
|
+
/** The reader's options: the CLIs' token for the host, else the upload token. */
|
|
84
|
+
const readOptions = (ctx) => {
|
|
85
|
+
const { host, scheme } = where(ctx.env);
|
|
86
|
+
const cli = terraformCredential(host, { env: ctx.env });
|
|
87
|
+
const token = cli?.token ?? uploadToken(ctx)?.value;
|
|
88
|
+
ctx.redactor.add(token);
|
|
89
|
+
return { host, scheme, token: token ?? false, ...(deps.registryFetch ? { fetch: deps.registryFetch } : {}) };
|
|
90
|
+
};
|
|
91
|
+
const read = async (ctx, run) => {
|
|
92
|
+
try {
|
|
93
|
+
return await run();
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
if (e instanceof RegistryError)
|
|
97
|
+
throw answerError(ctx, "publish-read-failed", withoutQueries(e.message));
|
|
98
|
+
throw e;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const versions = async (address, ctx) => {
|
|
102
|
+
const o = readOptions(ctx);
|
|
103
|
+
return read(ctx, () => registryVersions(sourceOf(o.host, addressOf(address)), o));
|
|
104
|
+
};
|
|
105
|
+
const held = async (address, version, ctx) => {
|
|
106
|
+
if (!(await versions(address, ctx)).includes(version))
|
|
107
|
+
return undefined;
|
|
108
|
+
const o = readOptions(ctx);
|
|
109
|
+
const source = sourceOf(o.host, addressOf(address));
|
|
110
|
+
return read(ctx, async () => {
|
|
111
|
+
const location = await registryLocation(source, version, o);
|
|
112
|
+
return { content: contentDigest(await fetchTarball(location, o)) };
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
const item = (ctx, present) => {
|
|
116
|
+
const { host } = where(ctx.env);
|
|
117
|
+
const a = addressOf(ctx.address);
|
|
118
|
+
return { name: `${a.namespace}/${a.name}/${a.system} ${ctx.version}`, destination: sourceOf(host, a), digest: ctx.content, ...(present ? { present: true } : {}) };
|
|
119
|
+
};
|
|
120
|
+
return {
|
|
121
|
+
kind: "gitlab",
|
|
122
|
+
name: "GitLab module registry",
|
|
123
|
+
destination: gitlabApi(options, process.env),
|
|
124
|
+
credentialEnv: tokenEnv,
|
|
125
|
+
key,
|
|
126
|
+
versions,
|
|
127
|
+
held,
|
|
128
|
+
async plan(ctx) {
|
|
129
|
+
checkModuleVersion(ctx);
|
|
130
|
+
needToken(ctx);
|
|
131
|
+
projectOf(ctx.env);
|
|
132
|
+
return [item(ctx)];
|
|
133
|
+
},
|
|
134
|
+
async stage(ctx) {
|
|
135
|
+
checkModuleVersion(ctx);
|
|
136
|
+
const token = needToken(ctx);
|
|
137
|
+
const project = projectOf(ctx.env);
|
|
138
|
+
const { api, host } = where(ctx.env);
|
|
139
|
+
const a = addressOf(ctx.address);
|
|
140
|
+
const source = sourceOf(host, a);
|
|
141
|
+
const staged = (present) => ({ target: "gitlab", destination: source, items: [item(ctx, present)], state: { project, host, version: ctx.version } });
|
|
142
|
+
const before = await held(ctx.address, ctx.version, ctx);
|
|
143
|
+
if (before) {
|
|
144
|
+
if (before.content !== ctx.content)
|
|
145
|
+
throw versionHeldError(source, ctx, before);
|
|
146
|
+
ctx.log(`${source} ${ctx.version} is already on GitLab with this content; nothing sent`);
|
|
147
|
+
return staged(true);
|
|
148
|
+
}
|
|
149
|
+
const url = gitlabUploadUrl(api, project, a, ctx.version);
|
|
150
|
+
let res;
|
|
151
|
+
try {
|
|
152
|
+
res = await fetch(url, { method: "PUT", headers: { ...gitlabAuthHeader(token), "content-type": "application/octet-stream" }, body: ctx.archive });
|
|
153
|
+
}
|
|
154
|
+
catch (e) {
|
|
155
|
+
throw answerError(ctx, "publish-upload-failed", `PUT ${url}: ${e.message}`);
|
|
156
|
+
}
|
|
157
|
+
const text = await res.text().catch(() => "");
|
|
158
|
+
if (res.ok) {
|
|
159
|
+
ctx.log(`uploaded ${source} ${ctx.version} to project ${project}`);
|
|
160
|
+
return staged(false);
|
|
161
|
+
}
|
|
162
|
+
if (/already exists/i.test(text)) {
|
|
163
|
+
// Another run uploaded it between the read and the upload: judge what it holds.
|
|
164
|
+
const now = await held(ctx.address, ctx.version, ctx);
|
|
165
|
+
if (now && now.content === ctx.content)
|
|
166
|
+
return staged(true);
|
|
167
|
+
if (now)
|
|
168
|
+
throw versionHeldError(source, ctx, now);
|
|
169
|
+
}
|
|
170
|
+
if (res.status === 401 || res.status === 403) {
|
|
171
|
+
throw answerError(ctx, "publish-credentials-refused", `GitLab answered ${res.status} to the upload of ${source} ${ctx.version}: it refused the token in ${token.from}`, token.from === "CI_JOB_TOKEN"
|
|
172
|
+
? "A job token uploads only to its own project; name another project's token in the declaration's tokenEnv."
|
|
173
|
+
: `Give the token in ${token.from} the api scope and at least the Developer role on project ${project}.`);
|
|
174
|
+
}
|
|
175
|
+
throw answerError(ctx, "publish-upload-failed", `GitLab answered ${res.status} to the upload of ${source} ${ctx.version}: ${withoutQueries(text.slice(0, 300))}`);
|
|
176
|
+
},
|
|
177
|
+
async reveal(ctx, staged) {
|
|
178
|
+
const listed = await versions(ctx.address, ctx);
|
|
179
|
+
if (!listed.includes(ctx.version)) {
|
|
180
|
+
throw answerError(ctx, "publish-reveal-missing", `${staged.destination} lists no ${ctx.version} after the upload`, "Check the project's package registry; run the publish again to upload it.");
|
|
181
|
+
}
|
|
182
|
+
const o = readOptions(ctx);
|
|
183
|
+
await read(ctx, () => registryLocation(staged.destination, ctx.version, o));
|
|
184
|
+
return { target: staged.target, destination: staged.destination, items: staged.items };
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type ModulePublishTarget, type ModuleTargetDeps } from "./module-target.js";
|
|
2
|
+
/** How an HCP Terraform (or Terraform Enterprise) module target is declared. */
|
|
3
|
+
export interface HcpModuleTargetOptions {
|
|
4
|
+
/** The organization, which is also the namespace private module sources name. */
|
|
5
|
+
organization: string;
|
|
6
|
+
/** The host. Default app.terraform.io; a Terraform Enterprise host otherwise. */
|
|
7
|
+
host?: string;
|
|
8
|
+
/** The variable the token is read from first, in place of `TFE_TOKEN`. */
|
|
9
|
+
tokenEnv?: string;
|
|
10
|
+
/** How long a reveal waits for the version's status to be `ok`. Default {@link HCP_PARSE_WAIT_MS}. */
|
|
11
|
+
waitMs?: number;
|
|
12
|
+
/** How long a reveal waits between reads. Default {@link HCP_POLL_MS}. */
|
|
13
|
+
pollMs?: number;
|
|
14
|
+
}
|
|
15
|
+
export declare const HCP_DEFAULT_HOST = "app.terraform.io";
|
|
16
|
+
/** How many times a call answered 429 is sent again. */
|
|
17
|
+
export declare const HCP_RATE_RETRIES = 5;
|
|
18
|
+
/** The first backoff after a 429 with no `Retry-After`, doubled on each retry. */
|
|
19
|
+
export declare const HCP_BACKOFF_MS = 1000;
|
|
20
|
+
export declare const HCP_PARSE_WAIT_MS = 120000;
|
|
21
|
+
export declare const HCP_POLL_MS = 2000;
|
|
22
|
+
/** The variables a target names as its credentials: the token variable, and the host's `TF_TOKEN_` form. */
|
|
23
|
+
export declare function hcpTokenEnv(o: Pick<HcpModuleTargetOptions, "tokenEnv" | "host">): string[];
|
|
24
|
+
/** The text of a 404 from a create: the two causes HCP's docs give. */
|
|
25
|
+
export declare const hcpNotFoundText: (org: string, host: string) => string;
|
|
26
|
+
/** How long to wait before the next try after a 429: `Retry-After` in seconds or as a date, else the backoff. */
|
|
27
|
+
export declare function retryDelay(retryAfter: string | null, attempt: number, now: number): number;
|
|
28
|
+
export declare function hcpModuleTarget(options: HcpModuleTargetOptions, deps?: ModuleTargetDeps): ModulePublishTarget<"hcp">;
|
|
29
|
+
//# sourceMappingURL=hcp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hcp.d.ts","sourceRoot":"","sources":["../../../src/registry/targets/hcp.ts"],"names":[],"mappings":"AAkDA,OAAO,EAQL,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EAEtB,MAAM,iBAAiB,CAAC;AAEzB,gFAAgF;AAChF,MAAM,WAAW,sBAAsB;IACrC,iFAAiF;IACjF,YAAY,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sGAAsG;IACtG,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,gBAAgB,qBAAqB,CAAC;AACnD,wDAAwD;AACxD,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,kFAAkF;AAClF,eAAO,MAAM,cAAc,OAAO,CAAC;AACnC,eAAO,MAAM,iBAAiB,SAAU,CAAC;AACzC,eAAO,MAAM,WAAW,OAAO,CAAC;AAIhC,4GAA4G;AAC5G,wBAAgB,WAAW,CAAC,CAAC,EAAE,IAAI,CAAC,sBAAsB,EAAE,UAAU,GAAG,MAAM,CAAC,GAAG,MAAM,EAAE,CAG1F;AAED,uEAAuE;AACvE,eAAO,MAAM,eAAe,QAAS,MAAM,QAAQ,MAAM,KAAG,MACiH,CAAC;AAE9K,iHAAiH;AACjH,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAQ1F;AAYD,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,EAAE,IAAI,GAAE,gBAAqB,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAiPxH"}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HCP Terraform's private module registry as a module target (chant#4604),
|
|
3
|
+
* on the API flow terragucci#1292's ruling settled for every tier, with no
|
|
4
|
+
* VCS connection:
|
|
5
|
+
*
|
|
6
|
+
* 1. GET /api/v2/organizations/:org/registry-modules/private/:org/:name/:provider
|
|
7
|
+
* and, only when that answers 404,
|
|
8
|
+
* POST /api/v2/organizations/:org/registry-modules
|
|
9
|
+
* {type: registry-modules, attributes: {name, provider, registry-name: private}}
|
|
10
|
+
* 2. POST /api/v2/organizations/:org/registry-modules/private/:org/:name/:provider/versions
|
|
11
|
+
* {type: registry-module-versions, attributes: {version, commit-sha}}
|
|
12
|
+
* whose 201 carries `links.upload`
|
|
13
|
+
* 3. PUT <links.upload>, Content-Type: application/octet-stream, the gzip tarball
|
|
14
|
+
*
|
|
15
|
+
* (developer.hashicorp.com/terraform/cloud-docs/api-docs/private-registry/modules).
|
|
16
|
+
* The archive is the one the bucket registry serves and the attestation signs
|
|
17
|
+
* (terragucci#1290 ruling B), and `commit-sha` carries the revision.
|
|
18
|
+
*
|
|
19
|
+
* `stage` makes the three calls. A module already there is not created
|
|
20
|
+
* again, and a version already there is judged by its content (`held`): the
|
|
21
|
+
* same content comes back `present` with nothing sent, other content is
|
|
22
|
+
* refused. HCP lists a version as soon as it is created, so there is nothing
|
|
23
|
+
* to hide before a reveal; `reveal` reads the module until the version's
|
|
24
|
+
* status is `ok`, through the contract's `sleep` and `now`, and a version
|
|
25
|
+
* still parsing when the wait runs out is reported in a line, without failing.
|
|
26
|
+
*
|
|
27
|
+
* The token is `TFE_TOKEN` (or the declaration's `tokenEnv`), else the one
|
|
28
|
+
* Terraform sends the host: `TF_TOKEN_<host>` or a credentials block
|
|
29
|
+
* (core's terraform/credentials). It goes in `Authorization: Bearer` to the
|
|
30
|
+
* API host only. The upload link is a signed archivist URL, so the token is
|
|
31
|
+
* not sent there, as go-tfe's upload sends none.
|
|
32
|
+
*
|
|
33
|
+
* Every call is retried on a 429 after `Retry-After`, else with a doubling
|
|
34
|
+
* backoff, up to {@link HCP_RATE_RETRIES} times; HCP's API allows 30 requests
|
|
35
|
+
* a second per user. A 404 on a create names the two causes the docs give:
|
|
36
|
+
* a token whose team lacks the Manage modules permission, or an organization
|
|
37
|
+
* without the private registry entitlement.
|
|
38
|
+
*
|
|
39
|
+
* Deprecation: the HCP docs read for this target (the private registry
|
|
40
|
+
* modules API and the publish page) give no call to deprecate a version, so
|
|
41
|
+
* HCP shows none and the bucket catalog carries it. Left for the maintainer
|
|
42
|
+
* run, as terragucci#1292 left them open: which token types work (team,
|
|
43
|
+
* user, organization), moving a module between API and VCS publishing, any
|
|
44
|
+
* archive size ceiling, and whether the upload link wants the token.
|
|
45
|
+
*/
|
|
46
|
+
import { PublishTargetError } from "@intentius/chant/lifecycle/release-publish";
|
|
47
|
+
import { terraformCredential, tokenVariable } from "@intentius/chant/terraform/credentials";
|
|
48
|
+
import { createHash } from "node:crypto";
|
|
49
|
+
import { RegistryError, fetchTarball, registryLocation } from "../reader.js";
|
|
50
|
+
import { compareSemver, parseSemver } from "../semver.js";
|
|
51
|
+
import { answerError, checkModuleVersion, contentDigest, defaultPublishFetch, versionHeldError, } from "./module-target.js";
|
|
52
|
+
export const HCP_DEFAULT_HOST = "app.terraform.io";
|
|
53
|
+
/** How many times a call answered 429 is sent again. */
|
|
54
|
+
export const HCP_RATE_RETRIES = 5;
|
|
55
|
+
/** The first backoff after a 429 with no `Retry-After`, doubled on each retry. */
|
|
56
|
+
export const HCP_BACKOFF_MS = 1000;
|
|
57
|
+
export const HCP_PARSE_WAIT_MS = 120000;
|
|
58
|
+
export const HCP_POLL_MS = 2000;
|
|
59
|
+
const JSON_API = "application/vnd.api+json";
|
|
60
|
+
/** The variables a target names as its credentials: the token variable, and the host's `TF_TOKEN_` form. */
|
|
61
|
+
export function hcpTokenEnv(o) {
|
|
62
|
+
const host = o.host ?? HCP_DEFAULT_HOST;
|
|
63
|
+
return [o.tokenEnv ?? "TFE_TOKEN", tokenVariable(host)].filter((v) => !!v);
|
|
64
|
+
}
|
|
65
|
+
/** The text of a 404 from a create: the two causes HCP's docs give. */
|
|
66
|
+
export const hcpNotFoundText = (org, host) => `${host} answered 404 for organization ${org}: either the token's team lacks the Manage modules permission, or the organization has no private module registry entitlement`;
|
|
67
|
+
/** How long to wait before the next try after a 429: `Retry-After` in seconds or as a date, else the backoff. */
|
|
68
|
+
export function retryDelay(retryAfter, attempt, now) {
|
|
69
|
+
if (retryAfter) {
|
|
70
|
+
const secs = Number(retryAfter.trim());
|
|
71
|
+
if (Number.isFinite(secs) && secs >= 0)
|
|
72
|
+
return secs * 1000;
|
|
73
|
+
const at = Date.parse(retryAfter);
|
|
74
|
+
if (Number.isFinite(at))
|
|
75
|
+
return Math.max(0, at - now);
|
|
76
|
+
}
|
|
77
|
+
return HCP_BACKOFF_MS * 2 ** attempt;
|
|
78
|
+
}
|
|
79
|
+
export function hcpModuleTarget(options, deps = {}) {
|
|
80
|
+
const fetch = deps.fetch ?? defaultPublishFetch;
|
|
81
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
82
|
+
const now = deps.now ?? Date.now;
|
|
83
|
+
const host = (options.host ?? HCP_DEFAULT_HOST).toLowerCase();
|
|
84
|
+
const org = options.organization;
|
|
85
|
+
const api = `https://${host}/api/v2`;
|
|
86
|
+
const tokenEnv = hcpTokenEnv(options);
|
|
87
|
+
const first = options.tokenEnv ?? "TFE_TOKEN";
|
|
88
|
+
const key = `hcp:${createHash("sha256")
|
|
89
|
+
.update(JSON.stringify({ organization: org, host, tokenEnv: options.tokenEnv ?? null }))
|
|
90
|
+
.digest("hex")
|
|
91
|
+
.slice(0, 16)}`;
|
|
92
|
+
const addressOf = (a) => ({ ...a, namespace: org });
|
|
93
|
+
const sourceOf = (a) => `${host}/${org}/${a.name}/${a.system}`;
|
|
94
|
+
const moduleUrl = (a) => `${api}/organizations/${encodeURIComponent(org)}/registry-modules/private/${encodeURIComponent(org)}/${encodeURIComponent(a.name)}/${encodeURIComponent(a.system)}`;
|
|
95
|
+
const token = (ctx) => {
|
|
96
|
+
const direct = ctx.env[first];
|
|
97
|
+
const found = direct ? { value: direct, from: first } : (() => {
|
|
98
|
+
const c = terraformCredential(host, { env: ctx.env });
|
|
99
|
+
return c ? { value: c.token, from: c.from } : null;
|
|
100
|
+
})();
|
|
101
|
+
if (found)
|
|
102
|
+
ctx.redactor.add(found.value);
|
|
103
|
+
return found;
|
|
104
|
+
};
|
|
105
|
+
const needToken = (ctx) => {
|
|
106
|
+
const t = token(ctx);
|
|
107
|
+
if (!t) {
|
|
108
|
+
throw new PublishTargetError("publish-credentials-missing", `no token for ${host}: set ${tokenEnv.join(" or ")}, or a credentials "${host}" block`, "Use a team token whose team has the Manage modules permission.");
|
|
109
|
+
}
|
|
110
|
+
return t;
|
|
111
|
+
};
|
|
112
|
+
/** One call, sent again on a 429 up to the limit. `auth` false sends no token (the upload link). */
|
|
113
|
+
const call = async (ctx, method, url, body, auth = true) => {
|
|
114
|
+
const headers = {};
|
|
115
|
+
if (auth)
|
|
116
|
+
headers.authorization = `Bearer ${needToken(ctx).value}`;
|
|
117
|
+
if (typeof body === "string")
|
|
118
|
+
headers["content-type"] = JSON_API;
|
|
119
|
+
else if (body)
|
|
120
|
+
headers["content-type"] = "application/octet-stream";
|
|
121
|
+
if (auth)
|
|
122
|
+
headers.accept = JSON_API;
|
|
123
|
+
for (let attempt = 0;; attempt++) {
|
|
124
|
+
let res;
|
|
125
|
+
try {
|
|
126
|
+
res = await fetch(url, { method, headers, ...(body !== undefined ? { body } : {}) });
|
|
127
|
+
}
|
|
128
|
+
catch (e) {
|
|
129
|
+
throw answerError(ctx, "publish-upload-failed", `${method} ${url.split("?")[0]}: ${e.message}`);
|
|
130
|
+
}
|
|
131
|
+
const text = await res.text().catch(() => "");
|
|
132
|
+
if (res.status === 429) {
|
|
133
|
+
if (attempt >= HCP_RATE_RETRIES) {
|
|
134
|
+
throw answerError(ctx, "publish-rate-limited", `${host} answered 429 to ${method} ${url.split("?")[0]} ${attempt + 1} times`, "HCP allows 30 requests a second per user; run the publish again later, and it sends only what is missing.");
|
|
135
|
+
}
|
|
136
|
+
const ms = retryDelay(res.headers.get("retry-after"), attempt, now());
|
|
137
|
+
ctx.log(`${host} answered 429; trying again in ${Math.ceil(ms / 1000)}s`);
|
|
138
|
+
await sleep(ms);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
let json = null;
|
|
142
|
+
try {
|
|
143
|
+
json = text ? JSON.parse(text) : null;
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
json = null;
|
|
147
|
+
}
|
|
148
|
+
return { status: res.status, json, text };
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
const refusedToken = (ctx, status, what) => {
|
|
152
|
+
const t = token(ctx);
|
|
153
|
+
return answerError(ctx, "publish-credentials-refused", `${host} answered ${status} to ${what}: it refused the token from ${t?.from ?? first}`, "Use a team token whose team has the Manage modules permission.");
|
|
154
|
+
};
|
|
155
|
+
const failed = (ctx, a, what) => answerError(ctx, "publish-upload-failed", `${host} answered ${a.status} to ${what}: ${a.text.slice(0, 300)}`);
|
|
156
|
+
/** The module's versions and their statuses, or undefined when HCP answers 404. */
|
|
157
|
+
const readModule = async (ctx, a) => {
|
|
158
|
+
const res = await call(ctx, "GET", moduleUrl(a));
|
|
159
|
+
if (res.status === 404)
|
|
160
|
+
return undefined;
|
|
161
|
+
if (res.status === 401 || res.status === 403)
|
|
162
|
+
throw refusedToken(ctx, res.status, `reading ${sourceOf(a)}`);
|
|
163
|
+
if (res.status !== 200)
|
|
164
|
+
throw failed(ctx, res, `reading ${sourceOf(a)}`);
|
|
165
|
+
const attrs = res.json?.data?.attributes ?? {};
|
|
166
|
+
const raw = Array.isArray(attrs["version-statuses"]) ? attrs["version-statuses"] : [];
|
|
167
|
+
return { versions: raw.filter((v) => typeof v.version === "string").map((v) => ({ version: v.version, status: typeof v.status === "string" ? v.status : "" })) };
|
|
168
|
+
};
|
|
169
|
+
const versions = async (address, ctx) => {
|
|
170
|
+
const doc = await readModule({ ...ctx, log: () => { } }, addressOf(address));
|
|
171
|
+
const vs = (doc?.versions ?? []).map((v) => v.version);
|
|
172
|
+
return vs.sort((x, y) => {
|
|
173
|
+
const a = parseSemver(x);
|
|
174
|
+
const b = parseSemver(y);
|
|
175
|
+
return a && b ? compareSemver(a, b) : x.localeCompare(y);
|
|
176
|
+
});
|
|
177
|
+
};
|
|
178
|
+
/** The archive of an `ok` version, read the way Terraform reads it: discovery, download, the tarball. */
|
|
179
|
+
const download = async (ctx, a, version) => {
|
|
180
|
+
const t = needToken(ctx);
|
|
181
|
+
const o = { token: t.value, ...(deps.registryFetch ? { fetch: deps.registryFetch } : {}) };
|
|
182
|
+
try {
|
|
183
|
+
const location = await registryLocation(sourceOf(a), version, o);
|
|
184
|
+
// The location is another host (archivist); the API token is not sent there.
|
|
185
|
+
const bytes = await fetchTarball(location, { credentials: { env: ctx.env }, ...(deps.registryFetch ? { fetch: deps.registryFetch } : {}) });
|
|
186
|
+
return contentDigest(bytes);
|
|
187
|
+
}
|
|
188
|
+
catch (e) {
|
|
189
|
+
if (e instanceof RegistryError)
|
|
190
|
+
throw answerError(ctx, "publish-read-failed", e.message.replace(/(https?:\/\/[^\s?]+)\?[^\s)"']*/g, "$1?[query]"));
|
|
191
|
+
throw e;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
const heldIn = async (ctx, a, version, doc) => {
|
|
195
|
+
const v = doc?.versions.find((x) => x.version === version);
|
|
196
|
+
if (!v)
|
|
197
|
+
return undefined;
|
|
198
|
+
if (v.status !== "ok")
|
|
199
|
+
return { content: "", status: v.status || "unknown" };
|
|
200
|
+
return { content: await download(ctx, a, version) };
|
|
201
|
+
};
|
|
202
|
+
const held = async (address, version, ctx) => {
|
|
203
|
+
const a = addressOf(address);
|
|
204
|
+
return heldIn(ctx, a, version, await readModule({ ...ctx, log: () => { } }, a));
|
|
205
|
+
};
|
|
206
|
+
const item = (ctx, present) => {
|
|
207
|
+
const a = addressOf(ctx.address);
|
|
208
|
+
return { name: `${org}/${a.name}/${a.system} ${ctx.version}`, destination: sourceOf(a), digest: ctx.content, ...(present ? { present: true } : {}) };
|
|
209
|
+
};
|
|
210
|
+
return {
|
|
211
|
+
kind: "hcp",
|
|
212
|
+
name: `HCP Terraform registry (${org})`,
|
|
213
|
+
destination: `${host}/${org}`,
|
|
214
|
+
credentialEnv: tokenEnv,
|
|
215
|
+
key,
|
|
216
|
+
versions,
|
|
217
|
+
held,
|
|
218
|
+
async plan(ctx) {
|
|
219
|
+
checkModuleVersion(ctx);
|
|
220
|
+
needToken(ctx);
|
|
221
|
+
return [item(ctx)];
|
|
222
|
+
},
|
|
223
|
+
async stage(ctx) {
|
|
224
|
+
checkModuleVersion(ctx);
|
|
225
|
+
needToken(ctx);
|
|
226
|
+
const a = addressOf(ctx.address);
|
|
227
|
+
const source = sourceOf(a);
|
|
228
|
+
const staged = (present) => ({ target: "hcp", destination: source, items: [item(ctx, present)], state: { organization: org, host, version: ctx.version } });
|
|
229
|
+
// 1. The module, created only when absent.
|
|
230
|
+
let doc = await readModule(ctx, a);
|
|
231
|
+
if (!doc) {
|
|
232
|
+
const res = await call(ctx, "POST", `${api}/organizations/${encodeURIComponent(org)}/registry-modules`, JSON.stringify({ data: { type: "registry-modules", attributes: { name: a.name, provider: a.system, "registry-name": "private" } } }));
|
|
233
|
+
if (res.status === 404)
|
|
234
|
+
throw answerError(ctx, "publish-not-found", hcpNotFoundText(org, host), "Give the token's team the Manage modules permission, and check the organization's plan includes the private registry.");
|
|
235
|
+
if (res.status === 401 || res.status === 403)
|
|
236
|
+
throw refusedToken(ctx, res.status, `creating ${source}`);
|
|
237
|
+
if (res.status === 422) {
|
|
238
|
+
// Another run created it between the read and the create.
|
|
239
|
+
doc = await readModule(ctx, a);
|
|
240
|
+
if (!doc)
|
|
241
|
+
throw failed(ctx, res, `creating ${source}`);
|
|
242
|
+
}
|
|
243
|
+
else if (res.status !== 201)
|
|
244
|
+
throw failed(ctx, res, `creating ${source}`);
|
|
245
|
+
else {
|
|
246
|
+
ctx.log(`created ${source} in the private registry`);
|
|
247
|
+
doc = { versions: [] };
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// A version already there: judged by its content, never sent again.
|
|
251
|
+
const judge = (h) => {
|
|
252
|
+
if (h.status) {
|
|
253
|
+
throw answerError(ctx, "publish-version-unfinished", `${source} ${ctx.version} is on ${host} with status ${h.status}, so its content can't be read`, h.status === "pending" ? "An earlier run created the version and its upload did not finish; delete the version in HCP Terraform's registry and publish again." : "Look at the version in HCP Terraform's registry; delete it and publish again.");
|
|
254
|
+
}
|
|
255
|
+
if (h.content !== ctx.content)
|
|
256
|
+
throw versionHeldError(source, ctx, h);
|
|
257
|
+
ctx.log(`${source} ${ctx.version} is already on ${host} with this content; nothing sent`);
|
|
258
|
+
return staged(true);
|
|
259
|
+
};
|
|
260
|
+
const before = await heldIn(ctx, a, ctx.version, doc);
|
|
261
|
+
if (before)
|
|
262
|
+
return judge(before);
|
|
263
|
+
// 2. The version, carrying the revision.
|
|
264
|
+
const created = await call(ctx, "POST", `${moduleUrl(a)}/versions`, JSON.stringify({ data: { type: "registry-module-versions", attributes: { version: ctx.version, "commit-sha": ctx.revision || null } } }));
|
|
265
|
+
if (created.status === 404)
|
|
266
|
+
throw answerError(ctx, "publish-not-found", hcpNotFoundText(org, host), "Give the token's team the Manage modules permission, and check the organization's plan includes the private registry.");
|
|
267
|
+
if (created.status === 401 || created.status === 403)
|
|
268
|
+
throw refusedToken(ctx, created.status, `creating ${source} ${ctx.version}`);
|
|
269
|
+
if (created.status === 422) {
|
|
270
|
+
const again = await heldIn(ctx, a, ctx.version, await readModule(ctx, a));
|
|
271
|
+
if (again)
|
|
272
|
+
return judge(again);
|
|
273
|
+
throw failed(ctx, created, `creating ${source} ${ctx.version}`);
|
|
274
|
+
}
|
|
275
|
+
if (created.status !== 201)
|
|
276
|
+
throw failed(ctx, created, `creating ${source} ${ctx.version}`);
|
|
277
|
+
const upload = created.json?.data?.links?.upload;
|
|
278
|
+
if (typeof upload !== "string" || !upload)
|
|
279
|
+
throw answerError(ctx, "publish-upload-failed", `${host} created ${source} ${ctx.version} and gave no upload link`);
|
|
280
|
+
// 3. The archive, to the signed upload link, with no token.
|
|
281
|
+
const put = await call(ctx, "PUT", upload, ctx.archive, false);
|
|
282
|
+
if (put.status < 200 || put.status >= 300) {
|
|
283
|
+
throw answerError(ctx, "publish-upload-failed", `the upload of ${source} ${ctx.version} answered ${put.status}: ${put.text.slice(0, 300)}`, "The version exists with status pending; delete it in HCP Terraform's registry and publish again.");
|
|
284
|
+
}
|
|
285
|
+
ctx.log(`uploaded ${source} ${ctx.version}`);
|
|
286
|
+
return staged(false);
|
|
287
|
+
},
|
|
288
|
+
async reveal(ctx, staged) {
|
|
289
|
+
const a = addressOf(ctx.address);
|
|
290
|
+
const wait = options.waitMs ?? HCP_PARSE_WAIT_MS;
|
|
291
|
+
const poll = options.pollMs ?? HCP_POLL_MS;
|
|
292
|
+
const start = now();
|
|
293
|
+
for (;;) {
|
|
294
|
+
const doc = await readModule(ctx, a);
|
|
295
|
+
const status = doc?.versions.find((v) => v.version === ctx.version)?.status;
|
|
296
|
+
if (status === "ok")
|
|
297
|
+
return { target: staged.target, destination: staged.destination, items: staged.items };
|
|
298
|
+
// The docs name pending and ok; a status that says it failed stops the wait, any other keeps it.
|
|
299
|
+
if (status && /error|fail/i.test(status)) {
|
|
300
|
+
throw answerError(ctx, "publish-reveal-failed", `${staged.destination} ${ctx.version} has status ${status} on ${host}, so HCP did not take the archive`, "Look at the version in HCP Terraform's registry; delete it and publish again.");
|
|
301
|
+
}
|
|
302
|
+
if (now() - start >= wait) {
|
|
303
|
+
ctx.log(`${staged.destination} ${ctx.version} is still ${status ?? "not listed"} on ${host} after ${Math.round(wait / 1000)}s; HCP lists it once it has parsed the archive`);
|
|
304
|
+
return { target: staged.target, destination: staged.destination, items: staged.items };
|
|
305
|
+
}
|
|
306
|
+
await sleep(poll);
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the Artifactory and Nexus module targets share (chant#4605): basic
|
|
3
|
+
* `user:token` write credentials (terragucci#1299 ruling B), a binary GET of
|
|
4
|
+
* an uploaded archive, and the wait for a version to be listed after its
|
|
5
|
+
* upload, since both hosts index an upload after the PUT answers.
|
|
6
|
+
*/
|
|
7
|
+
import { type Credential } from "@intentius/chant/lifecycle/publish-credentials";
|
|
8
|
+
import { PublishTargetError, type PublishContextBase } from "@intentius/chant/lifecycle/release-publish";
|
|
9
|
+
import type { RegistryFetch } from "../reader.js";
|
|
10
|
+
/** Where a target reads its basic credentials. */
|
|
11
|
+
export interface BasicCredentialEnv {
|
|
12
|
+
/** The variable the token (or password) is read from. */
|
|
13
|
+
tokenEnv: string;
|
|
14
|
+
/** The variable the user name is read from, when `user` is not given. */
|
|
15
|
+
userEnv: string;
|
|
16
|
+
/** The user name itself, in place of `userEnv`. */
|
|
17
|
+
user?: string;
|
|
18
|
+
}
|
|
19
|
+
/** A user and token, and the `Authorization` header they make. The header and the token are masked. */
|
|
20
|
+
export interface BasicCredential {
|
|
21
|
+
user: string;
|
|
22
|
+
token: Credential;
|
|
23
|
+
/** `Basic <base64 of user:token>`. */
|
|
24
|
+
header: string;
|
|
25
|
+
}
|
|
26
|
+
/** The basic credential the environment holds, added to the redactor, or null when either half is missing. */
|
|
27
|
+
export declare function readBasicCredential(ctx: Pick<PublishContextBase, "env" | "redactor">, names: BasicCredentialEnv): BasicCredential | null;
|
|
28
|
+
/** The basic credential, or the refusal naming both variables. */
|
|
29
|
+
export declare function needBasicCredential(ctx: Pick<PublishContextBase, "env" | "redactor">, names: BasicCredentialEnv, host: string, fix: string): BasicCredential;
|
|
30
|
+
/** The global fetch, in the reader's shape. */
|
|
31
|
+
export declare const defaultRegistryFetch: RegistryFetch;
|
|
32
|
+
/** A GET of bytes, with its status. A network failure is a `publish-read-failed`. */
|
|
33
|
+
export declare function getBytes(ctx: Pick<PublishContextBase, "redactor">, fetchFn: RegistryFetch, url: string, authorization?: string): Promise<{
|
|
34
|
+
status: number;
|
|
35
|
+
bytes: Uint8Array;
|
|
36
|
+
}>;
|
|
37
|
+
/** How long a reveal waits for the host to list an uploaded version, and how often it asks. */
|
|
38
|
+
export interface ListWait {
|
|
39
|
+
waitMs: number;
|
|
40
|
+
pollMs: number;
|
|
41
|
+
sleep: (ms: number) => Promise<void>;
|
|
42
|
+
now: () => number;
|
|
43
|
+
}
|
|
44
|
+
export declare const HOSTED_LIST_WAIT_MS = 30000;
|
|
45
|
+
export declare const HOSTED_POLL_MS = 2000;
|
|
46
|
+
/** Reads `list` until it holds `version` or the wait is over; true when it was listed. */
|
|
47
|
+
export declare function waitListed(list: () => Promise<string[]>, version: string, wait: ListWait): Promise<boolean>;
|
|
48
|
+
/** The refusal for a 401 or 403 on an upload, naming the variables, never their values. */
|
|
49
|
+
export declare function writeRefused(ctx: Pick<PublishContextBase, "redactor">, host: string, status: number, what: string, credential: BasicCredential, names: BasicCredentialEnv, fix: string): PublishTargetError;
|
|
50
|
+
/**
|
|
51
|
+
* The content of the archive a download location holds, as the CLIs would
|
|
52
|
+
* fetch it: the registry's credential goes along only to the registry's own
|
|
53
|
+
* host and only when the location has no query (a signed URL carries its own
|
|
54
|
+
* grant). Refuses a location that answers other than 2xx.
|
|
55
|
+
*/
|
|
56
|
+
export declare function locationContent(ctx: Pick<PublishContextBase, "redactor">, fetchFn: RegistryFetch, location: string, registryHost: string, authorization: string | undefined): Promise<{
|
|
57
|
+
content: string;
|
|
58
|
+
revision?: string;
|
|
59
|
+
}>;
|
|
60
|
+
/** The refusal for a download that holds other content than the version staged. */
|
|
61
|
+
export declare const revealMismatch: (ctx: Pick<PublishContextBase, "redactor">, where: string, version: string, got: string, want: string) => PublishTargetError;
|
|
62
|
+
//# sourceMappingURL=hosted.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hosted.d.ts","sourceRoot":"","sources":["../../../src/registry/targets/hosted.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,gDAAgD,CAAC;AACjG,OAAO,EAAE,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,MAAM,4CAA4C,CAAC;AACzG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAI/C,kDAAkD;AAClD,MAAM,WAAW,kBAAkB;IACjC,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,uGAAuG;AACvG,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,UAAU,CAAC;IAClB,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,8GAA8G;AAC9G,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,IAAI,CAAC,kBAAkB,EAAE,KAAK,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,kBAAkB,GAAG,eAAe,GAAG,IAAI,CAOxI;AAED,kEAAkE;AAClE,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,IAAI,CAAC,kBAAkB,EAAE,KAAK,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,eAAe,CAK5J;AAED,+CAA+C;AAC/C,eAAO,MAAM,oBAAoB,EAAE,aAA+C,CAAC;AAEnF,qFAAqF;AACrF,wBAAsB,QAAQ,CAC5B,GAAG,EAAE,IAAI,CAAC,kBAAkB,EAAE,UAAU,CAAC,EACzC,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,MAAM,EACX,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,CAAC,CAQhD;AAED,+FAA+F;AAC/F,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,GAAG,EAAE,MAAM,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,mBAAmB,QAAS,CAAC;AAC1C,eAAO,MAAM,cAAc,OAAQ,CAAC;AAEpC,0FAA0F;AAC1F,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAOjH;AAED,2FAA2F;AAC3F,wBAAgB,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,kBAAkB,EAAE,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,eAAe,EAAE,KAAK,EAAE,kBAAkB,EAAE,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAG3M;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,IAAI,CAAC,kBAAkB,EAAE,UAAU,CAAC,EACzC,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,GAAG,SAAS,GAChC,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAMjD;AAED,mFAAmF;AACnF,eAAO,MAAM,cAAc,QAAS,IAAI,CAAC,kBAAkB,EAAE,UAAU,CAAC,SAAS,MAAM,WAAW,MAAM,OAAO,MAAM,QAAQ,MAAM,KAAG,kBACmF,CAAC"}
|