@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,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A state where it is, opened for its lock and for what a run keeps beside
|
|
3
|
+
* it. Each backend locks its own way:
|
|
4
|
+
*
|
|
5
|
+
* s3 the lock file `use_lockfile` takes, `<key>.tflock`, written only
|
|
6
|
+
* when absent
|
|
7
|
+
* gcs the lock object `<prefix>/<workspace>.tflock`, written only when
|
|
8
|
+
* absent; the ID `force-unlock` takes is its generation
|
|
9
|
+
* azurerm a lease on the state blob, with the lock info base64 in its
|
|
10
|
+
* `terraformlockid` metadata; the binary cannot write the blob
|
|
11
|
+
* while chant holds the lease, so the store writes it
|
|
12
|
+
* local a lock file `<path>.tflock` beside the state, created only when
|
|
13
|
+
* absent. It keeps another chant run out; the binary's own local
|
|
14
|
+
* lock is a file lock it takes per command, so the run writes
|
|
15
|
+
* with `-lock=false` while it holds this one. A bare
|
|
16
|
+
* `terraform apply` on the same checkout at the same time is not
|
|
17
|
+
* kept out. Ruled on #4481 (ruling 4, 2026-10-25): chant's own
|
|
18
|
+
* lock file, no Terraform lock-info file, local backends allowed.
|
|
19
|
+
*
|
|
20
|
+
* Lifted from terragucci's `backend.ts` (c493e0e): `StateStore`, `s3Store`,
|
|
21
|
+
* `gcsStore` and `azureStore`, plus a local store and `removeBeside`.
|
|
22
|
+
*/
|
|
23
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
24
|
+
import { dirname, isAbsolute, join } from "node:path";
|
|
25
|
+
import { AzureBlobClient, type AzureTarget } from "./azure-blob";
|
|
26
|
+
import type { AzureState, GcsState, LocalState, LockableState, S3State } from "./backend";
|
|
27
|
+
import { GcsClient } from "./gcs";
|
|
28
|
+
import { StoreError, type StoreFetch } from "./object-store";
|
|
29
|
+
import { S3Client } from "./s3";
|
|
30
|
+
|
|
31
|
+
/** The lock a state's backend holds: the ID `force-unlock` takes, and the lock info the binary (or a run) wrote. */
|
|
32
|
+
export interface LockHolder {
|
|
33
|
+
id: string;
|
|
34
|
+
/** The lock info as written, JSON. */
|
|
35
|
+
info: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A state where it is, for what locks it and keeps beside it. */
|
|
39
|
+
export interface StateStore {
|
|
40
|
+
readonly backend: LockableState["backend"];
|
|
41
|
+
/** Where the state is. */
|
|
42
|
+
readonly location: string;
|
|
43
|
+
/** Where its lock is: the lock object, or the blob whose lease is the lock. */
|
|
44
|
+
readonly lockLocation: string;
|
|
45
|
+
/** The lock the backend holds now, or undefined. */
|
|
46
|
+
heldLock(): Promise<LockHolder | undefined>;
|
|
47
|
+
/** Take the state's lock with `info`, the way the backend takes it: false when it is held. */
|
|
48
|
+
lock(info: string): Promise<boolean>;
|
|
49
|
+
/** Release the lock `lock` took, or the one `adopt` took over. */
|
|
50
|
+
unlock(): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Write a new state under the lock, for a backend whose lock keeps the
|
|
53
|
+
* binary from writing (azurerm's lease); undefined for a backend the
|
|
54
|
+
* binary writes with `-lock=false` while the run holds the lock.
|
|
55
|
+
*/
|
|
56
|
+
write?: (body: string) => Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* The object beside the state whose key is the state's with `suffix` after
|
|
59
|
+
* it, in the same bucket, container or directory, under the state's own
|
|
60
|
+
* access controls: read (undefined when there is none), written, removed.
|
|
61
|
+
*/
|
|
62
|
+
readBeside(suffix: string): Promise<string | undefined>;
|
|
63
|
+
writeBeside(suffix: string, body: string): Promise<void>;
|
|
64
|
+
removeBeside(suffix: string): Promise<void>;
|
|
65
|
+
/** Where the object beside the state with `suffix` is, for messages. */
|
|
66
|
+
besideLocation(suffix: string): string;
|
|
67
|
+
/**
|
|
68
|
+
* Take over a lock this store finds held, which an earlier run of the same
|
|
69
|
+
* operation left held on purpose: `unlock` then releases it. A lease
|
|
70
|
+
* (azurerm) is taken over by its ID, the others need nothing.
|
|
71
|
+
*/
|
|
72
|
+
adopt(held: LockHolder): void;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The ID in a lock info's JSON, or "" when it names none. */
|
|
76
|
+
export function lockId(text: string): string {
|
|
77
|
+
try {
|
|
78
|
+
const v = JSON.parse(text) as { ID?: unknown };
|
|
79
|
+
return typeof v.ID === "string" ? v.ID : "";
|
|
80
|
+
} catch {
|
|
81
|
+
return "";
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const JSON_TYPE = "application/json";
|
|
86
|
+
|
|
87
|
+
/** The S3 state, locked by the lock file `use_lockfile` takes. */
|
|
88
|
+
function s3Store(o: S3State, fetchFn?: StoreFetch): StateStore {
|
|
89
|
+
const client = new S3Client(o.target, fetchFn);
|
|
90
|
+
const lockKey = `${o.key}.tflock`;
|
|
91
|
+
return {
|
|
92
|
+
backend: "s3",
|
|
93
|
+
location: `s3://${o.bucket}/${o.key}`,
|
|
94
|
+
lockLocation: `s3://${o.bucket}/${lockKey}`,
|
|
95
|
+
heldLock: async () => {
|
|
96
|
+
const text = await client.get(lockKey);
|
|
97
|
+
return text === undefined ? undefined : { id: lockId(text), info: text };
|
|
98
|
+
},
|
|
99
|
+
lock: (info) => client.putIfAbsent(lockKey, info, JSON_TYPE),
|
|
100
|
+
unlock: () => client.remove(lockKey),
|
|
101
|
+
readBeside: (suffix) => client.get(`${o.key}${suffix}`),
|
|
102
|
+
writeBeside: (suffix, body) => client.put(`${o.key}${suffix}`, body, JSON_TYPE),
|
|
103
|
+
removeBeside: (suffix) => client.remove(`${o.key}${suffix}`),
|
|
104
|
+
besideLocation: (suffix) => `s3://${o.bucket}/${o.key}${suffix}`,
|
|
105
|
+
adopt: () => {},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The GCS state, locked by `<prefix>/<workspace>.tflock`, whose generation is the lock's ID. */
|
|
110
|
+
function gcsStore(o: GcsState, fetchFn?: StoreFetch): StateStore {
|
|
111
|
+
const client = new GcsClient(o.target, fetchFn);
|
|
112
|
+
return {
|
|
113
|
+
backend: "gcs",
|
|
114
|
+
location: `gs://${o.bucket}/${o.key}`,
|
|
115
|
+
lockLocation: `gs://${o.bucket}/${o.lock}`,
|
|
116
|
+
heldLock: async () => {
|
|
117
|
+
const head = await client.head(o.lock);
|
|
118
|
+
const info = head.exists ? await client.get(o.lock) : undefined;
|
|
119
|
+
if (!head.exists || info === undefined) return undefined;
|
|
120
|
+
return { id: head.generation ?? lockId(info), info };
|
|
121
|
+
},
|
|
122
|
+
lock: (info) => client.putIfAbsent(o.lock, info, JSON_TYPE),
|
|
123
|
+
unlock: () => client.remove(o.lock),
|
|
124
|
+
readBeside: (suffix) => client.get(`${o.key}${suffix}`),
|
|
125
|
+
writeBeside: (suffix, body) => client.put(`${o.key}${suffix}`, body, JSON_TYPE),
|
|
126
|
+
removeBeside: (suffix) => client.remove(`${o.key}${suffix}`),
|
|
127
|
+
besideLocation: (suffix) => `gs://${o.bucket}/${o.key}${suffix}`,
|
|
128
|
+
adopt: () => {},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** The metadata an azurerm backend keeps a lock's info in, base64 JSON, on the leased blob. */
|
|
133
|
+
export const LOCK_METADATA = "terraformlockid";
|
|
134
|
+
|
|
135
|
+
/** The azurerm state blob, locked by a lease on it with the lock info in its metadata. */
|
|
136
|
+
function azureStore(o: AzureState, fetchFn?: StoreFetch): StateStore {
|
|
137
|
+
const client = new AzureBlobClient({ account: o.account, container: o.container, endpoint: o.endpoint, ...o.auth } as AzureTarget, fetchFn);
|
|
138
|
+
let leaseId: string | undefined;
|
|
139
|
+
const at = `az://${o.account}/${o.container}/${o.key}`;
|
|
140
|
+
return {
|
|
141
|
+
backend: "azurerm",
|
|
142
|
+
location: at,
|
|
143
|
+
lockLocation: at,
|
|
144
|
+
heldLock: async () => {
|
|
145
|
+
const head = await client.head(o.key);
|
|
146
|
+
if (!head.exists || head.leaseState !== "leased") return undefined;
|
|
147
|
+
const meta = head.metadata[LOCK_METADATA];
|
|
148
|
+
const info = meta ? Buffer.from(meta, "base64").toString("utf-8") : "";
|
|
149
|
+
return { id: lockId(info), info };
|
|
150
|
+
},
|
|
151
|
+
lock: async (info) => {
|
|
152
|
+
const id = lockId(info);
|
|
153
|
+
// The backend leases a blob that is there: a state with none yet gets an empty one first, as the backend makes it.
|
|
154
|
+
if (!(await client.head(o.key)).exists) await client.putBlob(o.key, "", JSON_TYPE, { ifNoneMatch: true });
|
|
155
|
+
if (!(await client.lease(o.key, "acquire", id))) return false;
|
|
156
|
+
leaseId = id;
|
|
157
|
+
await client.setMetadata(o.key, { [LOCK_METADATA]: Buffer.from(info).toString("base64") }, id);
|
|
158
|
+
return true;
|
|
159
|
+
},
|
|
160
|
+
unlock: async () => {
|
|
161
|
+
if (!leaseId) return;
|
|
162
|
+
const id = leaseId;
|
|
163
|
+
await client.setMetadata(o.key, {}, id);
|
|
164
|
+
await client.lease(o.key, "release", id);
|
|
165
|
+
leaseId = undefined;
|
|
166
|
+
},
|
|
167
|
+
write: async (body) => {
|
|
168
|
+
if (!leaseId) throw new StoreError(`${at} is written under its lease, and none is held`);
|
|
169
|
+
// As the backend writes it: a snapshot first when it takes snapshots, and the lock info kept on the blob.
|
|
170
|
+
if (o.snapshot && (await client.head(o.key)).exists) await client.snapshot(o.key);
|
|
171
|
+
const meta = (await client.head(o.key)).metadata;
|
|
172
|
+
await client.putBlob(o.key, body, JSON_TYPE, { leaseId, metadata: meta });
|
|
173
|
+
},
|
|
174
|
+
readBeside: (suffix) => client.get(`${o.key}${suffix}`),
|
|
175
|
+
writeBeside: async (suffix, body) => void (await client.putBlob(`${o.key}${suffix}`, body, JSON_TYPE)),
|
|
176
|
+
removeBeside: (suffix) => client.remove(`${o.key}${suffix}`),
|
|
177
|
+
besideLocation: (suffix) => `${at}${suffix}`,
|
|
178
|
+
adopt: (held) => {
|
|
179
|
+
leaseId = held.id;
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** A local state, locked by `<path>.tflock` created only when absent. `dir` is the root the path is relative to. */
|
|
185
|
+
function localStore(o: LocalState, dir: string): StateStore {
|
|
186
|
+
const file = isAbsolute(o.path) ? o.path : join(dir, o.path);
|
|
187
|
+
const lockFile = `${file}.tflock`;
|
|
188
|
+
const read = (f: string): string | undefined => (existsSync(f) ? readFileSync(f, "utf-8") : undefined);
|
|
189
|
+
return {
|
|
190
|
+
backend: "local",
|
|
191
|
+
location: file,
|
|
192
|
+
lockLocation: lockFile,
|
|
193
|
+
heldLock: async () => {
|
|
194
|
+
const text = read(lockFile);
|
|
195
|
+
return text === undefined ? undefined : { id: lockId(text), info: text };
|
|
196
|
+
},
|
|
197
|
+
lock: async (info) => {
|
|
198
|
+
mkdirSync(dirname(lockFile), { recursive: true });
|
|
199
|
+
try {
|
|
200
|
+
writeFileSync(lockFile, info, { flag: "wx" });
|
|
201
|
+
return true;
|
|
202
|
+
} catch (e) {
|
|
203
|
+
if ((e as NodeJS.ErrnoException).code === "EEXIST") return false;
|
|
204
|
+
throw e;
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
unlock: async () => rmSync(lockFile, { force: true }),
|
|
208
|
+
readBeside: async (suffix) => read(`${file}${suffix}`),
|
|
209
|
+
writeBeside: async (suffix, body) => {
|
|
210
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
211
|
+
writeFileSync(`${file}${suffix}`, body);
|
|
212
|
+
},
|
|
213
|
+
removeBeside: async (suffix) => rmSync(`${file}${suffix}`, { force: true }),
|
|
214
|
+
besideLocation: (suffix) => `${file}${suffix}`,
|
|
215
|
+
adopt: () => {},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** The store a state is in, opened with the credentials stateObject chose. `dir` is the root a local path is relative to. */
|
|
220
|
+
export function stateStore(o: LockableState, dir: string, fetchFn?: StoreFetch): StateStore {
|
|
221
|
+
if (o.backend === "s3") return s3Store(o, fetchFn);
|
|
222
|
+
if (o.backend === "gcs") return gcsStore(o, fetchFn);
|
|
223
|
+
if (o.backend === "azurerm") return azureStore(o, fetchFn);
|
|
224
|
+
return localStore(o, dir);
|
|
225
|
+
}
|
|
@@ -245,7 +245,8 @@ export function terragruntAffected(input: TerragruntAffectedInput): TerragruntAf
|
|
|
245
245
|
const moduleDirs = terragruntModuleDirs(unit);
|
|
246
246
|
const called = new Map<string, string>();
|
|
247
247
|
for (const m of moduleDirs) {
|
|
248
|
-
|
|
248
|
+
// Terragrunt runs tofu when it is installed, so a module's `.tofu` files count (chant#4457).
|
|
249
|
+
for (const d of rootWatchPaths(input.dir, { dir: m }, "tofu")) if (!moduleDirs.includes(d) && !called.has(d)) called.set(d, m);
|
|
249
250
|
}
|
|
250
251
|
const viaFile = new Map<string, string[]>();
|
|
251
252
|
const viaCall = new Map<string, string[]>();
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
import { describe, expect, it } from "vitest";
|
|
10
10
|
import { describeWorkspaceKindConformance } from "@intentius/chant-test-utils";
|
|
11
|
-
import {
|
|
11
|
+
import { readPackageKinds } from "../../../packages/core/src/workspace/kinds";
|
|
12
12
|
|
|
13
13
|
const main = `terraform {\n required_version = ">= 1.5.0"\n}\n\nresource "null_resource" "x" {}\n`;
|
|
14
14
|
|
|
@@ -26,24 +26,11 @@ describeWorkspaceKindConformance({
|
|
|
26
26
|
{ name: "a root with the estate.chdf.hcl sidecar", kind: "choudoufu", files: { "main.tf": main, "estate.chdf.hcl": "" }, claims: true },
|
|
27
27
|
{ name: "a root with a live block", kind: "choudoufu", files: { "main.tf": main + '\nlive {\n name = "prod"\n}\n' }, claims: true },
|
|
28
28
|
{ name: "a stock root", kind: "choudoufu", files: { "main.tf": main }, claims: false },
|
|
29
|
-
// The pulumi kind (#3179) is a probe and a check of Pulumi.yaml.
|
|
30
|
-
{ name: "a Pulumi program", kind: "pulumi", files: { "Pulumi.yaml": "name: app\nruntime: nodejs\n", "index.ts": "" }, claims: true },
|
|
31
|
-
{ name: "a Pulumi program one level down", kind: "pulumi", files: { "infra/Pulumi.yaml": "name: app\nruntime: nodejs\n" }, claims: false },
|
|
32
|
-
{ name: "a stack file alone", kind: "pulumi", files: { "Pulumi.dev.yaml": "config: {}\n" }, claims: false },
|
|
33
29
|
],
|
|
34
30
|
});
|
|
35
31
|
|
|
36
|
-
describe("pulumi
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
it("names a schema that loads", () => {
|
|
40
|
-
expect(loadMemberFileSchema(memberFile, packageDir)).toBeUndefined();
|
|
41
|
-
});
|
|
42
|
-
it("passes a program with a runtime, named or in an object", () => {
|
|
43
|
-
expect(checkMemberFile(memberFile, "Pulumi.yaml", "name: app\nruntime: nodejs\n")).toEqual([]);
|
|
44
|
-
expect(checkMemberFile(memberFile, "Pulumi.yaml", "name: app\nruntime:\n name: python\n")).toEqual([]);
|
|
45
|
-
});
|
|
46
|
-
it("fails one with no runtime", () => {
|
|
47
|
-
expect(checkMemberFile(memberFile, "Pulumi.yaml", "name: app\n").length).toBeGreaterThan(0);
|
|
32
|
+
describe("the pulumi kind", () => {
|
|
33
|
+
it("is not here: it moved to the pulumi lexicon (#4453)", () => {
|
|
34
|
+
expect(readPackageKinds(join(import.meta.dirname, "..")).kinds.map((k) => k.name)).not.toContain("pulumi");
|
|
48
35
|
});
|
|
49
36
|
});
|
package/workspace-kinds.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$comment": "Member kinds this lexicon supplies to a chant workspace (#2545). chant reads this file as data and never imports the lexicon. Both probes are behold's, narrowed to the member's own directory: a choudoufu estate is a Terraform root with an estate.chdf.hcl sidecar or a live block, and the lexicon's choudoufu mode (#2103) reads it, so the kind lives here. Each kind's graph block is how chant workspace graph reads a member (#2874): a reader project with this lexicon and one root named for the member, with moduleRoot at the workspace root so a member's ../modules calls stay readable. The pulumi kind
|
|
2
|
+
"$comment": "Member kinds this lexicon supplies to a chant workspace (#2545). chant reads this file as data and never imports the lexicon. Both probes are behold's, narrowed to the member's own directory: a choudoufu estate is a Terraform root with an estate.chdf.hcl sidecar or a live block, and the lexicon's choudoufu mode (#2103) reads it, so the kind lives here. Each kind's graph block is how chant workspace graph reads a member (#2874): a reader project with this lexicon and one root named for the member, with moduleRoot at the workspace root so a member's ../modules calls stay readable. The pulumi kind moved to @intentius/chant-lexicon-pulumi (#4453).",
|
|
3
3
|
"schema": 1,
|
|
4
4
|
"kinds": [
|
|
5
5
|
{
|
|
@@ -52,29 +52,6 @@
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
"name": "pulumi",
|
|
58
|
-
"description": "a Pulumi program, with a Pulumi.yaml in its directory",
|
|
59
|
-
"precedence": 310,
|
|
60
|
-
"probe": {
|
|
61
|
-
"anyFile": [
|
|
62
|
-
"Pulumi.yaml"
|
|
63
|
-
]
|
|
64
|
-
},
|
|
65
|
-
"memberFile": {
|
|
66
|
-
"path": "Pulumi.yaml",
|
|
67
|
-
"schema": "schemas/pulumi.schema.json"
|
|
68
|
-
},
|
|
69
|
-
"graph": {
|
|
70
|
-
"lexicon": "terraform",
|
|
71
|
-
"config": {
|
|
72
|
-
"pulumiMember": {
|
|
73
|
-
"dir": "{dir}",
|
|
74
|
-
"label": "{member}"
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
55
|
}
|
|
79
56
|
]
|
|
80
57
|
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { type Declarable } from "@intentius/chant/declarable";
|
|
2
|
-
/** The conventional place of the export, in the member's directory. */
|
|
3
|
-
export declare const PULUMI_EXPORT_FILE = "stack-export.json";
|
|
4
|
-
export interface PulumiMemberConfig {
|
|
5
|
-
/** The member's directory, absolute. */
|
|
6
|
-
dir: string;
|
|
7
|
-
/** Names the entities (`<label>/...`): the member's name. */
|
|
8
|
-
label?: string;
|
|
9
|
-
}
|
|
10
|
-
/** What the read found, carried on the graph's `meta.pulumiMember`. */
|
|
11
|
-
export interface PulumiMemberReport {
|
|
12
|
-
/** The export read, relative to the member's directory. */
|
|
13
|
-
export?: string;
|
|
14
|
-
objects: number;
|
|
15
|
-
/** False when the member has no readable export. */
|
|
16
|
-
exported: boolean;
|
|
17
|
-
/** Why nothing was read; set with `exported: false`. */
|
|
18
|
-
reason?: string;
|
|
19
|
-
}
|
|
20
|
-
export declare function readPulumiMember(opts: {
|
|
21
|
-
member: PulumiMemberConfig;
|
|
22
|
-
}): {
|
|
23
|
-
entities: Map<string, Declarable>;
|
|
24
|
-
warnings: string[];
|
|
25
|
-
report: PulumiMemberReport;
|
|
26
|
-
};
|
|
27
|
-
/** The report of the read in this process, left for `graphMeta` (see the aws member reader). */
|
|
28
|
-
export declare function setLastMemberReport(report: PulumiMemberReport | undefined): void;
|
|
29
|
-
export declare function lastMemberReport(): PulumiMemberReport | undefined;
|
|
30
|
-
//# sourceMappingURL=workspace-member.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"workspace-member.d.ts","sourceRoot":"","sources":["../src/workspace-member.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAqB,KAAK,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAGjF,uEAAuE;AACvE,eAAO,MAAM,kBAAkB,sBAAsB,CAAC;AAEtD,MAAM,WAAW,kBAAkB;IACjC,wCAAwC;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,uEAAuE;AACvE,MAAM,WAAW,kBAAkB;IACjC,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,QAAQ,EAAE,OAAO,CAAC;IAClB,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAiBD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IAAE,MAAM,EAAE,kBAAkB,CAAA;CAAE,GAAG;IACtE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAClC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,kBAAkB,CAAC;CAC5B,CAmDA;AAID,gGAAgG;AAChG,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,kBAAkB,GAAG,SAAS,GAAG,IAAI,CAEhF;AAED,wBAAgB,gBAAgB,IAAI,kBAAkB,GAAG,SAAS,CAEjE"}
|
package/dist/workspace-member.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Reading a pulumi workspace member into the graph (#3179).
|
|
3
|
-
*
|
|
4
|
-
* `chant workspace graph` writes a reader project for a member of kind
|
|
5
|
-
* `pulumi`, with `terraform.pulumiMember` in its config (see
|
|
6
|
-
* `workspace-kinds.json`). The plugin's `buildRoots` hook calls
|
|
7
|
-
* {@link readPulumiMember}, which reads the `pulumi stack export` JSON the
|
|
8
|
-
* member has checked in, at {@link PULUMI_EXPORT_FILE} in its directory. chant
|
|
9
|
-
* never runs the program and never calls `pulumi`: with no export there are no
|
|
10
|
-
* nodes, and `meta.pulumiMember` says `exported: false` and why.
|
|
11
|
-
*
|
|
12
|
-
* Nodes are the export's resources, keyed `<label>/<urn tail>`, where the tail
|
|
13
|
-
* is the URN after its stack and project (`aws:s3/bucket:Bucket::assets`).
|
|
14
|
-
* Provider resources (`pulumi:providers:*`) are left out; the resources that
|
|
15
|
-
* name them are not edged to them. Edges come from each resource's
|
|
16
|
-
* `dependencies` and `parent`, kept only when the target is a node. Inputs and
|
|
17
|
-
* outputs are not copied into the node, since an export can hold secrets.
|
|
18
|
-
*/
|
|
19
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
20
|
-
import { basename, join } from "node:path";
|
|
21
|
-
import { DECLARABLE_MARKER } from "@intentius/chant/declarable";
|
|
22
|
-
/** The conventional place of the export, in the member's directory. */
|
|
23
|
-
export const PULUMI_EXPORT_FILE = "stack-export.json";
|
|
24
|
-
const PROVIDER = /^pulumi:providers:/;
|
|
25
|
-
/** `urn:pulumi:<stack>::<project>::<tail>`: the tail, or the whole URN when it has another shape. */
|
|
26
|
-
function urnTail(urn) {
|
|
27
|
-
const parts = urn.split("::");
|
|
28
|
-
return urn.startsWith("urn:pulumi:") && parts.length >= 4 ? parts.slice(2).join("::") : urn;
|
|
29
|
-
}
|
|
30
|
-
export function readPulumiMember(opts) {
|
|
31
|
-
const { member } = opts;
|
|
32
|
-
const label = member.label ?? basename(member.dir);
|
|
33
|
-
const missing = (reason) => ({
|
|
34
|
-
entities: new Map(),
|
|
35
|
-
warnings: [`pulumi member "${label}" was not read: ${reason}`],
|
|
36
|
-
report: { objects: 0, exported: false, reason },
|
|
37
|
-
});
|
|
38
|
-
const file = join(member.dir, PULUMI_EXPORT_FILE);
|
|
39
|
-
if (!existsSync(file)) {
|
|
40
|
-
return missing(`no ${PULUMI_EXPORT_FILE} in the member; run \`pulumi stack export --file ${PULUMI_EXPORT_FILE}\` and check it in, and chant will read it (chant does not run the program)`);
|
|
41
|
-
}
|
|
42
|
-
let resources;
|
|
43
|
-
try {
|
|
44
|
-
const doc = JSON.parse(readFileSync(file, "utf-8"));
|
|
45
|
-
const list = doc?.deployment?.resources;
|
|
46
|
-
if (list !== undefined && !Array.isArray(list))
|
|
47
|
-
return missing(`${PULUMI_EXPORT_FILE} has a deployment.resources that is not a list`);
|
|
48
|
-
resources = (list ?? []);
|
|
49
|
-
}
|
|
50
|
-
catch (e) {
|
|
51
|
-
return missing(`${PULUMI_EXPORT_FILE} is not readable JSON: ${e.message}`);
|
|
52
|
-
}
|
|
53
|
-
const keyOf = new Map();
|
|
54
|
-
for (const r of resources) {
|
|
55
|
-
if (typeof r?.urn !== "string" || typeof r.type !== "string" || PROVIDER.test(r.type))
|
|
56
|
-
continue;
|
|
57
|
-
keyOf.set(r.urn, `${label}/${urnTail(r.urn)}`);
|
|
58
|
-
}
|
|
59
|
-
const entities = new Map();
|
|
60
|
-
for (const r of resources) {
|
|
61
|
-
const key = typeof r?.urn === "string" ? keyOf.get(r.urn) : undefined;
|
|
62
|
-
if (!key)
|
|
63
|
-
continue;
|
|
64
|
-
const references = new Map();
|
|
65
|
-
const add = (urn, via) => {
|
|
66
|
-
const to = typeof urn === "string" ? keyOf.get(urn) : undefined;
|
|
67
|
-
if (to && to !== key)
|
|
68
|
-
references.set(`${to}|${via}`, { to, viaAttr: via });
|
|
69
|
-
};
|
|
70
|
-
add(r.parent, "parent");
|
|
71
|
-
for (const d of Array.isArray(r.dependencies) ? r.dependencies : [])
|
|
72
|
-
add(d, "dependencies");
|
|
73
|
-
entities.set(key, {
|
|
74
|
-
lexicon: "terraform",
|
|
75
|
-
entityType: r.type,
|
|
76
|
-
kind: "resource",
|
|
77
|
-
props: {},
|
|
78
|
-
references: [...references.values()],
|
|
79
|
-
[DECLARABLE_MARKER]: true,
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
return { entities, warnings: [], report: { export: PULUMI_EXPORT_FILE, objects: entities.size, exported: true } };
|
|
83
|
-
}
|
|
84
|
-
let lastReport;
|
|
85
|
-
/** The report of the read in this process, left for `graphMeta` (see the aws member reader). */
|
|
86
|
-
export function setLastMemberReport(report) {
|
|
87
|
-
lastReport = report;
|
|
88
|
-
}
|
|
89
|
-
export function lastMemberReport() {
|
|
90
|
-
return lastReport;
|
|
91
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"title": "Pulumi.yaml",
|
|
4
|
-
"type": "object",
|
|
5
|
-
"required": [
|
|
6
|
-
"name",
|
|
7
|
-
"runtime"
|
|
8
|
-
],
|
|
9
|
-
"properties": {
|
|
10
|
-
"name": {
|
|
11
|
-
"type": "string",
|
|
12
|
-
"minLength": 1
|
|
13
|
-
},
|
|
14
|
-
"runtime": {
|
|
15
|
-
"anyOf": [
|
|
16
|
-
{
|
|
17
|
-
"type": "string",
|
|
18
|
-
"minLength": 1
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
"type": "object",
|
|
22
|
-
"required": [
|
|
23
|
-
"name"
|
|
24
|
-
],
|
|
25
|
-
"properties": {
|
|
26
|
-
"name": {
|
|
27
|
-
"type": "string"
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
]
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"deployment": {
|
|
4
|
-
"manifest": { "time": "2026-10-20T12:00:00Z", "magic": "x", "version": "v3.100.0" },
|
|
5
|
-
"resources": [
|
|
6
|
-
{ "urn": "urn:pulumi:dev::app::pulumi:pulumi:Stack::app-dev", "custom": false, "type": "pulumi:pulumi:Stack" },
|
|
7
|
-
{ "urn": "urn:pulumi:dev::app::pulumi:providers:aws::default", "custom": true, "id": "p1", "type": "pulumi:providers:aws" },
|
|
8
|
-
{
|
|
9
|
-
"urn": "urn:pulumi:dev::app::aws:s3/bucket:Bucket::assets",
|
|
10
|
-
"custom": true,
|
|
11
|
-
"id": "assets-1",
|
|
12
|
-
"type": "aws:s3/bucket:Bucket",
|
|
13
|
-
"parent": "urn:pulumi:dev::app::pulumi:pulumi:Stack::app-dev",
|
|
14
|
-
"provider": "urn:pulumi:dev::app::pulumi:providers:aws::default::p1",
|
|
15
|
-
"inputs": { "acl": "private" }
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"urn": "urn:pulumi:dev::app::aws:iam/role:Role::web",
|
|
19
|
-
"custom": true,
|
|
20
|
-
"id": "web-1",
|
|
21
|
-
"type": "aws:iam/role:Role",
|
|
22
|
-
"parent": "urn:pulumi:dev::app::pulumi:pulumi:Stack::app-dev"
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
"urn": "urn:pulumi:dev::app::aws:iam/rolePolicy:RolePolicy::webReadsAssets",
|
|
26
|
-
"custom": true,
|
|
27
|
-
"id": "web-1:read",
|
|
28
|
-
"type": "aws:iam/rolePolicy:RolePolicy",
|
|
29
|
-
"parent": "urn:pulumi:dev::app::pulumi:pulumi:Stack::app-dev",
|
|
30
|
-
"dependencies": [
|
|
31
|
-
"urn:pulumi:dev::app::aws:iam/role:Role::web",
|
|
32
|
-
"urn:pulumi:dev::app::aws:s3/bucket:Bucket::assets",
|
|
33
|
-
"urn:pulumi:dev::app::aws:s3/bucket:Bucket::elsewhere"
|
|
34
|
-
],
|
|
35
|
-
"inputs": { "policy": { "4dabf18193072939515568f2c6568e55": "1b47061264138c4ac30d75fd1eb44270", "ciphertext": "secret" } }
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"urn": "urn:pulumi:dev::app::my:mod:Web$aws:lambda/function:Function::handler",
|
|
39
|
-
"custom": true,
|
|
40
|
-
"id": "fn",
|
|
41
|
-
"type": "aws:lambda/function:Function",
|
|
42
|
-
"parent": "urn:pulumi:dev::app::my:mod:Web::web-app",
|
|
43
|
-
"dependencies": ["urn:pulumi:dev::app::aws:iam/role:Role::web"]
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
"urn": "urn:pulumi:dev::app::my:mod:Web::web-app",
|
|
47
|
-
"custom": false,
|
|
48
|
-
"type": "my:mod:Web",
|
|
49
|
-
"parent": "urn:pulumi:dev::app::pulumi:pulumi:Stack::app-dev"
|
|
50
|
-
}
|
|
51
|
-
]
|
|
52
|
-
}
|
|
53
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The pulumi member kind's graph block (#3179): the reader project the kind's
|
|
3
|
-
* graph config describes, run through chant graph over a checked-in
|
|
4
|
-
* `pulumi stack export`. Nothing here runs Pulumi.
|
|
5
|
-
*/
|
|
6
|
-
import { afterEach, describe, expect, test, vi } from "vitest";
|
|
7
|
-
import { dirname, join } from "node:path";
|
|
8
|
-
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
9
|
-
import { tmpdir } from "node:os";
|
|
10
|
-
import type { GraphIR } from "@intentius/chant/graph-ir";
|
|
11
|
-
import { runGraph } from "@intentius/chant/cli/handlers/graph";
|
|
12
|
-
import { readPackageKinds, substituteGraphConfig } from "../../../packages/core/src/workspace/kinds";
|
|
13
|
-
import { terraformPlugin } from "./plugin";
|
|
14
|
-
|
|
15
|
-
const packageDir = join(import.meta.dirname, "..");
|
|
16
|
-
const member = join(import.meta.dirname, "testdata", "pulumi-member");
|
|
17
|
-
|
|
18
|
-
const scratch: string[] = [];
|
|
19
|
-
afterEach(async () => {
|
|
20
|
-
vi.restoreAllMocks();
|
|
21
|
-
await Promise.all(scratch.splice(0).map((d) => rm(d, { recursive: true, force: true })));
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
async function graphOf(dir: string): Promise<{ ir: GraphIR; err: string[]; exit: number }> {
|
|
25
|
-
const graph = readPackageKinds(packageDir).kinds.find((k) => k.name === "pulumi")!.graph!;
|
|
26
|
-
expect(graph.lexicon).toBe("terraform");
|
|
27
|
-
const config = substituteGraphConfig(graph.config, { member: "infra", dir, workspace: dirname(dir) });
|
|
28
|
-
const project = await mkdtemp(join(tmpdir(), "chant-pulumi-member-"));
|
|
29
|
-
scratch.push(project);
|
|
30
|
-
await writeFile(join(project, "chant.config.json"), JSON.stringify({ lexicons: ["terraform"], terraform: config, graph: { lint: "report" } }));
|
|
31
|
-
const out: string[] = [];
|
|
32
|
-
const err: string[] = [];
|
|
33
|
-
vi.spyOn(console, "log").mockImplementation((line: unknown) => void out.push(String(line)));
|
|
34
|
-
vi.spyOn(console, "error").mockImplementation((line: unknown) => void err.push(String(line)));
|
|
35
|
-
const cwd = process.cwd();
|
|
36
|
-
process.chdir(project);
|
|
37
|
-
try {
|
|
38
|
-
const exit = await runGraph({ args: { command: "graph", path: ".", format: "ir" } as never, plugins: [terraformPlugin], serializers: [] });
|
|
39
|
-
return { ir: exit === 0 ? (JSON.parse(out.join("\n")) as GraphIR) : { nodes: [], edges: [], groups: {} }, err, exit };
|
|
40
|
-
} finally {
|
|
41
|
-
process.chdir(cwd);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
describe("pulumi member graph block (#3179)", () => {
|
|
46
|
-
test("the export's resources are nodes; provider resources are left out", async () => {
|
|
47
|
-
const { ir, err, exit } = await graphOf(member);
|
|
48
|
-
expect(exit, err.join("\n")).toBe(0);
|
|
49
|
-
expect(ir.nodes.map((n) => [n.id, n.kind]).sort()).toEqual([
|
|
50
|
-
["infra/aws:iam/role:Role::web", "aws:iam/role:Role"],
|
|
51
|
-
["infra/aws:iam/rolePolicy:RolePolicy::webReadsAssets", "aws:iam/rolePolicy:RolePolicy"],
|
|
52
|
-
["infra/aws:s3/bucket:Bucket::assets", "aws:s3/bucket:Bucket"],
|
|
53
|
-
["infra/my:mod:Web$aws:lambda/function:Function::handler", "aws:lambda/function:Function"],
|
|
54
|
-
["infra/my:mod:Web::web-app", "my:mod:Web"],
|
|
55
|
-
["infra/pulumi:pulumi:Stack::app-dev", "pulumi:pulumi:Stack"],
|
|
56
|
-
]);
|
|
57
|
-
expect(ir.meta?.pulumiMember).toEqual({ export: "stack-export.json", objects: 6, exported: true });
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test("dependencies and parent are edges; a dependency outside the export is not", async () => {
|
|
61
|
-
const { ir } = await graphOf(member);
|
|
62
|
-
const edges = ir.edges.map((e) => `${e.from.slice(6)} -> ${e.to.slice(6)} via ${e.viaAttr}`).sort();
|
|
63
|
-
expect(edges).toEqual([
|
|
64
|
-
"aws:iam/role:Role::web -> pulumi:pulumi:Stack::app-dev via parent",
|
|
65
|
-
"aws:iam/rolePolicy:RolePolicy::webReadsAssets -> aws:iam/role:Role::web via dependencies",
|
|
66
|
-
"aws:iam/rolePolicy:RolePolicy::webReadsAssets -> aws:s3/bucket:Bucket::assets via dependencies",
|
|
67
|
-
"aws:iam/rolePolicy:RolePolicy::webReadsAssets -> pulumi:pulumi:Stack::app-dev via parent",
|
|
68
|
-
"aws:s3/bucket:Bucket::assets -> pulumi:pulumi:Stack::app-dev via parent",
|
|
69
|
-
"my:mod:Web$aws:lambda/function:Function::handler -> aws:iam/role:Role::web via dependencies",
|
|
70
|
-
"my:mod:Web$aws:lambda/function:Function::handler -> my:mod:Web::web-app via parent",
|
|
71
|
-
"my:mod:Web::web-app -> pulumi:pulumi:Stack::app-dev via parent",
|
|
72
|
-
]);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test("inputs are not copied into the graph", async () => {
|
|
76
|
-
const { ir } = await graphOf(member);
|
|
77
|
-
expect(JSON.stringify(ir)).not.toContain("ciphertext");
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test("a program with no export has no nodes, and the member says why", async () => {
|
|
81
|
-
const { ir, err, exit } = await graphOf(join(import.meta.dirname, "testdata", "pulumi-unexported"));
|
|
82
|
-
expect(exit, err.join("\n")).toBe(0);
|
|
83
|
-
expect(ir.nodes).toEqual([]);
|
|
84
|
-
const report = ir.meta?.pulumiMember as { exported: boolean; reason: string };
|
|
85
|
-
expect(report.exported).toBe(false);
|
|
86
|
-
expect(report.reason).toMatch(/pulumi stack export/);
|
|
87
|
-
expect(err.join("\n")).toMatch(/was not read/);
|
|
88
|
-
});
|
|
89
|
-
});
|