Skip to content

Commit fee2cf8

Browse files
authored
feat(cli): add ability to configure hotswap properties for ECS (#30511)
### Issue #29618 ### Reason for this change We aim to speed up deployment times in our development environment by using the hotswap feature. However, our services have dependencies on each other, and the current hotswap behavior is too disruptive. ### Description of changes We modified the hotswap implementation for ECS services to pass the `minimumHealthyPercent` and `maximumHealthyPercent` configurable parameters. These parameters are exposed to the cli and can be passed as `--hotswap-ecs-minimum-healthy-percent <number>` and `--hotswap-ecs-maximum-healthy-percent <number>` The implementation is careful to maintain the existing behaviour. That is, if none of the new flags is used, the current `minimumHealthyPercent = 0` and `maximumHealthyPercent = undefined` values are used. ### Description of how you validated changes We added a unit test validating that the correct values are passed to the task definition. We also executed using the locally built version of cdk validating that the behavior is as expected: the parameters are respected during hotswap deployments, and the existing API is maintained. ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 1e73ac5 commit fee2cf8

File tree

12 files changed

+272
-20
lines changed

12 files changed

+272
-20
lines changed

packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/cli.integtest.ts

+52
Original file line numberDiff line numberDiff line change
@@ -2301,6 +2301,58 @@ integTest('hotswap deployment supports AppSync APIs with many functions',
23012301
}),
23022302
);
23032303

