Skip to content

Commit c832eaf

Browse files
author
Elad Ben-Israel
authored
feat(toolkit): deployment ui improvements (#1067)
A few UI improvements to the toolkit: - Remove a few emojis (sorry @RomainMuller) to reduce clutter. - Make output more concise. - Add "Creating changeset..." (because it takes ages) - Improve log view, align columns - Print resource path in log view - Clean up asset logs - Tone down colors a little
1 parent 28b28a0 commit c832eaf

File tree

8 files changed

+102
-46
lines changed

8 files changed

+102
-46
lines changed

packages/aws-cdk/bin/cdk.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -517,23 +517,33 @@ async function initCommandLine() {
517517
const deployName = renames.finalName(stack.name);
518518
519519
if (deployName !== stack.name) {
520-
success(' ⏳ Starting deployment of stack %s as %s...', colors.blue(stack.name), colors.blue(deployName));
520+
print('%s: deploying... (was %s)', colors.bold(deployName), colors.bold(stack.name));
521521
} else {
522-
success(' ⏳ Starting deployment of stack %s...', colors.blue(stack.name));
522+
print('%s: deploying...', colors.bold(stack.name));
523523
}
524524
525525
try {
526526
const result = await deployStack({ stack, sdk: aws, toolkitInfo, deployName, roleArn });
527-
const message = result.noOp ? ` Stack was already up-to-date, it has ARN ${colors.blue(result.stackArn)}`
528-
: ` Deployment of stack %s completed successfully, it has ARN ${colors.blue(result.stackArn)}`;
529-
data(result.stackArn);
530-
success(message, colors.blue(stack.name));
527+
const message = result.noOp
528+
? ` %s (no changes)`
529+
: ` %s`;
530+
531+
success('\n' + message, stack.name);
532+
533+
if (Object.keys(result.outputs).length > 0) {
534+
print('\nOutputs:');
535+
}
536+
531537
for (const name of Object.keys(result.outputs)) {
532538
const value = result.outputs[name];
533-
print('%s.%s = %s', colors.blue(deployName), colors.blue(name), colors.green(value));
539+
print('%s.%s = %s', colors.cyan(deployName), colors.cyan(name), colors.underline(colors.cyan(value)));
534540
}
541+
542+
print('\nStack ARN:');
543+
544+
data(result.stackArn);
535545
} catch (e) {
536-
error(' ❌ Deployment of stack %s failed: %s', colors.blue(stack.name), e);
546+
error('\n ❌ %s failed: %s', colors.bold(stack.name), e);
537547
throw e;
538548
}
539549
}
@@ -554,12 +564,12 @@ async function initCommandLine() {
554564
for (const stack of stacks) {
555565
const deployName = renames.finalName(stack.name);
556566
557-
success(' ⏳ Starting destruction of stack %s...', colors.blue(deployName));
567+
success('%s: destroying...', colors.blue(deployName));
558568
try {
559569
await destroyStack({ stack, sdk: aws, deployName, roleArn });
560-
success(' ✅ Stack %s successfully destroyed.', colors.blue(deployName));
570+
success('\n%s: destroyed', colors.blue(deployName));
561571
} catch (e) {
562-
error(' ❌ Destruction failed: %s', colors.blue(deployName), e);
572+
error('\n%s: destroy failed', colors.blue(deployName), e);
563573
throw e;
564574
}
565575
}

packages/aws-cdk/integ-tests/app/app.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ class MyStack extends cdk.Stack {
66
super(parent, id);
77
new sns.Topic(this, 'topic');
88

9-
console.log(new cdk.AvailabilityZoneProvider(this).availabilityZones);
10-
console.log(new cdk.SSMParameterProvider(this, { parameterName: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2' }).parameterValue(''));
9+
new cdk.AvailabilityZoneProvider(this).availabilityZones;
10+
new cdk.SSMParameterProvider(this, { parameterName: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2' }).parameterValue('');
1111
}
1212
}
1313

packages/aws-cdk/integ-tests/common.bash

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
scriptdir=$(cd $(dirname $0) && pwd)
2+
3+
toolkit_bin="${scriptdir}/../bin"
4+
5+
if [ ! -x ${toolkit_bin}/cdk ]; then
6+
echo "Unable to find 'cdk' under ${toolkit_bin}"
7+
exit 1
8+
fi
9+
10+
# make sure "this" toolkit is in the path
11+
export PATH=${toolkit_bin}:$PATH
12+
113
function cleanup_stack() {
214
local stack_arn=$1
315
echo "| ensuring ${stack_arn} is cleaned up"
@@ -71,6 +83,7 @@ function assert_lines() {
7183

7284
local lines="$(echo "${data}" | wc -l)"
7385
if [ "${lines}" -ne "${expected}" ]; then
86+
echo "${data}"
7487
fail "response has ${lines} lines and we expected ${expected} lines to be returned"
7588
fi
7689
}

packages/aws-cdk/integ-tests/test-cdk-deploy-all.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ echo "Stack deployed successfully"
1212
# verify that we only deployed a single stack (there's a single ARN in the output)
1313
lines="$(echo "${stack_arns}" | wc -l)"
1414
if [ "${lines}" -ne 2 ]; then
15+
echo "-- output -----------"
16+
echo "${stack_arns}"
17+
echo "---------------------"
1518
fail "cdk deploy returned ${lines} arns and we expected 2"
1619
fi
1720

packages/aws-cdk/integ-tests/test.sh

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,6 @@
22
set -euo pipefail
33
scriptdir=$(cd $(dirname $0) && pwd)
44

5-
toolkit_bin="${scriptdir}/../bin"
6-
7-
if [ ! -x ${toolkit_bin}/cdk ]; then
8-
echo "Unable to find 'cdk' under ${toolkit_bin}"
9-
exit 1
10-
fi
11-
12-
# make sure "this" toolkit is in the path
13-
export PATH=${toolkit_bin}:$PATH
14-
155
cd ${scriptdir}
166
for test in test-*.sh; do
177
echo "============================================================================================"

packages/aws-cdk/lib/api/deploy-stack.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import colors = require('colors/safe');
44
import YAML = require('js-yaml');
55
import uuid = require('uuid');
66
import { prepareAssets } from '../assets';
7-
import { debug, error } from '../logging';
7+
import { debug, error, print } from '../logging';
88
import { Mode } from './aws-auth/credentials';
99
import { ToolkitInfo } from './toolkit-info';
1010
import { describeStack, stackExists, stackFailedCreating, waitForChangeSet, waitForStack } from './util/cloudformation';
@@ -61,6 +61,7 @@ export async function deployStack(options: DeployStackOptions): Promise<DeploySt
6161

6262
const changeSetName = `CDK-${executionId}`;
6363
debug(`Attempting to create ChangeSet ${changeSetName} to ${update ? 'update' : 'create'} stack ${deployName}`);
64+
print(`%s: creating CloudFormation changeset...`, colors.bold(deployName));
6465
const changeSet = await cfn.createChangeSet({
6566
StackName: deployName,
6667
ChangeSetName: changeSetName,
@@ -83,7 +84,7 @@ export async function deployStack(options: DeployStackOptions): Promise<DeploySt
8384
debug('Initiating execution of changeset %s on stack %s', changeSetName, deployName);
8485
await cfn.executeChangeSet({ StackName: deployName, ChangeSetName: changeSetName }).promise();
8586
// tslint:disable-next-line:max-line-length
86-
const monitor = options.quiet ? undefined : new StackActivityMonitor(cfn, deployName, options.stack.metadata, changeSetDescription.Changes.length).start();
87+
const monitor = options.quiet ? undefined : new StackActivityMonitor(cfn, deployName, options.stack, changeSetDescription.Changes.length).start();
8788
debug('Execution of changeset %s on stack %s has started; waiting for the update to complete...', changeSetName, deployName);
8889
await waitForStack(cfn, deployName);
8990
if (monitor) { await monitor.stop(); }
@@ -153,7 +154,7 @@ export async function destroyStack(options: DestroyStackOptions) {
153154
if (!await stackExists(cfn, deployName)) {
154155
return;
155156
}
156-
const monitor = options.quiet ? undefined : new StackActivityMonitor(cfn, deployName).start();
157+
const monitor = options.quiet ? undefined : new StackActivityMonitor(cfn, deployName, options.stack).start();
157158
await cfn.deleteStack({ StackName: deployName, RoleARN: options.roleArn }).promise().catch(e => { throw e; });
158159
const destroyedStack = await waitForStack(cfn, deployName, false);
159160
if (monitor) { await monitor.stop(); }

packages/aws-cdk/lib/api/util/cloudformation/stack-activity-monitor.ts

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,14 @@ export class StackActivityMonitor {
6565
*/
6666
private readPromise?: Promise<AWS.CloudFormation.StackEvent[]>;
6767

68+
/**
69+
* The with of the "resource type" column.
70+
*/
71+
private readonly resourceTypeColumnWidth: number;
72+
6873
constructor(private readonly cfn: aws.CloudFormation,
6974
private readonly stackName: string,
70-
private readonly metadata?: cxapi.StackMetadata,
75+
private readonly stack: cxapi.SynthesizedStack,
7176
private readonly resourcesTotal?: number) {
7277

7378
if (this.resourcesTotal != null) {
@@ -78,6 +83,8 @@ export class StackActivityMonitor {
7883
// How many digits does this number take to represent?
7984
this.resourceDigits = Math.ceil(Math.log10(this.resourcesTotal));
8085
}
86+
87+
this.resourceTypeColumnWidth = calcMaxResourceTypeLength(this.stack.template);
8188
}
8289

8390
public start() {
@@ -164,20 +171,33 @@ export class StackActivityMonitor {
164171
const e = activity.event;
165172
const color = this.colorFromStatus(e.ResourceStatus);
166173
const md = this.findMetadataFor(e.LogicalResourceId);
174+
let reasonColor = colors.cyan;
167175

168-
let suffix = '';
176+
let stackTrace = '';
169177
if (md && e.ResourceStatus && e.ResourceStatus.indexOf('FAILED') !== -1) {
170-
suffix = `\n${md.entry.data} was created at: ${md.path}\n\t${md.entry.trace.join('\n\t\\_ ')}`;
178+
stackTrace = `\n\t${md.entry.trace.join('\n\t\\_ ')}`;
179+
reasonColor = colors.red;
180+
}
181+
182+
let resourceName = md ? md.path.replace(/\/Resource$/, '') : (e.LogicalResourceId || '');
183+
resourceName = resourceName.replace(/^\//, ''); // remove "/" prefix
184+
185+
// remove "<stack-name>/" prefix
186+
if (resourceName.startsWith(this.stackName + '/')) {
187+
resourceName = resourceName.substr(this.stackName.length + 1);
171188
}
172189

173-
process.stderr.write(util.format(color(`%s %s %s [%s] %s %s%s\n`),
190+
const logicalId = resourceName !== e.LogicalResourceId ? `(${e.LogicalResourceId}) ` : '';
191+
192+
process.stderr.write(util.format(` %s | %s | %s | %s | %s %s%s%s\n`,
174193
this.progress(),
175-
e.Timestamp,
176-
padRight(18, "" + e.ResourceStatus),
177-
e.ResourceType,
178-
e.LogicalResourceId,
179-
e.ResourceStatusReason ? e.ResourceStatusReason : '',
180-
suffix));
194+
new Date(e.Timestamp).toLocaleTimeString(),
195+
color(padRight(20, (e.ResourceStatus || '').substr(0, 20))), // pad left and trim
196+
padRight(this.resourceTypeColumnWidth, e.ResourceType || ''),
197+
color(colors.bold(resourceName)),
198+
logicalId,
199+
reasonColor(colors.bold(e.ResourceStatusReason ? e.ResourceStatusReason : '')),
200+
reasonColor(stackTrace)));
181201

182202
this.lastPrintTime = Date.now();
183203
}
@@ -188,10 +208,10 @@ export class StackActivityMonitor {
188208
private progress(): string {
189209
if (this.resourcesTotal == null) {
190210
// Don't have total, show simple count and hope the human knows
191-
return util.format('[%s]', this.resourcesDone);
211+
return padLeft(3, util.format('%s', this.resourcesDone)); // max 200 resources
192212
}
193213

194-
return util.format('[%s/%s]',
214+
return util.format('%s/%s',
195215
padLeft(this.resourceDigits, this.resourcesDone.toString()),
196216
padLeft(this.resourceDigits, this.resourcesTotal != null ? this.resourcesTotal.toString() : '?'));
197217
}
@@ -204,9 +224,11 @@ export class StackActivityMonitor {
204224
return;
205225
}
206226

207-
process.stderr.write(util.format(colors.blue('%s Currently in progress: %s\n'),
208-
this.progress(),
209-
Array.from(this.resourcesInProgress).join(', ')));
227+
if (this.resourcesInProgress.size > 0) {
228+
process.stderr.write(util.format('%s Currently in progress: %s\n',
229+
this.progress(),
230+
colors.bold(Array.from(this.resourcesInProgress).join(', '))));
231+
}
210232

211233
// We cheat a bit here. To prevent printInProgress() from repeatedly triggering,
212234
// we set the timestamp into the future. It will be reset whenever a regular print
@@ -215,9 +237,10 @@ export class StackActivityMonitor {
215237
}
216238

217239
private findMetadataFor(logicalId: string | undefined): { entry: cxapi.MetadataEntry, path: string } | undefined {
218-
if (!logicalId || !this.metadata) { return undefined; }
219-
for (const path of Object.keys(this.metadata)) {
220-
const entry = this.metadata[path].filter(e => e.type === 'aws:cdk:logicalId')
240+
const metadata = this.stack.metadata;
241+
if (!logicalId || !metadata) { return undefined; }
242+
for (const path of Object.keys(metadata)) {
243+
const entry = metadata[path].filter(e => e.type === 'aws:cdk:logicalId')
221244
.find(e => e.data === logicalId);
222245
if (entry) { return { entry, path }; }
223246
}
@@ -284,3 +307,15 @@ function padRight(n: number, x: string): string {
284307
function padLeft(n: number, x: string): string {
285308
return ' '.repeat(Math.max(0, n - x.length)) + x;
286309
}
310+
311+
function calcMaxResourceTypeLength(template: any) {
312+
const resources = (template && template.Resources) || {};
313+
let maxWidth = 0;
314+
for (const id of Object.keys(resources)) {
315+
const type = resources[id].Type || '';
316+
if (type.length > maxWidth) {
317+
maxWidth = type.length;
318+
}
319+
}
320+
return maxWidth;
321+
}

packages/aws-cdk/lib/assets.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ASSET_METADATA, ASSET_PREFIX_SEPARATOR, AssetMetadataEntry, StackMetadata, SynthesizedStack } from '@aws-cdk/cx-api';
22
import { CloudFormation } from 'aws-sdk';
3+
import colors = require('colors');
34
import fs = require('fs-extra');
45
import os = require('os');
56
import path = require('path');
@@ -78,11 +79,14 @@ async function prepareFileAsset(
7879
contentType
7980
});
8081

82+
const relativePath = path.relative(process.cwd(), asset.path);
83+
8184
const s3url = `s3://${toolkitInfo.bucketName}/${key}`;
85+
debug(`S3 url for ${relativePath}: ${s3url}`);
8286
if (changed) {
83-
success(` 👑 Asset ${asset.path} (${asset.packaging}) uploaded: ${s3url}`);
87+
success(`Updated: ${colors.bold(relativePath)} (${asset.packaging})`);
8488
} else {
85-
debug(` 👑 Asset ${asset.path} (${asset.packaging}) is up-to-date: ${s3url}`);
89+
debug(`Up-to-date: ${colors.bold(relativePath)} (${asset.packaging})`);
8690
}
8791

8892
return [

0 commit comments

Comments
 (0)