@intentius/chant-lexicon-terraform 0.124.46 → 0.124.49
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/dist/config.d.ts +12 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +9 -1
- package/dist/integrity.json +2 -2
- package/dist/manifest.json +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +12 -0
- package/dist/workspace-member.d.ts +30 -0
- package/dist/workspace-member.d.ts.map +1 -0
- package/dist/workspace-member.js +91 -0
- package/package.json +3 -2
- package/schemas/pulumi.schema.json +34 -0
- package/src/config.ts +9 -1
- package/src/plugin.ts +13 -0
- package/src/testdata/pulumi-member/Pulumi.yaml +2 -0
- package/src/testdata/pulumi-member/stack-export.json +53 -0
- package/src/testdata/pulumi-unexported/Pulumi.yaml +2 -0
- package/src/workspace-kinds.test.ts +21 -0
- package/src/workspace-member.test.ts +89 -0
- package/src/workspace-member.ts +126 -0
- package/workspace-kinds.json +24 -1
package/dist/config.d.ts
CHANGED
|
@@ -165,7 +165,7 @@ export declare const terraformConfigSchema: z.ZodObject<{
|
|
|
165
165
|
state: "state";
|
|
166
166
|
}>>;
|
|
167
167
|
/** Named root modules. The name is the entity-key prefix, so keep it stable. */
|
|
168
|
-
roots: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
168
|
+
roots: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
169
169
|
/** Root module directory, relative to the project root (where `chant.config.*` lives). */
|
|
170
170
|
dir: z.ZodString;
|
|
171
171
|
/** Terraform workspace to select for this root. Omitted means `default`. */
|
|
@@ -225,6 +225,17 @@ export declare const terraformConfigSchema: z.ZodObject<{
|
|
|
225
225
|
plan: "plan";
|
|
226
226
|
state: "state";
|
|
227
227
|
}>>;
|
|
228
|
+
}, z.core.$strict>>>;
|
|
229
|
+
/**
|
|
230
|
+
* One pulumi workspace member read into the graph (#3179). `chant workspace
|
|
231
|
+
* graph` writes this into the reader project it makes for a `pulumi` member;
|
|
232
|
+
* it is not meant to be written by hand. `dir` is the member's directory,
|
|
233
|
+
* absolute; `label` names the entities. The graph is the checked-in
|
|
234
|
+
* `stack-export.json` there; the program is never run.
|
|
235
|
+
*/
|
|
236
|
+
pulumiMember: z.ZodOptional<z.ZodObject<{
|
|
237
|
+
dir: z.ZodString;
|
|
238
|
+
label: z.ZodOptional<z.ZodString>;
|
|
228
239
|
}, z.core.$strict>>;
|
|
229
240
|
}, z.core.$strict>;
|
|
230
241
|
export type TerraformRootConfig = z.infer<typeof terraformRootSchema>;
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB;;;;EAA2C,CAAC;AAClF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E;;;;;;;;GAQG;AACH;;;GAGG;AACH,eAAO,MAAM,2BAA2B;;;EAA4B,CAAC;AAErE,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAEhF,eAAO,MAAM,mBAAmB;IAC9B,0FAA0F;;IAE1F,4EAA4E;;IAE5E,0DAA0D;;IAE1D,0DAA0D;;IAE1D;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;;IAEH,+EAA+E;;;;;;IAE/E;;;;;;;;;;;;;;OAcG;;;;;kBAEH,CAAC;AAEH,eAAO,MAAM,qBAAqB;IAChC;;;;;;;OAOG;;;;;;IAEH;;;;;;;OAOG;;;;;;IAEH;;;;;;;;;;;OAWG;;IAEH,6FAA6F;;;;;IAE7F,gFAAgF;;QAxFhF,0FAA0F;;QAE1F,4EAA4E;;QAE5E,0DAA0D;;QAE1D,0DAA0D;;QAE1D;;;;;;;;;;;;;;;;;;;;;;;;WAwBG;;QAEH,+EAA+E;;;;;;QAE/E;;;;;;;;;;;;;;WAcG;;;;;;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB;;;;EAA2C,CAAC;AAClF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E;;;;;;;;GAQG;AACH;;;GAGG;AACH,eAAO,MAAM,2BAA2B;;;EAA4B,CAAC;AAErE,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAEhF,eAAO,MAAM,mBAAmB;IAC9B,0FAA0F;;IAE1F,4EAA4E;;IAE5E,0DAA0D;;IAE1D,0DAA0D;;IAE1D;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;;IAEH,+EAA+E;;;;;;IAE/E;;;;;;;;;;;;;;OAcG;;;;;kBAEH,CAAC;AAEH,eAAO,MAAM,qBAAqB;IAChC;;;;;;;OAOG;;;;;;IAEH;;;;;;;OAOG;;;;;;IAEH;;;;;;;;;;;OAWG;;IAEH,6FAA6F;;;;;IAE7F,gFAAgF;;QAxFhF,0FAA0F;;QAE1F,4EAA4E;;QAE5E,0DAA0D;;QAE1D,0DAA0D;;QAE1D;;;;;;;;;;;;;;;;;;;;;;;;WAwBG;;QAEH,+EAA+E;;;;;;QAE/E;;;;;;;;;;;;;;WAcG;;;;;;IAwCH;;;;;;OAMG;;;;;kBAEH,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AACtE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,OAAO,QAAQ,yBAAyB,CAAC;IACvC,UAAU,WAAW;QACnB,SAAS,CAAC,EAAE,eAAe,CAAC;KAC7B;CACF;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,wBAAwB,GAAG,WAAW,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC"}
|
package/dist/config.js
CHANGED
|
@@ -137,5 +137,13 @@ export const terraformConfigSchema = z.strictObject({
|
|
|
137
137
|
/** The default {@link TerraformRootConfig.observe} for every root that sets none (#4358). */
|
|
138
138
|
observe: terraformObserveDepthSchema.optional(),
|
|
139
139
|
/** Named root modules. The name is the entity-key prefix, so keep it stable. */
|
|
140
|
-
roots: z.record(z.string(), terraformRootSchema),
|
|
140
|
+
roots: z.record(z.string(), terraformRootSchema).default({}),
|
|
141
|
+
/**
|
|
142
|
+
* One pulumi workspace member read into the graph (#3179). `chant workspace
|
|
143
|
+
* graph` writes this into the reader project it makes for a `pulumi` member;
|
|
144
|
+
* it is not meant to be written by hand. `dir` is the member's directory,
|
|
145
|
+
* absolute; `label` names the entities. The graph is the checked-in
|
|
146
|
+
* `stack-export.json` there; the program is never run.
|
|
147
|
+
*/
|
|
148
|
+
pulumiMember: z.strictObject({ dir: z.string().min(1), label: z.string().optional() }).optional(),
|
|
141
149
|
});
|
package/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "79de496d2a488bf4411f970d2901d39dd5637b654e0f6c364960554e4a2b8163",
|
|
5
5
|
"meta.json": "ca3d163bab055381827226140568f3bef7eaac187cebd76878e0b63e9e442356",
|
|
6
6
|
"types/index.d.ts": "022927e6a7b351cc4cf52d5f973f60ccd140e5810e17b32fb8868965aa1db161",
|
|
7
7
|
"rules/plan-before-apply.ts": "7c10245b2c3335253b48ee7d8b075d89f43116bde20313a5d1031b343fef1282",
|
|
@@ -56,5 +56,5 @@
|
|
|
56
56
|
"rules/tf045.ts": "760b1f4f385fbe7a86897efe3078fdeac11bcc9bfb806285d63db50488174b7b",
|
|
57
57
|
"skills/chant-terraform.md": "d9ca7b7c28aed0038ce775a3491150298492eed2858fd3651ed9bfca0e577ae8"
|
|
58
58
|
},
|
|
59
|
-
"composite": "
|
|
59
|
+
"composite": "1e1d62846c6afd833c8031d6d001f3d4deff7867252d399fbda1f319ec6b1bbe"
|
|
60
60
|
}
|
package/dist/manifest.json
CHANGED
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":"AACA,OAAO,KAAK,EAAsB,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAoElF;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,EAAE,aAkS7B,CAAC"}
|
package/dist/plugin.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { lastMemberReport, readPulumiMember, setLastMemberReport } from "./workspace-member.js";
|
|
1
2
|
import { terraformBehaviourKinds } from "./behaviour/kinds.js";
|
|
2
3
|
import { createSkillsLoader } from "@intentius/chant/lexicon-plugin-helpers";
|
|
3
4
|
import { terraformSerializer } from "./serializer.js";
|
|
@@ -155,7 +156,14 @@ export const terraformPlugin = {
|
|
|
155
156
|
* project root, which is what relative `dir` paths resolve against.
|
|
156
157
|
*/
|
|
157
158
|
async buildRoots(ctx) {
|
|
159
|
+
setLastMemberReport(undefined);
|
|
158
160
|
const namespace = ctx.config.terraform;
|
|
161
|
+
// A pulumi workspace member (#3179): its checked-in stack export, never the program.
|
|
162
|
+
if (namespace?.pulumiMember) {
|
|
163
|
+
const read = readPulumiMember({ member: namespace.pulumiMember });
|
|
164
|
+
setLastMemberReport(read.report);
|
|
165
|
+
return { entities: read.entities, warnings: read.warnings };
|
|
166
|
+
}
|
|
159
167
|
const roots = namespace?.roots ?? {};
|
|
160
168
|
if (Object.keys(roots).length === 0)
|
|
161
169
|
return { entities: new Map(), warnings: [] };
|
|
@@ -167,6 +175,10 @@ export const terraformPlugin = {
|
|
|
167
175
|
moduleRoot: namespace?.moduleRoot,
|
|
168
176
|
});
|
|
169
177
|
},
|
|
178
|
+
graphMeta() {
|
|
179
|
+
const report = lastMemberReport();
|
|
180
|
+
return report ? { pulumiMember: report } : undefined;
|
|
181
|
+
},
|
|
170
182
|
/** `chant terraform pin-rollout` (#3189). */
|
|
171
183
|
commands() {
|
|
172
184
|
return terraformCommands;
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-terraform",
|
|
3
|
-
"version": "0.124.
|
|
3
|
+
"version": "0.124.49",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"src/",
|
|
7
7
|
"dist/",
|
|
8
|
+
"schemas/",
|
|
8
9
|
"workspace-kinds.json"
|
|
9
10
|
],
|
|
10
11
|
"exports": {
|
|
@@ -67,7 +68,7 @@
|
|
|
67
68
|
"access": "public"
|
|
68
69
|
},
|
|
69
70
|
"peerDependencies": {
|
|
70
|
-
"@intentius/chant": "^0.124.
|
|
71
|
+
"@intentius/chant": "^0.124.49",
|
|
71
72
|
"zod": "^4.3.6"
|
|
72
73
|
},
|
|
73
74
|
"description": "terraform lexicon for chant, existing Terraform root modules as declarable entities",
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -146,7 +146,15 @@ export const terraformConfigSchema = z.strictObject({
|
|
|
146
146
|
/** The default {@link TerraformRootConfig.observe} for every root that sets none (#4358). */
|
|
147
147
|
observe: terraformObserveDepthSchema.optional(),
|
|
148
148
|
/** Named root modules. The name is the entity-key prefix, so keep it stable. */
|
|
149
|
-
roots: z.record(z.string(), terraformRootSchema),
|
|
149
|
+
roots: z.record(z.string(), terraformRootSchema).default({}),
|
|
150
|
+
/**
|
|
151
|
+
* One pulumi workspace member read into the graph (#3179). `chant workspace
|
|
152
|
+
* graph` writes this into the reader project it makes for a `pulumi` member;
|
|
153
|
+
* it is not meant to be written by hand. `dir` is the member's directory,
|
|
154
|
+
* absolute; `label` names the entities. The graph is the checked-in
|
|
155
|
+
* `stack-export.json` there; the program is never run.
|
|
156
|
+
*/
|
|
157
|
+
pulumiMember: z.strictObject({ dir: z.string().min(1), label: z.string().optional() }).optional(),
|
|
150
158
|
});
|
|
151
159
|
|
|
152
160
|
export type TerraformRootConfig = z.infer<typeof terraformRootSchema>;
|
package/src/plugin.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { lastMemberReport, readPulumiMember, setLastMemberReport } from "./workspace-member";
|
|
1
2
|
import type { AuditEntitiesInput, LexiconPlugin } from "@intentius/chant/lexicon";
|
|
2
3
|
import { terraformBehaviourKinds } from "./behaviour/kinds";
|
|
3
4
|
import type { CompletionContext, HoverContext } from "@intentius/chant/lsp/types";
|
|
@@ -180,7 +181,14 @@ export const terraformPlugin: LexiconPlugin = {
|
|
|
180
181
|
* project root, which is what relative `dir` paths resolve against.
|
|
181
182
|
*/
|
|
182
183
|
async buildRoots(ctx): Promise<{ entities: Map<string, Declarable>; warnings: string[] }> {
|
|
184
|
+
setLastMemberReport(undefined);
|
|
183
185
|
const namespace = (ctx.config as { terraform?: TerraformConfig }).terraform;
|
|
186
|
+
// A pulumi workspace member (#3179): its checked-in stack export, never the program.
|
|
187
|
+
if (namespace?.pulumiMember) {
|
|
188
|
+
const read = readPulumiMember({ member: namespace.pulumiMember });
|
|
189
|
+
setLastMemberReport(read.report);
|
|
190
|
+
return { entities: read.entities, warnings: read.warnings };
|
|
191
|
+
}
|
|
184
192
|
const roots = namespace?.roots ?? {};
|
|
185
193
|
if (Object.keys(roots).length === 0) return { entities: new Map(), warnings: [] };
|
|
186
194
|
return renderTerraformRoots({
|
|
@@ -192,6 +200,11 @@ export const terraformPlugin: LexiconPlugin = {
|
|
|
192
200
|
});
|
|
193
201
|
},
|
|
194
202
|
|
|
203
|
+
graphMeta() {
|
|
204
|
+
const report = lastMemberReport();
|
|
205
|
+
return report ? { pulumiMember: report } : undefined;
|
|
206
|
+
},
|
|
207
|
+
|
|
195
208
|
/** `chant terraform pin-rollout` (#3189). */
|
|
196
209
|
commands() {
|
|
197
210
|
return terraformCommands;
|
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
}
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* sidecar or a live block, which this lexicon's choudoufu mode reads.
|
|
7
7
|
*/
|
|
8
8
|
import { join } from "node:path";
|
|
9
|
+
import { describe, expect, it } from "vitest";
|
|
9
10
|
import { describeWorkspaceKindConformance } from "@intentius/chant-test-utils";
|
|
11
|
+
import { checkMemberFile, loadMemberFileSchema, readPackageKinds } from "../../../packages/core/src/workspace/kinds";
|
|
10
12
|
|
|
11
13
|
const main = `terraform {\n required_version = ">= 1.5.0"\n}\n\nresource "null_resource" "x" {}\n`;
|
|
12
14
|
|
|
@@ -24,5 +26,24 @@ describeWorkspaceKindConformance({
|
|
|
24
26
|
{ name: "a root with the estate.chdf.hcl sidecar", kind: "choudoufu", files: { "main.tf": main, "estate.chdf.hcl": "" }, claims: true },
|
|
25
27
|
{ name: "a root with a live block", kind: "choudoufu", files: { "main.tf": main + '\nlive {\n name = "prod"\n}\n' }, claims: true },
|
|
26
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 },
|
|
27
33
|
],
|
|
28
34
|
});
|
|
35
|
+
|
|
36
|
+
describe("pulumi member file", () => {
|
|
37
|
+
const packageDir = join(import.meta.dirname, "..");
|
|
38
|
+
const memberFile = readPackageKinds(packageDir).kinds.find((k) => k.name === "pulumi")!.memberFile!;
|
|
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);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,126 @@
|
|
|
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, type Declarable } from "@intentius/chant/declarable";
|
|
22
|
+
import type { EntityReference } from "@intentius/chant/graph-ir";
|
|
23
|
+
|
|
24
|
+
/** The conventional place of the export, in the member's directory. */
|
|
25
|
+
export const PULUMI_EXPORT_FILE = "stack-export.json";
|
|
26
|
+
|
|
27
|
+
export interface PulumiMemberConfig {
|
|
28
|
+
/** The member's directory, absolute. */
|
|
29
|
+
dir: string;
|
|
30
|
+
/** Names the entities (`<label>/...`): the member's name. */
|
|
31
|
+
label?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** What the read found, carried on the graph's `meta.pulumiMember`. */
|
|
35
|
+
export interface PulumiMemberReport {
|
|
36
|
+
/** The export read, relative to the member's directory. */
|
|
37
|
+
export?: string;
|
|
38
|
+
objects: number;
|
|
39
|
+
/** False when the member has no readable export. */
|
|
40
|
+
exported: boolean;
|
|
41
|
+
/** Why nothing was read; set with `exported: false`. */
|
|
42
|
+
reason?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface ExportedResource {
|
|
46
|
+
urn?: unknown;
|
|
47
|
+
type?: unknown;
|
|
48
|
+
parent?: unknown;
|
|
49
|
+
dependencies?: unknown;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const PROVIDER = /^pulumi:providers:/;
|
|
53
|
+
|
|
54
|
+
/** `urn:pulumi:<stack>::<project>::<tail>`: the tail, or the whole URN when it has another shape. */
|
|
55
|
+
function urnTail(urn: string): string {
|
|
56
|
+
const parts = urn.split("::");
|
|
57
|
+
return urn.startsWith("urn:pulumi:") && parts.length >= 4 ? parts.slice(2).join("::") : urn;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function readPulumiMember(opts: { member: PulumiMemberConfig }): {
|
|
61
|
+
entities: Map<string, Declarable>;
|
|
62
|
+
warnings: string[];
|
|
63
|
+
report: PulumiMemberReport;
|
|
64
|
+
} {
|
|
65
|
+
const { member } = opts;
|
|
66
|
+
const label = member.label ?? basename(member.dir);
|
|
67
|
+
const missing = (reason: string) => ({
|
|
68
|
+
entities: new Map<string, Declarable>(),
|
|
69
|
+
warnings: [`pulumi member "${label}" was not read: ${reason}`],
|
|
70
|
+
report: { objects: 0, exported: false, reason } as PulumiMemberReport,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const file = join(member.dir, PULUMI_EXPORT_FILE);
|
|
74
|
+
if (!existsSync(file)) {
|
|
75
|
+
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)`);
|
|
76
|
+
}
|
|
77
|
+
let resources: ExportedResource[];
|
|
78
|
+
try {
|
|
79
|
+
const doc = JSON.parse(readFileSync(file, "utf-8")) as { deployment?: { resources?: unknown } };
|
|
80
|
+
const list = doc?.deployment?.resources;
|
|
81
|
+
if (list !== undefined && !Array.isArray(list)) return missing(`${PULUMI_EXPORT_FILE} has a deployment.resources that is not a list`);
|
|
82
|
+
resources = (list ?? []) as ExportedResource[];
|
|
83
|
+
} catch (e) {
|
|
84
|
+
return missing(`${PULUMI_EXPORT_FILE} is not readable JSON: ${(e as Error).message}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const keyOf = new Map<string, string>();
|
|
88
|
+
for (const r of resources) {
|
|
89
|
+
if (typeof r?.urn !== "string" || typeof r.type !== "string" || PROVIDER.test(r.type)) continue;
|
|
90
|
+
keyOf.set(r.urn, `${label}/${urnTail(r.urn)}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const entities = new Map<string, Declarable>();
|
|
94
|
+
for (const r of resources) {
|
|
95
|
+
const key = typeof r?.urn === "string" ? keyOf.get(r.urn) : undefined;
|
|
96
|
+
if (!key) continue;
|
|
97
|
+
const references = new Map<string, EntityReference>();
|
|
98
|
+
const add = (urn: unknown, via: string) => {
|
|
99
|
+
const to = typeof urn === "string" ? keyOf.get(urn) : undefined;
|
|
100
|
+
if (to && to !== key) references.set(`${to}|${via}`, { to, viaAttr: via });
|
|
101
|
+
};
|
|
102
|
+
add(r.parent, "parent");
|
|
103
|
+
for (const d of Array.isArray(r.dependencies) ? r.dependencies : []) add(d, "dependencies");
|
|
104
|
+
entities.set(key, {
|
|
105
|
+
lexicon: "terraform",
|
|
106
|
+
entityType: r.type as string,
|
|
107
|
+
kind: "resource",
|
|
108
|
+
props: {},
|
|
109
|
+
references: [...references.values()],
|
|
110
|
+
[DECLARABLE_MARKER]: true,
|
|
111
|
+
} as unknown as Declarable);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return { entities, warnings: [], report: { export: PULUMI_EXPORT_FILE, objects: entities.size, exported: true } };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let lastReport: PulumiMemberReport | undefined;
|
|
118
|
+
|
|
119
|
+
/** The report of the read in this process, left for `graphMeta` (see the aws member reader). */
|
|
120
|
+
export function setLastMemberReport(report: PulumiMemberReport | undefined): void {
|
|
121
|
+
lastReport = report;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function lastMemberReport(): PulumiMemberReport | undefined {
|
|
125
|
+
return lastReport;
|
|
126
|
+
}
|
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.",
|
|
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 (#3179) is here because no lexicon knows Pulumi and this one already supplies the other infrastructure-as-code engines' kinds; its graph block reads the pulumi stack export checked in beside Pulumi.yaml as stack-export.json (src/workspace-member.ts); chant never runs the program, and with no export the member has no nodes and meta.pulumiMember says why.",
|
|
3
3
|
"schema": 1,
|
|
4
4
|
"kinds": [
|
|
5
5
|
{
|
|
@@ -52,6 +52,29 @@
|
|
|
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
|
+
}
|
|
55
78
|
}
|
|
56
79
|
]
|
|
57
80
|
}
|