Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { PropertyDifference, Resource } from '@aws-cdk/cloudformation-diff';
import type * as cxapi from '@aws-cdk/cx-api';
import type { Duration } from './types';
import type { ResourceMetadata } from '../../resource-metadata/resource-metadata';

/**
Expand Down Expand Up @@ -194,7 +195,7 @@ export interface HotswapDeployment {
/**
* The result of an attempted hotswap deployment
*/
export interface HotswapResult {
export interface HotswapResult extends Duration {
/**
* The stack that was hotswapped
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { BootstrapEnvironmentProgress } from '../payloads/bootstrap-environ
import type { MissingContext, UpdatedContext } from '../payloads/context';
import type { BuildAsset, DeployConfirmationRequest, PublishAsset, StackDeployProgress, SuccessfulDeployStackResult } from '../payloads/deploy';
import type { StackDestroy, StackDestroyProgress } from '../payloads/destroy';
import type { HotswapDeployment } from '../payloads/hotswap';
import type { AffectedResource, HotswapDeployment, HotswappableChange, HotswapResult, NonHotswappableChange } from '../payloads/hotswap';
import type { StackDetailsPayload } from '../payloads/list';
import type { CloudWatchLogEvent, CloudWatchLogMonitorControlEvent } from '../payloads/logs-monitor';
import type { StackRollbackProgress } from '../payloads/rollback';
Expand Down Expand Up @@ -202,10 +202,35 @@ export const IO = {
description: 'Starting a hotswap deployment',
interface: 'HotswapDeployment',
}),
CDK_TOOLKIT_I5410: make.info<Duration>({
CDK_TOOLKIT_I5401: make.trace<HotswappableChange>({
code: 'CDK_TOOLKIT_I5401',
description: 'A hotswappable change is processed as part of a hotswap deployment',
interface: 'HotswappableChange',
}),
CDK_TOOLKIT_I5402: make.trace<HotswappableChange>({
code: 'CDK_TOOLKIT_I5402',
description: 'The hotswappable change has completed',
interface: 'HotswappableChange',
}),
CDK_TOOLKIT_I5403: make.info<AffectedResource>({
code: 'CDK_TOOLKIT_I5403',
description: 'Resource affected by the current hotswap operation',
interface: 'AffectedResource',
}),
CDK_TOOLKIT_I5404: make.info<AffectedResource>({
code: 'CDK_TOOLKIT_I5404',
description: 'Resource affected by the current hotswap operation has finished changing',
interface: 'AffectedResource',
}),
CDK_TOOLKIT_I5405: make.info<NonHotswappableChange[]>({
code: 'CDK_TOOLKIT_I5405',
description: 'Non hotswappable resource that are ignored by the hotswap deployment',
interface: 'NonHotswappableChange[]',
}),
CDK_TOOLKIT_I5410: make.info<HotswapResult>({
code: 'CDK_TOOLKIT_I5410',
description: 'Hotswap deployment has ended, a full deployment might still follow if needed',
interface: 'Duration',
interface: 'HotswapResult',
}),

// Stack Monitor (55xx)
Expand Down
7 changes: 6 additions & 1 deletion packages/@aws-cdk/toolkit-lib/docs/message-registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@ group: Documents
| `CDK_TOOLKIT_I5314` | Initial watch deployment started | `info` | n/a |
| `CDK_TOOLKIT_I5315` | Queued watch deployment started | `info` | n/a |
| `CDK_TOOLKIT_I5400` | Starting a hotswap deployment | `trace` | {@link HotswapDeployment} |
| `CDK_TOOLKIT_I5410` | Hotswap deployment has ended, a full deployment might still follow if needed | `info` | {@link Duration} |
| `CDK_TOOLKIT_I5401` | A hotswappable change is processed as part of a hotswap deployment | `trace` | {@link HotswappableChange} |
| `CDK_TOOLKIT_I5402` | The hotswappable change has completed | `trace` | {@link HotswappableChange} |
| `CDK_TOOLKIT_I5403` | Resource affected by the current hotswap operation | `info` | {@link AffectedResource} |
| `CDK_TOOLKIT_I5404` | Resource affected by the current hotswap operation has finished changing | `info` | {@link AffectedResource} |
| `CDK_TOOLKIT_I5405` | Non hotswappable resource that are ignored by the hotswap deployment | `info` | {@link NonHotswappableChange[]} |
| `CDK_TOOLKIT_I5410` | Hotswap deployment has ended, a full deployment might still follow if needed | `info` | {@link HotswapResult} |
| `CDK_TOOLKIT_I5501` | Stack Monitoring: Start monitoring of a single stack | `info` | {@link StackMonitoringControlEvent} |
| `CDK_TOOLKIT_I5502` | Stack Monitoring: Activity event for a single stack | `info` | {@link StackActivity} |
| `CDK_TOOLKIT_I5503` | Stack Monitoring: Finished monitoring of a single stack | `info` | {@link StackMonitoringControlEvent} |
Expand Down
47 changes: 29 additions & 18 deletions packages/aws-cdk/lib/api/deployments/hotswap-deployments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export async function tryHotswapDeployment(
hotswapPropertyOverrides,
);

await hotswapSpan.end();
await hotswapSpan.end(result);

if (result?.hotswapped === true) {
return {
Expand All @@ -135,7 +135,7 @@ async function hotswapDeployment(
stack: cxapi.CloudFormationStackArtifact,
hotswapMode: HotswapMode,
hotswapPropertyOverrides: HotswapPropertyOverrides,
): Promise<HotswapResult> {
): Promise<Omit<HotswapResult, 'duration'>> {
// resolve the environment, so we can substitute things like AWS::Region in CFN expressions
const resolvedEnv = await sdkProvider.resolveEnvironment(stack.environment);
// create a new SDK using the CLI credentials, because the default one will not work for new-style synthesis -
Expand All @@ -162,7 +162,7 @@ async function hotswapDeployment(
currentTemplate.nestedStacks, hotswapPropertyOverrides,
);

await logNonHotswappableChanges(ioSpan, nonHotswappable, hotswapMode);
await logRejectedChanges(ioSpan, nonHotswappable, hotswapMode);

const hotswappableChanges = hotswappable.map(o => o.change);
const nonHotswappableChanges = nonHotswappable.map(n => n.change);
Expand All @@ -181,7 +181,7 @@ async function hotswapDeployment(
}

// apply the short-circuitable changes
await applyAllHotswappableChanges(sdk, ioSpan, hotswappable);
await applyAllHotswapOperations(sdk, ioSpan, hotswappable);

return {
stack,
Expand Down Expand Up @@ -489,26 +489,30 @@ function isCandidateForHotswapping(
};
}

async function applyAllHotswappableChanges(sdk: SDK, ioSpan: IMessageSpan<any>, hotswappableChanges: HotswapOperation[]): Promise<void[]> {
async function applyAllHotswapOperations(sdk: SDK, ioSpan: IMessageSpan<any>, hotswappableChanges: HotswapOperation[]): Promise<void[]> {
if (hotswappableChanges.length > 0) {
await ioSpan.notify(IO.DEFAULT_TOOLKIT_INFO.msg(`\n${ICON} hotswapping resources:`));
}
const limit = pLimit(10);
// eslint-disable-next-line @cdklabs/promiseall-no-unbounded-parallelism
return Promise.all(hotswappableChanges.map(hotswapOperation => limit(() => {
return applyHotswappableChange(sdk, ioSpan, hotswapOperation);
return applyHotswapOperation(sdk, ioSpan, hotswapOperation);
})));
}

async function applyHotswappableChange(sdk: SDK, ioSpan: IMessageSpan<any>, hotswapOperation: HotswapOperation): Promise<void> {
async function applyHotswapOperation(sdk: SDK, ioSpan: IMessageSpan<any>, hotswapOperation: HotswapOperation): Promise<void> {
// note the type of service that was successfully hotswapped in the User-Agent
const customUserAgent = `cdk-hotswap/success-${hotswapOperation.service}`;
sdk.appendCustomUserAgent(customUserAgent);

const resourceText = (r: AffectedResource) => r.description ?? `${r.resourceType} '${r.physicalName ?? r.logicalId}'`;

await ioSpan.notify(IO.CDK_TOOLKIT_I5401.msg(
`Hotswapping ${hotswapOperation.change.cause.logicalId} (${hotswapOperation.change.cause.newValue.Type})...`,
hotswapOperation.change,
));

for (const resource of hotswapOperation.change.resources) {
await ioSpan.notify(IO.DEFAULT_TOOLKIT_INFO.msg(format(` ${ICON} %s`, chalk.bold(resourceText(resource)))));
await ioSpan.notify(IO.CDK_TOOLKIT_I5403.msg(format(` ${ICON} %s`, chalk.bold(resourceText(resource))), resource));
}

// if the SDK call fails, an error will be thrown by the SDK
Expand All @@ -526,9 +530,14 @@ async function applyHotswappableChange(sdk: SDK, ioSpan: IMessageSpan<any>, hots
}

for (const resource of hotswapOperation.change.resources) {
await ioSpan.notify(IO.DEFAULT_TOOLKIT_INFO.msg(format(`${ICON} %s %s`, chalk.bold(resourceText(resource)), chalk.green('hotswapped!'))));
await ioSpan.notify(IO.CDK_TOOLKIT_I5404.msg(format(`${ICON} %s %s`, chalk.bold(resourceText(resource)), chalk.green('hotswapped!')), resource));
}

await ioSpan.notify(IO.CDK_TOOLKIT_I5402.msg(
`Hotswapping ${hotswapOperation.change.cause.logicalId} (${hotswapOperation.change.cause.newValue.Type})... Done`,
hotswapOperation.change,
));

sdk.removeCustomUserAgent(customUserAgent);
}

Expand All @@ -550,12 +559,12 @@ function formatWaiterErrorResult(result: WaiterResult) {
return main;
}

async function logNonHotswappableChanges(
async function logRejectedChanges(
ioSpan: IMessageSpan<any>,
nonHotswappableChanges: RejectedChange[],
rejectedChanges: RejectedChange[],
hotswapMode: HotswapMode,
): Promise<void> {
if (nonHotswappableChanges.length === 0) {
if (rejectedChanges.length === 0) {
return;
}
/**
Expand All @@ -566,9 +575,9 @@ async function logNonHotswappableChanges(
* This logic prevents us from logging that change as non-hotswappable when we hotswap it.
*/
if (hotswapMode === 'hotswap-only') {
nonHotswappableChanges = nonHotswappableChanges.filter((change) => change.hotswapOnlyVisible === true);
rejectedChanges = rejectedChanges.filter((change) => change.hotswapOnlyVisible === true);

if (nonHotswappableChanges.length === 0) {
if (rejectedChanges.length === 0) {
return;
}
}
Expand All @@ -581,12 +590,14 @@ async function logNonHotswappableChanges(
messages.push(format('%s %s', chalk.red('⚠️'), chalk.red('The following non-hotswappable changes were found:')));
}

for (const rejection of nonHotswappableChanges) {
messages.push(' ' + nonHotswappableChangeMessage(rejection.change));
const nonHotswappableChanges = rejectedChanges.map(r => r.change);

for (const change of nonHotswappableChanges) {
messages.push(' ' + nonHotswappableChangeMessage(change));
}
messages.push(''); // newline

await ioSpan.notify(IO.DEFAULT_TOOLKIT_INFO.msg(messages.join('\n')));
await ioSpan.notify(IO.CDK_TOOLKIT_I5405.msg(messages.join('\n'), nonHotswappableChanges));
}

/**
Expand Down
Loading