Skip to content

Commit 9d4b66a

Browse files
authored
fix(cli): cdk deploy output hook failure reason if cloudformation failed by hook (#24444)
new feature for issue: #21674 Reopen this PR, #21675 it's approved before, but nobody helps to proceed. ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/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/main/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 1a2bd6d commit 9d4b66a

File tree

2 files changed

+88
-3
lines changed

2 files changed

+88
-3
lines changed

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

+43-3
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,9 @@ abstract class ActivityPrinterBase implements IActivityPrinter {
398398

399399
protected readonly failures = new Array<StackActivity>();
400400

401+
402+
protected hookFailureMap = new Map<string, Map<string, string>>();
403+
401404
protected readonly stream: NodeJS.WriteStream;
402405

403406
constructor(protected readonly props: PrinterProps) {
@@ -411,8 +414,25 @@ abstract class ActivityPrinterBase implements IActivityPrinter {
411414
this.stream = props.stream;
412415
}
413416

417+
public failureReason(activity: StackActivity) {
418+
const resourceStatusReason = activity.event.ResourceStatusReason ?? '';
419+
const logicalResourceId = activity.event.LogicalResourceId ?? '';
420+
const hookFailureReasonMap = this.hookFailureMap.get(logicalResourceId);
421+
422+
if (hookFailureReasonMap !== undefined) {
423+
for (const hookType of hookFailureReasonMap.keys()) {
424+
if (resourceStatusReason.includes(hookType)) {
425+
return resourceStatusReason + ' : ' + hookFailureReasonMap.get(hookType);
426+
}
427+
}
428+
}
429+
return resourceStatusReason;
430+
}
431+
414432
public addActivity(activity: StackActivity) {
415433
const status = activity.event.ResourceStatus;
434+
const hookStatus = activity.event.HookStatus;
435+
const hookType = activity.event.HookType;
416436
if (!status || !activity.event.LogicalResourceId) { return; }
417437

418438
if (status === 'ROLLBACK_IN_PROGRESS' || status === 'UPDATE_ROLLBACK_IN_PROGRESS') {
@@ -455,6 +475,16 @@ abstract class ActivityPrinterBase implements IActivityPrinter {
455475
}
456476
this.resourcesPrevCompleteState[activity.event.LogicalResourceId] = status;
457477
}
478+
479+
if (hookStatus!== undefined && hookStatus.endsWith('_COMPLETE_FAILED') && activity.event.LogicalResourceId !== undefined && hookType !== undefined) {
480+
481+
if (this.hookFailureMap.has(activity.event.LogicalResourceId)) {
482+
this.hookFailureMap.get(activity.event.LogicalResourceId)?.set(hookType, activity.event.HookStatusReason ?? '');
483+
} else {
484+
this.hookFailureMap.set(activity.event.LogicalResourceId, new Map<string, string>());
485+
this.hookFailureMap.get(activity.event.LogicalResourceId)?.set(hookType, activity.event.HookStatusReason ?? '');
486+
}
487+
}
458488
}
459489

460490
public abstract print(): void;
@@ -529,8 +559,15 @@ export class HistoryActivityPrinter extends ActivityPrinterBase {
529559

530560
let stackTrace = '';
531561
const md = activity.metadata;
532-
if (md && e.ResourceStatus && e.ResourceStatus.indexOf('FAILED') !== -1) {
533-
stackTrace = md.entry.trace ? `\n\t${md.entry.trace.join('\n\t\\_ ')}` : '';
562+
563+
if (e.ResourceStatus && e.ResourceStatus.indexOf('FAILED') !== -1) {
564+
if (progress == undefined || progress) {
565+
e.ResourceStatusReason = e.ResourceStatusReason ? this.failureReason(activity) : '';
566+
}
567+
if (md) {
568+
stackTrace = md.entry.trace ? `\n\t${md.entry.trace.join('\n\t\\_ ')}` : '';
569+
570+
}
534571
reasonColor = chalk.red;
535572
}
536573

@@ -703,7 +740,7 @@ export class CurrentActivityPrinter extends ActivityPrinterBase {
703740

704741
private failureReasonOnNextLine(activity: StackActivity) {
705742
return hasErrorMessage(activity.event.ResourceStatus ?? '')
706-
? `\n${' '.repeat(TIMESTAMP_WIDTH + STATUS_WIDTH + 6)}${chalk.red(activity.event.ResourceStatusReason ?? '')}`
743+
? `\n${' '.repeat(TIMESTAMP_WIDTH + STATUS_WIDTH + 6)}${chalk.red(this.failureReason(activity) ?? '')}`
707744
: '';
708745
}
709746
}
@@ -718,6 +755,7 @@ function hasErrorMessage(status: string) {
718755
return status.endsWith('_FAILED') || status === 'ROLLBACK_IN_PROGRESS' || status === 'UPDATE_ROLLBACK_IN_PROGRESS';
719756
}
720757

758+
721759
function colorFromStatusResult(status?: string) {
722760
if (!status) {
723761
return chalk.reset;
@@ -767,3 +805,5 @@ function shorten(maxWidth: number, p: string) {
767805

768806
const TIMESTAMP_WIDTH = 12;
769807
const STATUS_WIDTH = 20;
808+
809+

packages/aws-cdk/test/api/stack-activity-monitor.test.ts

+45
Original file line numberDiff line numberDiff line change
@@ -222,3 +222,48 @@ test('prints "Failed Resources:" list, when at least one deployment fails', () =
222222
expect(output[2].trim()).toStrictEqual('Failed resources:');
223223
expect(output[3].trim()).toStrictEqual(`stack-name | ${HUMAN_TIME} | ${red('UPDATE_FAILED ')} | AWS::CloudFormation::Stack | ${red(bold('stack1'))}`);
224224
});
225+
226+
test('print failed resources because of hook failures', () => {
227+
const historyActivityPrinter = new HistoryActivityPrinter({
228+
resourceTypeColumnWidth: 23,
229+
resourcesTotal: 1,
230+
stream: process.stderr,
231+
});
232+
233+
const output = stderr.inspectSync(() => {
234+
historyActivityPrinter.addActivity({
235+
event: {
236+
LogicalResourceId: 'stack1',
237+
ResourceStatus: 'IN_PROGRESS',
238+
Timestamp: new Date(TIMESTAMP),
239+
ResourceType: 'AWS::CloudFormation::Stack',
240+
StackId: '',
241+
EventId: '',
242+
StackName: 'stack-name',
243+
HookStatus: 'HOOK_COMPLETE_FAILED',
244+
HookType: 'hook1',
245+
HookStatusReason: 'stack1 must obey certain rules',
246+
},
247+
});
248+
historyActivityPrinter.addActivity({
249+
event: {
250+
LogicalResourceId: 'stack1',
251+
ResourceStatus: 'UPDATE_FAILED',
252+
Timestamp: new Date(TIMESTAMP),
253+
ResourceType: 'AWS::CloudFormation::Stack',
254+
StackId: '',
255+
EventId: '',
256+
StackName: 'stack-name',
257+
ResourceStatusReason: 'The following hook(s) failed: hook1',
258+
},
259+
});
260+
historyActivityPrinter.stop();
261+
});
262+
263+
expect(output.length).toStrictEqual(4);
264+
expect(output[0].trim()).toStrictEqual(`stack-name | 0/2 | ${HUMAN_TIME} | ${reset('IN_PROGRESS ')} | AWS::CloudFormation::Stack | ${reset(bold('stack1'))}`);
265+
expect(output[1].trim()).toStrictEqual(`stack-name | 0/2 | ${HUMAN_TIME} | ${red('UPDATE_FAILED ')} | AWS::CloudFormation::Stack | ${red(bold('stack1 The following hook(s) failed: hook1 : stack1 must obey certain rules'))}`);
266+
expect(output[2].trim()).toStrictEqual('Failed resources:');
267+
expect(output[3].trim()).toStrictEqual(`stack-name | ${HUMAN_TIME} | ${red('UPDATE_FAILED ')} | AWS::CloudFormation::Stack | ${red(bold('stack1 The following hook(s) failed: hook1 : stack1 must obey certain rules'))}`);
268+
});
269+

0 commit comments

Comments
 (0)