packages/@aws-cdk/toolkit-lib/lib/private/activity-printer/base.ts (104 lines of code) (raw):
import type { CloudFormationStackArtifact } from '@aws-cdk/cx-api';
import type { IoMessage } from '../../api/io';
import { IO } from '../../api/io/private';
import { type StackActivity, type StackProgress } from '../../payloads';
import { maxResourceTypeLength, stackEventHasErrorMessage } from '../../util';
export interface IActivityPrinter {
notify(msg: IoMessage<unknown>): void;
}
export interface ActivityPrinterProps {
/**
* Stream to write to
*/
readonly stream: NodeJS.WriteStream;
}
export abstract class ActivityPrinterBase implements IActivityPrinter {
protected static readonly TIMESTAMP_WIDTH = 12;
protected static readonly STATUS_WIDTH = 20;
/**
* Stream to write to
*/
protected readonly stream: NodeJS.WriteStream;
/**
* The with of the "resource type" column.
*/
protected resourceTypeColumnWidth: number = maxResourceTypeLength({});
/**
* A list of resource IDs which are currently being processed
*/
protected resourcesInProgress: Record<string, StackActivity> = {};
protected stackProgress?: StackProgress;
protected rollingBack = false;
protected readonly failures = new Array<StackActivity>();
protected hookFailureMap = new Map<string, Map<string, string>>();
constructor(protected readonly props: ActivityPrinterProps) {
this.stream = props.stream;
}
protected abstract print(): void;
/**
* Receive a stack activity message
*/
public notify(msg: IoMessage<unknown>): void {
switch (true) {
case IO.CDK_TOOLKIT_I5501.is(msg):
this.start(msg.data);
break;
case IO.CDK_TOOLKIT_I5502.is(msg):
this.activity(msg.data);
break;
case IO.CDK_TOOLKIT_I5503.is(msg):
this.stop();
break;
default:
// ignore all other messages
break;
}
}
public start({ stack }: { stack: CloudFormationStackArtifact}) {
this.resourceTypeColumnWidth = maxResourceTypeLength(stack.template);
}
public activity(activity: StackActivity) {
// process the activity and then call print
this.addActivity(activity);
this.print();
}
public stop() {
// final print after the stack is done
this.print();
}
protected addActivity(activity: StackActivity) {
const status = activity.event.ResourceStatus;
const hookStatus = activity.event.HookStatus;
const hookType = activity.event.HookType;
if (!status || !activity.event.LogicalResourceId) {
return;
}
this.stackProgress = activity.progress;
if (status === 'ROLLBACK_IN_PROGRESS' || status === 'UPDATE_ROLLBACK_IN_PROGRESS') {
// Only triggered on the stack once we've started doing a rollback
this.rollingBack = true;
}
if (status.endsWith('_IN_PROGRESS')) {
this.resourcesInProgress[activity.event.LogicalResourceId] = activity;
}
if (stackEventHasErrorMessage(status)) {
const isCancelled = (activity.event.ResourceStatusReason ?? '').indexOf('cancelled') > -1;
// Cancelled is not an interesting failure reason
if (!isCancelled) {
this.failures.push(activity);
}
}
if (status.endsWith('_COMPLETE') || status.endsWith('_FAILED')) {
delete this.resourcesInProgress[activity.event.LogicalResourceId];
}
if (
hookStatus !== undefined &&
hookStatus.endsWith('_COMPLETE_FAILED') &&
activity.event.LogicalResourceId !== undefined &&
hookType !== undefined
) {
if (this.hookFailureMap.has(activity.event.LogicalResourceId)) {
this.hookFailureMap.get(activity.event.LogicalResourceId)?.set(hookType, activity.event.HookStatusReason ?? '');
} else {
this.hookFailureMap.set(activity.event.LogicalResourceId, new Map<string, string>());
this.hookFailureMap.get(activity.event.LogicalResourceId)?.set(hookType, activity.event.HookStatusReason ?? '');
}
}
}
protected failureReason(activity: StackActivity) {
const resourceStatusReason = activity.event.ResourceStatusReason ?? '';
const logicalResourceId = activity.event.LogicalResourceId ?? '';
const hookFailureReasonMap = this.hookFailureMap.get(logicalResourceId);
if (hookFailureReasonMap !== undefined) {
for (const hookType of hookFailureReasonMap.keys()) {
if (resourceStatusReason.includes(hookType)) {
return resourceStatusReason + ' : ' + hookFailureReasonMap.get(hookType);
}
}
}
return resourceStatusReason;
}
/**
* Is the activity a meta activity for the stack itself.
*/
protected isActivityForTheStack(activity: StackActivity) {
return activity.event.PhysicalResourceId === activity.event.StackId;
}
}