@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
package/dist/changed-roots.js
CHANGED
|
@@ -9,14 +9,17 @@
|
|
|
9
9
|
* in a local module it calls (followed through modules that call modules),
|
|
10
10
|
* or is one of its var files.
|
|
11
11
|
*
|
|
12
|
-
* The module calls are read with a regex over the root's
|
|
12
|
+
* The module calls are read with a regex over the root's HCL configuration
|
|
13
13
|
* files, the way `./op/activities/live-detect.ts` reads a live block, so
|
|
14
|
-
* this stays out of the HCL parser's dependency graph.
|
|
14
|
+
* this stays out of the HCL parser's dependency graph. Which files those are
|
|
15
|
+
* follows the root's binary through the lexicon's one lister (chant#4457):
|
|
16
|
+
* `.tf`, plus `.tofu` for a tofu or choudoufu root. Only a `source` that
|
|
15
17
|
* starts with `./` or `../` is followed; a registry or git source is a
|
|
16
18
|
* version pin in the root's own files, which a change to it already touches.
|
|
17
19
|
*/
|
|
18
|
-
import { existsSync,
|
|
20
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
19
21
|
import { join, posix, relative, resolve, sep } from "node:path";
|
|
22
|
+
import { isJsonConfigFile, listConfigFilesSafe } from "./hcl/files.js";
|
|
20
23
|
/** `source = "./..."` or `"../..."` inside a module block. Other sources are not on disk here. */
|
|
21
24
|
const LOCAL_SOURCE = /\bsource\s*=\s*"(\.{1,2}\/[^"]*)"/g;
|
|
22
25
|
const toPosix = (p) => p.split(sep).join("/");
|
|
@@ -25,18 +28,11 @@ function projectRelative(projectRoot, dir) {
|
|
|
25
28
|
const rel = toPosix(relative(projectRoot, dir));
|
|
26
29
|
return rel === "" ? "." : posix.normalize(rel).replace(/\/+$/, "");
|
|
27
30
|
}
|
|
28
|
-
/** Local module directories `dir` calls, read from
|
|
29
|
-
function localModuleDirs(dir) {
|
|
30
|
-
let names;
|
|
31
|
-
try {
|
|
32
|
-
names = readdirSync(dir);
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
return [];
|
|
36
|
-
}
|
|
31
|
+
/** Local module directories `dir` calls, read from the HCL configuration files `binary` reads. */
|
|
32
|
+
function localModuleDirs(dir, binary) {
|
|
37
33
|
const out = [];
|
|
38
|
-
for (const name of
|
|
39
|
-
if (
|
|
34
|
+
for (const name of listConfigFilesSafe(dir, binary)) {
|
|
35
|
+
if (isJsonConfigFile(name))
|
|
40
36
|
continue;
|
|
41
37
|
let text;
|
|
42
38
|
try {
|
|
@@ -58,7 +54,7 @@ function localModuleDirs(dir) {
|
|
|
58
54
|
* Every path, relative to `projectRoot`, whose change touches `root`: its
|
|
59
55
|
* directory, each local module it reaches, and its var files.
|
|
60
56
|
*/
|
|
61
|
-
export function rootWatchPaths(projectRoot, root) {
|
|
57
|
+
export function rootWatchPaths(projectRoot, root, binary) {
|
|
62
58
|
const rootDir = resolve(projectRoot, root.dir);
|
|
63
59
|
const seen = new Set();
|
|
64
60
|
const queue = [rootDir];
|
|
@@ -67,7 +63,7 @@ export function rootWatchPaths(projectRoot, root) {
|
|
|
67
63
|
if (seen.has(dir))
|
|
68
64
|
continue;
|
|
69
65
|
seen.add(dir);
|
|
70
|
-
queue.push(...localModuleDirs(dir));
|
|
66
|
+
queue.push(...localModuleDirs(dir, binary));
|
|
71
67
|
}
|
|
72
68
|
const paths = [...seen].map((d) => projectRelative(projectRoot, d));
|
|
73
69
|
for (const file of root.varFiles ?? [])
|
|
@@ -85,11 +81,11 @@ function covers(path, file) {
|
|
|
85
81
|
* sorted. `changedFiles` are relative to `projectRoot` with `/` separators,
|
|
86
82
|
* as `git diff --name-only --relative` prints them.
|
|
87
83
|
*/
|
|
88
|
-
export function changedRoots(projectRoot, roots, changedFiles) {
|
|
84
|
+
export function changedRoots(projectRoot, roots, changedFiles, binary) {
|
|
89
85
|
const files = changedFiles.map((f) => posix.normalize(f));
|
|
90
86
|
const touched = [];
|
|
91
87
|
for (const [name, root] of Object.entries(roots)) {
|
|
92
|
-
const watch = rootWatchPaths(projectRoot, root);
|
|
88
|
+
const watch = rootWatchPaths(projectRoot, root, binary);
|
|
93
89
|
if (files.some((f) => watch.some((p) => covers(p, f))))
|
|
94
90
|
touched.push(name);
|
|
95
91
|
}
|
package/dist/commands.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAuB,MAAM,oCAAoC,CAAC;AAE5F,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AAW3E,qGAAqG;AACrG,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG;IAAE,IAAI,EAAE,uBAAuB,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAuB,MAAM,oCAAoC,CAAC;AAE5F,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AAW3E,qGAAqG;AACrG,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG;IAAE,IAAI,EAAE,uBAAuB,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAkDvG;AAED,0CAA0C;AAC1C,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAIzD;AA4BD,qHAAqH;AACrH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG;IAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAqBjG;AA+CD,eAAO,MAAM,iBAAiB,EAAE,YAO/B,CAAC"}
|
package/dist/commands.js
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
import { splitJoinedFlags, unknownFlagError } from "@intentius/chant/cli/command-group";
|
|
17
17
|
const USAGE = "usage: chant terraform pin-rollout --module <source> --from <pin> --to <pin> [--root <dir>]... [--depends-on <dir>=<dir>]... " +
|
|
18
18
|
"[--ts-source <dir>=<file>]... [--canary <dir>]... [--waves-from <file> [--waves-prefix <dir>]] [--pull-request] " +
|
|
19
|
-
"[--base <branch>] [--remote <name>] [--applied-check <name>] [--json]";
|
|
20
|
-
const VALUE_FLAGS = new Set(["--module", "--from", "--to", "--root", "--depends-on", "--ts-source", "--canary", "--waves-from", "--waves-prefix", "--base", "--remote", "--applied-check"]);
|
|
19
|
+
"[--base <branch>] [--remote <name>] [--applied-check <name>] [--binary <terraform|tofu|choudoufu>] [--json]";
|
|
20
|
+
const VALUE_FLAGS = new Set(["--module", "--from", "--to", "--root", "--depends-on", "--ts-source", "--canary", "--waves-from", "--waves-prefix", "--base", "--remote", "--applied-check", "--binary"]);
|
|
21
21
|
const BOOLEAN_FLAGS = new Set(["--pull-request", "--json"]);
|
|
22
22
|
/** Parse the verb's flags into the activity's args. Throws on an unknown flag or a missing value. */
|
|
23
23
|
export function parsePinRolloutArgs(rawArgs) {
|
|
@@ -68,6 +68,7 @@ export function parsePinRolloutArgs(rawArgs) {
|
|
|
68
68
|
...(one("--base") ? { base: one("--base") } : {}),
|
|
69
69
|
...(one("--remote") ? { remote: one("--remote") } : {}),
|
|
70
70
|
...(one("--applied-check") ? { appliedCheck: one("--applied-check") } : {}),
|
|
71
|
+
...(one("--binary") ? { binary: one("--binary") } : {}),
|
|
71
72
|
};
|
|
72
73
|
return { args, json: flags.has("--json") };
|
|
73
74
|
}
|
|
@@ -183,6 +184,6 @@ export const terraformCommands = {
|
|
|
183
184
|
description: "Terraform and OpenTofu roots: roll a module version out as one pin-bump PR per wave; write a run report from a saved plan",
|
|
184
185
|
commands: [
|
|
185
186
|
{ name: "pin-rollout", description: "Read a pin-bump rollout's state and open the next wave's PR when it is due", handler: runPinRolloutVerb },
|
|
186
|
-
{ name: "report", description: "Write a terragucci.report/v1 run report and its index row from show -json of a composed plan", handler: runReportVerb },
|
|
187
|
+
{ name: "report", description: "Write a terragucci.report/v1 run report, its change set and its index row from show -json of a composed plan", handler: runReportVerb },
|
|
187
188
|
],
|
|
188
189
|
};
|
|
@@ -38,9 +38,27 @@
|
|
|
38
38
|
* when both roots are in the change.
|
|
39
39
|
*
|
|
40
40
|
* A root whose `vars` read an output not known yet (`DeployContext.unresolvedInputs`)
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
41
|
+
* cannot be planned as it stands: terraform takes no unknown variable. It is
|
|
42
|
+
* previewed instead (#4463, `./placeholder-preview.ts`): planned with a
|
|
43
|
+
* stand-in per variable that holds no value, typed from the root's
|
|
44
|
+
* `variable` block, with every attribute and output a stand-in drives marked
|
|
45
|
+
* unknown after apply. The member lists the variables under `placeholders`,
|
|
46
|
+
* and its artifact is the preview's shape, never a plan file. The apply plans
|
|
47
|
+
* the root again once the upstream applied, and core covers that round with
|
|
48
|
+
* the first approval when its shape is within the preview's; a `run` handed
|
|
49
|
+
* the shape itself holds the fresh plan to it. When no preview can be made
|
|
50
|
+
* (an undeclared variable, a `count` or `for_each` over a stand-in, a
|
|
51
|
+
* placeholder plan that fails) the member stays a hole naming the roots it
|
|
52
|
+
* waits on, and the hole's reason says why there is no preview.
|
|
53
|
+
*
|
|
54
|
+
* ## The plan's shape (#4436)
|
|
55
|
+
*
|
|
56
|
+
* `plan` also answers `shape`, `terraformPlanShape` over `show -json`: each
|
|
57
|
+
* resource the plan changes with its action, type, replacement, the
|
|
58
|
+
* attributes that differ and the ones unknown after apply. Core's
|
|
59
|
+
* `coveredRound` compares it with the shape of a later plan of the root. A
|
|
60
|
+
* previewed root's shape lists the upstreams it read as unknown inputs; a
|
|
61
|
+
* root that stays a hole has no plan, so no shape, and stays uncovered.
|
|
44
62
|
*
|
|
45
63
|
* ## choudoufu roots in a gated wave (#3459)
|
|
46
64
|
*
|
|
@@ -63,9 +81,30 @@
|
|
|
63
81
|
*
|
|
64
82
|
* chant's waves stay the order. Each root is its own one-root set, so
|
|
65
83
|
* choudoufu's wave over it is always wave 1.
|
|
84
|
+
*
|
|
85
|
+
* ## Composed runs (#4447)
|
|
86
|
+
*
|
|
87
|
+
* With `terraform.compose.enabled`, a group core forms of `terraform-apply`
|
|
88
|
+
* components that read each other (ws-107) plans as one plan and applies as
|
|
89
|
+
* one apply of a composed root, and a member's read of another member's new
|
|
90
|
+
* output is not a hole: the apply passes the value on. `groupReads`,
|
|
91
|
+
* `groupFallback`, `planGroup` and `runGroup` are in `./compose-group.ts`.
|
|
92
|
+
*
|
|
93
|
+
* ## A choudoufu member's outputs, for another member (#4455)
|
|
94
|
+
*
|
|
95
|
+
* The plugin also contributes {@link choudoufuOutputSource} to core's
|
|
96
|
+
* `member-outputs` verb (`CapabilityPlugin.engineOutputSources`), which is
|
|
97
|
+
* how a chant member's parameters read a choudoufu member of the same
|
|
98
|
+
* workspace: `choudoufu output -json` in the member's directory for the
|
|
99
|
+
* applied values, and `live-plan-set -json` over it for the planned ones,
|
|
100
|
+
* read the way `plan` reads a root's (`terraformPlannedOutputs`). The member
|
|
101
|
+
* is not a `terraform.roots` entry of the reading project; its directory
|
|
102
|
+
* comes from the workspace declaration.
|
|
66
103
|
*/
|
|
67
104
|
import type { Capability, PlannedOutputs } from "@intentius/chant/components/capability";
|
|
105
|
+
import { type PlanShapeArtifact } from "@intentius/chant/components/covered-round";
|
|
68
106
|
import { type CapabilityPlugin } from "@intentius/chant/components/capability-plugin";
|
|
107
|
+
import type { EngineOutputSource, EnginePlannedOutputs } from "@intentius/chant/components/verbs/member-outputs";
|
|
69
108
|
/** The `terraform-apply` step's input. */
|
|
70
109
|
export interface TerraformApplyStepInput {
|
|
71
110
|
/** Key into `terraform.roots`. Also the member's name in a wave's set digest. */
|
|
@@ -92,6 +131,29 @@ export interface TerraformApplyStepOutput {
|
|
|
92
131
|
* only a part of it is.
|
|
93
132
|
*/
|
|
94
133
|
export declare function terraformPlannedOutputs(json: unknown): PlannedOutputs;
|
|
134
|
+
/**
|
|
135
|
+
* What a choudoufu member's set plan says about its outputs (#4455), for
|
|
136
|
+
* `member-outputs`: {@link terraformPlannedOutputs} of the embedded plan,
|
|
137
|
+
* chant's digest of that plan, and whether it has changes to apply.
|
|
138
|
+
*/
|
|
139
|
+
export declare function choudoufuMemberPlannedOutputs(set: {
|
|
140
|
+
planDigest: string;
|
|
141
|
+
changed: boolean;
|
|
142
|
+
json: unknown;
|
|
143
|
+
}): EnginePlannedOutputs;
|
|
144
|
+
/**
|
|
145
|
+
* The choudoufu engine's member outputs (#4455): what core's
|
|
146
|
+
* `member-outputs` reads a workspace member of kind `choudoufu` through.
|
|
147
|
+
* Applied values from the member's root outputs (the record store, for a
|
|
148
|
+
* live estate); planned values from its one-root set plan.
|
|
149
|
+
*/
|
|
150
|
+
export declare const choudoufuOutputSource: EngineOutputSource;
|
|
151
|
+
/** The plan file a preview writes, removed once it is read: a plan made with stand-ins must never apply. */
|
|
152
|
+
export declare const PREVIEW_PLAN_FILE = ".chant-preview.tfplan";
|
|
153
|
+
/** What a previewed root hands `run` (#4463): the shape its plan had with stand-in values. Never a plan file. */
|
|
154
|
+
export interface TerraformShapeArtifact extends PlanShapeArtifact {
|
|
155
|
+
kind: "terraform-apply-shape";
|
|
156
|
+
}
|
|
95
157
|
/**
|
|
96
158
|
* choudoufu refused the plan `run` was handed by its wave (its exit 3):
|
|
97
159
|
* the live system moved since the plan, or the plan names another estate.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capability-plugin.d.ts","sourceRoot":"","sources":["../../src/components/capability-plugin.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"capability-plugin.d.ts","sourceRoot":"","sources":["../../src/components/capability-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsGG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAiC,cAAc,EAAE,MAAM,wCAAwC,CAAC;AAExH,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,2CAA2C,CAAC;AAGnD,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,+CAA+C,CAAC;AACzG,OAAO,KAAK,EAAmB,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,kDAAkD,CAAC;AAoBlI,0CAA0C;AAC1C,MAAM,WAAW,uBAAuB;IACtC,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;IACb,iFAAiF;IACjF,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,gFAAgF;IAChF,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,6FAA6F;AAC7F,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,YAAY,EAAE,OAAO,CAAC;IACtB,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AA8CD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,OAAO,GAAG,cAAc,CAerE;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,GAAG,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG,oBAAoB,CAEhI;AAID;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,EAAE,kBAInC,CAAC;AA4BF,4GAA4G;AAC5G,eAAO,MAAM,iBAAiB,0BAA0B,CAAC;AAEzD,iHAAiH;AACjH,MAAM,WAAW,sBAAuB,SAAQ,iBAAiB;IAC/D,IAAI,EAAE,uBAAuB,CAAC;CAC/B;AAqFD;;;;;;GAMG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;aAEpB,IAAI,EAAE,MAAM;IADxC,QAAQ,CAAC,SAAS,QAAQ;IAC1B,YAA4B,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAGxD;CACF;AA+CD,eAAO,MAAM,wBAAwB,EAAE,UAAU,CAAC,uBAAuB,EAAE,wBAAwB,CA8ElG,CAAC;AAEF,eAAO,MAAM,uBAAuB;aAClC,KAAK,YAAG,iBAAiB;CACjB,CAAC;AAEX,eAAO,MAAM,yBAAyB,EAAE,gBAcvC,CAAC"}
|
|
@@ -38,9 +38,27 @@
|
|
|
38
38
|
* when both roots are in the change.
|
|
39
39
|
*
|
|
40
40
|
* A root whose `vars` read an output not known yet (`DeployContext.unresolvedInputs`)
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
41
|
+
* cannot be planned as it stands: terraform takes no unknown variable. It is
|
|
42
|
+
* previewed instead (#4463, `./placeholder-preview.ts`): planned with a
|
|
43
|
+
* stand-in per variable that holds no value, typed from the root's
|
|
44
|
+
* `variable` block, with every attribute and output a stand-in drives marked
|
|
45
|
+
* unknown after apply. The member lists the variables under `placeholders`,
|
|
46
|
+
* and its artifact is the preview's shape, never a plan file. The apply plans
|
|
47
|
+
* the root again once the upstream applied, and core covers that round with
|
|
48
|
+
* the first approval when its shape is within the preview's; a `run` handed
|
|
49
|
+
* the shape itself holds the fresh plan to it. When no preview can be made
|
|
50
|
+
* (an undeclared variable, a `count` or `for_each` over a stand-in, a
|
|
51
|
+
* placeholder plan that fails) the member stays a hole naming the roots it
|
|
52
|
+
* waits on, and the hole's reason says why there is no preview.
|
|
53
|
+
*
|
|
54
|
+
* ## The plan's shape (#4436)
|
|
55
|
+
*
|
|
56
|
+
* `plan` also answers `shape`, `terraformPlanShape` over `show -json`: each
|
|
57
|
+
* resource the plan changes with its action, type, replacement, the
|
|
58
|
+
* attributes that differ and the ones unknown after apply. Core's
|
|
59
|
+
* `coveredRound` compares it with the shape of a later plan of the root. A
|
|
60
|
+
* previewed root's shape lists the upstreams it read as unknown inputs; a
|
|
61
|
+
* root that stays a hole has no plan, so no shape, and stays uncovered.
|
|
44
62
|
*
|
|
45
63
|
* ## choudoufu roots in a gated wave (#3459)
|
|
46
64
|
*
|
|
@@ -63,10 +81,39 @@
|
|
|
63
81
|
*
|
|
64
82
|
* chant's waves stay the order. Each root is its own one-root set, so
|
|
65
83
|
* choudoufu's wave over it is always wave 1.
|
|
84
|
+
*
|
|
85
|
+
* ## Composed runs (#4447)
|
|
86
|
+
*
|
|
87
|
+
* With `terraform.compose.enabled`, a group core forms of `terraform-apply`
|
|
88
|
+
* components that read each other (ws-107) plans as one plan and applies as
|
|
89
|
+
* one apply of a composed root, and a member's read of another member's new
|
|
90
|
+
* output is not a hole: the apply passes the value on. `groupReads`,
|
|
91
|
+
* `groupFallback`, `planGroup` and `runGroup` are in `./compose-group.ts`.
|
|
92
|
+
*
|
|
93
|
+
* ## A choudoufu member's outputs, for another member (#4455)
|
|
94
|
+
*
|
|
95
|
+
* The plugin also contributes {@link choudoufuOutputSource} to core's
|
|
96
|
+
* `member-outputs` verb (`CapabilityPlugin.engineOutputSources`), which is
|
|
97
|
+
* how a chant member's parameters read a choudoufu member of the same
|
|
98
|
+
* workspace: `choudoufu output -json` in the member's directory for the
|
|
99
|
+
* applied values, and `live-plan-set -json` over it for the planned ones,
|
|
100
|
+
* read the way `plan` reads a root's (`terraformPlannedOutputs`). The member
|
|
101
|
+
* is not a `terraform.roots` entry of the reading project; its directory
|
|
102
|
+
* comes from the workspace declaration.
|
|
66
103
|
*/
|
|
104
|
+
import { rmSync } from "node:fs";
|
|
105
|
+
import { dirname, join, resolve } from "node:path";
|
|
106
|
+
import { loadChantConfigUpward } from "@intentius/chant/config";
|
|
107
|
+
import { PlanShapeMovedError, planShapeDifferences, planShapeDigest, } from "@intentius/chant/components/covered-round";
|
|
67
108
|
import { computePlanDigest } from "@intentius/chant/lifecycle/plan-digest";
|
|
68
109
|
import { ownPackageVersion } from "@intentius/chant/components/capability-plugin";
|
|
69
|
-
import { choudoufuPlanSet, choudoufuWaveApply, choudoufuWaveRoot, terraformApply, terraformInit, terraformOutputs, terraformPlan, } from "../op/activities/terraform.js";
|
|
110
|
+
import { choudoufuMemberOutputs, choudoufuMemberPlanSet, choudoufuPlanSet, choudoufuWaveApply, choudoufuWaveRoot, terraformApply, terraformInit, terraformOutputs, terraformPlan, } from "../op/activities/terraform.js";
|
|
111
|
+
import { terraformPlanShape } from "../plan-shape.js";
|
|
112
|
+
import { terraformChangeSetPart } from "../change-set.js";
|
|
113
|
+
import { rootTexts } from "../compose/hcl.js";
|
|
114
|
+
import { declaredVariables } from "../compose/root.js";
|
|
115
|
+
import { terraformComposeGroup } from "./compose-group.js";
|
|
116
|
+
import { markPlaceholderDriven, placeholderVars, waitingHole } from "./placeholder-preview.js";
|
|
70
117
|
const rootArgs = (input) => ({ root: input.root, ...(input.cwd ? { cwd: input.cwd } : {}) });
|
|
71
118
|
function requireRoot(input) {
|
|
72
119
|
if (typeof input.root !== "string" || input.root === "") {
|
|
@@ -76,10 +123,14 @@ function requireRoot(input) {
|
|
|
76
123
|
async function planRoot(input, targets) {
|
|
77
124
|
requireRoot(input);
|
|
78
125
|
await terraformInit(rootArgs(input));
|
|
126
|
+
// One `show -json` per plan (#4479): its document is the digest, the change
|
|
127
|
+
// set, the planned outputs and the shape. Nothing here reads the
|
|
128
|
+
// `-no-color` render, so the plan skips it.
|
|
79
129
|
const planned = await terraformPlan({
|
|
80
130
|
...rootArgs(input),
|
|
81
131
|
...(input.vars ? { vars: input.vars } : {}),
|
|
82
132
|
...(targets && targets.length > 0 ? { targets } : {}),
|
|
133
|
+
text: false,
|
|
83
134
|
});
|
|
84
135
|
return {
|
|
85
136
|
planFile: planned.planFile,
|
|
@@ -127,12 +178,34 @@ export function terraformPlannedOutputs(json) {
|
|
|
127
178
|
return { known, unknown };
|
|
128
179
|
}
|
|
129
180
|
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
181
|
+
* What a choudoufu member's set plan says about its outputs (#4455), for
|
|
182
|
+
* `member-outputs`: {@link terraformPlannedOutputs} of the embedded plan,
|
|
183
|
+
* chant's digest of that plan, and whether it has changes to apply.
|
|
184
|
+
*/
|
|
185
|
+
export function choudoufuMemberPlannedOutputs(set) {
|
|
186
|
+
return { ...terraformPlannedOutputs(set.json), planDigest: set.planDigest, changes: set.changed };
|
|
187
|
+
}
|
|
188
|
+
const memberArgs = (ref) => ({ member: ref.member, dir: ref.dir, projectRoot: ref.workspaceRoot });
|
|
189
|
+
/**
|
|
190
|
+
* The choudoufu engine's member outputs (#4455): what core's
|
|
191
|
+
* `member-outputs` reads a workspace member of kind `choudoufu` through.
|
|
192
|
+
* Applied values from the member's root outputs (the record store, for a
|
|
193
|
+
* live estate); planned values from its one-root set plan.
|
|
194
|
+
*/
|
|
195
|
+
export const choudoufuOutputSource = {
|
|
196
|
+
kinds: ["choudoufu"],
|
|
197
|
+
applied: (_ctx, ref) => choudoufuMemberOutputs(memberArgs(ref)),
|
|
198
|
+
planned: async (_ctx, ref) => choudoufuMemberPlannedOutputs(await choudoufuMemberPlanSet(memberArgs(ref))),
|
|
199
|
+
};
|
|
200
|
+
const resolvedVars = (input) => Object.fromEntries(Object.entries(input.vars ?? {}).map(([k, v]) => [k, v === undefined ? null : v]));
|
|
201
|
+
/**
|
|
202
|
+
* The member of a root whose `vars` read outputs not known yet and that
|
|
203
|
+
* could not be previewed (#3999, #4463): no plan, a hole naming whose outputs
|
|
204
|
+
* it waits on and, when a preview was tried, why it was not possible, and a
|
|
205
|
+
* digest over the root, the vars as resolved and those names.
|
|
133
206
|
*/
|
|
134
|
-
function waitingRoot(ctx, input, unresolved) {
|
|
135
|
-
const vars =
|
|
207
|
+
function waitingRoot(ctx, input, unresolved, noPreview) {
|
|
208
|
+
const vars = resolvedVars(input);
|
|
136
209
|
const planDigest = computePlanDigest("terraform-apply-unresolved", { env: ctx.env, root: input.root, vars, unresolved });
|
|
137
210
|
const changeSet = {
|
|
138
211
|
member: {
|
|
@@ -141,12 +214,99 @@ function waitingRoot(ctx, input, unresolved) {
|
|
|
141
214
|
planner: "chant",
|
|
142
215
|
status: "planned",
|
|
143
216
|
planDigest,
|
|
144
|
-
holes: [
|
|
217
|
+
holes: [waitingHole(input.root, unresolved, noPreview)],
|
|
145
218
|
},
|
|
146
219
|
entries: [],
|
|
147
220
|
};
|
|
148
221
|
return { member: input.root, planDigest, changeSet, ...(ctx.targets?.length ? { targets: ctx.targets } : {}) };
|
|
149
222
|
}
|
|
223
|
+
/** The plan file a preview writes, removed once it is read: a plan made with stand-ins must never apply. */
|
|
224
|
+
export const PREVIEW_PLAN_FILE = ".chant-preview.tfplan";
|
|
225
|
+
function isShapeArtifact(value) {
|
|
226
|
+
const v = value;
|
|
227
|
+
return v?.kind === "terraform-apply-shape" && typeof v.digest === "string" && typeof v.shape === "object" && v.shape !== null;
|
|
228
|
+
}
|
|
229
|
+
/** The root's directory, as `terraform.roots` names it from the nearest `chant.config.*`, and the configured binary. */
|
|
230
|
+
async function rootDir(input) {
|
|
231
|
+
const start = resolve(input.cwd ?? process.cwd());
|
|
232
|
+
const { config, configPath } = await loadChantConfigUpward(start);
|
|
233
|
+
const terraform = config.terraform;
|
|
234
|
+
const dir = terraform?.roots?.[input.root]?.dir;
|
|
235
|
+
if (dir === undefined)
|
|
236
|
+
return undefined;
|
|
237
|
+
return { dir: resolve(configPath ? dirname(configPath) : start, dir), ...(terraform?.binary ? { binary: terraform.binary } : {}) };
|
|
238
|
+
}
|
|
239
|
+
const firstLines = (err) => {
|
|
240
|
+
const text = (err instanceof Error ? err.message : String(err)).trim();
|
|
241
|
+
const lines = text.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
242
|
+
return lines.slice(0, 4).join(" ").slice(0, 600);
|
|
243
|
+
};
|
|
244
|
+
/**
|
|
245
|
+
* Preview a root whose `vars` read outputs not known yet (#4463): plan it
|
|
246
|
+
* with a typed stand-in per such variable, mark what the stand-ins drive
|
|
247
|
+
* unknown, and answer the change set, the planned outputs and the shape of
|
|
248
|
+
* that plan, with the shape as the artifact. The plan file is removed. When
|
|
249
|
+
* no preview can be made, the member stays a hole saying why.
|
|
250
|
+
*/
|
|
251
|
+
async function previewRoot(ctx, input, unresolved, targets) {
|
|
252
|
+
const root = await rootDir(input);
|
|
253
|
+
if (root === undefined)
|
|
254
|
+
return waitingRoot(ctx, input, unresolved, `no root named "${input.root}" in terraform.roots to read its variables from`);
|
|
255
|
+
const { dir } = root;
|
|
256
|
+
// #4457: the same files the binary reads (`.tofu` only for tofu and choudoufu).
|
|
257
|
+
const stand = placeholderVars(input.vars, declaredVariables(rootTexts(dir, root.binary)));
|
|
258
|
+
if (typeof stand === "string")
|
|
259
|
+
return waitingRoot(ctx, input, unresolved, stand);
|
|
260
|
+
let planned;
|
|
261
|
+
try {
|
|
262
|
+
await terraformInit(rootArgs(input));
|
|
263
|
+
planned = await terraformPlan({
|
|
264
|
+
...rootArgs(input),
|
|
265
|
+
vars: stand.vars,
|
|
266
|
+
planFile: PREVIEW_PLAN_FILE,
|
|
267
|
+
...(targets ? { targets } : {}),
|
|
268
|
+
text: false,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
return waitingRoot(ctx, input, unresolved, `the plan with stand-in values failed: ${firstLines(err)}`);
|
|
273
|
+
}
|
|
274
|
+
finally {
|
|
275
|
+
rmSync(join(dir, PREVIEW_PLAN_FILE), { force: true });
|
|
276
|
+
}
|
|
277
|
+
const marked = markPlaceholderDriven(planned.json, stand.placeholders);
|
|
278
|
+
if ("refused" in marked)
|
|
279
|
+
return waitingRoot(ctx, input, unresolved, marked.refused);
|
|
280
|
+
const placeholders = stand.placeholders.map((name) => `var.${name}`);
|
|
281
|
+
const shape = terraformPlanShape(marked.plan, input.root, { unknownInputs: unresolved });
|
|
282
|
+
const artifact = { kind: "terraform-apply-shape", member: input.root, shape, digest: planShapeDigest(shape) };
|
|
283
|
+
const part = terraformChangeSetPart({
|
|
284
|
+
member: input.root,
|
|
285
|
+
plan: marked.plan,
|
|
286
|
+
...(planned.changeSet?.member.planner ? { planner: planned.changeSet.member.planner } : {}),
|
|
287
|
+
...(planned.changeSet?.member.scope ? { scope: planned.changeSet.member.scope } : {}),
|
|
288
|
+
});
|
|
289
|
+
// The stand-ins' values are not real, so the digest is over the preview's shape and entries, never the raw plan.
|
|
290
|
+
const planDigest = computePlanDigest("terraform-apply-preview", {
|
|
291
|
+
env: ctx.env,
|
|
292
|
+
root: input.root,
|
|
293
|
+
vars: resolvedVars(input),
|
|
294
|
+
unresolved,
|
|
295
|
+
placeholders,
|
|
296
|
+
shape: artifact.digest,
|
|
297
|
+
entries: part.entries,
|
|
298
|
+
});
|
|
299
|
+
const changeSet = { ...part, member: { ...part.member, planDigest, placeholders } };
|
|
300
|
+
return {
|
|
301
|
+
member: input.root,
|
|
302
|
+
planDigest,
|
|
303
|
+
artifact,
|
|
304
|
+
changeSet,
|
|
305
|
+
outputs: terraformPlannedOutputs(marked.plan),
|
|
306
|
+
shape,
|
|
307
|
+
...(targets ? { targets } : {}),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
150
310
|
/**
|
|
151
311
|
* choudoufu refused the plan `run` was handed by its wave (its exit 3):
|
|
152
312
|
* the live system moved since the plan, or the plan names another estate.
|
|
@@ -203,9 +363,9 @@ export const terraformApplyCapability = {
|
|
|
203
363
|
async plan(ctx, input) {
|
|
204
364
|
requireRoot(input);
|
|
205
365
|
const unresolved = [...(ctx.unresolvedInputs ?? [])].sort();
|
|
206
|
-
if (unresolved.length > 0)
|
|
207
|
-
return waitingRoot(ctx, input, unresolved);
|
|
208
366
|
const targets = ctx.targets && ctx.targets.length > 0 ? ctx.targets : undefined;
|
|
367
|
+
if (unresolved.length > 0)
|
|
368
|
+
return previewRoot(ctx, input, unresolved, targets);
|
|
209
369
|
// A targeted root plans with `plan -target`, never as a choudoufu one-root set.
|
|
210
370
|
const wave = targets ? { wave: false } : await choudoufuWaveRoot({ ...rootArgs(input), ...(input.vars ? { vars: input.vars } : {}) });
|
|
211
371
|
if (wave.wave) {
|
|
@@ -214,7 +374,7 @@ export const terraformApplyCapability = {
|
|
|
214
374
|
planDigest: set.planDigest,
|
|
215
375
|
choudoufu: { planSetFile: set.planSetFile, setDigest: set.setDigest },
|
|
216
376
|
};
|
|
217
|
-
return { member: input.root, planDigest: set.planDigest, artifact, changeSet: set.changeSet, outputs: terraformPlannedOutputs(set.json) };
|
|
377
|
+
return { member: input.root, planDigest: set.planDigest, artifact, changeSet: set.changeSet, outputs: terraformPlannedOutputs(set.json), shape: terraformPlanShape(set.json, input.root) };
|
|
218
378
|
}
|
|
219
379
|
const { changeSet, json, ...planned } = await planRoot(input, targets);
|
|
220
380
|
return {
|
|
@@ -223,6 +383,7 @@ export const terraformApplyCapability = {
|
|
|
223
383
|
artifact: planned,
|
|
224
384
|
...(changeSet ? { changeSet } : {}),
|
|
225
385
|
outputs: terraformPlannedOutputs(json),
|
|
386
|
+
shape: terraformPlanShape(json, input.root),
|
|
226
387
|
...(targets ? { targets } : {}),
|
|
227
388
|
};
|
|
228
389
|
},
|
|
@@ -233,10 +394,30 @@ export const terraformApplyCapability = {
|
|
|
233
394
|
await terraformInit(rootArgs(input));
|
|
234
395
|
return terraformOutputs(rootArgs(input));
|
|
235
396
|
},
|
|
397
|
+
// #4447 (ws-107): a group core forms of components that read each other plans and applies as one composed run
|
|
398
|
+
// when `terraform.compose.enabled` is set (./compose-group.ts). Delegates, so the methods are read when called.
|
|
399
|
+
groupReads: (ctx, members) => terraformComposeGroup.groupReads(ctx, members),
|
|
400
|
+
groupFallback: (ctx, members) => terraformComposeGroup.groupFallback(ctx, members),
|
|
401
|
+
planGroup: (ctx, members) => terraformComposeGroup.planGroup(ctx, members),
|
|
402
|
+
runGroup: (ctx, run, members) => terraformComposeGroup.runGroup(ctx, run, members),
|
|
236
403
|
async run(ctx, input) {
|
|
237
404
|
const fromWave = ctx.plans?.[input.root];
|
|
238
405
|
if (isChoudoufuWavePlan(fromWave))
|
|
239
406
|
return runChoudoufuWave(ctx, input, fromWave);
|
|
407
|
+
if (isShapeArtifact(fromWave)) {
|
|
408
|
+
// A root previewed with stand-ins (#4463): plan it now with the real
|
|
409
|
+
// values, and apply that plan only when it is within the preview's shape.
|
|
410
|
+
const fresh = await planRoot(input, ctx.targets);
|
|
411
|
+
const differences = planShapeDifferences(fromWave.shape, terraformPlanShape(fresh.json, input.root));
|
|
412
|
+
if (differences.length > 0)
|
|
413
|
+
throw new PlanShapeMovedError(input.root, differences);
|
|
414
|
+
const applied = await terraformApply({ ...rootArgs(input), planFile: fresh.planFile });
|
|
415
|
+
if (!applied.applied) {
|
|
416
|
+
throw new Error(`terraform-apply: root "${input.root}" refused the plan it was given (${applied.refused ?? "refused"}). ${applied.refusal ?? ""}`);
|
|
417
|
+
}
|
|
418
|
+
const outputs = await terraformOutputs(rootArgs(input));
|
|
419
|
+
return { root: input.root, planDigest: fresh.planDigest, fromWavePlan: false, outputs };
|
|
420
|
+
}
|
|
240
421
|
const planned = isPlannedRoot(fromWave)
|
|
241
422
|
? fromWave
|
|
242
423
|
: await planRoot(input, ctx.targets).then(({ planFile, planDigest }) => ({ planFile, planDigest }));
|
|
@@ -268,4 +449,7 @@ export const terraformCapabilityPlugin = {
|
|
|
268
449
|
families() {
|
|
269
450
|
return TERRAFORM_VERB_FAMILIES;
|
|
270
451
|
},
|
|
452
|
+
engineOutputSources() {
|
|
453
|
+
return [choudoufuOutputSource];
|
|
454
|
+
},
|
|
271
455
|
};
|
|
@@ -0,0 +1,145 @@
|
|
|
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 type { CapabilityGroupOutcome, CapabilityGroupPlan, GroupContext, GroupFallback, GroupMember, GroupRun, ReportedGroupRead } from "@intentius/chant/components/capability";
|
|
39
|
+
import type { DriverComponent } from "@intentius/chant/components/driver";
|
|
40
|
+
import { type TargetCloudIdentityLookup } from "@intentius/chant/workspace/ci-cloud-identity";
|
|
41
|
+
import { type Composition } from "../compose.js";
|
|
42
|
+
import type { TerraformConfig } from "../config.js";
|
|
43
|
+
import { type BinaryExec } from "../state/exec.js";
|
|
44
|
+
import { type ComposedRunner } from "../compose/estate.js";
|
|
45
|
+
/** The step input as the group methods read it; `TerraformApplyStepInput` in ./capability-plugin.ts. */
|
|
46
|
+
interface StepInput {
|
|
47
|
+
root: string;
|
|
48
|
+
vars?: Record<string, unknown>;
|
|
49
|
+
cwd?: string;
|
|
50
|
+
}
|
|
51
|
+
/** The project a group's roots belong to. */
|
|
52
|
+
export interface ComposeProject {
|
|
53
|
+
/** Where `chant.config.*` was found: roots resolve against it and `.chant-compose/` is written there. */
|
|
54
|
+
projectRoot: string;
|
|
55
|
+
terraform: TerraformConfig;
|
|
56
|
+
}
|
|
57
|
+
/** What a runner is built from. */
|
|
58
|
+
export interface GroupRunnerOptions extends ComposeProject {
|
|
59
|
+
composition: Composition;
|
|
60
|
+
/** Take each state's lock: `runGroup` does, `planGroup` does not. */
|
|
61
|
+
lock: boolean;
|
|
62
|
+
/** Each member's step `vars` as written, by component. */
|
|
63
|
+
vars: Record<string, Record<string, unknown> | undefined>;
|
|
64
|
+
/** The outputs of components outside the group that a member's `vars` read. */
|
|
65
|
+
componentOutputs: Record<string, Record<string, unknown>>;
|
|
66
|
+
env: NodeJS.ProcessEnv;
|
|
67
|
+
/** A directory of the run's own, removed after the run is released. */
|
|
68
|
+
work: string;
|
|
69
|
+
}
|
|
70
|
+
/** What the group methods use, each replaceable in a test. */
|
|
71
|
+
export interface ComposeGroupDeps {
|
|
72
|
+
/** The project's config. Default: `loadChantConfigUpward` from the current directory. */
|
|
73
|
+
project?: () => Promise<ComposeProject>;
|
|
74
|
+
/** The run for a composition. Default: `EstateRun` when `estate` is set, else `ComposedRun`. */
|
|
75
|
+
runner?: (o: GroupRunnerOptions) => ComposedRunner;
|
|
76
|
+
/**
|
|
77
|
+
* Each target's cloud identity for `env` (`resolveTargetCloudIdentity`,
|
|
78
|
+
* #4332), or undefined when there is no workspace to read. Default:
|
|
79
|
+
* `declaredTargetCloudIdentity` from the project root.
|
|
80
|
+
*/
|
|
81
|
+
identity?: (projectRoot: string, env: string) => Promise<TargetCloudIdentityLookup | undefined>;
|
|
82
|
+
/** How `choudoufu version` runs. Default `runBinary`. */
|
|
83
|
+
exec?: BinaryExec;
|
|
84
|
+
/** A root's outputs as its state holds them, for a member whose part of the working state could not be shown. Default `terraformOutputs`. */
|
|
85
|
+
outputs?: (root: string) => Promise<Record<string, unknown>>;
|
|
86
|
+
env?: NodeJS.ProcessEnv;
|
|
87
|
+
log?: (line: string) => void;
|
|
88
|
+
}
|
|
89
|
+
/** The group methods `terraform-apply` declares. */
|
|
90
|
+
export interface ComposeGroupMethods {
|
|
91
|
+
groupReads(ctx: GroupContext, members: readonly GroupMember<StepInput>[]): Promise<ReportedGroupRead[]>;
|
|
92
|
+
groupFallback(ctx: GroupContext, members: readonly GroupMember<StepInput>[]): Promise<GroupFallback | undefined>;
|
|
93
|
+
planGroup(ctx: GroupContext, members: readonly GroupMember<StepInput>[]): Promise<CapabilityGroupPlan>;
|
|
94
|
+
runGroup(ctx: GroupContext, run: GroupRun, members: readonly GroupMember<StepInput>[]): Promise<CapabilityGroupOutcome>;
|
|
95
|
+
}
|
|
96
|
+
/** The members as components, each deploying its one step, for `compositionIfEnabled`. */
|
|
97
|
+
export declare function memberComponents(members: readonly GroupMember<StepInput>[]): DriverComponent[];
|
|
98
|
+
/**
|
|
99
|
+
* Each member's identity as `compositionFor` compares it: `identityKey` of
|
|
100
|
+
* `lookup` for the member's root, which a rule's target globs match by the
|
|
101
|
+
* root's key in `terraform.roots` or by its directory relative to the
|
|
102
|
+
* project (the first declared rule that either matches wins). Undefined
|
|
103
|
+
* without a lookup, so no member falls back as `identity`.
|
|
104
|
+
*/
|
|
105
|
+
export declare function memberIdentity(project: ComposeProject, members: readonly GroupMember<StepInput>[], lookup: TargetCloudIdentityLookup | undefined): ((member: string) => string | undefined) | undefined;
|
|
106
|
+
/**
|
|
107
|
+
* The composition of exactly the group's members (core has already formed the
|
|
108
|
+
* group), or undefined when `terraform.compose` is not enabled.
|
|
109
|
+
*/
|
|
110
|
+
export declare function groupComposition(project: ComposeProject, members: readonly GroupMember<StepInput>[], env: NodeJS.ProcessEnv, identity?: (member: string) => string | undefined): Composition | undefined;
|
|
111
|
+
/**
|
|
112
|
+
* The members' HCL reads of each other (#4447): each `terraform_remote_state`
|
|
113
|
+
* block whose state is another member's backend, and each
|
|
114
|
+
* `terraform_estate_outputs` block naming another member's estate, as the
|
|
115
|
+
* composition finds them. Each output the reader's code takes off the block
|
|
116
|
+
* is one read, `<upstream>.<output>` as a `stackOutput()` read is named; a
|
|
117
|
+
* block whose outputs the code does not name one by one is one read with
|
|
118
|
+
* the block's address as `ref`. Core cannot see these in the step's `vars`.
|
|
119
|
+
*/
|
|
120
|
+
export declare function hclGroupReads(project: ComposeProject, members: readonly GroupMember<StepInput>[]): ReportedGroupRead[];
|
|
121
|
+
/**
|
|
122
|
+
* What core is told of the group (#4419 ruling 1): the whole change's
|
|
123
|
+
* fallback when it is not `one-root`; else each member the composition left
|
|
124
|
+
* out as a drop with its own kind and reason, so core groups the rest
|
|
125
|
+
* again without them; else `one-root`. Undefined when every member
|
|
126
|
+
* composes.
|
|
127
|
+
*/
|
|
128
|
+
export declare function groupFallbackOf(c: Composition, members: readonly string[]): GroupFallback | undefined;
|
|
129
|
+
/**
|
|
130
|
+
* The outputs of components outside the group that the members' `vars`
|
|
131
|
+
* read, recovered from each member's input, which core resolved against
|
|
132
|
+
* them: `writeComposedRoot` turns those reads into literals.
|
|
133
|
+
*/
|
|
134
|
+
export declare function outsideOutputs(members: readonly GroupMember<StepInput>[]): Record<string, Record<string, unknown>>;
|
|
135
|
+
/** A composed plan that no longer matches the one approved. Core's moved-plan mark, so nothing applies and the command exits 3. */
|
|
136
|
+
export declare class ComposedPlanMovedError extends Error {
|
|
137
|
+
readonly planMoved = true;
|
|
138
|
+
constructor(message: string);
|
|
139
|
+
}
|
|
140
|
+
/** The group methods, with `deps` in place of the defaults. */
|
|
141
|
+
export declare function composeGroup(deps?: ComposeGroupDeps): ComposeGroupMethods;
|
|
142
|
+
/** The group methods `terraform-apply` uses. */
|
|
143
|
+
export declare const terraformComposeGroup: ComposeGroupMethods;
|
|
144
|
+
export {};
|
|
145
|
+
//# sourceMappingURL=compose-group.d.ts.map
|