@intentius/chant-lexicon-aws 0.124.28 → 0.124.29
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/components/__tests__/mock-cloud-executor.d.ts +9 -0
- package/dist/components/__tests__/mock-cloud-executor.d.ts.map +1 -1
- package/dist/components/apply.d.ts +16 -0
- package/dist/components/apply.d.ts.map +1 -1
- package/dist/components/cloud-executor.d.ts +23 -0
- package/dist/components/cloud-executor.d.ts.map +1 -1
- package/dist/integrity.json +2 -2
- package/dist/manifest.json +1 -1
- package/package.json +2 -2
- package/src/components/__tests__/mock-cloud-executor.ts +23 -1
- package/src/components/apply.ts +184 -1
- package/src/components/cfn-plan.test.ts +224 -0
- package/src/components/cloud-executor.ts +47 -5
|
@@ -38,6 +38,15 @@ export interface FakeStackConfig {
|
|
|
38
38
|
* update with an empty diff — cfn-deploy must treat it as an idempotent no-op
|
|
39
39
|
* success (#960). */
|
|
40
40
|
noChanges?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* When true, no stack exists yet: `describeStack` throws CloudFormation's
|
|
43
|
+
* "does not exist", a change set is a CREATE that makes an empty
|
|
44
|
+
* `REVIEW_IN_PROGRESS` stack (`stackCreated`), and `deleteStackShell`
|
|
45
|
+
* removes it (#4084).
|
|
46
|
+
*/
|
|
47
|
+
missing?: boolean;
|
|
48
|
+
/** Set by the mock: the empty `REVIEW_IN_PROGRESS` stack a CREATE change set made for a missing stack. */
|
|
49
|
+
shell?: boolean;
|
|
41
50
|
}
|
|
42
51
|
export interface FakeDeploymentConfig {
|
|
43
52
|
/** Terminal status CodeDeploy reports for this deployment. Default: "Succeeded". */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mock-cloud-executor.d.ts","sourceRoot":"","sources":["../../../src/components/__tests__/mock-cloud-executor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EACV,SAAS,EAIT,aAAa,EAKb,eAAe,EAWhB,MAAM,mBAAmB,CAAC;AAK3B,OAAO,KAAK,EAMV,kBAAkB,EAEnB,MAAM,kDAAkD,CAAC;AAE1D,2LAA2L;AAC3L,KAAK,YAAY,GAAG,aAAa,GAAG;IAAE,KAAK,EAAE,kBAAkB,CAAA;CAAE,CAAC;AAElE,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;CACf;AAED,2DAA2D;AAC3D,MAAM,WAAW,eAAe;IAC9B,kIAAkI;IAClI,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC;IACtB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,kIAAkI;IAClI,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;yBAGqB;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"mock-cloud-executor.d.ts","sourceRoot":"","sources":["../../../src/components/__tests__/mock-cloud-executor.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* invoke, or SSM host command).
|
|
20
20
|
*/
|
|
21
21
|
import type { Capability } from "@intentius/chant/components/capability";
|
|
22
|
+
import type { ChangeSetEntry } from "@intentius/chant/change-set";
|
|
22
23
|
import { type CfnChange, type CfnChangeSet, type CloudExecutor } from "./cloud-executor.js";
|
|
23
24
|
/**
|
|
24
25
|
* True when CloudFormation reports the change set has nothing to apply. AWS
|
|
@@ -28,6 +29,21 @@ import { type CfnChange, type CfnChangeSet, type CloudExecutor } from "./cloud-e
|
|
|
28
29
|
* reason is a genuine create failure and must NOT be treated as a no-op.
|
|
29
30
|
*/
|
|
30
31
|
export declare function isNoopChangeSet(changeSet: CfnChangeSet): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Whether a proposed change replaces its resource. CloudFormation's
|
|
34
|
+
* `Replacement: Conditional` counts: the replacement depends on a value it
|
|
35
|
+
* resolves only while applying, so a reviewer has to read it as one (#4084).
|
|
36
|
+
*/
|
|
37
|
+
export declare function cfnChangeReplaces(change: CfnChange): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* One CloudFormation resource change as a change-set entry (#4084). The
|
|
40
|
+
* address is the logical id, which is also the entity name the aws
|
|
41
|
+
* lexicon's graph IR gives the resource, so the entry maps onto its node
|
|
42
|
+
* (#4022). `Add` is a create, `Modify` an update or (when it replaces) a
|
|
43
|
+
* replace, `Remove` a delete, `Import` an import that changes nothing else,
|
|
44
|
+
* and `Dynamic` an update CloudFormation could not describe further.
|
|
45
|
+
*/
|
|
46
|
+
export declare function cfnChangeEntry(member: string, change: CfnChange): ChangeSetEntry;
|
|
31
47
|
/**
|
|
32
48
|
* How `cfn-deploy` handles a changeset that requires resource replacement.
|
|
33
49
|
* - `block`: refuse to apply; surface the changeset for human review.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../src/components/apply.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;
|
|
1
|
+
{"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../src/components/apply.ts"],"names":[],"mappings":"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"}
|
|
@@ -28,6 +28,13 @@ export interface CfnChange {
|
|
|
28
28
|
resourceType: string;
|
|
29
29
|
/** True when CloudFormation must replace (destroy + recreate) this resource to apply the change. */
|
|
30
30
|
replacement: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* CloudFormation's own `Replacement` value, when the backend reports one.
|
|
33
|
+
* `Conditional` means the replacement depends on a value CloudFormation
|
|
34
|
+
* resolves only while applying; `replacement` is false for it, and a plan
|
|
35
|
+
* counts it as a replace (#4084).
|
|
36
|
+
*/
|
|
37
|
+
replacementMode?: "True" | "False" | "Conditional";
|
|
31
38
|
/** Resource-specific detail, when the executor's changeset backend provides it (e.g. property/scope). */
|
|
32
39
|
details?: string[];
|
|
33
40
|
}
|
|
@@ -42,6 +49,15 @@ export interface CfnChangeSet {
|
|
|
42
49
|
statusReason?: string;
|
|
43
50
|
/** True the first time this stack is created (no prior stack exists). */
|
|
44
51
|
isCreate: boolean;
|
|
52
|
+
/** The change set's type: CREATE for a stack that does not exist yet or was never executed, else UPDATE. */
|
|
53
|
+
changeSetType?: "CREATE" | "UPDATE";
|
|
54
|
+
/**
|
|
55
|
+
* True when no stack existed before this change set, so CloudFormation made
|
|
56
|
+
* an empty `REVIEW_IN_PROGRESS` stack to hold it. A plan that deletes the
|
|
57
|
+
* change set without executing it deletes that stack too
|
|
58
|
+
* (`deleteStackShell`, #4084).
|
|
59
|
+
*/
|
|
60
|
+
stackCreated?: boolean;
|
|
45
61
|
changes: CfnChange[];
|
|
46
62
|
}
|
|
47
63
|
export interface CfnCreateChangeSetArgs {
|
|
@@ -73,6 +89,13 @@ export interface CloudFormationClient {
|
|
|
73
89
|
describeStack(stackName: string): Promise<CfnStackStatus>;
|
|
74
90
|
/** Trigger CloudFormation's native rollback-to-last-known-good-state for a stack (saga compensation). */
|
|
75
91
|
rollbackStack(stackName: string): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Delete a stack only while it is the empty `REVIEW_IN_PROGRESS` stack a
|
|
94
|
+
* CREATE change set made, and say whether it deleted it (#4084). A stack in
|
|
95
|
+
* any other status is left alone. Optional: without it, a plan leaves the
|
|
96
|
+
* empty stack, and the next CREATE change set reuses it.
|
|
97
|
+
*/
|
|
98
|
+
deleteStackShell?(stackName: string): Promise<boolean>;
|
|
76
99
|
}
|
|
77
100
|
export interface EcsUpdateServiceArgs {
|
|
78
101
|
cluster: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud-executor.d.ts","sourceRoot":"","sources":["../../src/components/cloud-executor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAOH,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,kDAAkD,CAAC;AAIjG,YAAY,EAAE,YAAY,EAAE,CAAC;AAM7B,MAAM,WAAW,SAAS;IACxB,6GAA6G;IAC7G,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAID,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC3D,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,oGAAoG;IACpG,WAAW,EAAE,OAAO,CAAC;IACrB,yGAAyG;IACzG,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf;;;kCAG8B;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,SAAS,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,oBAAoB;IACnC,yGAAyG;IACzG,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IACrE,8CAA8C;IAC9C,gBAAgB,CAAC,IAAI,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,4FAA4F;IAC5F,eAAe,CAAC,IAAI,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,mHAAmH;IACnH,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7G,uDAAuD;IACvD,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAC1D,yGAAyG;IACzG,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"cloud-executor.d.ts","sourceRoot":"","sources":["../../src/components/cloud-executor.ts"],"names":[],"mappings":"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"}
|
package/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "29c9887a1882150b11c617301801d923e1b4e756987c69ce00dee206875b73bb",
|
|
5
5
|
"meta.json": "435429f998bf94568544f01780e35c28b73e7d030520d59d4025867d272cdb75",
|
|
6
6
|
"types/index.d.ts": "9e99bb26113f101eb7073759e1b02b8d06aac73379254a64a5e492d52506156c",
|
|
7
7
|
"rules/hardcoded-region.ts": "957eadcaa9f39cc17d21437565d7a94607d9b082cf28918b178fa067f07498a9",
|
|
@@ -73,5 +73,5 @@
|
|
|
73
73
|
"skills/chant-aws-eks.md": "8789255709ff004ad0a875fd5999edcdc66fc6e33d710db058d9f42703bcfdfe",
|
|
74
74
|
"skills/chant-aws-carve-terraform.md": "8780b94ce60aded935938d5ef20ea667738354051da1bf9ca6d70d9886f65d21"
|
|
75
75
|
},
|
|
76
|
-
"composite": "
|
|
76
|
+
"composite": "97cbae196d19e31a37706dbaa43fdca83f538f4508d0164c5f1c09f5cec274a6"
|
|
77
77
|
}
|
package/dist/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-aws",
|
|
3
|
-
"version": "0.124.
|
|
3
|
+
"version": "0.124.29",
|
|
4
4
|
"description": "AWS CloudFormation lexicon for chant — declarative IaC in TypeScript",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
@@ -80,6 +80,6 @@
|
|
|
80
80
|
"typescript": "^5.9.3"
|
|
81
81
|
},
|
|
82
82
|
"peerDependencies": {
|
|
83
|
-
"@intentius/chant": "^0.124.
|
|
83
|
+
"@intentius/chant": "^0.124.29"
|
|
84
84
|
}
|
|
85
85
|
}
|
|
@@ -73,6 +73,15 @@ export interface FakeStackConfig {
|
|
|
73
73
|
* update with an empty diff — cfn-deploy must treat it as an idempotent no-op
|
|
74
74
|
* success (#960). */
|
|
75
75
|
noChanges?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* When true, no stack exists yet: `describeStack` throws CloudFormation's
|
|
78
|
+
* "does not exist", a change set is a CREATE that makes an empty
|
|
79
|
+
* `REVIEW_IN_PROGRESS` stack (`stackCreated`), and `deleteStackShell`
|
|
80
|
+
* removes it (#4084).
|
|
81
|
+
*/
|
|
82
|
+
missing?: boolean;
|
|
83
|
+
/** Set by the mock: the empty `REVIEW_IN_PROGRESS` stack a CREATE change set made for a missing stack. */
|
|
84
|
+
shell?: boolean;
|
|
76
85
|
}
|
|
77
86
|
|
|
78
87
|
export interface FakeDeploymentConfig {
|
|
@@ -218,11 +227,15 @@ export function createMockCloudExecutor(options: MockCloudExecutorOptions = {}):
|
|
|
218
227
|
};
|
|
219
228
|
}
|
|
220
229
|
pendingChangeSets.set(changeSetName, { stackName: args.stackName, changes });
|
|
230
|
+
const created = config?.missing === true;
|
|
231
|
+
if (created) stacks.set(args.stackName, { ...config, missing: false, shell: true });
|
|
221
232
|
return {
|
|
222
233
|
changeSetName,
|
|
223
234
|
stackName: args.stackName,
|
|
224
235
|
status: "CREATE_COMPLETE",
|
|
225
|
-
isCreate: config?.isCreate ??
|
|
236
|
+
isCreate: config?.isCreate ?? created,
|
|
237
|
+
changeSetType: created || config?.isCreate ? "CREATE" : "UPDATE",
|
|
238
|
+
stackCreated: created,
|
|
226
239
|
changes,
|
|
227
240
|
};
|
|
228
241
|
},
|
|
@@ -236,8 +249,17 @@ export function createMockCloudExecutor(options: MockCloudExecutorOptions = {}):
|
|
|
236
249
|
},
|
|
237
250
|
async describeStack(stackName: string) {
|
|
238
251
|
record("cloudformation", "describeStack", stackName);
|
|
252
|
+
if (stacks.get(stackName)?.missing) throw new Error(`Stack with id ${stackName} does not exist`);
|
|
253
|
+
if (stacks.get(stackName)?.shell) return { stackStatus: "REVIEW_IN_PROGRESS", outputs: {} };
|
|
239
254
|
return stackStatus(stackName);
|
|
240
255
|
},
|
|
256
|
+
async deleteStackShell(stackName: string) {
|
|
257
|
+
record("cloudformation", "deleteStackShell", stackName);
|
|
258
|
+
const config = stacks.get(stackName);
|
|
259
|
+
if (!config?.shell) return false;
|
|
260
|
+
stacks.set(stackName, { ...config, shell: false, missing: true });
|
|
261
|
+
return true;
|
|
262
|
+
},
|
|
241
263
|
async waitForStack(stackName: string) {
|
|
242
264
|
record("cloudformation", "waitForStack", stackName);
|
|
243
265
|
return stackStatus(stackName);
|
package/src/components/apply.ts
CHANGED
|
@@ -19,7 +19,11 @@
|
|
|
19
19
|
* invoke, or SSM host command).
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import
|
|
22
|
+
import { createHash } from "node:crypto";
|
|
23
|
+
import { readFileSync } from "node:fs";
|
|
24
|
+
import type { Capability, CapabilityPlan, DeployContext } from "@intentius/chant/components/capability";
|
|
25
|
+
import type { ChangeSetEntry, ChangeSetHole, ChangeSetPart } from "@intentius/chant/change-set";
|
|
26
|
+
import { computePlanDigest } from "@intentius/chant/lifecycle/plan-digest";
|
|
23
27
|
import { defaultCloudExecutor, type CfnChange, type CfnChangeSet, type CloudExecutor } from "./cloud-executor";
|
|
24
28
|
|
|
25
29
|
/** CloudFormation's wording when a change set has nothing to apply — the stack
|
|
@@ -44,6 +48,172 @@ export function isNoopChangeSet(changeSet: CfnChangeSet): boolean {
|
|
|
44
48
|
|
|
45
49
|
// ── cfn-deploy ───────────────────────────────────────────────────────────────
|
|
46
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Whether a proposed change replaces its resource. CloudFormation's
|
|
53
|
+
* `Replacement: Conditional` counts: the replacement depends on a value it
|
|
54
|
+
* resolves only while applying, so a reviewer has to read it as one (#4084).
|
|
55
|
+
*/
|
|
56
|
+
export function cfnChangeReplaces(change: CfnChange): boolean {
|
|
57
|
+
return change.replacement || change.replacementMode === "Conditional";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* One CloudFormation resource change as a change-set entry (#4084). The
|
|
62
|
+
* address is the logical id, which is also the entity name the aws
|
|
63
|
+
* lexicon's graph IR gives the resource, so the entry maps onto its node
|
|
64
|
+
* (#4022). `Add` is a create, `Modify` an update or (when it replaces) a
|
|
65
|
+
* replace, `Remove` a delete, `Import` an import that changes nothing else,
|
|
66
|
+
* and `Dynamic` an update CloudFormation could not describe further.
|
|
67
|
+
*/
|
|
68
|
+
export function cfnChangeEntry(member: string, change: CfnChange): ChangeSetEntry {
|
|
69
|
+
const attributes = [...new Set(change.details ?? [])].sort().map((path) => ({ path }));
|
|
70
|
+
const base = {
|
|
71
|
+
member,
|
|
72
|
+
lexicon: "aws",
|
|
73
|
+
planner: "chant" as const,
|
|
74
|
+
address: change.logicalResourceId,
|
|
75
|
+
type: change.resourceType,
|
|
76
|
+
name: change.logicalResourceId,
|
|
77
|
+
attributes,
|
|
78
|
+
};
|
|
79
|
+
switch (change.action) {
|
|
80
|
+
case "Add":
|
|
81
|
+
return { ...base, action: "create" };
|
|
82
|
+
case "Remove":
|
|
83
|
+
return { ...base, action: "delete", disruption: "destroy" };
|
|
84
|
+
case "Import":
|
|
85
|
+
return { ...base, action: "no-op", importing: true };
|
|
86
|
+
case "Dynamic":
|
|
87
|
+
return { ...base, action: cfnChangeReplaces(change) ? "replace" : "update", disruption: cfnChangeReplaces(change) ? "replace" : "unknown" };
|
|
88
|
+
default:
|
|
89
|
+
return cfnChangeReplaces(change)
|
|
90
|
+
? { ...base, action: "replace", disruption: "replace" }
|
|
91
|
+
: { ...base, action: "update", disruption: "in-place" };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** The template's bytes as a digest, or `null` when it cannot be read here (the change set then reports why). */
|
|
96
|
+
function templateDigest(path: string): string | null {
|
|
97
|
+
try {
|
|
98
|
+
return `sha256:${createHash("sha256").update(readFileSync(path)).digest("hex")}`;
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* What a `cfn-deploy` plan binds: the environment, the stack, the template's
|
|
106
|
+
* bytes and the parameters as resolved, and the stacks whose outputs were
|
|
107
|
+
* not known yet. The proposed changes are not in it; they are the member's
|
|
108
|
+
* entries, which the change-set document's digest binds (#4084).
|
|
109
|
+
*/
|
|
110
|
+
function cfnPlanDigest(
|
|
111
|
+
ctx: DeployContext,
|
|
112
|
+
stack: string,
|
|
113
|
+
input: CfnDeployInput,
|
|
114
|
+
parameters: Record<string, string | null>,
|
|
115
|
+
unresolved: string[],
|
|
116
|
+
): string {
|
|
117
|
+
return computePlanDigest("cfn-deploy-plan", {
|
|
118
|
+
env: ctx.env,
|
|
119
|
+
stack,
|
|
120
|
+
template: input.template,
|
|
121
|
+
templateDigest: templateDigest(input.template),
|
|
122
|
+
parameters,
|
|
123
|
+
unresolved,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Plan a `cfn-deploy` step without changing the stack (#4084): create a
|
|
129
|
+
* change set for the template and the resolved parameters, read the changes
|
|
130
|
+
* it proposes, and delete it without executing it. A stack that does not
|
|
131
|
+
* exist yet gets a CREATE change set, so every resource reads as a create,
|
|
132
|
+
* and the empty `REVIEW_IN_PROGRESS` stack CloudFormation made to hold it is
|
|
133
|
+
* deleted too. A change set with nothing to apply (`isNoopChangeSet`) gives
|
|
134
|
+
* a member with no entries. A step whose parameters read outputs that are
|
|
135
|
+
* not known yet, from a stack the same run applies first, creates no change
|
|
136
|
+
* set: its member gets a hole saying whose outputs it waits on.
|
|
137
|
+
*/
|
|
138
|
+
async function planCfnDeploy(executor: CloudExecutor, ctx: DeployContext, input: CfnDeployInput): Promise<CapabilityPlan> {
|
|
139
|
+
const stack = input.stack ?? ctx.component;
|
|
140
|
+
const raw: Record<string, unknown> = { ...(input.inputs ?? {}), ...(input.imageRef !== undefined ? { ImageRef: input.imageRef } : {}) };
|
|
141
|
+
const parameters: Record<string, string | null> = {};
|
|
142
|
+
const missing: string[] = [];
|
|
143
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
144
|
+
if (value === undefined || value === null) {
|
|
145
|
+
parameters[key] = null;
|
|
146
|
+
missing.push(key);
|
|
147
|
+
} else parameters[key] = typeof value === "string" ? value : JSON.stringify(value);
|
|
148
|
+
}
|
|
149
|
+
const unresolved = [...(ctx.unresolvedInputs ?? [])].sort();
|
|
150
|
+
const planDigest = cfnPlanDigest(ctx, stack, input, parameters, unresolved);
|
|
151
|
+
const member = (holes: ChangeSetHole[]): ChangeSetPart["member"] => ({
|
|
152
|
+
member: stack,
|
|
153
|
+
lexicon: "aws",
|
|
154
|
+
planner: "chant",
|
|
155
|
+
status: "planned",
|
|
156
|
+
planDigest,
|
|
157
|
+
holes,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
if (unresolved.length > 0 || missing.length > 0) {
|
|
161
|
+
const reason =
|
|
162
|
+
unresolved.length > 0
|
|
163
|
+
? `inputs from ${unresolved.join(", ")}; re-checked on apply`
|
|
164
|
+
: `inputs ${missing.sort().join(", ")} are known only at apply; re-checked on apply`;
|
|
165
|
+
return {
|
|
166
|
+
member: stack,
|
|
167
|
+
planDigest,
|
|
168
|
+
changeSet: { member: member([{ address: stack, type: "AWS::CloudFormation::Stack", reason }]), entries: [] },
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const changeSet = await executor.cloudformation.createChangeSet({
|
|
173
|
+
stackName: stack,
|
|
174
|
+
templatePath: input.template,
|
|
175
|
+
parameters: flattenCfnInputs(input),
|
|
176
|
+
});
|
|
177
|
+
try {
|
|
178
|
+
if (isNoopChangeSet(changeSet)) {
|
|
179
|
+
return { member: stack, planDigest, changeSet: { member: member([]), entries: [] } };
|
|
180
|
+
}
|
|
181
|
+
if (changeSet.status === "FAILED") {
|
|
182
|
+
throw new Error(
|
|
183
|
+
`cfn-deploy "${stack}": CloudFormation could not create a change set to plan it${changeSet.statusReason ? `: ${changeSet.statusReason}` : ""}`,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
const entries = changeSet.changes.map((c) => cfnChangeEntry(stack, c));
|
|
187
|
+
return { member: stack, planDigest, changeSet: { member: member([]), entries } };
|
|
188
|
+
} finally {
|
|
189
|
+
await cleanUpPlanChangeSet(executor, changeSet);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Delete a plan's change set, and the empty stack a CREATE change set made
|
|
195
|
+
* when no stack existed before. A failure to clean up does not fail the
|
|
196
|
+
* plan: the change set holds nothing that applies, and the next CREATE
|
|
197
|
+
* change set reuses an empty stack left behind.
|
|
198
|
+
*/
|
|
199
|
+
async function cleanUpPlanChangeSet(executor: CloudExecutor, changeSet: CfnChangeSet): Promise<void> {
|
|
200
|
+
try {
|
|
201
|
+
await executor.cloudformation.deleteChangeSet({ stackName: changeSet.stackName, changeSetName: changeSet.changeSetName });
|
|
202
|
+
} catch (err) {
|
|
203
|
+
console.error(`cfn-deploy plan "${changeSet.stackName}": could not delete change set ${changeSet.changeSetName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
204
|
+
}
|
|
205
|
+
if (changeSet.stackCreated && executor.cloudformation.deleteStackShell) {
|
|
206
|
+
try {
|
|
207
|
+
await executor.cloudformation.deleteStackShell(changeSet.stackName);
|
|
208
|
+
} catch (err) {
|
|
209
|
+
console.error(`cfn-deploy plan "${changeSet.stackName}": could not delete the empty stack its CREATE change set made: ${err instanceof Error ? err.message : String(err)}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Whether a CloudFormation error says the stack does not exist. */
|
|
215
|
+
const STACK_MISSING = /does not exist/i;
|
|
216
|
+
|
|
47
217
|
/**
|
|
48
218
|
* How `cfn-deploy` handles a changeset that requires resource replacement.
|
|
49
219
|
* - `block`: refuse to apply; surface the changeset for human review.
|
|
@@ -132,6 +302,19 @@ export function createCfnDeployCapability(
|
|
|
132
302
|
): Capability<CfnDeployInput, CfnDeployOutput> {
|
|
133
303
|
return {
|
|
134
304
|
kind: "cfn-deploy",
|
|
305
|
+
plan(ctx, input) {
|
|
306
|
+
return planCfnDeploy(executor, ctx, input);
|
|
307
|
+
},
|
|
308
|
+
async outputs(ctx, input) {
|
|
309
|
+
// The outputs the stack has now, for a dependent planned before it
|
|
310
|
+
// applies (#3183). A stack that does not exist yet has none.
|
|
311
|
+
try {
|
|
312
|
+
return (await executor.cloudformation.describeStack(input.stack ?? ctx.component)).outputs;
|
|
313
|
+
} catch (err) {
|
|
314
|
+
if (STACK_MISSING.test(err instanceof Error ? err.message : String(err))) return {};
|
|
315
|
+
throw err;
|
|
316
|
+
}
|
|
317
|
+
},
|
|
135
318
|
async run(ctx, input) {
|
|
136
319
|
const stack = input.stack ?? ctx.component;
|
|
137
320
|
const onReplace = input.onReplace ?? "block";
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cfn-deploy`'s plan (#4084): a change set created, read and deleted, never
|
|
3
|
+
* executed, whose changes become the member's change-set entries, so
|
|
4
|
+
* `chant components pr-plan` counts what each stack will change.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
8
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { CapabilityRegistry } from "@intentius/chant/components/capability";
|
|
12
|
+
import type { DriverComponent } from "@intentius/chant/components/driver";
|
|
13
|
+
import { planFanOut } from "@intentius/chant/components/fan-out";
|
|
14
|
+
import { planPrSet, prReport } from "@intentius/chant/components/pr-run";
|
|
15
|
+
import { composeChangeSet } from "@intentius/chant/change-set";
|
|
16
|
+
import { groupChangeSet, planSummaryHeadline } from "@intentius/chant/plan-summary";
|
|
17
|
+
import { memberCounts, prStatusDescription, renderPrNote } from "@intentius/chant/pr-loop";
|
|
18
|
+
import { createCfnDeployCapability } from "./apply";
|
|
19
|
+
import { createMockCloudExecutor, type FakeStackConfig } from "./__tests__/mock-cloud-executor";
|
|
20
|
+
|
|
21
|
+
let dir: string;
|
|
22
|
+
let template: string;
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
dir = mkdtempSync(join(tmpdir(), "cfn-plan-"));
|
|
26
|
+
template = join(dir, "stack.template.json");
|
|
27
|
+
writeFileSync(template, JSON.stringify({ Resources: { Topic: { Type: "AWS::SNS::Topic" } } }));
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(() => rmSync(dir, { recursive: true, force: true }));
|
|
31
|
+
|
|
32
|
+
const ctx = { env: "local", component: "network" };
|
|
33
|
+
|
|
34
|
+
async function planOne(config: FakeStackConfig) {
|
|
35
|
+
const mock = createMockCloudExecutor({ stacks: { network: config } });
|
|
36
|
+
const capability = createCfnDeployCapability(mock.executor);
|
|
37
|
+
const planned = await capability.plan!(ctx, { stack: "network", template });
|
|
38
|
+
const doc = composeChangeSet([planned.changeSet!]);
|
|
39
|
+
return { mock, planned, doc, counts: memberCounts(doc, "network") };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const methods = (mock: ReturnType<typeof createMockCloudExecutor>) => mock.calls.map((c) => c.method);
|
|
43
|
+
|
|
44
|
+
describe("cfn-deploy plan (#4084)", () => {
|
|
45
|
+
it("an update in place counts as an update, and the change set is deleted without being executed", async () => {
|
|
46
|
+
const { mock, planned, doc, counts } = await planOne({
|
|
47
|
+
changes: [{ action: "Modify", logicalResourceId: "Topic", resourceType: "AWS::SNS::Topic", replacement: false, replacementMode: "False", details: ["DisplayName"] }],
|
|
48
|
+
});
|
|
49
|
+
expect(planned.member).toBe("network");
|
|
50
|
+
expect(counts).toEqual({ create: 0, update: 1, replace: 0, delete: 0 });
|
|
51
|
+
expect(doc.entries[0]).toMatchObject({
|
|
52
|
+
member: "network",
|
|
53
|
+
lexicon: "aws",
|
|
54
|
+
planner: "chant",
|
|
55
|
+
address: "Topic",
|
|
56
|
+
type: "AWS::SNS::Topic",
|
|
57
|
+
action: "update",
|
|
58
|
+
disruption: "in-place",
|
|
59
|
+
attributes: [{ path: "DisplayName" }],
|
|
60
|
+
});
|
|
61
|
+
expect(methods(mock)).toEqual(["createChangeSet", "deleteChangeSet"]);
|
|
62
|
+
expect(methods(mock)).not.toContain("executeChangeSet");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("a new resource in an existing stack counts as a create", async () => {
|
|
66
|
+
const { counts, mock } = await planOne({
|
|
67
|
+
changes: [{ action: "Add", logicalResourceId: "Queue", resourceType: "AWS::SQS::Queue", replacement: false }],
|
|
68
|
+
});
|
|
69
|
+
expect(counts).toEqual({ create: 1, update: 0, replace: 0, delete: 0 });
|
|
70
|
+
expect(methods(mock)).not.toContain("deleteStackShell");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("Replacement True and Conditional both count as a replace, and the summary lists them as destroys or replacements", async () => {
|
|
74
|
+
const { counts, doc } = await planOne({
|
|
75
|
+
changes: [
|
|
76
|
+
{ action: "Modify", logicalResourceId: "Table", resourceType: "AWS::DynamoDB::Table", replacement: true, replacementMode: "True" },
|
|
77
|
+
{ action: "Modify", logicalResourceId: "Topic", resourceType: "AWS::SNS::Topic", replacement: false, replacementMode: "Conditional" },
|
|
78
|
+
{ action: "Remove", logicalResourceId: "Old", resourceType: "AWS::SQS::Queue", replacement: false },
|
|
79
|
+
],
|
|
80
|
+
});
|
|
81
|
+
expect(counts).toEqual({ create: 0, update: 0, replace: 2, delete: 1 });
|
|
82
|
+
expect(planSummaryHeadline(groupChangeSet(doc))).toContain("3 destroys or replacements");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("a change set with nothing to apply is a member with no entries and no holes", async () => {
|
|
86
|
+
const { counts, doc, mock } = await planOne({ noChanges: true });
|
|
87
|
+
expect(counts).toEqual({ create: 0, update: 0, replace: 0, delete: 0 });
|
|
88
|
+
expect(doc.members[0]).toMatchObject({ status: "planned", holes: [] });
|
|
89
|
+
expect(groupChangeSet(doc, { unit: "member" }).groups[0].noChanges).toBe(true);
|
|
90
|
+
expect(methods(mock)).toEqual(["createChangeSet", "deleteChangeSet"]);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("a stack that does not exist yet counts every resource as a create, and the empty stack the CREATE change set made is deleted", async () => {
|
|
94
|
+
const { counts, mock } = await planOne({
|
|
95
|
+
missing: true,
|
|
96
|
+
changes: [
|
|
97
|
+
{ action: "Add", logicalResourceId: "Topic", resourceType: "AWS::SNS::Topic", replacement: false },
|
|
98
|
+
{ action: "Add", logicalResourceId: "Queue", resourceType: "AWS::SQS::Queue", replacement: false },
|
|
99
|
+
],
|
|
100
|
+
});
|
|
101
|
+
expect(counts).toEqual({ create: 2, update: 0, replace: 0, delete: 0 });
|
|
102
|
+
expect(methods(mock)).toEqual(["createChangeSet", "deleteChangeSet", "deleteStackShell"]);
|
|
103
|
+
await expect(mock.executor.cloudformation.describeStack("network")).rejects.toThrow(/does not exist/);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("a change set that fails for any other reason fails the plan, and is still deleted", async () => {
|
|
107
|
+
const base = createMockCloudExecutor({ stacks: { network: {} } });
|
|
108
|
+
const executor = {
|
|
109
|
+
...base.executor,
|
|
110
|
+
cloudformation: {
|
|
111
|
+
...base.executor.cloudformation,
|
|
112
|
+
createChangeSet: async () => ({ changeSetName: "cs-1", stackName: "network", status: "FAILED", statusReason: "Template format error", isCreate: false, changes: [] }),
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
const capability = createCfnDeployCapability(executor);
|
|
116
|
+
await expect(capability.plan!(ctx, { stack: "network", template })).rejects.toThrow(/Template format error/);
|
|
117
|
+
expect(methods(base)).toEqual(["deleteChangeSet"]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("the plan digest binds the template's bytes and the parameters, not the change set's name", async () => {
|
|
121
|
+
const a = await planOne({ changes: [] });
|
|
122
|
+
const b = await planOne({ changes: [] });
|
|
123
|
+
expect(b.planned.planDigest).toBe(a.planned.planDigest);
|
|
124
|
+
writeFileSync(template, JSON.stringify({ Resources: { Topic: { Type: "AWS::SNS::Topic", Properties: { DisplayName: "x" } } } }));
|
|
125
|
+
const c = await planOne({ changes: [] });
|
|
126
|
+
expect(c.planned.planDigest).not.toBe(a.planned.planDigest);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("reads a stack's outputs as they stand, and none for a stack that does not exist", async () => {
|
|
130
|
+
const mock = createMockCloudExecutor({ stacks: { network: { outputs: { TopicArn: "arn:t" } }, gone: { missing: true } } });
|
|
131
|
+
const capability = createCfnDeployCapability(mock.executor);
|
|
132
|
+
expect(await capability.outputs!(ctx, { stack: "network", template })).toEqual({ TopicArn: "arn:t" });
|
|
133
|
+
expect(await capability.outputs!(ctx, { stack: "gone", template })).toEqual({});
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe("pr-plan over cfn-deploy members (#4084)", () => {
|
|
138
|
+
const components = (): DriverComponent[] => [
|
|
139
|
+
{ name: "network", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "cfn-deploy", stack: "network", template }] }] },
|
|
140
|
+
{
|
|
141
|
+
name: "cluster-a",
|
|
142
|
+
dependsOn: ["network"],
|
|
143
|
+
deploy: [
|
|
144
|
+
{
|
|
145
|
+
phase: "Apply",
|
|
146
|
+
steps: [{ kind: "cfn-deploy", stack: "cluster-a", template, inputs: { TopicArn: { stackOutput: { stack: "network", name: "TopicArn" } } } }],
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
async function planBoth(stacks: Record<string, FakeStackConfig>) {
|
|
153
|
+
const mock = createMockCloudExecutor({ stacks });
|
|
154
|
+
const registry = new CapabilityRegistry();
|
|
155
|
+
registry.register(createCfnDeployCapability(mock.executor));
|
|
156
|
+
const all = components();
|
|
157
|
+
const plan = planFanOut({ components: all, changed: ["network"] });
|
|
158
|
+
const set = await planPrSet({ components: all, plan, registry, env: "local" });
|
|
159
|
+
const report = prReport({
|
|
160
|
+
stage: "plan",
|
|
161
|
+
pr: 7,
|
|
162
|
+
env: "local",
|
|
163
|
+
base: "1".repeat(40),
|
|
164
|
+
head: "2".repeat(40),
|
|
165
|
+
op: "pr-7",
|
|
166
|
+
gate: "pr-apply",
|
|
167
|
+
plan,
|
|
168
|
+
signal: { changed: ["network"], unclaimed: [] } as never,
|
|
169
|
+
set,
|
|
170
|
+
approval: { status: "pending" },
|
|
171
|
+
status: "planned",
|
|
172
|
+
});
|
|
173
|
+
return { mock, set, report };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
it("counts each member's changes from its change set, and the note and status line show them", async () => {
|
|
177
|
+
const { set, report } = await planBoth({
|
|
178
|
+
network: {
|
|
179
|
+
outputs: { TopicArn: "arn:topic" },
|
|
180
|
+
changes: [{ action: "Modify", logicalResourceId: "Topic", resourceType: "AWS::SNS::Topic", replacement: false, details: ["DisplayName"] }],
|
|
181
|
+
},
|
|
182
|
+
"cluster-a": { changes: [{ action: "Add", logicalResourceId: "clusterAQueue", resourceType: "AWS::SQS::Queue", replacement: false }] },
|
|
183
|
+
});
|
|
184
|
+
expect(set.members.map((m) => [m.member, m.counts])).toEqual([
|
|
185
|
+
["cluster-a", { create: 1, update: 0, replace: 0, delete: 0 }],
|
|
186
|
+
["network", { create: 0, update: 1, replace: 0, delete: 0 }],
|
|
187
|
+
]);
|
|
188
|
+
const note = renderPrNote(report);
|
|
189
|
+
expect(note).toContain("| `network` | network | planned | 0 | 1 | 0 | 0 |");
|
|
190
|
+
expect(note).toContain("| `cluster-a` | cluster-a | planned | 1 | 0 | 0 | 0 |");
|
|
191
|
+
expect(note).not.toContain("no changes");
|
|
192
|
+
expect(prStatusDescription(report)).toMatch(/^2 members: 2 to change, 0 to destroy or replace;/);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("a member whose inputs come from a stack the run creates first says so, with no counts and no change set", async () => {
|
|
196
|
+
const { mock, set, report } = await planBoth({
|
|
197
|
+
network: { missing: true, changes: [{ action: "Add", logicalResourceId: "Topic", resourceType: "AWS::SNS::Topic", replacement: false }] },
|
|
198
|
+
"cluster-a": { missing: true },
|
|
199
|
+
});
|
|
200
|
+
const clusterA = set.doc.members.find((m) => m.member === "cluster-a")!;
|
|
201
|
+
expect(clusterA).toMatchObject({ status: "planned", holes: [{ address: "cluster-a", reason: "inputs from network; re-checked on apply" }] });
|
|
202
|
+
expect(set.doc.entries.filter((e) => e.member === "cluster-a")).toEqual([]);
|
|
203
|
+
expect(mock.calls.filter((c) => c.method === "createChangeSet").map((c) => (c.args as { stackName: string }).stackName)).toEqual(["network"]);
|
|
204
|
+
const note = renderPrNote(report);
|
|
205
|
+
expect(note).toContain("| `cluster-a` | cluster-a | planned; inputs from network; re-checked on apply | ? | ? | ? | ? |");
|
|
206
|
+
expect(note).toContain("| `network` | network | planned | 1 | 0 | 0 | 0 |");
|
|
207
|
+
expect(prStatusDescription(report)).toContain("1 with holes");
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("planning the same state twice gives the same digest, so the apply's plan matches the approved one", async () => {
|
|
211
|
+
const stacks = (): Record<string, FakeStackConfig> => ({
|
|
212
|
+
network: { outputs: { TopicArn: "arn:topic" }, changes: [{ action: "Modify", logicalResourceId: "Topic", resourceType: "AWS::SNS::Topic", replacement: false }] },
|
|
213
|
+
"cluster-a": { noChanges: true },
|
|
214
|
+
});
|
|
215
|
+
const first = await planBoth(stacks());
|
|
216
|
+
const again = await planBoth(stacks());
|
|
217
|
+
expect(again.set.doc.digest).toBe(first.set.doc.digest);
|
|
218
|
+
const replaced = await planBoth({
|
|
219
|
+
...stacks(),
|
|
220
|
+
network: { outputs: { TopicArn: "arn:topic" }, changes: [{ action: "Modify", logicalResourceId: "Topic", resourceType: "AWS::SNS::Topic", replacement: true }] },
|
|
221
|
+
});
|
|
222
|
+
expect(replaced.set.doc.digest).not.toBe(first.set.doc.digest);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
@@ -45,6 +45,13 @@ export interface CfnChange {
|
|
|
45
45
|
resourceType: string;
|
|
46
46
|
/** True when CloudFormation must replace (destroy + recreate) this resource to apply the change. */
|
|
47
47
|
replacement: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* CloudFormation's own `Replacement` value, when the backend reports one.
|
|
50
|
+
* `Conditional` means the replacement depends on a value CloudFormation
|
|
51
|
+
* resolves only while applying; `replacement` is false for it, and a plan
|
|
52
|
+
* counts it as a replace (#4084).
|
|
53
|
+
*/
|
|
54
|
+
replacementMode?: "True" | "False" | "Conditional";
|
|
48
55
|
/** Resource-specific detail, when the executor's changeset backend provides it (e.g. property/scope). */
|
|
49
56
|
details?: string[];
|
|
50
57
|
}
|
|
@@ -60,6 +67,15 @@ export interface CfnChangeSet {
|
|
|
60
67
|
statusReason?: string;
|
|
61
68
|
/** True the first time this stack is created (no prior stack exists). */
|
|
62
69
|
isCreate: boolean;
|
|
70
|
+
/** The change set's type: CREATE for a stack that does not exist yet or was never executed, else UPDATE. */
|
|
71
|
+
changeSetType?: "CREATE" | "UPDATE";
|
|
72
|
+
/**
|
|
73
|
+
* True when no stack existed before this change set, so CloudFormation made
|
|
74
|
+
* an empty `REVIEW_IN_PROGRESS` stack to hold it. A plan that deletes the
|
|
75
|
+
* change set without executing it deletes that stack too
|
|
76
|
+
* (`deleteStackShell`, #4084).
|
|
77
|
+
*/
|
|
78
|
+
stackCreated?: boolean;
|
|
63
79
|
changes: CfnChange[];
|
|
64
80
|
}
|
|
65
81
|
|
|
@@ -92,6 +108,13 @@ export interface CloudFormationClient {
|
|
|
92
108
|
describeStack(stackName: string): Promise<CfnStackStatus>;
|
|
93
109
|
/** Trigger CloudFormation's native rollback-to-last-known-good-state for a stack (saga compensation). */
|
|
94
110
|
rollbackStack(stackName: string): Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* Delete a stack only while it is the empty `REVIEW_IN_PROGRESS` stack a
|
|
113
|
+
* CREATE change set made, and say whether it deleted it (#4084). A stack in
|
|
114
|
+
* any other status is left alone. Optional: without it, a plan leaves the
|
|
115
|
+
* empty stack, and the next CREATE change set reuses it.
|
|
116
|
+
*/
|
|
117
|
+
deleteStackShell?(stackName: string): Promise<boolean>;
|
|
95
118
|
}
|
|
96
119
|
|
|
97
120
|
// ── ECS ──────────────────────────────────────────────────────────────────��─
|
|
@@ -376,14 +399,17 @@ const realEcr: EcrClient = {
|
|
|
376
399
|
},
|
|
377
400
|
};
|
|
378
401
|
|
|
379
|
-
/**
|
|
380
|
-
|
|
402
|
+
/**
|
|
403
|
+
* CREATE for a stack that doesn't exist (or is stuck in REVIEW_IN_PROGRESS from
|
|
404
|
+
* a prior unexecuted CREATE), else UPDATE; and whether a stack existed at all.
|
|
405
|
+
*/
|
|
406
|
+
async function cfnChangeSetType(stackName: string): Promise<{ type: "CREATE" | "UPDATE"; existed: boolean }> {
|
|
381
407
|
try {
|
|
382
408
|
const { stdout } = await run(`aws cloudformation describe-stacks --stack-name ${q(stackName)}`);
|
|
383
409
|
const status = (JSON.parse(stdout) as { Stacks?: Array<{ StackStatus?: string }> }).Stacks?.[0]?.StackStatus;
|
|
384
|
-
return status === "REVIEW_IN_PROGRESS" ? "CREATE" : "UPDATE";
|
|
410
|
+
return { type: status === "REVIEW_IN_PROGRESS" ? "CREATE" : "UPDATE", existed: true };
|
|
385
411
|
} catch {
|
|
386
|
-
return "CREATE"; // describe-stacks errors when the stack doesn't exist
|
|
412
|
+
return { type: "CREATE", existed: false }; // describe-stacks errors when the stack doesn't exist
|
|
387
413
|
}
|
|
388
414
|
}
|
|
389
415
|
|
|
@@ -429,7 +455,7 @@ const realCloudFormation: CloudFormationClient = {
|
|
|
429
455
|
// exist yet ("Stack ... does not exist"). Pick CREATE for a new stack, and
|
|
430
456
|
// for one still in REVIEW_IN_PROGRESS (a prior CREATE change set never
|
|
431
457
|
// executed), mirroring what `aws cloudformation deploy` does under the hood.
|
|
432
|
-
const changeSetType = await cfnChangeSetType(args.stackName);
|
|
458
|
+
const { type: changeSetType, existed } = await cfnChangeSetType(args.stackName);
|
|
433
459
|
let body = "";
|
|
434
460
|
try {
|
|
435
461
|
body = readFileSync(args.templatePath, "utf8");
|
|
@@ -473,11 +499,16 @@ const realCloudFormation: CloudFormationClient = {
|
|
|
473
499
|
status: described.Status,
|
|
474
500
|
statusReason: described.StatusReason,
|
|
475
501
|
isCreate: described.Status === "CREATE_COMPLETE" && !described.Changes?.length,
|
|
502
|
+
changeSetType,
|
|
503
|
+
stackCreated: !existed,
|
|
476
504
|
changes: (described.Changes ?? []).map((c) => ({
|
|
477
505
|
action: c.ResourceChange.Action as CfnChange["action"],
|
|
478
506
|
logicalResourceId: c.ResourceChange.LogicalResourceId,
|
|
479
507
|
resourceType: c.ResourceChange.ResourceType,
|
|
480
508
|
replacement: c.ResourceChange.Replacement === "True",
|
|
509
|
+
...(c.ResourceChange.Replacement === "True" || c.ResourceChange.Replacement === "False" || c.ResourceChange.Replacement === "Conditional"
|
|
510
|
+
? { replacementMode: c.ResourceChange.Replacement }
|
|
511
|
+
: {}),
|
|
481
512
|
details: c.ResourceChange.Details?.map((d) => d.Target?.Name).filter((x): x is string => !!x),
|
|
482
513
|
})),
|
|
483
514
|
};
|
|
@@ -515,6 +546,17 @@ const realCloudFormation: CloudFormationClient = {
|
|
|
515
546
|
async rollbackStack(stackName) {
|
|
516
547
|
await run(`aws cloudformation rollback-stack --stack-name ${q(stackName)}`);
|
|
517
548
|
},
|
|
549
|
+
async deleteStackShell(stackName) {
|
|
550
|
+
let status: string;
|
|
551
|
+
try {
|
|
552
|
+
status = (await realCloudFormation.describeStack(stackName)).stackStatus;
|
|
553
|
+
} catch {
|
|
554
|
+
return false; // gone already
|
|
555
|
+
}
|
|
556
|
+
if (status !== "REVIEW_IN_PROGRESS") return false;
|
|
557
|
+
await run(`aws cloudformation delete-stack --stack-name ${q(stackName)}`);
|
|
558
|
+
return true;
|
|
559
|
+
},
|
|
518
560
|
};
|
|
519
561
|
|
|
520
562
|
/** Deployment id from an ECS `update-service` response. Tolerant of Floci, whose
|