@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/src/hcl/parse.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The one shared HCL parse for the terraform lexicon.
|
|
3
3
|
*
|
|
4
|
-
* Two entry points, one entity shape. `parseTerraformRootDir` reads the
|
|
5
|
-
* files of a directory (non-recursive, matching Terraform's own
|
|
6
|
-
* scoping
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Two entry points, one entity shape. `parseTerraformRootDir` reads the
|
|
5
|
+
* configuration files of a directory (non-recursive, matching Terraform's own
|
|
6
|
+
* module scoping; `.tf` and `.tf.json`, plus `.tofu` and `.tofu.json` for a
|
|
7
|
+
* tofu or choudoufu root, see `./files.ts`) and is what `buildRoots()` calls.
|
|
8
|
+
* `parseTerraformRootContent` takes the joined-file string `chant audit` hands
|
|
9
|
+
* a lexicon for a discovered root module: every configuration file in filename
|
|
10
|
+
* order behind a `# file: <name>` line comment, narrowed to the same set.
|
|
9
11
|
* Both funnel through `blocksToEntities`, so an entity the audit path sees is
|
|
10
12
|
* the entity the build path sees.
|
|
11
13
|
*
|
|
@@ -18,7 +20,7 @@
|
|
|
18
20
|
* changed in `packages/core/package.json` for this.
|
|
19
21
|
*/
|
|
20
22
|
|
|
21
|
-
import { existsSync,
|
|
23
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
22
24
|
import { join } from "node:path";
|
|
23
25
|
import { loadHcl2json, type Hcl2Json } from "@intentius/chant/terraform/hcl2json";
|
|
24
26
|
import { DECLARABLE_MARKER, type Declarable } from "@intentius/chant/declarable";
|
|
@@ -27,6 +29,20 @@ import type { SuppressionDirective } from "@intentius/chant/lint/suppressions";
|
|
|
27
29
|
import type { TerraformDeleteMode } from "../config";
|
|
28
30
|
import { redactSensitiveBody, redactSensitiveSource } from "./redact";
|
|
29
31
|
import { scanSuppressions, directivesFor, type FileScan } from "./suppressions";
|
|
32
|
+
import { isJsonConfigFile, jsonConfigTree, listConfigFiles, selectConfigFiles } from "./files";
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
TERRAFORM_SUFFIXES,
|
|
36
|
+
TOFU_SUFFIXES,
|
|
37
|
+
isConfigFileFor,
|
|
38
|
+
isJsonConfigFile,
|
|
39
|
+
isTofuConfigFile,
|
|
40
|
+
jsonConfigTree,
|
|
41
|
+
listConfigFiles,
|
|
42
|
+
listConfigFilesSafe,
|
|
43
|
+
readsTofuFiles,
|
|
44
|
+
selectConfigFiles,
|
|
45
|
+
} from "./files";
|
|
30
46
|
|
|
31
47
|
/** A parsed HCL block body, as `@cdktn/hcl2json` encodes it. */
|
|
32
48
|
export type BlockBody = Record<string, unknown>;
|
|
@@ -40,6 +56,10 @@ export type BlockBody = Record<string, unknown>;
|
|
|
40
56
|
* the instance count is a plan-time answer and often a state-time one, but it
|
|
41
57
|
* is a shape a reader should be told rather than left to infer from a node
|
|
42
58
|
* count that never grows.
|
|
59
|
+
*
|
|
60
|
+
* Since #4454 a literal expansion also gets its instance nodes, children of
|
|
61
|
+
* the block's node (`TerraformEntity.instanceKeys`). The block node stays,
|
|
62
|
+
* and the edges stay block to block.
|
|
43
63
|
*/
|
|
44
64
|
export type TerraformExpansion = "count" | "for_each";
|
|
45
65
|
|
|
@@ -151,6 +171,27 @@ export interface TerraformEntity extends Declarable {
|
|
|
151
171
|
* ROOT's name, not the module's: the module is not separately applied.
|
|
152
172
|
*/
|
|
153
173
|
readonly stack: string;
|
|
174
|
+
/**
|
|
175
|
+
* The instance keys a `resource` or `data` block's literal `count` or
|
|
176
|
+
* `for_each` expands to (#4454), as Terraform prints them: `[0]`, `[1]`
|
|
177
|
+
* for `count = 2`, `["a"]` for a `for_each` over a literal map or
|
|
178
|
+
* `toset([...])`. Core's `withInstanceNodes` reads this lexicon-neutral
|
|
179
|
+
* field, as `buildGraphIr` reads `stack`, and draws one instance node per
|
|
180
|
+
* key under the block's node. Absent when the block is not expanded or its
|
|
181
|
+
* expansion is not a literal. Since #4486 `./instances.ts` also sets it
|
|
182
|
+
* for an expansion it can count from the root's variables (`count = var.n`
|
|
183
|
+
* with `n` known), and for a `module` call.
|
|
184
|
+
*/
|
|
185
|
+
readonly instanceKeys?: readonly string[];
|
|
186
|
+
/**
|
|
187
|
+
* The instance nodes of a block under an expanded module call (#4486), by
|
|
188
|
+
* full node id: `estate/module.app[0]/aws_eip.pool[1]`, with `key` the
|
|
189
|
+
* block's own key (`[1]`, absent when the block is not expanded itself)
|
|
190
|
+
* and `scope` the module path as Terraform prints it (`module.app[0]`).
|
|
191
|
+
* Core's `entityInstances` reads this in place of `instanceKeys`, which
|
|
192
|
+
* such a block never carries. Set by `./instances.ts`.
|
|
193
|
+
*/
|
|
194
|
+
readonly instances?: ReadonlyArray<{ readonly id: string; readonly key?: string; readonly scope?: string }>;
|
|
154
195
|
/**
|
|
155
196
|
* Every reference in this block, resolved to the entity keys it points at
|
|
156
197
|
* (chant #2265), in core's lexicon-neutral {@link EntityReference} shape.
|
|
@@ -183,6 +224,23 @@ export const OUTPUT_TYPE = "Terraform::Output";
|
|
|
183
224
|
export const LOCALS_TYPE = "Terraform::Locals";
|
|
184
225
|
/** A `live { estate = "..." }` block, or an `estate.chdf.hcl` sidecar's content (#2103). */
|
|
185
226
|
export const LIVE_TYPE = "Terraform::Live";
|
|
227
|
+
/**
|
|
228
|
+
* The four configuration blocks that change how a plan treats addresses
|
|
229
|
+
* rather than declaring anything (#4458): `moved`, `import`, `removed` and
|
|
230
|
+
* `check`. The first three carry no label and key as `moved`, `import`,
|
|
231
|
+
* `removed` (numbered `~2`, `~3` when a root holds several, as `locals` is);
|
|
232
|
+
* a `check` keys as `check.<name>`. Their references are edges like any other
|
|
233
|
+
* block's: a `moved` block's `from` and `to`, an `import` block's `to`, a
|
|
234
|
+
* `removed` block's `from`, and everything a `check` block's assertions and
|
|
235
|
+
* scoped `data` blocks read. A `from` usually names an address that is no
|
|
236
|
+
* longer declared, which resolves to no entity and so draws no edge, and
|
|
237
|
+
* that is all that happens: an unresolved reference is dropped, never
|
|
238
|
+
* reported (`./edges.ts`).
|
|
239
|
+
*/
|
|
240
|
+
export const MOVED_TYPE = "Terraform::Moved";
|
|
241
|
+
export const IMPORT_TYPE = "Terraform::Import";
|
|
242
|
+
export const REMOVED_TYPE = "Terraform::Removed";
|
|
243
|
+
export const CHECK_TYPE = "Terraform::Check";
|
|
186
244
|
|
|
187
245
|
/**
|
|
188
246
|
* Terragrunt config entities (#3417). A `terragrunt.hcl` or `root.hcl` is not
|
|
@@ -214,6 +272,55 @@ function expansionOf(body: BlockBody): TerraformExpansion | undefined {
|
|
|
214
272
|
return undefined;
|
|
215
273
|
}
|
|
216
274
|
|
|
275
|
+
/** The most instance keys read off one literal; past it the block draws as the block alone. */
|
|
276
|
+
const MAX_LITERAL_INSTANCES = 256;
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* The instance keys a literal expansion gives (#4454), as Terraform prints
|
|
280
|
+
* them, or undefined when the expansion is not a literal a read of the
|
|
281
|
+
* source can count. `count` as a number (`count = 3`), `for_each` as a
|
|
282
|
+
* literal map (`{ a = 1 }`, keys in Terraform's lexical order) or
|
|
283
|
+
* `toset([...])` over string literals. Anything holding a reference is a
|
|
284
|
+
* plan-time answer.
|
|
285
|
+
*/
|
|
286
|
+
export function literalInstanceKeys(body: BlockBody): string[] | undefined {
|
|
287
|
+
const keys = literalKeys(body);
|
|
288
|
+
if (keys === undefined || keys.length > MAX_LITERAL_INSTANCES) return undefined;
|
|
289
|
+
return keys;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function literalKeys(body: BlockBody): string[] | undefined {
|
|
293
|
+
if ("count" in body) {
|
|
294
|
+
const count = body.count;
|
|
295
|
+
const n = typeof count === "number" ? count
|
|
296
|
+
: typeof count === "string" && /^\$\{\s*\d+\s*\}$/.test(count.trim()) ? Number(count.trim().slice(2, -1))
|
|
297
|
+
: undefined;
|
|
298
|
+
if (n === undefined || !Number.isInteger(n) || n < 0) return undefined;
|
|
299
|
+
return Array.from({ length: n }, (_, i) => `[${i}]`);
|
|
300
|
+
}
|
|
301
|
+
if ("for_each" in body) {
|
|
302
|
+
const each = body.for_each;
|
|
303
|
+
if (typeof each === "object" && each !== null && !Array.isArray(each)) {
|
|
304
|
+
const names = Object.keys(each);
|
|
305
|
+
if (names.some((k) => k.includes("${"))) return undefined;
|
|
306
|
+
return names.sort().map((k) => `[${JSON.stringify(k)}]`);
|
|
307
|
+
}
|
|
308
|
+
if (typeof each === "string") {
|
|
309
|
+
const m = /^\$\{\s*toset\(\s*(\[[\s\S]*\])\s*\)\s*\}$/.exec(each.trim());
|
|
310
|
+
if (!m) return undefined;
|
|
311
|
+
let list: unknown;
|
|
312
|
+
try {
|
|
313
|
+
list = JSON.parse(m[1]!.replace(/,\s*\]$/, "]"));
|
|
314
|
+
} catch {
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
if (!Array.isArray(list) || !list.every((v) => typeof v === "string")) return undefined;
|
|
318
|
+
return [...new Set(list as string[])].sort().map((k) => `[${JSON.stringify(k)}]`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return undefined;
|
|
322
|
+
}
|
|
323
|
+
|
|
217
324
|
/**
|
|
218
325
|
* Build one entity. Written out rather than run through `createResource`
|
|
219
326
|
* from `@intentius/chant/runtime`: that factory is for generated resource
|
|
@@ -234,6 +341,7 @@ export function terraformEntity(
|
|
|
234
341
|
suppressions?: readonly SuppressionDirective[],
|
|
235
342
|
): TerraformEntity {
|
|
236
343
|
const expansion = expansionOf(body);
|
|
344
|
+
const instanceKeys = expansion && (entityType === RESOURCE_TYPE || entityType === DATA_TYPE) ? literalInstanceKeys(body) : undefined;
|
|
237
345
|
const labels = entityType === RESOURCE_TYPE ? blockLabels(address)
|
|
238
346
|
: entityType === DATA_TYPE && address.startsWith("data.") ? blockLabels(address.slice("data.".length))
|
|
239
347
|
: undefined;
|
|
@@ -243,6 +351,7 @@ export function terraformEntity(
|
|
|
243
351
|
entityType,
|
|
244
352
|
kind: "resource",
|
|
245
353
|
stack: root,
|
|
354
|
+
...(instanceKeys ? { instanceKeys } : {}),
|
|
246
355
|
props: {
|
|
247
356
|
address,
|
|
248
357
|
...(labels ?? {}),
|
|
@@ -302,11 +411,20 @@ export function splitBundleContent(content: string, fallbackName = "main.tf"): T
|
|
|
302
411
|
return files.filter((f) => f.source.trim() !== "");
|
|
303
412
|
}
|
|
304
413
|
|
|
305
|
-
/**
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
414
|
+
/**
|
|
415
|
+
* Every configuration file directly under `dir` that a root run by `binary`
|
|
416
|
+
* reads, in filename order, non-recursive: `.tf` and `.tf.json`, plus `.tofu`
|
|
417
|
+
* and `.tofu.json` for a tofu or choudoufu root, with `x.tofu` shadowing
|
|
418
|
+
* `x.tf` (chant#4457). The shared lister is `./files.ts`'s `listConfigFiles`.
|
|
419
|
+
*/
|
|
420
|
+
export function listTerraformFiles(dir: string, binary?: string): string[] {
|
|
421
|
+
return listConfigFiles(dir, binary);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** One file's tree in hcl2json's shape: hcl2json for HCL, {@link jsonConfigTree} for Terraform's JSON syntax. */
|
|
425
|
+
export async function parseFileTree(parser: Hcl2Json, file: TerraformFile): Promise<Record<string, unknown>> {
|
|
426
|
+
if (isJsonConfigFile(file.name)) return jsonConfigTree(file.source);
|
|
427
|
+
return (await parser.parse(file.name, file.source)) as Record<string, unknown>;
|
|
310
428
|
}
|
|
311
429
|
|
|
312
430
|
/** The Terragrunt config files directly under `dir` (`root.hcl`, `terragrunt.hcl`), in filename order like the audit's bundle. */
|
|
@@ -488,7 +606,7 @@ export async function blocksToEntities(
|
|
|
488
606
|
};
|
|
489
607
|
|
|
490
608
|
for (const file of files) {
|
|
491
|
-
const tree =
|
|
609
|
+
const tree = await parseFileTree(parser, file);
|
|
492
610
|
const scan = scanSuppressions(file.name, file.source);
|
|
493
611
|
if (isTerragruntFileName(file.name)) {
|
|
494
612
|
terragruntBlocks(tree, file, scan, add);
|
|
@@ -503,6 +621,10 @@ export async function blocksToEntities(
|
|
|
503
621
|
oneLabel(tree, "output", OUTPUT_TYPE, (n) => `output.${n}`, file, scan);
|
|
504
622
|
twoLabels(tree, "resource", RESOURCE_TYPE, (t, n) => `${t}.${n}`, file, scan);
|
|
505
623
|
twoLabels(tree, "data", DATA_TYPE, (t, n) => `data.${t}.${n}`, file, scan);
|
|
624
|
+
unlabelled(tree, "moved", MOVED_TYPE, file, scan);
|
|
625
|
+
unlabelled(tree, "import", IMPORT_TYPE, file, scan);
|
|
626
|
+
unlabelled(tree, "removed", REMOVED_TYPE, file, scan);
|
|
627
|
+
oneLabel(tree, "check", CHECK_TYPE, (n) => `check.${n}`, file, scan);
|
|
506
628
|
}
|
|
507
629
|
|
|
508
630
|
if (sidecar) {
|
|
@@ -568,8 +690,9 @@ function terragruntBlocks(
|
|
|
568
690
|
}
|
|
569
691
|
|
|
570
692
|
/**
|
|
571
|
-
* Parse a root module directory. Reads
|
|
572
|
-
*
|
|
693
|
+
* Parse a root module directory. Reads the configuration files directly under
|
|
694
|
+
* `dir` that `modeOptions.binary` reads ({@link listTerraformFiles}), plus the
|
|
695
|
+
* `estate.chdf.hcl` sidecar beside them when present. Throws whatever the
|
|
573
696
|
* parser throws for malformed HCL; `buildRoots()` is where that becomes a
|
|
574
697
|
* warning.
|
|
575
698
|
*/
|
|
@@ -581,7 +704,7 @@ export async function parseTerraformRootDir(
|
|
|
581
704
|
callers: readonly string[] = [],
|
|
582
705
|
): Promise<Map<string, Declarable>> {
|
|
583
706
|
// A child module is a Terraform module, never a Terragrunt unit (#3417).
|
|
584
|
-
const names = [...listTerraformFiles(dir), ...(callers.length === 0 ? listTerragruntFiles(dir) : [])];
|
|
707
|
+
const names = [...listTerraformFiles(dir, modeOptions?.binary), ...(callers.length === 0 ? listTerragruntFiles(dir) : [])];
|
|
585
708
|
const files = names.map((name) => ({
|
|
586
709
|
name,
|
|
587
710
|
source: readFileSync(join(dir, name), "utf-8"),
|
|
@@ -610,7 +733,17 @@ export async function parseTerraformRootContent(
|
|
|
610
733
|
hcl2json?: Hcl2Json,
|
|
611
734
|
modeOptions?: TerraformRootModeOptions,
|
|
612
735
|
): Promise<Map<string, Declarable>> {
|
|
613
|
-
return blocksToEntities(splitBundleContent(content), root, hcl2json, modeOptions);
|
|
736
|
+
return blocksToEntities(bundleFilesFor(splitBundleContent(content), modeOptions?.binary), root, hcl2json, modeOptions);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* The files of a joined bundle a root run by `binary` reads: the same set
|
|
741
|
+
* {@link listTerraformFiles} gives for a directory holding them, plus the
|
|
742
|
+
* Terragrunt files, in bundle order (chant#4457).
|
|
743
|
+
*/
|
|
744
|
+
export function bundleFilesFor(files: readonly TerraformFile[], binary?: string): TerraformFile[] {
|
|
745
|
+
const keep = new Set(selectConfigFiles(files.map((f) => f.name), binary));
|
|
746
|
+
return files.filter((f) => keep.has(f.name) || isTerragruntFileName(f.name));
|
|
614
747
|
}
|
|
615
748
|
|
|
616
749
|
/** The root name an audit of the audited directory itself parses under. */
|
package/src/hcl/roots.ts
CHANGED
|
@@ -34,6 +34,7 @@ import type { TerraformRootConfig } from "../config";
|
|
|
34
34
|
import { LIVE_TYPE, parseTerraformRootDir } from "./parse";
|
|
35
35
|
import { descendModules, resolveCallModuleType, type CallModuleType } from "./descend";
|
|
36
36
|
import { resolveEntityReferences } from "./edges";
|
|
37
|
+
import { resolveInstanceNodes } from "./instances";
|
|
37
38
|
|
|
38
39
|
export interface TerraformRootsResult {
|
|
39
40
|
entities: Map<string, Declarable>;
|
|
@@ -111,6 +112,11 @@ export async function renderTerraformRoots(
|
|
|
111
112
|
for (const [key, entity] of descended.entities) entities.set(key, entity);
|
|
112
113
|
warnings.push(...descended.warnings);
|
|
113
114
|
|
|
115
|
+
// Instance nodes the root's variables let the declared graph count,
|
|
116
|
+
// and those under expanded module calls (#4486). Needs the root and
|
|
117
|
+
// every child scope parsed, so it runs once the descent has.
|
|
118
|
+
await resolveInstanceNodes(entities, name, { dir, ...(root.varFiles ? { varFiles: root.varFiles } : {}), hcl2json: opts.hcl2json });
|
|
119
|
+
|
|
114
120
|
// A `live` block or `estate.chdf.hcl` sidecar only takes effect under
|
|
115
121
|
// choudoufu; under any other binary it parses fine but is inert, which
|
|
116
122
|
// is worth a warning rather than silence (#2103).
|
package/src/hcl/suppressions.ts
CHANGED
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
import type { SuppressionDirective, SuppressionIds } from "@intentius/chant/lint/suppressions";
|
|
28
28
|
|
|
29
29
|
/** A block header keyword with zero labels (`terraform {`, `locals {`). */
|
|
30
|
-
const BARE_RE = /^\s*(terraform|locals|remote_state)\s*\{/;
|
|
30
|
+
const BARE_RE = /^\s*(terraform|locals|remote_state|moved|import|removed)\s*\{/;
|
|
31
31
|
/** One label (`provider "aws" {`, `module "vpc" {`, `variable "x" {`, `output "x" {`). */
|
|
32
|
-
const ONE_LABEL_RE = /^\s*(provider|module|variable|output|dependency|generate)\s+"([^"]*)"\s*\{/;
|
|
32
|
+
const ONE_LABEL_RE = /^\s*(provider|module|variable|output|check|dependency|generate)\s+"([^"]*)"\s*\{/;
|
|
33
33
|
/** Two labels (`resource "aws_s3_bucket" "assets" {`, `data "aws_ami" "x" {`). */
|
|
34
34
|
const TWO_LABEL_RE = /^\s*(resource|data)\s+"([^"]*)"\s+"([^"]*)"\s*\{/;
|
|
35
35
|
|
|
@@ -45,6 +45,12 @@ function addressFor(keyword: string, labels: string[]): string {
|
|
|
45
45
|
return "locals";
|
|
46
46
|
case "remote_state":
|
|
47
47
|
return "remote_state";
|
|
48
|
+
case "moved":
|
|
49
|
+
case "import":
|
|
50
|
+
case "removed":
|
|
51
|
+
return keyword;
|
|
52
|
+
case "check":
|
|
53
|
+
return `check.${labels[0]}`;
|
|
48
54
|
case "dependency":
|
|
49
55
|
return `dependency.${labels[0]}`;
|
|
50
56
|
case "generate":
|
package/src/index.ts
CHANGED
|
@@ -10,10 +10,85 @@ export {
|
|
|
10
10
|
terraformApplyCapability,
|
|
11
11
|
TerraformPlanMovedError,
|
|
12
12
|
TERRAFORM_VERB_FAMILIES,
|
|
13
|
+
// #4455: a choudoufu member's outputs, read for core's `member-outputs`.
|
|
14
|
+
choudoufuOutputSource,
|
|
15
|
+
choudoufuMemberPlannedOutputs,
|
|
13
16
|
type TerraformApplyStepInput,
|
|
14
17
|
type TerraformApplyStepOutput,
|
|
15
18
|
} from "./components/capability-plugin";
|
|
16
19
|
|
|
20
|
+
// Composed runs (#4442): the working state merged and split, the composed
|
|
21
|
+
// plan cut into one plan per member, and each member's apply outcome.
|
|
22
|
+
export { mergeStates, splitState, splitStates, derivedLineage, COMPOSE_LINEAGE, type SplitState } from "./compose/state-merge";
|
|
23
|
+
export {
|
|
24
|
+
slicePlan as sliceComposedPlan,
|
|
25
|
+
sliceText,
|
|
26
|
+
planCounts,
|
|
27
|
+
composedReads,
|
|
28
|
+
composedMemberPlans,
|
|
29
|
+
type PlanCounts,
|
|
30
|
+
type ComposedMemberRead,
|
|
31
|
+
type ComposedMemberPlan,
|
|
32
|
+
type ComposedMemberPlansOptions,
|
|
33
|
+
} from "./compose/slice";
|
|
34
|
+
export { readApplyEvents, memberApply, composedApplyOutcomes, type ApplyEvents, type MemberApply, type MemberApplyOutcome } from "./compose/apply-events";
|
|
35
|
+
export { topModule, stripModule, stripChange, moduleChanges } from "./compose/module-address";
|
|
36
|
+
|
|
37
|
+
// Composed runs of choudoufu estates (#4445): one plan and one apply, values read from a post-apply plan.
|
|
38
|
+
export {
|
|
39
|
+
EstateRun,
|
|
40
|
+
estateVersionFallback,
|
|
41
|
+
estateOutcomes,
|
|
42
|
+
readEstateApplyText,
|
|
43
|
+
type ComposedRunner,
|
|
44
|
+
type ComposedRunPlan,
|
|
45
|
+
type ComposedRunApply,
|
|
46
|
+
type ComposedMemberOutcome,
|
|
47
|
+
type EstateRunOptions,
|
|
48
|
+
} from "./compose/estate";
|
|
49
|
+
export { priorStateValues, stateResources, stateOutputs, type StateResourceValues } from "./applied-state";
|
|
50
|
+
|
|
51
|
+
// Composed runs over roots with state (#4444): one plan and one apply, each
|
|
52
|
+
// state written back whatever happened. Implements `ComposedRunner` (#4445).
|
|
53
|
+
export {
|
|
54
|
+
ComposedRun,
|
|
55
|
+
composedPlanDigest,
|
|
56
|
+
changesSomething,
|
|
57
|
+
type ComposedRunOptions,
|
|
58
|
+
type ComposedRunStage,
|
|
59
|
+
type StateRunPlan,
|
|
60
|
+
type StateRunApply,
|
|
61
|
+
type StateMemberOutcome,
|
|
62
|
+
type ComposedStateWrite,
|
|
63
|
+
type ComposeHook,
|
|
64
|
+
type ComposeHookMoment,
|
|
65
|
+
} from "./compose/run";
|
|
66
|
+
|
|
67
|
+
// The plan shape of a `show -json` plan (#4436), for core's `coveredRound`.
|
|
68
|
+
export { terraformPlanShape, type TerraformPlanShapeOptions } from "./plan-shape";
|
|
69
|
+
|
|
70
|
+
// Composed runs (#4419, #4438): which roots compose, in what order, and why
|
|
71
|
+
// the others fall back. Off unless `terraform.compose.enabled` is set.
|
|
72
|
+
export {
|
|
73
|
+
compositionFor,
|
|
74
|
+
compositionIfEnabled,
|
|
75
|
+
composeOff,
|
|
76
|
+
composeSet,
|
|
77
|
+
readOrder,
|
|
78
|
+
moduleNames,
|
|
79
|
+
fallbackLine,
|
|
80
|
+
withPlanFailed,
|
|
81
|
+
identityKey,
|
|
82
|
+
DEFAULT_COMPOSE_MAX,
|
|
83
|
+
COMPOSE_STEP_KIND,
|
|
84
|
+
type Composition,
|
|
85
|
+
type CompositionInput,
|
|
86
|
+
type ComposedRead,
|
|
87
|
+
type ComposeFallback,
|
|
88
|
+
type ComposeFallbackKind,
|
|
89
|
+
type ExpectedBinary,
|
|
90
|
+
} from "./compose";
|
|
91
|
+
|
|
17
92
|
// Serializer
|
|
18
93
|
export { terraformSerializer } from "./serializer";
|
|
19
94
|
|
|
@@ -68,6 +143,7 @@ export {
|
|
|
68
143
|
choudoufuLiveLs,
|
|
69
144
|
choudoufuLiveCheck,
|
|
70
145
|
choudoufuAdopt,
|
|
146
|
+
choudoufuPlanSet,
|
|
71
147
|
terraformPinRollout,
|
|
72
148
|
} from "./op/builders";
|
|
73
149
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* choudoufu's live-plan omission codes, in chant's vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* Its own module so the observation reader (`./describe-resources.ts`) and
|
|
5
|
+
* the drift adapters (`./drift-record.ts`) read one table, and the drift
|
|
6
|
+
* adapters bundle without the reader's process and file-system imports.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { UnobservedReason } from "@intentius/chant/observation";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* choudoufu's omission reason codes, mapped onto chant's total vocabulary
|
|
13
|
+
* (`internal/live/projection/result.go`). `"absent"` is not an
|
|
14
|
+
* {@link UnobservedReason}: it is the tri-state's OBSERVED-ABSENT, the one
|
|
15
|
+
* omission that is a claim of knowledge rather than of ignorance.
|
|
16
|
+
*
|
|
17
|
+
* A code this table does not know maps to `read-failed`, which is the safe
|
|
18
|
+
* direction: no NOT-OBSERVED verdict ever becomes a create or a delete.
|
|
19
|
+
*/
|
|
20
|
+
export const LIVE_PLAN_OMISSION_REASONS: Readonly<Record<string, UnobservedReason | "absent">> = {
|
|
21
|
+
// The provider was asked and reported, normally, that nothing is there.
|
|
22
|
+
ABSENT: "absent",
|
|
23
|
+
// The provider errored, or could not be reached in a usable order.
|
|
24
|
+
FAILED: "read-failed",
|
|
25
|
+
PARENT_UNAVAILABLE: "read-failed",
|
|
26
|
+
CYCLE: "read-failed",
|
|
27
|
+
INCOMPLETE_BLOCK: "read-failed",
|
|
28
|
+
LISTED_NOT_IMPORTABLE: "read-failed",
|
|
29
|
+
// Something exists at the identity and does not carry this estate's marker.
|
|
30
|
+
// Normally answered by the `unowned[]` section before this table is read;
|
|
31
|
+
// this entry is what happens if a document ever carries the omission alone.
|
|
32
|
+
UNOWNED: "read-failed",
|
|
33
|
+
// The mode cannot name, list or tag this instance at all: a server-assigned
|
|
34
|
+
// identity with no marker to find it by, a resource with no cloud object to
|
|
35
|
+
// read, or an address another declared instance has taken over.
|
|
36
|
+
NEEDS_DISCOVERY: "unsupported-kind",
|
|
37
|
+
UNREADABLE: "unsupported-kind",
|
|
38
|
+
SUPERSEDED: "unsupported-kind",
|
|
39
|
+
// The run's own tag index saw an object at the identity and the import
|
|
40
|
+
// read could not confirm it (choudoufu #1043). The read failed; the
|
|
41
|
+
// object is not proposed for creation.
|
|
42
|
+
VERIFIED_NOT_IMPORTABLE: "read-failed",
|
|
43
|
+
// -target or -exclude left the block out of the plan graph (choudoufu
|
|
44
|
+
// #1176): nothing was read because the caller asked for it not to be.
|
|
45
|
+
OUT_OF_SCOPE: "filtered",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/** Does this text name a credentials or authorization failure rather than any other error? */
|
|
49
|
+
export function readsAsCredentials(text: string): boolean {
|
|
50
|
+
return /\b(no valid credential|credentials?|not authorized|unauthorized|accessdenied|access denied|expiredtoken|invalidclienttokenid|signature)\b/i.test(
|
|
51
|
+
text,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
@@ -104,7 +104,7 @@ describe("LSP completions", () => {
|
|
|
104
104
|
it("completes root-entry keys inside chant.config.ts's terraform.roots.<name>", () => {
|
|
105
105
|
const content = "export default {\n terraform: {\n roots: {\n app: {\n ";
|
|
106
106
|
const items = completions(ctx({ content, position: { line: 4, character: 8 } }));
|
|
107
|
-
expect(items.map((i) => i.label).sort()).toEqual(["backendConfig", "delete", "dir", "varFiles", "workspace"]);
|
|
107
|
+
expect(items.map((i) => i.label).sort()).toEqual(["backendConfig", "backendConfigFiles", "delete", "dir", "signalOnly", "varFiles", "workspace"]);
|
|
108
108
|
});
|
|
109
109
|
|
|
110
110
|
it("completes the terraform namespace's own top-level keys", () => {
|
package/src/lsp/option-keys.ts
CHANGED
|
@@ -25,7 +25,9 @@ export const ROOT_ENTRY_KEYS: OptionKey[] = [
|
|
|
25
25
|
{ key: "workspace", detail: "Terraform workspace to select for this root. Omitted means `default`." },
|
|
26
26
|
{ key: "varFiles", detail: "`-var-file` arguments, in order, relative to `dir`." },
|
|
27
27
|
{ key: "backendConfig", detail: "`-backend-config` key/value pairs handed to `init`." },
|
|
28
|
+
{ key: "backendConfigFiles", detail: "Backend-config files handed to `init` as `-backend-config=<path>`, before the pairs, relative to `dir`." },
|
|
28
29
|
{ key: "delete", detail: '"never" | "owned-only" | "gated" — this root\'s delete mode, mapped onto choudoufu\'s policy block on a live root.' },
|
|
30
|
+
{ key: "signalOnly", detail: "`true` when the root is declared only so a change to it brings this project into the change. The build and live reads skip it." },
|
|
29
31
|
];
|
|
30
32
|
|
|
31
33
|
/** `TerraformApplyOpConfig`'s keys (`TerraformApplyOp({ <here> })`). */
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `choudoufuPlanSet` as an Op activity (#4483, H3 of #4477). One Op plans two
|
|
3
|
+
* live choudoufu roots, one per cloud, and hands each root's `changeSet` to
|
|
4
|
+
* `composeChangeSet`, the shape behold's three-cloud front door takes instead
|
|
5
|
+
* of composing the change set in a script.
|
|
6
|
+
*
|
|
7
|
+
* The activity is the real one, resolved through `loadActivities`, so it runs
|
|
8
|
+
* the configured binary. A fake `choudoufu` on `PATH` answers `version` and
|
|
9
|
+
* prints a one-root set plan document for `live-plan-set`, exiting 2 as
|
|
10
|
+
* choudoufu does for a plan with changes. Nothing reaches a cloud.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { chmodSync, mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { afterAll, afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
17
|
+
import { composeChangeSet, loadActivities, runOpLocally, validateActivitySteps, validateStepOutputRefs, type ActivityProfile, type OpConfig } from "@intentius/chant/op";
|
|
18
|
+
import { choudoufuPlanSet } from "../builders";
|
|
19
|
+
import * as contracts from "../activity-contracts";
|
|
20
|
+
import { __resetChoudoufuVersionCheckForTests } from "./terraform";
|
|
21
|
+
|
|
22
|
+
const scratch: string[] = [];
|
|
23
|
+
afterAll(() => {
|
|
24
|
+
for (const d of scratch) rmSync(d, { recursive: true, force: true });
|
|
25
|
+
});
|
|
26
|
+
beforeEach(() => __resetChoudoufuVersionCheckForTests());
|
|
27
|
+
afterEach(() => vi.unstubAllEnvs());
|
|
28
|
+
|
|
29
|
+
const PROFILES: Record<string, ActivityProfile> = {
|
|
30
|
+
longInfra: { timeout: "5m", retry: { maximumAttempts: 1 } },
|
|
31
|
+
fastIdempotent: { timeout: "5m", retry: { maximumAttempts: 1 } },
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** A fake choudoufu: `version`, and `live-plan-set -json -out-dir=<d> <root>` printing a one-root document. */
|
|
35
|
+
const FAKE = `#!/bin/sh
|
|
36
|
+
if [ "$1" = "version" ]; then echo "choudoufu v0.31.0 (based on OpenTofu v1.13.0)"; exit 0; fi
|
|
37
|
+
if [ "$1" = "live-plan-set" ]; then
|
|
38
|
+
for a in "$@"; do root="$a"; done
|
|
39
|
+
name=$(basename "$root")
|
|
40
|
+
cat <<EOF
|
|
41
|
+
{"format_version":"1","roots":[{"root":"$root","estate":"$name","status":"planned","error":"","changes":true,"plan":{"format_version":"1.2","resource_changes":[{"address":"null_resource.$name","mode":"managed","type":"null_resource","name":"$name","change":{"actions":["create"],"before":null,"after":{}}}]},"digest":"sha256:root-$name"}],"summary":{"roots":1,"planned":1,"changed":1,"failed":0},"digest":"sha256:set-$name","exit_code":2}
|
|
42
|
+
EOF
|
|
43
|
+
exit 2
|
|
44
|
+
fi
|
|
45
|
+
echo "fake choudoufu: unexpected $*" >&2
|
|
46
|
+
exit 1
|
|
47
|
+
`;
|
|
48
|
+
|
|
49
|
+
function project(): string {
|
|
50
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), "chant-plan-set-op-")));
|
|
51
|
+
scratch.push(dir);
|
|
52
|
+
const bin = join(dir, "bin");
|
|
53
|
+
mkdirSync(bin);
|
|
54
|
+
writeFileSync(join(bin, "choudoufu"), FAKE);
|
|
55
|
+
chmodSync(join(bin, "choudoufu"), 0o755);
|
|
56
|
+
vi.stubEnv("PATH", `${bin}:${process.env.PATH ?? ""}`);
|
|
57
|
+
for (const root of ["aws", "azure"]) {
|
|
58
|
+
mkdirSync(join(dir, "estates", root), { recursive: true });
|
|
59
|
+
writeFileSync(join(dir, "estates", root, "main.tf"), "");
|
|
60
|
+
}
|
|
61
|
+
writeFileSync(
|
|
62
|
+
join(dir, "chant.config.json"),
|
|
63
|
+
JSON.stringify({ lexicons: ["terraform"], terraform: { binary: "choudoufu", roots: { aws: { dir: "estates/aws" }, azure: { dir: "estates/azure" } } } }),
|
|
64
|
+
);
|
|
65
|
+
return dir;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function frontDoor(cwd: string): OpConfig {
|
|
69
|
+
const aws = choudoufuPlanSet("aws", { cwd, id: "aws" });
|
|
70
|
+
const azure = choudoufuPlanSet("azure", { cwd, id: "azure" });
|
|
71
|
+
return {
|
|
72
|
+
name: "front-door-plan",
|
|
73
|
+
overview: "",
|
|
74
|
+
phases: [
|
|
75
|
+
{ name: "Plan", steps: [aws, azure] },
|
|
76
|
+
{ name: "Compose", steps: [composeChangeSet({ parts: [aws.out.changeSet, azure.out.changeSet], id: "doc" })] },
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
describe("choudoufuPlanSet as an Op activity (#4483)", () => {
|
|
82
|
+
test("is resolved from the terraform lexicon's activities", async () => {
|
|
83
|
+
const activities = await loadActivities(["terraform"]);
|
|
84
|
+
expect(typeof activities.get("choudoufuPlanSet")).toBe("function");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("its contract validates the steps and the changeSet references", () => {
|
|
88
|
+
const map = new Map(Object.values(contracts).map((c) => [c.name, c]));
|
|
89
|
+
const config = frontDoor("/tmp/x");
|
|
90
|
+
expect(validateActivitySteps(config, map)).toEqual([]);
|
|
91
|
+
expect(validateStepOutputRefs(config, map)).toEqual([]);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("an Op plans two clouds' roots and composes one change set from their parts", async () => {
|
|
95
|
+
const dir = project();
|
|
96
|
+
const activities = await loadActivities(["terraform"]);
|
|
97
|
+
const compose = activities.get("composeChangeSet")!;
|
|
98
|
+
const documents: Array<{ document: { members?: Array<{ member: string }> }; digest: string }> = [];
|
|
99
|
+
activities.set("composeChangeSet", async (args, signal) => {
|
|
100
|
+
const out = (await compose(args, signal)) as (typeof documents)[number];
|
|
101
|
+
documents.push(out);
|
|
102
|
+
return out;
|
|
103
|
+
});
|
|
104
|
+
const result = await runOpLocally(frontDoor(dir), activities, PROFILES);
|
|
105
|
+
expect(result.status).toBe("ok");
|
|
106
|
+
expect(result.records.filter((r) => r.fn === "choudoufuPlanSet").map((r) => r.status)).toEqual(["ok", "ok"]);
|
|
107
|
+
expect(documents).toHaveLength(1);
|
|
108
|
+
expect(documents[0]!.digest).toMatch(/^jcs1-sha256:[0-9a-f]{64}$/);
|
|
109
|
+
expect((documents[0]!.document.members ?? []).map((m) => m.member).sort()).toEqual(["aws", "azure"]);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
* - choudoufuLiveLs: `live-ls -estate=<estate> -json [-consistent]` (#2103).
|
|
17
17
|
* - choudoufuLiveCheck: `live-check -json`, no cloud calls (#2103).
|
|
18
18
|
* - choudoufuAdopt: the tag writes that claim an adoption ledger's matches (#2105).
|
|
19
|
+
* - choudoufuPlanSet: `live-plan-set -json` over one live root, as a
|
|
20
|
+
* one-root set (#3459). Its `changeSet` is the root's part, so an Op can
|
|
21
|
+
* plan several clouds' roots and hand the parts to `composeChangeSet`
|
|
22
|
+
* (#4483).
|
|
19
23
|
* - terraformPinRollout: one run of a pin-bump rollout, opening at most one
|
|
20
24
|
* wave's pull request (#3189). It loads the HCL parser on first call.
|
|
21
25
|
*
|
|
@@ -34,6 +38,8 @@ export {
|
|
|
34
38
|
choudoufuLiveLs,
|
|
35
39
|
choudoufuLiveCheck,
|
|
36
40
|
choudoufuAdopt,
|
|
41
|
+
choudoufuPlanSet,
|
|
42
|
+
choudoufuPlanSetCommand,
|
|
37
43
|
terraformInitCommand,
|
|
38
44
|
terraformPlanCommand,
|
|
39
45
|
terraformApplyCommand,
|
|
@@ -52,6 +58,7 @@ export {
|
|
|
52
58
|
DEFAULT_TERRAFORM_BINARY,
|
|
53
59
|
DEFAULT_LIVE_PLAN_DOCUMENT_FILE,
|
|
54
60
|
MIN_CHOUDOUFU_VERSION,
|
|
61
|
+
MIN_CHOUDOUFU_ESTATES_VERSION,
|
|
55
62
|
classifyApprovalRefusal,
|
|
56
63
|
CHOUDOUFU_LIVE_PLAN_OUT_REFUSAL,
|
|
57
64
|
CHOUDOUFU_APPROVAL_EXIT_CODE,
|
|
@@ -78,6 +85,8 @@ export type {
|
|
|
78
85
|
ChoudoufuLiveLsResult,
|
|
79
86
|
ChoudoufuLiveCheckResult,
|
|
80
87
|
ChoudoufuAdoptResult,
|
|
88
|
+
ChoudoufuPlanSet,
|
|
89
|
+
ChoudoufuPlanSetResult,
|
|
81
90
|
AdoptionRefusal,
|
|
82
91
|
PlanChangeCounts,
|
|
83
92
|
LivePlanUnownedCounts,
|