Skip to content

Commit fe143da

Browse files
mrgraingithub-actions
and
github-actions
authored
feat(toolkit-lib): report hotswap messages into a message span (#247)
Requested by early `toolkit-lib` users, all hotswap messages are now reported into span. Structured data reported for start and end is intentionally limited and will be extended further in future. --- By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license --------- Signed-off-by: github-actions <[email protected]> Co-authored-by: github-actions <[email protected]>
1 parent ec93ebd commit fe143da

File tree

7 files changed

+204
-71
lines changed

7 files changed

+204
-71
lines changed

packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/payloads/hotswap.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,30 @@
11
import type { PropertyDifference, Resource } from '@aws-cdk/cloudformation-diff';
2+
import type * as cxapi from '@aws-cdk/cx-api';
3+
4+
/**
5+
* A resource affected by a change
6+
*/
7+
export interface AffectedResource {
8+
/**
9+
* The logical ID of the affected resource in the template
10+
*/
11+
readonly logicalId: string;
12+
/**
13+
* The CloudFormation type of the resource
14+
* This could be a custom type.
15+
*/
16+
readonly resourceType: string;
17+
/**
18+
* The friendly description of the affected resource
19+
*/
20+
readonly description?: string;
21+
/**
22+
* The physical name of the resource when deployed.
23+
*
24+
* A physical name is not always available, e.g. new resources will not have one until after the deployment
25+
*/
26+
readonly physicalName?: string;
27+
}
228

329
/**
430
* Represents a change in a resource
@@ -22,9 +48,28 @@ export interface ResourceChange {
2248
readonly propertyUpdates: Record<string, PropertyDifference<unknown>>;
2349
}
2450

51+
/**
52+
* A change that can be hotswapped
53+
*/
2554
export interface HotswappableChange {
2655
/**
2756
* The resource change that is causing the hotswap.
2857
*/
2958
readonly cause: ResourceChange;
3059
}
60+
61+
/**
62+
* Information about a hotswap deployment
63+
*/
64+
export interface HotswapDeployment {
65+
/**
66+
* The stack that's currently being deployed
67+
*/
68+
readonly stack: cxapi.CloudFormationStackArtifact;
69+
70+
/**
71+
* The mode the hotswap deployment was initiated with.
72+
*/
73+
readonly mode: 'hotswap-only' | 'fall-back';
74+
}
75+

packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/private/messages.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { BootstrapEnvironmentProgress } from '../payloads/bootstrap-environ
55
import type { MissingContext, UpdatedContext } from '../payloads/context';
66
import type { BuildAsset, DeployConfirmationRequest, PublishAsset, StackDeployProgress, SuccessfulDeployStackResult } from '../payloads/deploy';
77
import type { StackDestroy, StackDestroyProgress } from '../payloads/destroy';
8+
import type { HotswapDeployment } from '../payloads/hotswap';
89
import type { StackDetailsPayload } from '../payloads/list';
910
import type { CloudWatchLogEvent, CloudWatchLogMonitorControlEvent } from '../payloads/logs-monitor';
1011
import type { StackRollbackProgress } from '../payloads/rollback';
@@ -188,6 +189,16 @@ export const IO = {
188189
}),
189190

190191
// Hotswap (54xx)
192+
CDK_TOOLKIT_I5400: make.trace<HotswapDeployment>({
193+
code: 'CDK_TOOLKIT_I5400',
194+
description: 'Starting a hotswap deployment',
195+
interface: 'HotswapDeployment',
196+
}),
197+
CDK_TOOLKIT_I5410: make.info<Duration>({
198+
code: 'CDK_TOOLKIT_I5410',
199+
description: 'Hotswap deployment has ended, a full deployment might still follow if needed',
200+
interface: 'Duration',
201+
}),
191202

