@intentius/chant-lexicon-terraform 0.124.52 → 0.124.54
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 +7 -6
- 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/commands.d.ts.map +1 -1
- package/dist/commands.js +4 -3
- package/dist/components/capability-plugin.d.ts +65 -3
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/capability-plugin.js +197 -13
- 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/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/config.d.ts +59 -13
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +43 -9
- 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/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 +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -2
- package/dist/integrity.json +3 -3
- 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/lsp/option-keys.d.ts.map +1 -1
- package/dist/lsp/option-keys.js +2 -0
- package/dist/manifest.json +1 -1
- 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 +153 -5
- package/dist/op/activities/terraform.d.ts.map +1 -1
- package/dist/op/activities/terraform.js +205 -20
- 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/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 +32 -18
- package/dist/report.d.ts +15 -6
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +28 -55
- package/dist/skills/chant-terraform.md +1 -1
- package/dist/state/azure-blob.d.ts +68 -0
- package/dist/state/azure-blob.d.ts.map +1 -0
- package/dist/state/azure-blob.js +201 -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 +62 -0
- package/dist/state/gcs.d.ts.map +1 -0
- package/dist/state/gcs.js +224 -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 +37 -0
- package/dist/state/object-store.d.ts.map +1 -0
- package/dist/state/object-store.js +20 -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 +68 -0
- package/dist/state/s3.d.ts.map +1 -0
- package/dist/state/s3.js +170 -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/terragrunt/affected.d.ts.map +1 -1
- package/dist/terragrunt/affected.js +2 -1
- package/package.json +7 -3
- 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 +11 -0
- package/src/__fixtures__/drift-record/live-plan-sections.json +27 -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/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/commands.ts +4 -3
- package/src/components/capability-plugin.test.ts +172 -5
- package/src/components/capability-plugin.ts +212 -11
- 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 +112 -56
- 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/config-file-listers.test.ts +265 -0
- package/src/config.ts +44 -9
- package/src/describe-resources.engine-facts.test.ts +412 -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/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 +76 -0
- package/src/live-plan-reasons.ts +54 -0
- package/src/lsp/completions.test.ts +1 -1
- package/src/lsp/option-keys.ts +2 -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 +266 -22
- 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/pin/bundle.test.ts +2 -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 +33 -19
- 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 +223 -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 +254 -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 +35 -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 +193 -0
- package/src/state/store.ts +225 -0
- package/src/terragrunt/affected.ts +2 -1
- 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 @@
|
|
|
1
|
+
{"version":3,"file":"compose-group.d.ts","sourceRoot":"","sources":["../../src/components/compose-group.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `terraform-apply`'s group methods (chant#4447, ws-107): core forms a group
|
|
3
|
+
* of `terraform-apply` components that read each other, and these plan it
|
|
4
|
+
* as one plan and apply it as one apply of a composed root, so a value one
|
|
5
|
+
* root creates reaches its readers inside that apply.
|
|
6
|
+
*
|
|
7
|
+
* - `groupReads` reports to core each member's `terraform_remote_state` and
|
|
8
|
+
* `terraform_estate_outputs` reads of another, as the composition finds
|
|
9
|
+
* them, so roots wired in HCL alone form a group. Nothing while
|
|
10
|
+
* `terraform.compose.enabled` is unset.
|
|
11
|
+
* - `groupFallback` answers `off` while `terraform.compose.enabled` is unset,
|
|
12
|
+
* else builds the {@link Composition} (#4438) from the files, the config
|
|
13
|
+
* and each member's cloud identity, and answers its fallback, if any. A
|
|
14
|
+
* member the composition leaves out is dropped with its own kind and
|
|
15
|
+
* reason, and core forms the group again from the rest (#4419 ruling 1).
|
|
16
|
+
* A composition of estates also asks `choudoufu version` (#4445).
|
|
17
|
+
* Nothing plans.
|
|
18
|
+
* - `planGroup` builds the composition again, plans it once with
|
|
19
|
+
* `ComposedRun` (#4444) or, for estates, `EstateRun` (#4445), and answers
|
|
20
|
+
* each member's part (#4442): its change-set part, planned outputs and
|
|
21
|
+
* shape, with its own plan's digest. The group's digest is the composed
|
|
22
|
+
* plan's with the generated files. The run is released before it returns,
|
|
23
|
+
* with no lock taken, so a plan that core then refuses or makes wait
|
|
24
|
+
* leaves no lock held and no file changed.
|
|
25
|
+
* - `runGroup` plans once more, this time under each state's lock, and
|
|
26
|
+
* applies only when that plan's digest is the one `planGroup` answered;
|
|
27
|
+
* otherwise it throws a moved plan and nothing applies. Whatever happens,
|
|
28
|
+
* it releases the run. Each member's outputs come from the run: its part
|
|
29
|
+
* of the working state's `show -json` for roots with state, the post-apply
|
|
30
|
+
* plan's `prior_state` for estates.
|
|
31
|
+
*
|
|
32
|
+
* Each member's identity is `ci.identity.cloud` resolved for its root as
|
|
33
|
+
* the target (`resolveTargetCloudIdentity`, #4332), by the root's key or its
|
|
34
|
+
* directory, whichever a rule's target glob matches, compared by
|
|
35
|
+
* `identityKey`, so members that apply under different identities do not
|
|
36
|
+
* compose (#4438's `identity`).
|
|
37
|
+
*/
|
|
38
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
39
|
+
import { tmpdir } from "node:os";
|
|
40
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
41
|
+
import { loadChantConfigUpward } from "@intentius/chant/config";
|
|
42
|
+
import { wiringReferencesAt } from "@intentius/chant/components/driver";
|
|
43
|
+
import { computePlanDigest } from "@intentius/chant/lifecycle/plan-digest";
|
|
44
|
+
import { declaredTargetCloudIdentity } from "@intentius/chant/workspace/ci-cloud-identity";
|
|
45
|
+
import { COMPOSE_STEP_KIND, composeOff, compositionIfEnabled, identityKey, rootHclReads } from "../compose.js";
|
|
46
|
+
import { terraformBinary, terraformOutputs } from "../op/activities/terraform.js";
|
|
47
|
+
import { runBinary } from "../state/exec.js";
|
|
48
|
+
import { estateVersionFallback, EstateRun } from "../compose/estate.js";
|
|
49
|
+
import { ComposedRun } from "../compose/run.js";
|
|
50
|
+
import { relPath, rootTexts } from "../compose/hcl.js";
|
|
51
|
+
async function loadProject() {
|
|
52
|
+
const start = resolve(process.cwd());
|
|
53
|
+
const { config, configPath } = await loadChantConfigUpward(start);
|
|
54
|
+
const projectRoot = configPath ? dirname(configPath) : start;
|
|
55
|
+
const terraform = config.terraform;
|
|
56
|
+
return { projectRoot, terraform: { ...(terraform ?? {}), roots: terraform?.roots ?? {} } };
|
|
57
|
+
}
|
|
58
|
+
function defaultRunner(o, log) {
|
|
59
|
+
if (o.composition.estate) {
|
|
60
|
+
return new EstateRun({ projectRoot: o.projectRoot, composition: o.composition, terraform: o.terraform, env: o.env, work: o.work, ...(log ? { log } : {}) });
|
|
61
|
+
}
|
|
62
|
+
return new ComposedRun({
|
|
63
|
+
projectRoot: o.projectRoot,
|
|
64
|
+
composition: o.composition,
|
|
65
|
+
terraform: o.terraform,
|
|
66
|
+
binary: { path: terraformBinary(o.terraform) },
|
|
67
|
+
env: o.env,
|
|
68
|
+
work: o.work,
|
|
69
|
+
lock: o.lock,
|
|
70
|
+
...(o.env.GITHUB_RUN_ID ? { runId: `github-run-${o.env.GITHUB_RUN_ID}` } : {}),
|
|
71
|
+
vars: o.vars,
|
|
72
|
+
componentOutputs: o.componentOutputs,
|
|
73
|
+
// A state that could not be written is kept here when its backend cannot take it; it outlives the run's own directory.
|
|
74
|
+
reportDir: join(o.projectRoot, ".chant", "compose", "unwritten"),
|
|
75
|
+
...(log ? { log } : {}),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/** The members as components, each deploying its one step, for `compositionIfEnabled`. */
|
|
79
|
+
export function memberComponents(members) {
|
|
80
|
+
return members.map((m) => ({
|
|
81
|
+
name: m.component,
|
|
82
|
+
...(m.dependsOn ? { dependsOn: [...m.dependsOn] } : {}),
|
|
83
|
+
deploy: [{ phase: "Apply", steps: [{ ...m.step, kind: COMPOSE_STEP_KIND }] }],
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Each member's identity as `compositionFor` compares it: `identityKey` of
|
|
88
|
+
* `lookup` for the member's root, which a rule's target globs match by the
|
|
89
|
+
* root's key in `terraform.roots` or by its directory relative to the
|
|
90
|
+
* project (the first declared rule that either matches wins). Undefined
|
|
91
|
+
* without a lookup, so no member falls back as `identity`.
|
|
92
|
+
*/
|
|
93
|
+
export function memberIdentity(project, members, lookup) {
|
|
94
|
+
if (!lookup)
|
|
95
|
+
return undefined;
|
|
96
|
+
const names = (root) => {
|
|
97
|
+
const dir = project.terraform.roots[root]?.dir;
|
|
98
|
+
if (dir === undefined)
|
|
99
|
+
return [root];
|
|
100
|
+
return [root, relPath(project.projectRoot, isAbsolute(dir) ? dir : resolve(project.projectRoot, dir))];
|
|
101
|
+
};
|
|
102
|
+
const keys = new Map(members.map((m) => [m.component, identityKey(lookup(names(m.step.root)))]));
|
|
103
|
+
return (member) => keys.get(member);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The composition of exactly the group's members (core has already formed the
|
|
107
|
+
* group), or undefined when `terraform.compose` is not enabled.
|
|
108
|
+
*/
|
|
109
|
+
export function groupComposition(project, members, env, identity) {
|
|
110
|
+
const targets = Object.fromEntries(members.flatMap((m) => (m.ctx.targets?.length ? [[m.component, m.ctx.targets]] : [])));
|
|
111
|
+
return compositionIfEnabled({
|
|
112
|
+
projectRoot: project.projectRoot,
|
|
113
|
+
terraform: project.terraform,
|
|
114
|
+
components: memberComponents(members),
|
|
115
|
+
selected: new Set(members.map((m) => m.component)),
|
|
116
|
+
env,
|
|
117
|
+
targets,
|
|
118
|
+
...(identity ? { identity } : {}),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
122
|
+
/** The data source and the attribute that holds its outputs, for each kind of HCL read. */
|
|
123
|
+
const HCL_READ = { "remote-state": ["terraform_remote_state", "outputs"], "estate-outputs": ["terraform_estate_outputs", "values"] };
|
|
124
|
+
/** The outputs the texts read off `data.<type>.<name>.<field>`, by attribute or by index, sorted. */
|
|
125
|
+
function outputsRead(texts, type, name, field) {
|
|
126
|
+
const re = new RegExp(`\\bdata\\.${type}\\.${escapeRe(name)}\\.${field}(?:\\.([A-Za-z_][\\w-]*)|\\[\\s*\\\\?"([^"\\\\]+)\\\\?"\\s*\\])`, "g");
|
|
127
|
+
const out = new Set();
|
|
128
|
+
for (const text of texts)
|
|
129
|
+
for (const m of text.matchAll(re))
|
|
130
|
+
out.add((m[1] ?? m[2]));
|
|
131
|
+
return [...out].sort();
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The members' HCL reads of each other (#4447): each `terraform_remote_state`
|
|
135
|
+
* block whose state is another member's backend, and each
|
|
136
|
+
* `terraform_estate_outputs` block naming another member's estate, as the
|
|
137
|
+
* composition finds them. Each output the reader's code takes off the block
|
|
138
|
+
* is one read, `<upstream>.<output>` as a `stackOutput()` read is named; a
|
|
139
|
+
* block whose outputs the code does not name one by one is one read with
|
|
140
|
+
* the block's address as `ref`. Core cannot see these in the step's `vars`.
|
|
141
|
+
*/
|
|
142
|
+
export function hclGroupReads(project, members) {
|
|
143
|
+
const reads = rootHclReads({ projectRoot: project.projectRoot, terraform: project.terraform, components: memberComponents(members) });
|
|
144
|
+
const out = [];
|
|
145
|
+
for (const [reader, rs] of reads) {
|
|
146
|
+
const m = members.find((x) => x.component === reader);
|
|
147
|
+
const dir = m ? project.terraform.roots[m.step.root]?.dir : undefined;
|
|
148
|
+
const texts = dir === undefined ? [] : rootTexts(isAbsolute(dir) ? dir : resolve(project.projectRoot, dir), project.terraform.binary).map((t) => t.text);
|
|
149
|
+
for (const r of rs) {
|
|
150
|
+
if (r.via === "vars")
|
|
151
|
+
continue;
|
|
152
|
+
const [type, field] = HCL_READ[r.via];
|
|
153
|
+
const named = outputsRead(texts, type, r.name, field);
|
|
154
|
+
if (named.length === 0)
|
|
155
|
+
out.push({ reader, upstream: r.upstream, ref: `data.${type}.${r.name}` });
|
|
156
|
+
for (const output of named)
|
|
157
|
+
out.push({ reader, upstream: r.upstream, ref: `${r.upstream}.${output}`, output });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* What core is told of the group (#4419 ruling 1): the whole change's
|
|
164
|
+
* fallback when it is not `one-root`; else each member the composition left
|
|
165
|
+
* out as a drop with its own kind and reason, so core groups the rest
|
|
166
|
+
* again without them; else `one-root`. Undefined when every member
|
|
167
|
+
* composes.
|
|
168
|
+
*/
|
|
169
|
+
export function groupFallbackOf(c, members) {
|
|
170
|
+
if (c.fallback && c.fallback.kind !== "one-root")
|
|
171
|
+
return { kind: c.fallback.kind, reason: c.fallback.reason };
|
|
172
|
+
const drop = c.fallbacks.flatMap((f) => (f.member !== undefined && members.includes(f.member) ? [{ component: f.member, kind: f.kind, reason: f.reason }] : []));
|
|
173
|
+
if (!c.fallback) {
|
|
174
|
+
const named = new Set(drop.map((d) => d.component));
|
|
175
|
+
for (const m of members) {
|
|
176
|
+
if (!named.has(m) && !c.members.includes(m))
|
|
177
|
+
drop.push({ component: m, kind: "path", reason: `\`${m}\` deploys no root the composition could read` });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (drop.length > 0)
|
|
181
|
+
return { kind: drop[0].kind, reason: drop[0].reason, drop };
|
|
182
|
+
if (c.fallback)
|
|
183
|
+
return { kind: c.fallback.kind, reason: c.fallback.reason };
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
/** `value` at the JSON Pointer `at`. */
|
|
187
|
+
function atPointer(value, at) {
|
|
188
|
+
if (at === "")
|
|
189
|
+
return value;
|
|
190
|
+
return at
|
|
191
|
+
.slice(1)
|
|
192
|
+
.split("/")
|
|
193
|
+
.map((p) => p.replace(/~1/g, "/").replace(/~0/g, "~"))
|
|
194
|
+
.reduce((acc, key) => (acc == null ? acc : acc[key]), value);
|
|
195
|
+
}
|
|
196
|
+
/** Set `value` at the dotted `path` of `target`. */
|
|
197
|
+
function setPath(target, path, value) {
|
|
198
|
+
const keys = path.split(".");
|
|
199
|
+
let at = target;
|
|
200
|
+
for (const k of keys.slice(0, -1)) {
|
|
201
|
+
const next = at[k];
|
|
202
|
+
at = (next && typeof next === "object" ? next : (at[k] = {}));
|
|
203
|
+
}
|
|
204
|
+
at[keys[keys.length - 1]] = value;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* The outputs of components outside the group that the members' `vars`
|
|
208
|
+
* read, recovered from each member's input, which core resolved against
|
|
209
|
+
* them: `writeComposedRoot` turns those reads into literals.
|
|
210
|
+
*/
|
|
211
|
+
export function outsideOutputs(members) {
|
|
212
|
+
var _a;
|
|
213
|
+
const inGroup = new Set(members.map((m) => m.component));
|
|
214
|
+
const out = {};
|
|
215
|
+
for (const m of members) {
|
|
216
|
+
for (const r of wiringReferencesAt(m.step)) {
|
|
217
|
+
if (inGroup.has(r.source))
|
|
218
|
+
continue;
|
|
219
|
+
const value = atPointer(m.input, r.at);
|
|
220
|
+
if (value !== undefined)
|
|
221
|
+
setPath((out[_a = r.source] ?? (out[_a] = {})), r.output, value);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return out;
|
|
225
|
+
}
|
|
226
|
+
/** The members' unresolved reads of components outside the group, by member. */
|
|
227
|
+
function unresolvedOf(members) {
|
|
228
|
+
return new Map(members.flatMap((m) => (m.ctx.unresolvedInputs?.length ? [[m.component, [...m.ctx.unresolvedInputs].sort()]] : [])));
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* The group while a member reads an output of a component outside it that
|
|
232
|
+
* is not known yet: no member is planned, each gets a hole naming what the
|
|
233
|
+
* group waits on, and core plans the group again once those apply.
|
|
234
|
+
*/
|
|
235
|
+
function waitingGroup(ctx, members, unresolved) {
|
|
236
|
+
const waits = [...new Set([...unresolved.values()].flat())].sort();
|
|
237
|
+
const by = [...unresolved.keys()].sort();
|
|
238
|
+
const plans = {};
|
|
239
|
+
for (const m of members) {
|
|
240
|
+
const root = m.step.root;
|
|
241
|
+
const vars = Object.fromEntries(Object.entries((m.input?.vars ?? {})).map(([k, v]) => [k, v === undefined ? null : v]));
|
|
242
|
+
const planDigest = computePlanDigest("terraform-apply-group-unresolved", { env: ctx.env, root, vars, unresolved: waits });
|
|
243
|
+
const reason = by.length === 1 && by[0] === m.component
|
|
244
|
+
? `inputs from ${waits.join(", ")}; planned again once they apply`
|
|
245
|
+
: `inputs from ${waits.join(", ")}, read by ${by.join(", ")} in its group; planned again once they apply`;
|
|
246
|
+
const changeSet = {
|
|
247
|
+
member: { member: root, lexicon: "terraform", planner: "chant", status: "planned", planDigest, holes: [{ address: root, type: "terraform_root", reason }] },
|
|
248
|
+
entries: [],
|
|
249
|
+
};
|
|
250
|
+
plans[m.component] = { member: root, planDigest, changeSet };
|
|
251
|
+
}
|
|
252
|
+
return { members: plans, digest: computePlanDigest("terraform-apply-group-unresolved", { components: ctx.components, unresolved: waits }) };
|
|
253
|
+
}
|
|
254
|
+
/** A composed plan that no longer matches the one approved. Core's moved-plan mark, so nothing applies and the command exits 3. */
|
|
255
|
+
export class ComposedPlanMovedError extends Error {
|
|
256
|
+
constructor(message) {
|
|
257
|
+
super(message);
|
|
258
|
+
this.planMoved = true;
|
|
259
|
+
this.name = "ComposedPlanMovedError";
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/** The group methods, with `deps` in place of the defaults. */
|
|
263
|
+
export function composeGroup(deps = {}) {
|
|
264
|
+
const project = deps.project ?? loadProject;
|
|
265
|
+
const exec = deps.exec ?? runBinary;
|
|
266
|
+
const envOf = () => deps.env ?? process.env;
|
|
267
|
+
const log = deps.log ?? ((line) => console.error(line));
|
|
268
|
+
const readOutputs = deps.outputs ?? ((root) => terraformOutputs({ root }));
|
|
269
|
+
const identities = deps.identity ?? declaredTargetCloudIdentity;
|
|
270
|
+
/** The composition of `members` as `groupFallback` judges it, with each member's identity. */
|
|
271
|
+
async function compositionOf(ctx, p, members) {
|
|
272
|
+
const lookup = await identities(p.projectRoot, ctx.env);
|
|
273
|
+
const c = groupComposition(p, members, envOf(), memberIdentity(p, members, lookup));
|
|
274
|
+
if (!c)
|
|
275
|
+
throw new Error(composeOff(p.terraform)?.reason ?? "`terraform.compose` is not enabled");
|
|
276
|
+
return c;
|
|
277
|
+
}
|
|
278
|
+
/** The composition the group plans as, or throws when it does not compose. */
|
|
279
|
+
async function composed(ctx, members) {
|
|
280
|
+
const p = await project();
|
|
281
|
+
const off = composeOff(p.terraform);
|
|
282
|
+
if (off)
|
|
283
|
+
throw new Error(off.reason);
|
|
284
|
+
const c = await compositionOf(ctx, p, members);
|
|
285
|
+
const refused = groupFallbackOf(c, members.map((m) => m.component));
|
|
286
|
+
if (refused)
|
|
287
|
+
throw new Error(`the group no longer composes: ${refused.reason}`);
|
|
288
|
+
return { p, c };
|
|
289
|
+
}
|
|
290
|
+
/** Build a runner, run `fn` on it, and release it and its directory whatever happened. */
|
|
291
|
+
async function withRunner(p, c, members, lock, fn) {
|
|
292
|
+
const work = mkdtempSync(join(tmpdir(), "chant-compose-"));
|
|
293
|
+
const o = {
|
|
294
|
+
...p,
|
|
295
|
+
composition: c,
|
|
296
|
+
lock,
|
|
297
|
+
vars: Object.fromEntries(members.map((m) => [m.component, m.step.vars])),
|
|
298
|
+
componentOutputs: outsideOutputs(members),
|
|
299
|
+
env: envOf(),
|
|
300
|
+
work,
|
|
301
|
+
};
|
|
302
|
+
const runner = deps.runner ? deps.runner(o) : defaultRunner(o, log);
|
|
303
|
+
try {
|
|
304
|
+
return await fn(runner);
|
|
305
|
+
}
|
|
306
|
+
finally {
|
|
307
|
+
try {
|
|
308
|
+
await runner.release();
|
|
309
|
+
}
|
|
310
|
+
finally {
|
|
311
|
+
rmSync(work, { recursive: true, force: true });
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
async groupReads(_ctx, members) {
|
|
317
|
+
const p = await project();
|
|
318
|
+
if (composeOff(p.terraform))
|
|
319
|
+
return [];
|
|
320
|
+
return hclGroupReads(p, members);
|
|
321
|
+
},
|
|
322
|
+
async groupFallback(ctx, members) {
|
|
323
|
+
const p = await project();
|
|
324
|
+
const off = composeOff(p.terraform);
|
|
325
|
+
if (off)
|
|
326
|
+
return { kind: off.kind, reason: off.reason };
|
|
327
|
+
const c = await compositionOf(ctx, p, members);
|
|
328
|
+
// A member left out is dropped alone; core asks again with the rest, and the estate check runs then.
|
|
329
|
+
const refused = groupFallbackOf(c, members.map((m) => m.component));
|
|
330
|
+
if (refused)
|
|
331
|
+
return refused;
|
|
332
|
+
if (c.estate) {
|
|
333
|
+
const binary = p.terraform.binary ?? "choudoufu";
|
|
334
|
+
const v = await exec(binary, ["version"], p.projectRoot, envOf());
|
|
335
|
+
// A binary that cannot say its version is left to the plan, which says why it fails.
|
|
336
|
+
const old = v.code === 0 ? estateVersionFallback(v.stdout) : undefined;
|
|
337
|
+
if (old)
|
|
338
|
+
return { kind: old.kind, reason: old.reason };
|
|
339
|
+
}
|
|
340
|
+
return undefined;
|
|
341
|
+
},
|
|
342
|
+
async planGroup(ctx, members) {
|
|
343
|
+
const unresolved = unresolvedOf(members);
|
|
344
|
+
if (unresolved.size > 0)
|
|
345
|
+
return waitingGroup(ctx, members, unresolved);
|
|
346
|
+
const { p, c } = await composed(ctx, members);
|
|
347
|
+
// Planned without locks and released at once: nothing stays held or changed if core refuses the plan or makes it wait.
|
|
348
|
+
const planned = await withRunner(p, c, members, false, (r) => r.plan());
|
|
349
|
+
if (planned.fallback)
|
|
350
|
+
throw new Error(`the group no longer composes: ${planned.fallback.reason}`);
|
|
351
|
+
if (planned.error)
|
|
352
|
+
throw new Error(planned.error);
|
|
353
|
+
const plans = {};
|
|
354
|
+
for (const m of members) {
|
|
355
|
+
const part = planned.members[m.component];
|
|
356
|
+
if (!part)
|
|
357
|
+
throw new Error(`the composed plan has no part for ${m.component}`);
|
|
358
|
+
plans[m.component] = { member: part.member, planDigest: part.planDigest, changeSet: part.changeSet, outputs: part.outputs, shape: part.shape };
|
|
359
|
+
}
|
|
360
|
+
const artifact = { digest: planned.digest, estate: c.estate === true };
|
|
361
|
+
return { members: plans, digest: planned.digest, artifact };
|
|
362
|
+
},
|
|
363
|
+
async runGroup(ctx, run, members) {
|
|
364
|
+
if (unresolvedOf(members).size > 0)
|
|
365
|
+
throw new Error("the group reads outputs of components outside it that are not known yet, so it cannot apply");
|
|
366
|
+
const approved = run.artifact;
|
|
367
|
+
if (typeof approved?.digest !== "string" || approved.digest === "")
|
|
368
|
+
throw new Error("no composed plan of the group to apply");
|
|
369
|
+
const { p, c } = await composed(ctx, members);
|
|
370
|
+
const notStarted = (error) => ({
|
|
371
|
+
members: Object.fromEntries(members.map((m) => [m.component, { status: "not-started", error }])),
|
|
372
|
+
error,
|
|
373
|
+
});
|
|
374
|
+
const applied = await withRunner(p, c, members, true, async (r) => {
|
|
375
|
+
// The approved plan was made without locks; plan again under them, and apply only that same plan.
|
|
376
|
+
const planned = await r.plan();
|
|
377
|
+
if (planned.fallback)
|
|
378
|
+
return notStarted(`the group no longer composes: ${planned.fallback.reason}`);
|
|
379
|
+
if (planned.error)
|
|
380
|
+
return notStarted(planned.error);
|
|
381
|
+
if (planned.digest !== approved.digest) {
|
|
382
|
+
throw new ComposedPlanMovedError(`the composed plan moved since it was approved (approved ${approved.digest}, planned now ${planned.digest}), so nothing applied`);
|
|
383
|
+
}
|
|
384
|
+
return r.apply();
|
|
385
|
+
});
|
|
386
|
+
const out = {};
|
|
387
|
+
for (const m of members) {
|
|
388
|
+
const o = applied.members[m.component];
|
|
389
|
+
if (!o) {
|
|
390
|
+
out[m.component] = { status: "failed", error: applied.error ?? `the composed apply said nothing of ${m.component}` };
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
let outputs = o.outputs;
|
|
394
|
+
// A root's state is written by now; read its outputs from there when its part of the working state could not be shown.
|
|
395
|
+
if (!outputs && !c.estate && (o.status === "applied" || o.status === "no-changes")) {
|
|
396
|
+
outputs = await readOutputs(m.step.root).catch(() => undefined);
|
|
397
|
+
}
|
|
398
|
+
const held = o.lockHeld ? `its state was kept unwritten${o.kept ? ` at ${o.kept}` : ""}, and its lock stays held until a later run writes it` : undefined;
|
|
399
|
+
const error = [o.error, held].filter(Boolean).join("; ");
|
|
400
|
+
out[m.component] = { status: o.status, ...(outputs ? { outputs } : {}), ...(error ? { error } : {}) };
|
|
401
|
+
}
|
|
402
|
+
return { members: out, ...(applied.error ? { error: applied.error } : {}) };
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
/** The group methods `terraform-apply` uses. */
|
|
407
|
+
export const terraformComposeGroup = composeGroup();
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Previewing a root whose `vars` read outputs not known yet (#4463).
|
|
3
|
+
*
|
|
4
|
+
* terraform takes no unknown variable, so such a root used to get no plan at
|
|
5
|
+
* pull-request time, only a hole. Now it is planned with a stand-in for each
|
|
6
|
+
* variable that holds no value yet, typed from the root's own `variable`
|
|
7
|
+
* block ({@link terraformPlaceholder}). Every attribute and output the
|
|
8
|
+
* stand-ins drive is then marked unknown after apply in the plan JSON
|
|
9
|
+
* ({@link markPlaceholderDriven}) before the change set, the shape and the
|
|
10
|
+
* planned outputs are read from it, so no stand-in value reaches the note or
|
|
11
|
+
* the digest. The member's change-set part lists the variables under
|
|
12
|
+
* `placeholders`.
|
|
13
|
+
*
|
|
14
|
+
* What a stand-in drives is read from the plan's own `configuration`: an
|
|
15
|
+
* attribute whose expression references `var.<name>`, through module inputs
|
|
16
|
+
* too, plus any string attribute or output whose planned value carries
|
|
17
|
+
* {@link TERRAFORM_UNKNOWN_MARK}. A value that reaches an attribute another
|
|
18
|
+
* way (a number through a `local`) is not marked; the fresh plan at apply
|
|
19
|
+
* then differs from the preview and asks for an approval of its own, which
|
|
20
|
+
* costs a round and never applies a change nobody approved.
|
|
21
|
+
*
|
|
22
|
+
* A preview is not possible when a variable that holds the unknown read is
|
|
23
|
+
* not declared, when the unknown sits inside a larger value, when a `count`
|
|
24
|
+
* or `for_each` reads a stand-in (the preview would show a count the real
|
|
25
|
+
* value does not have), or when the placeholder plan fails, say on a
|
|
26
|
+
* `validation` block. The member then stays a hole, as before, and the
|
|
27
|
+
* hole's reason says why (the 2026-10-25 ruling on #4463).
|
|
28
|
+
*/
|
|
29
|
+
import type { ChangeSetHole } from "@intentius/chant/change-set";
|
|
30
|
+
/** The marker every string stand-in carries, so a reader can tell it from a real value. */
|
|
31
|
+
export declare const TERRAFORM_UNKNOWN_MARK = "known-after-apply";
|
|
32
|
+
/**
|
|
33
|
+
* A stand-in for a variable of `type`, a Terraform type expression as the
|
|
34
|
+
* `variable` block writes it (`string`, `list(string)`, `object({ id = string })`).
|
|
35
|
+
*
|
|
36
|
+
* - `string`, `any` or no type: {@link TERRAFORM_UNKNOWN_MARK}.
|
|
37
|
+
* - `number`: 0. `bool`: false.
|
|
38
|
+
* - `list(...)`, `set(...)`: an empty list. `map(...)`: an empty map.
|
|
39
|
+
* - `tuple([a, b])`: one stand-in per element type.
|
|
40
|
+
* - `object({ a = t })`: one stand-in per attribute, `optional(t)` read as `t`.
|
|
41
|
+
*
|
|
42
|
+
* An empty collection plans no element, so a resource that iterates one
|
|
43
|
+
* plans none; the fresh plan at apply then asks for its own approval.
|
|
44
|
+
*/
|
|
45
|
+
export declare function terraformPlaceholder(type: string | undefined): unknown;
|
|
46
|
+
/** The variables a preview gives stand-ins, and the `vars` it plans with. */
|
|
47
|
+
export interface PlaceholderVars {
|
|
48
|
+
/** The variable names given a stand-in, sorted. */
|
|
49
|
+
placeholders: string[];
|
|
50
|
+
/** The step's `vars` with each of them replaced by its stand-in. */
|
|
51
|
+
vars: Record<string, unknown>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The stand-ins for a step's `vars` (#4463): each variable whose value is
|
|
55
|
+
* `undefined` (a `stackOutput()` read with nothing to read yet) gets
|
|
56
|
+
* {@link terraformPlaceholder} of its declared type. Returns why instead when
|
|
57
|
+
* no preview can be made: no variable holds the unknown read, one that does
|
|
58
|
+
* is not declared in the root, or the unknown sits inside a larger value.
|
|
59
|
+
*/
|
|
60
|
+
export declare function placeholderVars(vars: Record<string, unknown> | undefined, declared: ReadonlyMap<string, {
|
|
61
|
+
type?: string;
|
|
62
|
+
}>): PlaceholderVars | string;
|
|
63
|
+
/** What {@link markPlaceholderDriven} returns. */
|
|
64
|
+
export type MarkedPlan = {
|
|
65
|
+
plan: unknown;
|
|
66
|
+
} | {
|
|
67
|
+
refused: string;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* `planJson` (`show -json` of the placeholder plan) with every attribute and
|
|
71
|
+
* root output the stand-ins for `placeholders` drive marked unknown after
|
|
72
|
+
* apply, their planned values dropped. Returns why instead when a `count` or
|
|
73
|
+
* `for_each` reads a stand-in. The input is not changed.
|
|
74
|
+
*/
|
|
75
|
+
export declare function markPlaceholderDriven(planJson: unknown, placeholders: readonly string[]): MarkedPlan;
|
|
76
|
+
/** The hole a root keeps when its preview is not possible: the old reason, and why there is no preview. */
|
|
77
|
+
export declare function waitingHole(root: string, unresolved: readonly string[], why?: string): ChangeSetHole;
|
|
78
|
+
//# sourceMappingURL=placeholder-preview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"placeholder-preview.d.ts","sourceRoot":"","sources":["../../src/components/placeholder-preview.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEjE,2FAA2F;AAC3F,eAAO,MAAM,sBAAsB,sBAAsB,CAAC;AAgC1D;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CA2BtE;AAKD,6EAA6E;AAC7E,MAAM,WAAW,eAAe;IAC9B,mDAAmD;IACnD,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,eAAe,GAAG,MAAM,CAgBrJ;AAiED,kDAAkD;AAClD,MAAM,MAAM,UAAU,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjE;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,CAiCpG;AAED,2GAA2G;AAC3G,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,aAAa,CAMpG"}
|