@intentius/chant-lexicon-terraform 0.124.52 → 0.124.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -6
- package/dist/__fixtures__/pr-loop/estate.component.d.ts +7 -0
- package/dist/__fixtures__/pr-loop/estate.component.d.ts.map +1 -0
- package/dist/__fixtures__/pr-loop/estate.component.js +7 -0
- package/dist/__fixtures__/report/generate.d.ts +26 -0
- package/dist/__fixtures__/report/generate.d.ts.map +1 -0
- package/dist/__fixtures__/report/generate.js +58 -0
- package/dist/applied-state.d.ts +27 -0
- package/dist/applied-state.d.ts.map +1 -0
- package/dist/applied-state.js +44 -0
- package/dist/backend-config.d.ts +53 -0
- package/dist/backend-config.d.ts.map +1 -0
- package/dist/backend-config.js +136 -0
- package/dist/behaviour/kinds.d.ts.map +1 -1
- package/dist/behaviour/kinds.js +4 -0
- package/dist/behaviour/predict.d.ts.map +1 -1
- package/dist/behaviour/predict.js +6 -2
- package/dist/behaviour/reference-catalog.d.ts +16 -5
- package/dist/behaviour/reference-catalog.d.ts.map +1 -1
- package/dist/behaviour/reference-catalog.js +16 -5
- package/dist/behaviour/request.d.ts.map +1 -1
- package/dist/behaviour/request.js +3 -2
- package/dist/changed-roots.d.ts +6 -4
- package/dist/changed-roots.d.ts.map +1 -1
- package/dist/changed-roots.js +14 -18
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +4 -3
- package/dist/components/capability-plugin.d.ts +65 -3
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/capability-plugin.js +197 -13
- package/dist/components/compose-group.d.ts +145 -0
- package/dist/components/compose-group.d.ts.map +1 -0
- package/dist/components/compose-group.js +407 -0
- package/dist/components/placeholder-preview.d.ts +78 -0
- package/dist/components/placeholder-preview.d.ts.map +1 -0
- package/dist/components/placeholder-preview.js +254 -0
- package/dist/compose/apply-events.d.ts +46 -0
- package/dist/compose/apply-events.d.ts.map +1 -0
- package/dist/compose/apply-events.js +77 -0
- package/dist/compose/estate.d.ts +194 -0
- package/dist/compose/estate.d.ts.map +1 -0
- package/dist/compose/estate.js +363 -0
- package/dist/compose/hcl.d.ts +99 -0
- package/dist/compose/hcl.d.ts.map +1 -0
- package/dist/compose/hcl.js +302 -0
- package/dist/compose/module-address.d.ts +24 -0
- package/dist/compose/module-address.d.ts.map +1 -0
- package/dist/compose/module-address.js +43 -0
- package/dist/compose/root.d.ts +127 -0
- package/dist/compose/root.d.ts.map +1 -0
- package/dist/compose/root.js +586 -0
- package/dist/compose/run.d.ts +183 -0
- package/dist/compose/run.d.ts.map +1 -0
- package/dist/compose/run.js +447 -0
- package/dist/compose/slice.d.ts +102 -0
- package/dist/compose/slice.d.ts.map +1 -0
- package/dist/compose/slice.js +249 -0
- package/dist/compose/state-address.d.ts +28 -0
- package/dist/compose/state-address.d.ts.map +1 -0
- package/dist/compose/state-address.js +154 -0
- package/dist/compose/state-merge.d.ts +52 -0
- package/dist/compose/state-merge.d.ts.map +1 -0
- package/dist/compose/state-merge.js +135 -0
- package/dist/compose.d.ts +179 -0
- package/dist/compose.d.ts.map +1 -0
- package/dist/compose.js +482 -0
- package/dist/composites/terraform-apply-op.d.ts.map +1 -1
- package/dist/composites/terraform-apply-op.js +7 -3
- package/dist/config.d.ts +59 -13
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +43 -9
- package/dist/describe-resources.d.ts +74 -12
- package/dist/describe-resources.d.ts.map +1 -1
- package/dist/describe-resources.js +374 -66
- package/dist/drift-read.d.ts +33 -0
- package/dist/drift-read.d.ts.map +1 -0
- package/dist/drift-read.js +243 -0
- package/dist/drift-record.d.ts +147 -0
- package/dist/drift-record.d.ts.map +1 -0
- package/dist/drift-record.js +383 -0
- package/dist/hcl/edges.d.ts.map +1 -1
- package/dist/hcl/edges.js +24 -1
- package/dist/hcl/files.d.ts +61 -0
- package/dist/hcl/files.d.ts.map +1 -0
- package/dist/hcl/files.js +221 -0
- package/dist/hcl/instances.d.ts +82 -0
- package/dist/hcl/instances.d.ts.map +1 -0
- package/dist/hcl/instances.js +390 -0
- package/dist/hcl/parse.d.ts +81 -9
- package/dist/hcl/parse.d.ts.map +1 -1
- package/dist/hcl/parse.js +114 -16
- package/dist/hcl/roots.d.ts.map +1 -1
- package/dist/hcl/roots.js +5 -0
- package/dist/hcl/suppressions.d.ts.map +1 -1
- package/dist/hcl/suppressions.js +8 -2
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -2
- package/dist/integrity.json +3 -3
- package/dist/live-plan-reasons.d.ts +21 -0
- package/dist/live-plan-reasons.d.ts.map +1 -0
- package/dist/live-plan-reasons.js +47 -0
- package/dist/lsp/option-keys.d.ts.map +1 -1
- package/dist/lsp/option-keys.js +2 -0
- package/dist/manifest.json +1 -1
- package/dist/op/activities/index.d.ts +6 -2
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/index.js +5 -1
- package/dist/op/activities/live-detect.d.ts +4 -3
- package/dist/op/activities/live-detect.d.ts.map +1 -1
- package/dist/op/activities/live-detect.js +57 -32
- package/dist/op/activities/pin-rollout.d.ts +2 -0
- package/dist/op/activities/pin-rollout.d.ts.map +1 -1
- package/dist/op/activities/pin-rollout.js +1 -0
- package/dist/op/activities/terraform.d.ts +153 -5
- package/dist/op/activities/terraform.d.ts.map +1 -1
- package/dist/op/activities/terraform.js +205 -20
- package/dist/op/activity-contracts.d.ts +27 -0
- package/dist/op/activity-contracts.d.ts.map +1 -1
- package/dist/op/activity-contracts.js +19 -1
- package/dist/op/builders.d.ts +7 -1
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/builders.js +9 -0
- package/dist/pin/edit.d.ts.map +1 -1
- package/dist/pin/edit.js +35 -0
- package/dist/pin/rollout.d.ts +2 -0
- package/dist/pin/rollout.d.ts.map +1 -1
- package/dist/pin/rollout.js +14 -11
- package/dist/plan-shape.d.ts +30 -0
- package/dist/plan-shape.d.ts.map +1 -0
- package/dist/plan-shape.js +125 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +32 -18
- package/dist/report.d.ts +15 -6
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +28 -55
- package/dist/skills/chant-terraform.md +1 -1
- package/dist/state/azure-blob.d.ts +68 -0
- package/dist/state/azure-blob.d.ts.map +1 -0
- package/dist/state/azure-blob.js +201 -0
- package/dist/state/backend.d.ts +158 -0
- package/dist/state/backend.d.ts.map +1 -0
- package/dist/state/backend.js +406 -0
- package/dist/state/exec.d.ts +39 -0
- package/dist/state/exec.d.ts.map +1 -0
- package/dist/state/exec.js +80 -0
- package/dist/state/gcs.d.ts +62 -0
- package/dist/state/gcs.d.ts.map +1 -0
- package/dist/state/gcs.js +224 -0
- package/dist/state/index.d.ts +15 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +14 -0
- package/dist/state/locks.d.ts +105 -0
- package/dist/state/locks.d.ts.map +1 -0
- package/dist/state/locks.js +130 -0
- package/dist/state/object-store.d.ts +37 -0
- package/dist/state/object-store.d.ts.map +1 -0
- package/dist/state/object-store.js +20 -0
- package/dist/state/root-states.d.ts +131 -0
- package/dist/state/root-states.d.ts.map +1 -0
- package/dist/state/root-states.js +241 -0
- package/dist/state/s3.d.ts +68 -0
- package/dist/state/s3.d.ts.map +1 -0
- package/dist/state/s3.js +170 -0
- package/dist/state/store.d.ts +51 -0
- package/dist/state/store.d.ts.map +1 -0
- package/dist/state/store.js +185 -0
- package/dist/terragrunt/affected.d.ts.map +1 -1
- package/dist/terragrunt/affected.js +2 -1
- package/package.json +7 -3
- package/src/__fixtures__/ACCEPTANCE.md +69 -7
- package/src/__fixtures__/compose/apply-failed-in-app.jsonl +12 -0
- package/src/__fixtures__/compose/state-app.json +66 -0
- package/src/__fixtures__/compose/state-edge.json +40 -0
- package/src/__fixtures__/compose/state-net.json +58 -0
- package/src/__fixtures__/compose/three-app.plan.json +184 -0
- package/src/__fixtures__/compose/three-composed.plan.json +509 -0
- package/src/__fixtures__/compose/three-edge.plan.json +144 -0
- package/src/__fixtures__/compose/three-net.plan.json +174 -0
- package/src/__fixtures__/compose/unknown-composed.plan.json +228 -0
- package/src/__fixtures__/compose/working-after-apply.json +145 -0
- package/src/__fixtures__/config-blocks/app/main.tf +61 -0
- package/src/__fixtures__/config-blocks/app/modules/network/main.tf +4 -0
- package/src/__fixtures__/config-blocks/app-json/main.tf.json +39 -0
- package/src/__fixtures__/config-files/shadow/main.tf +3 -0
- package/src/__fixtures__/config-files/shadow/main.tofu +3 -0
- package/src/__fixtures__/config-files/shadow/other.tf +3 -0
- package/src/__fixtures__/config-files/tf/main.tf +11 -0
- package/src/__fixtures__/config-files/tf-json/main.tf.json +18 -0
- package/src/__fixtures__/config-files/tofu/main.tofu +11 -0
- package/src/__fixtures__/config-files/tofu-json/main.tofu.json +18 -0
- package/src/__fixtures__/config-files/tofu-only/main.tofu +3 -0
- package/src/__fixtures__/drift-record/README.md +11 -0
- package/src/__fixtures__/drift-record/live-plan-sections.json +27 -0
- package/src/__fixtures__/instance-forms/app/main.tf +110 -0
- package/src/__fixtures__/instance-forms/app/modules/svc/main.tf +3 -0
- package/src/__fixtures__/instance-vars/app/main.tf +52 -0
- package/src/__fixtures__/instance-vars/app/modules/app/main.tf +12 -0
- package/src/__fixtures__/instance-vars/app/modules/edge/main.tf +3 -0
- package/src/__fixtures__/instance-vars/app/terraform.tfvars +1 -0
- package/src/__fixtures__/live-module-keyed/live-graph.json +259 -0
- package/src/__fixtures__/live-module-keyed/live-plan.json +87 -0
- package/src/__fixtures__/live-module-keyed/root/estate.chdf.hcl +1 -0
- package/src/__fixtures__/live-module-keyed/root/main.tf +21 -0
- package/src/__fixtures__/live-module-keyed/root/modules/app/main.tf +17 -0
- package/src/__fixtures__/live-module-keyed/root/modules/edge/main.tf +7 -0
- package/src/__fixtures__/live-module-keyed/root/versions.tf +30 -0
- package/src/__fixtures__/live-module-keyed/show-state.json +146 -0
- package/src/__fixtures__/pr-loop/README.md +50 -0
- package/src/__fixtures__/pr-loop/chant.config.json +13 -0
- package/src/__fixtures__/pr-loop/estate.component.ts +10 -0
- package/src/__fixtures__/pr-loop/roots/a/main.tf +11 -0
- package/src/__fixtures__/pr-loop/roots/app/main.tf +19 -0
- package/src/__fixtures__/pr-loop/roots/b/main.tf +7 -0
- package/src/__fixtures__/pr-loop/roots/dns/main.tf +3 -0
- package/src/__fixtures__/pr-loop/roots/net/main.tf +7 -0
- package/src/__fixtures__/report/applied.show.json +51 -0
- package/src/__fixtures__/report/generate.ts +63 -0
- package/src/__fixtures__/report/plan.show.json +200 -0
- package/src/__fixtures__/report/tf-apply/change-set.json +170 -0
- package/src/__fixtures__/report/tf-apply/report.json +338 -0
- package/src/__fixtures__/report/tf-plan/change-set.json +170 -0
- package/src/__fixtures__/report/tf-plan/report.json +320 -0
- package/src/__fixtures__/three-cloud-azure.graph.json +290 -0
- package/src/applied-state.ts +52 -0
- package/src/backend-config.test.ts +105 -0
- package/src/backend-config.ts +133 -0
- package/src/behaviour/kinds.ts +4 -0
- package/src/behaviour/predict.ts +5 -2
- package/src/behaviour/reference-catalog.plugin.test.ts +65 -0
- package/src/behaviour/reference-catalog.ts +16 -5
- package/src/behaviour/request.ts +3 -2
- package/src/change-set-instances.test.ts +98 -0
- package/src/changed-roots.ts +14 -16
- package/src/commands.ts +4 -3
- package/src/components/capability-plugin.test.ts +172 -5
- package/src/components/capability-plugin.ts +212 -11
- package/src/components/change-set-graph.e2e.test.ts +11 -5
- package/src/components/compose-group.test.ts +486 -0
- package/src/components/compose-group.ts +489 -0
- package/src/components/demo-composed-roots.e2e.test.ts +440 -0
- package/src/components/member-outputs.test.ts +76 -0
- package/src/components/placeholder-preview.test.ts +147 -0
- package/src/components/placeholder-preview.ts +257 -0
- package/src/components/pr-loop-compose.tofu.e2e.test.ts +363 -0
- package/src/components/pr-loop.tofu.test.ts +112 -56
- package/src/compose/apply-events.test.ts +62 -0
- package/src/compose/apply-events.ts +100 -0
- package/src/compose/estate.choudoufu.e2e.test.ts +112 -0
- package/src/compose/estate.test.ts +421 -0
- package/src/compose/estate.ts +443 -0
- package/src/compose/hcl.ts +311 -0
- package/src/compose/module-address.ts +49 -0
- package/src/compose/root.test.ts +416 -0
- package/src/compose/root.ts +592 -0
- package/src/compose/run.test.ts +440 -0
- package/src/compose/run.tofu.test.ts +176 -0
- package/src/compose/run.ts +527 -0
- package/src/compose/slice.test.ts +184 -0
- package/src/compose/slice.ts +290 -0
- package/src/compose/state-address.ts +157 -0
- package/src/compose/state-merge.test.ts +131 -0
- package/src/compose/state-merge.ts +148 -0
- package/src/compose.test.ts +248 -0
- package/src/compose.ts +627 -0
- package/src/composites/terraform-apply-op.test.ts +9 -0
- package/src/composites/terraform-apply-op.ts +7 -3
- package/src/config-file-listers.test.ts +265 -0
- package/src/config.ts +44 -9
- package/src/describe-resources.engine-facts.test.ts +412 -0
- package/src/describe-resources.live-module-keyed.test.ts +290 -0
- package/src/describe-resources.ts +416 -70
- package/src/drift-read.ts +284 -0
- package/src/drift-record.test.ts +223 -0
- package/src/drift-record.ts +455 -0
- package/src/hcl/config-blocks.test.ts +174 -0
- package/src/hcl/edges.ts +21 -1
- package/src/hcl/files.ts +234 -0
- package/src/hcl/instance-forms.test.ts +94 -0
- package/src/hcl/instances.test.ts +137 -0
- package/src/hcl/instances.ts +422 -0
- package/src/hcl/parse.test.ts +139 -1
- package/src/hcl/parse.ts +149 -16
- package/src/hcl/roots.ts +6 -0
- package/src/hcl/suppressions.ts +8 -2
- package/src/index.ts +76 -0
- package/src/live-plan-reasons.ts +54 -0
- package/src/lsp/completions.test.ts +1 -1
- package/src/lsp/option-keys.ts +2 -0
- package/src/op/activities/choudoufu-plan-set-op.test.ts +111 -0
- package/src/op/activities/index.ts +9 -0
- package/src/op/activities/live-detect.ts +55 -26
- package/src/op/activities/pin-rollout.ts +3 -0
- package/src/op/activities/registry.test.ts +2 -1
- package/src/op/activities/terraform.test.ts +55 -0
- package/src/op/activities/terraform.ts +266 -22
- package/src/op/activity-contracts.test.ts +1 -0
- package/src/op/activity-contracts.ts +24 -1
- package/src/op/builders.ts +14 -0
- package/src/pin/bundle.test.ts +2 -0
- package/src/pin/edit.ts +30 -0
- package/src/pin/rollout.ts +16 -11
- package/src/plan-shape.test.ts +148 -0
- package/src/plan-shape.ts +128 -0
- package/src/plugin.ts +33 -19
- package/src/report.test.ts +82 -1
- package/src/report.ts +31 -54
- package/src/signal-only-root.test.ts +72 -0
- package/src/skills/chant-terraform.md +1 -1
- package/src/state/azure-blob.ts +223 -0
- package/src/state/backend-verdict.test.ts +187 -0
- package/src/state/backend.test.ts +127 -0
- package/src/state/backend.ts +488 -0
- package/src/state/exec.ts +111 -0
- package/src/state/gcs.ts +254 -0
- package/src/state/index.ts +15 -0
- package/src/state/locks.test.ts +84 -0
- package/src/state/locks.ts +201 -0
- package/src/state/object-store.ts +35 -0
- package/src/state/root-states-floci.e2e.test.ts +66 -0
- package/src/state/root-states.test.ts +462 -0
- package/src/state/root-states.ts +309 -0
- package/src/state/s3.ts +193 -0
- package/src/state/store.ts +225 -0
- package/src/terragrunt/affected.ts +2 -1
- package/src/workspace-kinds.test.ts +4 -17
- package/workspace-kinds.json +1 -24
- package/dist/workspace-member.d.ts +0 -30
- package/dist/workspace-member.d.ts.map +0 -1
- package/dist/workspace-member.js +0 -91
- package/schemas/pulumi.schema.json +0 -34
- package/src/testdata/pulumi-member/Pulumi.yaml +0 -2
- package/src/testdata/pulumi-member/stack-export.json +0 -53
- package/src/testdata/pulumi-unexported/Pulumi.yaml +0 -2
- package/src/workspace-member.test.ts +0 -89
- package/src/workspace-member.ts +0 -126
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The plan shape of a terraform-family root (#4436): what `terraform show
|
|
3
|
+
* -json` says a plan does, without the values, in the form core's
|
|
4
|
+
* `coveredRound` compares (#4419). `terraform-apply`'s `plan` returns it, and
|
|
5
|
+
* terragucci calls it for the plan JSON of a note (terragucci#1268).
|
|
6
|
+
*
|
|
7
|
+
* Like `./change-set`, this module imports only `@intentius/chant` subpaths,
|
|
8
|
+
* never the lexicon's entry point.
|
|
9
|
+
*/
|
|
10
|
+
import type { PlanShape } from "@intentius/chant/components/covered-round";
|
|
11
|
+
export interface TerraformPlanShapeOptions {
|
|
12
|
+
/** The inputs the plan was made without, as `<component>.<output>`. */
|
|
13
|
+
unknownInputs?: string[];
|
|
14
|
+
/** The inputs the plan read with a value, by reference. */
|
|
15
|
+
knownInputs?: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The shape of `planJson` (`terraform show -json`, from terraform, tofu or
|
|
19
|
+
* choudoufu) for `member`.
|
|
20
|
+
*
|
|
21
|
+
* One entry per `resource_changes` item that is not `no-op` or `read`.
|
|
22
|
+
* `logicalId` is the address, with `#<deposed>` for a deposed object. `type`
|
|
23
|
+
* is the resource type. `properties` are the top-level attributes that differ
|
|
24
|
+
* between `before` and `after` (on a create, those the plan sets or leaves
|
|
25
|
+
* unknown), and `unknown` the ones `after_unknown` marks, so `unknown` is
|
|
26
|
+
* within `properties`. A delete has no properties. Values never enter it, so
|
|
27
|
+
* a sensitive attribute is only a name.
|
|
28
|
+
*/
|
|
29
|
+
export declare function terraformPlanShape(planJson: unknown, member: string, options?: TerraformPlanShapeOptions): PlanShape;
|
|
30
|
+
//# sourceMappingURL=plan-shape.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan-shape.d.ts","sourceRoot":"","sources":["../src/plan-shape.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAoC,MAAM,2CAA2C,CAAC;AAuD7G,MAAM,WAAW,yBAAyB;IACxC,uEAAuE;IACvE,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,yBAA8B,GAAG,SAAS,CA2CxH"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The plan shape of a terraform-family root (#4436): what `terraform show
|
|
3
|
+
* -json` says a plan does, without the values, in the form core's
|
|
4
|
+
* `coveredRound` compares (#4419). `terraform-apply`'s `plan` returns it, and
|
|
5
|
+
* terragucci calls it for the plan JSON of a note (terragucci#1268).
|
|
6
|
+
*
|
|
7
|
+
* Like `./change-set`, this module imports only `@intentius/chant` subpaths,
|
|
8
|
+
* never the lexicon's entry point.
|
|
9
|
+
*/
|
|
10
|
+
import { normalizePlanShape } from "@intentius/chant/components/covered-round";
|
|
11
|
+
import { actionFor } from "./change-set.js";
|
|
12
|
+
const isObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
13
|
+
/** Whether an `after_unknown` value marks anything under it unknown. */
|
|
14
|
+
function marks(v) {
|
|
15
|
+
if (v === true)
|
|
16
|
+
return true;
|
|
17
|
+
if (Array.isArray(v))
|
|
18
|
+
return v.some(marks);
|
|
19
|
+
if (isObject(v))
|
|
20
|
+
return Object.values(v).some(marks);
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
function sameJson(a, b) {
|
|
24
|
+
if (a === b)
|
|
25
|
+
return true;
|
|
26
|
+
if (Array.isArray(a) && Array.isArray(b))
|
|
27
|
+
return a.length === b.length && a.every((x, i) => sameJson(x, b[i]));
|
|
28
|
+
if (isObject(a) && isObject(b)) {
|
|
29
|
+
const ka = Object.keys(a);
|
|
30
|
+
return ka.length === Object.keys(b).length && ka.every((k) => k in b && sameJson(a[k], b[k]));
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
/** Whether the value at `path` in an `after_unknown` tree is unknown: `true` at the path or on a prefix of it. */
|
|
35
|
+
function unknownAt(tree, path) {
|
|
36
|
+
let at = tree;
|
|
37
|
+
for (const step of path) {
|
|
38
|
+
if (at === true)
|
|
39
|
+
return true;
|
|
40
|
+
if (Array.isArray(at) && typeof step === "number")
|
|
41
|
+
at = at[step];
|
|
42
|
+
else if (isObject(at) && typeof step === "string")
|
|
43
|
+
at = at[step];
|
|
44
|
+
else
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return marks(at);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* How a change replaces its resource. A `replace_paths` entry names an
|
|
51
|
+
* attribute whose change forces it. When every such attribute is unknown
|
|
52
|
+
* after apply, the plan cannot say the value will differ, so the replacement
|
|
53
|
+
* is `conditional`; one with a known forcing attribute is `true`. A delete
|
|
54
|
+
* and create pair with no `replace_paths` (a tainted object, `-replace`) is
|
|
55
|
+
* `true`. An update with none replaces nothing (`false`); a create or delete
|
|
56
|
+
* has no replacement to state.
|
|
57
|
+
*/
|
|
58
|
+
function replacementFor(change, action) {
|
|
59
|
+
const paths = (Array.isArray(change.replace_paths) ? change.replace_paths : []).filter(Array.isArray);
|
|
60
|
+
if (paths.length > 0)
|
|
61
|
+
return paths.every((p) => unknownAt(change.after_unknown, p)) ? "conditional" : "true";
|
|
62
|
+
if (action === "replace")
|
|
63
|
+
return "true";
|
|
64
|
+
if (action === "update")
|
|
65
|
+
return "false";
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The shape of `planJson` (`terraform show -json`, from terraform, tofu or
|
|
70
|
+
* choudoufu) for `member`.
|
|
71
|
+
*
|
|
72
|
+
* One entry per `resource_changes` item that is not `no-op` or `read`.
|
|
73
|
+
* `logicalId` is the address, with `#<deposed>` for a deposed object. `type`
|
|
74
|
+
* is the resource type. `properties` are the top-level attributes that differ
|
|
75
|
+
* between `before` and `after` (on a create, those the plan sets or leaves
|
|
76
|
+
* unknown), and `unknown` the ones `after_unknown` marks, so `unknown` is
|
|
77
|
+
* within `properties`. A delete has no properties. Values never enter it, so
|
|
78
|
+
* a sensitive attribute is only a name.
|
|
79
|
+
*/
|
|
80
|
+
export function terraformPlanShape(planJson, member, options = {}) {
|
|
81
|
+
const plan = isObject(planJson) ? planJson : {};
|
|
82
|
+
const raw = Array.isArray(plan.resource_changes) ? plan.resource_changes : [];
|
|
83
|
+
const entries = [];
|
|
84
|
+
for (const r of raw) {
|
|
85
|
+
const rc = isObject(r) ? r : {};
|
|
86
|
+
const change = isObject(rc.change) ? rc.change : {};
|
|
87
|
+
const action = actionFor(change.actions);
|
|
88
|
+
if (action === "no-op" || action === "read")
|
|
89
|
+
continue;
|
|
90
|
+
const address = String(rc.address ?? "");
|
|
91
|
+
const before = isObject(change.before) ? change.before : {};
|
|
92
|
+
const after = isObject(change.after) ? change.after : {};
|
|
93
|
+
const afterUnknown = isObject(change.after_unknown) ? change.after_unknown : {};
|
|
94
|
+
const properties = [];
|
|
95
|
+
const unknown = [];
|
|
96
|
+
if (action !== "delete" && action !== "forget") {
|
|
97
|
+
for (const k of new Set([...Object.keys(before), ...Object.keys(after), ...Object.keys(afterUnknown)])) {
|
|
98
|
+
const isUnknown = marks(afterUnknown[k]);
|
|
99
|
+
const differs = action === "create"
|
|
100
|
+
? isUnknown || (after[k] !== undefined && after[k] !== null)
|
|
101
|
+
: isUnknown || !sameJson(before[k] ?? null, after[k] ?? null);
|
|
102
|
+
if (!differs)
|
|
103
|
+
continue;
|
|
104
|
+
properties.push(k);
|
|
105
|
+
if (isUnknown)
|
|
106
|
+
unknown.push(k);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const replacement = replacementFor(change, action);
|
|
110
|
+
entries.push({
|
|
111
|
+
logicalId: typeof rc.deposed === "string" ? `${address}#${rc.deposed}` : address,
|
|
112
|
+
action,
|
|
113
|
+
...(replacement ? { replacement } : {}),
|
|
114
|
+
...(typeof rc.type === "string" ? { type: rc.type } : {}),
|
|
115
|
+
properties,
|
|
116
|
+
unknown,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return normalizePlanShape({
|
|
120
|
+
member,
|
|
121
|
+
entries,
|
|
122
|
+
unknownInputs: options.unknownInputs ?? [],
|
|
123
|
+
...(options.knownInputs ? { knownInputs: options.knownInputs } : {}),
|
|
124
|
+
});
|
|
125
|
+
}
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsB,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAqElF;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,EAAE,aAgT7B,CAAC"}
|
package/dist/plugin.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { lastMemberReport, readPulumiMember, setLastMemberReport } from "./workspace-member.js";
|
|
2
1
|
import { terraformBehaviourKinds } from "./behaviour/kinds.js";
|
|
3
2
|
import { createSkillsLoader } from "@intentius/chant/lexicon-plugin-helpers";
|
|
4
3
|
import { terraformSerializer } from "./serializer.js";
|
|
@@ -8,7 +7,7 @@ import { withCallersChain } from "./lint/post-synth/scope.js";
|
|
|
8
7
|
import { terraformAuditCatalog } from "./lint/audit-catalog.js";
|
|
9
8
|
import { completions } from "./lsp/completions.js";
|
|
10
9
|
import { hover } from "./lsp/hover.js";
|
|
11
|
-
import { terraformConfigSchema } from "./config.js";
|
|
10
|
+
import { ownRoots, terraformConfigSchema } from "./config.js";
|
|
12
11
|
import { renderTerraformRoots } from "./hcl/roots.js";
|
|
13
12
|
import { existsSync } from "node:fs";
|
|
14
13
|
import { join } from "node:path";
|
|
@@ -17,6 +16,7 @@ import { descendModules } from "./hcl/descend.js";
|
|
|
17
16
|
import { TERRAFORM_STATE_OWNERSHIP_KEYS } from "./state-ownership.js";
|
|
18
17
|
import { terraformCommands } from "./commands.js";
|
|
19
18
|
import { terraformCapabilityPack } from "./capabilities/adapter.js";
|
|
19
|
+
import { TERRAFORM_REFERENCE_CATALOG } from "./behaviour/reference-catalog.js";
|
|
20
20
|
const loadSkills = createSkillsLoader(import.meta.url, [
|
|
21
21
|
{
|
|
22
22
|
file: "chant-terraform.md",
|
|
@@ -69,6 +69,8 @@ export const terraformPlugin = {
|
|
|
69
69
|
behaviourKinds: terraformBehaviourKinds,
|
|
70
70
|
name: "terraform",
|
|
71
71
|
capabilityPack: terraformCapabilityPack,
|
|
72
|
+
// Keyed by provider type (#2360); see its module doc for which nodes it reads (#4535).
|
|
73
|
+
referenceCatalog: TERRAFORM_REFERENCE_CATALOG,
|
|
72
74
|
serializer: terraformSerializer,
|
|
73
75
|
configSchema: terraformConfigSchema,
|
|
74
76
|
// ── Required lifecycle methods ────────────────────────────────
|
|
@@ -120,6 +122,17 @@ export const terraformPlugin = {
|
|
|
120
122
|
const { describeResources } = await import("./describe-resources.js");
|
|
121
123
|
return describeResources(options);
|
|
122
124
|
},
|
|
125
|
+
/**
|
|
126
|
+
* Each root's drift, as drift-record stream lines (behold#995): a stock
|
|
127
|
+
* root through its state or a plan, a live root through `live-plan` (and a
|
|
128
|
+
* plan), one resource through `live-plan -resource`. `chant workspace
|
|
129
|
+
* drift` reaches it through `chant lifecycle drift` in a member's reader
|
|
130
|
+
* project. See `./drift-read.ts`.
|
|
131
|
+
*/
|
|
132
|
+
async readDrift(options) {
|
|
133
|
+
const { readTerraformDrift } = await import("./drift-read.js");
|
|
134
|
+
return readTerraformDrift(options);
|
|
135
|
+
},
|
|
123
136
|
/**
|
|
124
137
|
* The prediction, for whichever estate `options.from` names (#2495). The
|
|
125
138
|
* producer is `predictTerraformBehaviour` and this is its second caller, not
|
|
@@ -156,15 +169,9 @@ export const terraformPlugin = {
|
|
|
156
169
|
* project root, which is what relative `dir` paths resolve against.
|
|
157
170
|
*/
|
|
158
171
|
async buildRoots(ctx) {
|
|
159
|
-
setLastMemberReport(undefined);
|
|
160
172
|
const namespace = ctx.config.terraform;
|
|
161
|
-
// A
|
|
162
|
-
|
|
163
|
-
const read = readPulumiMember({ member: namespace.pulumiMember });
|
|
164
|
-
setLastMemberReport(read.report);
|
|
165
|
-
return { entities: read.entities, warnings: read.warnings };
|
|
166
|
-
}
|
|
167
|
-
const roots = namespace?.roots ?? {};
|
|
173
|
+
// A `signalOnly` root (#4484) feeds change detection only, so it builds no entities here.
|
|
174
|
+
const roots = ownRoots(namespace?.roots);
|
|
168
175
|
if (Object.keys(roots).length === 0)
|
|
169
176
|
return { entities: new Map(), warnings: [] };
|
|
170
177
|
return renderTerraformRoots({
|
|
@@ -175,10 +182,6 @@ export const terraformPlugin = {
|
|
|
175
182
|
moduleRoot: namespace?.moduleRoot,
|
|
176
183
|
});
|
|
177
184
|
},
|
|
178
|
-
graphMeta() {
|
|
179
|
-
const report = lastMemberReport();
|
|
180
|
-
return report ? { pulumiMember: report } : undefined;
|
|
181
|
-
},
|
|
182
185
|
/** `chant terraform pin-rollout` (#3189). */
|
|
183
186
|
commands() {
|
|
184
187
|
return terraformCommands;
|
|
@@ -186,14 +189,16 @@ export const terraformPlugin = {
|
|
|
186
189
|
/**
|
|
187
190
|
* The `terraform.roots` a change touched (#3183): a changed file in the
|
|
188
191
|
* root's directory, in a local module it calls, or one of its var files.
|
|
189
|
-
* See `./changed-roots.ts`.
|
|
192
|
+
* See `./changed-roots.ts`. Every root counts, a `signalOnly` one too
|
|
193
|
+
* (#4484): that is what it is declared for.
|
|
190
194
|
*/
|
|
191
195
|
async changedUnits(ctx) {
|
|
192
|
-
const
|
|
196
|
+
const tf = ctx.config.terraform;
|
|
197
|
+
const roots = tf?.roots ?? {};
|
|
193
198
|
if (Object.keys(roots).length === 0)
|
|
194
199
|
return [];
|
|
195
200
|
const { changedRoots } = await import("./changed-roots.js");
|
|
196
|
-
return changedRoots(ctx.projectRoot, roots, ctx.changedFiles);
|
|
201
|
+
return changedRoots(ctx.projectRoot, roots, ctx.changedFiles, tf?.binary);
|
|
197
202
|
},
|
|
198
203
|
lintRules() {
|
|
199
204
|
return rules;
|
|
@@ -263,13 +268,22 @@ export const terraformPlugin = {
|
|
|
263
268
|
// A local audit knows the root's directory, so it can say whether the
|
|
264
269
|
// lock file is beside the `.tf` files (TF040); a fetched one cannot.
|
|
265
270
|
const lockFile = input?.dir !== undefined ? existsSync(join(input.dir, LOCK_FILENAME)) : undefined;
|
|
266
|
-
|
|
271
|
+
// An audit has no `terraform.binary` to follow (chant#4457). A root
|
|
272
|
+
// that carries a `.tofu` file is written for OpenTofu, so it is read
|
|
273
|
+
// as a tofu root; any other is read as terraform, the config default.
|
|
274
|
+
const binary = /^# file: .*\.tofu(\.json)?\s*$/m.test(content) ? "tofu" : undefined;
|
|
275
|
+
const modeOptions = {
|
|
276
|
+
...(binary !== undefined ? { binary } : {}),
|
|
277
|
+
...(lockFile !== undefined ? { lockFile } : {}),
|
|
278
|
+
};
|
|
279
|
+
const entities = await parseTerraformRootContent(content, root, undefined, Object.keys(modeOptions).length > 0 ? modeOptions : undefined);
|
|
267
280
|
if (input?.dir === undefined)
|
|
268
281
|
return entities;
|
|
269
282
|
const { entities: children } = await descendModules(entities, {
|
|
270
283
|
dir: input.dir,
|
|
271
284
|
root,
|
|
272
285
|
projectRoot: input.baseDir ?? input.dir,
|
|
286
|
+
...(binary !== undefined ? { modeOptions: { binary } } : {}),
|
|
273
287
|
});
|
|
274
288
|
for (const [key, entity] of children)
|
|
275
289
|
entities.set(key, entity);
|
package/dist/report.d.ts
CHANGED
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
* chant's `groupChangeSet`, so a report here and one from terragucci agree on
|
|
28
28
|
* what changed.
|
|
29
29
|
*/
|
|
30
|
+
import { type ChangeSetDocument } from "@intentius/chant/change-set";
|
|
31
|
+
export { topModule } from "./compose/module-address.js";
|
|
30
32
|
type Json = Record<string, unknown>;
|
|
31
33
|
export declare const REPORT_SCHEMA = "terragucci.report/v1";
|
|
32
34
|
export declare const INDEX_SCHEMA = "terragucci.report-index/v1";
|
|
@@ -113,8 +115,6 @@ export interface IndexRow {
|
|
|
113
115
|
pull_request_url?: string;
|
|
114
116
|
job_url?: string;
|
|
115
117
|
}
|
|
116
|
-
/** The top-level module an address or module address is in, or undefined for the composed root's own. */
|
|
117
|
-
export declare function topModule(address: string): string | undefined;
|
|
118
118
|
/** The plan's `resource_changes` that belong to module `m` (undefined: the root's own), each with the module taken off its address. */
|
|
119
119
|
export declare function slicePlan(plan: unknown, m: string | undefined): Json;
|
|
120
120
|
/** One `read` of the report schema's `reads`, as a composed run writes it. */
|
|
@@ -127,8 +127,15 @@ export interface ReportRead {
|
|
|
127
127
|
why?: string;
|
|
128
128
|
composed: true;
|
|
129
129
|
}
|
|
130
|
-
/**
|
|
131
|
-
|
|
130
|
+
/**
|
|
131
|
+
* The estate named by the first `live { estate = "..." }` block in a
|
|
132
|
+
* directory's configuration files, in filename order, or undefined. A `live`
|
|
133
|
+
* block takes effect only under choudoufu, so the files are the ones
|
|
134
|
+
* choudoufu reads (`.tf`, `.tf.json`, `.tofu` and `.tofu.json`, chant#4457)
|
|
135
|
+
* unless `binary` names another. A JSON-syntax file's `live` block is the one
|
|
136
|
+
* nested in its `terraform` block (chant#4504).
|
|
137
|
+
*/
|
|
138
|
+
export declare function estateOfDir(dir: string, binary?: string): string | undefined;
|
|
132
139
|
/** Each module's estate, read from `<repo>/<rootDir>/<module>`. A module whose directory has no `live` block is left out. */
|
|
133
140
|
export declare function moduleEstates(repo: string, rootDir: string, modules: readonly string[]): Record<string, string>;
|
|
134
141
|
/**
|
|
@@ -164,6 +171,8 @@ export declare function atmosStackReads(repo: string): ComponentReads;
|
|
|
164
171
|
export declare function componentOutputReads(plan: Json, m: string, componentReads: ComponentReads, modules: readonly string[], rootOf: (m: string | undefined) => string): ReportRead[];
|
|
165
172
|
export interface TerraformReport {
|
|
166
173
|
report: Json;
|
|
174
|
+
/** The change set whose digest `report.change_set` names: what behold reads beside the report. */
|
|
175
|
+
changeSet: ChangeSetDocument;
|
|
167
176
|
/** The run's directory, relative to the project's own index: `yyyy/mm/<commit>/<stage>`. */
|
|
168
177
|
path: string;
|
|
169
178
|
row: IndexRow;
|
|
@@ -188,7 +197,8 @@ export interface ReportStore {
|
|
|
188
197
|
put(key: string, body: string): Promise<void>;
|
|
189
198
|
}
|
|
190
199
|
/**
|
|
191
|
-
* Write the run's `report.json` and
|
|
200
|
+
* Write the run's `report.json` and `change-set.json` (the document whose
|
|
201
|
+
* digest the report names, as `chant change-set` writes it), and add its row to the project's index
|
|
192
202
|
* (`<project>/index.json`, paths relative to it) and to the prefix's
|
|
193
203
|
* (`index.json`, paths starting at the project), the two places terragucci
|
|
194
204
|
* writes. Returns the keys written.
|
|
@@ -203,5 +213,4 @@ export declare function dirStore(dir: string): ReportStore;
|
|
|
203
213
|
* for credentials. A 404 on read is no object.
|
|
204
214
|
*/
|
|
205
215
|
export declare function httpStore(base: string, fetcher?: typeof fetch): ReportStore;
|
|
206
|
-
export {};
|
|
207
216
|
//# sourceMappingURL=report.d.ts.map
|
package/dist/report.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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"}
|
package/dist/report.js
CHANGED
|
@@ -32,6 +32,10 @@ import { dirname, join } from "node:path";
|
|
|
32
32
|
import { changeSetDigest, composeChangeSet } from "@intentius/chant/change-set";
|
|
33
33
|
import { groupChangeSet } from "@intentius/chant/plan-summary";
|
|
34
34
|
import { plannerForBinary, terraformChangeSetPart } from "./change-set.js";
|
|
35
|
+
import { moduleChanges, stripModule, topModule } from "./compose/module-address.js";
|
|
36
|
+
import { stateResources } from "./applied-state.js";
|
|
37
|
+
import { isJsonConfigFile, jsonLiveBlocks, listConfigFiles } from "./hcl/files.js";
|
|
38
|
+
export { topModule } from "./compose/module-address.js";
|
|
35
39
|
const isObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
36
40
|
export const REPORT_SCHEMA = "terragucci.report/v1";
|
|
37
41
|
export const INDEX_SCHEMA = "terragucci.report-index/v1";
|
|
@@ -66,39 +70,14 @@ export function runFactsFromEnv(env, over = {}) {
|
|
|
66
70
|
// ---------------------------------------------------------------------------
|
|
67
71
|
// Slicing the composed plan into one plan per module.
|
|
68
72
|
// ---------------------------------------------------------------------------
|
|
69
|
-
const TOP_MODULE = /^module\.([^.[\]]+)(?:\[(?:"(?:[^"\\]|\\.)*"|\d+)\])?(?:\.|$)/;
|
|
70
|
-
/** The top-level module an address or module address is in, or undefined for the composed root's own. */
|
|
71
|
-
export function topModule(address) {
|
|
72
|
-
return TOP_MODULE.exec(address)?.[1];
|
|
73
|
-
}
|
|
74
|
-
/** `address` with its leading `module.<m>` (and index) taken off. */
|
|
75
|
-
function unprefix(address) {
|
|
76
|
-
const m = TOP_MODULE.exec(address);
|
|
77
|
-
return m ? address.slice(m[0].length) : address;
|
|
78
|
-
}
|
|
79
73
|
/** The plan's `resource_changes` that belong to module `m` (undefined: the root's own), each with the module taken off its address. */
|
|
80
74
|
export function slicePlan(plan, m) {
|
|
81
75
|
const p = isObject(plan) ? plan : {};
|
|
82
|
-
|
|
83
|
-
const mine = list.filter((r) => isObject(r) && topModule(String(r.module_address ?? r.address ?? "")) === m);
|
|
84
|
-
const resource_changes = mine.map((r) => {
|
|
85
|
-
const rc = r;
|
|
86
|
-
const out = { ...rc, address: unprefix(String(rc.address)) };
|
|
87
|
-
if (typeof rc.previous_address === "string")
|
|
88
|
-
out.previous_address = unprefix(rc.previous_address);
|
|
89
|
-
if (typeof rc.module_address === "string") {
|
|
90
|
-
const rest = unprefix(rc.module_address);
|
|
91
|
-
if (rest === "" || rest === rc.module_address)
|
|
92
|
-
delete out.module_address;
|
|
93
|
-
else
|
|
94
|
-
out.module_address = rest;
|
|
95
|
-
}
|
|
96
|
-
return out;
|
|
97
|
-
});
|
|
98
|
-
return { ...(p.errored === true ? { errored: true } : {}), resource_changes };
|
|
76
|
+
return { ...(p.errored === true ? { errored: true } : {}), resource_changes: moduleChanges(p.resource_changes, m) };
|
|
99
77
|
}
|
|
100
78
|
// ---------------------------------------------------------------------------
|
|
101
|
-
// Applied values: the state after the apply gives the values the plan left unknown
|
|
79
|
+
// Applied values: the state after the apply gives the values the plan left unknown,
|
|
80
|
+
// read through `./applied-state`, as the composed estate run reads them.
|
|
102
81
|
// ---------------------------------------------------------------------------
|
|
103
82
|
/** Whether a sensitivity map marks anything under it `true`. */
|
|
104
83
|
function marks(v) {
|
|
@@ -110,27 +89,6 @@ function marks(v) {
|
|
|
110
89
|
return Object.values(v).some(marks);
|
|
111
90
|
return false;
|
|
112
91
|
}
|
|
113
|
-
/** Every resource of a `show -json` state, by address. A deposed object is left out. */
|
|
114
|
-
function stateResources(show) {
|
|
115
|
-
const out = new Map();
|
|
116
|
-
const walk = (mod) => {
|
|
117
|
-
if (!isObject(mod))
|
|
118
|
-
return;
|
|
119
|
-
for (const r of Array.isArray(mod.resources) ? mod.resources : []) {
|
|
120
|
-
if (!isObject(r) || typeof r.address !== "string" || r.deposed_key !== undefined)
|
|
121
|
-
continue;
|
|
122
|
-
out.set(r.address, { values: isObject(r.values) ? r.values : {}, sensitive: isObject(r.sensitive_values) ? r.sensitive_values : {} });
|
|
123
|
-
}
|
|
124
|
-
for (const c of Array.isArray(mod.child_modules) ? mod.child_modules : [])
|
|
125
|
-
walk(c);
|
|
126
|
-
};
|
|
127
|
-
const root = isObject(show) ? show : {};
|
|
128
|
-
// A plan's `prior_state` is the state; a `show -json` of state has `values` itself.
|
|
129
|
-
const state = isObject(root.prior_state) ? root.prior_state : root;
|
|
130
|
-
const values = isObject(state.values) ? state.values : {};
|
|
131
|
-
walk(values.root_module);
|
|
132
|
-
return out;
|
|
133
|
-
}
|
|
134
92
|
/** Each unknown attribute given the value the state holds. One the state lacks stays unknown; a sensitive one stays `sensitive`. */
|
|
135
93
|
function resolveUnknowns(attributes, res) {
|
|
136
94
|
if (!res || !attributes.some((a) => a.unknown))
|
|
@@ -147,16 +105,29 @@ function resolveUnknowns(attributes, res) {
|
|
|
147
105
|
return { ...rest, ...(a.before !== undefined ? { before: a.before } : {}), after: value };
|
|
148
106
|
});
|
|
149
107
|
}
|
|
150
|
-
/**
|
|
151
|
-
|
|
108
|
+
/**
|
|
109
|
+
* The estate named by the first `live { estate = "..." }` block in a
|
|
110
|
+
* directory's configuration files, in filename order, or undefined. A `live`
|
|
111
|
+
* block takes effect only under choudoufu, so the files are the ones
|
|
112
|
+
* choudoufu reads (`.tf`, `.tf.json`, `.tofu` and `.tofu.json`, chant#4457)
|
|
113
|
+
* unless `binary` names another. A JSON-syntax file's `live` block is the one
|
|
114
|
+
* nested in its `terraform` block (chant#4504).
|
|
115
|
+
*/
|
|
116
|
+
export function estateOfDir(dir, binary = "choudoufu") {
|
|
152
117
|
let files;
|
|
153
118
|
try {
|
|
154
|
-
files =
|
|
119
|
+
files = listConfigFiles(dir, binary);
|
|
155
120
|
}
|
|
156
121
|
catch {
|
|
157
122
|
return undefined;
|
|
158
123
|
}
|
|
159
124
|
for (const f of files) {
|
|
125
|
+
if (isJsonConfigFile(f)) {
|
|
126
|
+
const estate = jsonLiveBlocks(readFileSync(join(dir, f), "utf8")).map((b) => b.estate).find((e) => typeof e === "string" && e !== "");
|
|
127
|
+
if (estate)
|
|
128
|
+
return estate;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
160
131
|
const text = readFileSync(join(dir, f), "utf8").replace(/^\s*(#|\/\/).*$/gm, "");
|
|
161
132
|
const live = /\blive\s*\{/.exec(text);
|
|
162
133
|
if (!live)
|
|
@@ -240,7 +211,7 @@ function unknownOutputsOf(plan, u) {
|
|
|
240
211
|
const [type, rname, attr] = data ? [parts[1], parts[2], parts[3]] : [parts[0], parts[1], parts[2]];
|
|
241
212
|
if (!type || !rname)
|
|
242
213
|
continue;
|
|
243
|
-
const unprefixed = (c) =>
|
|
214
|
+
const unprefixed = (c) => stripModule(String(c.address)).replace(/\[[^\]]*\]$/, "");
|
|
244
215
|
for (const c of changes) {
|
|
245
216
|
if (unprefixed(c) !== `${data ? "data." : ""}${type}.${rname}`)
|
|
246
217
|
continue;
|
|
@@ -604,7 +575,7 @@ export function terraformReport(input) {
|
|
|
604
575
|
...(run.pull_request_url ? { pull_request_url: run.pull_request_url } : {}),
|
|
605
576
|
...(run.job_url ? { job_url: run.job_url } : {}),
|
|
606
577
|
};
|
|
607
|
-
return { report, path, row };
|
|
578
|
+
return { report, changeSet: doc, path, row };
|
|
608
579
|
}
|
|
609
580
|
// ---------------------------------------------------------------------------
|
|
610
581
|
// The indexes.
|
|
@@ -632,7 +603,8 @@ export function addToIndex(existing, row) {
|
|
|
632
603
|
return { schema: INDEX_SCHEMA, reports: reports.slice(0, INDEX_ROWS) };
|
|
633
604
|
}
|
|
634
605
|
/**
|
|
635
|
-
* Write the run's `report.json` and
|
|
606
|
+
* Write the run's `report.json` and `change-set.json` (the document whose
|
|
607
|
+
* digest the report names, as `chant change-set` writes it), and add its row to the project's index
|
|
636
608
|
* (`<project>/index.json`, paths relative to it) and to the prefix's
|
|
637
609
|
* (`index.json`, paths starting at the project), the two places terragucci
|
|
638
610
|
* writes. Returns the keys written.
|
|
@@ -645,6 +617,7 @@ export async function writeReport(store, r) {
|
|
|
645
617
|
keys.push(key);
|
|
646
618
|
};
|
|
647
619
|
await put(`${project}/${r.path}/report.json`, r.report);
|
|
620
|
+
await put(`${project}/${r.path}/change-set.json`, r.changeSet);
|
|
648
621
|
for (const [key, path] of [[`${project}/index.json`, r.path], ["index.json", `${project}/${r.path}`]]) {
|
|
649
622
|
await put(key, addToIndex(await store.read(key), { ...r.row, path }));
|
|
650
623
|
}
|
|
@@ -33,7 +33,7 @@ export default {
|
|
|
33
33
|
|
|
34
34
|
## What a build produces
|
|
35
35
|
|
|
36
|
-
Every block of every configured root becomes one entity, keyed `<root>/<address>`: `terraform { }` -> `Terraform::Terraform`, `provider "x" {}` -> `Terraform::Provider`, `resource "x" "y" {}` -> `Terraform::Resource`, `data "x" "y" {}` -> `Terraform::Data`, `module "x" {}` -> `Terraform::Module`, `variable "x" {}` -> `Terraform::Variable`, `output "x" {}` -> `Terraform::Output`, `locals {}` -> `Terraform::Locals`. Each carries `props.address`, `props.body` (the block, verbatim), `props.file` and `props.root`. A root whose `dir` doesn't exist, or whose HCL the parser refuses, warns and contributes no entities — the rest of the build is unaffected.
|
|
36
|
+
Every block of every configured root becomes one entity, keyed `<root>/<address>`: `terraform { }` -> `Terraform::Terraform`, `provider "x" {}` -> `Terraform::Provider`, `resource "x" "y" {}` -> `Terraform::Resource`, `data "x" "y" {}` -> `Terraform::Data`, `module "x" {}` -> `Terraform::Module`, `variable "x" {}` -> `Terraform::Variable`, `output "x" {}` -> `Terraform::Output`, `locals {}` -> `Terraform::Locals`, `moved {}` -> `Terraform::Moved`, `import {}` -> `Terraform::Import`, `removed {}` -> `Terraform::Removed`, `check "x" {}` -> `Terraform::Check`. Each carries `props.address`, `props.body` (the block, verbatim), `props.file` and `props.root`. A root whose `dir` doesn't exist, or whose HCL the parser refuses, warns and contributes no entities — the rest of the build is unaffected.
|
|
37
37
|
|
|
38
38
|
## The one thing to check before anything else
|
|
39
39
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { type StoreFetch } from "./object-store.js";
|
|
2
|
+
/** The REST version every request names. */
|
|
3
|
+
export declare const AZURE_VERSION = "2021-08-06";
|
|
4
|
+
/** Entra ID's public cloud. */
|
|
5
|
+
export declare const AZURE_AUTHORITY = "https://login.microsoftonline.com";
|
|
6
|
+
export interface AzureLocation {
|
|
7
|
+
account: string;
|
|
8
|
+
container: string;
|
|
9
|
+
/** The account's blob endpoint, no trailing slash: https://<account>.blob.core.windows.net, or an emulator's http://host:10000/<account>. */
|
|
10
|
+
endpoint: string;
|
|
11
|
+
}
|
|
12
|
+
/** The job's federated identity: an Entra app or managed identity that trusts the forge's token. */
|
|
13
|
+
export interface AzureOidc {
|
|
14
|
+
tenantId: string;
|
|
15
|
+
clientId: string;
|
|
16
|
+
tokenFile: string;
|
|
17
|
+
/** Entra ID's address, https://host, no path. */
|
|
18
|
+
authority: string;
|
|
19
|
+
}
|
|
20
|
+
export type AzureTarget = AzureLocation & ({
|
|
21
|
+
accountKey: string;
|
|
22
|
+
} | {
|
|
23
|
+
oidc: AzureOidc;
|
|
24
|
+
} | {
|
|
25
|
+
sas: string;
|
|
26
|
+
});
|
|
27
|
+
/** A blob's properties, as a HEAD answers them: never its body. */
|
|
28
|
+
export interface BlobHead {
|
|
29
|
+
exists: boolean;
|
|
30
|
+
etag?: string;
|
|
31
|
+
/** x-ms-lease-state: `leased` while a lease holds it. */
|
|
32
|
+
leaseState?: string;
|
|
33
|
+
/** Its metadata, by name without the x-ms-meta- prefix. */
|
|
34
|
+
metadata: Record<string, string>;
|
|
35
|
+
}
|
|
36
|
+
/** The Shared Key Authorization header for a request. */
|
|
37
|
+
export declare function sharedKey(account: string, accountKey: string, method: string, rawUrl: string, headers: Record<string, string>): string;
|
|
38
|
+
export declare class AzureBlobClient {
|
|
39
|
+
readonly target: AzureTarget;
|
|
40
|
+
private readonly fetchFn;
|
|
41
|
+
private readonly readToken;
|
|
42
|
+
private token?;
|
|
43
|
+
constructor(target: AzureTarget, fetchFn?: StoreFetch, readToken?: (file: string) => string);
|
|
44
|
+
private where;
|
|
45
|
+
private blobUrl;
|
|
46
|
+
/** The storage token for the OIDC identity: once per client, again a minute before it expires. */
|
|
47
|
+
private bearer;
|
|
48
|
+
/** The headers for one request, signed or with the bearer token. */
|
|
49
|
+
private headers;
|
|
50
|
+
get(key: string): Promise<string | undefined>;
|
|
51
|
+
/** A blob's properties and metadata, from a HEAD. */
|
|
52
|
+
head(key: string): Promise<BlobHead>;
|
|
53
|
+
/** Take a snapshot of a blob: its id, the time Azure gives it. */
|
|
54
|
+
snapshot(key: string): Promise<string>;
|
|
55
|
+
/** Write a block blob with metadata, under a lease when one holds it; with `ifNoneMatch`, only when there is none (false when one was there). */
|
|
56
|
+
putBlob(key: string, body: string, contentType: string, options?: {
|
|
57
|
+
leaseId?: string;
|
|
58
|
+
metadata?: Record<string, string>;
|
|
59
|
+
ifNoneMatch?: boolean;
|
|
60
|
+
}): Promise<boolean>;
|
|
61
|
+
/** Delete a blob. One that is not there is deleted already. */
|
|
62
|
+
remove(key: string): Promise<void>;
|
|
63
|
+
/** A blob lease: acquire one that never expires with the id given (false when one holds it), or release it. */
|
|
64
|
+
lease(key: string, action: "acquire" | "release", id: string): Promise<boolean>;
|
|
65
|
+
/** Set a blob's metadata, all of it, under its lease when one holds it. */
|
|
66
|
+
setMetadata(key: string, metadata: Record<string, string>, leaseId?: string): Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=azure-blob.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"azure-blob.d.ts","sourceRoot":"","sources":["../../src/state/azure-blob.ts"],"names":[],"mappings":"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"}
|