Skip to content

Commit b267a8e

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

File tree

4 files changed

+95
-26
lines changed

4 files changed

+95
-26
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
@@ -58,6 +58,45 @@ export interface HotswappableChange {
5858
readonly cause: ResourceChange;
5959
}
6060

61+
export enum NonHotswappableReason {
62+
/**
63+
* Tags are not hotswappable
64+
*/
65+
TAGS = 'tags',
66+
/**
67+
* Changed resource properties are not hotswappable on this resource type
68+
*/
69+
PROPERTIES = 'properties',
70+
/**
71+
* A stack output has changed
72+
*/
73+
OUTPUT = 'output',
74+
/**
75+
* A dependant resource is not hotswappable
76+
*/
77+
DEPENDENCY_UNSUPPORTED = 'dependency-unsupported',
78+
/**
79+
* The resource type is not hotswappable
80+
*/
81+
RESOURCE_UNSUPPORTED = 'resource-unsupported',
82+
/**
83+
* The resource is created in the deployment
84+
*/
85+
RESOURCE_CREATION = 'resource-creation',
86+
/**
87+
* The resource is removed in the deployment
88+
*/
89+
RESOURCE_DELETION = 'resource-deletion',
90+
/**
91+
* The resource identified by the logical id has its type changed
92+
*/
93+
RESOURCE_TYPE_CHANGED = 'resource-type-changed',
94+
/**
95+
* The nested stack is created in the deployment
96+
*/
97+
NESTED_STACK_CREATION = 'nested-stack-creation',
98+
}
99+
61100
/**
62101
* Information about a hotswap deployment
63102
*/

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

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as cfn_diff from '@aws-cdk/cloudformation-diff';
33
import type * as cxapi from '@aws-cdk/cx-api';
44
import type { WaiterResult } from '@smithy/util-waiter';
55
import * as chalk from 'chalk';
6-
import type { ResourceChange } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/payloads';
6+
import { NonHotswappableReason, type ResourceChange } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/payloads';
77
import type { IMessageSpan, IoHelper } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private';
88
import { IO, SPAN } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private';
99
import type { SDK, SdkProvider } from '../aws-auth';
@@ -77,7 +77,11 @@ const RESOURCE_DETECTORS: { [key: string]: HotswapDetector } = {
7777
return [];
7878
}
7979

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