2304+
integTest('hotswap ECS deployment respects properties override', withDefaultFixture(async (fixture) => {
2305+
// Update the CDK context with the new ECS properties
2306+
let ecsMinimumHealthyPercent = 100;
2307+
let ecsMaximumHealthyPercent = 200;
2308+
let cdkJson = JSON.parse(await fs.readFile(path.join(fixture.integTestDir, 'cdk.json'), 'utf8'));
2309+
cdkJson = {
2310+
...cdkJson,
2311+
hotswap: {
2312+
ecs: {
2313+
minimumHealthyPercent: ecsMinimumHealthyPercent,
2314+
maximumHealthyPercent: ecsMaximumHealthyPercent,
2315+
},
2316+
},
2317+
};
2318+
2319+
await fs.writeFile(path.join(fixture.integTestDir, 'cdk.json'), JSON.stringify(cdkJson));
2320+
2321+
// GIVEN
2322+
const stackArn = await fixture.cdkDeploy('ecs-hotswap', {
2323+
captureStderr: false,
2324+
});
2325+
2326+
// WHEN
2327+
await fixture.cdkDeploy('ecs-hotswap', {
2328+
options: [
2329+
'--hotswap',
2330+
],
2331+
modEnv: {
2332+
DYNAMIC_ECS_PROPERTY_VALUE: 'new value',
2333+
},
2334+
});
2335+
2336+
const describeStacksResponse = await fixture.aws.cloudFormation.send(
2337+
new DescribeStacksCommand({
2338+
StackName: stackArn,
2339+
}),
2340+
);
2341+
2342+
const clusterName = describeStacksResponse.Stacks?.[0].Outputs?.find(output => output.OutputKey == 'ClusterName')?.OutputValue!;
2343+
const serviceName = describeStacksResponse.Stacks?.[0].Outputs?.find(output => output.OutputKey == 'ServiceName')?.OutputValue!;
2344+
2345+
// THEN
2346+
const describeServicesResponse = await fixture.aws.ecs.send(
2347+
new DescribeServicesCommand({
2348+
cluster: clusterName,
2349+
services: [serviceName],
2350+
}),
2351+
);
2352+
expect(describeServicesResponse.services?.[0].deploymentConfiguration?.minimumHealthyPercent).toEqual(ecsMinimumHealthyPercent);
2353+
expect(describeServicesResponse.services?.[0].deploymentConfiguration?.maximumPercent).toEqual(ecsMaximumHealthyPercent);
2354+
}));
2355+
23042356
async function listChildren(parent: string, pred: (x: string) => Promise<boolean>) {
23052357
const ret = new Array<string>();
23062358
for (const child of await fs.readdir(parent, { encoding: 'utf-8' })) {

packages/aws-cdk/README.md

+13
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,19 @@ Hotswapping is currently supported for the following changes
451451
- VTL mapping template changes for AppSync Resolvers and Functions.
452452
- Schema changes for AppSync GraphQL Apis.
453453

454+
You can optionally configure the behavior of your hotswap deployments in `cdk.json`. Currently you can only configure ECS hotswap behavior:
455+
456+
```json
457+
{
458+
"hotswap": {
459+
"ecs": {
460+
"minimumHealthyPercent": 100,
461+
"maximumHealthyPercent": 250
462+
}
463+
}
464+
}
465+
```
466+
454467
**⚠ Note #1**: This command deliberately introduces drift in CloudFormation stacks in order to speed up deployments.
455468
For this reason, only use it for development purposes.
456469
**Never use this flag for your production deployments**!

packages/aws-cdk/lib/api/deploy-stack.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as uuid from 'uuid';
55
import { ISDK, SdkProvider } from './aws-auth';
66
import { EnvironmentResources } from './environment-resources';
77
import { CfnEvaluationException } from './evaluate-cloudformation-template';
8-
import { HotswapMode, ICON } from './hotswap/common';
8+
import { HotswapMode, HotswapPropertyOverrides, ICON } from './hotswap/common';
99
import { tryHotswapDeployment } from './hotswap-deployments';
1010
import { addMetadataAssetsToManifest } from '../assets';
1111
import { Tag } from '../cdk-toolkit';
@@ -173,6 +173,11 @@ export interface DeployStackOptions {
173173
*/
174174
readonly hotswap?: HotswapMode;
175175

176+
/**
177+
* Extra properties that configure hotswap behavior
178+
*/
179+
readonly hotswapPropertyOverrides?: HotswapPropertyOverrides;
180+
176181
/**
177182
* The extra string to append to the User-Agent header when performing AWS SDK calls.
178183
*
@@ -264,6 +269,7 @@ export async function deployStack(options: DeployStackOptions): Promise<DeploySt
264269
: templateParams.supplyAll(finalParameterValues);
265270

266271
const hotswapMode = options.hotswap ?? HotswapMode.FULL_DEPLOYMENT;
272+
const hotswapPropertyOverrides = options.hotswapPropertyOverrides ?? new HotswapPropertyOverrides();
267273

268274
if (await canSkipDeploy(options, cloudFormationStack, stackParams.hasChanges(cloudFormationStack.parameters))) {
269275
debug(`${deployName}: skipping deployment (use --force to override)`);
@@ -303,7 +309,7 @@ export async function deployStack(options: DeployStackOptions): Promise<DeploySt
303309
// attempt to short-circuit the deployment if possible
304310
try {
305311
const hotswapDeploymentResult = await tryHotswapDeployment(
306-
options.sdkProvider, stackParams.values, cloudFormationStack, stackArtifact, hotswapMode,
312+
options.sdkProvider, stackParams.values, cloudFormationStack, stackArtifact, hotswapMode, hotswapPropertyOverrides,
307313
);
308314
if (hotswapDeploymentResult) {
309315
return hotswapDeploymentResult;

packages/aws-cdk/lib/api/deployments.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { ISDK } from './aws-auth/sdk';
1010
import { CredentialsOptions, SdkForEnvironment, SdkProvider } from './aws-auth/sdk-provider';
1111
import { deployStack, DeployStackResult, destroyStack, DeploymentMethod } from './deploy-stack';
1212
import { EnvironmentResources, EnvironmentResourcesRegistry } from './environment-resources';
13-
import { HotswapMode } from './hotswap/common';
13+
import { HotswapMode, HotswapPropertyOverrides } from './hotswap/common';
1414
import { loadCurrentTemplateWithNestedStacks, loadCurrentTemplate, RootTemplateWithNestedStacks } from './nested-stack-helpers';
1515
import { determineAllowCrossAccountAssetPublishing } from './util/checks';
1616
import { CloudFormationStack, Template, ResourcesToImport, ResourceIdentifierSummaries, stabilizeStack, uploadStackTemplateAssets } from './util/cloudformation';
@@ -182,6 +182,11 @@ export interface DeployStackOptions {
182182
*/
183183
readonly hotswap?: HotswapMode;
184184

185+
/**
186+
* Properties that configure hotswap behavior
187+
*/
188+
readonly hotswapPropertyOverrides?: HotswapPropertyOverrides;
189+
185190
/**
186191
* The extra string to append to the User-Agent header when performing AWS SDK calls.
187192
*
@@ -498,6 +503,7 @@ export class Deployments {
498503
ci: options.ci,
499504
rollback: options.rollback,
500505
hotswap: options.hotswap,
506+
hotswapPropertyOverrides: options.hotswapPropertyOverrides,
501507
extraUserAgent: options.extraUserAgent,
502508
resourcesToImport: options.resourcesToImport,
503509
overrideTemplate: options.overrideTemplate,

packages/aws-cdk/lib/api/hotswap-deployments.ts

+24-7
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { EvaluateCloudFormationTemplate } from './evaluate-cloudformation-templa
77
import { print } from '../logging';
88
import { isHotswappableAppSyncChange } from './hotswap/appsync-mapping-templates';
99
import { isHotswappableCodeBuildProjectChange } from './hotswap/code-build-projects';
10-
import { ICON, ChangeHotswapResult, HotswapMode, HotswappableChange, NonHotswappableChange, HotswappableChangeCandidate, ClassifiedResourceChanges, reportNonHotswappableChange, reportNonHotswappableResource } from './hotswap/common';
10+
import { ICON, ChangeHotswapResult, HotswapMode, HotswappableChange, NonHotswappableChange, HotswappableChangeCandidate, HotswapPropertyOverrides, ClassifiedResourceChanges, reportNonHotswappableChange, reportNonHotswappableResource } from './hotswap/common';
1111
import { isHotswappableEcsServiceChange } from './hotswap/ecs-services';
1212
import { isHotswappableLambdaFunctionChange } from './hotswap/lambda-functions';
1313
import { skipChangeForS3DeployCustomResourcePolicy, isHotswappableS3BucketDeploymentChange } from './hotswap/s3-bucket-deployments';
@@ -20,7 +20,10 @@ import { CloudFormationStack } from './util/cloudformation';
2020
const pLimit: typeof import('p-limit') = require('p-limit');
2121

2222
type HotswapDetector = (
23-
logicalId: string, change: HotswappableChangeCandidate, evaluateCfnTemplate: EvaluateCloudFormationTemplate
23+
logicalId: string,
24+
change: HotswappableChangeCandidate,
25+
evaluateCfnTemplate: EvaluateCloudFormationTemplate,
26+
hotswapPropertyOverrides: HotswapPropertyOverrides,
2427
) => Promise<ChangeHotswapResult>;
2528

2629
const RESOURCE_DETECTORS: { [key: string]: HotswapDetector } = {
@@ -62,7 +65,7 @@ const RESOURCE_DETECTORS: { [key: string]: HotswapDetector } = {
6265
export async function tryHotswapDeployment(
6366
sdkProvider: SdkProvider, assetParams: { [key: string]: string },
6467
cloudFormationStack: CloudFormationStack, stackArtifact: cxapi.CloudFormationStackArtifact,
65-
hotswapMode: HotswapMode,
68+
hotswapMode: HotswapMode, hotswapPropertyOverrides: HotswapPropertyOverrides,
6669
): Promise<DeployStackResult | undefined> {
6770
// resolve the environment, so we can substitute things like AWS::Region in CFN expressions
6871
const resolvedEnv = await sdkProvider.resolveEnvironment(stackArtifact.environment);
@@ -86,7 +89,7 @@ export async function tryHotswapDeployment(
8689

8790
const stackChanges = cfn_diff.fullDiff(currentTemplate.deployedRootTemplate, stackArtifact.template);
8891
const { hotswappableChanges, nonHotswappableChanges } = await classifyResourceChanges(
89-
stackChanges, evaluateCfnTemplate, sdk, currentTemplate.nestedStacks,
92+
stackChanges, evaluateCfnTemplate, sdk, currentTemplate.nestedStacks, hotswapPropertyOverrides,
9093
);
9194

9295
logNonHotswappableChanges(nonHotswappableChanges, hotswapMode);
@@ -113,6 +116,7 @@ async function classifyResourceChanges(
113116
evaluateCfnTemplate: EvaluateCloudFormationTemplate,
114117
sdk: ISDK,
115118
nestedStackNames: { [nestedStackName: string]: NestedStackTemplates },
119+
hotswapPropertyOverrides: HotswapPropertyOverrides,
116120
): Promise<ClassifiedResourceChanges> {
117121
const resourceDifferences = getStackResourceDifferences(stackChanges);
118122

@@ -131,7 +135,14 @@ async function classifyResourceChanges(
131135
// gather the results of the detector functions
132136
for (const [logicalId, change] of Object.entries(resourceDifferences)) {
133137
if (change.newValue?.Type === 'AWS::CloudFormation::Stack' && change.oldValue?.Type === 'AWS::CloudFormation::Stack') {
134-
const nestedHotswappableResources = await findNestedHotswappableChanges(logicalId, change, nestedStackNames, evaluateCfnTemplate, sdk);
138+
const nestedHotswappableResources = await findNestedHotswappableChanges(
139+
logicalId,
140+
change,
141+
nestedStackNames,
142+
evaluateCfnTemplate,
143+
sdk,
144+
hotswapPropertyOverrides,
145+
);
135146
hotswappableResources.push(...nestedHotswappableResources.hotswappableChanges);
136147
nonHotswappableResources.push(...nestedHotswappableResources.nonHotswappableChanges);
137148

@@ -151,7 +162,7 @@ async function classifyResourceChanges(
151162
const resourceType: string = hotswappableChangeCandidate.newValue.Type;
152163
if (resourceType in RESOURCE_DETECTORS) {
153164
// run detector functions lazily to prevent unhandled promise rejections
154-
promises.push(() => RESOURCE_DETECTORS[resourceType](logicalId, hotswappableChangeCandidate, evaluateCfnTemplate));
165+
promises.push(() => RESOURCE_DETECTORS[resourceType](logicalId, hotswappableChangeCandidate, evaluateCfnTemplate, hotswapPropertyOverrides));
155166
} else {
156167
reportNonHotswappableChange(nonHotswappableResources, hotswappableChangeCandidate, undefined, 'This resource type is not supported for hotswap deployments');
157168
}
@@ -233,6 +244,7 @@ async function findNestedHotswappableChanges(
233244
nestedStackTemplates: { [nestedStackName: string]: NestedStackTemplates },
234245
evaluateCfnTemplate: EvaluateCloudFormationTemplate,
235246
sdk: ISDK,
247+
hotswapPropertyOverrides: HotswapPropertyOverrides,
236248
): Promise<ClassifiedResourceChanges> {
237249
const nestedStack = nestedStackTemplates[logicalId];
238250
if (!nestedStack.physicalName) {
@@ -256,7 +268,12 @@ async function findNestedHotswappableChanges(
256268
nestedStackTemplates[logicalId].deployedTemplate, nestedStackTemplates[logicalId].generatedTemplate,
257269
);
258270

259-
return classifyResourceChanges(nestedDiff, evaluateNestedCfnTemplate, sdk, nestedStackTemplates[logicalId].nestedStackTemplates);
271+
return classifyResourceChanges(
272+
nestedDiff,
273+
evaluateNestedCfnTemplate,
274+
sdk,
275+
nestedStackTemplates[logicalId].nestedStackTemplates,
276+
hotswapPropertyOverrides);
260277
}
261278

262279
/** Returns 'true' if a pair of changes is for the same resource. */

packages/aws-cdk/lib/api/hotswap/common.ts

+46
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,52 @@ export class HotswappableChangeCandidate {
9898

9999
type Exclude = { [key: string]: Exclude | true }
100100

101+
/**
102+
* Represents configuration property overrides for hotswap deployments
103+
*/
104+
export class HotswapPropertyOverrides {
105+
// Each supported resource type will have its own properties. Currently this is ECS
106+
ecsHotswapProperties?: EcsHotswapProperties;
107+
108+
public constructor (ecsHotswapProperties?: EcsHotswapProperties) {
109+
this.ecsHotswapProperties = ecsHotswapProperties;
110+
}
111+
}
112+
113+
/**
114+
* Represents configuration properties for ECS hotswap deployments
115+
*/
116+
export class EcsHotswapProperties {
117+
// The lower limit on the number of your service's tasks that must remain in the RUNNING state during a deployment, as a percentage of the desiredCount
118+
readonly minimumHealthyPercent?: number;
119+
// The upper limit on the number of your service's tasks that are allowed in the RUNNING or PENDING state during a deployment, as a percentage of the desiredCount
120+
readonly maximumHealthyPercent?: number;
121+
122+
public constructor (minimumHealthyPercent?: number, maximumHealthyPercent?: number) {
123+
if (minimumHealthyPercent !== undefined && minimumHealthyPercent < 0 ) {
124+
throw new Error('hotswap-ecs-minimum-healthy-percent can\'t be a negative number');
125+
}
126+
if (maximumHealthyPercent !== undefined && maximumHealthyPercent < 0 ) {
127+
throw new Error('hotswap-ecs-maximum-healthy-percent can\'t be a negative number');
128+
}
129+
// In order to preserve the current behaviour, when minimumHealthyPercent is not defined, it will be set to the currently default value of 0
130+
if (minimumHealthyPercent == undefined) {
131+
this.minimumHealthyPercent = 0;
132+
} else {
133+
this.minimumHealthyPercent = minimumHealthyPercent;
134+
}
135+
this.maximumHealthyPercent = maximumHealthyPercent;
136+
}
137+
138+
/**
139+
* Check if any hotswap properties are defined
140+
* @returns true if all properties are undefined, false otherwise
141+
*/
142+
public isEmpty(): boolean {
143+
return this.minimumHealthyPercent === 0 && this.maximumHealthyPercent === undefined;
144+
}
145+
}
146+
101147
/**
102148
* This function transforms all keys (recursively) in the provided `val` object.
103149
*

packages/aws-cdk/lib/api/hotswap/ecs-services.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import * as AWS from 'aws-sdk';
2-
import { ChangeHotswapResult, classifyChanges, HotswappableChangeCandidate, lowerCaseFirstCharacter, reportNonHotswappableChange, transformObjectKeys } from './common';
2+
import { ChangeHotswapResult, classifyChanges, HotswappableChangeCandidate, HotswapPropertyOverrides, lowerCaseFirstCharacter, reportNonHotswappableChange, transformObjectKeys } from './common';
33
import { ISDK } from '../aws-auth';
44
import { EvaluateCloudFormationTemplate } from '../evaluate-cloudformation-template';
55

66
export async function isHotswappableEcsServiceChange(
7-
logicalId: string, change: HotswappableChangeCandidate, evaluateCfnTemplate: EvaluateCloudFormationTemplate,
7+
logicalId: string,
8+
change: HotswappableChangeCandidate,
9+
evaluateCfnTemplate: EvaluateCloudFormationTemplate,
10+
hotswapPropertyOverrides: HotswapPropertyOverrides,
811
): Promise<ChangeHotswapResult> {
912
// the only resource change we can evaluate here is an ECS TaskDefinition
1013
if (change.newValue.Type !== 'AWS::ECS::TaskDefinition') {
@@ -83,6 +86,10 @@ export async function isHotswappableEcsServiceChange(
8386
const registerTaskDefResponse = await sdk.ecs().registerTaskDefinition(lowercasedTaskDef).promise();
8487
const taskDefRevArn = registerTaskDefResponse.taskDefinition?.taskDefinitionArn;
8588

89+
let ecsHotswapProperties = hotswapPropertyOverrides.ecsHotswapProperties;
90+
let minimumHealthyPercent = ecsHotswapProperties?.minimumHealthyPercent;
91+
let maximumHealthyPercent = ecsHotswapProperties?.maximumHealthyPercent;
92+
8693
// Step 2 - update the services using that TaskDefinition to point to the new TaskDefinition Revision
8794
const servicePerClusterUpdates: { [cluster: string]: Array<{ promise: Promise<any>; ecsService: EcsService }> } = {};
8895
for (const ecsService of ecsServicesReferencingTaskDef) {
@@ -105,7 +112,8 @@ export async function isHotswappableEcsServiceChange(
105112
cluster: clusterName,
106113
forceNewDeployment: true,
107114
deploymentConfiguration: {
108-
minimumHealthyPercent: 0,
115+
minimumHealthyPercent: minimumHealthyPercent !== undefined ? minimumHealthyPercent : 0,
116+
maximumPercent: maximumHealthyPercent !== undefined ? maximumHealthyPercent : undefined,
109117
},
110118
}).promise(),
111119
ecsService: ecsService,

packages/aws-cdk/lib/cdk-toolkit.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { CloudAssembly, DefaultSelection, ExtendedStackSelection, StackCollectio
1313
import { CloudExecutable } from './api/cxapp/cloud-executable';
1414
import { Deployments } from './api/deployments';
1515
import { GarbageCollector } from './api/garbage-collection/garbage-collector';
16-
import { HotswapMode } from './api/hotswap/common';
16+
import { HotswapMode, HotswapPropertyOverrides, EcsHotswapProperties } from './api/hotswap/common';
1717
import { findCloudWatchLogGroups } from './api/logs/find-cloudwatch-logs';
1818
import { CloudWatchLogEventMonitor } from './api/logs/logs-monitor';
1919
import { createDiffChangeSet, ResourcesToImport } from './api/util/cloudformation';
@@ -237,6 +237,14 @@ export class CdkToolkit {
237237
warning('⚠️ They should only be used for development - never use them for your production Stacks!\n');
238238
}
239239

240+
let hotswapPropertiesFromSettings = this.props.configuration.settings.get(['hotswap']) || {};
241+
242+
let hotswapPropertyOverrides = new HotswapPropertyOverrides();
243+
hotswapPropertyOverrides.ecsHotswapProperties = new EcsHotswapProperties(
244+
hotswapPropertiesFromSettings.ecs?.minimumHealthyPercent,
245+
hotswapPropertiesFromSettings.ecs?.maximumHealthyPercent,
246+
);
247+
240248
const stacks = stackCollection.stackArtifacts;
241249

242250
const stackOutputs: { [key: string]: any } = { };
@@ -347,6 +355,7 @@ export class CdkToolkit {
347355
ci: options.ci,
348356
rollback: options.rollback,
349357
hotswap: options.hotswap,
358+
hotswapPropertyOverrides: hotswapPropertyOverrides,
350359
extraUserAgent: options.extraUserAgent,
351360
assetParallelism: options.assetParallelism,
352361
ignoreNoStacks: options.ignoreNoStacks,

packages/aws-cdk/lib/settings.ts

+6
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,12 @@ export class Settings {
292292
assetParallelism: argv['asset-parallelism'],
293293
assetPrebuild: argv['asset-prebuild'],
294294
ignoreNoStacks: argv['ignore-no-stacks'],
295+
hotswap: {
296+
ecs: {
297+
minimumEcsHealthyPercent: argv.minimumEcsHealthyPercent,
298+
maximumEcsHealthyPercent: argv.maximumEcsHealthyPercent,
299+
},
300+
},
295301
unstable: argv.unstable,
296302
});
297303
}

packages/aws-cdk/test/api/deploy-stack.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ test('correctly passes CFN parameters when hotswapping', async () => {
151151
});
152152

153153
// THEN
154-
expect(tryHotswapDeployment).toHaveBeenCalledWith(expect.anything(), { A: 'A-value', B: 'B=value' }, expect.anything(), expect.anything(), HotswapMode.FALL_BACK);
154+
expect(tryHotswapDeployment).toHaveBeenCalledWith(expect.anything(), { A: 'A-value', B: 'B=value' }, expect.anything(), expect.anything(), HotswapMode.FALL_BACK, expect.anything());
155155
});
156156

157157
test('correctly passes SSM parameters when hotswapping', async () => {
@@ -181,7 +181,7 @@ test('correctly passes SSM parameters when hotswapping', async () => {
181181
});
182182

183183
// THEN
184-
expect(tryHotswapDeployment).toHaveBeenCalledWith(expect.anything(), { SomeParameter: 'SomeValue' }, expect.anything(), expect.anything(), HotswapMode.FALL_BACK);
184+
expect(tryHotswapDeployment).toHaveBeenCalledWith(expect.anything(), { SomeParameter: 'SomeValue' }, expect.anything(), expect.anything(), HotswapMode.FALL_BACK, expect.anything());
185185
});
186186

187187
test('call CreateStack when method=direct and the stack doesnt exist yet', async () => {

0 commit comments

Comments
 (0)