Skip to content

Commit fd306ee

Browse files
authored
fix: deploy monitor count is off if there are > 100 changes (#20067)
The deployment monitor failed to account for the fact the `DescribeChangeSet` operation returns a paginated output, and only represents up to 100 changes per pages. Since only the first page was considered, the progress bar would always start with a value of 101 (or less), instead of the correct count. This aggregates the `Changes` arrays from all pages of the change set description so the count is correct. Fixes #11805 ---- ### All Submissions: * [X] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent f0aff23 commit fd306ee

File tree

2 files changed

+45
-10
lines changed

2 files changed

+45
-10
lines changed

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

+5-2
Original file line numberDiff line numberDiff line change
@@ -329,8 +329,12 @@ async function prepareAndExecuteChangeSet(
329329
Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM', 'CAPABILITY_AUTO_EXPAND'],
330330
Tags: options.tags,
331331
}).promise();
332+
333+
const execute = options.execute ?? true;
334+
332335
debug('Initiated creation of changeset: %s; waiting for it to finish creating...', changeSet.Id);
333-
const changeSetDescription = await waitForChangeSet(cfn, deployName, changeSetName);
336+
// Fetching all pages if we'll execute, so we can have the correct change count when monitoring.
337+
const changeSetDescription = await waitForChangeSet(cfn, deployName, changeSetName, { fetchAll: execute });
334338

335339
// Update termination protection only if it has changed.
336340
const terminationProtection = stackArtifact.terminationProtection ?? false;
@@ -352,7 +356,6 @@ async function prepareAndExecuteChangeSet(
352356
return { noOp: true, outputs: cloudFormationStack.outputs, stackArn: changeSet.StackId! };
353357
}
354358

355-
const execute = options.execute === undefined ? true : options.execute;
356359
if (execute) {
357360
debug('Initiating execution of changeset %s on stack %s', changeSet.Id, deployName);
358361

packages/aws-cdk/lib/api/util/cloudformation.ts

+40-8
Original file line numberDiff line numberDiff line change
@@ -170,14 +170,40 @@ export class CloudFormationStack {
170170
/**
171171
* Describe a changeset in CloudFormation, regardless of its current state.
172172
*
173-
* @param cfn a CloudFormation client
174-
* @param stackName the name of the Stack the ChangeSet belongs to
173+
* @param cfn a CloudFormation client
174+
* @param stackName the name of the Stack the ChangeSet belongs to
175175
* @param changeSetName the name of the ChangeSet
176+
* @param fetchAll if true, fetches all pages of the change set description.
176177
*
177178
* @returns CloudFormation information about the ChangeSet
178179
*/
179-
async function describeChangeSet(cfn: CloudFormation, stackName: string, changeSetName: string): Promise<CloudFormation.DescribeChangeSetOutput> {
180+
async function describeChangeSet(
181+
cfn: CloudFormation,
182+
stackName: string,
183+
changeSetName: string,
184+
{ fetchAll }: { fetchAll: boolean },
185+
): Promise<CloudFormation.DescribeChangeSetOutput> {
180186
const response = await cfn.describeChangeSet({ StackName: stackName, ChangeSetName: changeSetName }).promise();
187+
188+
// If fetchAll is true, traverse all pages from the change set description.
189+
while (fetchAll && response.NextToken != null) {
190+
const nextPage = await cfn.describeChangeSet({
191+
StackName: stackName,
192+
ChangeSetName: response.ChangeSetId ?? changeSetName,
193+
NextToken: response.NextToken,
194+
}).promise();
195+
196+
// Consolidate the changes
197+
if (nextPage.Changes != null) {
198+
response.Changes = response.Changes != null
199+
? response.Changes.concat(nextPage.Changes)
200+
: nextPage.Changes;
201+
}
202+
203+
// Forward the new NextToken
204+
response.NextToken = nextPage.NextToken;
205+
}
206+
181207
return response;
182208
}
183209

@@ -207,17 +233,23 @@ async function waitFor<T>(valueProvider: () => Promise<T | null | undefined>, ti
207233
* Will return a changeset that is either ready to be executed or has no changes.
208234
* Will throw in other cases.
209235
*
210-
* @param cfn a CloudFormation client
211-
* @param stackName the name of the Stack that the ChangeSet belongs to
236+
* @param cfn a CloudFormation client
237+
* @param stackName the name of the Stack that the ChangeSet belongs to
212238
* @param changeSetName the name of the ChangeSet
239+
* @param fetchAll if true, fetches all pages of the ChangeSet before returning.
213240
*
214241
* @returns the CloudFormation description of the ChangeSet
215242
*/
216243
// eslint-disable-next-line max-len
217-
export async function waitForChangeSet(cfn: CloudFormation, stackName: string, changeSetName: string): Promise<CloudFormation.DescribeChangeSetOutput> {
244+
export async function waitForChangeSet(
245+
cfn: CloudFormation,
246+
stackName: string,
247+
changeSetName: string,
248+
{ fetchAll }: { fetchAll: boolean },
249+
): Promise<CloudFormation.DescribeChangeSetOutput> {
218250
debug('Waiting for changeset %s on stack %s to finish creating...', changeSetName, stackName);
219251
const ret = await waitFor(async () => {
220-
const description = await describeChangeSet(cfn, stackName, changeSetName);
252+
const description = await describeChangeSet(cfn, stackName, changeSetName, { fetchAll });
221253
// The following doesn't use a switch because tsc will not allow fall-through, UNLESS it is allows
222254
// EVERYWHERE that uses this library directly or indirectly, which is undesirable.
223255
if (description.Status === 'CREATE_PENDING' || description.Status === 'CREATE_IN_PROGRESS') {
@@ -453,4 +485,4 @@ export class ParameterValues {
453485
}
454486
}
455487

456-
export type ParameterChanges = boolean | 'ssm';
488+
export type ParameterChanges = boolean | 'ssm';

0 commit comments

Comments
 (0)