Skip to content
Closed
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
1 change: 1 addition & 0 deletions packages/@aws-cdk/toolkit-lib/lib/api/io/private/codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const CODES = {
CDK_TOOLKIT_I5900: 'Deployment results on success',

CDK_TOOLKIT_E5001: 'No stacks found',
CDK_TOOLKIT_E5002: 'Stack monitoring error',

// 6: Rollback
CDK_TOOLKIT_I6000: 'Provides rollback times',
Expand Down
4 changes: 2 additions & 2 deletions packages/aws-cdk/lib/api/deployments/deploy-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,7 @@ class FullCloudFormationDeployment {
private async monitorDeployment(startTime: Date, expectedChanges: number | undefined): Promise<SuccessfulDeployStackResult> {
const monitor = this.options.quiet
? undefined
: StackActivityMonitor.withDefaultPrinter(this.cfn, this.stackName, this.stackArtifact, {
: await StackActivityMonitor.withDefaultPrinter(this.cfn, { ioHost: this.ioHost, action: this.action }, this.stackName, this.stackArtifact, {
resourcesTotal: expectedChanges,
progress: this.options.progress,
changeSetCreationTime: startTime,
Expand Down Expand Up @@ -698,7 +698,7 @@ export async function destroyStack(options: DestroyStackOptions, { ioHost, actio
}
const monitor = options.quiet
? undefined
: StackActivityMonitor.withDefaultPrinter(cfn, deployName, options.stack, {
: await StackActivityMonitor.withDefaultPrinter(cfn, { ioHost, action }, deployName, options.stack, {
ci: options.ci,
}).start();

Expand Down
2 changes: 1 addition & 1 deletion packages/aws-cdk/lib/api/deployments/deployments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ export class Deployments {

const monitor = options.quiet
? undefined
: StackActivityMonitor.withDefaultPrinter(cfn, deployName, options.stack, {
: await StackActivityMonitor.withDefaultPrinter(cfn, { ioHost: this.ioHost, action: this.action }, deployName, options.stack, {
ci: options.ci,
}).start();

Expand Down
93 changes: 62 additions & 31 deletions packages/aws-cdk/lib/api/stack-events/stack-activity-monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { ArtifactMetadataEntryType, type MetadataEntry } from '@aws-cdk/cloud-as
import type { CloudFormationStackArtifact } from '@aws-cdk/cx-api';
import * as chalk from 'chalk';
import { ResourceEvent, StackEventPoller } from './stack-event-poller';
import { error, info } from '../../logging';
import { CliIoHost, IoMessageLevel } from '../../toolkit/cli-io-host';
import { CliIoHost, IoMessageLevel, IoMessaging } from '../../toolkit/cli-io-host';
import type { ICloudFormationClient } from '../aws-auth';
import { RewritableBlock } from './display';
import { error, info } from '../../cli/messages';

export interface StackActivity extends ResourceEvent {
readonly metadata?: ResourceMetadata;
Expand Down Expand Up @@ -90,6 +90,7 @@ export class StackActivityMonitor {
*/
public static withDefaultPrinter(
cfn: ICloudFormationClient,
{ ioHost, action }: IoMessaging,
stackName: string,
stackArtifact: CloudFormationStackArtifact,
options: WithDefaultPrinterProps = {},
Expand All @@ -100,6 +101,8 @@ export class StackActivityMonitor {
resourceTypeColumnWidth: calcMaxResourceTypeLength(stackArtifact.template),
resourcesTotal: options.resourcesTotal,
stream,
ioHost,
action,
};

const isWindows = process.platform === 'win32';
Expand All @@ -115,7 +118,7 @@ export class StackActivityMonitor {
? new CurrentActivityPrinter(props)
: new HistoryActivityPrinter(props);

return new StackActivityMonitor(cfn, stackName, printer, stackArtifact, options.changeSetCreationTime);
return new StackActivityMonitor(cfn, ioHost, action, stackName, printer, stackArtifact, options.changeSetCreationTime);
}

/**
Expand All @@ -139,6 +142,8 @@ export class StackActivityMonitor {

constructor(
cfn: ICloudFormationClient,
private readonly ioHost: IoMessaging['ioHost'],
private readonly action: IoMessaging['action'],
private readonly stackName: string,
private readonly printer: IActivityPrinter,
private readonly stack?: CloudFormationStackArtifact,
Expand All @@ -150,9 +155,9 @@ export class StackActivityMonitor {
});
}

public start() {
public async start() {
this.active = true;
this.printer.start();
await this.printer.start();
this.scheduleNextTick();
return this;
}
Expand All @@ -168,7 +173,7 @@ export class StackActivityMonitor {
// up not printing the failure reason to users.
await this.finalPollToEnd();

this.printer.stop();
await this.printer.stop();
}

private scheduleNextTick() {
Expand All @@ -194,9 +199,9 @@ export class StackActivityMonitor {
return;
}

this.printer.print();
await this.printer.print();
} catch (e) {
error('Error occurred while monitoring stack: %s', e);
await this.ioHost.notify(error(this.action, util.format('Error occurred while monitoring stack: %s', e), 'CDK_TOOLKIT_E5002'));
}
this.scheduleNextTick();
}
Expand Down Expand Up @@ -321,15 +326,32 @@ interface PrinterProps {
* Stream to write to
*/
readonly stream: NodeJS.WriteStream;

/**
* The IoHost used for messaging
*/
readonly ioHost: IoMessaging['ioHost'];

/**
* The current action.
*/
readonly action: IoMessaging['action'];
}

export interface IActivityPrinter {
readonly updateSleep: number;

/**
* Synchronously adds an activity to the print log
*/
addActivity(activity: StackActivity): void;
print(): void;
start(): void;
stop(): void;

/**
*
*/
print(): Promise<void>;
start(): Promise<void>;
stop(): Promise<void>;
}

abstract class ActivityPrinterBase implements IActivityPrinter {
Expand Down Expand Up @@ -370,13 +392,19 @@ abstract class ActivityPrinterBase implements IActivityPrinter {

protected hookFailureMap = new Map<string, Map<string, string>>();

protected ioHost: IoMessaging['ioHost'];
protected action: IoMessaging['action'];

constructor(protected readonly props: PrinterProps) {
// +1 because the stack also emits a "COMPLETE" event at the end, and that wasn't
// counted yet. This makes it line up with the amount of events we expect.
this.resourcesTotal = props.resourcesTotal ? props.resourcesTotal + 1 : undefined;

// How many digits does this number take to represent?
this.resourceDigits = this.resourcesTotal ? Math.ceil(Math.log10(this.resourcesTotal)) : 0;

this.ioHost = props.ioHost;
this.action = props.action;
}

public failureReason(activity: StackActivity) {
Expand Down Expand Up @@ -458,13 +486,13 @@ abstract class ActivityPrinterBase implements IActivityPrinter {
}
}

public abstract print(): void;
public abstract print(): Promise<void>;

public start() {
public async start() {
// Empty on purpose
}

public stop() {
public async stop() {
// Empty on purpose
}
}
Expand Down Expand Up @@ -497,33 +525,33 @@ export class HistoryActivityPrinter extends ActivityPrinterBase {
public addActivity(activity: StackActivity) {
super.addActivity(activity);
this.printable.push(activity);
this.print();
}

public print() {
public async print() {
for (const activity of this.printable) {
this.printOne(activity);
await this.printOne(activity);
}
this.printable.splice(0, this.printable.length);
this.printInProgress();
await this.printInProgress();
}

public stop() {
public async stop() {
await this.print();
// Print failures at the end
if (this.failures.length > 0) {
info('\nFailed resources:');
await this.ioHost.notify(info(this.action, '\nFailed resources:'));
for (const failure of this.failures) {
// Root stack failures are not interesting
if (failure.isStackEvent) {
continue;
}

this.printOne(failure, false);
await this.printOne(failure, false);
}
}
}

private printOne(activity: StackActivity, progress?: boolean) {
private async printOne(activity: StackActivity, progress?: boolean) {
const event = activity.event;
const color = colorFromStatusResult(event.ResourceStatus);
let reasonColor = chalk.cyan;
Expand All @@ -545,7 +573,8 @@ export class HistoryActivityPrinter extends ActivityPrinterBase {

const logicalId = resourceName !== event.LogicalResourceId ? `(${event.LogicalResourceId}) ` : '';

info(
await this.ioHost.notify(info(
this.action,
util.format(
'%s | %s%s | %s | %s | %s %s%s%s',
event.StackName,
Expand All @@ -558,7 +587,7 @@ export class HistoryActivityPrinter extends ActivityPrinterBase {
reasonColor(chalk.bold(event.ResourceStatusReason ? event.ResourceStatusReason : '')),
reasonColor(stackTrace),
),
);
));

this.lastPrintTime = Date.now();
}
Expand All @@ -582,19 +611,20 @@ export class HistoryActivityPrinter extends ActivityPrinterBase {
/**
* If some resources are taking a while to create, notify the user about what's currently in progress
*/
private printInProgress() {
private async printInProgress() {
if (Date.now() < this.lastPrintTime + this.inProgressDelay) {
return;
}

if (Object.keys(this.resourcesInProgress).length > 0) {
info(
await this.ioHost.notify(info(
this.action,
util.format(
'%s Currently in progress: %s',
this.progress(),
chalk.bold(Object.keys(this.resourcesInProgress).join(', ')),
),
);
));
}

// We cheat a bit here. To prevent printInProgress() from repeatedly triggering,
Expand Down Expand Up @@ -634,7 +664,7 @@ export class CurrentActivityPrinter extends ActivityPrinterBase {
this.block = new RewritableBlock(this.stream);
}

public print(): void {
public async print(): Promise<void> {
const lines = [];

// Add a progress bar at the top
Expand All @@ -648,7 +678,7 @@ export class CurrentActivityPrinter extends ActivityPrinterBase {
}

// Normally we'd only print "resources in progress", but it's also useful
// to keep an eye on the failures and know about the specific errors asquickly
// to keep an eye on the failures and know about the specific errors as quickly
// as possible (while the stack is still rolling back), so add those in.
const toPrint: StackActivity[] = [...this.failures, ...Object.values(this.resourcesInProgress)];
toPrint.sort((a, b) => a.event.Timestamp!.getTime() - b.event.Timestamp!.getTime());
Expand All @@ -672,14 +702,15 @@ export class CurrentActivityPrinter extends ActivityPrinterBase {
this.block.displayLines(lines);
}

public start() {
public async start() {
// Need to prevent the waiter from printing 'stack not stable' every 5 seconds, it messes
// with the output calculations.
this.oldLogThreshold = CliIoHost.instance().logLevel;
CliIoHost.instance().logLevel = 'info';
}

public stop() {
public async stop() {
await this.print();
CliIoHost.instance().logLevel = this.oldLogThreshold;

// Print failures at the end
Expand Down
4 changes: 2 additions & 2 deletions packages/aws-cdk/test/api/_helpers/console-listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ class ConsoleListener {
return (res as Inspector);
}

inspectSync(fn: (output: Output) => void): Output {
async inspectSync(fn: (output: Output) => Promise<void>): Promise<Output> {
const inspect = this.inspect();
try {
fn(inspect.output);
await fn(inspect.output);
} finally {
inspect.restore();
}
Expand Down
Loading
Loading