Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(toolkit): ui improvements #1067

Merged
merged 3 commits into from
Nov 5, 2018
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
32 changes: 21 additions & 11 deletions packages/aws-cdk/bin/cdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -623,23 +623,33 @@ async function initCommandLine() {
const deployName = renames.finalName(stack.name);

if (deployName !== stack.name) {
success(' ⏳ Starting deployment of stack %s as %s...', colors.blue(stack.name), colors.blue(deployName));
print('%s: deploying... (was %s)', colors.bold(deployName), colors.bold(stack.name));
} else {
success(' ⏳ Starting deployment of stack %s...', colors.blue(stack.name));
print('%s: deploying...', colors.bold(stack.name));
}

try {
const result = await deployStack({ stack, sdk: aws, toolkitInfo, deployName, roleArn });
const message = result.noOp ? ` ✅ Stack was already up-to-date, it has ARN ${colors.blue(result.stackArn)}`
: ` ✅ Deployment of stack %s completed successfully, it has ARN ${colors.blue(result.stackArn)}`;
data(result.stackArn);
success(message, colors.blue(stack.name));
const message = result.noOp
? ` ✅ %s (no changes)`
: ` ✅ %s`;

success('\n' + message, stack.name);

if (Object.keys(result.outputs).length > 0) {
print('\nOutputs:');
}

for (const name of Object.keys(result.outputs)) {
const value = result.outputs[name];
print('%s.%s = %s', colors.blue(deployName), colors.blue(name), colors.green(value));
print('%s.%s = %s', colors.cyan(deployName), colors.cyan(name), colors.underline(colors.cyan(value)));
}

print('\nStack ARN:');

data(result.stackArn);
} catch (e) {
error(' ❌ Deployment of stack %s failed: %s', colors.blue(stack.name), e);
error('\n ❌ %s failed: %s', colors.bold(stack.name), e);
throw e;
}
}
Expand All @@ -660,12 +670,12 @@ async function initCommandLine() {
for (const stack of stacks) {
const deployName = renames.finalName(stack.name);

success(' ⏳ Starting destruction of stack %s...', colors.blue(deployName));
success('%s: destroying...', colors.blue(deployName));
try {
await destroyStack({ stack, sdk: aws, deployName, roleArn });
success(' ✅ Stack %s successfully destroyed.', colors.blue(deployName));
success('\n%s: destroyed', colors.blue(deployName));
} catch (e) {
error(' ❌ Destruction failed: %s', colors.blue(deployName), e);
error('\n%s: destroy failed', colors.blue(deployName), e);
throw e;
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/aws-cdk/integ-tests/app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ class MyStack extends cdk.Stack {
super(parent, id);
new sns.Topic(this, 'topic');

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

Expand Down
13 changes: 13 additions & 0 deletions packages/aws-cdk/integ-tests/common.bash
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
scriptdir=$(cd $(dirname $0) && pwd)

toolkit_bin="${scriptdir}/../bin"

if [ ! -x ${toolkit_bin}/cdk ]; then
echo "Unable to find 'cdk' under ${toolkit_bin}"
exit 1
fi

# make sure "this" toolkit is in the path
export PATH=${toolkit_bin}:$PATH

function cleanup_stack() {
local stack_arn=$1
echo "| ensuring ${stack_arn} is cleaned up"
Expand Down Expand Up @@ -71,6 +83,7 @@ function assert_lines() {

local lines="$(echo "${data}" | wc -l)"
if [ "${lines}" -ne "${expected}" ]; then
echo "${data}"
fail "response has ${lines} lines and we expected ${expected} lines to be returned"
fi
}
3 changes: 3 additions & 0 deletions packages/aws-cdk/integ-tests/test-cdk-deploy-all.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ echo "Stack deployed successfully"
# verify that we only deployed a single stack (there's a single ARN in the output)
lines="$(echo "${stack_arns}" | wc -l)"
if [ "${lines}" -ne 2 ]; then
echo "-- output -----------"
echo "${stack_arns}"
echo "---------------------"
fail "cdk deploy returned ${lines} arns and we expected 2"
fi

Expand Down
10 changes: 0 additions & 10 deletions packages/aws-cdk/integ-tests/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,6 @@
set -euo pipefail
scriptdir=$(cd $(dirname $0) && pwd)

toolkit_bin="${scriptdir}/../bin"

if [ ! -x ${toolkit_bin}/cdk ]; then
echo "Unable to find 'cdk' under ${toolkit_bin}"
exit 1
fi

# make sure "this" toolkit is in the path
export PATH=${toolkit_bin}:$PATH

cd ${scriptdir}
for test in test-*.sh; do
echo "============================================================================================"
Expand Down
7 changes: 4 additions & 3 deletions packages/aws-cdk/lib/api/deploy-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import colors = require('colors/safe');
import YAML = require('js-yaml');
import uuid = require('uuid');
import { prepareAssets } from '../assets';
import { debug, error } from '../logging';
import { debug, error, print } from '../logging';
import { Mode } from './aws-auth/credentials';
import { ToolkitInfo } from './toolkit-info';
import { describeStack, stackExists, stackFailedCreating, waitForChangeSet, waitForStack } from './util/cloudformation';
Expand Down Expand Up @@ -61,6 +61,7 @@ export async function deployStack(options: DeployStackOptions): Promise<DeploySt

const changeSetName = `CDK-${executionId}`;
debug(`Attempting to create ChangeSet ${changeSetName} to ${update ? 'update' : 'create'} stack ${deployName}`);
print(`%s: creating CloudFormation changeset...`, colors.bold(deployName));
const changeSet = await cfn.createChangeSet({
StackName: deployName,
ChangeSetName: changeSetName,
Expand All @@ -83,7 +84,7 @@ export async function deployStack(options: DeployStackOptions): Promise<DeploySt
debug('Initiating execution of changeset %s on stack %s', changeSetName, deployName);
await cfn.executeChangeSet({ StackName: deployName, ChangeSetName: changeSetName }).promise();
// tslint:disable-next-line:max-line-length
const monitor = options.quiet ? undefined : new StackActivityMonitor(cfn, deployName, options.stack.metadata, changeSetDescription.Changes.length).start();
const monitor = options.quiet ? undefined : new StackActivityMonitor(cfn, deployName, options.stack, changeSetDescription.Changes.length).start();
debug('Execution of changeset %s on stack %s has started; waiting for the update to complete...', changeSetName, deployName);
await waitForStack(cfn, deployName);
if (monitor) { await monitor.stop(); }
Expand Down Expand Up @@ -153,7 +154,7 @@ export async function destroyStack(options: DestroyStackOptions) {
if (!await stackExists(cfn, deployName)) {
return;
}
const monitor = options.quiet ? undefined : new StackActivityMonitor(cfn, deployName).start();
const monitor = options.quiet ? undefined : new StackActivityMonitor(cfn, deployName, options.stack).start();
await cfn.deleteStack({ StackName: deployName, RoleARN: options.roleArn }).promise().catch(e => { throw e; });
const destroyedStack = await waitForStack(cfn, deployName, false);
if (monitor) { await monitor.stop(); }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,14 @@ export class StackActivityMonitor {
*/
private readPromise?: Promise<AWS.CloudFormation.StackEvent[]>;

/**
* The with of the "resource type" column.
*/
private readonly resourceTypeColumnWidth: number;

constructor(private readonly cfn: aws.CloudFormation,
private readonly stackName: string,
private readonly metadata?: cxapi.StackMetadata,
private readonly stack: cxapi.SynthesizedStack,
private readonly resourcesTotal?: number) {

if (this.resourcesTotal != null) {
Expand All @@ -78,6 +83,8 @@ export class StackActivityMonitor {
// How many digits does this number take to represent?
this.resourceDigits = Math.ceil(Math.log10(this.resourcesTotal));
}

this.resourceTypeColumnWidth = calcMaxResourceTypeLength(this.stack.template);
}

public start() {
Expand Down Expand Up @@ -164,20 +171,33 @@ export class StackActivityMonitor {
const e = activity.event;
const color = this.colorFromStatus(e.ResourceStatus);
const md = this.findMetadataFor(e.LogicalResourceId);
let reasonColor = colors.cyan;

let suffix = '';
let stackTrace = '';
if (md && e.ResourceStatus && e.ResourceStatus.indexOf('FAILED') !== -1) {
suffix = `\n${md.entry.data} was created at: ${md.path}\n\t${md.entry.trace.join('\n\t\\_ ')}`;
stackTrace = `\n\t${md.entry.trace.join('\n\t\\_ ')}`;
reasonColor = colors.red;
}

let resourceName = md ? md.path.replace(/\/Resource$/, '') : (e.LogicalResourceId || '');
resourceName = resourceName.replace(/^\//, ''); // remove "/" prefix

// remove "<stack-name>/" prefix
if (resourceName.startsWith(this.stackName + '/')) {
resourceName = resourceName.substr(this.stackName.length + 1);
}

process.stderr.write(util.format(color(`%s %s %s [%s] %s %s%s\n`),
const logicalId = resourceName !== e.LogicalResourceId ? `(${e.LogicalResourceId}) ` : '';

process.stderr.write(util.format(` %s | %s | %s | %s | %s %s%s%s\n`,
this.progress(),
e.Timestamp,
padRight(18, "" + e.ResourceStatus),
e.ResourceType,
e.LogicalResourceId,
e.ResourceStatusReason ? e.ResourceStatusReason : '',
suffix));
new Date(e.Timestamp).toLocaleTimeString(),
color(padRight(20, (e.ResourceStatus || '').substr(0, 20))), // pad left and trim
padRight(this.resourceTypeColumnWidth, e.ResourceType || ''),
color(colors.bold(resourceName)),
logicalId,
reasonColor(colors.bold(e.ResourceStatusReason ? e.ResourceStatusReason : '')),
reasonColor(stackTrace)));

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

return util.format('[%s/%s]',
return util.format('%s/%s',
padLeft(this.resourceDigits, this.resourcesDone.toString()),
padLeft(this.resourceDigits, this.resourcesTotal != null ? this.resourcesTotal.toString() : '?'));
}
Expand All @@ -204,9 +224,11 @@ export class StackActivityMonitor {
return;
}

process.stderr.write(util.format(colors.blue('%s Currently in progress: %s\n'),
this.progress(),
Array.from(this.resourcesInProgress).join(', ')));
if (this.resourcesInProgress.size > 0) {
process.stderr.write(util.format('%s Currently in progress: %s\n',
this.progress(),
colors.bold(Array.from(this.resourcesInProgress).join(', '))));
}

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

private findMetadataFor(logicalId: string | undefined): { entry: cxapi.MetadataEntry, path: string } | undefined {
if (!logicalId || !this.metadata) { return undefined; }
for (const path of Object.keys(this.metadata)) {
const entry = this.metadata[path].filter(e => e.type === 'aws:cdk:logicalId')
const metadata = this.stack.metadata;
if (!logicalId || !metadata) { return undefined; }
for (const path of Object.keys(metadata)) {
const entry = metadata[path].filter(e => e.type === 'aws:cdk:logicalId')
.find(e => e.data === logicalId);
if (entry) { return { entry, path }; }
}
Expand Down Expand Up @@ -284,3 +307,15 @@ function padRight(n: number, x: string): string {
function padLeft(n: number, x: string): string {
return ' '.repeat(Math.max(0, n - x.length)) + x;
}

function calcMaxResourceTypeLength(template: any) {
const resources = (template && template.Resources) || {};
let maxWidth = 0;
for (const id of Object.keys(resources)) {
const type = resources[id].Type || '';
if (type.length > maxWidth) {
maxWidth = type.length;
}
}
return maxWidth;
}
8 changes: 6 additions & 2 deletions packages/aws-cdk/lib/assets.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ASSET_METADATA, ASSET_PREFIX_SEPARATOR, AssetMetadataEntry, StackMetadata, SynthesizedStack } from '@aws-cdk/cx-api';
import { CloudFormation } from 'aws-sdk';
import colors = require('colors');
import fs = require('fs-extra');
import os = require('os');
import path = require('path');
Expand Down Expand Up @@ -78,11 +79,14 @@ async function prepareFileAsset(
contentType
});

const relativePath = path.relative(process.cwd(), asset.path);

const s3url = `s3://${toolkitInfo.bucketName}/${key}`;
debug(`S3 url for ${relativePath}: ${s3url}`);
if (changed) {
success(` 👑 Asset ${asset.path} (${asset.packaging}) uploaded: ${s3url}`);
success(`Updated: ${colors.bold(relativePath)} (${asset.packaging})`);
} else {
debug(` 👑 Asset ${asset.path} (${asset.packaging}) is up-to-date: ${s3url}`);
debug(`Up-to-date: ${colors.bold(relativePath)} (${asset.packaging})`);
}

return [
Expand Down