Skip to content

Commit 20d8427

Browse files
authored
chore(toolkit): consistent durations and error codes (#33287)
### Issue #33255 Closes #33255 ### Reason for this change Introducing consistent duration messages. ### Description of changes - A new helper to end a timer with a message - Attempted to bring a system into message codes, this deliberately breaks some existing codes (better now than later) - changed payload prop `time` to `duration` on timings/duration messages. ### Describe any new or updated permissions being added n/a ### Description of how you validated changes existing 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 5e35788 commit 20d8427

File tree

5 files changed

+96
-63
lines changed

5 files changed

+96
-63
lines changed

packages/@aws-cdk/toolkit/lib/api/io/private/codes.ts

+17-10
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
11
import { IoMessageCode } from '../io-message';
22

33
export const CODES = {
4-
// Toolkit Info codes
5-
CDK_TOOLKIT_I0001: 'Display stack data',
6-
CDK_TOOLKIT_I0002: 'Successfully deployed stacks',
7-
CDK_TOOLKIT_I3001: 'Informs about any log groups that are traced as part of the deployment',
8-
CDK_TOOLKIT_I5001: 'Display synthesis times',
9-
CDK_TOOLKIT_I5050: 'Confirm rollback',
4+
// Synth
5+
CDK_TOOLKIT_I1000: 'Provides synthesis times',
6+
CDK_TOOLKIT_I1901: 'Provides stack data',
7+
CDK_TOOLKIT_I1902: 'Successfully deployed stacks',
8+
9+
// Deploy
10+
CDK_TOOLKIT_I5000: 'Provides deployment times',
11+
CDK_TOOLKIT_I5001: 'Provides total time in deploy action, including synth and rollback',
12+
CDK_TOOLKIT_I5031: 'Informs about any log groups that are traced as part of the deployment',
13+
CDK_TOOLKIT_I5050: 'Confirm rollback during deployment',
1014
CDK_TOOLKIT_I5060: 'Confirm deploy security sensitive changes',
11-
CDK_TOOLKIT_I7010: 'Confirm destroy stacks',
15+
CDK_TOOLKIT_I5900: 'Deployment results on success',
1216

13-
// Toolkit Warning codes
17+
// Rollback
18+
CDK_TOOLKIT_I6000: 'Provides rollback times',
1419

15-
// Toolkit Error codes
20+
// Destroy
21+
CDK_TOOLKIT_I7000: 'Provides destroy times',
22+
CDK_TOOLKIT_I7010: 'Confirm destroy stacks',
1623

17-
// Assembly Info codes
24+
// Assembly codes
1825
CDK_ASSEMBLY_I0042: 'Writing updated context',
1926
CDK_ASSEMBLY_I0241: 'Fetching missing context',
2027
CDK_ASSEMBLY_I1000: 'Cloud assembly output starts',

packages/@aws-cdk/toolkit/lib/api/io/private/timer.ts

+30
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { VALID_CODE } from './codes';
2+
import { info } from './messages';
3+
import { ActionAwareIoHost } from './types';
14
import { formatTime } from '../../aws-cdk';
25

36
/**
@@ -29,4 +32,31 @@ export class Timer {
2932
asSec: formatTime(elapsedTime),
3033
};
3134
}
35+
36+
/**
37+
* Ends the current timer as a specified timing and notifies the IoHost.
38+
* @returns the elapsed time
39+
*/
40+
public async endAs(ioHost: ActionAwareIoHost, type: 'synth' | 'deploy' | 'rollback' | 'destroy') {
41+
const duration = this.end();
42+
const { code, text } = timerMessageProps(type);
43+
44+
await ioHost.notify(info(`\n✨ ${text} time: ${duration.asSec}s\n`, code, {
45+
duration: duration.asMs,
46+
}));
47+
48+
return duration;
49+
}
50+
}
51+
52+
function timerMessageProps(type: 'synth' | 'deploy' | 'rollback'| 'destroy'): {
53+
code: VALID_CODE;
54+
text: string;
55+
} {
56+
switch (type) {
57+
case 'synth': return { code: 'CDK_TOOLKIT_I1000', text: 'Synthesis' };
58+
case 'deploy': return { code: 'CDK_TOOLKIT_I5000', text: 'Deployment' };
59+
case 'rollback': return { code: 'CDK_TOOLKIT_I6000', text: 'Rollback' };
60+
case 'destroy': return { code: 'CDK_TOOLKIT_I7000', text: 'Destroy' };
61+
}
3262
}

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

+46-50
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { type RollbackOptions } from '../actions/rollback';
1414
import { type SynthOptions } from '../actions/synth';
1515
import { patternsArrayForWatch, WatchOptions } from '../actions/watch';
1616
import { type SdkOptions } from '../api/aws-auth';
17-
import { DEFAULT_TOOLKIT_STACK_NAME, SdkProvider, SuccessfulDeployStackResult, StackCollection, Deployments, HotswapMode, StackActivityProgress, ResourceMigrator, obscureTemplate, serializeStructure, tagsForStack, CliIoHost, validateSnsTopicArn, Concurrency, WorkGraphBuilder, AssetBuildNode, AssetPublishNode, StackNode, formatErrorMessage, CloudWatchLogEventMonitor, findCloudWatchLogGroups } from '../api/aws-cdk';
17+
import { DEFAULT_TOOLKIT_STACK_NAME, SdkProvider, SuccessfulDeployStackResult, StackCollection, Deployments, HotswapMode, StackActivityProgress, ResourceMigrator, obscureTemplate, serializeStructure, tagsForStack, CliIoHost, validateSnsTopicArn, Concurrency, WorkGraphBuilder, AssetBuildNode, AssetPublishNode, StackNode, formatErrorMessage, CloudWatchLogEventMonitor, findCloudWatchLogGroups, formatTime } 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';
2020
import { ToolkitError } from '../api/errors';
@@ -158,10 +158,12 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
158158
*/
159159
public async synth(cx: ICloudAssemblySource, options: SynthOptions = {}): Promise<ICloudAssemblySource> {
160160
const ioHost = withAction(this.ioHost, 'synth');
161+
const synthTimer = Timer.start();
161162
const assembly = await this.assemblyFromSource(cx);
162163
const stacks = assembly.selectStacksV2(options.stacks ?? ALL_STACKS);
163164
const autoValidateStacks = options.validateStacks ? [assembly.selectStacksForValidation()] : [];
164165
await this.validateStacksMetadata(stacks.concat(...autoValidateStacks), ioHost);
166+
await synthTimer.endAs(ioHost, 'synth');
165167

166168
// if we have a single stack, print it to STDOUT
167169
const message = `Successfully synthesized to ${chalk.blue(path.resolve(stacks.assembly.directory))}`;
@@ -175,7 +177,7 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
175177
const firstStack = stacks.firstStack!;
176178
const template = firstStack.template;
177179
const obscuredTemplate = obscureTemplate(template);
178-
await ioHost.notify(result(message, 'CDK_TOOLKIT_I0001', {
180+
await ioHost.notify(result(message, 'CDK_TOOLKIT_I1901', {
179181
...assemblyData,
180182
stack: {
181183
stackName: firstStack.stackName,
@@ -187,7 +189,7 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
187189
}));
188190
} else {
189191
// not outputting template to stdout, let's explain things to the user a little bit...
190-
await ioHost.notify(result(chalk.green(message), 'CDK_TOOLKIT_I0002', assemblyData));
192+
await ioHost.notify(result(chalk.green(message), 'CDK_TOOLKIT_I1902', assemblyData));
191193
await ioHost.notify(info(`Supply a stack id (${stacks.stackArtifacts.map((s) => chalk.green(s.hierarchicalId)).join(', ')}) to display its template.`));
192194
}
193195

@@ -235,14 +237,10 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
235237
*/
236238
private async _deploy(assembly: StackAssembly, action: 'deploy' | 'watch', options: ExtendedDeployOptions = {}) {
237239
const ioHost = withAction(this.ioHost, action);
238-
const timer = Timer.start();
240+
const synthTimer = Timer.start();
239241
const stackCollection = assembly.selectStacksV2(options.stacks ?? ALL_STACKS);
240242
await this.validateStacksMetadata(stackCollection, ioHost);
241-
242-
const synthTime = timer.end();
243-
await ioHost.notify(info(`\n✨ Synthesis time: ${synthTime.asSec}s\n`, 'CDK_TOOLKIT_I5001', {
244-
time: synthTime.asMs,
245-
}));
243+
const synthDuration = await synthTimer.endAs(ioHost, 'synth');
246244

247245
if (stackCollection.stackCount === 0) {
248246
await ioHost.notify(error('This app contains no stacks'));
@@ -352,14 +350,14 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
352350
await ioHost.notify(
353351
info(`${chalk.bold(stack.displayName)}: deploying... [${stackIndex}/${stackCollection.stackCount}]`),
354352
);
355-
const startDeployTime = Timer.start();
353+
const deployTimer = Timer.start();
356354

357355
let tags = options.tags;
358356
if (!tags || tags.length === 0) {
359357
tags = tagsForStack(stack);
360358
}
361359

362-
let elapsedDeployTime;
360+
let deployDuration;
363361
try {
364362
let deployResult: SuccessfulDeployStackResult | undefined;
365363

@@ -405,7 +403,6 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
405403
if (options.force) {
406404
await ioHost.notify(warn(`${motivation}. Rolling back first (--force).`));
407405
} else {
408-
// @todo reintroduce concurrency and corked logging in CliHost
409406
const confirmed = await ioHost.requestResponse(confirm('CDK_TOOLKIT_I5050', question, motivation, true, concurrency));
410407
if (!confirmed) { throw new ToolkitError('Aborted by user'); }
411408
}
@@ -429,7 +426,6 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
429426
if (options.force) {
430427
await ioHost.notify(warn(`${motivation}. Proceeding with regular deployment (--force).`));
431428
} else {
432-
// @todo reintroduce concurrency and corked logging in CliHost
433429
const confirmed = await ioHost.requestResponse(confirm('CDK_TOOLKIT_I5050', question, motivation, true, concurrency));
434430
if (!confirmed) { throw new ToolkitError('Aborted by user'); }
435431
}
@@ -448,24 +444,20 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
448444
? ` ✅ ${stack.displayName} (no changes)`
449445
: ` ✅ ${stack.displayName}`;
450446

451-
await ioHost.notify(success('\n' + message));
452-
elapsedDeployTime = startDeployTime.end();
453-
await ioHost.notify(info(`\n✨ Deployment time: ${elapsedDeployTime.asSec}s\n`));
447+
await ioHost.notify(result(chalk.green('\n' + message), 'CDK_TOOLKIT_I5900', deployResult));
448+
deployDuration = await deployTimer.endAs(ioHost, 'deploy');
454449

455450
if (Object.keys(deployResult.outputs).length > 0) {
456-
await ioHost.notify(info('Outputs:'));
457-
451+
const buffer = ['Outputs:'];
458452
stackOutputs[stack.stackName] = deployResult.outputs;
459-
}
460453

461-
for (const name of Object.keys(deployResult.outputs).sort()) {
462-
const value = deployResult.outputs[name];
463-
await ioHost.notify(info(`${chalk.cyan(stack.id)}.${chalk.cyan(name)} = ${chalk.underline(chalk.cyan(value))}`));
454+
for (const name of Object.keys(deployResult.outputs).sort()) {
455+
const value = deployResult.outputs[name];
456+
buffer.push(`${chalk.cyan(stack.id)}.${chalk.cyan(name)} = ${chalk.underline(chalk.cyan(value))}`);
457+
}
458+
await ioHost.notify(info(buffer.join('\n')));
464459
}
465-
466-
await ioHost.notify(info('Stack ARN:'));
467-
468-
await ioHost.notify(info(deployResult.stackArn));
460+
await ioHost.notify(info(`Stack ARN:\n${deployResult.stackArn}`));
469461
} catch (e: any) {
470462
// It has to be exactly this string because an integration test tests for
471463
// "bold(stackname) failed: ResourceNotReady: <error>"
@@ -482,7 +474,7 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
482474
foundLogGroupsResult.sdk,
483475
foundLogGroupsResult.logGroupNames,
484476
);
485-
await ioHost.notify(info(`The following log groups are added: ${foundLogGroupsResult.logGroupNames}`, 'CDK_TOOLKIT_I3001'));
477+
await ioHost.notify(info(`The following log groups are added: ${foundLogGroupsResult.logGroupNames}`, 'CDK_TOOLKIT_I5031'));
486478
}
487479

488480
// If an outputs file has been specified, create the file path and write stack outputs to it once.
@@ -496,7 +488,8 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
496488
});
497489
}
498490
}
499-
await ioHost.notify(info(`\n✨ Total time: ${synthTime.asSec + elapsedDeployTime.asSec}s\n`));
491+
const duration = synthDuration.asMs + (deployDuration?.asMs ?? 0);
492+
await ioHost.notify(info(`\n✨ Total time: ${formatTime(duration)}s\n`, 'CDK_TOOLKIT_I5001', { duration }));
500493
};
501494

502495
const assetBuildTime = options.assetBuildTime ?? AssetBuildTime.ALL_BEFORE_DEPLOY;
@@ -649,13 +642,10 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
649642
*/
650643
private async _rollback(assembly: StackAssembly, action: 'rollback' | 'deploy' | 'watch', options: RollbackOptions): Promise<void> {
651644
const ioHost = withAction(this.ioHost, action);
652-
const timer = Timer.start();
645+
const synthTimer = Timer.start();
653646
const stacks = assembly.selectStacksV2(options.stacks);
654647
await this.validateStacksMetadata(stacks, ioHost);
655-
const synthTime = timer.end();
656-
await ioHost.notify(info(`\n✨ Synthesis time: ${synthTime.asSec}s\n`, 'CDK_TOOLKIT_I5001', {
657-
time: synthTime.asMs,
658-
}));
648+
await synthTimer.endAs(ioHost, 'synth');
659649

660650
if (stacks.stackCount === 0) {
661651
await ioHost.notify(error('No stacks selected'));
@@ -666,7 +656,7 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
666656

667657
for (const stack of stacks.stackArtifacts) {
668658
await ioHost.notify(info(`Rolling back ${chalk.bold(stack.displayName)}`));
669-
const startRollbackTime = Timer.start();
659+
const rollbackTimer = Timer.start();
670660
const deployments = await this.deploymentsForAction('rollback');
671661
try {
672662
const stackResult = await deployments.rollbackStack({
@@ -680,8 +670,7 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
680670
if (!stackResult.notInRollbackableState) {
681671
anyRollbackable = true;
682672
}
683-
const elapsedRollbackTime = startRollbackTime.end();
684-
await ioHost.notify(info(`\n✨ Rollback time: ${elapsedRollbackTime.asSec}s\n`));
673+
await rollbackTimer.endAs(ioHost, 'rollback');
685674
} catch (e: any) {
686675
await ioHost.notify(error(`\n ❌ ${chalk.bold(stack.displayName)} failed: ${formatErrorMessage(e)}`));
687676
throw new ToolkitError('Rollback failed (use --force to orphan failing resources)');
@@ -707,8 +696,10 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
707696
*/
708697
private async _destroy(assembly: StackAssembly, action: 'deploy' | 'destroy', options: DestroyOptions): Promise<void> {
709698
const ioHost = withAction(this.ioHost, action);
699+
const synthTimer = Timer.start();
710700
// The stacks will have been ordered for deployment, so reverse them for deletion.
711701
const stacks = await assembly.selectStacksV2(options.stacks).reversed();
702+
await synthTimer.endAs(ioHost, 'synth');
712703

713704
const motivation = 'Destroying stacks is an irreversible action';
714705
const question = `Are you sure you want to delete: ${chalk.red(stacks.hierarchicalIds.join(', '))}`;
@@ -717,21 +708,26 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab
717708
return ioHost.notify(error('Aborted by user'));
718709
}
719710

720-
for (const [index, stack] of stacks.stackArtifacts.entries()) {
721-
await ioHost.notify(success(`${chalk.blue(stack.displayName)}: destroying... [${index + 1}/${stacks.stackCount}]`));
722-
try {
723-
const deployments = await this.deploymentsForAction(action);
724-
await deployments.destroyStack({
725-
stack,
726-
deployName: stack.stackName,
727-
roleArn: options.roleArn,
728-
ci: options.ci,
729-
});
730-
await ioHost.notify(success(`\n ✅ ${chalk.blue(stack.displayName)}: ${action}ed`));
731-
} catch (e) {
732-
await ioHost.notify(error(`\n ❌ ${chalk.blue(stack.displayName)}: ${action} failed ${e}`));
733-
throw e;
711+
const destroyTimer = Timer.start();
712+
try {
713+
for (const [index, stack] of stacks.stackArtifacts.entries()) {
714+
await ioHost.notify(success(`${chalk.blue(stack.displayName)}: destroying... [${index + 1}/${stacks.stackCount}]`));
715+
try {
716+
const deployments = await this.deploymentsForAction(action);
717+
await deployments.destroyStack({
718+
stack,
719+
deployName: stack.stackName,
720+
roleArn: options.roleArn,
721+
ci: options.ci,
722+
});
723+
await ioHost.notify(success(`\n ✅ ${chalk.blue(stack.displayName)}: ${action}ed`));
724+
} catch (e) {
725+
await ioHost.notify(error(`\n ❌ ${chalk.blue(stack.displayName)}: ${action} failed ${e}`));
726+
throw e;
727+
}
734728
}
729+
} finally {
730+
await destroyTimer.endAs(ioHost, 'destroy');
735731
}
736732
}
737733

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ describe('deploy', () => {
148148
expect(ioHost.notifySpy).toHaveBeenCalledWith(expect.objectContaining({
149149
action: 'deploy',
150150
level: 'info',
151-
code: 'CDK_TOOLKIT_I3001',
151+
code: 'CDK_TOOLKIT_I5031',
152152
message: expect.stringContaining('The following log groups are added: /aws/lambda/lambda-function-name'),
153153
}));
154154
});

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ describe('synth', () => {
4545
expect(ioHost.notifySpy).toHaveBeenCalledWith(expect.objectContaining({
4646
action: 'synth',
4747
level: 'result',
48-
code: 'CDK_TOOLKIT_I0001',
48+
code: 'CDK_TOOLKIT_I1901',
4949
message: expect.stringContaining('Successfully synthesized'),
5050
data: expect.objectContaining({
5151
stacksCount: 1,
@@ -66,7 +66,7 @@ describe('synth', () => {
6666
expect(ioHost.notifySpy).toHaveBeenCalledWith(expect.objectContaining({
6767
action: 'synth',
6868
level: 'result',
69-
code: 'CDK_TOOLKIT_I0002',
69+
code: 'CDK_TOOLKIT_I1902',
7070
message: expect.stringContaining('Successfully synthesized'),
7171
data: expect.objectContaining({
7272
stacksCount: 2,

0 commit comments

Comments
 (0)