192203
// Stack Monitor (55xx)
193204
CDK_TOOLKIT_I5501: make.info<StackMonitoringControlEvent>({
@@ -443,4 +454,9 @@ export const SPAN = {
443454
start: IO.CDK_TOOLKIT_I5220,
444455
end: IO.CDK_TOOLKIT_I5221,
445456
},
457+
HOTSWAP: {
458+
name: 'hotswap-deployment',
459+
start: IO.CDK_TOOLKIT_I5400,
460+
end: IO.CDK_TOOLKIT_I5410,
461+
},
446462
} satisfies Record<string, SpanDefinition<any, any>>;

packages/@aws-cdk/toolkit-lib/docs/message-registry.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ group: Documents
3838
| `CDK_TOOLKIT_I5313` | File event detected during active deployment, changes are queued | `info` | {@link FileWatchEvent} |
3939
| `CDK_TOOLKIT_I5314` | Initial watch deployment started | `info` | n/a |
4040
| `CDK_TOOLKIT_I5315` | Queued watch deployment started | `info` | n/a |
41+
| `CDK_TOOLKIT_I5400` | Starting a hotswap deployment | `trace` | {@link HotswapDeployment} |
42+
| `CDK_TOOLKIT_I5410` | Hotswap deployment has ended, a full deployment might still follow if needed | `info` | {@link Duration} |
4143
| `CDK_TOOLKIT_I5501` | Stack Monitoring: Start monitoring of a single stack | `info` | {@link StackMonitoringControlEvent} |
4244
| `CDK_TOOLKIT_I5502` | Stack Monitoring: Activity event for a single stack | `info` | {@link StackActivity} |
4345
| `CDK_TOOLKIT_I5503` | Stack Monitoring: Finished monitoring of a single stack | `info` | {@link StackMonitoringControlEvent} |

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

Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ import {
3030
import type { ChangeSetDeploymentMethod, DeploymentMethod } from './deployment-method';
3131
import type { DeployStackResult, SuccessfulDeployStackResult } from './deployment-result';
3232
import { tryHotswapDeployment } from './hotswap-deployments';
33-
import type { IoHelper } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private';
34-
import { debug, info, warn } from '../../cli/messages';
33+
import { IO, type IoHelper } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private';
3534
import { ToolkitError } from '../../toolkit/error';
3635
import { formatErrorMessage } from '../../util';
3736
import type { SDK, SdkProvider, ICloudFormationClient } from '../aws-auth';
@@ -214,7 +213,7 @@ export async function deployStack(options: DeployStackOptions, ioHelper: IoHelpe
214213
let cloudFormationStack = await CloudFormationStack.lookup(cfn, deployName);
215214

216215
if (cloudFormationStack.stackStatus.isCreationFailure) {
217-
await ioHelper.notify(debug(
216+
await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(
218217
`Found existing stack ${deployName} that had previously failed creation. Deleting it before attempting to re-create it.`,
219218
));
220219
await cfn.deleteStack({ StackName: deployName });
@@ -253,11 +252,11 @@ export async function deployStack(options: DeployStackOptions, ioHelper: IoHelpe
253252
const hotswapPropertyOverrides = options.hotswapPropertyOverrides ?? new HotswapPropertyOverrides();
254253

255254
if (await canSkipDeploy(options, cloudFormationStack, stackParams.hasChanges(cloudFormationStack.parameters), ioHelper)) {
256-
await ioHelper.notify(debug(`${deployName}: skipping deployment (use --force to override)`));
255+
await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`${deployName}: skipping deployment (use --force to override)`));
257256
// if we can skip deployment and we are performing a hotswap, let the user know
258257
// that no hotswap deployment happened
259258
if (hotswapMode !== HotswapMode.FULL_DEPLOYMENT) {
260-
await ioHelper.notify(info(
259+
await ioHelper.notify(IO.DEFAULT_TOOLKIT_INFO.msg(
261260
format(
262261
`\n ${ICON} %s\n`,
263262
chalk.bold('hotswap deployment skipped - no changes were detected (use --force to override)'),
@@ -271,7 +270,7 @@ export async function deployStack(options: DeployStackOptions, ioHelper: IoHelpe
271270
stackArn: cloudFormationStack.stackId,
272271
};
273272
} else {
274-
await ioHelper.notify(debug(`${deployName}: deploying...`));
273+
await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`${deployName}: deploying...`));
275274
}
276275

277276
const bodyParameter = await makeBodyParameter(
@@ -285,7 +284,7 @@ export async function deployStack(options: DeployStackOptions, ioHelper: IoHelpe
285284
try {
286285
bootstrapStackName = (await options.envResources.lookupToolkit()).stackName;
287286
} catch (e) {
288-
await ioHelper.notify(debug(`Could not determine the bootstrap stack name: ${e}`));
287+
await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`Could not determine the bootstrap stack name: ${e}`));
289288
}
290289
await publishAssets(legacyAssets.toManifest(stackArtifact.assembly.directory), options.sdkProvider, stackEnv, {
291290
parallel: options.assetParallelism,
@@ -301,27 +300,30 @@ export async function deployStack(options: DeployStackOptions, ioHelper: IoHelpe
301300
stackParams.values,
302301
cloudFormationStack,
303302
stackArtifact,
304-
hotswapMode, hotswapPropertyOverrides,
303+
hotswapMode,
304+
hotswapPropertyOverrides,
305305
);
306+
306307
if (hotswapDeploymentResult) {
307308
return hotswapDeploymentResult;
308309
}
309-
await ioHelper.notify(info(format(
310+
311+
await ioHelper.notify(IO.DEFAULT_TOOLKIT_INFO.msg(format(
310312
'Could not perform a hotswap deployment, as the stack %s contains non-Asset changes',
311313
stackArtifact.displayName,
312314
)));
313315
} catch (e) {
314316
if (!(e instanceof CfnEvaluationException)) {
315317
throw e;
316318
}
317-
await ioHelper.notify(info(format(
319+
await ioHelper.notify(IO.DEFAULT_TOOLKIT_INFO.msg(format(
318320
'Could not perform a hotswap deployment, because the CloudFormation template could not be resolved: %s',
319321
formatErrorMessage(e),
320322
)));
321323
}
322324

323325
if (hotswapMode === HotswapMode.FALL_BACK) {
324-
await ioHelper.notify(info('Falling back to doing a full deployment'));
326+
await ioHelper.notify(IO.DEFAULT_TOOLKIT_INFO.msg('Falling back to doing a full deployment'));
325327
options.sdk.appendCustomUserAgent('cdk-hotswap/fallback');
326328
} else {
327329
return {
@@ -404,17 +406,17 @@ class FullCloudFormationDeployment {
404406
await this.updateTerminationProtection();
405407

406408
if (changeSetHasNoChanges(changeSetDescription)) {
407-
await this.ioHelper.notify(debug(format('No changes are to be performed on %s.', this.stackName)));
409+
await this.ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(format('No changes are to be performed on %s.', this.stackName)));
408410
if (execute) {
409-
await this.ioHelper.notify(debug(format('Deleting empty change set %s', changeSetDescription.ChangeSetId)));
411+
await this.ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(format('Deleting empty change set %s', changeSetDescription.ChangeSetId)));
410412
await this.cfn.deleteChangeSet({
411413
StackName: this.stackName,
412414
ChangeSetName: changeSetName,
413415
});
414416
}
415417

416418
if (this.options.force) {
417-
await this.ioHelper.notify(warn(
419+
await this.ioHelper.notify(IO.DEFAULT_TOOLKIT_WARN.msg(
418420
[
419421
'You used the --force flag, but CloudFormation reported that the deployment would not make any changes.',
420422
'According to CloudFormation, all resources are already up-to-date with the state in your CDK app.',
@@ -434,7 +436,7 @@ class FullCloudFormationDeployment {
434436
}
435437

436438
if (!execute) {
437-
await this.ioHelper.notify(info(format(
439+
await this.ioHelper.notify(IO.DEFAULT_TOOLKIT_INFO.msg(format(
438440
'Changeset %s created and waiting in review for manual execution (--no-execute)',
439441
changeSetDescription.ChangeSetId,
440442
)));
@@ -466,8 +468,8 @@ class FullCloudFormationDeployment {
466468
private async createChangeSet(changeSetName: string, willExecute: boolean, importExistingResources: boolean) {
467469
await this.cleanupOldChangeset(changeSetName);
468470

469-
await this.ioHelper.notify(debug(`Attempting to create ChangeSet with name ${changeSetName} to ${this.verb} stack ${this.stackName}`));
470-
await this.ioHelper.notify(info(format('%s: creating CloudFormation changeset...', chalk.bold(this.stackName))));
471+
await this.ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`Attempting to create ChangeSet with name ${changeSetName} to ${this.verb} stack ${this.stackName}`));
472+
await this.ioHelper.notify(IO.DEFAULT_TOOLKIT_INFO.msg(format('%s: creating CloudFormation changeset...', chalk.bold(this.stackName))));
471473
const changeSet = await this.cfn.createChangeSet({
472474
StackName: this.stackName,
473475
ChangeSetName: changeSetName,
@@ -479,15 +481,15 @@ class FullCloudFormationDeployment {
479481
...this.commonPrepareOptions(),
480482
});
481483

482-
await this.ioHelper.notify(debug(format('Initiated creation of changeset: %s; waiting for it to finish creating...', changeSet.Id)));
484+
await this.ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(format('Initiated creation of changeset: %s; waiting for it to finish creating...', changeSet.Id)));
483485
// Fetching all pages if we'll execute, so we can have the correct change count when monitoring.
484486
return waitForChangeSet(this.cfn, this.ioHelper, this.stackName, changeSetName, {
485487
fetchAll: willExecute,
486488
});
487489
}
488490

489491
private async executeChangeSet(changeSet: DescribeChangeSetCommandOutput): Promise<SuccessfulDeployStackResult> {
490-
await this.ioHelper.notify(debug(format('Initiating execution of changeset %s on stack %s', changeSet.ChangeSetId, this.stackName)));
492+
await this.ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(format('Initiating execution of changeset %s on stack %s', changeSet.ChangeSetId, this.stackName)));
491493

492494
await this.cfn.executeChangeSet({
493495
StackName: this.stackName,
@@ -496,7 +498,7 @@ class FullCloudFormationDeployment {
496498
...this.commonExecuteOptions(),
497499
});
498500

499-
await this.ioHelper.notify(debug(
501+
await this.ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(
500502
format(
501503
'Execution of changeset %s on stack %s has started; waiting for the update to complete...',
502504
changeSet.ChangeSetId,
@@ -513,7 +515,7 @@ class FullCloudFormationDeployment {
513515
if (this.cloudFormationStack.exists) {
514516
// Delete any existing change sets generated by CDK since change set names must be unique.
515517
// The delete request is successful as long as the stack exists (even if the change set does not exist).
516-
await this.ioHelper.notify(debug(`Removing existing change set with name ${changeSetName} if it exists`));
518+
await this.ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`Removing existing change set with name ${changeSetName} if it exists`));
517519
await this.cfn.deleteChangeSet({
518520
StackName: this.stackName,
519521
ChangeSetName: changeSetName,
@@ -525,7 +527,7 @@ class FullCloudFormationDeployment {
525527
// Update termination protection only if it has changed.
526528
const terminationProtection = this.stackArtifact.terminationProtection ?? false;
527529
if (!!this.cloudFormationStack.terminationProtection !== terminationProtection) {
528-
await this.ioHelper.notify(debug(
530+
await this.ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(
529531
format (
530532
'Updating termination protection from %s to %s for stack %s',
531533
this.cloudFormationStack.terminationProtection,
@@ -537,12 +539,12 @@ class FullCloudFormationDeployment {
537539
StackName: this.stackName,
538540
EnableTerminationProtection: terminationProtection,
539541
});
540-
await this.ioHelper.notify(debug(format('Termination protection updated to %s for stack %s', terminationProtection, this.stackName)));
542+
await this.ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(format('Termination protection updated to %s for stack %s', terminationProtection, this.stackName)));
541543
}
542544
}
543545

544546
private async directDeployment(): Promise<SuccessfulDeployStackResult> {
545-
await this.ioHelper.notify(info(format('%s: %s stack...', chalk.bold(this.stackName), this.update ? 'updating' : 'creating')));
547+
await this.ioHelper.notify(IO.DEFAULT_TOOLKIT_INFO.msg(format('%s: %s stack...', chalk.bold(this.stackName), this.update ? 'updating' : 'creating')));
546548

547549
const startTime = new Date();
548550

@@ -558,7 +560,7 @@ class FullCloudFormationDeployment {
558560
});
559561
} catch (err: any) {
560562
if (err.message === 'No updates are to be performed.') {
561-
await this.ioHelper.notify(debug(format('No updates are to be performed for stack %s', this.stackName)));
563+
await this.ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(format('No updates are to be performed for stack %s', this.stackName)));
562564
return {
563565
type: 'did-deploy-stack',
564566
noOp: true,
@@ -611,7 +613,7 @@ class FullCloudFormationDeployment {
611613
} finally {
612614
await monitor.stop();
613615
}
614-
debug(format('Stack %s has completed updating', this.stackName));
616+
await this.ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(format('Stack %s has completed updating', this.stackName)));
615617
return {
616618
type: 'did-deploy-stack',
617619
noOp: false,
@@ -709,11 +711,11 @@ async function canSkipDeploy(
709711
ioHelper: IoHelper,
710712
): Promise<boolean> {
711713
const deployName = deployStackOptions.deployName || deployStackOptions.stack.stackName;
712-
await ioHelper.notify(debug(`${deployName}: checking if we can skip deploy`));
714+
await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`${deployName}: checking if we can skip deploy`));
713715

714716
// Forced deploy
715717
if (deployStackOptions.force) {
716-
await ioHelper.notify(debug(`${deployName}: forced deployment`));
718+
await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`${deployName}: forced deployment`));
717719
return false;
718720
}
719721

@@ -722,53 +724,53 @@ async function canSkipDeploy(
722724
deployStackOptions.deploymentMethod?.method === 'change-set' &&
723725
deployStackOptions.deploymentMethod.execute === false
724726
) {
725-
await ioHelper.notify(debug(`${deployName}: --no-execute, always creating change set`));
727+
await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`${deployName}: --no-execute, always creating change set`));
726728
return false;
727729
}
728730

729731
// No existing stack
730732
if (!cloudFormationStack.exists) {
731-
await ioHelper.notify(debug(`${deployName}: no existing stack`));
733+
await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`${deployName}: no existing stack`));
732734
return false;
733735
}
734736

735737
// Template has changed (assets taken into account here)
736738
if (JSON.stringify(deployStackOptions.stack.template) !== JSON.stringify(await cloudFormationStack.template())) {
737-
await ioHelper.notify(debug(`${deployName}: template has changed`));
739+
await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`${deployName}: template has changed`));
738740
return false;
739741
}
740742

741743
// Tags have changed
742744
if (!compareTags(cloudFormationStack.tags, deployStackOptions.tags ?? [])) {
743-
await ioHelper.notify(debug(`${deployName}: tags have changed`));
745+
await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`${deployName}: tags have changed`));
744746
return false;
745747
}
746748

747749
// Notification arns have changed
748750
if (!arrayEquals(cloudFormationStack.notificationArns, deployStackOptions.notificationArns ?? [])) {
749-
await ioHelper.notify(debug(`${deployName}: notification arns have changed`));
751+
await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`${deployName}: notification arns have changed`));
750752
return false;
751753
}
752754

753755
// Termination protection has been updated
754756
if (!!deployStackOptions.stack.terminationProtection !== !!cloudFormationStack.terminationProtection) {
755-
await ioHelper.notify(debug(`${deployName}: termination protection has been updated`));
757+
await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`${deployName}: termination protection has been updated`));
756758
return false;
757759
}
758760

759761
// Parameters have changed
760762
if (parameterChanges) {
761763
if (parameterChanges === 'ssm') {
762-
await ioHelper.notify(debug(`${deployName}: some parameters come from SSM so we have to assume they may have changed`));
764+
await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`${deployName}: some parameters come from SSM so we have to assume they may have changed`));
763765
} else {
764-
await ioHelper.notify(debug(`${deployName}: parameters have changed`));
766+
await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`${deployName}: parameters have changed`));
765767
}
766768
return false;
767769
}
768770

769771
// Existing stack is in a failed state
770772
if (cloudFormationStack.stackStatus.isFailure) {
771-
await ioHelper.notify(debug(`${deployName}: stack is in a failure state`));
773+
await ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`${deployName}: stack is in a failure state`));
772774
return false;
773775
}
774776

0 commit comments

Comments
 (0)