8387
'AWS::CDK::Metadata': async () => [],
@@ -208,7 +212,8 @@ async function classifyResourceChanges(
208212
for (const logicalId of Object.keys(stackChanges.outputs.changes)) {
209213
nonHotswappableResources.push({
210214
hotswappable: false,
211-
reason: 'output was changed',
215+
reason: NonHotswappableReason.OUTPUT,
216+
description: 'output was changed',
212217
logicalId,
213218
rejectedChanges: [],
214219
resourceType: 'Stack Output',
@@ -251,6 +256,7 @@ async function classifyResourceChanges(
251256
reportNonHotswappableChange(
252257
nonHotswappableResources,
253258
hotswappableChangeCandidate,
259+
NonHotswappableReason.RESOURCE_UNSUPPORTED,
254260
undefined,
255261
'This resource type is not supported for hotswap deployments',
256262
);
@@ -348,7 +354,8 @@ async function findNestedHotswappableChanges(
348354
{
349355
hotswappable: false,
350356
logicalId,
351-
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`,
357+
reason: NonHotswappableReason.NESTED_STACK_CREATION,
358+
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`,
352359
rejectedChanges: [],
353360
resourceType: 'AWS::CloudFormation::Stack',
354361
},
@@ -423,15 +430,17 @@ function isCandidateForHotswapping(
423430
resourceType: change.newValue!.Type,
424431
logicalId,
425432
rejectedChanges: [],
426-
reason: `resource '${logicalId}' was created by this deployment`,
433+
reason: NonHotswappableReason.RESOURCE_CREATION,
434+
description: `resource '${logicalId}' was created by this deployment`,
427435
};
428436
} else if (!change.newValue) {
429437
return {
430438
hotswappable: false,
431439
resourceType: change.oldValue!.Type,
432440
logicalId,
433441
rejectedChanges: [],
434-
reason: `resource '${logicalId}' was destroyed by this deployment`,
442+
reason: NonHotswappableReason.RESOURCE_DELETION,
443+
description: `resource '${logicalId}' was destroyed by this deployment`,
435444
};
436445
}
437446

@@ -442,7 +451,8 @@ function isCandidateForHotswapping(
442451
resourceType: change.newValue?.Type,
443452
logicalId,
444453
rejectedChanges: [],
445-
reason: `resource '${logicalId}' had its type changed from '${change.oldValue?.Type}' to '${change.newValue?.Type}'`,
454+
reason: NonHotswappableReason.RESOURCE_TYPE_CHANGED,
455+
description: `resource '${logicalId}' had its type changed from '${change.oldValue?.Type}' to '${change.newValue?.Type}'`,
446456
};
447457
}
448458

@@ -551,14 +561,14 @@ async function logNonHotswappableChanges(
551561
chalk.bold(change.logicalId),
552562
chalk.bold(change.resourceType),
553563
chalk.bold(change.rejectedChanges),
554-
chalk.red(change.reason),
564+
chalk.red(change.description),
555565
));
556566
} else {
557567
messages.push(format(
558568
' logicalID: %s, type: %s, reason: %s',
559569
chalk.bold(change.logicalId),
560570
chalk.bold(change.resourceType),
561-
chalk.red(change.reason),
571+
chalk.red(change.description),
562572
));
563573
}
564574
}

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

@@ -63,11 +64,15 @@ export interface NonHotswappableChange {
6364
readonly resourceType: string;
6465
readonly rejectedChanges: Array<string>;
6566
readonly logicalId: string;
67+
/**
68+
* Why was this change was deemed non-hotswappable
69+
*/
70+
readonly reason: NonHotswappableReason;
6671
/**
6772
* Tells the user exactly why this change was deemed non-hotswappable and what its logical ID is.
68-
* If not specified, `reason` will be autofilled to state that the properties listed in `rejectedChanges` are not hotswappable.
73+
* If not specified, `displayReason` default to state that the properties listed in `rejectedChanges` are not hotswappable.
6974
*/
70-
readonly reason?: string;
75+
readonly description?: string;
7176
/**
7277
* Whether or not to show this change when listing non-hotswappable changes in HOTSWAP_ONLY mode. Does not affect
7378
* listing in FALL_BACK mode.
@@ -200,13 +205,15 @@ export class ClassifiedChanges {
200205
const nonHotswappablePropNames = Object.keys(this.nonHotswappableProps);
201206
if (nonHotswappablePropNames.length > 0) {
202207
const tagOnlyChange = nonHotswappablePropNames.length === 1 && nonHotswappablePropNames[0] === 'Tags';
208+
const reason = tagOnlyChange ? NonHotswappableReason.TAGS : NonHotswappableReason.PROPERTIES;
209+
const description = tagOnlyChange ? 'Tags are not hotswappable' : `resource properties '${nonHotswappablePropNames}' are not hotswappable on this resource type`;
210+
203211
reportNonHotswappableChange(
204212
ret,
205213
this.change,
214+
reason,
206215
this.nonHotswappableProps,
207-
tagOnlyChange
208-
? 'Tags are not hotswappable'
209-
: `resource properties '${nonHotswappablePropNames}' are not hotswappable on this resource type`,
216+
description,
210217
);
211218
}
212219
}
@@ -234,27 +241,26 @@ export function classifyChanges(xs: ResourceChange, hotswappablePropNames: strin
234241
export function reportNonHotswappableChange(
235242
ret: ChangeHotswapResult,
236243
change: ResourceChange,
244+
reason: NonHotswappableReason,
237245
nonHotswappableProps?: PropDiffs,
238-
reason?: string,
239-
hotswapOnlyVisible?: boolean,
246+
description?: string,
247+
hotswapOnlyVisible: boolean = true,
240248
): void {
241-
let hotswapOnlyVisibility = true;
242-
if (hotswapOnlyVisible === false) {
243-
hotswapOnlyVisibility = false;
244-
}
245249
ret.push({
246250
hotswappable: false,
247251
rejectedChanges: Object.keys(nonHotswappableProps ?? change.propertyUpdates),
248252
logicalId: change.logicalId,
249253
resourceType: change.newValue.Type,
250254
reason,
251-
hotswapOnlyVisible: hotswapOnlyVisibility,
255+
description: description,
256+
hotswapOnlyVisible,
252257
});
253258
}
254259

255260
export function reportNonHotswappableResource(
256261
change: ResourceChange,
257-
reason?: string,
262+
reason: NonHotswappableReason,
263+
description?: string,
258264
): ChangeHotswapResult {
259265
return [
260266
{
@@ -263,6 +269,7 @@ export function reportNonHotswappableResource(
263269
logicalId: change.logicalId,
264270
resourceType: change.newValue.Type,
265271
reason,
272+
description: description,
266273
},
267274
];
268275
}

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

@@ -45,9 +45,21 @@ export async function isHotswappableEcsServiceChange(
4545
}
4646
}
4747
if (ecsServicesReferencingTaskDef.length === 0) {
48-
// if there are no resources referencing the TaskDefinition,
49-
// hotswap is not possible in FALL_BACK mode
50-
reportNonHotswappableChange(ret, change, undefined, 'No ECS services reference the changed task definition', false);
48+
/**
49+
* ECS Services can have a task definition that doesn't refer to the task definition being updated.
50+
* We have to log this as a non-hotswappable change to the task definition, but when we do,
51+
* we wind up hotswapping the task definition and logging it as a non-hotswappable change.
52+
*
53+
* This logic prevents us from logging that change as non-hotswappable when we hotswap it.
54+
*/
55+
reportNonHotswappableChange(
56+
ret,
57+
change,
58+
NonHotswappableReason.DEPENDENCY_UNSUPPORTED,
59+
undefined,
60+
'No ECS services reference the changed task definition',
61+
false,
62+
);
5163
}
5264
if (resourcesReferencingTaskDef.length > ecsServicesReferencingTaskDef.length) {
5365
// if something besides an ECS Service is referencing the TaskDefinition,
@@ -57,6 +69,7 @@ export async function isHotswappableEcsServiceChange(
5769
reportNonHotswappableChange(
5870
ret,
5971
change,
72+
NonHotswappableReason.DEPENDENCY_UNSUPPORTED,
6073
undefined,
6174
`A resource '${taskRef.LogicalId}' with Type '${taskRef.Type}' that is not an ECS Service was found referencing the changed TaskDefinition '${logicalId}'`,
6275
);

0 commit comments

Comments
 (0)