Skip to content

Commit ff953f2

Browse files
committed
chore: adds a structured reason to non hotswappable resources
1 parent db4029c commit ff953f2

File tree

4 files changed

+95
-25
lines changed

4 files changed

+95
-25
lines changed

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,45 @@ export interface HotswappableChange {
7070
readonly resources: AffectedResource[];
7171
}
7272

73+
export enum NonHotswappableReason {
74+
/**
75+
* Tags are not hotswappable
76+
*/
77+
TAGS = 'tags',
78+
/**
79+
* Changed resource properties are not hotswappable on this resource type
80+
*/
81+
PROPERTIES = 'properties',
82+
/**
83+
* A stack output has changed
84+
*/
85+
OUTPUT = 'output',
86+
/**
87+
* A dependant resource is not hotswappable
88+
*/
89+
DEPENDENCY_UNSUPPORTED = 'dependency-unsupported',
90+
/**
91+
* The resource type is not hotswappable
92+
*/
93+
RESOURCE_UNSUPPORTED = 'resource-unsupported',
94+
/**
95+
* The resource is created in the deployment
96+
*/
97+
RESOURCE_CREATION = 'resource-creation',
98+
/**
99+
* The resource is removed in the deployment
100+
*/
101+
RESOURCE_DELETION = 'resource-deletion',
102+
/**
103+
* The resource identified by the logical id has its type changed
104+
*/
105+
RESOURCE_TYPE_CHANGED = 'resource-type-changed',
106+
/**
107+
* The nested stack is created in the deployment
108+
*/
109+
NESTED_STACK_CREATION = 'nested-stack-creation',
110+
}
111+
73112
/**
74113
* Information about a hotswap deployment
75114
*/

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

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type * as cxapi from '@aws-cdk/cx-api';
44
import type { WaiterResult } from '@smithy/util-waiter';
55
import * as chalk from 'chalk';
66
import type { AffectedResource, ResourceChange } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/payloads';
7+
import { NonHotswappableReason } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/payloads';
78
import type { IMessageSpan, IoHelper } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private';
89
import { IO, SPAN } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private';
910
import type { SDK, SdkProvider } from '../aws-auth';
@@ -77,7 +78,11 @@ const RESOURCE_DETECTORS: { [key: string]: HotswapDetector } = {
7778
return [];
7879
}
7980

80-
return reportNonHotswappableResource(change, 'This resource type is not supported for hotswap deployments');
81+
return reportNonHotswappableResource(
82+
change,
83+
NonHotswappableReason.RESOURCE_UNSUPPORTED,
84+
'This resource type is not supported for hotswap deployments',
85+
);
8186
},
8287

