@intentius/chant-lexicon-pulumi 0.124.57 → 0.124.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -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 +2 -2
- package/dist/manifest.json +1 -1
- package/dist/run.d.ts +18 -2
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +37 -3
- package/dist/stack-ops.d.ts +60 -1
- package/dist/stack-ops.d.ts.map +1 -1
- package/dist/stack-ops.js +138 -5
- package/package.json +2 -2
- package/src/index.ts +1 -1
- package/src/run.test.ts +40 -0
- package/src/run.ts +39 -3
- package/src/stack-ops.test.ts +112 -0
- package/src/stack-ops.ts +189 -4
package/README.md
CHANGED
|
@@ -39,16 +39,19 @@ A program that reads another stack through `StackReference` orders its stack aft
|
|
|
39
39
|
|
|
40
40
|
## Gated stack operations
|
|
41
41
|
|
|
42
|
-
`src/stack-ops.ts` ([#4578](https://github.com/INTENTIUS/chant/issues/4578)) runs
|
|
42
|
+
`src/stack-ops.ts` ([#4578](https://github.com/INTENTIUS/chant/issues/4578)) runs stack operations, each only after its gated request (#4551) is approved on the workspace ledger by someone other than the requester, under the op `pulumi-stack-ops`. The first call records the request and returns the `chant approve` command; the same call after the approval spends it, runs once, and records how it ended.
|
|
43
43
|
|
|
44
44
|
| Function | Request | What runs after approval |
|
|
45
45
|
|---|---|---|
|
|
46
46
|
| `gatedStackExport` | `stack-export` of `<stack>` | `pulumi stack export --stack <s>` (or plunge's), written mode 600 to a file outside every git working tree; refused in CI |
|
|
47
47
|
| `gatedStackCancel` | `stack-cancel` of `<stack>` with its lock id | `pulumi cancel --yes --stack <s>`, once the lock is still the approved one (read from a `file://` backend's lock directory); refused on plunge, which has no stack lock |
|
|
48
48
|
| `gatedStackMove` | `stack-move` of `move:<source>:<dest>` with the URNs | `pulumi state move --source <s> --dest <d> --yes <urn>...`, or `plunge stack move --stack <s> --dest <d> <urn>... --json --yes` after plunge's plan; both stacks preview first, and the move runs only when the previews change nothing but the moved resources |
|
|
49
|
+
| `gatedStackAction` | `stack-action` of `action:<stack>:<name>` with the URN and the preview's digest | `pulumi up --yes --target <urn> --replace <urn> --stack <s>` (or plunge's), once ([#4654](https://github.com/INTENTIUS/chant/issues/4654)); see below |
|
|
49
50
|
|
|
50
51
|
`pulumiStackLockIds({ dir, stack })` ([#4615](https://github.com/INTENTIUS/chant/issues/4615)) reads the lock ids a stack holds now, the same read `gatedStackCancel` checks with, so a product can offer the id to approve. It answers `checked: false` on a backend other than `file://`, and no ids for a plunge stack.
|
|
51
52
|
|
|
53
|
+
A day-2 action on a stack is a `command:local:Command` or `command:remote:Command` resource whose replacement is the operation, such as a key rotation. `gatedStackAction({ dir, stack, urn, name, engine, ... })` refuses any other resource type with `action-type` before anything runs. Each call previews `preview --json --target <urn> --replace <urn> --stack <s>` and refuses with `action-scope`, naming the other URNs, when the preview changes anything besides that one replacement; nothing is asked for then. The request binds the stack, the URN and the preview's digest, so a preview that changed after the approval asks again. Once approved, the update runs, and the finished line keeps `result: applied` (or `failed`) with `detail.result` set to `ok` (or `failed`), the exit code and the last lines of output. The same call after it ran answers `spent`, unless it passes `anew`. A stack on plunge runs both through plunge.
|
|
54
|
+
|
|
52
55
|
The writes go through `runGatedPulumi` and `runGatedPlunge` in `src/run.ts`, whose fixed forms (`PULUMI_GATED_COMMANDS`, `PLUNGE_GATED_COMMANDS`) are the only ones allowed.
|
|
53
56
|
|
|
54
57
|
## Drift: `chant workspace drift`
|
package/dist/index.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export { pulumiEngineEvents, pulumiStreamPreview, pulumiAppliedSteps, pulumiPoli
|
|
|
16
16
|
export { readPulumiDrift, type PulumiDriftRunners } from "./drift-read.js";
|
|
17
17
|
export { pulumiAuditCatalog } from "./lint/audit-catalog.js";
|
|
18
18
|
export { PulumiWatchOp, type PulumiWatchOpConfig, type PulumiWatchOpResources } from "./composites/pulumi-watch-op.js";
|
|
19
|
-
export { gatedStackExport, gatedStackCancel, gatedStackMove, moveLeftovers, diyStackLockIds, pulumiStackLockIds, type PulumiStackLocks, PULUMI_STACK_OPS_OP, type PulumiStackOpOutcome, type PulumiStackOpCommon, type PulumiStackOpKind } from "./stack-ops.js";
|
|
19
|
+
export { gatedStackExport, gatedStackCancel, gatedStackMove, gatedStackAction, checkStackActionUrn, stackActionScope, stackActionSubject, PulumiStackActionRefused, PULUMI_ACTION_TYPE_PREFIX, type PulumiStackActionOptions, type PulumiStackActionResult, type PulumiStackActionRefusal, moveLeftovers, diyStackLockIds, pulumiStackLockIds, type PulumiStackLocks, PULUMI_STACK_OPS_OP, type PulumiStackOpOutcome, type PulumiStackOpCommon, type PulumiStackOpKind } from "./stack-ops.js";
|
|
20
20
|
export { initReviewStack, removeReviewStack, parseReviewStackLocation, prNumberOf, pulumiTemplateStack, reviewConfigSource, reviewStackFor, reviewStackLocation, reviewStackTeardown, REVIEW_STACK_TEARDOWN, type ReviewStackOptions, type ReviewStackOutcome, type ReviewStackTeardown, type ReviewStackTeardownOptions, } from "./review-stack.js";
|
|
21
21
|
export { pulumiStackReferences, parsePulumiStackName, stackReferenceOwner, PULUMI_STACK_REFERENCE_TYPE, type PulumiStackReference } from "./stack-references.js";
|
|
22
22
|
export { PulumiAdoptOp, type PulumiAdoptOpConfig, type PulumiAdoptOpResources, type PulumiAdoptStack } from "./composites/pulumi-adopt-op.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":"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"}
|
package/dist/index.js
CHANGED
|
@@ -18,7 +18,7 @@ export { readPulumiDrift } from "./drift-read.js";
|
|
|
18
18
|
export { pulumiAuditCatalog } from "./lint/audit-catalog.js";
|
|
19
19
|
// The Preview/Drift watch composite and its step builders (#4494).
|
|
20
20
|
export { PulumiWatchOp } from "./composites/pulumi-watch-op.js";
|
|
21
|
-
export { gatedStackExport, gatedStackCancel, gatedStackMove, moveLeftovers, diyStackLockIds, pulumiStackLockIds, PULUMI_STACK_OPS_OP } from "./stack-ops.js";
|
|
21
|
+
export { gatedStackExport, gatedStackCancel, gatedStackMove, gatedStackAction, checkStackActionUrn, stackActionScope, stackActionSubject, PulumiStackActionRefused, PULUMI_ACTION_TYPE_PREFIX, moveLeftovers, diyStackLockIds, pulumiStackLockIds, PULUMI_STACK_OPS_OP } from "./stack-ops.js";
|
|
22
22
|
// Review stacks (INTENTIUS/plywood#15): pr-<n> stacks only.
|
|
23
23
|
export { initReviewStack, removeReviewStack, parseReviewStackLocation, prNumberOf, pulumiTemplateStack, reviewConfigSource, reviewStackFor, reviewStackLocation, reviewStackTeardown, REVIEW_STACK_TEARDOWN, } from "./review-stack.js";
|
|
24
24
|
export { pulumiStackReferences, parsePulumiStackName, stackReferenceOwner, PULUMI_STACK_REFERENCE_TYPE } from "./stack-references.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": "d9975374a07d5764ef1b0b209c48fc2ec1b4ec4669808c6d6b1b6429d23a97d6",
|
|
5
5
|
"meta.json": "ca3d163bab055381827226140568f3bef7eaac187cebd76878e0b63e9e442356",
|
|
6
6
|
"types/index.d.ts": "883bcdf6a960e21360e79df010ca2087719fffec60fc39541c5ecd32d661e328",
|
|
7
7
|
"rules/member-dir-absolute.ts": "4805d5e247ad544f942e1b2c36fc15a5087ca0aacaf804183a269021bd60e276",
|
|
@@ -25,5 +25,5 @@
|
|
|
25
25
|
"skills/chant-pulumi-stack-export.md": "aadd67f5e067de61dfda43d57492bca08bd22429e4baf314d6856a810dc0e8dc",
|
|
26
26
|
"skills/chant-pulumi-plunge.md": "8a7f2f9f1124744dee9645905f141057ca01f5a88e34e1bc3858d69406d7ff3a"
|
|
27
27
|
},
|
|
28
|
-
"composite": "
|
|
28
|
+
"composite": "a4603282326e0ed1abf7f6c24fd702e35975fbbc9b1cdee72341269a8f9a9ac1"
|
|
29
29
|
}
|
package/dist/manifest.json
CHANGED
package/dist/run.d.ts
CHANGED
|
@@ -167,9 +167,25 @@ export declare function runAdoptPlunge(run: PulumiRunner, args: string[], cwd: s
|
|
|
167
167
|
*
|
|
168
168
|
* - `pulumi cancel --yes --stack <stack>`: releases a stuck update's lock;
|
|
169
169
|
* - `pulumi state move --source <stack> --dest <stack> --yes <urn>...`: moves
|
|
170
|
-
* resources between stock stacks, never `--include-parents
|
|
170
|
+
* resources between stock stacks, never `--include-parents`;
|
|
171
|
+
* - `pulumi up --yes --target <urn> --replace <urn> --stack <stack>`: a
|
|
172
|
+
* day-2 action (#4654), the replacement of one `command:` resource, the
|
|
173
|
+
* same URN in both places. Its preview, `preview --json --target <urn>
|
|
174
|
+
* --replace <urn> --stack <stack>`, is a read and passes
|
|
175
|
+
* {@link runAllowedPulumi}. `up` takes `--replace` here only:
|
|
176
|
+
* {@link PULUMI_APPLY_COMMANDS} still refuses it.
|
|
171
177
|
*/
|
|
172
178
|
export declare const PULUMI_GATED_COMMANDS: readonly string[];
|
|
179
|
+
/** A stack action's preview (#4654): `preview --json --target <urn> --replace <urn> --stack <stack>`, a read. */
|
|
180
|
+
export declare function pulumiActionPreviewArgs(opts: {
|
|
181
|
+
stack: string;
|
|
182
|
+
urn: string;
|
|
183
|
+
}): string[];
|
|
184
|
+
/** A stack action's update (#4654): `up --yes --target <urn> --replace <urn> --stack <stack>`, under `pulumi` or `plunge`. */
|
|
185
|
+
export declare function pulumiActionUpArgs(opts: {
|
|
186
|
+
stack: string;
|
|
187
|
+
urn: string;
|
|
188
|
+
}): string[];
|
|
173
189
|
/**
|
|
174
190
|
* plunge's resource move (INTENTIUS/plunge#56) in its two forms. The first
|
|
175
191
|
* prints the plan and writes nothing, so it passes {@link runAllowedPlunge};
|
|
@@ -197,7 +213,7 @@ export declare function plungeMoveArgs(opts: {
|
|
|
197
213
|
export declare function runGatedPulumi(run: PulumiRunner, args: string[], cwd: string, env?: Record<string, string>): Promise<{
|
|
198
214
|
stdout: string;
|
|
199
215
|
}>;
|
|
200
|
-
/** Run
|
|
216
|
+
/** Run a writing form of {@link PLUNGE_GATED_COMMANDS}, the move or a stack action's update, refusing any other argv. Only `./stack-ops.ts` calls it, after the request is spent. */
|
|
201
217
|
export declare function runGatedPlunge(run: PulumiRunner, args: string[], cwd: string, env?: Record<string, string>): Promise<{
|
|
202
218
|
stdout: string;
|
|
203
219
|
}>;
|
package/dist/run.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"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"}
|
package/dist/run.js
CHANGED
|
@@ -438,12 +438,39 @@ export async function runAdoptPlunge(run, args, cwd, env) {
|
|
|
438
438
|
*
|
|
439
439
|
* - `pulumi cancel --yes --stack <stack>`: releases a stuck update's lock;
|
|
440
440
|
* - `pulumi state move --source <stack> --dest <stack> --yes <urn>...`: moves
|
|
441
|
-
* resources between stock stacks, never `--include-parents
|
|
441
|
+
* resources between stock stacks, never `--include-parents`;
|
|
442
|
+
* - `pulumi up --yes --target <urn> --replace <urn> --stack <stack>`: a
|
|
443
|
+
* day-2 action (#4654), the replacement of one `command:` resource, the
|
|
444
|
+
* same URN in both places. Its preview, `preview --json --target <urn>
|
|
445
|
+
* --replace <urn> --stack <stack>`, is a read and passes
|
|
446
|
+
* {@link runAllowedPulumi}. `up` takes `--replace` here only:
|
|
447
|
+
* {@link PULUMI_APPLY_COMMANDS} still refuses it.
|
|
442
448
|
*/
|
|
443
449
|
export const PULUMI_GATED_COMMANDS = [
|
|
444
450
|
"pulumi cancel --yes --stack <stack>",
|
|
445
451
|
"pulumi state move --source <stack> --dest <stack> --yes <urn>...",
|
|
452
|
+
"pulumi up --yes --target <urn> --replace <urn> --stack <stack>",
|
|
446
453
|
];
|
|
454
|
+
/** A stack action's preview (#4654): `preview --json --target <urn> --replace <urn> --stack <stack>`, a read. */
|
|
455
|
+
export function pulumiActionPreviewArgs(opts) {
|
|
456
|
+
return ["preview", "--json", "--target", opts.urn, "--replace", opts.urn, "--stack", opts.stack];
|
|
457
|
+
}
|
|
458
|
+
/** A stack action's update (#4654): `up --yes --target <urn> --replace <urn> --stack <stack>`, under `pulumi` or `plunge`. */
|
|
459
|
+
export function pulumiActionUpArgs(opts) {
|
|
460
|
+
return ["up", "--yes", "--target", opts.urn, "--replace", opts.urn, "--stack", opts.stack];
|
|
461
|
+
}
|
|
462
|
+
/** Exactly {@link pulumiActionUpArgs}' form: one URN, targeted and replaced, on one stack. */
|
|
463
|
+
function allowedActionUp(args) {
|
|
464
|
+
return (args.length === 8 &&
|
|
465
|
+
args[0] === "up" &&
|
|
466
|
+
args[1] === "--yes" &&
|
|
467
|
+
args[2] === "--target" &&
|
|
468
|
+
isUrn(args[3]) &&
|
|
469
|
+
args[4] === "--replace" &&
|
|
470
|
+
args[5] === args[3] &&
|
|
471
|
+
args[6] === "--stack" &&
|
|
472
|
+
isValue(args[7]));
|
|
473
|
+
}
|
|
447
474
|
/**
|
|
448
475
|
* plunge's resource move (INTENTIUS/plunge#56) in its two forms. The first
|
|
449
476
|
* prints the plan and writes nothing, so it passes {@link runAllowedPlunge};
|
|
@@ -452,6 +479,7 @@ export const PULUMI_GATED_COMMANDS = [
|
|
|
452
479
|
export const PLUNGE_GATED_COMMANDS = [
|
|
453
480
|
"plunge stack move --stack <stack> --dest <stack> <urn>... --json",
|
|
454
481
|
"plunge stack move --stack <stack> --dest <stack> <urn>... --json --yes",
|
|
482
|
+
"plunge up --yes --target <urn> --replace <urn> --stack <stack>",
|
|
455
483
|
];
|
|
456
484
|
const isUrn = (v) => typeof v === "string" && v.startsWith("urn:pulumi:");
|
|
457
485
|
/** `cancel --yes --stack <stack>`. */
|
|
@@ -480,6 +508,8 @@ function allowedGatedPulumi(args) {
|
|
|
480
508
|
const urns = args.slice(7);
|
|
481
509
|
return args[2] === "--source" && isValue(args[3]) && args[4] === "--dest" && isValue(args[5]) && args[6] === "--yes" && urns.length > 0 && urns.every(isUrn);
|
|
482
510
|
}
|
|
511
|
+
if (args[0] === "up")
|
|
512
|
+
return allowedActionUp(args);
|
|
483
513
|
return false;
|
|
484
514
|
}
|
|
485
515
|
/** Run one of {@link PULUMI_GATED_COMMANDS}, refusing any other argv. Only `./stack-ops.ts` calls it, after the request is spent. */
|
|
@@ -489,9 +519,13 @@ export async function runGatedPulumi(run, args, cwd, env) {
|
|
|
489
519
|
}
|
|
490
520
|
return run(args, env ? { cwd, env } : { cwd });
|
|
491
521
|
}
|
|
492
|
-
/** Run
|
|
522
|
+
/** Run a writing form of {@link PLUNGE_GATED_COMMANDS}, the move or a stack action's update, refusing any other argv. Only `./stack-ops.ts` calls it, after the request is spent. */
|
|
493
523
|
export async function runGatedPlunge(run, args, cwd, env) {
|
|
494
|
-
if (
|
|
524
|
+
if (args[0] === "up") {
|
|
525
|
+
if (!allowedActionUp(args))
|
|
526
|
+
throw new Error(`chant runs a plunge stack action only as ${PLUNGE_GATED_COMMANDS[2]}, not plunge ${args.join(" ")}`);
|
|
527
|
+
}
|
|
528
|
+
else if (!allowedPlungeMove(args, true)) {
|
|
495
529
|
throw new Error(`chant moves plunge resources only as ${PLUNGE_GATED_COMMANDS[1]}, not plunge ${args.join(" ")}`);
|
|
496
530
|
}
|
|
497
531
|
return run(args, env ? { cwd, env } : { cwd });
|
package/dist/stack-ops.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { type PulumiMemberEngine } from "./member-engine.js";
|
|
|
4
4
|
import { type PulumiRunner } from "./run.js";
|
|
5
5
|
/** The op the requests are recorded under, unless the caller names its own. */
|
|
6
6
|
export declare const PULUMI_STACK_OPS_OP = "pulumi-stack-ops";
|
|
7
|
-
export type PulumiStackOpKind = "stack-export" | "stack-cancel" | "stack-move";
|
|
7
|
+
export type PulumiStackOpKind = "stack-export" | "stack-cancel" | "stack-move" | "stack-action";
|
|
8
8
|
/** chant's gated-request API (#4551), injectable for tests. */
|
|
9
9
|
export interface GatedRequestApi {
|
|
10
10
|
requestGate: typeof RequestGate;
|
|
@@ -172,4 +172,63 @@ export declare function moveLeftovers(urns: readonly string[], source: ChangeSet
|
|
|
172
172
|
* for an empty or non-URN list or two engines.
|
|
173
173
|
*/
|
|
174
174
|
export declare function gatedStackMove(opts: PulumiStackMoveOptions): Promise<PulumiStackOpOutcome<PulumiStackMoveResult>>;
|
|
175
|
+
/** Why a stack action was refused before anything was asked (#4654). */
|
|
176
|
+
export type PulumiStackActionRefusal = "action-urn" | "action-type" | "action-scope";
|
|
177
|
+
/** A stack action refused before its request: `code` says why, `urns` names what the preview changed besides the action, for `action-scope`. */
|
|
178
|
+
export declare class PulumiStackActionRefused extends Error {
|
|
179
|
+
readonly code: PulumiStackActionRefusal;
|
|
180
|
+
readonly urns: string[];
|
|
181
|
+
constructor(code: PulumiStackActionRefusal, message: string, urns?: string[]);
|
|
182
|
+
}
|
|
183
|
+
/** The resource types an action may name: the command provider's (`command:local:Command`, `command:remote:Command`). */
|
|
184
|
+
export declare const PULUMI_ACTION_TYPE_PREFIX = "command:";
|
|
185
|
+
/** The subject an action's request is asked and approved under: `action:<stack>:<name>`, or the URN in place of a name. */
|
|
186
|
+
export declare const stackActionSubject: (stack: string, nameOrUrn: string) => string;
|
|
187
|
+
export interface PulumiStackActionOptions extends PulumiStackOpCommon {
|
|
188
|
+
/** The stack's directory, where `Pulumi.yaml` is. */
|
|
189
|
+
dir: string;
|
|
190
|
+
cwd?: string;
|
|
191
|
+
/** The stack, as `--stack` takes it. */
|
|
192
|
+
stack: string;
|
|
193
|
+
engine?: PulumiMemberEngine;
|
|
194
|
+
/** The `command:` resource whose replacement is the action. */
|
|
195
|
+
urn: string;
|
|
196
|
+
/** The action's name in the caller's config, which the subject carries in place of the URN. */
|
|
197
|
+
name?: string;
|
|
198
|
+
/** Ask anew even when the same request applied already. Absent, such a call answers `spent`, so a retried job never runs the action twice. */
|
|
199
|
+
anew?: boolean;
|
|
200
|
+
}
|
|
201
|
+
export interface PulumiStackActionResult {
|
|
202
|
+
urn: string;
|
|
203
|
+
engine: PulumiMemberEngine;
|
|
204
|
+
/** The preview's digest the request bound. */
|
|
205
|
+
preview: string;
|
|
206
|
+
/** The update's exit code: 0, since a failed update ends `failed`. */
|
|
207
|
+
exit: number;
|
|
208
|
+
/** The update's last lines of output. */
|
|
209
|
+
tail: string;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Check an action's URN before anything runs: a Pulumi URN on `stack` whose
|
|
213
|
+
* type is a command provider's. Throws {@link PulumiStackActionRefused}.
|
|
214
|
+
*/
|
|
215
|
+
export declare function checkStackActionUrn(stack: string, urn: string): void;
|
|
216
|
+
/**
|
|
217
|
+
* What a preview changes besides the action's replacement (#4654): each URN
|
|
218
|
+
* with a changing step other than `urn`, and `urn` itself when it changes
|
|
219
|
+
* other than by replacement or is not replaced at all. Empty when the
|
|
220
|
+
* preview replaces `urn` and nothing else.
|
|
221
|
+
*/
|
|
222
|
+
export declare function stackActionScope(urn: string, preview: unknown): {
|
|
223
|
+
others: string[];
|
|
224
|
+
problem?: string;
|
|
225
|
+
};
|
|
226
|
+
/**
|
|
227
|
+
* A day-2 action on a stack (#4654): the replacement of one `command:`
|
|
228
|
+
* resource, previewed, asked for under a gated request, and run once a
|
|
229
|
+
* second identity approves. Refused before anything runs for a URN that is
|
|
230
|
+
* not a command resource's (`action-type`), and before anything is asked for
|
|
231
|
+
* a preview that changes more than that replacement (`action-scope`).
|
|
232
|
+
*/
|
|
233
|
+
export declare function gatedStackAction(opts: PulumiStackActionOptions): Promise<PulumiStackOpOutcome<PulumiStackActionResult>>;
|
|
175
234
|
//# sourceMappingURL=stack-ops.d.ts.map
|
package/dist/stack-ops.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stack-ops.d.ts","sourceRoot":"","sources":["../src/stack-ops.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"stack-ops.d.ts","sourceRoot":"","sources":["../src/stack-ops.ts"],"names":[],"mappings":"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"}
|
package/dist/stack-ops.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* | export | `stack-export` | `<stack>` | `stack`, `engine`, `deliver: requester` |
|
|
17
17
|
* | cancel | `stack-cancel` | `<stack>` | `stack`, `lockId` |
|
|
18
18
|
* | move | `stack-move` | `move:<source>:<dest>` | `source`, `dest`, `engine`, `urns` (sorted, comma-joined) |
|
|
19
|
+
* | action | `stack-action` | `action:<stack>:<name>` (the URN without a name) | `stack`, `urn`, `engine`, `preview` (the preview's digest), `name` when given |
|
|
19
20
|
*
|
|
20
21
|
* The first call records the pending fact and answers `waiting` with the
|
|
21
22
|
* `chant approve` command. A later call with the same request reuses the
|
|
@@ -47,17 +48,30 @@
|
|
|
47
48
|
* again afterwards; a change left on either finishes the request `failed`
|
|
48
49
|
* with the changes named.
|
|
49
50
|
*
|
|
51
|
+
* - action (#4654): a day-2 action is a `command:` resource
|
|
52
|
+
* (`command:local:Command`, `command:remote:Command`) whose replacement is
|
|
53
|
+
* the operation. Its type is checked before anything runs (`action-type`).
|
|
54
|
+
* Each call previews `preview --json --target <urn> --replace <urn>`, and
|
|
55
|
+
* refuses (`action-scope`) unless the preview changes that URN alone, by
|
|
56
|
+
* a replacement. The request binds the preview's digest, so a preview
|
|
57
|
+
* that moved after the approval asks again. Once approved, `up --yes
|
|
58
|
+
* --target <urn> --replace <urn> --stack <s>` under the stack's engine,
|
|
59
|
+
* and the finished line keeps chant's `applied | failed` with
|
|
60
|
+
* `detail.result` `ok` or `failed`, the exit code and the output's last
|
|
61
|
+
* lines. A request that applied answers `spent` to the same call, unless
|
|
62
|
+
* the caller asks `anew`.
|
|
63
|
+
*
|
|
50
64
|
* The writes go through `runGatedPulumi` and `runGatedPlunge`
|
|
51
65
|
* (`./run.ts`), whose fixed forms are the whole of what may run.
|
|
52
66
|
*/
|
|
53
67
|
import { createHash } from "node:crypto";
|
|
54
68
|
import { existsSync, readdirSync, writeFileSync } from "node:fs";
|
|
55
69
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
56
|
-
import { pulumiNodeKey } from "@intentius/chant/pulumi-urn";
|
|
70
|
+
import { parsePulumiUrn, pulumiNodeKey } from "@intentius/chant/pulumi-urn";
|
|
57
71
|
import { pulumiStackBackend } from "./adopt.js";
|
|
58
|
-
import { pulumiExportResources } from "./change-set.js";
|
|
72
|
+
import { pulumiExportResources, pulumiPreviewDigest, pulumiSteps, PULUMI_UNCHANGED_OPS } from "./change-set.js";
|
|
59
73
|
import { pulumiMemberEngine } from "./member-engine.js";
|
|
60
|
-
import { defaultPlungeRunner, defaultPulumiRunner, plungeMoveArgs, previewPulumiMemberRead, pulumiCancelArgs, pulumiRunFailure, pulumiStateMoveArgs, runAllowedPlunge, runAllowedPulumi, runGatedPlunge, runGatedPulumi, } from "./run.js";
|
|
74
|
+
import { defaultPlungeRunner, defaultPulumiRunner, plungeMoveArgs, previewPulumiMemberRead, pulumiActionPreviewArgs, pulumiActionUpArgs, pulumiCancelArgs, pulumiRunFailure, pulumiStateMoveArgs, runAllowedPlunge, runAllowedPulumi, runGatedPlunge, runGatedPulumi, } from "./run.js";
|
|
61
75
|
import { isObject } from "./values.js";
|
|
62
76
|
/** The op the requests are recorded under, unless the caller names its own. */
|
|
63
77
|
export const PULUMI_STACK_OPS_OP = "pulumi-stack-ops";
|
|
@@ -80,12 +94,13 @@ const sameFields = (a, b) => Object.keys(a).length === Object.keys(b).length &&
|
|
|
80
94
|
* every operation. `precheck` runs after the approval and before the request
|
|
81
95
|
* is spent, so a refusal there leaves the approval unspent.
|
|
82
96
|
*/
|
|
83
|
-
async function askThenAct(common, kind, subject, fields, description, act, precheck) {
|
|
97
|
+
async function askThenAct(common, kind, subject, fields, description, act, precheck, options = {}) {
|
|
84
98
|
const api = await gatedApi(common.gated);
|
|
85
99
|
const op = common.op ?? PULUMI_STACK_OPS_OP;
|
|
86
100
|
const cwd = common.ledgerCwd ?? process.cwd();
|
|
101
|
+
// With reuseApplied (#4654), a request with these fields that applied already is asked again as itself, so it answers `spent`.
|
|
87
102
|
const standing = (await api.readGatedRequests(op, { cwd }).catch(() => []))
|
|
88
|
-
.filter((r) => !r.applied && r.request.kind === kind && r.request.subject === subject && r.request.by === common.by && sameFields(r.request.fields, fields))
|
|
103
|
+
.filter((r) => (options.reuseApplied || !r.applied) && r.request.kind === kind && r.request.subject === subject && r.request.by === common.by && sameFields(r.request.fields, fields))
|
|
89
104
|
.at(-1);
|
|
90
105
|
const request = { kind, subject, by: common.by, at: standing?.request.at ?? common.now ?? new Date().toISOString(), fields };
|
|
91
106
|
const asked = await api.requestGate({
|
|
@@ -398,3 +413,121 @@ export async function gatedStackMove(opts) {
|
|
|
398
413
|
}
|
|
399
414
|
});
|
|
400
415
|
}
|
|
416
|
+
/** A stack action refused before its request: `code` says why, `urns` names what the preview changed besides the action, for `action-scope`. */
|
|
417
|
+
export class PulumiStackActionRefused extends Error {
|
|
418
|
+
constructor(code, message, urns = []) {
|
|
419
|
+
super(`${code}: ${message}`);
|
|
420
|
+
this.name = "PulumiStackActionRefused";
|
|
421
|
+
this.code = code;
|
|
422
|
+
this.urns = urns;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
/** The resource types an action may name: the command provider's (`command:local:Command`, `command:remote:Command`). */
|
|
426
|
+
export const PULUMI_ACTION_TYPE_PREFIX = "command:";
|
|
427
|
+
/** An action's name as a caller's config gives it, for the request's subject. */
|
|
428
|
+
const ACTION_NAME = /^[A-Za-z0-9_][A-Za-z0-9_.-]{0,99}$/;
|
|
429
|
+
/** The subject an action's request is asked and approved under: `action:<stack>:<name>`, or the URN in place of a name. */
|
|
430
|
+
export const stackActionSubject = (stack, nameOrUrn) => `action:${stack}:${nameOrUrn}`;
|
|
431
|
+
/** The last `n` lines of `text`, trimmed. */
|
|
432
|
+
const lastLines = (text, n) => (text ?? "").trim().split("\n").slice(-n).join("\n");
|
|
433
|
+
/**
|
|
434
|
+
* Check an action's URN before anything runs: a Pulumi URN on `stack` whose
|
|
435
|
+
* type is a command provider's. Throws {@link PulumiStackActionRefused}.
|
|
436
|
+
*/
|
|
437
|
+
export function checkStackActionUrn(stack, urn) {
|
|
438
|
+
const parsed = parsePulumiUrn(urn);
|
|
439
|
+
if (!parsed)
|
|
440
|
+
throw new PulumiStackActionRefused("action-urn", `${JSON.stringify(urn)} is not a resource URN (urn:pulumi:<stack>::<project>::<type>::<name>)`);
|
|
441
|
+
const stackName = stack.split("/").at(-1);
|
|
442
|
+
if (parsed.stack !== stackName)
|
|
443
|
+
throw new PulumiStackActionRefused("action-urn", `${urn} names stack ${parsed.stack}, and the action runs on ${stack}`);
|
|
444
|
+
if (!parsed.type.startsWith(PULUMI_ACTION_TYPE_PREFIX)) {
|
|
445
|
+
throw new PulumiStackActionRefused("action-type", `${urn} is a ${parsed.type}, and an action is a command resource (command:local:Command or command:remote:Command), whose replacement runs it; replacing any other resource is an update`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* What a preview changes besides the action's replacement (#4654): each URN
|
|
450
|
+
* with a changing step other than `urn`, and `urn` itself when it changes
|
|
451
|
+
* other than by replacement or is not replaced at all. Empty when the
|
|
452
|
+
* preview replaces `urn` and nothing else.
|
|
453
|
+
*/
|
|
454
|
+
export function stackActionScope(urn, preview) {
|
|
455
|
+
const steps = pulumiSteps(preview) ?? [];
|
|
456
|
+
const others = new Set();
|
|
457
|
+
let replaced = false;
|
|
458
|
+
let problem;
|
|
459
|
+
for (const s of steps) {
|
|
460
|
+
const op = s.op ?? "";
|
|
461
|
+
if (PULUMI_UNCHANGED_OPS.has(op))
|
|
462
|
+
continue;
|
|
463
|
+
if (s.urn !== urn) {
|
|
464
|
+
others.add(s.urn);
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
if (op === "replace" || op === "create-replacement" || op === "delete-replaced")
|
|
468
|
+
replaced = true;
|
|
469
|
+
else
|
|
470
|
+
problem = `the preview shows ${op} of ${urn}, not its replacement`;
|
|
471
|
+
}
|
|
472
|
+
if (!problem && !replaced)
|
|
473
|
+
problem = `the preview does not replace ${urn}`;
|
|
474
|
+
return { others: [...others].sort(), ...(problem ? { problem } : {}) };
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* A day-2 action on a stack (#4654): the replacement of one `command:`
|
|
478
|
+
* resource, previewed, asked for under a gated request, and run once a
|
|
479
|
+
* second identity approves. Refused before anything runs for a URN that is
|
|
480
|
+
* not a command resource's (`action-type`), and before anything is asked for
|
|
481
|
+
* a preview that changes more than that replacement (`action-scope`).
|
|
482
|
+
*/
|
|
483
|
+
export async function gatedStackAction(opts) {
|
|
484
|
+
checkStackActionUrn(opts.stack, opts.urn);
|
|
485
|
+
if (opts.name !== undefined && !ACTION_NAME.test(opts.name))
|
|
486
|
+
throw new Error(`an action's name is letters, digits, "_", "." and "-", not ${JSON.stringify(opts.name)}`);
|
|
487
|
+
const env = opts.env ?? process.env;
|
|
488
|
+
const dir = stackDirOf(opts.dir, opts.cwd);
|
|
489
|
+
const engine = engineOf(dir, opts.engine, env);
|
|
490
|
+
const target = { stack: opts.stack, urn: opts.urn };
|
|
491
|
+
const previewArgs = pulumiActionPreviewArgs(target);
|
|
492
|
+
let stdout;
|
|
493
|
+
try {
|
|
494
|
+
({ stdout } = engine === "plunge" ? await runAllowedPlunge(opts.plunge ?? defaultPlungeRunner, previewArgs, dir) : await runAllowedPulumi(opts.run ?? defaultPulumiRunner, previewArgs, dir));
|
|
495
|
+
}
|
|
496
|
+
catch (err) {
|
|
497
|
+
throw new Error(`${engine} ${previewArgs.join(" ")} failed: ${why(err, engine)}`);
|
|
498
|
+
}
|
|
499
|
+
let preview;
|
|
500
|
+
try {
|
|
501
|
+
preview = JSON.parse(stdout);
|
|
502
|
+
}
|
|
503
|
+
catch {
|
|
504
|
+
throw new Error(`${engine} ${previewArgs.join(" ")} printed no JSON`);
|
|
505
|
+
}
|
|
506
|
+
const scope = stackActionScope(opts.urn, preview);
|
|
507
|
+
if (scope.others.length > 0) {
|
|
508
|
+
throw new PulumiStackActionRefused("action-scope", `the preview of ${opts.stack} replacing ${opts.urn} also changes ${scope.others.join(", ")}, and an action replaces its command resource alone; nothing was asked for`, scope.others);
|
|
509
|
+
}
|
|
510
|
+
if (scope.problem)
|
|
511
|
+
throw new PulumiStackActionRefused("action-scope", `${scope.problem}; nothing was asked for`);
|
|
512
|
+
const digest = pulumiPreviewDigest(preview, { changedOnly: true });
|
|
513
|
+
const subject = stackActionSubject(opts.stack, opts.name ?? opts.urn);
|
|
514
|
+
const fields = { stack: opts.stack, urn: opts.urn, engine, preview: digest, ...(opts.name ? { name: opts.name } : {}) };
|
|
515
|
+
return askThenAct(opts, "stack-action", subject, fields, `action ${opts.name ?? opts.urn} on stack ${opts.stack}: replace ${opts.urn}, requested by ${opts.by}`, async () => {
|
|
516
|
+
const args = pulumiActionUpArgs(target);
|
|
517
|
+
try {
|
|
518
|
+
const done = engine === "plunge" ? await runGatedPlunge(opts.plunge ?? defaultPlungeRunner, args, dir) : await runGatedPulumi(opts.run ?? defaultPulumiRunner, args, dir);
|
|
519
|
+
const tail = lastLines(done.stdout, 10);
|
|
520
|
+
return { result: { urn: opts.urn, engine, preview: digest, exit: 0, tail }, detail: { result: "ok", exit: 0, tail: tail.slice(-400), urn: opts.urn } };
|
|
521
|
+
}
|
|
522
|
+
catch (err) {
|
|
523
|
+
const f = pulumiRunFailure(err);
|
|
524
|
+
const exit = f.exitCode ?? 1;
|
|
525
|
+
const tail = lastLines(`${f.stdout ?? ""}\n${f.stderr ?? ""}`, 10);
|
|
526
|
+
return {
|
|
527
|
+
result: { urn: opts.urn, engine, preview: digest, exit, tail },
|
|
528
|
+
detail: { result: "failed", exit, tail: tail.slice(-400), urn: opts.urn },
|
|
529
|
+
failed: `${engine} ${args.join(" ")} failed: ${why(err, engine)}`,
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
}, undefined, { reuseApplied: !opts.anew });
|
|
533
|
+
}
|
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.58",
|
|
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.58",
|
|
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/index.ts
CHANGED
|
@@ -108,7 +108,7 @@ export { readPulumiDrift, type PulumiDriftRunners } from "./drift-read";
|
|
|
108
108
|
export { pulumiAuditCatalog } from "./lint/audit-catalog";
|
|
109
109
|
// The Preview/Drift watch composite and its step builders (#4494).
|
|
110
110
|
export { PulumiWatchOp, type PulumiWatchOpConfig, type PulumiWatchOpResources } from "./composites/pulumi-watch-op";
|
|
111
|
-
export { gatedStackExport, gatedStackCancel, gatedStackMove, moveLeftovers, diyStackLockIds, pulumiStackLockIds, type PulumiStackLocks, PULUMI_STACK_OPS_OP, type PulumiStackOpOutcome, type PulumiStackOpCommon, type PulumiStackOpKind } from "./stack-ops";
|
|
111
|
+
export { gatedStackExport, gatedStackCancel, gatedStackMove, gatedStackAction, checkStackActionUrn, stackActionScope, stackActionSubject, PulumiStackActionRefused, PULUMI_ACTION_TYPE_PREFIX, type PulumiStackActionOptions, type PulumiStackActionResult, type PulumiStackActionRefusal, moveLeftovers, diyStackLockIds, pulumiStackLockIds, type PulumiStackLocks, PULUMI_STACK_OPS_OP, type PulumiStackOpOutcome, type PulumiStackOpCommon, type PulumiStackOpKind } from "./stack-ops";
|
|
112
112
|
// Review stacks (INTENTIUS/plywood#15): pr-<n> stacks only.
|
|
113
113
|
export {
|
|
114
114
|
initReviewStack,
|
package/src/run.test.ts
CHANGED
|
@@ -14,6 +14,8 @@ import {
|
|
|
14
14
|
plungeAdoptArgs,
|
|
15
15
|
plungeGraphArgs,
|
|
16
16
|
plungeMoveArgs,
|
|
17
|
+
pulumiActionPreviewArgs,
|
|
18
|
+
pulumiActionUpArgs,
|
|
17
19
|
pulumiCancelArgs,
|
|
18
20
|
pulumiStateMoveArgs,
|
|
19
21
|
runGatedPlunge,
|
|
@@ -93,6 +95,44 @@ describe("gated stack operations (#4578)", () => {
|
|
|
93
95
|
});
|
|
94
96
|
});
|
|
95
97
|
|
|
98
|
+
describe("a stack action's forms (#4654)", () => {
|
|
99
|
+
const u = "urn:pulumi:prod-us::shop::command:local:Command::rotate";
|
|
100
|
+
const other = "urn:pulumi:prod-us::shop::command:local:Command::flush";
|
|
101
|
+
test("the preview is a read; the update passes the gated entry points only", async () => {
|
|
102
|
+
const { run, calls } = fake(() => "{}");
|
|
103
|
+
await runAllowedPulumi(run, pulumiActionPreviewArgs({ stack: "prod-us", urn: u }), "/tmp");
|
|
104
|
+
await runAllowedPlunge(run, pulumiActionPreviewArgs({ stack: "prod-us", urn: u }), "/tmp");
|
|
105
|
+
await runGatedPulumi(run, pulumiActionUpArgs({ stack: "prod-us", urn: u }), "/tmp");
|
|
106
|
+
await runGatedPlunge(run, pulumiActionUpArgs({ stack: "prod-us", urn: u }), "/tmp");
|
|
107
|
+
const up = ["up", "--yes", "--target", u, "--replace", u, "--stack", "prod-us"];
|
|
108
|
+
expect(calls.map((c) => c.args)).toEqual([
|
|
109
|
+
["preview", "--json", "--target", u, "--replace", u, "--stack", "prod-us"],
|
|
110
|
+
["preview", "--json", "--target", u, "--replace", u, "--stack", "prod-us"],
|
|
111
|
+
up,
|
|
112
|
+
up,
|
|
113
|
+
]);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("no other update form gets through", async () => {
|
|
117
|
+
const { run, calls } = fake(() => "{}");
|
|
118
|
+
const up = pulumiActionUpArgs({ stack: "prod-us", urn: u });
|
|
119
|
+
await expect(runAllowedPulumi(run, up, "/tmp")).rejects.toThrow(/not pulumi up/);
|
|
120
|
+
await expect(runApplyPulumi(run, up, "/tmp")).rejects.toThrow();
|
|
121
|
+
for (const args of [
|
|
122
|
+
["up", "--yes", "--target", u, "--replace", other, "--stack", "prod-us"],
|
|
123
|
+
["up", "--yes", "--replace", u, "--stack", "prod-us"],
|
|
124
|
+
["up", "--yes", "--target", u, "--replace", u, "--stack", "prod-us", "--skip-preview"],
|
|
125
|
+
["up", "--target", u, "--replace", u, "--stack", "prod-us"],
|
|
126
|
+
["up", "--yes", "--target", "rotate", "--replace", "rotate", "--stack", "prod-us"],
|
|
127
|
+
["up", "--yes", "--target", u, "--replace", u, "--stack", "--json"],
|
|
128
|
+
]) {
|
|
129
|
+
await expect(runGatedPulumi(run, args, "/tmp")).rejects.toThrow(/gated Pulumi stack operation only as/);
|
|
130
|
+
await expect(runGatedPlunge(run, args, "/tmp")).rejects.toThrow(/plunge stack action only as/);
|
|
131
|
+
}
|
|
132
|
+
expect(calls).toEqual([]);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
96
136
|
describe("plunge stack adopt (#4562)", () => {
|
|
97
137
|
test("the preview form passes runAllowedPlunge, and only runAdoptPlunge runs --yes", async () => {
|
|
98
138
|
const { run, calls } = fake(() => "{}");
|
package/src/run.ts
CHANGED
|
@@ -492,13 +492,45 @@ export async function runAdoptPlunge(run: PulumiRunner, args: string[], cwd: str
|
|
|
492
492
|
*
|
|
493
493
|
* - `pulumi cancel --yes --stack <stack>`: releases a stuck update's lock;
|
|
494
494
|
* - `pulumi state move --source <stack> --dest <stack> --yes <urn>...`: moves
|
|
495
|
-
* resources between stock stacks, never `--include-parents
|
|
495
|
+
* resources between stock stacks, never `--include-parents`;
|
|
496
|
+
* - `pulumi up --yes --target <urn> --replace <urn> --stack <stack>`: a
|
|
497
|
+
* day-2 action (#4654), the replacement of one `command:` resource, the
|
|
498
|
+
* same URN in both places. Its preview, `preview --json --target <urn>
|
|
499
|
+
* --replace <urn> --stack <stack>`, is a read and passes
|
|
500
|
+
* {@link runAllowedPulumi}. `up` takes `--replace` here only:
|
|
501
|
+
* {@link PULUMI_APPLY_COMMANDS} still refuses it.
|
|
496
502
|
*/
|
|
497
503
|
export const PULUMI_GATED_COMMANDS: readonly string[] = [
|
|
498
504
|
"pulumi cancel --yes --stack <stack>",
|
|
499
505
|
"pulumi state move --source <stack> --dest <stack> --yes <urn>...",
|
|
506
|
+
"pulumi up --yes --target <urn> --replace <urn> --stack <stack>",
|
|
500
507
|
];
|
|
501
508
|
|
|
509
|
+
/** A stack action's preview (#4654): `preview --json --target <urn> --replace <urn> --stack <stack>`, a read. */
|
|
510
|
+
export function pulumiActionPreviewArgs(opts: { stack: string; urn: string }): string[] {
|
|
511
|
+
return ["preview", "--json", "--target", opts.urn, "--replace", opts.urn, "--stack", opts.stack];
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/** A stack action's update (#4654): `up --yes --target <urn> --replace <urn> --stack <stack>`, under `pulumi` or `plunge`. */
|
|
515
|
+
export function pulumiActionUpArgs(opts: { stack: string; urn: string }): string[] {
|
|
516
|
+
return ["up", "--yes", "--target", opts.urn, "--replace", opts.urn, "--stack", opts.stack];
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/** Exactly {@link pulumiActionUpArgs}' form: one URN, targeted and replaced, on one stack. */
|
|
520
|
+
function allowedActionUp(args: readonly string[]): boolean {
|
|
521
|
+
return (
|
|
522
|
+
args.length === 8 &&
|
|
523
|
+
args[0] === "up" &&
|
|
524
|
+
args[1] === "--yes" &&
|
|
525
|
+
args[2] === "--target" &&
|
|
526
|
+
isUrn(args[3]) &&
|
|
527
|
+
args[4] === "--replace" &&
|
|
528
|
+
args[5] === args[3] &&
|
|
529
|
+
args[6] === "--stack" &&
|
|
530
|
+
isValue(args[7])
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
|
|
502
534
|
/**
|
|
503
535
|
* plunge's resource move (INTENTIUS/plunge#56) in its two forms. The first
|
|
504
536
|
* prints the plan and writes nothing, so it passes {@link runAllowedPlunge};
|
|
@@ -507,6 +539,7 @@ export const PULUMI_GATED_COMMANDS: readonly string[] = [
|
|
|
507
539
|
export const PLUNGE_GATED_COMMANDS: readonly string[] = [
|
|
508
540
|
"plunge stack move --stack <stack> --dest <stack> <urn>... --json",
|
|
509
541
|
"plunge stack move --stack <stack> --dest <stack> <urn>... --json --yes",
|
|
542
|
+
"plunge up --yes --target <urn> --replace <urn> --stack <stack>",
|
|
510
543
|
];
|
|
511
544
|
|
|
512
545
|
const isUrn = (v: string | undefined): v is string => typeof v === "string" && v.startsWith("urn:pulumi:");
|
|
@@ -539,6 +572,7 @@ function allowedGatedPulumi(args: string[]): boolean {
|
|
|
539
572
|
const urns = args.slice(7);
|
|
540
573
|
return args[2] === "--source" && isValue(args[3]) && args[4] === "--dest" && isValue(args[5]) && args[6] === "--yes" && urns.length > 0 && urns.every(isUrn);
|
|
541
574
|
}
|
|
575
|
+
if (args[0] === "up") return allowedActionUp(args);
|
|
542
576
|
return false;
|
|
543
577
|
}
|
|
544
578
|
|
|
@@ -550,9 +584,11 @@ export async function runGatedPulumi(run: PulumiRunner, args: string[], cwd: str
|
|
|
550
584
|
return run(args, env ? { cwd, env } : { cwd });
|
|
551
585
|
}
|
|
552
586
|
|
|
553
|
-
/** Run
|
|
587
|
+
/** Run a writing form of {@link PLUNGE_GATED_COMMANDS}, the move or a stack action's update, refusing any other argv. Only `./stack-ops.ts` calls it, after the request is spent. */
|
|
554
588
|
export async function runGatedPlunge(run: PulumiRunner, args: string[], cwd: string, env?: Record<string, string>): Promise<{ stdout: string }> {
|
|
555
|
-
if (
|
|
589
|
+
if (args[0] === "up") {
|
|
590
|
+
if (!allowedActionUp(args)) throw new Error(`chant runs a plunge stack action only as ${PLUNGE_GATED_COMMANDS[2]}, not plunge ${args.join(" ")}`);
|
|
591
|
+
} else if (!allowedPlungeMove(args, true)) {
|
|
556
592
|
throw new Error(`chant moves plunge resources only as ${PLUNGE_GATED_COMMANDS[1]}, not plunge ${args.join(" ")}`);
|
|
557
593
|
}
|
|
558
594
|
return run(args, env ? { cwd, env } : { cwd });
|
package/src/stack-ops.test.ts
CHANGED
|
@@ -16,8 +16,11 @@ import {
|
|
|
16
16
|
diyStackLockIds,
|
|
17
17
|
gatedStackCancel,
|
|
18
18
|
gatedStackExport,
|
|
19
|
+
gatedStackAction,
|
|
19
20
|
gatedStackMove,
|
|
20
21
|
moveLeftovers,
|
|
22
|
+
PulumiStackActionRefused,
|
|
23
|
+
stackActionScope,
|
|
21
24
|
pulumiStackLockIds,
|
|
22
25
|
PULUMI_STACK_OPS_OP,
|
|
23
26
|
type GatedRequestApi,
|
|
@@ -284,3 +287,112 @@ describe("the stack lock reader (chant#4615)", () => {
|
|
|
284
287
|
expect(await pulumiStackLockIds({ dir, stack: "dev", engine: "plunge", env: {} })).toEqual({ checked: true, ids: [] });
|
|
285
288
|
});
|
|
286
289
|
});
|
|
290
|
+
|
|
291
|
+
describe("action (chant#4654)", () => {
|
|
292
|
+
const rotate = "urn:pulumi:prod-us::shop::command:local:Command::rotate";
|
|
293
|
+
const bucket = "urn:pulumi:prod-us::shop::aws:s3/bucket:Bucket::b";
|
|
294
|
+
const stackUrn = "urn:pulumi:prod-us::shop::pulumi:pulumi:Stack::shop-prod-us";
|
|
295
|
+
const replaceSteps = (urn: string) => [
|
|
296
|
+
{ op: "create-replacement", urn, newState: { type: "command:local:Command", inputs: { create: "./rotate.sh" } } },
|
|
297
|
+
{ op: "replace", urn, newState: { type: "command:local:Command", inputs: { create: "./rotate.sh" } } },
|
|
298
|
+
{ op: "delete-replaced", urn, oldState: { type: "command:local:Command", inputs: { create: "./rotate.sh" } } },
|
|
299
|
+
];
|
|
300
|
+
const doc = (steps: unknown[]) => JSON.stringify({ steps: [{ op: "same", urn: stackUrn }, ...steps] });
|
|
301
|
+
const previewArgs = ["preview", "--json", "--target", rotate, "--replace", rotate, "--stack", "prod-us"];
|
|
302
|
+
const upArgs = ["up", "--yes", "--target", rotate, "--replace", rotate, "--stack", "prod-us"];
|
|
303
|
+
|
|
304
|
+
test("the first call previews the replacement and answers waiting, with no update", async () => {
|
|
305
|
+
const l = ledger();
|
|
306
|
+
const pulumi = runner(() => doc(replaceSteps(rotate)));
|
|
307
|
+
const first = await gatedStackAction({ by: "alice", rules: { mode: "ledger" }, gated: l.api, env: {}, run: pulumi.run, dir: stackDir(), stack: "prod-us", engine: "pulumi", urn: rotate, name: "rotate" });
|
|
308
|
+
expect(first.status).toBe("waiting");
|
|
309
|
+
expect(first.digest).toMatch(/^sha256:/);
|
|
310
|
+
expect(pulumi.calls).toEqual([previewArgs]);
|
|
311
|
+
expect(pulumi.calls.some((c) => c[0] === "up")).toBe(false);
|
|
312
|
+
expect(first.request).toMatchObject({
|
|
313
|
+
kind: "stack-action",
|
|
314
|
+
subject: "action:prod-us:rotate",
|
|
315
|
+
fields: { stack: "prod-us", urn: rotate, engine: "pulumi", name: "rotate", preview: expect.stringMatching(/sha256:/) },
|
|
316
|
+
});
|
|
317
|
+
expect(l.spent.size).toBe(0);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test("a preview that replaces a second resource is refused action-scope, naming it, and asks for nothing", async () => {
|
|
321
|
+
const l = ledger();
|
|
322
|
+
const pulumi = runner(() => doc([...replaceSteps(rotate), ...replaceSteps(bucket)]));
|
|
323
|
+
const call = gatedStackAction({ by: "alice", rules: { mode: "ledger" }, gated: l.api, env: {}, run: pulumi.run, dir: stackDir(), stack: "prod-us", engine: "pulumi", urn: rotate });
|
|
324
|
+
await expect(call).rejects.toThrow(PulumiStackActionRefused);
|
|
325
|
+
await expect(call).rejects.toMatchObject({ code: "action-scope", urns: [bucket] });
|
|
326
|
+
await expect(call).rejects.toThrow(new RegExp(`^action-scope: .*${bucket.replace(/[$.]/g, "\\$&")}`));
|
|
327
|
+
expect(l.pending).toEqual([]);
|
|
328
|
+
expect(pulumi.calls.every((c) => c[0] === "preview")).toBe(true);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test("a preview that does not replace the URN, or updates it in place, is refused action-scope", () => {
|
|
332
|
+
expect(stackActionScope(rotate, JSON.parse(doc([])))).toEqual({ others: [], problem: `the preview does not replace ${rotate}` });
|
|
333
|
+
expect(stackActionScope(rotate, JSON.parse(doc([{ op: "update", urn: rotate }])))).toMatchObject({ problem: `the preview shows update of ${rotate}, not its replacement` });
|
|
334
|
+
expect(stackActionScope(rotate, JSON.parse(doc(replaceSteps(rotate))))).toEqual({ others: [] });
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test("a URN of another type is refused action-type before any engine call", async () => {
|
|
338
|
+
const l = ledger();
|
|
339
|
+
const pulumi = runner(() => doc([]));
|
|
340
|
+
const common = { by: "alice", rules: { mode: "ledger" as const }, gated: l.api, env: {}, run: pulumi.run, dir: stackDir(), stack: "prod-us", engine: "pulumi" as const };
|
|
341
|
+
await expect(gatedStackAction({ ...common, urn: bucket })).rejects.toMatchObject({ code: "action-type" });
|
|
342
|
+
await expect(gatedStackAction({ ...common, urn: bucket })).rejects.toThrow(/aws:s3\/bucket:Bucket/);
|
|
343
|
+
await expect(gatedStackAction({ ...common, urn: "rotate" })).rejects.toMatchObject({ code: "action-urn" });
|
|
344
|
+
await expect(gatedStackAction({ ...common, urn: "urn:pulumi:dev::shop::command:local:Command::rotate" })).rejects.toMatchObject({ code: "action-urn" });
|
|
345
|
+
expect(pulumi.calls).toEqual([]);
|
|
346
|
+
expect(l.pending).toEqual([]);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test("after a second identity approves, the update runs once, applied and finished are written, and a repeat is spent", async () => {
|
|
350
|
+
const l = ledger();
|
|
351
|
+
const pulumi = runner((args) => (args[0] === "up" ? "Updating (prod-us)\n ++ command:local:Command rotate created replacement\nResources: 1 replaced" : doc(replaceSteps(rotate))));
|
|
352
|
+
const opts = { by: "alice", rules: { mode: "ledger" as const }, gated: l.api, env: {}, run: pulumi.run, dir: stackDir(), stack: "prod-us", engine: "pulumi" as const, urn: rotate, name: "rotate" };
|
|
353
|
+
const first = await gatedStackAction(opts);
|
|
354
|
+
l.approve(first.digest, "alice");
|
|
355
|
+
expect((await gatedStackAction(opts)).status).toBe("refused");
|
|
356
|
+
expect(pulumi.calls.filter((c) => c[0] === "up")).toEqual([]);
|
|
357
|
+
|
|
358
|
+
l.approve(first.digest, "bob");
|
|
359
|
+
const done = await gatedStackAction(opts);
|
|
360
|
+
expect(done.status).toBe("done");
|
|
361
|
+
expect(done.digest).toBe(first.digest);
|
|
362
|
+
if (done.status !== "done") throw new Error("not done");
|
|
363
|
+
expect(done.result).toMatchObject({ urn: rotate, engine: "pulumi", exit: 0, tail: expect.stringContaining("1 replaced") });
|
|
364
|
+
expect(pulumi.calls.filter((c) => c[0] === "up")).toEqual([upArgs]);
|
|
365
|
+
expect(l.spent.has(first.digest)).toBe(true);
|
|
366
|
+
expect(l.finished).toEqual([{ digest: first.digest, result: "applied", detail: expect.objectContaining({ result: "ok", exit: 0, urn: rotate }) }]);
|
|
367
|
+
|
|
368
|
+
const again = await gatedStackAction(opts);
|
|
369
|
+
expect(again.status).toBe("spent");
|
|
370
|
+
expect(again.digest).toBe(first.digest);
|
|
371
|
+
expect(pulumi.calls.filter((c) => c[0] === "up")).toHaveLength(1);
|
|
372
|
+
// Asked anew, the same action is a new request that waits for its own approval.
|
|
373
|
+
const anew = await gatedStackAction({ ...opts, anew: true, now: "2000-01-01T00:00:00.000Z" });
|
|
374
|
+
expect(anew.status).toBe("waiting");
|
|
375
|
+
expect(pulumi.calls.filter((c) => c[0] === "up")).toHaveLength(1);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
test("a failed update finishes failed with the exit code and the output's tail", async () => {
|
|
379
|
+
const l = ledger();
|
|
380
|
+
const pulumi = runner((args) => (args[0] === "up" ? Object.assign(new Error("exit 255"), { code: 255, stdout: "error: rotate.sh exited 3" }) : doc(replaceSteps(rotate))));
|
|
381
|
+
const opts = { by: "alice", rules: { mode: "ledger" as const }, gated: l.api, env: {}, run: pulumi.run, dir: stackDir(), stack: "prod-us", engine: "pulumi" as const, urn: rotate };
|
|
382
|
+
l.approve((await gatedStackAction(opts)).digest, "bob");
|
|
383
|
+
const failed = await gatedStackAction(opts);
|
|
384
|
+
expect(failed.status).toBe("failed");
|
|
385
|
+
expect(l.finished[0]).toMatchObject({ result: "failed", detail: { result: "failed", exit: 255, tail: "error: rotate.sh exited 3", urn: rotate } });
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
test("a stack on plunge previews and updates under plunge", async () => {
|
|
389
|
+
const l = ledger();
|
|
390
|
+
const plunge = runner((args) => (args[0] === "up" ? "done" : doc(replaceSteps(rotate))));
|
|
391
|
+
const opts = { by: "alice", rules: { mode: "ledger" as const }, gated: l.api, env: {}, plunge: plunge.run, dir: stackDir(), stack: "prod-us", engine: "plunge" as const, urn: rotate };
|
|
392
|
+
const first = await gatedStackAction(opts);
|
|
393
|
+
expect(first.request.fields.engine).toBe("plunge");
|
|
394
|
+
l.approve(first.digest, "bob");
|
|
395
|
+
expect((await gatedStackAction(opts)).status).toBe("done");
|
|
396
|
+
expect(plunge.calls).toEqual([previewArgs, previewArgs, upArgs]);
|
|
397
|
+
});
|
|
398
|
+
});
|
package/src/stack-ops.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* | export | `stack-export` | `<stack>` | `stack`, `engine`, `deliver: requester` |
|
|
17
17
|
* | cancel | `stack-cancel` | `<stack>` | `stack`, `lockId` |
|
|
18
18
|
* | move | `stack-move` | `move:<source>:<dest>` | `source`, `dest`, `engine`, `urns` (sorted, comma-joined) |
|
|
19
|
+
* | action | `stack-action` | `action:<stack>:<name>` (the URN without a name) | `stack`, `urn`, `engine`, `preview` (the preview's digest), `name` when given |
|
|
19
20
|
*
|
|
20
21
|
* The first call records the pending fact and answers `waiting` with the
|
|
21
22
|
* `chant approve` command. A later call with the same request reuses the
|
|
@@ -47,6 +48,19 @@
|
|
|
47
48
|
* again afterwards; a change left on either finishes the request `failed`
|
|
48
49
|
* with the changes named.
|
|
49
50
|
*
|
|
51
|
+
* - action (#4654): a day-2 action is a `command:` resource
|
|
52
|
+
* (`command:local:Command`, `command:remote:Command`) whose replacement is
|
|
53
|
+
* the operation. Its type is checked before anything runs (`action-type`).
|
|
54
|
+
* Each call previews `preview --json --target <urn> --replace <urn>`, and
|
|
55
|
+
* refuses (`action-scope`) unless the preview changes that URN alone, by
|
|
56
|
+
* a replacement. The request binds the preview's digest, so a preview
|
|
57
|
+
* that moved after the approval asks again. Once approved, `up --yes
|
|
58
|
+
* --target <urn> --replace <urn> --stack <s>` under the stack's engine,
|
|
59
|
+
* and the finished line keeps chant's `applied | failed` with
|
|
60
|
+
* `detail.result` `ok` or `failed`, the exit code and the output's last
|
|
61
|
+
* lines. A request that applied answers `spent` to the same call, unless
|
|
62
|
+
* the caller asks `anew`.
|
|
63
|
+
*
|
|
50
64
|
* The writes go through `runGatedPulumi` and `runGatedPlunge`
|
|
51
65
|
* (`./run.ts`), whose fixed forms are the whole of what may run.
|
|
52
66
|
*/
|
|
@@ -63,15 +77,17 @@ import type {
|
|
|
63
77
|
GatedRequestRules,
|
|
64
78
|
RequestGateResult,
|
|
65
79
|
} from "@intentius/chant/lifecycle/gated-request";
|
|
66
|
-
import { pulumiNodeKey } from "@intentius/chant/pulumi-urn";
|
|
80
|
+
import { parsePulumiUrn, pulumiNodeKey } from "@intentius/chant/pulumi-urn";
|
|
67
81
|
import { pulumiStackBackend } from "./adopt";
|
|
68
|
-
import { pulumiExportResources } from "./change-set";
|
|
82
|
+
import { pulumiExportResources, pulumiPreviewDigest, pulumiSteps, PULUMI_UNCHANGED_OPS } from "./change-set";
|
|
69
83
|
import { pulumiMemberEngine, type PulumiMemberEngine } from "./member-engine";
|
|
70
84
|
import {
|
|
71
85
|
defaultPlungeRunner,
|
|
72
86
|
defaultPulumiRunner,
|
|
73
87
|
plungeMoveArgs,
|
|
74
88
|
previewPulumiMemberRead,
|
|
89
|
+
pulumiActionPreviewArgs,
|
|
90
|
+
pulumiActionUpArgs,
|
|
75
91
|
pulumiCancelArgs,
|
|
76
92
|
pulumiRunFailure,
|
|
77
93
|
pulumiStateMoveArgs,
|
|
@@ -86,7 +102,7 @@ import { isObject } from "./values";
|
|
|
86
102
|
/** The op the requests are recorded under, unless the caller names its own. */
|
|
87
103
|
export const PULUMI_STACK_OPS_OP = "pulumi-stack-ops";
|
|
88
104
|
|
|
89
|
-
export type PulumiStackOpKind = "stack-export" | "stack-cancel" | "stack-move";
|
|
105
|
+
export type PulumiStackOpKind = "stack-export" | "stack-cancel" | "stack-move" | "stack-action";
|
|
90
106
|
|
|
91
107
|
/** chant's gated-request API (#4551), injectable for tests. */
|
|
92
108
|
export interface GatedRequestApi {
|
|
@@ -152,12 +168,14 @@ async function askThenAct<T>(
|
|
|
152
168
|
description: string,
|
|
153
169
|
act: () => Promise<{ result: T; detail: Record<string, string | number | boolean>; failed?: string }>,
|
|
154
170
|
precheck?: () => Promise<void>,
|
|
171
|
+
options: { reuseApplied?: boolean } = {},
|
|
155
172
|
): Promise<PulumiStackOpOutcome<T>> {
|
|
156
173
|
const api = await gatedApi(common.gated);
|
|
157
174
|
const op = common.op ?? PULUMI_STACK_OPS_OP;
|
|
158
175
|
const cwd = common.ledgerCwd ?? process.cwd();
|
|
176
|
+
// With reuseApplied (#4654), a request with these fields that applied already is asked again as itself, so it answers `spent`.
|
|
159
177
|
const standing = (await api.readGatedRequests(op, { cwd }).catch(() => []))
|
|
160
|
-
.filter((r) => !r.applied && r.request.kind === kind && r.request.subject === subject && r.request.by === common.by && sameFields(r.request.fields, fields))
|
|
178
|
+
.filter((r) => (options.reuseApplied || !r.applied) && r.request.kind === kind && r.request.subject === subject && r.request.by === common.by && sameFields(r.request.fields, fields))
|
|
161
179
|
.at(-1);
|
|
162
180
|
const request: GatedRequest = { kind, subject, by: common.by, at: standing?.request.at ?? common.now ?? new Date().toISOString(), fields };
|
|
163
181
|
const asked: RequestGateResult = await api.requestGate({
|
|
@@ -550,3 +568,170 @@ export async function gatedStackMove(opts: PulumiStackMoveOptions): Promise<Pulu
|
|
|
550
568
|
},
|
|
551
569
|
);
|
|
552
570
|
}
|
|
571
|
+
|
|
572
|
+
// ── action ───────────────────────────────────────────────────────────────
|
|
573
|
+
|
|
574
|
+
/** Why a stack action was refused before anything was asked (#4654). */
|
|
575
|
+
export type PulumiStackActionRefusal = "action-urn" | "action-type" | "action-scope";
|
|
576
|
+
|
|
577
|
+
/** A stack action refused before its request: `code` says why, `urns` names what the preview changed besides the action, for `action-scope`. */
|
|
578
|
+
export class PulumiStackActionRefused extends Error {
|
|
579
|
+
readonly code: PulumiStackActionRefusal;
|
|
580
|
+
readonly urns: string[];
|
|
581
|
+
constructor(code: PulumiStackActionRefusal, message: string, urns: string[] = []) {
|
|
582
|
+
super(`${code}: ${message}`);
|
|
583
|
+
this.name = "PulumiStackActionRefused";
|
|
584
|
+
this.code = code;
|
|
585
|
+
this.urns = urns;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/** The resource types an action may name: the command provider's (`command:local:Command`, `command:remote:Command`). */
|
|
590
|
+
export const PULUMI_ACTION_TYPE_PREFIX = "command:";
|
|
591
|
+
|
|
592
|
+
/** An action's name as a caller's config gives it, for the request's subject. */
|
|
593
|
+
const ACTION_NAME = /^[A-Za-z0-9_][A-Za-z0-9_.-]{0,99}$/;
|
|
594
|
+
|
|
595
|
+
/** The subject an action's request is asked and approved under: `action:<stack>:<name>`, or the URN in place of a name. */
|
|
596
|
+
export const stackActionSubject = (stack: string, nameOrUrn: string): string => `action:${stack}:${nameOrUrn}`;
|
|
597
|
+
|
|
598
|
+
export interface PulumiStackActionOptions extends PulumiStackOpCommon {
|
|
599
|
+
/** The stack's directory, where `Pulumi.yaml` is. */
|
|
600
|
+
dir: string;
|
|
601
|
+
cwd?: string;
|
|
602
|
+
/** The stack, as `--stack` takes it. */
|
|
603
|
+
stack: string;
|
|
604
|
+
engine?: PulumiMemberEngine;
|
|
605
|
+
/** The `command:` resource whose replacement is the action. */
|
|
606
|
+
urn: string;
|
|
607
|
+
/** The action's name in the caller's config, which the subject carries in place of the URN. */
|
|
608
|
+
name?: string;
|
|
609
|
+
/** Ask anew even when the same request applied already. Absent, such a call answers `spent`, so a retried job never runs the action twice. */
|
|
610
|
+
anew?: boolean;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
export interface PulumiStackActionResult {
|
|
614
|
+
urn: string;
|
|
615
|
+
engine: PulumiMemberEngine;
|
|
616
|
+
/** The preview's digest the request bound. */
|
|
617
|
+
preview: string;
|
|
618
|
+
/** The update's exit code: 0, since a failed update ends `failed`. */
|
|
619
|
+
exit: number;
|
|
620
|
+
/** The update's last lines of output. */
|
|
621
|
+
tail: string;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/** The last `n` lines of `text`, trimmed. */
|
|
625
|
+
const lastLines = (text: string | undefined, n: number): string =>
|
|
626
|
+
(text ?? "").trim().split("\n").slice(-n).join("\n");
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Check an action's URN before anything runs: a Pulumi URN on `stack` whose
|
|
630
|
+
* type is a command provider's. Throws {@link PulumiStackActionRefused}.
|
|
631
|
+
*/
|
|
632
|
+
export function checkStackActionUrn(stack: string, urn: string): void {
|
|
633
|
+
const parsed = parsePulumiUrn(urn);
|
|
634
|
+
if (!parsed) throw new PulumiStackActionRefused("action-urn", `${JSON.stringify(urn)} is not a resource URN (urn:pulumi:<stack>::<project>::<type>::<name>)`);
|
|
635
|
+
const stackName = stack.split("/").at(-1);
|
|
636
|
+
if (parsed.stack !== stackName) throw new PulumiStackActionRefused("action-urn", `${urn} names stack ${parsed.stack}, and the action runs on ${stack}`);
|
|
637
|
+
if (!parsed.type.startsWith(PULUMI_ACTION_TYPE_PREFIX)) {
|
|
638
|
+
throw new PulumiStackActionRefused(
|
|
639
|
+
"action-type",
|
|
640
|
+
`${urn} is a ${parsed.type}, and an action is a command resource (command:local:Command or command:remote:Command), whose replacement runs it; replacing any other resource is an update`,
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* What a preview changes besides the action's replacement (#4654): each URN
|
|
647
|
+
* with a changing step other than `urn`, and `urn` itself when it changes
|
|
648
|
+
* other than by replacement or is not replaced at all. Empty when the
|
|
649
|
+
* preview replaces `urn` and nothing else.
|
|
650
|
+
*/
|
|
651
|
+
export function stackActionScope(urn: string, preview: unknown): { others: string[]; problem?: string } {
|
|
652
|
+
const steps = pulumiSteps(preview) ?? [];
|
|
653
|
+
const others = new Set<string>();
|
|
654
|
+
let replaced = false;
|
|
655
|
+
let problem: string | undefined;
|
|
656
|
+
for (const s of steps) {
|
|
657
|
+
const op = s.op ?? "";
|
|
658
|
+
if (PULUMI_UNCHANGED_OPS.has(op)) continue;
|
|
659
|
+
if (s.urn !== urn) {
|
|
660
|
+
others.add(s.urn!);
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
if (op === "replace" || op === "create-replacement" || op === "delete-replaced") replaced = true;
|
|
664
|
+
else problem = `the preview shows ${op} of ${urn}, not its replacement`;
|
|
665
|
+
}
|
|
666
|
+
if (!problem && !replaced) problem = `the preview does not replace ${urn}`;
|
|
667
|
+
return { others: [...others].sort(), ...(problem ? { problem } : {}) };
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* A day-2 action on a stack (#4654): the replacement of one `command:`
|
|
672
|
+
* resource, previewed, asked for under a gated request, and run once a
|
|
673
|
+
* second identity approves. Refused before anything runs for a URN that is
|
|
674
|
+
* not a command resource's (`action-type`), and before anything is asked for
|
|
675
|
+
* a preview that changes more than that replacement (`action-scope`).
|
|
676
|
+
*/
|
|
677
|
+
export async function gatedStackAction(opts: PulumiStackActionOptions): Promise<PulumiStackOpOutcome<PulumiStackActionResult>> {
|
|
678
|
+
checkStackActionUrn(opts.stack, opts.urn);
|
|
679
|
+
if (opts.name !== undefined && !ACTION_NAME.test(opts.name)) throw new Error(`an action's name is letters, digits, "_", "." and "-", not ${JSON.stringify(opts.name)}`);
|
|
680
|
+
const env = opts.env ?? process.env;
|
|
681
|
+
const dir = stackDirOf(opts.dir, opts.cwd);
|
|
682
|
+
const engine = engineOf(dir, opts.engine, env);
|
|
683
|
+
const target = { stack: opts.stack, urn: opts.urn };
|
|
684
|
+
|
|
685
|
+
const previewArgs = pulumiActionPreviewArgs(target);
|
|
686
|
+
let stdout: string;
|
|
687
|
+
try {
|
|
688
|
+
({ stdout } = engine === "plunge" ? await runAllowedPlunge(opts.plunge ?? defaultPlungeRunner, previewArgs, dir) : await runAllowedPulumi(opts.run ?? defaultPulumiRunner, previewArgs, dir));
|
|
689
|
+
} catch (err) {
|
|
690
|
+
throw new Error(`${engine} ${previewArgs.join(" ")} failed: ${why(err, engine)}`);
|
|
691
|
+
}
|
|
692
|
+
let preview: unknown;
|
|
693
|
+
try {
|
|
694
|
+
preview = JSON.parse(stdout);
|
|
695
|
+
} catch {
|
|
696
|
+
throw new Error(`${engine} ${previewArgs.join(" ")} printed no JSON`);
|
|
697
|
+
}
|
|
698
|
+
const scope = stackActionScope(opts.urn, preview);
|
|
699
|
+
if (scope.others.length > 0) {
|
|
700
|
+
throw new PulumiStackActionRefused(
|
|
701
|
+
"action-scope",
|
|
702
|
+
`the preview of ${opts.stack} replacing ${opts.urn} also changes ${scope.others.join(", ")}, and an action replaces its command resource alone; nothing was asked for`,
|
|
703
|
+
scope.others,
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
if (scope.problem) throw new PulumiStackActionRefused("action-scope", `${scope.problem}; nothing was asked for`);
|
|
707
|
+
|
|
708
|
+
const digest = pulumiPreviewDigest(preview, { changedOnly: true });
|
|
709
|
+
const subject = stackActionSubject(opts.stack, opts.name ?? opts.urn);
|
|
710
|
+
const fields: Record<string, string> = { stack: opts.stack, urn: opts.urn, engine, preview: digest, ...(opts.name ? { name: opts.name } : {}) };
|
|
711
|
+
return askThenAct(
|
|
712
|
+
opts,
|
|
713
|
+
"stack-action",
|
|
714
|
+
subject,
|
|
715
|
+
fields,
|
|
716
|
+
`action ${opts.name ?? opts.urn} on stack ${opts.stack}: replace ${opts.urn}, requested by ${opts.by}`,
|
|
717
|
+
async () => {
|
|
718
|
+
const args = pulumiActionUpArgs(target);
|
|
719
|
+
try {
|
|
720
|
+
const done = engine === "plunge" ? await runGatedPlunge(opts.plunge ?? defaultPlungeRunner, args, dir) : await runGatedPulumi(opts.run ?? defaultPulumiRunner, args, dir);
|
|
721
|
+
const tail = lastLines(done.stdout, 10);
|
|
722
|
+
return { result: { urn: opts.urn, engine, preview: digest, exit: 0, tail }, detail: { result: "ok", exit: 0, tail: tail.slice(-400), urn: opts.urn } };
|
|
723
|
+
} catch (err) {
|
|
724
|
+
const f = pulumiRunFailure(err);
|
|
725
|
+
const exit = f.exitCode ?? 1;
|
|
726
|
+
const tail = lastLines(`${f.stdout ?? ""}\n${f.stderr ?? ""}`, 10);
|
|
727
|
+
return {
|
|
728
|
+
result: { urn: opts.urn, engine, preview: digest, exit, tail },
|
|
729
|
+
detail: { result: "failed", exit, tail: tail.slice(-400), urn: opts.urn },
|
|
730
|
+
failed: `${engine} ${args.join(" ")} failed: ${why(err, engine)}`,
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
},
|
|
734
|
+
undefined,
|
|
735
|
+
{ reuseApplied: !opts.anew },
|
|
736
|
+
);
|
|
737
|
+
}
|