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

fix(toolkit): 'cdk deploy' support updates to Outputs #2029

Merged
merged 2 commits into from
Mar 18, 2019
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
9 changes: 5 additions & 4 deletions packages/aws-cdk/lib/api/deploy-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { debug, error, print } from '../logging';
import { toYAML } from '../serialize';
import { Mode } from './aws-auth/credentials';
import { ToolkitInfo } from './toolkit-info';
import { describeStack, stackExists, stackFailedCreating, waitForChangeSet, waitForStack } from './util/cloudformation';
import { changeSetHasNoChanges, describeStack, stackExists, stackFailedCreating, waitForChangeSet, waitForStack } from './util/cloudformation';
import { StackActivityMonitor } from './util/cloudformation/stack-activity-monitor';
import { StackStatus } from './util/cloudformation/stack-status';
import { SDK } from './util/sdk';
Expand Down Expand Up @@ -77,16 +77,17 @@ export async function deployStack(options: DeployStackOptions): Promise<DeploySt
}).promise();
debug('Initiated creation of changeset: %s; waiting for it to finish creating...', changeSet.Id);
const changeSetDescription = await waitForChangeSet(cfn, deployName, changeSetName);
if (!changeSetDescription || !changeSetDescription.Changes || changeSetDescription.Changes.length === 0) {
debug('No changes are to be performed on %s, assuming success.', deployName);

if (changeSetHasNoChanges(changeSetDescription)) {
debug('No changes are to be performed on %s.', deployName);
await cfn.deleteChangeSet({ StackName: deployName, ChangeSetName: changeSetName }).promise();
return { noOp: true, outputs: await getStackOutputs(cfn, deployName), stackArn: changeSet.StackId! };
}

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, 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
35 changes: 28 additions & 7 deletions packages/aws-cdk/lib/api/util/cloudformation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,32 +86,53 @@ async function waitFor<T>(valueProvider: () => Promise<T | null | undefined>, ti
/**
* Waits for a ChangeSet to be available for triggering a StackUpdate.
*
* Will return a changeset that is either ready to be executed or has no changes.
* Will throw in other cases.
*
* @param cfn a CloudFormation client
* @param stackName the name of the Stack that the ChangeSet belongs to
* @param changeSetName the name of the ChangeSet
*
* @returns the CloudFormation description of the ChangeSet
*/
// tslint:disable-next-line:max-line-length
export async function waitForChangeSet(cfn: CloudFormation, stackName: string, changeSetName: string): Promise<CloudFormation.DescribeChangeSetOutput | undefined> {
export async function waitForChangeSet(cfn: CloudFormation, stackName: string, changeSetName: string): Promise<CloudFormation.DescribeChangeSetOutput> {
debug('Waiting for changeset %s on stack %s to finish creating...', changeSetName, stackName);
return waitFor(async () => {
const ret = await waitFor(async () => {
const description = await describeChangeSet(cfn, stackName, changeSetName);
// The following doesn't use a switch because tsc will not allow fall-through, UNLESS it is allows
// EVERYWHERE that uses this library directly or indirectly, which is undesirable.
if (description.Status === 'CREATE_PENDING' || description.Status === 'CREATE_IN_PROGRESS') {
debug('Changeset %s on stack %s is still creating', changeSetName, stackName);
return undefined;
} else if (description.Status === 'CREATE_COMPLETE') {
}

if (description.Status === 'CREATE_COMPLETE' || changeSetHasNoChanges(description)) {
return description;
} else if (description.Status === 'FAILED') {
if (description.StatusReason && description.StatusReason.startsWith('The submitted information didn\'t contain changes.')) {
return description;
}
}

// tslint:disable-next-line:max-line-length
throw new Error(`Failed to create ChangeSet ${changeSetName} on ${stackName}: ${description.Status || 'NO_STATUS'}, ${description.StatusReason || 'no reason provided'}`);
});

if (!ret) {
throw new Error('Change set took too long to be created; aborting');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really a failure mode here? Like... some kind of a timeout?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the question? Can this really happen in practice, can the code path be taken, ...?

}

return ret;
}

/**
* Return true if the given change set has no changes
*
* This must be determined from the status, not the 'Changes' array on the
* object; the latter can be empty because no resources were changed, but if
* there are changes to Outputs, the change set can still be executed.
*/
export function changeSetHasNoChanges(description: CloudFormation.DescribeChangeSetOutput) {
return description.Status === 'FAILED'
&& description.StatusReason
&& description.StatusReason.startsWith('The submitted information didn\'t contain changes.');
}

/**
Expand Down
Loading