8388
'AWS::CDK::Metadata': async () => [],
@@ -210,7 +215,8 @@ async function classifyResourceChanges(
210215
for (const logicalId of Object.keys(stackChanges.outputs.changes)) {
211216
nonHotswappableResources.push({
212217
hotswappable: false,
213-
reason: 'output was changed',
218+
reason: NonHotswappableReason.OUTPUT,
219+
description: 'output was changed',
214220
logicalId,
215221
rejectedChanges: [],
216222
resourceType: 'Stack Output',
@@ -253,6 +259,7 @@ async function classifyResourceChanges(
253259
reportNonHotswappableChange(
254260
nonHotswappableResources,
255261
hotswappableChangeCandidate,
262+
NonHotswappableReason.RESOURCE_UNSUPPORTED,
256263
undefined,
257264
'This resource type is not supported for hotswap deployments',
258265
);
@@ -350,7 +357,8 @@ async function findNestedHotswappableChanges(
350357
{
351358
hotswappable: false,
352359
logicalId,
353-
reason: `physical name for AWS::CloudFormation::Stack '${logicalId}' could not be found in CloudFormation, so this is a newly created nested stack and cannot be hotswapped`,
360+
reason: NonHotswappableReason.NESTED_STACK_CREATION,
361+
description: `physical name for AWS::CloudFormation::Stack '${logicalId}' could not be found in CloudFormation, so this is a newly created nested stack and cannot be hotswapped`,
354362
rejectedChanges: [],
355363
resourceType: 'AWS::CloudFormation::Stack',
356364
},
@@ -425,15 +433,17 @@ function isCandidateForHotswapping(
425433
resourceType: change.newValue!.Type,
426434
logicalId,
427435
rejectedChanges: [],
428-
reason: `resource '${logicalId}' was created by this deployment`,
436+
reason: NonHotswappableReason.RESOURCE_CREATION,
437+
description: `resource '${logicalId}' was created by this deployment`,
429438
};
430439
} else if (!change.newValue) {
431440
return {
432441
hotswappable: false,
433442
resourceType: change.oldValue!.Type,
434443
logicalId,
435444
rejectedChanges: [],
436-
reason: `resource '${logicalId}' was destroyed by this deployment`,
445+
reason: NonHotswappableReason.RESOURCE_DELETION,
446+
description: `resource '${logicalId}' was destroyed by this deployment`,
437447
};
438448
}
439449

@@ -444,7 +454,8 @@ function isCandidateForHotswapping(
444454
resourceType: change.newValue?.Type,
445455
logicalId,
446456
rejectedChanges: [],
447-
reason: `resource '${logicalId}' had its type changed from '${change.oldValue?.Type}' to '${change.newValue?.Type}'`,
457+
reason: NonHotswappableReason.RESOURCE_TYPE_CHANGED,
458+
description: `resource '${logicalId}' had its type changed from '${change.oldValue?.Type}' to '${change.newValue?.Type}'`,
448459
};
449460
}
450461

@@ -555,14 +566,14 @@ async function logNonHotswappableChanges(
555566
chalk.bold(change.logicalId),
556567
chalk.bold(change.resourceType),
557568
chalk.bold(change.rejectedChanges),
558-
chalk.red(change.reason),
569+
chalk.red(change.description),
559570
));
560571
} else {
561572
messages.push(format(
562573
' logicalID: %s, type: %s, reason: %s',
563574
chalk.bold(change.logicalId),
564575
chalk.bold(change.resourceType),
565-
chalk.red(change.reason),
576+
chalk.red(change.description),
566577
));
567578
}
568579
}

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

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { PropertyDifference } from '@aws-cdk/cloudformation-diff';
22
import type { CloudFormationStackArtifact } from '@aws-cdk/cx-api';
33
import type { HotswappableChange, ResourceChange } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/payloads/hotswap';
4+
import { NonHotswappableReason } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/payloads/hotswap';
45
import { ToolkitError } from '../../toolkit/error';
56
import type { SDK } from '../aws-auth';
67

@@ -58,11 +59,15 @@ export interface NonHotswappableChange {
5859
readonly resourceType: string;
5960
readonly rejectedChanges: Array<string>;
6061
readonly logicalId: string;
62+
/**
63+
* Why was this change was deemed non-hotswappable
64+
*/
65+
readonly reason: NonHotswappableReason;
6166
/**
6267
* Tells the user exactly why this change was deemed non-hotswappable and what its logical ID is.
63-
* If not specified, `reason` will be autofilled to state that the properties listed in `rejectedChanges` are not hotswappable.
68+
* If not specified, `displayReason` default to state that the properties listed in `rejectedChanges` are not hotswappable.
6469
*/
65-
readonly reason?: string;
70+
readonly description?: string;
6671
/**
6772
* Whether or not to show this change when listing non-hotswappable changes in HOTSWAP_ONLY mode. Does not affect
6873
* listing in FALL_BACK mode.
@@ -195,13 +200,15 @@ export class ClassifiedChanges {
195200
const nonHotswappablePropNames = Object.keys(this.nonHotswappableProps);
196201
if (nonHotswappablePropNames.length > 0) {
197202
const tagOnlyChange = nonHotswappablePropNames.length === 1 && nonHotswappablePropNames[0] === 'Tags';
203+
const reason = tagOnlyChange ? NonHotswappableReason.TAGS : NonHotswappableReason.PROPERTIES;
204+
const description = tagOnlyChange ? 'Tags are not hotswappable' : `resource properties '${nonHotswappablePropNames}' are not hotswappable on this resource type`;
205+
198206
reportNonHotswappableChange(
199207
ret,
200208
this.change,
209+
reason,
201210
this.nonHotswappableProps,
202-
tagOnlyChange
203-
? 'Tags are not hotswappable'
204-
: `resource properties '${nonHotswappablePropNames}' are not hotswappable on this resource type`,
211+
description,
205212
);
206213
}
207214
}
@@ -229,27 +236,26 @@ export function classifyChanges(xs: ResourceChange, hotswappablePropNames: strin
229236
export function reportNonHotswappableChange(
230237
ret: ChangeHotswapResult,
231238
change: ResourceChange,
239+
reason: NonHotswappableReason,
232240
nonHotswappableProps?: PropDiffs,
233-
reason?: string,
234-
hotswapOnlyVisible?: boolean,
241+
description?: string,
242+
hotswapOnlyVisible: boolean = true,
235243
): void {
236-
let hotswapOnlyVisibility = true;
237-
if (hotswapOnlyVisible === false) {
238-
hotswapOnlyVisibility = false;
239-
}
240244
ret.push({
241245
hotswappable: false,
242246
rejectedChanges: Object.keys(nonHotswappableProps ?? change.propertyUpdates),
243247
logicalId: change.logicalId,
244248
resourceType: change.newValue.Type,
245249
reason,
246-
hotswapOnlyVisible: hotswapOnlyVisibility,
250+
description: description,
251+
hotswapOnlyVisible,
247252
});
248253
}
249254

250255
export function reportNonHotswappableResource(
251256
change: ResourceChange,
252-
reason?: string,
257+
reason: NonHotswappableReason,
258+
description?: string,
253259
): ChangeHotswapResult {
254260
return [
255261
{
@@ -258,6 +264,7 @@ export function reportNonHotswappableResource(
258264
logicalId: change.logicalId,
259265
resourceType: change.newValue.Type,
260266
reason,
267+
description: description,
261268
},
262269
];
263270
}

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

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
reportNonHotswappableChange,
88
transformObjectKeys,
99
} from './common';
10-
import type { ResourceChange } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/payloads/hotswap';
10+
import { NonHotswappableReason, type ResourceChange } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/payloads/hotswap';
1111
import type { SDK } from '../aws-auth';
1212
import type { EvaluateCloudFormationTemplate } from '../evaluate-cloudformation-template';
1313

@@ -48,9 +48,21 @@ export async function isHotswappableEcsServiceChange(
4848
}
4949
}
5050
if (ecsServicesReferencingTaskDef.length === 0) {
51-
// if there are no resources referencing the TaskDefinition,
52-
// hotswap is not possible in FALL_BACK mode
53-
reportNonHotswappableChange(ret, change, undefined, 'No ECS services reference the changed task definition', false);
51+
/**
52+
* ECS Services can have a task definition that doesn't refer to the task definition being updated.
53+
* We have to log this as a non-hotswappable change to the task definition, but when we do,
54+
* we wind up hotswapping the task definition and logging it as a non-hotswappable change.
55+
*
56+
* This logic prevents us from logging that change as non-hotswappable when we hotswap it.
57+
*/
58+
reportNonHotswappableChange(
59+
ret,
60+
change,
61+
NonHotswappableReason.DEPENDENCY_UNSUPPORTED,
62+
undefined,
63+
'No ECS services reference the changed task definition',
64+
false,
65+
);
5466
}
5567
if (resourcesReferencingTaskDef.length > ecsServicesReferencingTaskDef.length) {
5668
// if something besides an ECS Service is referencing the TaskDefinition,
@@ -60,6 +72,7 @@ export async function isHotswappableEcsServiceChange(
6072
reportNonHotswappableChange(
6173
ret,
6274
change,
75+
NonHotswappableReason.DEPENDENCY_UNSUPPORTED,
6376
undefined,
6477
`A resource '${taskRef.LogicalId}' with Type '${taskRef.Type}' that is not an ECS Service was found referencing the changed TaskDefinition '${logicalId}'`,
6578
);

0 commit comments

Comments
 (0)