@intentius/chant-lexicon-pulumi 0.124.58 → 0.124.59
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/drift-read.d.ts +9 -2
- package/dist/drift-read.d.ts.map +1 -1
- package/dist/drift-read.js +15 -1
- package/dist/integrity.json +3 -3
- package/dist/lint/post-synth/pul015.d.ts +3 -2
- package/dist/lint/post-synth/pul015.d.ts.map +1 -1
- package/dist/lint/post-synth/pul015.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/okf/index.md +1 -1
- package/dist/okf/rules/PUL015.md +2 -2
- package/dist/rules/pul015.ts +4 -3
- package/package.json +2 -2
- package/src/drift-read.test.ts +73 -1
- package/src/drift-read.ts +24 -3
- package/src/lint/post-synth/post-synth.test.ts +47 -1
- package/src/lint/post-synth/pul015.ts +4 -3
package/dist/drift-read.d.ts
CHANGED
|
@@ -13,8 +13,12 @@
|
|
|
13
13
|
* | plunge | unset | `plunge drift --resource <urn> --stream`, once per resource in the export |
|
|
14
14
|
* | plunge | `<urn>` | the same, for that URN alone |
|
|
15
15
|
*
|
|
16
|
-
* Each read passes `--stack` (#4496): the
|
|
17
|
-
* `pulumi.member.stack`, else the
|
|
16
|
+
* Each read passes `--stack` (#4496): the stack asked for (`chant workspace
|
|
17
|
+
* drift --stack`), else the member's declared `pulumi.member.stack`, else the
|
|
18
|
+
* stack the export's URNs name. A stack asked for is read from that stack's
|
|
19
|
+
* export in the index store alone; with none there the read is refused
|
|
20
|
+
* (`no-drift-read`), where without `--stack` the member reads the store's
|
|
21
|
+
* export, then the checked-in file.
|
|
18
22
|
*
|
|
19
23
|
* The one read is `refresh`. The member's engine is `pulumiMemberEngine`'s:
|
|
20
24
|
* the member config's `engine`, then a plunge backend URL in `Pulumi.yaml`.
|
|
@@ -25,10 +29,13 @@
|
|
|
25
29
|
*/
|
|
26
30
|
import type { ReadDriftOptions, ReadDriftResult } from "@intentius/chant/drift-record";
|
|
27
31
|
import { type PulumiRunner } from "./run.js";
|
|
32
|
+
import type { SnapshotStore } from "@intentius/chant/lifecycle/snapshot-store";
|
|
28
33
|
/** The runners, injectable so the tests run no binary. */
|
|
29
34
|
export interface PulumiDriftRunners {
|
|
30
35
|
run?: PulumiRunner;
|
|
31
36
|
plunge?: PulumiRunner;
|
|
37
|
+
/** The index store the exports are read from. Default: the project's. */
|
|
38
|
+
exportStore?: SnapshotStore | null;
|
|
32
39
|
}
|
|
33
40
|
/** `LexiconPlugin.readDrift` for the pulumi lexicon. */
|
|
34
41
|
export declare function readPulumiDrift(options: ReadDriftOptions, runners?: PulumiDriftRunners): Promise<ReadDriftResult>;
|
package/dist/drift-read.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drift-read.d.ts","sourceRoot":"","sources":["../src/drift-read.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"drift-read.d.ts","sourceRoot":"","sources":["../src/drift-read.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,KAAK,EAA0B,gBAAgB,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAG/G,OAAO,EAA2C,KAAK,YAAY,EAAE,MAAM,OAAO,CAAC;AAEnF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2CAA2C,CAAC;AAE/E,0DAA0D;AAC1D,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,EAAE,YAAY,CAAC;IACnB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,yEAAyE;IACzE,WAAW,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;CACpC;AAED,wDAAwD;AACxD,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,eAAe,CAAC,CAgD3H"}
|
package/dist/drift-read.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { driftRecordLine, driftSettle, driftStart } from "@intentius/chant/drift-record";
|
|
2
2
|
import { pulumiMemberEngine, refreshPulumiMember } from "./run.js";
|
|
3
|
+
import { readStoredPulumiExport } from "./stack-export-snapshot.js";
|
|
3
4
|
/** `LexiconPlugin.readDrift` for the pulumi lexicon. */
|
|
4
5
|
export async function readPulumiDrift(options, runners = {}) {
|
|
5
6
|
const member = options.config.pulumi?.member;
|
|
@@ -9,6 +10,18 @@ export async function readPulumiDrift(options, runners = {}) {
|
|
|
9
10
|
return { refused: { code: "read-unsupported", message: `a Pulumi member answers refresh, not ${options.read}` } };
|
|
10
11
|
}
|
|
11
12
|
const { engine } = pulumiMemberEngine(member.dir, member.engine);
|
|
13
|
+
const stack = options.stack ?? member.stack;
|
|
14
|
+
if (options.stack !== undefined) {
|
|
15
|
+
const stored = await readStoredPulumiExport({ dir: member.dir, stack: options.stack, ...(runners.exportStore !== undefined ? { store: runners.exportStore } : {}) });
|
|
16
|
+
if (!stored) {
|
|
17
|
+
return {
|
|
18
|
+
refused: {
|
|
19
|
+
code: "no-drift-read",
|
|
20
|
+
message: `the index store holds no export for stack ${options.stack} of ${options.member}; the pulumi-up capability writes one after each apply through chant, so apply that stack once, or read without --stack`,
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
12
25
|
let records;
|
|
13
26
|
try {
|
|
14
27
|
records = await refreshPulumiMember({
|
|
@@ -16,7 +29,8 @@ export async function readPulumiDrift(options, runners = {}) {
|
|
|
16
29
|
dir: member.dir,
|
|
17
30
|
observedAt: options.observedAt,
|
|
18
31
|
...(member.engine ? { engine: member.engine } : {}),
|
|
19
|
-
...(
|
|
32
|
+
...(stack ? { stack } : {}),
|
|
33
|
+
...(runners.exportStore !== undefined ? { exportStore: runners.exportStore } : {}),
|
|
20
34
|
...(options.resource !== undefined ? { urns: [options.resource] } : {}),
|
|
21
35
|
...(runners.run ? { run: runners.run } : {}),
|
|
22
36
|
...(runners.plunge ? { plunge: runners.plunge } : {}),
|
package/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "36b210d8802b50b7293b719add906eb945a655ac56fb904660c7c6f210363318",
|
|
5
5
|
"meta.json": "ca3d163bab055381827226140568f3bef7eaac187cebd76878e0b63e9e442356",
|
|
6
6
|
"types/index.d.ts": "883bcdf6a960e21360e79df010ca2087719fffec60fc39541c5ecd32d661e328",
|
|
7
7
|
"rules/member-dir-absolute.ts": "4805d5e247ad544f942e1b2c36fc15a5087ca0aacaf804183a269021bd60e276",
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
"rules/pul012.ts": "136f9764a532f56ce72461641550c9c4cee6af06c30968d8374f1a65a1b810d4",
|
|
21
21
|
"rules/pul013.ts": "a19b397fecc2bda3e5f32c8f339b0befbd8f1148b32e70a3ba4f9370ee103379",
|
|
22
22
|
"rules/pul014.ts": "25dd5ca4cb7f9906c608f4b7f78559efd188215c9a9a2f9f131498a847d3259b",
|
|
23
|
-
"rules/pul015.ts": "
|
|
23
|
+
"rules/pul015.ts": "d6805c3b8bfacf0fc4a08fb466b066d949a8a5d164b7927cebc50111d1807ae9",
|
|
24
24
|
"skills/chant-pulumi.md": "aef93407423daf6d0acc492a4d5bbf002fbae67b4f993ad36f86725686b98d61",
|
|
25
25
|
"skills/chant-pulumi-stack-export.md": "aadd67f5e067de61dfda43d57492bca08bd22429e4baf314d6856a810dc0e8dc",
|
|
26
26
|
"skills/chant-pulumi-plunge.md": "8a7f2f9f1124744dee9645905f141057ca01f5a88e34e1bc3858d69406d7ff3a"
|
|
27
27
|
},
|
|
28
|
-
"composite": "
|
|
28
|
+
"composite": "e0f89c0f3684dc4a84e5ca6d311e2901881c5bbd892bd5cbc8eabbe1e367bd76"
|
|
29
29
|
}
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
* `plunge+file://...` or `plunge://...` runs stock `pulumi` against a
|
|
10
10
|
* backend scheme only plunge opens, and every read fails.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* One direction only, by maintainer ruling (#4559): `engine: "plunge"` with a
|
|
13
|
+
* stock backend is not reported, because plunge opens stock backends and the
|
|
14
|
+
* config may mean it.
|
|
14
15
|
*/
|
|
15
16
|
import type { PostSynthCheck } from "@intentius/chant/lint/post-synth";
|
|
16
17
|
export declare const pul015: PostSynthCheck;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pul015.d.ts","sourceRoot":"","sources":["../../../src/lint/post-synth/pul015.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"pul015.d.ts","sourceRoot":"","sources":["../../../src/lint/post-synth/pul015.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,cAAc,EAAyC,MAAM,kCAAkC,CAAC;AAK9G,eAAO,MAAM,MAAM,EAAE,cAkBpB,CAAC"}
|
|
@@ -3,7 +3,7 @@ import { lastMemberRead } from "../../workspace-member.js";
|
|
|
3
3
|
import { finding } from "./member-read-helpers.js";
|
|
4
4
|
export const pul015 = {
|
|
5
5
|
id: "PUL015",
|
|
6
|
-
description: "pulumi.member.engine says pulumi, but
|
|
6
|
+
description: "pulumi.member.engine says pulumi, but the resolved engine is plunge",
|
|
7
7
|
check(_ctx) {
|
|
8
8
|
const read = lastMemberRead();
|
|
9
9
|
if (read?.engine !== "pulumi")
|
package/dist/manifest.json
CHANGED
package/dist/okf/index.md
CHANGED
|
@@ -18,5 +18,5 @@ okf_version: '0.2'
|
|
|
18
18
|
* [PUL012](/rules/PUL012.md) - The pulumi member's stack export holds resources the stack reads and does not manage (external: true)
|
|
19
19
|
* [PUL013](/rules/PUL013.md) - The pulumi member's stack export has a deployment version other than 3, the one chant reads
|
|
20
20
|
* [PUL014](/rules/PUL014.md) - The pulumi member's stack export reads but holds no resources besides the stack and its providers
|
|
21
|
-
* [PUL015](/rules/PUL015.md) - pulumi.member.engine says pulumi, but
|
|
21
|
+
* [PUL015](/rules/PUL015.md) - pulumi.member.engine says pulumi, but the resolved engine is plunge
|
|
22
22
|
* [PUL101](/rules/PUL101.md) - `pulumi.member.dir` is an absolute path written in source.
|
package/dist/okf/rules/PUL015.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
type: post-synth-check
|
|
3
3
|
title: PUL015
|
|
4
|
-
description: pulumi.member.engine says pulumi, but
|
|
4
|
+
description: pulumi.member.engine says pulumi, but the resolved engine is plunge
|
|
5
5
|
id: PUL015
|
|
6
6
|
severity: error
|
|
7
7
|
category: post-synth
|
|
8
8
|
lexicon: pulumi
|
|
9
9
|
docs: https://intentius.io/chant/lexicons/pulumi/rules/
|
|
10
10
|
---
|
|
11
|
-
pulumi.member.engine says pulumi, but
|
|
11
|
+
pulumi.member.engine says pulumi, but the resolved engine is plunge
|
package/dist/rules/pul015.ts
CHANGED
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
* `plunge+file://...` or `plunge://...` runs stock `pulumi` against a
|
|
10
10
|
* backend scheme only plunge opens, and every read fails.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* One direction only, by maintainer ruling (#4559): `engine: "plunge"` with a
|
|
13
|
+
* stock backend is not reported, because plunge opens stock backends and the
|
|
14
|
+
* config may mean it.
|
|
14
15
|
*/
|
|
15
16
|
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
16
17
|
import { pulumiMemberEngine } from "../../run";
|
|
@@ -19,7 +20,7 @@ import { finding } from "./member-read-helpers";
|
|
|
19
20
|
|
|
20
21
|
export const pul015: PostSynthCheck = {
|
|
21
22
|
id: "PUL015",
|
|
22
|
-
description: "pulumi.member.engine says pulumi, but
|
|
23
|
+
description: "pulumi.member.engine says pulumi, but the resolved engine is plunge",
|
|
23
24
|
|
|
24
25
|
check(_ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
25
26
|
const read = lastMemberRead();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-pulumi",
|
|
3
|
-
"version": "0.124.
|
|
3
|
+
"version": "0.124.59",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"src/",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"access": "public"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"@intentius/chant": "^0.124.
|
|
57
|
+
"@intentius/chant": "^0.124.59",
|
|
58
58
|
"zod": "^4.3.6"
|
|
59
59
|
},
|
|
60
60
|
"description": "pulumi lexicon for chant: a Pulumi program as a workspace member, its preview in the change set and its refresh as drift records",
|
package/src/drift-read.test.ts
CHANGED
|
@@ -9,13 +9,16 @@
|
|
|
9
9
|
* `plunge-member`'s export is a cut of the recorded plunge stack's. No binary
|
|
10
10
|
* runs here.
|
|
11
11
|
*/
|
|
12
|
-
import { readFileSync } from "node:fs";
|
|
12
|
+
import { cpSync, mkdirSync, mkdtempSync, readFileSync } from "node:fs";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
13
14
|
import { join } from "node:path";
|
|
14
15
|
import { describe, expect, test } from "vitest";
|
|
16
|
+
import { createSnapshotStore } from "@intentius/chant/lifecycle/snapshot-store";
|
|
15
17
|
import { foldDriftLines, type ReadDriftOptions } from "@intentius/chant/drift-record";
|
|
16
18
|
import { readPulumiDrift } from "./drift-read";
|
|
17
19
|
import { pulumiPlugin } from "./plugin";
|
|
18
20
|
import type { PulumiRunner } from "./run";
|
|
21
|
+
import { writePulumiExportSnapshot } from "./stack-export-snapshot";
|
|
19
22
|
|
|
20
23
|
const testdata = join(import.meta.dirname, "testdata");
|
|
21
24
|
const text = (...path: string[]) => readFileSync(join(testdata, ...path), "utf-8");
|
|
@@ -104,3 +107,72 @@ describe("readPulumiDrift", () => {
|
|
|
104
107
|
expect(never.calls).toEqual([]);
|
|
105
108
|
});
|
|
106
109
|
});
|
|
110
|
+
|
|
111
|
+
describe("readPulumiDrift with --stack", () => {
|
|
112
|
+
const exportText = text("pulumi-preview", "stack-export.json");
|
|
113
|
+
|
|
114
|
+
/** A member directory with Pulumi.yaml and no checked-in export, and a store holding the stacks named. */
|
|
115
|
+
async function memberWithStore(stacks: string[]) {
|
|
116
|
+
const dir = mkdtempSync(join(tmpdir(), "pulumi-drift-stack-"));
|
|
117
|
+
cpSync(join(testdata, "pulumi-preview", "Pulumi.yaml"), join(dir, "Pulumi.yaml"));
|
|
118
|
+
mkdirSync(join(dir, "node_modules"));
|
|
119
|
+
const store = createSnapshotStore({ store: "local", dir: mkdtempSync(join(tmpdir(), "pulumi-drift-store-")) });
|
|
120
|
+
for (const stack of stacks) {
|
|
121
|
+
const wrote = await writePulumiExportSnapshot({
|
|
122
|
+
dir,
|
|
123
|
+
stack,
|
|
124
|
+
environment: "prod",
|
|
125
|
+
engine: "pulumi",
|
|
126
|
+
run: async () => ({ stdout: exportText }),
|
|
127
|
+
store,
|
|
128
|
+
});
|
|
129
|
+
expect(wrote.written, JSON.stringify(wrote)).toBe(true);
|
|
130
|
+
}
|
|
131
|
+
return { dir, store };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
test("reads the named stack's stored export and passes that stack to every refresh", async () => {
|
|
135
|
+
const { dir, store } = await memberWithStore(["staging", "prod-eu"]);
|
|
136
|
+
const pulumi = fake(() => text("pulumi-refresh", "bucket-edited.json"));
|
|
137
|
+
const answer = await readPulumiDrift(options(dir, { stack: "prod-eu" }), { run: pulumi.run, plunge: never.run, exportStore: store });
|
|
138
|
+
if (!("lines" in answer)) throw new Error(JSON.stringify(answer));
|
|
139
|
+
expect(pulumi.calls.length).toBeGreaterThan(0);
|
|
140
|
+
for (const call of pulumi.calls) expect(call.slice(call.indexOf("--stack"), call.indexOf("--stack") + 2)).toEqual(["--stack", "prod-eu"]);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("beats the member's declared stack", async () => {
|
|
144
|
+
const { dir, store } = await memberWithStore(["staging", "prod-eu"]);
|
|
145
|
+
const pulumi = fake(() => text("pulumi-refresh", "bucket-edited.json"));
|
|
146
|
+
const config = { pulumi: { member: { dir, label: "site", stack: "staging" } } };
|
|
147
|
+
const answer = await readPulumiDrift({ ...options(dir, { stack: "prod-eu", resource: bucket }), config }, { run: pulumi.run, plunge: never.run, exportStore: store });
|
|
148
|
+
expect("lines" in answer).toBe(true);
|
|
149
|
+
expect(pulumi.calls).toEqual([["refresh", "--preview-only", "--json", "--stack", "prod-eu", "--target", bucket]]);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("a store with no export for that stack is an error naming the stack, and nothing runs", async () => {
|
|
153
|
+
const { dir, store } = await memberWithStore(["staging"]);
|
|
154
|
+
const spy = fake(() => text("pulumi-refresh", "bucket-edited.json"));
|
|
155
|
+
const answer = await readPulumiDrift(options(dir, { stack: "prod-eu" }), { run: spy.run, plunge: never.run, exportStore: store });
|
|
156
|
+
expect(answer).toMatchObject({ refused: { code: "no-drift-read" } });
|
|
157
|
+
expect("refused" in answer && answer.refused.message).toMatch(/holds no export for stack prod-eu of site/);
|
|
158
|
+
expect(spy.calls).toEqual([]);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("an empty store is refused too, though a checked-in export sits beside the member", async () => {
|
|
162
|
+
const dir = join(testdata, "pulumi-preview");
|
|
163
|
+
const store = createSnapshotStore({ store: "local", dir: mkdtempSync(join(tmpdir(), "pulumi-drift-store-")) });
|
|
164
|
+
const spy = fake(() => text("pulumi-refresh", "bucket-edited.json"));
|
|
165
|
+
const answer = await readPulumiDrift(options(dir, { stack: "floci" }), { run: spy.run, plunge: never.run, exportStore: store });
|
|
166
|
+
expect(answer).toMatchObject({ refused: { code: "no-drift-read" } });
|
|
167
|
+
expect(spy.calls).toEqual([]);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("without --stack the read is as before: the checked-in export when the store has none", async () => {
|
|
171
|
+
const dir = join(testdata, "pulumi-preview");
|
|
172
|
+
const store = createSnapshotStore({ store: "local", dir: mkdtempSync(join(tmpdir(), "pulumi-drift-store-")) });
|
|
173
|
+
const pulumi = fake(() => text("pulumi-refresh", "bucket-edited.json"));
|
|
174
|
+
const answer = await readPulumiDrift(options(dir, { resource: bucket }), { run: pulumi.run, plunge: never.run, exportStore: store });
|
|
175
|
+
expect("lines" in answer).toBe(true);
|
|
176
|
+
expect(pulumi.calls).toEqual([["refresh", "--preview-only", "--json", "--stack", "floci", "--target", bucket]]);
|
|
177
|
+
});
|
|
178
|
+
});
|
package/src/drift-read.ts
CHANGED
|
@@ -13,8 +13,12 @@
|
|
|
13
13
|
* | plunge | unset | `plunge drift --resource <urn> --stream`, once per resource in the export |
|
|
14
14
|
* | plunge | `<urn>` | the same, for that URN alone |
|
|
15
15
|
*
|
|
16
|
-
* Each read passes `--stack` (#4496): the
|
|
17
|
-
* `pulumi.member.stack`, else the
|
|
16
|
+
* Each read passes `--stack` (#4496): the stack asked for (`chant workspace
|
|
17
|
+
* drift --stack`), else the member's declared `pulumi.member.stack`, else the
|
|
18
|
+
* stack the export's URNs name. A stack asked for is read from that stack's
|
|
19
|
+
* export in the index store alone; with none there the read is refused
|
|
20
|
+
* (`no-drift-read`), where without `--stack` the member reads the store's
|
|
21
|
+
* export, then the checked-in file.
|
|
18
22
|
*
|
|
19
23
|
* The one read is `refresh`. The member's engine is `pulumiMemberEngine`'s:
|
|
20
24
|
* the member config's `engine`, then a plunge backend URL in `Pulumi.yaml`.
|
|
@@ -27,11 +31,15 @@ import type { DriftLine, DriftRecord, ReadDriftOptions, ReadDriftResult } from "
|
|
|
27
31
|
import { driftRecordLine, driftSettle, driftStart } from "@intentius/chant/drift-record";
|
|
28
32
|
import type { PulumiConfig } from "./config";
|
|
29
33
|
import { pulumiMemberEngine, refreshPulumiMember, type PulumiRunner } from "./run";
|
|
34
|
+
import { readStoredPulumiExport } from "./stack-export-snapshot";
|
|
35
|
+
import type { SnapshotStore } from "@intentius/chant/lifecycle/snapshot-store";
|
|
30
36
|
|
|
31
37
|
/** The runners, injectable so the tests run no binary. */
|
|
32
38
|
export interface PulumiDriftRunners {
|
|
33
39
|
run?: PulumiRunner;
|
|
34
40
|
plunge?: PulumiRunner;
|
|
41
|
+
/** The index store the exports are read from. Default: the project's. */
|
|
42
|
+
exportStore?: SnapshotStore | null;
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
/** `LexiconPlugin.readDrift` for the pulumi lexicon. */
|
|
@@ -42,6 +50,18 @@ export async function readPulumiDrift(options: ReadDriftOptions, runners: Pulumi
|
|
|
42
50
|
return { refused: { code: "read-unsupported", message: `a Pulumi member answers refresh, not ${options.read}` } };
|
|
43
51
|
}
|
|
44
52
|
const { engine } = pulumiMemberEngine(member.dir, member.engine);
|
|
53
|
+
const stack = options.stack ?? member.stack;
|
|
54
|
+
if (options.stack !== undefined) {
|
|
55
|
+
const stored = await readStoredPulumiExport({ dir: member.dir, stack: options.stack, ...(runners.exportStore !== undefined ? { store: runners.exportStore } : {}) });
|
|
56
|
+
if (!stored) {
|
|
57
|
+
return {
|
|
58
|
+
refused: {
|
|
59
|
+
code: "no-drift-read",
|
|
60
|
+
message: `the index store holds no export for stack ${options.stack} of ${options.member}; the pulumi-up capability writes one after each apply through chant, so apply that stack once, or read without --stack`,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
45
65
|
let records: DriftRecord[];
|
|
46
66
|
try {
|
|
47
67
|
records = await refreshPulumiMember({
|
|
@@ -49,7 +69,8 @@ export async function readPulumiDrift(options: ReadDriftOptions, runners: Pulumi
|
|
|
49
69
|
dir: member.dir,
|
|
50
70
|
observedAt: options.observedAt,
|
|
51
71
|
...(member.engine ? { engine: member.engine } : {}),
|
|
52
|
-
...(
|
|
72
|
+
...(stack ? { stack } : {}),
|
|
73
|
+
...(runners.exportStore !== undefined ? { exportStore: runners.exportStore } : {}),
|
|
53
74
|
...(options.resource !== undefined ? { urns: [options.resource] } : {}),
|
|
54
75
|
...(runners.run ? { run: runners.run } : {}),
|
|
55
76
|
...(runners.plunge ? { plunge: runners.plunge } : {}),
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
1
3
|
import { join } from "node:path";
|
|
2
|
-
import { afterEach, describe, expect, test } from "vitest";
|
|
4
|
+
import { afterEach, describe, expect, test, vi } from "vitest";
|
|
3
5
|
import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
|
|
4
6
|
import { PULUMI_SECRET_SIG, PULUMI_SIG_KEY } from "../../values";
|
|
5
7
|
import { setLastMemberRead, setLastMemberReport } from "../../workspace-member";
|
|
@@ -23,6 +25,7 @@ import { pul015 } from "./pul015";
|
|
|
23
25
|
const ctx = { outputs: new Map(), entities: new Map() } as unknown as PostSynthContext;
|
|
24
26
|
|
|
25
27
|
afterEach(() => {
|
|
28
|
+
vi.unstubAllEnvs();
|
|
26
29
|
setLastMemberReport(undefined);
|
|
27
30
|
setLastMemberRead(undefined);
|
|
28
31
|
});
|
|
@@ -196,4 +199,47 @@ describe("pulumi post-synth checks", () => {
|
|
|
196
199
|
readWith([], { dir: stockDir, engine: "plunge" });
|
|
197
200
|
expect(pul015.check(ctx)).toEqual([]);
|
|
198
201
|
});
|
|
202
|
+
|
|
203
|
+
test("PUL015 names the source of the resolved plunge engine", () => {
|
|
204
|
+
// Pulumi.yaml
|
|
205
|
+
readWith([], { dir: plungeDir, engine: "pulumi" });
|
|
206
|
+
expect(pul015.check(ctx)[0].message).toContain("Pulumi.yaml names the plunge backend");
|
|
207
|
+
|
|
208
|
+
// PULUMI_BACKEND_URL, on a program whose Pulumi.yaml names no backend
|
|
209
|
+
vi.stubEnv("PULUMI_BACKEND_URL", "plunge+s3://bucket/state");
|
|
210
|
+
readWith([], { dir: stockDir, engine: "pulumi" });
|
|
211
|
+
const fromEnv = pul015.check(ctx);
|
|
212
|
+
expect(fromEnv).toHaveLength(1);
|
|
213
|
+
expect(fromEnv[0].message).toContain("PULUMI_BACKEND_URL names the plunge backend plunge+s3://bucket/state");
|
|
214
|
+
readWith([], { dir: stockDir, engine: "plunge" });
|
|
215
|
+
expect(pul015.check(ctx)).toEqual([]);
|
|
216
|
+
vi.stubEnv("PULUMI_BACKEND_URL", "s3://bucket/state");
|
|
217
|
+
readWith([], { dir: stockDir, engine: "pulumi" });
|
|
218
|
+
expect(pul015.check(ctx)).toEqual([]);
|
|
219
|
+
vi.unstubAllEnvs();
|
|
220
|
+
|
|
221
|
+
// plywood.yml, engine and backend
|
|
222
|
+
const root = mkdtempSync(join(tmpdir(), "pul015-"));
|
|
223
|
+
mkdirSync(join(root, ".git"));
|
|
224
|
+
const member = join(root, "infra");
|
|
225
|
+
mkdirSync(member);
|
|
226
|
+
writeFileSync(join(member, "Pulumi.yaml"), "name: app\nruntime: nodejs\n");
|
|
227
|
+
writeFileSync(join(root, "plywood.yml"), "engine: plunge\n");
|
|
228
|
+
readWith([], { dir: member, engine: "pulumi" });
|
|
229
|
+
const fromEngine = pul015.check(ctx);
|
|
230
|
+
expect(fromEngine).toHaveLength(1);
|
|
231
|
+
expect(fromEngine[0].message).toContain("plywood.yml says engine: plunge");
|
|
232
|
+
writeFileSync(join(root, "plywood.yml"), "backend: plunge+file:///state\n");
|
|
233
|
+
const fromBackend = pul015.check(ctx);
|
|
234
|
+
expect(fromBackend).toHaveLength(1);
|
|
235
|
+
expect(fromBackend[0].message).toContain("plywood.yml names the plunge backend plunge+file:///state");
|
|
236
|
+
writeFileSync(join(root, "plywood.yml"), "engine: pulumi\n");
|
|
237
|
+
expect(pul015.check(ctx)).toEqual([]);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test("PUL015 does not report the other direction: engine plunge on a stock backend", () => {
|
|
241
|
+
vi.stubEnv("PULUMI_BACKEND_URL", "s3://bucket/state");
|
|
242
|
+
readWith([], { dir: stockDir, engine: "plunge" });
|
|
243
|
+
expect(pul015.check(ctx)).toEqual([]);
|
|
244
|
+
});
|
|
199
245
|
});
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
* `plunge+file://...` or `plunge://...` runs stock `pulumi` against a
|
|
10
10
|
* backend scheme only plunge opens, and every read fails.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* One direction only, by maintainer ruling (#4559): `engine: "plunge"` with a
|
|
13
|
+
* stock backend is not reported, because plunge opens stock backends and the
|
|
14
|
+
* config may mean it.
|
|
14
15
|
*/
|
|
15
16
|
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
16
17
|
import { pulumiMemberEngine } from "../../run";
|
|
@@ -19,7 +20,7 @@ import { finding } from "./member-read-helpers";
|
|
|
19
20
|
|
|
20
21
|
export const pul015: PostSynthCheck = {
|
|
21
22
|
id: "PUL015",
|
|
22
|
-
description: "pulumi.member.engine says pulumi, but
|
|
23
|
+
description: "pulumi.member.engine says pulumi, but the resolved engine is plunge",
|
|
23
24
|
|
|
24
25
|
check(_ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
25
26
|
const read = lastMemberRead();
|