@intentius/chant-lexicon-terraform 0.124.51 → 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 +5 -0
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +87 -4
- 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 +216 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +666 -0
- 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 +84 -4
- 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/composites/terraform-pin-rollout-op.test.ts +2 -2
- 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 +423 -0
- package/src/report.ts +752 -0
- 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
CHANGED
|
@@ -22,5 +22,10 @@ export declare function parsePinRolloutArgs(rawArgs: string[]): {
|
|
|
22
22
|
};
|
|
23
23
|
/** Exit status for a rollout's status. */
|
|
24
24
|
export declare function pinRolloutExitCode(status: string): number;
|
|
25
|
+
/** Parse `chant terraform report`'s flags. Throws on an unknown flag, a missing value or a missing required flag. */
|
|
26
|
+
export declare function parseReportArgs(rawArgs: string[]): {
|
|
27
|
+
values: Map<string, string>;
|
|
28
|
+
json: boolean;
|
|
29
|
+
};
|
|
25
30
|
export declare const terraformCommands: CommandGroup;
|
|
26
31
|
//# sourceMappingURL=commands.d.ts.map
|
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
|
}
|
|
@@ -99,8 +100,90 @@ async function runPinRolloutVerb(ctx) {
|
|
|
99
100
|
return 1;
|
|
100
101
|
}
|
|
101
102
|
}
|
|
103
|
+
const REPORT_USAGE = "usage: chant terraform report --stage tf-plan|tf-apply --plan <show.json> [--applied <show-after-apply.json>] (--out <dir> | --to <url>) " +
|
|
104
|
+
"[--project <host/path>] [--commit <sha>] [--pull-request <n>] [--binary <name>] [--binary-version <v>] [--root-dir <dir>] [--self-root <dir>] [--repo <dir>] " +
|
|
105
|
+
"[--started <iso>] [--finished <iso>] [--json]";
|
|
106
|
+
const REPORT_VALUE_FLAGS = new Set(["--stage", "--plan", "--applied", "--out", "--to", "--project", "--commit", "--pull-request", "--binary", "--binary-version", "--root-dir", "--self-root", "--repo", "--started", "--finished"]);
|
|
107
|
+
/** Parse `chant terraform report`'s flags. Throws on an unknown flag, a missing value or a missing required flag. */
|
|
108
|
+
export function parseReportArgs(rawArgs) {
|
|
109
|
+
const tokens = splitJoinedFlags(rawArgs, new Set(["--json"]));
|
|
110
|
+
const values = new Map();
|
|
111
|
+
let json = false;
|
|
112
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
113
|
+
const t = tokens[i];
|
|
114
|
+
if (t === "--json") {
|
|
115
|
+
json = true;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (!REPORT_VALUE_FLAGS.has(t))
|
|
119
|
+
throw t.startsWith("-") ? unknownFlagError(t, REPORT_USAGE) : new Error(`unexpected argument "${t}"\n${REPORT_USAGE}`);
|
|
120
|
+
const v = tokens[++i];
|
|
121
|
+
if (v === undefined || v.startsWith("--"))
|
|
122
|
+
throw new Error(`${t} needs a value\n${REPORT_USAGE}`);
|
|
123
|
+
values.set(t, v);
|
|
124
|
+
}
|
|
125
|
+
const stage = values.get("--stage");
|
|
126
|
+
if (stage !== "tf-plan" && stage !== "tf-apply")
|
|
127
|
+
throw new Error(`--stage is tf-plan or tf-apply\n${REPORT_USAGE}`);
|
|
128
|
+
if (!values.has("--plan"))
|
|
129
|
+
throw new Error(`--plan is required\n${REPORT_USAGE}`);
|
|
130
|
+
if (stage === "tf-apply" && !values.has("--applied"))
|
|
131
|
+
throw new Error(`--stage tf-apply needs --applied, the show -json of a plan made after the apply\n${REPORT_USAGE}`);
|
|
132
|
+
if (values.has("--out") === values.has("--to"))
|
|
133
|
+
throw new Error(`give one of --out <dir> and --to <url>\n${REPORT_USAGE}`);
|
|
134
|
+
return { values, json };
|
|
135
|
+
}
|
|
136
|
+
async function runReportVerb(ctx) {
|
|
137
|
+
try {
|
|
138
|
+
const { values, json } = parseReportArgs(ctx.rawArgs);
|
|
139
|
+
const { readFileSync } = await import("node:fs");
|
|
140
|
+
const { runFactsFromEnv, terraformReport, writeReport, httpStore, dirStore, moduleEstates, atmosStackReads, topModule } = await import("./report.js");
|
|
141
|
+
const read = (flag) => {
|
|
142
|
+
const file = values.get(flag);
|
|
143
|
+
try {
|
|
144
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
throw new Error(`${flag} ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
const run = runFactsFromEnv(process.env, { pullRequest: values.get("--pull-request"), project: values.get("--project"), commit: values.get("--commit") });
|
|
151
|
+
const plan = read("--plan");
|
|
152
|
+
// The plan holds no `live` block: each module's estate is read from its directory, <repo>/<root-dir>/<module>.
|
|
153
|
+
const repo = values.get("--repo") ?? process.env.GITHUB_WORKSPACE ?? process.cwd();
|
|
154
|
+
const modules = [...new Set((plan.resource_changes ?? []).map((r) => topModule(String(r.module_address ?? r.address ?? ""))).filter((m) => m !== undefined))];
|
|
155
|
+
const estates = moduleEstates(repo, values.get("--root-dir") ?? "", modules);
|
|
156
|
+
// An Atmos repo wires its components with !terraform.output in the stack manifests, which the plan does not keep.
|
|
157
|
+
const componentReads = atmosStackReads(repo);
|
|
158
|
+
const result = terraformReport({
|
|
159
|
+
stage: values.get("--stage"),
|
|
160
|
+
plan,
|
|
161
|
+
...(Object.keys(estates).length > 0 ? { estates } : {}),
|
|
162
|
+
...(Object.keys(componentReads).length > 0 ? { componentReads } : {}),
|
|
163
|
+
...(values.has("--applied") ? { applied: read("--applied") } : {}),
|
|
164
|
+
run,
|
|
165
|
+
...(values.has("--binary") ? { binary: values.get("--binary") } : {}),
|
|
166
|
+
...(values.has("--binary-version") ? { binaryVersion: values.get("--binary-version") } : {}),
|
|
167
|
+
...(values.has("--root-dir") ? { rootDir: values.get("--root-dir") } : {}),
|
|
168
|
+
...(values.has("--self-root") ? { selfRoot: values.get("--self-root") } : {}),
|
|
169
|
+
...(values.has("--started") ? { started: values.get("--started") } : {}),
|
|
170
|
+
finished: values.get("--finished") ?? new Date().toISOString().replace(/\.\d+Z$/, "Z"),
|
|
171
|
+
});
|
|
172
|
+
const store = values.has("--out") ? dirStore(values.get("--out")) : httpStore(values.get("--to"));
|
|
173
|
+
const keys = await writeReport(store, result);
|
|
174
|
+
console.log(json ? JSON.stringify({ path: result.path, keys, row: result.row }, null, 2) : `wrote ${keys.join(", ")}`);
|
|
175
|
+
return 0;
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
179
|
+
return 1;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
102
182
|
export const terraformCommands = {
|
|
103
183
|
name: "terraform",
|
|
104
|
-
description: "Terraform and OpenTofu roots: roll a module version out as one pin-bump PR per wave",
|
|
105
|
-
commands: [
|
|
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",
|
|
185
|
+
commands: [
|
|
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 },
|
|
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 },
|
|
188
|
+
],
|
|
106
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
|
};
|