Skip to content

Commit 64338f2

Browse files
authored
chore(toolkit): fix various inconsistenncies with hotswap settings (#33240)
### Description of changes - Make `ExtendedDeployOptions` properly private - Don't print warnings if hotswap mode is not provided - default `watch` action to `HOTSWAP_ONLY` - fix `watch` user-agent ### Describe any new or updated permissions being added n/a ### Description of how you validated changes updated tests ### 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 62b3b60 commit 64338f2

File tree

6 files changed

+162
-40
lines changed

6 files changed

+162
-40
lines changed

packages/@aws-cdk/toolkit/lib/actions/deploy/index.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -175,22 +175,6 @@ export interface BaseDeployOptions {
175175
readonly concurrency?: number;
176176
}
177177

178-
/**
179-
* Deploy options needed by the watch command.
180-
* Intentionally not exported because these options are not
181-
* meant to be public facing.
182-
*
183-
* @internal
184-
*/
185-
export interface ExtendedDeployOptions extends DeployOptions {
186-
/**
187-
* The extra string to append to the User-Agent header when performing AWS SDK calls.
188-
*
189-
* @default - nothing extra is appended to the User-Agent header
190-
*/
191-
readonly extraUserAgent?: string;
192-
}
193-
194178
export interface DeployOptions extends BaseDeployOptions {
195179
/**
196180
* ARNs of SNS topics that CloudFormation will notify with stack related events
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,17 @@
1+
import { DeployOptions } from '..';
2+
13
export * from './helpers';
4+
5+
/**
6+
* Deploy options needed by the watch command.
7+
* Intentionally not exported because these options are not
8+
* meant to be public facing.
9+
*/
10+
export interface ExtendedDeployOptions extends DeployOptions {
11+
/**
12+
* The extra string to append to the User-Agent header when performing AWS SDK calls.
13+
*
14+
* @default - nothing extra is appended to the User-Agent header
15+
*/
16+
readonly extraUserAgent?: string;
17+
}

packages/@aws-cdk/toolkit/lib/actions/watch/index.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { BaseDeployOptions } from '../deploy';
1+
import type { BaseDeployOptions, HotswapMode } from '../deploy';
22

33
export interface WatchOptions extends BaseDeployOptions {
44
/**
@@ -45,6 +45,17 @@ export interface WatchOptions extends BaseDeployOptions {
4545
* @default 'cdk.out'
4646
*/
4747
readonly outdir?: string;
48+
49+
/**
50+
* @TODO can this be part of `DeploymentMethod`
51+
*
52+
* Whether to perform a 'hotswap' deployment.
53+
* A 'hotswap' deployment will attempt to short-circuit CloudFormation
54+
* and update the affected resources like Lambda functions directly.
55+
*
56+
* @default HotswapMode.HOTSWAP_ONLY
57+
*/
58+
readonly hotswap?: HotswapMode;
4859
}
4960

5061
export function patternsArrayForWatch(

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

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@ import * as chalk from 'chalk';
44
import * as chokidar from 'chokidar';
55
import * as fs from 'fs-extra';
66
import { ToolkitServices } from './private';
7-
import { AssetBuildTime, DeployOptions, ExtendedDeployOptions, RequireApproval } from '../actions/deploy';
8-
import { buildParameterMap, removePublishedAssets } from '../actions/deploy/private';
9-
import { DestroyOptions } from '../actions/destroy';
10-
import { DiffOptions } from '../actions/diff';
7+
import { AssetBuildTime, type DeployOptions, RequireApproval } from '../actions/deploy';
8+
import { type ExtendedDeployOptions, buildParameterMap, removePublishedAssets } from '../actions/deploy/private';
9+
import { type DestroyOptions } from '../actions/destroy';
10+
import { type DiffOptions } from '../actions/diff';
1111
import { diffRequiresApproval } from '../actions/diff/private';
12-
import { ListOptions } from '../actions/list';
13-
import { RollbackOptions } from '../actions/rollback';
14-
import { SynthOptions } from '../actions/synth';
12+
import { type ListOptions } from '../actions/list';
13+
import { type RollbackOptions } from '../actions/rollback';
14+
import { type SynthOptions } from '../actions/synth';
1515
import { patternsArrayForWatch, WatchOptions } from '../actions/watch';
16-
import { SdkOptions } from '../api/aws-auth';
16+
import { type SdkOptions } from '../api/aws-auth';
1717
import { DEFAULT_TOOLKIT_STACK_NAME, SdkProvider, SuccessfulDeployStackResult, StackCollection, Deployments, HotswapMode, StackActivityProgress, ResourceMigrator, obscureTemplate, serializeStructure, tagsForStack, CliIoHost, validateSnsTopicArn, Concurrency, WorkGraphBuilder, AssetBuildNode, AssetPublishNode, StackNode, formatErrorMessage } from '../api/aws-cdk';
1818
import { CachedCloudAssemblySource, IdentityCloudAssemblySource, StackAssembly, ICloudAssemblySource, StackSelectionStrategy } from '../api/cloud-assembly';
1919
import { ALL_STACKS, CloudAssemblySourceBuilder } from '../api/cloud-assembly/private';
@@ -234,11 +234,12 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
234234

235235
const parameterMap = buildParameterMap(options.parameters?.parameters);
236236

237-
if (options.hotswap !== HotswapMode.FULL_DEPLOYMENT) {
238-
await ioHost.notify(warn(
237+
const hotswapMode = options.hotswap ?? HotswapMode.FULL_DEPLOYMENT;
238+
if (hotswapMode !== HotswapMode.FULL_DEPLOYMENT) {
239+
await ioHost.notify(warn([
239240
'⚠️ The --hotswap and --hotswap-fallback flags deliberately introduce CloudFormation drift to speed up deployments',
240-
));
241-
await ioHost.notify(warn('⚠️ They should only be used for development - never use them for your production Stacks!\n'));
241+
'⚠️ They should only be used for development - never use them for your production Stacks!',
242+
].join('\n')));
242243
}
243244

244245
// @TODO
@@ -367,7 +368,7 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
367368
progress,
368369
ci: options.ci,
369370
rollback,
370-
hotswap: options.hotswap,
371+
hotswap: hotswapMode,
371372
extraUserAgent: options.extraUserAgent,
372373
// hotswapPropertyOverrides: hotswapPropertyOverrides,
373374
assetParallelism: options.assetParallelism,
@@ -769,21 +770,18 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
769770
assembly: StackAssembly,
770771
options: WatchOptions,
771772
): Promise<void> {
773+
// watch defaults hotswap to enabled
774+
const hotswap = options.hotswap ?? HotswapMode.HOTSWAP_ONLY;
772775
const deployOptions: ExtendedDeployOptions = {
773776
...options,
774777
requireApproval: RequireApproval.NEVER,
775778
// cloudWatchLogMonitor,
776-
hotswap: options.hotswap,
777-
extraUserAgent: `cdk-watch/hotswap-${options.hotswap !== HotswapMode.FALL_BACK ? 'on' : 'off'}`,
778-
concurrency: options.concurrency,
779+
hotswap,
780+
extraUserAgent: `cdk-watch/hotswap-${hotswap === HotswapMode.FULL_DEPLOYMENT ? 'off' : 'on'}`,
779781
};
780782

781783
try {
782-
await this._deploy(
783-
assembly,
784-
'watch',
785-
deployOptions,
786-
);
784+
await this._deploy(assembly, 'watch', deployOptions);
787785
} catch {
788786
// just continue - deploy will show the error
789787
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { HotswapMode } from '../../lib';
2+
import { Toolkit } from '../../lib/toolkit';
3+
import { builderFixture, TestIoHost } from '../_helpers';
4+
5+
const ioHost = new TestIoHost();
6+
const toolkit = new Toolkit({ ioHost });
7+
8+
let mockDeployStack = jest.fn().mockResolvedValue({
9+
type: 'did-deploy-stack',
10+
stackArn: 'arn:aws:cloudformation:region:account:stack/test-stack',
11+
outputs: {},
12+
noOp: false,
13+
});
14+
15+
jest.mock('../../lib/api/aws-cdk', () => {
16+
return {
17+
...jest.requireActual('../../lib/api/aws-cdk'),
18+
Deployments: jest.fn().mockImplementation(() => ({
19+
deployStack: mockDeployStack,
20+
resolveEnvironment: jest.fn().mockResolvedValue({}),
21+
isSingleAssetPublished: jest.fn().mockResolvedValue(true),
22+
readCurrentTemplate: jest.fn().mockResolvedValue({ Resources: {} }),
23+
})),
24+
};
25+
});
26+
27+
beforeEach(() => {
28+
ioHost.notifySpy.mockClear();
29+
ioHost.requestSpy.mockClear();
30+
jest.clearAllMocks();
31+
});
32+
33+
describe('deploy with hotswap', () => {
34+
test('does print hotswap warnings for FALL_BACK mode', async () => {
35+
// WHEN
36+
const cx = await builderFixture(toolkit, 'two-empty-stacks');
37+
await toolkit.deploy(cx, {
38+
hotswap: HotswapMode.FALL_BACK,
39+
});
40+
41+
// THEN
42+
expect(ioHost.notifySpy).toHaveBeenCalledWith(expect.objectContaining({
43+
level: 'warn',
44+
message: expect.stringContaining('hotswap'),
45+
}));
46+
});
47+
48+
test('does print hotswap warnings for HOTSWAP_ONLY mode', async () => {
49+
// WHEN
50+
const cx = await builderFixture(toolkit, 'two-empty-stacks');
51+
await toolkit.deploy(cx, {
52+
hotswap: HotswapMode.HOTSWAP_ONLY,
53+
});
54+
55+
// THEN
56+
expect(ioHost.notifySpy).toHaveBeenCalledWith(expect.objectContaining({
57+
level: 'warn',
58+
message: expect.stringContaining('hotswap'),
59+
}));
60+
});
61+
});
62+
63+
describe('deploy without hotswap', () => {
64+
test('does not print hotswap warnings when mode is undefined', async () => {
65+
// WHEN
66+
const cx = await builderFixture(toolkit, 'two-empty-stacks');
67+
await toolkit.deploy(cx, {
68+
hotswap: undefined,
69+
});
70+
71+
// THEN
72+
expect(ioHost.notifySpy).not.toHaveBeenCalledWith(expect.objectContaining({
73+
level: 'warn',
74+
message: expect.stringContaining('hotswap'),
75+
}));
76+
});
77+
78+
test('does not print hotswap warning for FULL_DEPLOYMENT mode', async () => {
79+
// WHEN
80+
const cx = await builderFixture(toolkit, 'two-empty-stacks');
81+
await toolkit.deploy(cx, {
82+
hotswap: HotswapMode.FULL_DEPLOYMENT,
83+
});
84+
85+
// THEN
86+
expect(ioHost.notifySpy).not.toHaveBeenCalledWith(expect.objectContaining({
87+
level: 'warn',
88+
message: expect.stringContaining('hotswap'),
89+
}));
90+
});
91+
});

packages/@aws-cdk/toolkit/test/actions/watch.test.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,11 @@ describe('watch', () => {
128128
}));
129129
});
130130

131-
describe.each([HotswapMode.FALL_BACK, HotswapMode.HOTSWAP_ONLY])('%p mode', (hotswapMode) => {
131+
describe.each([
132+
[HotswapMode.FALL_BACK, 'on'],
133+
[HotswapMode.HOTSWAP_ONLY, 'on'],
134+
[HotswapMode.FULL_DEPLOYMENT, 'off'],
135+
])('%p mode', (hotswapMode, userAgent) => {
132136
test('passes through the correct hotswap mode to deployStack()', async () => {
133137
// WHEN
134138
const cx = await builderFixture(toolkit, 'stack-with-role');
@@ -143,10 +147,28 @@ describe('watch', () => {
143147
// THEN
144148
expect(deploySpy).toHaveBeenCalledWith(expect.anything(), 'watch', expect.objectContaining({
145149
hotswap: hotswapMode,
146-
extraUserAgent: `cdk-watch/hotswap-${hotswapMode !== HotswapMode.FALL_BACK ? 'on' : 'off'}`,
150+
extraUserAgent: `cdk-watch/hotswap-${userAgent}`,
147151
}));
148152
});
149153
});
154+
155+
test('defaults hotswap to HOTSWAP_ONLY', async () => {
156+
// WHEN
157+
const cx = await builderFixture(toolkit, 'stack-with-role');
158+
ioHost.level = 'warn';
159+
await toolkit.watch(cx, {
160+
include: [],
161+
hotswap: undefined, // force the default
162+
});
163+
164+
await fakeChokidarWatcherOn.readyCallback();
165+
166+
// THEN
167+
expect(deploySpy).toHaveBeenCalledWith(expect.anything(), 'watch', expect.objectContaining({
168+
hotswap: HotswapMode.HOTSWAP_ONLY,
169+
extraUserAgent: 'cdk-watch/hotswap-on',
170+
}));
171+
});
150172
});
151173

152174
// @todo unit test watch with file events

0 commit comments

Comments
 (0)