@intentius/chant-lexicon-pulumi 0.124.58 → 0.124.60
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/change-set.d.ts +38 -4
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +85 -13
- 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/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -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/pulumi-up.d.ts +2 -0
- package/dist/pulumi-up.d.ts.map +1 -1
- package/dist/pulumi-up.js +5 -1
- package/dist/rules/pul015.ts +4 -3
- package/package.json +2 -2
- package/src/change-set.ts +93 -16
- package/src/drift-read.test.ts +73 -1
- package/src/drift-read.ts +24 -3
- package/src/index.ts +3 -0
- package/src/lint/post-synth/post-synth.test.ts +47 -1
- package/src/lint/post-synth/pul015.ts +4 -3
- package/src/preview-generated.test.ts +113 -0
- package/src/pulumi-up.test.ts +12 -1
- package/src/pulumi-up.ts +5 -1
package/dist/change-set.d.ts
CHANGED
|
@@ -68,7 +68,11 @@
|
|
|
68
68
|
* each step's op, URN, provider, id, inputs before and after, `diffReasons`,
|
|
69
69
|
* `replaceReasons` and `detailedDiff`, sorted by URN and op. It leaves out
|
|
70
70
|
* `config`, `duration`, `diagnostics` and `changeSummary`, and the states'
|
|
71
|
-
* outputs, which at preview are the last deployment's or unknown.
|
|
71
|
+
* outputs, which at preview are the last deployment's or unknown. It also
|
|
72
|
+
* leaves out what the engine makes up while planning a create or a
|
|
73
|
+
* replacement (plywood#56): an autoname such as `events-3f45941` is drawn
|
|
74
|
+
* again by every preview, so it reads as {@link PULUMI_GENERATED}. See
|
|
75
|
+
* {@link pulumiPlannedInputs}. Unknowns already print as one fixed string. For a
|
|
72
76
|
* member on the resource lock grain the `pulumi-up` capability passes
|
|
73
77
|
* `changedOnly` (#4541, ruling B), which leaves `same` and `read` steps out.
|
|
74
78
|
*
|
|
@@ -140,14 +144,44 @@ export interface PulumiPreviewDigestOptions {
|
|
|
140
144
|
*/
|
|
141
145
|
changedOnly?: boolean;
|
|
142
146
|
}
|
|
147
|
+
/** What stands in a digest for an input the engine made up while planning (plywood#56). */
|
|
148
|
+
export declare const PULUMI_GENERATED = "<generated at plan time>";
|
|
149
|
+
/**
|
|
150
|
+
* The inputs a step would write, with the values the engine made up while
|
|
151
|
+
* planning replaced by {@link PULUMI_GENERATED} (plywood#56). Two previews of
|
|
152
|
+
* one create draw different autonames (`events-3f45941`, then
|
|
153
|
+
* `events-9c01d2e`), and a digest over them could never match a later plan of
|
|
154
|
+
* the same change.
|
|
155
|
+
*
|
|
156
|
+
* Only a create or a replacement draws a name: an update's inputs keep the one
|
|
157
|
+
* the resource was created with, and that stays in `before` and `after`. A
|
|
158
|
+
* value counts as generated when the engine lists its key in `__defaults`
|
|
159
|
+
* (the inputs the provider filled in, not the program) and it reads as an
|
|
160
|
+
* autoname of this resource: its logical name, or the start of it when the
|
|
161
|
+
* provider shortened it, then a dash and seven hex characters. An engine that
|
|
162
|
+
* prints no `__defaults` is held to the exact logical name. Every other input
|
|
163
|
+
* stays in, the defaults that are plain values included, so a changed input
|
|
164
|
+
* still changes the digest. `before` is the recorded state and is never masked.
|
|
165
|
+
*/
|
|
166
|
+
export declare function pulumiPlannedInputs(step: PulumiStep): Json | null;
|
|
143
167
|
/**
|
|
144
168
|
* The member's plan digest: `computePlanDigest("pulumi-preview", …)` over
|
|
145
169
|
* what each step proposes, sorted by URN and op, so two renderings of one
|
|
146
|
-
* preview agree. See the module comment for what it leaves out
|
|
147
|
-
*
|
|
148
|
-
* `
|
|
170
|
+
* preview agree. See the module comment for what it leaves out, and
|
|
171
|
+
* {@link pulumiPlannedInputs} for the values the engine made up that it leaves
|
|
172
|
+
* out of a create. With `changedOnly`, `same` and `read` steps are left out
|
|
173
|
+
* too, under the label `pulumi-preview-changed`, so the two digests never
|
|
174
|
+
* collide.
|
|
149
175
|
*/
|
|
150
176
|
export declare function pulumiPreviewDigest(preview: unknown, opts?: PulumiPreviewDigestOptions): string;
|
|
177
|
+
/**
|
|
178
|
+
* The digest of each resource the preview changes, by URN (plywood#55): the
|
|
179
|
+
* steps of that URN (a replace is several), as {@link pulumiPreviewDigest}
|
|
180
|
+
* reads them, with the values the engine made up left out. A `same` or `read` step is not in it whatever the lock grain,
|
|
181
|
+
* so what a pull request left alone never makes its plan differ from a plan
|
|
182
|
+
* made after another change landed on it.
|
|
183
|
+
*/
|
|
184
|
+
export declare function pulumiResourceDigests(preview: unknown): Record<string, string>;
|
|
151
185
|
/** The action and disruption one URN's steps mean, or undefined for an op the change set has no action for. */
|
|
152
186
|
export declare function pulumiStepsAction(steps: PulumiStep[]): {
|
|
153
187
|
action: ChangeSetAction;
|
package/dist/change-set.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"change-set.d.ts","sourceRoot":"","sources":["../src/change-set.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"change-set.d.ts","sourceRoot":"","sources":["../src/change-set.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+FG;AAEH,OAAO,KAAK,EACV,eAAe,EAEf,mBAAmB,EAGnB,aAAa,EACd,MAAM,6BAA6B,CAAC;AAGrC,OAAO,EAA6E,KAAK,IAAI,EAAE,MAAM,UAAU,CAAC;AAEhH,kGAAkG;AAClG,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,IAAI,CAAC;IACd,OAAO,CAAC,EAAE,IAAI,CAAC;CAChB;AAED,qFAAqF;AACrF,MAAM,WAAW,UAAU;IACzB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,GAAG,IAAI,CAAC;CAC9E;AAED,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,UAAU,EAAE,GAAG,SAAS,CAGlE;AAED,+EAA+E;AAC/E,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,0GAA0G;AAC1G,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,sBAAsB,EAAE,CAI5E;AAID,kHAAkH;AAClH,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAA6B,CAAC;AAEnF,MAAM,WAAW,0BAA0B;IACzC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,2FAA2F;AAC3F,eAAO,MAAM,gBAAgB,6BAA6B,CAAC;AAW3D;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,IAAI,CAcjE;AAoBD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,GAAE,0BAA+B,GAAG,MAAM,CAMnG;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS9E;AAID,+GAA+G;AAC/G,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG;IAAE,MAAM,EAAE,eAAe,CAAC;IAAC,UAAU,CAAC,EAAE,mBAAmB,CAAC;IAAC,SAAS,CAAC,EAAE,IAAI,CAAA;CAAE,GAAG,SAAS,CAElJ;AAsFD,MAAM,WAAW,sBAAsB;IACrC,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qGAAqG;IACrG,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+FAA+F;IAC/F,MAAM,CAAC,EAAE,0BAA0B,CAAC;CACrC;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,aAAa,CAyH9E"}
|
package/dist/change-set.js
CHANGED
|
@@ -68,7 +68,11 @@
|
|
|
68
68
|
* each step's op, URN, provider, id, inputs before and after, `diffReasons`,
|
|
69
69
|
* `replaceReasons` and `detailedDiff`, sorted by URN and op. It leaves out
|
|
70
70
|
* `config`, `duration`, `diagnostics` and `changeSummary`, and the states'
|
|
71
|
-
* outputs, which at preview are the last deployment's or unknown.
|
|
71
|
+
* outputs, which at preview are the last deployment's or unknown. It also
|
|
72
|
+
* leaves out what the engine makes up while planning a create or a
|
|
73
|
+
* replacement (plywood#56): an autoname such as `events-3f45941` is drawn
|
|
74
|
+
* again by every preview, so it reads as {@link PULUMI_GENERATED}. See
|
|
75
|
+
* {@link pulumiPlannedInputs}. Unknowns already print as one fixed string. For a
|
|
72
76
|
* member on the resource lock grain the `pulumi-up` capability passes
|
|
73
77
|
* `changedOnly` (#4541, ruling B), which leaves `same` and `read` steps out.
|
|
74
78
|
*
|
|
@@ -108,30 +112,98 @@ export function pulumiExportResources(doc) {
|
|
|
108
112
|
const byString = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
109
113
|
/** Step ops that change nothing: what {@link PulumiPreviewDigestOptions.changedOnly} leaves out of the digest. */
|
|
110
114
|
export const PULUMI_UNCHANGED_OPS = new Set(["same", "read"]);
|
|
115
|
+
/** What stands in a digest for an input the engine made up while planning (plywood#56). */
|
|
116
|
+
export const PULUMI_GENERATED = "<generated at plan time>";
|
|
117
|
+
/** Pulumi's autonaming: the logical name, a dash and seven random hex characters. */
|
|
118
|
+
const AUTONAME = /^(.+)-[0-9a-f]{7}$/;
|
|
119
|
+
/** The step's resource name: the last part of its URN. */
|
|
120
|
+
function logicalName(urn) {
|
|
121
|
+
const at = urn?.lastIndexOf("::") ?? -1;
|
|
122
|
+
return urn && at >= 0 ? urn.slice(at + 2) : (urn ?? "");
|
|
123
|
+
}
|
|
111
124
|
/**
|
|
112
|
-
* The
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* `
|
|
116
|
-
*
|
|
125
|
+
* The inputs a step would write, with the values the engine made up while
|
|
126
|
+
* planning replaced by {@link PULUMI_GENERATED} (plywood#56). Two previews of
|
|
127
|
+
* one create draw different autonames (`events-3f45941`, then
|
|
128
|
+
* `events-9c01d2e`), and a digest over them could never match a later plan of
|
|
129
|
+
* the same change.
|
|
130
|
+
*
|
|
131
|
+
* Only a create or a replacement draws a name: an update's inputs keep the one
|
|
132
|
+
* the resource was created with, and that stays in `before` and `after`. A
|
|
133
|
+
* value counts as generated when the engine lists its key in `__defaults`
|
|
134
|
+
* (the inputs the provider filled in, not the program) and it reads as an
|
|
135
|
+
* autoname of this resource: its logical name, or the start of it when the
|
|
136
|
+
* provider shortened it, then a dash and seven hex characters. An engine that
|
|
137
|
+
* prints no `__defaults` is held to the exact logical name. Every other input
|
|
138
|
+
* stays in, the defaults that are plain values included, so a changed input
|
|
139
|
+
* still changes the digest. `before` is the recorded state and is never masked.
|
|
117
140
|
*/
|
|
118
|
-
export function
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
141
|
+
export function pulumiPlannedInputs(step) {
|
|
142
|
+
const inputs = step.newState?.inputs;
|
|
143
|
+
if (!isObject(inputs))
|
|
144
|
+
return inputs ?? null;
|
|
145
|
+
const draws = step.oldState === undefined || step.op === "create" || step.op === "create-replacement" || step.op === "replace";
|
|
146
|
+
if (!draws)
|
|
147
|
+
return inputs;
|
|
148
|
+
const name = logicalName(step.urn).toLowerCase();
|
|
149
|
+
const filled = Array.isArray(inputs.__defaults) ? new Set(inputs.__defaults.filter((k) => typeof k === "string")) : undefined;
|
|
150
|
+
const out = {};
|
|
151
|
+
for (const [key, value] of Object.entries(inputs)) {
|
|
152
|
+
const stem = typeof value === "string" ? AUTONAME.exec(value)?.[1]?.toLowerCase() : undefined;
|
|
153
|
+
const generated = stem !== undefined && name !== "" && (filled ? filled.has(key) && (name.startsWith(stem) || stem.startsWith(name)) : stem === name);
|
|
154
|
+
out[key] = generated ? PULUMI_GENERATED : value;
|
|
155
|
+
}
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
158
|
+
/** One step as a digest sees it: what it proposes, without the values the engine made up. */
|
|
159
|
+
function digestStep(s) {
|
|
160
|
+
return {
|
|
122
161
|
op: s.op ?? null,
|
|
123
162
|
urn: s.urn ?? null,
|
|
124
163
|
provider: s.provider ?? null,
|
|
125
164
|
id: s.newState?.id ?? s.oldState?.id ?? null,
|
|
126
165
|
before: s.oldState?.inputs ?? null,
|
|
127
|
-
after: s
|
|
166
|
+
after: pulumiPlannedInputs(s),
|
|
128
167
|
diffReasons: [...(s.diffReasons ?? [])].sort(),
|
|
129
168
|
replaceReasons: [...(s.replaceReasons ?? [])].sort(),
|
|
130
169
|
detailedDiff: s.detailedDiff ?? null,
|
|
131
|
-
}
|
|
132
|
-
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const byStep = (a, b) => byString(`${a.urn}\u0000${a.op}`, `${b.urn}\u0000${b.op}`);
|
|
173
|
+
/**
|
|
174
|
+
* The member's plan digest: `computePlanDigest("pulumi-preview", …)` over
|
|
175
|
+
* what each step proposes, sorted by URN and op, so two renderings of one
|
|
176
|
+
* preview agree. See the module comment for what it leaves out, and
|
|
177
|
+
* {@link pulumiPlannedInputs} for the values the engine made up that it leaves
|
|
178
|
+
* out of a create. With `changedOnly`, `same` and `read` steps are left out
|
|
179
|
+
* too, under the label `pulumi-preview-changed`, so the two digests never
|
|
180
|
+
* collide.
|
|
181
|
+
*/
|
|
182
|
+
export function pulumiPreviewDigest(preview, opts = {}) {
|
|
183
|
+
const steps = (pulumiSteps(preview) ?? [])
|
|
184
|
+
.filter((s) => !opts.changedOnly || !PULUMI_UNCHANGED_OPS.has(s.op ?? ""))
|
|
185
|
+
.map(digestStep)
|
|
186
|
+
.sort(byStep);
|
|
133
187
|
return computePlanDigest(opts.changedOnly ? "pulumi-preview-changed" : "pulumi-preview", steps);
|
|
134
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* The digest of each resource the preview changes, by URN (plywood#55): the
|
|
191
|
+
* steps of that URN (a replace is several), as {@link pulumiPreviewDigest}
|
|
192
|
+
* reads them, with the values the engine made up left out. A `same` or `read` step is not in it whatever the lock grain,
|
|
193
|
+
* so what a pull request left alone never makes its plan differ from a plan
|
|
194
|
+
* made after another change landed on it.
|
|
195
|
+
*/
|
|
196
|
+
export function pulumiResourceDigests(preview) {
|
|
197
|
+
const byUrn = new Map();
|
|
198
|
+
for (const s of pulumiSteps(preview) ?? []) {
|
|
199
|
+
if (PULUMI_UNCHANGED_OPS.has(s.op ?? ""))
|
|
200
|
+
continue;
|
|
201
|
+
const list = byUrn.get(s.urn) ?? [];
|
|
202
|
+
list.push(digestStep(s));
|
|
203
|
+
byUrn.set(s.urn, list);
|
|
204
|
+
}
|
|
205
|
+
return Object.fromEntries([...byUrn].sort(([a], [b]) => byString(a, b)).map(([urn, steps]) => [urn, computePlanDigest("pulumi-resource", steps.sort(byStep))]));
|
|
206
|
+
}
|
|
135
207
|
const REPLACE_OPS = new Set(["replace", "create-replacement", "delete-replaced"]);
|
|
136
208
|
/** The action and disruption one URN's steps mean, or undefined for an op the change set has no action for. */
|
|
137
209
|
export function pulumiStepsAction(steps) {
|
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/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { pulumiPlugin } from "./plugin.js";
|
|
|
2
2
|
export { pulumiSerializer } from "./serializer.js";
|
|
3
3
|
export { pulumiConfigSchema, pulumiMemberConfigSchema, type PulumiConfig } from "./config.js";
|
|
4
4
|
export { readPulumiMember, PULUMI_EXPORT_FILE, type PulumiMemberConfig, type PulumiMemberReport } from "./workspace-member.js";
|
|
5
|
-
export { pulumiPreviewPart, pulumiPreviewDigest, pulumiSteps, pulumiExportResources, type PulumiPreviewPartInput, type PulumiStep, type PulumiStepState, type PulumiExportedResource, } from "./change-set.js";
|
|
5
|
+
export { pulumiPreviewPart, pulumiPreviewDigest, pulumiResourceDigests, pulumiPlannedInputs, PULUMI_GENERATED, pulumiSteps, pulumiExportResources, type PulumiPreviewPartInput, type PulumiStep, type PulumiStepState, type PulumiExportedResource, } from "./change-set.js";
|
|
6
6
|
export { pulumiRefreshArgs, pulumiRefreshDriftRecords, pulumiRefreshDocumentRecords, type PulumiRefreshRead, type PulumiRefreshDriftInput, type PulumiRefreshDocumentInput, } from "./drift-record.js";
|
|
7
7
|
export { previewPulumiMember, previewPulumiMemberRead, refreshPulumiMember, pulumiMemberEngine, pulumiMemberStackOutputs, pulumiPreviewArgs, pulumiStackOutputArgs, runAllowedPlunge, runAllowedPulumi, readMemberExport, defaultPulumiRunner, PULUMI_ALLOWED_COMMANDS, PULUMI_APPLY_COMMANDS, runApplyPulumi, runApplyPlunge, pulumiUpArgs, plungeUpArgs, pulumiUpPreviewArgs, PULUMI_REVIEW_STACK_COMMANDS, REVIEW_STACK_NAME, isReviewStackName, reviewStackInitArgs, reviewStackDestroyArgs, reviewStackRmArgs, runReviewStackPulumi, runReviewStackPlunge, type PulumiPolicyPack, type PulumiApplyTail, type PulumiRunner, type PreviewPulumiMemberOptions, type PulumiMemberPreview, type PulumiMemberEngine, type PulumiMemberStackOutputsOptions, type RefreshPulumiMemberOptions, } from "./run.js";
|
|
8
8
|
export { pulumiExportedStackOutputs, pulumiPlannedStackOutputs, type PulumiStackOutput } from "./stack-outputs.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAC3F,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC5H,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,WAAW,EACX,qBAAqB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,sBAAsB,GAC5B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,iBAAiB,EACjB,yBAAyB,EACzB,4BAA4B,EAC5B,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,GAChC,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,EACnB,kBAAkB,EAClB,wBAAwB,EACxB,iBAAiB,EACjB,qBAAqB,EACrB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,uBAAuB,EACvB,qBAAqB,EACrB,cAAc,EACd,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,4BAA4B,EAC5B,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,0BAA0B,EAC/B,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,+BAA+B,EACpC,KAAK,0BAA0B,GAChC,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,0BAA0B,EAAE,yBAAyB,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAChH,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7E,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,0BAA0B,EAC1B,0BAA0B,EAC1B,oBAAoB,EACpB,0BAA0B,EAC1B,KAAK,iBAAiB,EACtB,KAAK,yBAAyB,GAC/B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAEnF,OAAO,EACL,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,EACxB,kBAAkB,EAClB,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,uBAAuB,EACvB,uBAAuB,EACvB,+BAA+B,EAC/B,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,eAAe,GACrB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,KAAK,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACpG,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,mBAAmB,EACnB,cAAc,EACd,4BAA4B,EAC5B,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,GACxB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,eAAe,EAAE,KAAK,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,EAAE,aAAa,EAAE,KAAK,mBAAmB,EAAE,KAAK,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AACpH,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,cAAc,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,KAAK,wBAAwB,EAAE,KAAK,uBAAuB,EAAE,KAAK,wBAAwB,EAAE,aAAa,EAAE,eAAe,EAAE,kBAAkB,EAAE,KAAK,gBAAgB,EAAE,mBAAmB,EAAE,KAAK,oBAAoB,EAAE,KAAK,mBAAmB,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAE5d,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,wBAAwB,EACxB,UAAU,EACV,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,0BAA0B,GAChC,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC9J,OAAO,EAAE,aAAa,EAAE,KAAK,mBAAmB,EAAE,KAAK,sBAAsB,EAAE,KAAK,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAC3I,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,KAAK,eAAe,EAAE,KAAK,iBAAiB,EAAE,MAAM,SAAS,CAAC;AACnI,OAAO,EAAE,aAAa,IAAI,iBAAiB,EAAE,WAAW,IAAI,eAAe,EAAE,MAAM,eAAe,CAAC;AAEnG,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAElG,OAAO,EAAE,0BAA0B,EAAE,KAAK,kBAAkB,EAAE,KAAK,0BAA0B,EAAE,MAAM,0BAA0B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export { pulumiPlugin } from "./plugin.js";
|
|
|
2
2
|
export { pulumiSerializer } from "./serializer.js";
|
|
3
3
|
export { pulumiConfigSchema, pulumiMemberConfigSchema } from "./config.js";
|
|
4
4
|
export { readPulumiMember, PULUMI_EXPORT_FILE } from "./workspace-member.js";
|
|
5
|
-
export { pulumiPreviewPart, pulumiPreviewDigest, pulumiSteps, pulumiExportResources, } from "./change-set.js";
|
|
5
|
+
export { pulumiPreviewPart, pulumiPreviewDigest, pulumiResourceDigests, pulumiPlannedInputs, PULUMI_GENERATED, pulumiSteps, pulumiExportResources, } from "./change-set.js";
|
|
6
6
|
export { pulumiRefreshArgs, pulumiRefreshDriftRecords, pulumiRefreshDocumentRecords, } from "./drift-record.js";
|
|
7
7
|
export { previewPulumiMember, previewPulumiMemberRead, refreshPulumiMember, pulumiMemberEngine, pulumiMemberStackOutputs, pulumiPreviewArgs, pulumiStackOutputArgs, runAllowedPlunge, runAllowedPulumi, readMemberExport, defaultPulumiRunner, PULUMI_ALLOWED_COMMANDS, PULUMI_APPLY_COMMANDS, runApplyPulumi, runApplyPlunge, pulumiUpArgs, plungeUpArgs, pulumiUpPreviewArgs, PULUMI_REVIEW_STACK_COMMANDS, REVIEW_STACK_NAME, isReviewStackName, reviewStackInitArgs, reviewStackDestroyArgs, reviewStackRmArgs, runReviewStackPulumi, runReviewStackPlunge, } from "./run.js";
|
|
8
8
|
export { pulumiExportedStackOutputs, pulumiPlannedStackOutputs } from "./stack-outputs.js";
|
package/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "6066d06e053b59c3170661488757ef2e85187346d1d3e6e5e69f126a38dd4913",
|
|
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": "d6d6dfcd5c8b2e4908518689e3727e4ca7e761bf6a407f7d6dc9bbb116ea9ac3"
|
|
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/pulumi-up.d.ts
CHANGED
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
* through `DeployContext.lockGrain`, else `resource` on plunge and
|
|
27
27
|
* `target` on stock Pulumi, #4552) the digest leaves `same` and `read`
|
|
28
28
|
* steps out (ruling B);
|
|
29
|
+
* - `resourceDigests`: one digest per resource the preview changes, by URN,
|
|
30
|
+
* whatever the grain (plywood#55);
|
|
29
31
|
* - `outputs` from the stack step's planned outputs;
|
|
30
32
|
* - `shape` from `pulumiPlanShape` (`./plan-shape.ts`);
|
|
31
33
|
* - `artifact`: the engine, the shape, the digest, CrossGuard's findings,
|
package/dist/pulumi-up.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pulumi-up.d.ts","sourceRoot":"","sources":["../src/pulumi-up.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"pulumi-up.d.ts","sourceRoot":"","sources":["../src/pulumi-up.ts"],"names":[],"mappings":"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"}
|
package/dist/pulumi-up.js
CHANGED
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
* through `DeployContext.lockGrain`, else `resource` on plunge and
|
|
27
27
|
* `target` on stock Pulumi, #4552) the digest leaves `same` and `read`
|
|
28
28
|
* steps out (ruling B);
|
|
29
|
+
* - `resourceDigests`: one digest per resource the preview changes, by URN,
|
|
30
|
+
* whatever the grain (plywood#55);
|
|
29
31
|
* - `outputs` from the stack step's planned outputs;
|
|
30
32
|
* - `shape` from `pulumiPlanShape` (`./plan-shape.ts`);
|
|
31
33
|
* - `artifact`: the engine, the shape, the digest, CrossGuard's findings,
|
|
@@ -139,7 +141,7 @@ import { basename, isAbsolute, join, relative, resolve } from "node:path";
|
|
|
139
141
|
import { PlanShapeMovedError } from "@intentius/chant/components/covered-round";
|
|
140
142
|
import { computePlanDigest } from "@intentius/chant/lifecycle/plan-digest";
|
|
141
143
|
import { parseYAML } from "@intentius/chant/yaml";
|
|
142
|
-
import { pulumiPreviewPart } from "./change-set.js";
|
|
144
|
+
import { pulumiPreviewPart, pulumiResourceDigests } from "./change-set.js";
|
|
143
145
|
import { PULUMI_STREAMING_PREVIEW_ENV, pulumiAppliedSteps, pulumiFailedUrns, pulumiLandedUrns, pulumiPolicyEvents, pulumiPolicyFindings, pulumiStreamError, pulumiStreamPreview, pulumiStreamSummary, pulumiStreamTimings, } from "./engine-events.js";
|
|
144
146
|
import { CHANT_EXIT } from "@intentius/chant/exit-codes";
|
|
145
147
|
import { pulumiExitOf } from "./exit-codes.js";
|
|
@@ -565,6 +567,8 @@ async function planStack(ctx, original, opts) {
|
|
|
565
567
|
planDigest,
|
|
566
568
|
artifact,
|
|
567
569
|
changeSet: read.part,
|
|
570
|
+
// plywood#55: each changed resource's own digest, which a merged wave compares with the pull request's per resource.
|
|
571
|
+
resourceDigests: pulumiResourceDigests(read.preview),
|
|
568
572
|
outputs: { known: planned.known, unknown: planned.unknown },
|
|
569
573
|
shape: read.shape,
|
|
570
574
|
...(ctx.targets?.length ? { targets: ctx.targets } : {}),
|
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.60",
|
|
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.60",
|
|
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/change-set.ts
CHANGED
|
@@ -68,7 +68,11 @@
|
|
|
68
68
|
* each step's op, URN, provider, id, inputs before and after, `diffReasons`,
|
|
69
69
|
* `replaceReasons` and `detailedDiff`, sorted by URN and op. It leaves out
|
|
70
70
|
* `config`, `duration`, `diagnostics` and `changeSummary`, and the states'
|
|
71
|
-
* outputs, which at preview are the last deployment's or unknown.
|
|
71
|
+
* outputs, which at preview are the last deployment's or unknown. It also
|
|
72
|
+
* leaves out what the engine makes up while planning a create or a
|
|
73
|
+
* replacement (plywood#56): an autoname such as `events-3f45941` is drawn
|
|
74
|
+
* again by every preview, so it reads as {@link PULUMI_GENERATED}. See
|
|
75
|
+
* {@link pulumiPlannedInputs}. Unknowns already print as one fixed string. For a
|
|
72
76
|
* member on the resource lock grain the `pulumi-up` capability passes
|
|
73
77
|
* `changedOnly` (#4541, ruling B), which leaves `same` and `read` steps out.
|
|
74
78
|
*
|
|
@@ -164,31 +168,104 @@ export interface PulumiPreviewDigestOptions {
|
|
|
164
168
|
changedOnly?: boolean;
|
|
165
169
|
}
|
|
166
170
|
|
|
171
|
+
/** What stands in a digest for an input the engine made up while planning (plywood#56). */
|
|
172
|
+
export const PULUMI_GENERATED = "<generated at plan time>";
|
|
173
|
+
|
|
174
|
+
/** Pulumi's autonaming: the logical name, a dash and seven random hex characters. */
|
|
175
|
+
const AUTONAME = /^(.+)-[0-9a-f]{7}$/;
|
|
176
|
+
|
|
177
|
+
/** The step's resource name: the last part of its URN. */
|
|
178
|
+
function logicalName(urn: string | undefined): string {
|
|
179
|
+
const at = urn?.lastIndexOf("::") ?? -1;
|
|
180
|
+
return urn && at >= 0 ? urn.slice(at + 2) : (urn ?? "");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The inputs a step would write, with the values the engine made up while
|
|
185
|
+
* planning replaced by {@link PULUMI_GENERATED} (plywood#56). Two previews of
|
|
186
|
+
* one create draw different autonames (`events-3f45941`, then
|
|
187
|
+
* `events-9c01d2e`), and a digest over them could never match a later plan of
|
|
188
|
+
* the same change.
|
|
189
|
+
*
|
|
190
|
+
* Only a create or a replacement draws a name: an update's inputs keep the one
|
|
191
|
+
* the resource was created with, and that stays in `before` and `after`. A
|
|
192
|
+
* value counts as generated when the engine lists its key in `__defaults`
|
|
193
|
+
* (the inputs the provider filled in, not the program) and it reads as an
|
|
194
|
+
* autoname of this resource: its logical name, or the start of it when the
|
|
195
|
+
* provider shortened it, then a dash and seven hex characters. An engine that
|
|
196
|
+
* prints no `__defaults` is held to the exact logical name. Every other input
|
|
197
|
+
* stays in, the defaults that are plain values included, so a changed input
|
|
198
|
+
* still changes the digest. `before` is the recorded state and is never masked.
|
|
199
|
+
*/
|
|
200
|
+
export function pulumiPlannedInputs(step: PulumiStep): Json | null {
|
|
201
|
+
const inputs = step.newState?.inputs;
|
|
202
|
+
if (!isObject(inputs)) return inputs ?? null;
|
|
203
|
+
const draws = step.oldState === undefined || step.op === "create" || step.op === "create-replacement" || step.op === "replace";
|
|
204
|
+
if (!draws) return inputs;
|
|
205
|
+
const name = logicalName(step.urn).toLowerCase();
|
|
206
|
+
const filled = Array.isArray(inputs.__defaults) ? new Set(inputs.__defaults.filter((k): k is string => typeof k === "string")) : undefined;
|
|
207
|
+
const out: Json = {};
|
|
208
|
+
for (const [key, value] of Object.entries(inputs)) {
|
|
209
|
+
const stem = typeof value === "string" ? AUTONAME.exec(value)?.[1]?.toLowerCase() : undefined;
|
|
210
|
+
const generated = stem !== undefined && name !== "" && (filled ? filled.has(key) && (name.startsWith(stem) || stem.startsWith(name)) : stem === name);
|
|
211
|
+
out[key] = generated ? PULUMI_GENERATED : value;
|
|
212
|
+
}
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** One step as a digest sees it: what it proposes, without the values the engine made up. */
|
|
217
|
+
function digestStep(s: PulumiStep) {
|
|
218
|
+
return {
|
|
219
|
+
op: s.op ?? null,
|
|
220
|
+
urn: s.urn ?? null,
|
|
221
|
+
provider: s.provider ?? null,
|
|
222
|
+
id: s.newState?.id ?? s.oldState?.id ?? null,
|
|
223
|
+
before: s.oldState?.inputs ?? null,
|
|
224
|
+
after: pulumiPlannedInputs(s),
|
|
225
|
+
diffReasons: [...(s.diffReasons ?? [])].sort(),
|
|
226
|
+
replaceReasons: [...(s.replaceReasons ?? [])].sort(),
|
|
227
|
+
detailedDiff: s.detailedDiff ?? null,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const byStep = (a: { urn: string | null; op: string | null }, b: { urn: string | null; op: string | null }): number =>
|
|
232
|
+
byString(`${a.urn}\u0000${a.op}`, `${b.urn}\u0000${b.op}`);
|
|
233
|
+
|
|
167
234
|
/**
|
|
168
235
|
* The member's plan digest: `computePlanDigest("pulumi-preview", …)` over
|
|
169
236
|
* what each step proposes, sorted by URN and op, so two renderings of one
|
|
170
|
-
* preview agree. See the module comment for what it leaves out
|
|
171
|
-
*
|
|
172
|
-
* `
|
|
237
|
+
* preview agree. See the module comment for what it leaves out, and
|
|
238
|
+
* {@link pulumiPlannedInputs} for the values the engine made up that it leaves
|
|
239
|
+
* out of a create. With `changedOnly`, `same` and `read` steps are left out
|
|
240
|
+
* too, under the label `pulumi-preview-changed`, so the two digests never
|
|
241
|
+
* collide.
|
|
173
242
|
*/
|
|
174
243
|
export function pulumiPreviewDigest(preview: unknown, opts: PulumiPreviewDigestOptions = {}): string {
|
|
175
244
|
const steps = (pulumiSteps(preview) ?? [])
|
|
176
245
|
.filter((s) => !opts.changedOnly || !PULUMI_UNCHANGED_OPS.has(s.op ?? ""))
|
|
177
|
-
.map(
|
|
178
|
-
|
|
179
|
-
urn: s.urn ?? null,
|
|
180
|
-
provider: s.provider ?? null,
|
|
181
|
-
id: s.newState?.id ?? s.oldState?.id ?? null,
|
|
182
|
-
before: s.oldState?.inputs ?? null,
|
|
183
|
-
after: s.newState?.inputs ?? null,
|
|
184
|
-
diffReasons: [...(s.diffReasons ?? [])].sort(),
|
|
185
|
-
replaceReasons: [...(s.replaceReasons ?? [])].sort(),
|
|
186
|
-
detailedDiff: s.detailedDiff ?? null,
|
|
187
|
-
}))
|
|
188
|
-
.sort((a, b) => byString(`${a.urn}\u0000${a.op}`, `${b.urn}\u0000${b.op}`));
|
|
246
|
+
.map(digestStep)
|
|
247
|
+
.sort(byStep);
|
|
189
248
|
return computePlanDigest(opts.changedOnly ? "pulumi-preview-changed" : "pulumi-preview", steps);
|
|
190
249
|
}
|
|
191
250
|
|
|
251
|
+
/**
|
|
252
|
+
* The digest of each resource the preview changes, by URN (plywood#55): the
|
|
253
|
+
* steps of that URN (a replace is several), as {@link pulumiPreviewDigest}
|
|
254
|
+
* reads them, with the values the engine made up left out. A `same` or `read` step is not in it whatever the lock grain,
|
|
255
|
+
* so what a pull request left alone never makes its plan differ from a plan
|
|
256
|
+
* made after another change landed on it.
|
|
257
|
+
*/
|
|
258
|
+
export function pulumiResourceDigests(preview: unknown): Record<string, string> {
|
|
259
|
+
const byUrn = new Map<string, ReturnType<typeof digestStep>[]>();
|
|
260
|
+
for (const s of pulumiSteps(preview) ?? []) {
|
|
261
|
+
if (PULUMI_UNCHANGED_OPS.has(s.op ?? "")) continue;
|
|
262
|
+
const list = byUrn.get(s.urn!) ?? [];
|
|
263
|
+
list.push(digestStep(s));
|
|
264
|
+
byUrn.set(s.urn!, list);
|
|
265
|
+
}
|
|
266
|
+
return Object.fromEntries([...byUrn].sort(([a], [b]) => byString(a, b)).map(([urn, steps]) => [urn, computePlanDigest("pulumi-resource", steps.sort(byStep))]));
|
|
267
|
+
}
|
|
268
|
+
|
|
192
269
|
const REPLACE_OPS = new Set(["replace", "create-replacement", "delete-replaced"]);
|
|
193
270
|
|
|
194
271
|
/** The action and disruption one URN's steps mean, or undefined for an op the change set has no action for. */
|
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 } : {}),
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,9 @@ export { readPulumiMember, PULUMI_EXPORT_FILE, type PulumiMemberConfig, type Pul
|
|
|
5
5
|
export {
|
|
6
6
|
pulumiPreviewPart,
|
|
7
7
|
pulumiPreviewDigest,
|
|
8
|
+
pulumiResourceDigests,
|
|
9
|
+
pulumiPlannedInputs,
|
|
10
|
+
PULUMI_GENERATED,
|
|
8
11
|
pulumiSteps,
|
|
9
12
|
pulumiExportResources,
|
|
10
13
|
type PulumiPreviewPartInput,
|
|
@@ -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();
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Values the engine makes up while planning stay out of the digest (plywood#56).
|
|
3
|
+
* `testdata/pulumi-preview/preview-create.json` is a recorded first preview:
|
|
4
|
+
* its queues carry autonames (`events-3f45941`, `worker-0-84f29f5`) the
|
|
5
|
+
* provider filled in, listed in each step's `__defaults`.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { describe, expect, test } from "vitest";
|
|
10
|
+
import { PULUMI_GENERATED, pulumiPlannedInputs, pulumiPreviewDigest, pulumiResourceDigests, type PulumiStep } from "./change-set";
|
|
11
|
+
|
|
12
|
+
const created = JSON.parse(readFileSync(join(import.meta.dirname, "testdata", "pulumi-preview", "preview-create.json"), "utf-8")) as { steps: PulumiStep[] };
|
|
13
|
+
const copy = () => JSON.parse(JSON.stringify(created)) as typeof created;
|
|
14
|
+
const stepOf = (doc: typeof created, name: string) => doc.steps.find((s) => s.urn?.endsWith(`::${name}`))!;
|
|
15
|
+
const inputsOf = (doc: typeof created, name: string) => stepOf(doc, name).newState!.inputs as Record<string, unknown>;
|
|
16
|
+
|
|
17
|
+
/** The same preview drawn again: every autoname gets another suffix. */
|
|
18
|
+
function replanned(): typeof created {
|
|
19
|
+
const doc = copy();
|
|
20
|
+
for (const s of doc.steps) {
|
|
21
|
+
const inputs = s.newState?.inputs as Record<string, unknown> | undefined;
|
|
22
|
+
if (!inputs || !Array.isArray(inputs.__defaults)) continue;
|
|
23
|
+
for (const key of inputs.__defaults as string[]) {
|
|
24
|
+
const v = inputs[key];
|
|
25
|
+
if (typeof v === "string" && /-[0-9a-f]{7}$/.test(v)) inputs[key] = v.replace(/[0-9a-f]{7}$/, "0123abc");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return doc;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe("a create's digest leaves out what the engine drew while planning", () => {
|
|
32
|
+
test("the recorded preview holds autonames, and a second draw gives the same digest", () => {
|
|
33
|
+
expect(inputsOf(created, "events").name).toBe("events-3f45941");
|
|
34
|
+
expect(inputsOf(replanned(), "events").name).toBe("events-0123abc");
|
|
35
|
+
expect(pulumiPreviewDigest(replanned())).toBe(pulumiPreviewDigest(created));
|
|
36
|
+
expect(pulumiPreviewDigest(replanned(), { changedOnly: true })).toBe(pulumiPreviewDigest(created, { changedOnly: true }));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("the name is replaced by one marker, and the rest of the inputs are kept", () => {
|
|
40
|
+
const planned = pulumiPlannedInputs(stepOf(created, "events")) as Record<string, unknown>;
|
|
41
|
+
expect(planned.name).toBe(PULUMI_GENERATED);
|
|
42
|
+
expect(planned.messageRetentionSeconds).toBe(3600);
|
|
43
|
+
expect(planned.maxMessageSize).toBe(262144);
|
|
44
|
+
expect(planned.tags).toEqual({ "corpus-owner": "plunge", "corpus-program": "aws-ts" });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("two creates that differ in an input a reviewer saw still differ", () => {
|
|
48
|
+
for (const change of [
|
|
49
|
+
(d: typeof created) => { inputsOf(d, "events").messageRetentionSeconds = 7200; },
|
|
50
|
+
(d: typeof created) => { (inputsOf(d, "worker-0").tags as Record<string, string>)["corpus-index"] = "9"; },
|
|
51
|
+
// a default that is a plain value counts: it decides what gets applied
|
|
52
|
+
(d: typeof created) => { inputsOf(d, "events").maxMessageSize = 1024; },
|
|
53
|
+
// a name the program set is not a draw, and a changed one is a change
|
|
54
|
+
(d: typeof created) => { inputsOf(d, "app-role").name = "rec-ts-other"; },
|
|
55
|
+
]) {
|
|
56
|
+
const other = replanned();
|
|
57
|
+
change(other);
|
|
58
|
+
expect(pulumiPreviewDigest(other)).not.toBe(pulumiPreviewDigest(created));
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("a different logical name or a new resource changes the digest", () => {
|
|
63
|
+
const renamed = replanned();
|
|
64
|
+
stepOf(renamed, "events").urn = stepOf(renamed, "events").urn!.replace("::events", "::journal");
|
|
65
|
+
expect(pulumiPreviewDigest(renamed)).not.toBe(pulumiPreviewDigest(created));
|
|
66
|
+
const extra = replanned();
|
|
67
|
+
extra.steps.push({ ...stepOf(extra, "docs-notify"), urn: stepOf(extra, "docs-notify").urn!.replace("::docs-notify", "::more-notify") });
|
|
68
|
+
expect(pulumiPreviewDigest(extra)).not.toBe(pulumiPreviewDigest(created));
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("a string that only looks like a suffix is kept unless the engine filled it in", () => {
|
|
72
|
+
const doc = copy();
|
|
73
|
+
const step = stepOf(doc, "events");
|
|
74
|
+
(step.newState!.inputs as Record<string, unknown>).description = "events-3f45941";
|
|
75
|
+
expect((pulumiPlannedInputs(step) as Record<string, unknown>).description).toBe("events-3f45941");
|
|
76
|
+
// a name the program wrote is not listed in __defaults
|
|
77
|
+
(step.newState!.inputs as Record<string, unknown>).__defaults = [];
|
|
78
|
+
expect((pulumiPlannedInputs(step) as Record<string, unknown>).name).toBe("events-3f45941");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("an engine that prints no __defaults is held to the exact logical name", () => {
|
|
82
|
+
const step: PulumiStep = { op: "create", urn: "urn:pulumi:dev::p::aws:sqs/queue:Queue::events", newState: { inputs: { name: "events-3f45941", other: "worker-3f45941" } } };
|
|
83
|
+
expect(pulumiPlannedInputs(step)).toEqual({ name: PULUMI_GENERATED, other: "worker-3f45941" });
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("an update keeps its inputs, since it draws no name", () => {
|
|
87
|
+
const update: PulumiStep = {
|
|
88
|
+
op: "update",
|
|
89
|
+
urn: "urn:pulumi:dev::p::aws:sqs/queue:Queue::events",
|
|
90
|
+
oldState: { inputs: { name: "events-3f45941", messageRetentionSeconds: 60 } },
|
|
91
|
+
newState: { inputs: { __defaults: ["name"], name: "events-3f45941", messageRetentionSeconds: 7200 } },
|
|
92
|
+
};
|
|
93
|
+
expect(pulumiPlannedInputs(update)).toBe(update.newState!.inputs);
|
|
94
|
+
const other = JSON.parse(JSON.stringify(update)) as PulumiStep;
|
|
95
|
+
(other.oldState!.inputs as Record<string, unknown>).name = "events-aaaaaaa";
|
|
96
|
+
(other.newState!.inputs as Record<string, unknown>).name = "events-aaaaaaa";
|
|
97
|
+
expect(pulumiPreviewDigest({ steps: [other] })).not.toBe(pulumiPreviewDigest({ steps: [update] }));
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("a replacement draws a new name too", () => {
|
|
101
|
+
const replace = (suffix: string): PulumiStep => ({
|
|
102
|
+
op: "replace",
|
|
103
|
+
urn: "urn:pulumi:dev::p::aws:sqs/queue:Queue::events",
|
|
104
|
+
oldState: { inputs: { name: "events-1111111", fifoQueue: false } },
|
|
105
|
+
newState: { inputs: { __defaults: ["name"], name: `events-${suffix}`, fifoQueue: true } },
|
|
106
|
+
replaceReasons: ["fifoQueue"],
|
|
107
|
+
});
|
|
108
|
+
expect(pulumiPreviewDigest({ steps: [replace("2222222")] })).toBe(pulumiPreviewDigest({ steps: [replace("3333333")] }));
|
|
109
|
+
const flipped = replace("2222222");
|
|
110
|
+
(flipped.newState!.inputs as Record<string, unknown>).fifoQueue = false;
|
|
111
|
+
expect(pulumiPreviewDigest({ steps: [flipped] })).not.toBe(pulumiPreviewDigest({ steps: [replace("2222222")] }));
|
|
112
|
+
});
|
|
113
|
+
});
|
package/src/pulumi-up.test.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { join, relative } from "node:path";
|
|
|
18
18
|
import { describe, expect, test } from "vitest";
|
|
19
19
|
import { isPlanMovedError, type DeployContext } from "@intentius/chant/components/capability";
|
|
20
20
|
import { PlanShapeMovedError } from "@intentius/chant/components/covered-round";
|
|
21
|
-
import { pulumiPreviewDigest } from "./change-set";
|
|
21
|
+
import { pulumiPreviewDigest, pulumiResourceDigests } from "./change-set";
|
|
22
22
|
import { pulumiStreamPreview, PULUMI_STREAMING_PREVIEW_ENV } from "./engine-events";
|
|
23
23
|
import {
|
|
24
24
|
createPulumiUpCapability,
|
|
@@ -111,6 +111,17 @@ describe("plan on plunge", () => {
|
|
|
111
111
|
expect(artifact.policy.warnings).toHaveLength(1);
|
|
112
112
|
});
|
|
113
113
|
|
|
114
|
+
test("answers a digest for each resource the preview changes, on either grain (plywood#55)", async () => {
|
|
115
|
+
const cap = createPulumiUpCapability({ plunge: engine().run, env: {}, tmp: tmp(), snapshots: null });
|
|
116
|
+
const resource = await cap.plan!(ctx, step({ engine: "plunge" }));
|
|
117
|
+
const stack = await cap.plan!(ctx, step({ engine: "plunge", lockGrain: "target" }));
|
|
118
|
+
expect(resource.resourceDigests).toEqual(pulumiResourceDigests(pulumiStreamPreview(stream)));
|
|
119
|
+
expect(stack.resourceDigests).toEqual(resource.resourceDigests);
|
|
120
|
+
const changed = (pulumiStreamPreview(stream) as { steps: { op: string }[] }).steps.filter((x) => x.op !== "same" && x.op !== "read");
|
|
121
|
+
expect(Object.keys(resource.resourceDigests!).length).toBeGreaterThan(0);
|
|
122
|
+
expect(Object.keys(resource.resourceDigests!).length).toBeLessThanOrEqual(changed.length);
|
|
123
|
+
});
|
|
124
|
+
|
|
114
125
|
test("the target grain keeps every step in the digest", async () => {
|
|
115
126
|
const cap = createPulumiUpCapability({ plunge: engine().run, env: {}, tmp: tmp(), snapshots: null });
|
|
116
127
|
const planned = await cap.plan!(ctx, step({ engine: "plunge", lockGrain: "target" }));
|
package/src/pulumi-up.ts
CHANGED
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
* through `DeployContext.lockGrain`, else `resource` on plunge and
|
|
27
27
|
* `target` on stock Pulumi, #4552) the digest leaves `same` and `read`
|
|
28
28
|
* steps out (ruling B);
|
|
29
|
+
* - `resourceDigests`: one digest per resource the preview changes, by URN,
|
|
30
|
+
* whatever the grain (plywood#55);
|
|
29
31
|
* - `outputs` from the stack step's planned outputs;
|
|
30
32
|
* - `shape` from `pulumiPlanShape` (`./plan-shape.ts`);
|
|
31
33
|
* - `artifact`: the engine, the shape, the digest, CrossGuard's findings,
|
|
@@ -154,7 +156,7 @@ import type { GatePolicyFindings } from "@intentius/chant/gate-policy";
|
|
|
154
156
|
import { computePlanDigest } from "@intentius/chant/lifecycle/plan-digest";
|
|
155
157
|
import type { SnapshotStore } from "@intentius/chant/lifecycle/snapshot-store";
|
|
156
158
|
import { parseYAML } from "@intentius/chant/yaml";
|
|
157
|
-
import { pulumiPreviewPart } from "./change-set";
|
|
159
|
+
import { pulumiPreviewPart, pulumiResourceDigests } from "./change-set";
|
|
158
160
|
import {
|
|
159
161
|
PULUMI_STREAMING_PREVIEW_ENV,
|
|
160
162
|
pulumiAppliedSteps,
|
|
@@ -795,6 +797,8 @@ async function planStack(ctx: DeployContext, original: PulumiUpStepInput, opts:
|
|
|
795
797
|
planDigest,
|
|
796
798
|
artifact,
|
|
797
799
|
changeSet: read.part,
|
|
800
|
+
// plywood#55: each changed resource's own digest, which a merged wave compares with the pull request's per resource.
|
|
801
|
+
resourceDigests: pulumiResourceDigests(read.preview),
|
|
798
802
|
outputs: { known: planned.known, unknown: planned.unknown },
|
|
799
803
|
shape: read.shape,
|
|
800
804
|
...(ctx.targets?.length ? { targets: ctx.targets } : {}),
|