sst 2.48.5 → 2.49.2
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/bootstrap.js +1 -1
- package/credentials.d.ts +4 -4
- package/credentials.js +14 -3
- package/package.json +33 -33
- package/package.json.bak +33 -33
- package/stacks/deploy.d.ts +1 -1
- package/stacks/deploy.js +151 -51
- package/stacks/synth.js +14 -4
- package/support/base-site-archiver.mjs +9 -9
- package/support/bootstrap-metadata-function/index.mjs +35879 -61286
- package/support/bridge/live-lambda.mjs +54 -106
- package/support/custom-resources/index.mjs +49974 -89842
- package/support/event-bus-retrier/index.mjs +32 -85
- package/support/job-manager/index.mjs +8205 -29763
- package/support/rds-migrator/index.mjs +42 -26
- package/support/script-function/index.mjs +32065 -40311
- package/support/signing-function/index.mjs +2806 -1466
- package/support/ssr-site-function-archiver.mjs +7 -7
- package/support/ssr-warmer/index.mjs +22577 -27092
- package/util/user-configuration.d.ts +89 -0
- package/util/user-configuration.js +311 -0
- package/cdk/deploy-stack.d.ts +0 -180
- package/cdk/deploy-stack.js +0 -520
- package/cdk/deployments-wrapper.d.ts +0 -3
- package/cdk/deployments-wrapper.js +0 -117
- package/cdk/deployments.d.ts +0 -323
- package/cdk/deployments.js +0 -408
package/cdk/deploy-stack.js
DELETED
|
@@ -1,520 +0,0 @@
|
|
|
1
|
-
import * as uuid from "uuid";
|
|
2
|
-
import { AssetManifestBuilder } from "sst-aws-cdk/lib/api/deployments/asset-manifest-builder.js";
|
|
3
|
-
import { publishAssets } from "sst-aws-cdk/lib/api/deployments/asset-publishing.js";
|
|
4
|
-
import { addMetadataAssetsToManifest } from "sst-aws-cdk/lib/api/deployments/assets.js";
|
|
5
|
-
import { determineAllowCrossAccountAssetPublishing } from "sst-aws-cdk/lib/api/deployments/checks.js";
|
|
6
|
-
import { changeSetHasNoChanges, CloudFormationStack, TemplateParameters, waitForChangeSet, waitForStackDeploy, waitForStackDelete, } from "sst-aws-cdk/lib/api/deployments/cloudformation.js";
|
|
7
|
-
import { tryHotswapDeployment } from "sst-aws-cdk/lib/api/deployments/hotswap-deployments.js";
|
|
8
|
-
import { debug, info, warning } from "sst-aws-cdk/lib/logging.js";
|
|
9
|
-
import { ToolkitError } from "sst-aws-cdk/lib/toolkit/error.js";
|
|
10
|
-
import { formatErrorMessage } from "sst-aws-cdk/lib/util/error.js";
|
|
11
|
-
import { CfnEvaluationException } from "sst-aws-cdk/lib/api/evaluate-cloudformation-template.js";
|
|
12
|
-
import { HotswapMode, HotswapPropertyOverrides, } from "sst-aws-cdk/lib/api/hotswap/common.js";
|
|
13
|
-
import { makeBodyParameter, } from "sst-aws-cdk/lib/api/util/template-body-parameter.js";
|
|
14
|
-
import { callWithRetry } from "./util.js";
|
|
15
|
-
export async function deployStack(options) {
|
|
16
|
-
const stackArtifact = options.stack;
|
|
17
|
-
const stackEnv = options.resolvedEnvironment;
|
|
18
|
-
options.sdk.appendCustomUserAgent(options.extraUserAgent);
|
|
19
|
-
const cfn = options.sdk.cloudFormation();
|
|
20
|
-
const deployName = options.deployName || stackArtifact.stackName;
|
|
21
|
-
let cloudFormationStack = await callWithRetry(() => CloudFormationStack.lookup(cfn, deployName));
|
|
22
|
-
if (cloudFormationStack.stackStatus.isCreationFailure) {
|
|
23
|
-
debug(`Found existing stack ${deployName} that had previously failed creation. Deleting it before attempting to re-create it.`);
|
|
24
|
-
await cfn.deleteStack({ StackName: deployName });
|
|
25
|
-
const deletedStack = await waitForStackDelete(cfn, deployName);
|
|
26
|
-
if (deletedStack && deletedStack.stackStatus.name !== "DELETE_COMPLETE") {
|
|
27
|
-
throw new ToolkitError(`Failed deleting stack ${deployName} that had previously failed creation (current state: ${deletedStack.stackStatus})`);
|
|
28
|
-
}
|
|
29
|
-
// Update variable to mark that the stack does not exist anymore, but avoid
|
|
30
|
-
// doing an actual lookup in CloudFormation (which would be silly to do if
|
|
31
|
-
// we just deleted it).
|
|
32
|
-
cloudFormationStack = CloudFormationStack.doesNotExist(cfn, deployName);
|
|
33
|
-
}
|
|
34
|
-
// Detect "legacy" assets (which remain in the metadata) and publish them via
|
|
35
|
-
// an ad-hoc asset manifest, while passing their locations via template
|
|
36
|
-
// parameters.
|
|
37
|
-
const legacyAssets = new AssetManifestBuilder();
|
|
38
|
-
const assetParams = await addMetadataAssetsToManifest(stackArtifact, legacyAssets, options.envResources, options.reuseAssets);
|
|
39
|
-
const finalParameterValues = { ...options.parameters, ...assetParams };
|
|
40
|
-
const templateParams = TemplateParameters.fromTemplate(stackArtifact.template);
|
|
41
|
-
const stackParams = options.usePreviousParameters
|
|
42
|
-
? templateParams.updateExisting(finalParameterValues, cloudFormationStack.parameters)
|
|
43
|
-
: templateParams.supplyAll(finalParameterValues);
|
|
44
|
-
if (await canSkipDeploy(options, cloudFormationStack, stackParams.hasChanges(cloudFormationStack.parameters))) {
|
|
45
|
-
debug(`${deployName}: skipping deployment (use --force to override)`);
|
|
46
|
-
// if we can skip deployment and we are performing a hotswap, let the user know
|
|
47
|
-
// that no hotswap deployment happened
|
|
48
|
-
if (options.hotswap) {
|
|
49
|
-
}
|
|
50
|
-
return {
|
|
51
|
-
type: "did-deploy-stack",
|
|
52
|
-
noOp: true,
|
|
53
|
-
outputs: cloudFormationStack.outputs,
|
|
54
|
-
stackArn: cloudFormationStack.stackId,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
debug(`${deployName}: deploying...`);
|
|
59
|
-
}
|
|
60
|
-
const bodyParameter = await makeBodyParameter(stackArtifact, options.resolvedEnvironment, legacyAssets, options.envResources, options.overrideTemplate);
|
|
61
|
-
let bootstrapStackName;
|
|
62
|
-
try {
|
|
63
|
-
bootstrapStackName = (await options.envResources.lookupToolkit()).stackName;
|
|
64
|
-
}
|
|
65
|
-
catch (e) {
|
|
66
|
-
debug(`Could not determine the bootstrap stack name: ${e}`);
|
|
67
|
-
}
|
|
68
|
-
await publishAssets(legacyAssets.toManifest(stackArtifact.assembly.directory), options.sdkProvider, stackEnv, {
|
|
69
|
-
parallel: options.assetParallelism,
|
|
70
|
-
allowCrossAccount: await determineAllowCrossAccountAssetPublishing(options.sdk, bootstrapStackName),
|
|
71
|
-
});
|
|
72
|
-
const hotswapMode = options.hotswap;
|
|
73
|
-
const hotswapPropertyOverrides = options.hotswapPropertyOverrides ?? new HotswapPropertyOverrides();
|
|
74
|
-
if (hotswapMode && hotswapMode !== HotswapMode.FULL_DEPLOYMENT) {
|
|
75
|
-
// attempt to short-circuit the deployment if possible
|
|
76
|
-
try {
|
|
77
|
-
const hotswapDeploymentResult = await tryHotswapDeployment(options.sdkProvider, stackParams.values, cloudFormationStack, stackArtifact, hotswapMode, hotswapPropertyOverrides);
|
|
78
|
-
if (hotswapDeploymentResult) {
|
|
79
|
-
return hotswapDeploymentResult;
|
|
80
|
-
}
|
|
81
|
-
info("Could not perform a hotswap deployment, as the stack %s contains non-Asset changes", stackArtifact.displayName);
|
|
82
|
-
}
|
|
83
|
-
catch (e) {
|
|
84
|
-
if (!(e instanceof CfnEvaluationException)) {
|
|
85
|
-
throw e;
|
|
86
|
-
}
|
|
87
|
-
info("Could not perform a hotswap deployment, because the CloudFormation template could not be resolved: %s", formatErrorMessage(e));
|
|
88
|
-
}
|
|
89
|
-
if (hotswapMode === HotswapMode.FALL_BACK) {
|
|
90
|
-
info("Falling back to doing a full deployment");
|
|
91
|
-
options.sdk.appendCustomUserAgent("cdk-hotswap/fallback");
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
return {
|
|
95
|
-
type: "did-deploy-stack",
|
|
96
|
-
noOp: true,
|
|
97
|
-
stackArn: cloudFormationStack.stackId,
|
|
98
|
-
outputs: cloudFormationStack.outputs,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
// could not short-circuit the deployment, perform a full CFN deploy instead
|
|
103
|
-
const fullDeployment = new FullCloudFormationDeployment(options, cloudFormationStack, stackArtifact, stackParams, bodyParameter);
|
|
104
|
-
return fullDeployment.performDeployment();
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* This class shares state and functionality between the different full deployment modes
|
|
108
|
-
*/
|
|
109
|
-
class FullCloudFormationDeployment {
|
|
110
|
-
options;
|
|
111
|
-
cloudFormationStack;
|
|
112
|
-
stackArtifact;
|
|
113
|
-
stackParams;
|
|
114
|
-
bodyParameter;
|
|
115
|
-
cfn;
|
|
116
|
-
stackName;
|
|
117
|
-
update;
|
|
118
|
-
verb;
|
|
119
|
-
uuid;
|
|
120
|
-
constructor(options, cloudFormationStack, stackArtifact, stackParams, bodyParameter) {
|
|
121
|
-
this.options = options;
|
|
122
|
-
this.cloudFormationStack = cloudFormationStack;
|
|
123
|
-
this.stackArtifact = stackArtifact;
|
|
124
|
-
this.stackParams = stackParams;
|
|
125
|
-
this.bodyParameter = bodyParameter;
|
|
126
|
-
this.cfn = options.sdk.cloudFormation();
|
|
127
|
-
this.stackName = options.deployName ?? stackArtifact.stackName;
|
|
128
|
-
this.update =
|
|
129
|
-
cloudFormationStack.exists &&
|
|
130
|
-
cloudFormationStack.stackStatus.name !== "REVIEW_IN_PROGRESS";
|
|
131
|
-
this.verb = this.update ? "update" : "create";
|
|
132
|
-
this.uuid = uuid.v4();
|
|
133
|
-
}
|
|
134
|
-
async performDeployment() {
|
|
135
|
-
const deploymentMethod = this.options.deploymentMethod ?? {
|
|
136
|
-
method: "change-set",
|
|
137
|
-
};
|
|
138
|
-
if (deploymentMethod.method === "direct" &&
|
|
139
|
-
this.options.resourcesToImport) {
|
|
140
|
-
throw new ToolkitError("Importing resources requires a changeset deployment");
|
|
141
|
-
}
|
|
142
|
-
switch (deploymentMethod.method) {
|
|
143
|
-
case "change-set":
|
|
144
|
-
return this.changeSetDeployment(deploymentMethod);
|
|
145
|
-
case "direct":
|
|
146
|
-
return this.directDeployment();
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
async changeSetDeployment(deploymentMethod) {
|
|
150
|
-
const changeSetName = deploymentMethod.changeSetName ?? "cdk-deploy-change-set";
|
|
151
|
-
const execute = deploymentMethod.execute ?? true;
|
|
152
|
-
const importExistingResources = deploymentMethod.importExistingResources ?? false;
|
|
153
|
-
const changeSetDescription = await this.createChangeSet(changeSetName, execute, importExistingResources);
|
|
154
|
-
await this.updateTerminationProtection();
|
|
155
|
-
if (changeSetHasNoChanges(changeSetDescription)) {
|
|
156
|
-
debug("No changes are to be performed on %s.", this.stackName);
|
|
157
|
-
if (execute) {
|
|
158
|
-
debug("Deleting empty change set %s", changeSetDescription.ChangeSetId);
|
|
159
|
-
await this.cfn.deleteChangeSet({
|
|
160
|
-
StackName: this.stackName,
|
|
161
|
-
ChangeSetName: changeSetName,
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
if (this.options.force) {
|
|
165
|
-
warning([
|
|
166
|
-
"You used the --force flag, but CloudFormation reported that the deployment would not make any changes.",
|
|
167
|
-
"According to CloudFormation, all resources are already up-to-date with the state in your CDK app.",
|
|
168
|
-
"",
|
|
169
|
-
"You cannot use the --force flag to get rid of changes you made in the console. Try using",
|
|
170
|
-
"CloudFormation drift detection instead: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-stack-drift.html",
|
|
171
|
-
].join("\n"));
|
|
172
|
-
}
|
|
173
|
-
return {
|
|
174
|
-
type: "did-deploy-stack",
|
|
175
|
-
noOp: true,
|
|
176
|
-
outputs: this.cloudFormationStack.outputs,
|
|
177
|
-
stackArn: changeSetDescription.StackId,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
if (!execute) {
|
|
181
|
-
info("Changeset %s created and waiting in review for manual execution (--no-execute)", changeSetDescription.ChangeSetId);
|
|
182
|
-
return {
|
|
183
|
-
type: "did-deploy-stack",
|
|
184
|
-
noOp: false,
|
|
185
|
-
outputs: this.cloudFormationStack.outputs,
|
|
186
|
-
stackArn: changeSetDescription.StackId,
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
// If there are replacements in the changeset, check the rollback flag and stack status
|
|
190
|
-
const replacement = hasReplacement(changeSetDescription);
|
|
191
|
-
const isPausedFailState = this.cloudFormationStack.stackStatus.isRollbackable;
|
|
192
|
-
const rollback = this.options.rollback ?? true;
|
|
193
|
-
if (isPausedFailState && replacement) {
|
|
194
|
-
return {
|
|
195
|
-
type: "failpaused-need-rollback-first",
|
|
196
|
-
reason: "replacement",
|
|
197
|
-
status: this.cloudFormationStack.stackStatus.name,
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
if (isPausedFailState && rollback) {
|
|
201
|
-
return {
|
|
202
|
-
type: "failpaused-need-rollback-first",
|
|
203
|
-
reason: "not-norollback",
|
|
204
|
-
status: this.cloudFormationStack.stackStatus.name,
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
if (!rollback && replacement) {
|
|
208
|
-
return { type: "replacement-requires-rollback" };
|
|
209
|
-
}
|
|
210
|
-
return this.executeChangeSet(changeSetDescription);
|
|
211
|
-
}
|
|
212
|
-
async createChangeSet(changeSetName, willExecute, importExistingResources) {
|
|
213
|
-
await this.cleanupOldChangeset(changeSetName);
|
|
214
|
-
debug(`Attempting to create ChangeSet with name ${changeSetName} to ${this.verb} stack ${this.stackName}`);
|
|
215
|
-
const changeSet = await this.cfn.createChangeSet({
|
|
216
|
-
StackName: this.stackName,
|
|
217
|
-
ChangeSetName: changeSetName,
|
|
218
|
-
ChangeSetType: this.options.resourcesToImport
|
|
219
|
-
? "IMPORT"
|
|
220
|
-
: this.update
|
|
221
|
-
? "UPDATE"
|
|
222
|
-
: "CREATE",
|
|
223
|
-
ResourcesToImport: this.options.resourcesToImport,
|
|
224
|
-
Description: `CDK Changeset for execution ${this.uuid}`,
|
|
225
|
-
ClientToken: `create${this.uuid}`,
|
|
226
|
-
ImportExistingResources: importExistingResources,
|
|
227
|
-
...this.commonPrepareOptions(),
|
|
228
|
-
});
|
|
229
|
-
debug("Initiated creation of changeset: %s; waiting for it to finish creating...", changeSet.Id);
|
|
230
|
-
// Fetching all pages if we'll execute, so we can have the correct change count when monitoring.
|
|
231
|
-
return waitForChangeSet(this.cfn, this.stackName, changeSetName, {
|
|
232
|
-
fetchAll: willExecute,
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
async executeChangeSet(changeSet) {
|
|
236
|
-
debug("Initiating execution of changeset %s on stack %s", changeSet.ChangeSetId, this.stackName);
|
|
237
|
-
await this.cfn.executeChangeSet({
|
|
238
|
-
StackName: this.stackName,
|
|
239
|
-
ChangeSetName: changeSet.ChangeSetName,
|
|
240
|
-
ClientRequestToken: `exec${this.uuid}`,
|
|
241
|
-
...this.commonExecuteOptions(),
|
|
242
|
-
});
|
|
243
|
-
debug("Execution of changeset %s on stack %s has started; waiting for the update to complete...", changeSet.ChangeSetId, this.stackName);
|
|
244
|
-
// +1 for the extra event emitted from updates.
|
|
245
|
-
const changeSetLength = (changeSet.Changes ?? []).length + (this.update ? 1 : 0);
|
|
246
|
-
return this.monitorDeployment(changeSet.CreationTime, changeSetLength);
|
|
247
|
-
}
|
|
248
|
-
async cleanupOldChangeset(changeSetName) {
|
|
249
|
-
if (this.cloudFormationStack.exists) {
|
|
250
|
-
// Delete any existing change sets generated by CDK since change set names must be unique.
|
|
251
|
-
// The delete request is successful as long as the stack exists (even if the change set does not exist).
|
|
252
|
-
debug(`Removing existing change set with name ${changeSetName} if it exists`);
|
|
253
|
-
await this.cfn.deleteChangeSet({
|
|
254
|
-
StackName: this.stackName,
|
|
255
|
-
ChangeSetName: changeSetName,
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
async updateTerminationProtection() {
|
|
260
|
-
// Update termination protection only if it has changed.
|
|
261
|
-
const terminationProtection = this.stackArtifact.terminationProtection ?? false;
|
|
262
|
-
if (!!this.cloudFormationStack.terminationProtection !== terminationProtection) {
|
|
263
|
-
debug("Updating termination protection from %s to %s for stack %s", this.cloudFormationStack.terminationProtection, terminationProtection, this.stackName);
|
|
264
|
-
await this.cfn.updateTerminationProtection({
|
|
265
|
-
StackName: this.stackName,
|
|
266
|
-
EnableTerminationProtection: terminationProtection,
|
|
267
|
-
});
|
|
268
|
-
debug("Termination protection updated to %s for stack %s", terminationProtection, this.stackName);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
async directDeployment() {
|
|
272
|
-
const startTime = new Date();
|
|
273
|
-
if (this.update) {
|
|
274
|
-
await this.updateTerminationProtection();
|
|
275
|
-
try {
|
|
276
|
-
await this.cfn.updateStack({
|
|
277
|
-
StackName: this.stackName,
|
|
278
|
-
ClientRequestToken: `update${this.uuid}`,
|
|
279
|
-
...this.commonPrepareOptions(),
|
|
280
|
-
...this.commonExecuteOptions(),
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
catch (err) {
|
|
284
|
-
if (err.message === "No updates are to be performed.") {
|
|
285
|
-
debug("No updates are to be performed for stack %s", this.stackName);
|
|
286
|
-
return {
|
|
287
|
-
type: "did-deploy-stack",
|
|
288
|
-
noOp: true,
|
|
289
|
-
outputs: this.cloudFormationStack.outputs,
|
|
290
|
-
stackArn: this.cloudFormationStack.stackId,
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
throw err;
|
|
294
|
-
}
|
|
295
|
-
if (this.options.noMonitor)
|
|
296
|
-
return;
|
|
297
|
-
return this.monitorDeployment(startTime, undefined);
|
|
298
|
-
}
|
|
299
|
-
else {
|
|
300
|
-
// Take advantage of the fact that we can set termination protection during create
|
|
301
|
-
const terminationProtection = this.stackArtifact.terminationProtection ?? false;
|
|
302
|
-
await this.cfn.createStack({
|
|
303
|
-
StackName: this.stackName,
|
|
304
|
-
ClientRequestToken: `create${this.uuid}`,
|
|
305
|
-
...(terminationProtection
|
|
306
|
-
? { EnableTerminationProtection: true }
|
|
307
|
-
: undefined),
|
|
308
|
-
...this.commonPrepareOptions(),
|
|
309
|
-
...this.commonExecuteOptions(),
|
|
310
|
-
});
|
|
311
|
-
if (this.options.noMonitor)
|
|
312
|
-
return;
|
|
313
|
-
return this.monitorDeployment(startTime, undefined);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
async monitorDeployment(startTime, expectedChanges) {
|
|
317
|
-
// const monitor = this.options.quiet
|
|
318
|
-
// ? undefined
|
|
319
|
-
// : StackActivityMonitor.withDefaultPrinter(
|
|
320
|
-
// this.cfn,
|
|
321
|
-
// this.stackName,
|
|
322
|
-
// this.stackArtifact,
|
|
323
|
-
// {
|
|
324
|
-
// resourcesTotal: expectedChanges,
|
|
325
|
-
// progress: this.options.progress,
|
|
326
|
-
// changeSetCreationTime: startTime,
|
|
327
|
-
// ci: this.options.ci,
|
|
328
|
-
// }
|
|
329
|
-
// ).start();
|
|
330
|
-
let finalState = this.cloudFormationStack;
|
|
331
|
-
try {
|
|
332
|
-
const successStack = await waitForStackDeploy(this.cfn, this.stackName);
|
|
333
|
-
// This shouldn't really happen, but catch it anyway. You never know.
|
|
334
|
-
if (!successStack) {
|
|
335
|
-
throw new ToolkitError("Stack deploy failed (the stack disappeared while we were deploying it)");
|
|
336
|
-
}
|
|
337
|
-
finalState = successStack;
|
|
338
|
-
}
|
|
339
|
-
catch (e) {
|
|
340
|
-
throw new ToolkitError(suffixWithErrors(formatErrorMessage(e) /*, monitor?.errors*/));
|
|
341
|
-
}
|
|
342
|
-
finally {
|
|
343
|
-
// await monitor?.stop();
|
|
344
|
-
}
|
|
345
|
-
debug("Stack %s has completed updating", this.stackName);
|
|
346
|
-
return {
|
|
347
|
-
type: "did-deploy-stack",
|
|
348
|
-
noOp: false,
|
|
349
|
-
outputs: finalState.outputs,
|
|
350
|
-
stackArn: finalState.stackId,
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
/**
|
|
354
|
-
* Return the options that are shared between CreateStack, UpdateStack and CreateChangeSet
|
|
355
|
-
*/
|
|
356
|
-
commonPrepareOptions() {
|
|
357
|
-
return {
|
|
358
|
-
Capabilities: [
|
|
359
|
-
"CAPABILITY_IAM",
|
|
360
|
-
"CAPABILITY_NAMED_IAM",
|
|
361
|
-
"CAPABILITY_AUTO_EXPAND",
|
|
362
|
-
],
|
|
363
|
-
NotificationARNs: this.options.notificationArns,
|
|
364
|
-
Parameters: this.stackParams.apiParameters,
|
|
365
|
-
RoleARN: this.options.roleArn,
|
|
366
|
-
TemplateBody: this.bodyParameter.TemplateBody,
|
|
367
|
-
TemplateURL: this.bodyParameter.TemplateURL,
|
|
368
|
-
Tags: this.options.tags,
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
/**
|
|
372
|
-
* Return the options that are shared between UpdateStack and CreateChangeSet
|
|
373
|
-
*
|
|
374
|
-
* Be careful not to add in keys for options that aren't used, as the features may not have been
|
|
375
|
-
* deployed everywhere yet.
|
|
376
|
-
*/
|
|
377
|
-
commonExecuteOptions() {
|
|
378
|
-
const shouldDisableRollback = this.options.rollback === false;
|
|
379
|
-
return {
|
|
380
|
-
StackName: this.stackName,
|
|
381
|
-
...(shouldDisableRollback ? { DisableRollback: true } : undefined),
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
export async function destroyStack(options) {
|
|
386
|
-
const deployName = options.deployName || options.stack.stackName;
|
|
387
|
-
const cfn = options.sdk.cloudFormation();
|
|
388
|
-
const currentStack = await CloudFormationStack.lookup(cfn, deployName);
|
|
389
|
-
if (!currentStack.exists) {
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
/*
|
|
393
|
-
const monitor = options.quiet
|
|
394
|
-
? undefined
|
|
395
|
-
: StackActivityMonitor.withDefaultPrinter(cfn, deployName, options.stack, {
|
|
396
|
-
ci: options.ci,
|
|
397
|
-
}).start();
|
|
398
|
-
*/
|
|
399
|
-
try {
|
|
400
|
-
await cfn.deleteStack({ StackName: deployName, RoleARN: options.roleArn });
|
|
401
|
-
const destroyedStack = await waitForStackDelete(cfn, deployName);
|
|
402
|
-
if (destroyedStack &&
|
|
403
|
-
destroyedStack.stackStatus.name !== "DELETE_COMPLETE") {
|
|
404
|
-
throw new ToolkitError(`Failed to destroy ${deployName}: ${destroyedStack.stackStatus}`);
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
catch (e) {
|
|
408
|
-
throw new ToolkitError(suffixWithErrors(formatErrorMessage(e) /* , monitor?.errors */));
|
|
409
|
-
}
|
|
410
|
-
finally {
|
|
411
|
-
/*
|
|
412
|
-
if (monitor) {
|
|
413
|
-
await monitor.stop();
|
|
414
|
-
}
|
|
415
|
-
*/
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
/**
|
|
419
|
-
* Checks whether we can skip deployment
|
|
420
|
-
*
|
|
421
|
-
* We do this in a complicated way by preprocessing (instead of just
|
|
422
|
-
* looking at the changeset), because if there are nested stacks involved
|
|
423
|
-
* the changeset will always show the nested stacks as needing to be
|
|
424
|
-
* updated, and the deployment will take a long time to in effect not
|
|
425
|
-
* do anything.
|
|
426
|
-
*/
|
|
427
|
-
async function canSkipDeploy(deployStackOptions, cloudFormationStack, parameterChanges) {
|
|
428
|
-
const deployName = deployStackOptions.deployName || deployStackOptions.stack.stackName;
|
|
429
|
-
debug(`${deployName}: checking if we can skip deploy`);
|
|
430
|
-
// Forced deploy
|
|
431
|
-
if (deployStackOptions.force) {
|
|
432
|
-
debug(`${deployName}: forced deployment`);
|
|
433
|
-
return false;
|
|
434
|
-
}
|
|
435
|
-
// Creating changeset only (default true), never skip
|
|
436
|
-
if (deployStackOptions.deploymentMethod?.method === "change-set" &&
|
|
437
|
-
deployStackOptions.deploymentMethod.execute === false) {
|
|
438
|
-
debug(`${deployName}: --no-execute, always creating change set`);
|
|
439
|
-
return false;
|
|
440
|
-
}
|
|
441
|
-
// No existing stack
|
|
442
|
-
if (!cloudFormationStack.exists) {
|
|
443
|
-
debug(`${deployName}: no existing stack`);
|
|
444
|
-
return false;
|
|
445
|
-
}
|
|
446
|
-
// SST check: stack is not busy
|
|
447
|
-
if (cloudFormationStack.stackStatus.isInProgress) {
|
|
448
|
-
debug(`${deployName}: stack is busy`);
|
|
449
|
-
return false;
|
|
450
|
-
}
|
|
451
|
-
// Template has changed (assets taken into account here)
|
|
452
|
-
if (JSON.stringify(deployStackOptions.stack.template) !==
|
|
453
|
-
JSON.stringify(await cloudFormationStack.template())) {
|
|
454
|
-
debug(`${deployName}: template has changed`);
|
|
455
|
-
return false;
|
|
456
|
-
}
|
|
457
|
-
// Tags have changed
|
|
458
|
-
if (!compareTags(cloudFormationStack.tags, deployStackOptions.tags ?? [])) {
|
|
459
|
-
debug(`${deployName}: tags have changed`);
|
|
460
|
-
return false;
|
|
461
|
-
}
|
|
462
|
-
// Notification arns have changed
|
|
463
|
-
if (!arrayEquals(cloudFormationStack.notificationArns, deployStackOptions.notificationArns ?? [])) {
|
|
464
|
-
debug(`${deployName}: notification arns have changed`);
|
|
465
|
-
return false;
|
|
466
|
-
}
|
|
467
|
-
// Termination protection has been updated
|
|
468
|
-
if (!!deployStackOptions.stack.terminationProtection !==
|
|
469
|
-
!!cloudFormationStack.terminationProtection) {
|
|
470
|
-
debug(`${deployName}: termination protection has been updated`);
|
|
471
|
-
return false;
|
|
472
|
-
}
|
|
473
|
-
// Parameters have changed
|
|
474
|
-
if (parameterChanges) {
|
|
475
|
-
if (parameterChanges === "ssm") {
|
|
476
|
-
debug(`${deployName}: some parameters come from SSM so we have to assume they may have changed`);
|
|
477
|
-
}
|
|
478
|
-
else {
|
|
479
|
-
debug(`${deployName}: parameters have changed`);
|
|
480
|
-
}
|
|
481
|
-
return false;
|
|
482
|
-
}
|
|
483
|
-
// Existing stack is in a failed state
|
|
484
|
-
if (cloudFormationStack.stackStatus.isFailure) {
|
|
485
|
-
debug(`${deployName}: stack is in a failure state`);
|
|
486
|
-
return false;
|
|
487
|
-
}
|
|
488
|
-
// We can skip deploy
|
|
489
|
-
return true;
|
|
490
|
-
}
|
|
491
|
-
/**
|
|
492
|
-
* Compares two list of tags, returns true if identical.
|
|
493
|
-
*/
|
|
494
|
-
function compareTags(a, b) {
|
|
495
|
-
if (a.length !== b.length) {
|
|
496
|
-
return false;
|
|
497
|
-
}
|
|
498
|
-
for (const aTag of a) {
|
|
499
|
-
const bTag = b.find((tag) => tag.Key === aTag.Key);
|
|
500
|
-
if (!bTag || bTag.Value !== aTag.Value) {
|
|
501
|
-
return false;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
return true;
|
|
505
|
-
}
|
|
506
|
-
function suffixWithErrors(msg, errors) {
|
|
507
|
-
return errors && errors.length > 0 ? `${msg}: ${errors.join(", ")}` : msg;
|
|
508
|
-
}
|
|
509
|
-
function arrayEquals(a, b) {
|
|
510
|
-
return (a.every((item) => b.includes(item)) && b.every((item) => a.includes(item)));
|
|
511
|
-
}
|
|
512
|
-
function hasReplacement(cs) {
|
|
513
|
-
return (cs.Changes ?? []).some((c) => {
|
|
514
|
-
// @ts-ignore
|
|
515
|
-
const a = c.ResourceChange?.PolicyAction;
|
|
516
|
-
return (a === "ReplaceAndDelete" ||
|
|
517
|
-
a === "ReplaceAndRetain" ||
|
|
518
|
-
a === "ReplaceAndSnapshot");
|
|
519
|
-
});
|
|
520
|
-
}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import { SdkProvider } from "sst-aws-cdk/lib/api/aws-auth/sdk-provider.js";
|
|
2
|
-
import { DeployStackOptions as PublishStackAssetsOptions } from "./deployments.js";
|
|
3
|
-
export declare function publishDeployAssets(sdkProvider: SdkProvider, options: PublishStackAssetsOptions): Promise<any>;
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import * as cxapi from "@aws-cdk/cx-api";
|
|
2
|
-
import { AssetManifestBuilder } from "sst-aws-cdk/lib/api/deployments/asset-manifest-builder.js";
|
|
3
|
-
import { AssetManifest } from "cdk-assets";
|
|
4
|
-
import { debug } from "sst-aws-cdk/lib/logging.js";
|
|
5
|
-
import { CloudFormationStack, TemplateParameters, waitForStackDelete, } from "sst-aws-cdk/lib/api/deployments/cloudformation.js";
|
|
6
|
-
import { addMetadataAssetsToManifest } from "sst-aws-cdk/lib/api/deployments/assets.js";
|
|
7
|
-
import { makeBodyParameter } from "sst-aws-cdk/lib/api/util/template-body-parameter.js";
|
|
8
|
-
import { publishAssets, Deployments, } from "./deployments.js";
|
|
9
|
-
import { lazy } from "../util/lazy.js";
|
|
10
|
-
export async function publishDeployAssets(sdkProvider, options) {
|
|
11
|
-
const { deployment, envResources, stackSdk, resolvedEnvironment, executionRoleArn, } = await useDeployment().get(sdkProvider, options);
|
|
12
|
-
const assetArtifacts = options.stack.dependencies.filter(cxapi.AssetManifestArtifact.isAssetManifestArtifact);
|
|
13
|
-
for (const asset of assetArtifacts) {
|
|
14
|
-
const manifest = AssetManifest.fromFile(asset.file);
|
|
15
|
-
await publishAssets(manifest, sdkProvider, resolvedEnvironment, {
|
|
16
|
-
quiet: options.quiet,
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
return deployStack({
|
|
20
|
-
stack: options.stack,
|
|
21
|
-
noMonitor: true,
|
|
22
|
-
resolvedEnvironment,
|
|
23
|
-
deployName: options.deployName,
|
|
24
|
-
notificationArns: options.notificationArns,
|
|
25
|
-
quiet: options.quiet,
|
|
26
|
-
sdk: stackSdk,
|
|
27
|
-
sdkProvider,
|
|
28
|
-
roleArn: executionRoleArn,
|
|
29
|
-
reuseAssets: options.reuseAssets,
|
|
30
|
-
envResources,
|
|
31
|
-
tags: options.tags,
|
|
32
|
-
deploymentMethod: options.deploymentMethod,
|
|
33
|
-
force: options.force,
|
|
34
|
-
parameters: options.parameters,
|
|
35
|
-
usePreviousParameters: options.usePreviousParameters,
|
|
36
|
-
progress: options.progress,
|
|
37
|
-
ci: options.ci,
|
|
38
|
-
rollback: options.rollback,
|
|
39
|
-
hotswap: options.hotswap,
|
|
40
|
-
extraUserAgent: options.extraUserAgent,
|
|
41
|
-
resourcesToImport: options.resourcesToImport,
|
|
42
|
-
overrideTemplate: options.overrideTemplate,
|
|
43
|
-
assetParallelism: options.assetParallelism,
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
const useDeployment = lazy(() => {
|
|
47
|
-
const state = new Map();
|
|
48
|
-
return {
|
|
49
|
-
async get(sdkProvider, options) {
|
|
50
|
-
const region = options.stack.environment.region;
|
|
51
|
-
if (!state.has(region)) {
|
|
52
|
-
const deployment = new Deployments({ sdkProvider });
|
|
53
|
-
const env = await deployment.envs.accessStackForMutableStackOperations(options.stack);
|
|
54
|
-
const envResources = env.resources;
|
|
55
|
-
const executionRoleArn = await env.replacePlaceholders(options.roleArn ?? options.stack.cloudFormationExecutionRoleArn);
|
|
56
|
-
// Do a verification of the bootstrap stack version
|
|
57
|
-
await deployment.validateBootstrapStackVersion(options.stack.stackName, options.stack.requiresBootstrapStackVersion, options.stack.bootstrapStackVersionSsmParameter, envResources);
|
|
58
|
-
state.set(region, {
|
|
59
|
-
deployment,
|
|
60
|
-
envResources,
|
|
61
|
-
stackSdk: env.sdk,
|
|
62
|
-
resolvedEnvironment: env.resolvedEnvironment,
|
|
63
|
-
executionRoleArn,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
return state.get(region);
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
});
|
|
70
|
-
async function deployStack(options) {
|
|
71
|
-
const stackArtifact = options.stack;
|
|
72
|
-
const stackEnv = options.resolvedEnvironment;
|
|
73
|
-
options.sdk.appendCustomUserAgent(options.extraUserAgent);
|
|
74
|
-
const cfn = options.sdk.cloudFormation();
|
|
75
|
-
const deployName = options.deployName || stackArtifact.stackName;
|
|
76
|
-
let cloudFormationStack = await CloudFormationStack.lookup(cfn, deployName);
|
|
77
|
-
if (cloudFormationStack.stackStatus.isCreationFailure) {
|
|
78
|
-
debug(`Found existing stack ${deployName} that had previously failed creation. Deleting it before attempting to re-create it.`);
|
|
79
|
-
await cfn.deleteStack({ StackName: deployName });
|
|
80
|
-
const deletedStack = await waitForStackDelete(cfn, deployName);
|
|
81
|
-
if (deletedStack && deletedStack.stackStatus.name !== "DELETE_COMPLETE") {
|
|
82
|
-
throw new Error(`Failed deleting stack ${deployName} that had previously failed creation (current state: ${deletedStack.stackStatus})`);
|
|
83
|
-
}
|
|
84
|
-
// Update variable to mark that the stack does not exist anymore, but avoid
|
|
85
|
-
// doing an actual lookup in CloudFormation (which would be silly to do if
|
|
86
|
-
// we just deleted it).
|
|
87
|
-
cloudFormationStack = CloudFormationStack.doesNotExist(cfn, deployName);
|
|
88
|
-
}
|
|
89
|
-
// Detect "legacy" assets (which remain in the metadata) and publish them via
|
|
90
|
-
// an ad-hoc asset manifest, while passing their locations via template
|
|
91
|
-
// parameters.
|
|
92
|
-
const legacyAssets = new AssetManifestBuilder();
|
|
93
|
-
const assetParams = await addMetadataAssetsToManifest(stackArtifact, legacyAssets, options.envResources, options.reuseAssets);
|
|
94
|
-
const finalParameterValues = { ...options.parameters, ...assetParams };
|
|
95
|
-
const templateParams = TemplateParameters.fromTemplate(stackArtifact.template);
|
|
96
|
-
const stackParams = options.usePreviousParameters
|
|
97
|
-
? templateParams.updateExisting(finalParameterValues, cloudFormationStack.parameters)
|
|
98
|
-
: templateParams.supplyAll(finalParameterValues);
|
|
99
|
-
const bodyParameter = await makeBodyParameter(stackArtifact, options.resolvedEnvironment, legacyAssets, options.envResources, options.overrideTemplate);
|
|
100
|
-
await publishAssets(legacyAssets.toManifest(stackArtifact.assembly.directory), options.sdkProvider, stackEnv, { quiet: options.quiet });
|
|
101
|
-
return {
|
|
102
|
-
isUpdate: cloudFormationStack.exists &&
|
|
103
|
-
cloudFormationStack.stackStatus.name !== "REVIEW_IN_PROGRESS",
|
|
104
|
-
params: {
|
|
105
|
-
StackName: deployName,
|
|
106
|
-
TemplateBody: bodyParameter.TemplateBody,
|
|
107
|
-
TemplateURL: bodyParameter.TemplateURL,
|
|
108
|
-
Parameters: stackParams.apiParameters,
|
|
109
|
-
Capabilities: [
|
|
110
|
-
"CAPABILITY_IAM",
|
|
111
|
-
"CAPABILITY_NAMED_IAM",
|
|
112
|
-
"CAPABILITY_AUTO_EXPAND",
|
|
113
|
-
],
|
|
114
|
-
Tags: options.tags,
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
}
|