Skip to content

Commit

Permalink
Merge branch 'main' into docs/s3deployment-source-jsondata-name-fix
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] authored Oct 2, 2023
2 parents 440bcef + 26d4afe commit aefaf85
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 108 deletions.
10 changes: 5 additions & 5 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@ Shout out to our top contributors!
- [otaviomacedo](https://github.com/otaviomacedo)
- [madeline-k](https://github.com/madeline-k)
- [NetaNir](https://github.com/NetaNir)
- [robertd](https://github.com/robertd)
- [comcalvi](https://github.com/comcalvi)
- [robertd](https://github.com/robertd)
- [pahud](https://github.com/pahud)
- [peterwoodworth](https://github.com/peterwoodworth)
- [MrArnoldPalmer](https://github.com/MrArnoldPalmer)
- [mrgrain](https://github.com/mrgrain)
- [MrArnoldPalmer](https://github.com/MrArnoldPalmer)
- [peterwoodworth](https://github.com/peterwoodworth)
- [TheRealAmazonKendra](https://github.com/TheRealAmazonKendra)
- [nija-at](https://github.com/nija-at)
- [hoegertn](https://github.com/hoegertn)
- [nmussy](https://github.com/nmussy)
- [lpizzinidev](https://github.com/lpizzinidev)
- [jumic](https://github.com/jumic)
- [SoManyHs](https://github.com/SoManyHs)
- [watany-dev](https://github.com/watany-dev)


_Last updated: Fri, 01 Sep 23 00:09:05 +0000_
_Last updated: Sun, 01 Oct 23 00:09:32 +0000_
Original file line number Diff line number Diff line change
Expand Up @@ -221,14 +221,15 @@ export class StackActivityMonitor {
* see a next page and the last event in the page is new to us (and within the time window).
* haven't seen the final event
*/
private async readNewEvents(): Promise<void> {
private async readNewEvents(stackName?: string): Promise<void> {
const stackToPollForEvents = stackName ?? this.stackName;
const events: StackActivity[] = [];

const CFN_SUCCESS_STATUS = ['UPDATE_COMPLETE', 'CREATE_COMPLETE', 'DELETE_COMPLETE', 'DELETE_SKIPPED'];
try {
let nextToken: string | undefined;
let finished = false;
while (!finished) {
const response = await this.cfn.describeStackEvents({ StackName: this.stackName, NextToken: nextToken }).promise();
const response = await this.cfn.describeStackEvents({ StackName: stackToPollForEvents, NextToken: nextToken }).promise();
const eventPage = response?.StackEvents ?? [];

for (const event of eventPage) {
Expand All @@ -249,6 +250,13 @@ export class StackActivityMonitor {
event: event,
metadata: this.findMetadataFor(event.LogicalResourceId),
});

if (event.ResourceType === 'AWS::CloudFormation::Stack' && !CFN_SUCCESS_STATUS.includes(event.ResourceStatus ?? '')) {
// If the event is not for `this` stack, recursively call for events in the nested stack
if (event.PhysicalResourceId !== stackToPollForEvents) {
await this.readNewEvents(event.PhysicalResourceId);
}
}
}

// We're also done if there's nothing left to read
Expand All @@ -258,7 +266,7 @@ export class StackActivityMonitor {
}
}
} catch (e: any) {
if (e.code === 'ValidationError' && e.message === `Stack [${this.stackName}] does not exist`) {
if (e.code === 'ValidationError' && e.message === `Stack [${stackToPollForEvents}] does not exist`) {
return;
}
throw e;
Expand Down Expand Up @@ -475,7 +483,7 @@ abstract class ActivityPrinterBase implements IActivityPrinter {
this.resourcesPrevCompleteState[activity.event.LogicalResourceId] = status;
}

if (hookStatus!== undefined && hookStatus.endsWith('_COMPLETE_FAILED') && activity.event.LogicalResourceId !== undefined && hookType !== undefined) {
if (hookStatus !== undefined && hookStatus.endsWith('_COMPLETE_FAILED') && activity.event.LogicalResourceId !== undefined && hookType !== undefined) {

if (this.hookFailureMap.has(activity.event.LogicalResourceId)) {
this.hookFailureMap.get(activity.event.LogicalResourceId)?.set(hookType, activity.event.HookStatusReason ?? '');
Expand Down Expand Up @@ -803,4 +811,3 @@ function shorten(maxWidth: number, p: string) {

const TIMESTAMP_WIDTH = 12;
const STATUS_WIDTH = 20;

267 changes: 181 additions & 86 deletions packages/aws-cdk/test/util/stack-monitor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,95 +10,171 @@ beforeEach(() => {
printer = new FakePrinter();
});

test('continue to the next page if it exists', async () => {
await testMonitorWithEventCalls([
(request) => {
expect(request.NextToken).toBeUndefined();
return {
StackEvents: [event(102)],
NextToken: 'some-token',
};
},
(request) => {
expect(request.NextToken).toBe('some-token');
return {
StackEvents: [event(101)],
};
},
]);

// Printer sees them in chronological order
expect(printer.eventIds).toEqual(['101', '102']);
});
describe('stack monitor event ordering and pagination', () => {
test('continue to the next page if it exists', async () => {
await testMonitorWithEventCalls([
(request) => {
expect(request.NextToken).toBeUndefined();
return {
StackEvents: [event(102)],
NextToken: 'some-token',
};
},
(request) => {
expect(request.NextToken).toBe('some-token');
return {
StackEvents: [event(101)],
};
},
]);

test('do not page further if we already saw the last event', async () => {
await testMonitorWithEventCalls([
(request) => {
expect(request.NextToken).toBeUndefined();
return {
StackEvents: [event(101)],
};
},
(request) => {
expect(request.NextToken).toBeUndefined();
return {
StackEvents: [event(102), event(101)],
NextToken: 'some-token',
};
},
(request) => {
// Did not use the token
expect(request.NextToken).toBeUndefined();
return {};
},
]);

// Seen in chronological order
expect(printer.eventIds).toEqual(['101', '102']);
});
// Printer sees them in chronological order
expect(printer.eventIds).toEqual(['101', '102']);
});

test('do not page further if we already saw the last event', async () => {
await testMonitorWithEventCalls([
(request) => {
expect(request.NextToken).toBeUndefined();
return {
StackEvents: [event(101)],
};
},
(request) => {
expect(request.NextToken).toBeUndefined();
return {
StackEvents: [event(102), event(101)],
NextToken: 'some-token',
};
},
(request) => {
// Did not use the token
expect(request.NextToken).toBeUndefined();
return {};
},
]);

// Seen in chronological order
expect(printer.eventIds).toEqual(['101', '102']);
});

test('do not page further if the last event is too old', async () => {
await testMonitorWithEventCalls([
(request) => {
expect(request.NextToken).toBeUndefined();
return {
StackEvents: [event(101), event(95)],
NextToken: 'some-token',
};
},
(request) => {
// Start again from the top
expect(request.NextToken).toBeUndefined();
return {};
},
]);

// Seen only the new one
expect(printer.eventIds).toEqual(['101']);
});

test('do a final request after the monitor is stopped', async () => {
await testMonitorWithEventCalls([
// Before stop
(request) => {
expect(request.NextToken).toBeUndefined();
return {
StackEvents: [event(101)],
};
},
],
// After stop
[
(request) => {
expect(request.NextToken).toBeUndefined();
return {
StackEvents: [event(102), event(101)],
};
},
]);

test('do not page further if the last event is too old', async () => {
await testMonitorWithEventCalls([
(request) => {
expect(request.NextToken).toBeUndefined();
return {
StackEvents: [event(101), event(95)],
NextToken: 'some-token',
};
},
(request) => {
// Start again from the top
expect(request.NextToken).toBeUndefined();
return {};
},
]);

// Seen only the new one
expect(printer.eventIds).toEqual(['101']);
// Seen both
expect(printer.eventIds).toEqual(['101', '102']);
});
});

test('do a final request after the monitor is stopped', async () => {
await testMonitorWithEventCalls([
// Before stop
(request) => {
expect(request.NextToken).toBeUndefined();
return {
StackEvents: [event(101)],
};
},
],
// After stop
[
(request) => {
expect(request.NextToken).toBeUndefined();
return {
StackEvents: [event(102), event(101)],
};
},
]);

// Seen both
expect(printer.eventIds).toEqual(['101', '102']);
describe('stack monitor, collecting errors from events', () => {
test('return errors from the root stack', async () => {
const monitor = await testMonitorWithEventCalls([
(request) => {
expect(request.NextToken).toBeUndefined();
return {
StackEvents: [addErrorToStackEvent(event(100))],
};
},
]);

expect(monitor.errors).toStrictEqual(['Test Error']);
});

test('return errors from the nested stack', async () => {
const monitor = await testMonitorWithEventCalls([
(request) => {
expect(request.StackName).toStrictEqual('StackName');
return {
StackEvents: [
addErrorToStackEvent(
event(100), {
logicalResourceId: 'nestedStackLogicalResourceId',
physicalResourceId: 'nestedStackPhysicalResourceId',
resourceType: 'AWS::CloudFormation::Stack',
resourceStatusReason: 'nested stack failed',
},
),
],
};
},
(request) => {
expect(request.StackName).toStrictEqual('nestedStackPhysicalResourceId');
return {
StackEvents: [
addErrorToStackEvent(
event(101), {
logicalResourceId: 'nestedResource',
resourceType: 'Some::Nested::Resource',
resourceStatusReason: 'actual failure error message',
},
),
],
};
},
]);

expect(monitor.errors).toStrictEqual(['actual failure error message', 'nested stack failed']);
});

test('does not check for nested stacks that have already completed successfully', async () => {
const monitor = await testMonitorWithEventCalls([
(request) => {
expect(request.StackName).toStrictEqual('StackName');
return {
StackEvents: [
addErrorToStackEvent(
event(100), {
logicalResourceId: 'nestedStackLogicalResourceId',
physicalResourceId: 'nestedStackPhysicalResourceId',
resourceType: 'AWS::CloudFormation::Stack',
resourceStatusReason: 'nested stack status reason',
resourceStatus: 'CREATE_COMPLETE',
},
),
],
};
},
]);

expect(monitor.errors).toStrictEqual([]);
});
});

const T0 = 1597837230504;
Expand All @@ -115,10 +191,28 @@ function event(nr: number): AWS.CloudFormation.StackEvent {
};
}

function addErrorToStackEvent(
eventToUpdate: AWS.CloudFormation.StackEvent,
props: {
resourceStatus?: string,
resourceType?: string,
resourceStatusReason?: string,
logicalResourceId?: string,
physicalResourceId?: string,
} = {},
): AWS.CloudFormation.StackEvent {
eventToUpdate.ResourceStatus = props.resourceStatus ?? 'UPDATE_FAILED';
eventToUpdate.ResourceType = props.resourceType ?? 'Test::Resource::Type';
eventToUpdate.ResourceStatusReason = props.resourceStatusReason ?? 'Test Error';
eventToUpdate.LogicalResourceId = props.logicalResourceId ?? 'testLogicalId';
eventToUpdate.PhysicalResourceId = props.physicalResourceId ?? 'testPhysicalResourceId';
return eventToUpdate;
}

async function testMonitorWithEventCalls(
beforeStopInvocations: Array<(x: AWS.CloudFormation.DescribeStackEventsInput) => AWS.CloudFormation.DescribeStackEventsOutput>,
afterStopInvocations: Array<(x: AWS.CloudFormation.DescribeStackEventsInput) => AWS.CloudFormation.DescribeStackEventsOutput> = [],
) {
): Promise<StackActivityMonitor> {
let describeStackEvents = (jest.fn() as jest.Mock<AWS.CloudFormation.DescribeStackEventsOutput, [AWS.CloudFormation.DescribeStackEventsInput]>);

let finished = false;
Expand All @@ -144,6 +238,7 @@ async function testMonitorWithEventCalls(
const monitor = new StackActivityMonitor(sdk.cloudFormation(), 'StackName', printer, undefined, new Date(T100)).start();
await waitForCondition(() => finished);
await monitor.stop();
return monitor;
}

class FakePrinter implements IActivityPrinter {
Expand Down
4 changes: 2 additions & 2 deletions tools/@aws-cdk/spec2cdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
},
"license": "Apache-2.0",
"dependencies": {
"@aws-cdk/aws-service-spec": "^0.0.17",
"@aws-cdk/service-spec-types": "^0.0.17",
"@aws-cdk/aws-service-spec": "^0.0.18",
"@aws-cdk/service-spec-types": "^0.0.18",
"@cdklabs/tskb": "^0.0.1",
"@cdklabs/typewriter": "^0.0.2",
"camelcase": "^6",
Expand Down
Loading

0 comments on commit aefaf85

Please sign in to comment.