diff --git a/packages/@aws-cdk/toolkit/lib/io/logger.ts b/packages/@aws-cdk/toolkit/lib/io/logger.ts index e829f0ecf3984..cadb82ff15d02 100644 --- a/packages/@aws-cdk/toolkit/lib/io/logger.ts +++ b/packages/@aws-cdk/toolkit/lib/io/logger.ts @@ -3,6 +3,9 @@ import { formatSdkLoggerContent } from 'aws-cdk/lib/api/aws-auth/sdk-logger'; import { ToolkitAction } from '../types'; import { IIoHost, IoMessage, IoRequest } from './io-host'; import { trace } from './messages'; +import { withCorkedLogging } from 'aws-cdk/lib/logging'; +import { ToolkitError } from '../api/errors'; +import chalk from 'chalk'; export function withAction(ioHost: IIoHost, action: ToolkitAction) { return { @@ -109,3 +112,37 @@ export function asSdkLogger(ioHost: IIoHost, action: ToolkitAction): Logger { } }; } + +let TESTING = false; + +export function markTesting() { + TESTING = true; +} + +/** + * Ask the user for a yes/no confirmation + * + * Automatically fail the confirmation in case we're in a situation where the confirmation + * cannot be interactively obtained from a human at the keyboard. + */ +export async function askUserConfirmation( + ioHost: IIoHost, + concurrency: number, + motivation: string, + question: string, +) { + await withCorkedLogging(async () => { + // only talk to user if STDIN is a terminal (otherwise, fail) + if (!TESTING && !process.stdin.isTTY) { + throw new ToolkitError(`${motivation}, but terminal (TTY) is not attached so we are unable to get a confirmation from the user`); + } + + // only talk to user if concurrency is 1 (otherwise, fail) + if (concurrency > 1) { + throw new ToolkitError(`${motivation}, but concurrency is greater than 1 so we are unable to get a confirmation from the user`); + } + + const confirmed = await ioHost.requestResponse(prompt(`${chalk.cyan(question)} (y/n)?`)); + if (!confirmed) { throw new ToolkitError('Aborted by user'); } + }); +} diff --git a/packages/@aws-cdk/toolkit/lib/toolkit.ts b/packages/@aws-cdk/toolkit/lib/toolkit.ts index 61d1ad38d95c9..eae2d78f213d7 100644 --- a/packages/@aws-cdk/toolkit/lib/toolkit.ts +++ b/packages/@aws-cdk/toolkit/lib/toolkit.ts @@ -28,7 +28,7 @@ import { StackAssembly } from './api/cloud-assembly/stack-assembly'; import { ICloudAssemblySource } from './api/cloud-assembly/types'; import { ToolkitError } from './api/errors'; import { IIoHost } from './io/io-host'; -import { asSdkLogger, withAction } from './io/logger'; +import { askUserConfirmation, asSdkLogger, withAction } from './io/logger'; import { data, error, highlight, info, success, warning } from './io/messages'; import { Timer } from './io/timer'; import { StackSelectionStrategy, ToolkitAction } from './types'; @@ -235,6 +235,7 @@ export class Toolkit { // const currentTemplate = await deployments.readCurrentTemplate(stack); // if (printSecurityDiff(currentTemplate, stack, requireApproval)) { // await askUserConfirmation( + // ioHost, // concurrency, // '"--require-approval" is enabled and stack includes security-sensitive updates', // 'Do you wish to deploy these changes', @@ -314,6 +315,7 @@ export class Toolkit { await ioHost.notify(warning(`${motivation}. Rolling back first (--force).`)); } else { await askUserConfirmation( + ioHost, concurrency, motivation, `${motivation}. Roll back first and then proceed with deployment`, @@ -339,6 +341,7 @@ export class Toolkit { await ioHost.notify(warning(`${motivation}. Proceeding with regular deployment (--force).`)); } else { await askUserConfirmation( + ioHost, concurrency, motivation, `${motivation}. Perform a regular deployment`, @@ -384,14 +387,15 @@ export class Toolkit { [`❌ ${chalk.bold(stack.stackName)} failed:`, ...(e.name ? [`${e.name}:`] : []), e.message].join(' '), ); } finally { - if (options.cloudWatchLogMonitor) { - const foundLogGroupsResult = await findCloudWatchLogGroups(await this.sdkProvider('deploy'), stack); - options.cloudWatchLogMonitor.addLogGroups( - foundLogGroupsResult.env, - foundLogGroupsResult.sdk, - foundLogGroupsResult.logGroupNames, - ); - } + // @TODO + // if (options.cloudWatchLogMonitor) { + // const foundLogGroupsResult = await findCloudWatchLogGroups(await this.sdkProvider('deploy'), stack); + // options.cloudWatchLogMonitor.addLogGroups( + // foundLogGroupsResult.env, + // foundLogGroupsResult.sdk, + // foundLogGroupsResult.logGroupNames, + // ); + // } // If an outputs file has been specified, create the file path and write stack outputs to it once. // Outputs are written after all stacks have been deployed. If a stack deployment fails, // all of the outputs from successfully deployed stacks before the failure will still be written.