Skip to content

Commit

Permalink
fix(core): Add hook for applying aspects to new constructs created du…
Browse files Browse the repository at this point in the history
…ring and after aspects invocation

TODO upgrade constructs when required hooks feature is added
  • Loading branch information
rv2673 committed Nov 13, 2022
1 parent 280b876 commit 3eb9c1b
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 4 deletions.
13 changes: 12 additions & 1 deletion packages/@aws-cdk/core/lib/private/synthesis.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as cxapi from '@aws-cdk/cx-api';
import { IConstruct } from 'constructs';
import { Construct, IConstruct } from 'constructs';
import { Annotations } from '../annotations';
import { App } from '../app';
import { Aspects, IAspect } from '../aspect';
Expand Down Expand Up @@ -131,6 +131,17 @@ function invokeAspects(root: IConstruct) {
invoked.push(aspect);
}

if (construct instanceof Construct) {
// Add hook for future added children to also have aspects applied
construct.registerChildHook({
handle: (child: IConstruct) => {
if (!Stage.isStage(child)) {
recurse(child, allAspectsHere);
}
},
});
}
// Apply aspects to existing children
for (const child of construct.node.children) {
if (!Stage.isStage(child)) {
recurse(child, allAspectsHere);
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/core/lib/stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1404,6 +1404,7 @@ function count(xs: string[]): Record<string, number> {
}

// These imports have to be at the end to prevent circular imports
/* eslint-disable import/order */
import { CfnOutput } from './cfn-output';
import { addDependency } from './deps';
import { FileSystem } from './fs';
Expand All @@ -1418,4 +1419,3 @@ import { getExportable } from './private/refs';
import { Fact, RegionInfo } from '@aws-cdk/region-info';
import { deployTimeLookup } from './private/region-lookup';
import { makeUniqueResourceName } from './private/unique-resource-name';

24 changes: 24 additions & 0 deletions packages/@aws-cdk/core/test/aspect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,28 @@ describe('aspect', () => {
expect(root.node.metadata.length).toEqual(1);
expect(child.node.metadata.length).toEqual(1);
});


test('Aspects are applied to constructs added by aspects.', () => {
const app = new App();
const root = new MyConstruct(app, 'MyConstruct');
const child = new MyConstruct(root, 'ChildConstruct');
let child2: MyConstruct | undefined = undefined;
Aspects.of(root).add(new MyAspect());
Aspects.of(child).add({
visit(construct: IConstruct) {
if (construct === child) {
child2 = new MyConstruct(root, 'ChildAddedByAspect');
}
},
});
app.synth();
expect(child2).not.toBeUndefined();
expect(root.node.metadata[0].type).toEqual('foo');
expect(root.node.metadata[0].data).toEqual('bar');
expect(child.node.metadata[0].type).toEqual('foo');
expect(child.node.metadata[0].data).toEqual('bar');
expect(((child2 as any) as IConstruct).node.metadata[0].type).toEqual('foo');
expect(((child2 as any) as IConstruct).node.metadata[0].data).toEqual('bar');
});
});
106 changes: 104 additions & 2 deletions packages/@aws-cdk/core/test/cross-environment-token.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Construct } from 'constructs';
import { App, CfnOutput, CfnResource, PhysicalName, Resource, Stack } from '../lib';
import { Construct, IConstruct } from 'constructs';
import { App, Aspects, CfnOutput, CfnResource, IAspect, PhysicalName, Resource, Stack } from '../lib';
import { toCloudFormation } from './util';

/* eslint-disable quote-props */
Expand Down Expand Up @@ -250,6 +250,100 @@ describe('cross environment', () => {
});
});

test('crossRegionReferences created resources get stack aspects applied', () => {
// GIVEN
const app = new App();
const stack1 = new Stack(app, 'Stack1', {
env: {
account: '123456789012',
region: 'bermuda-triangle-1337',
},
crossRegionReferences: true,
});
const stack2 = new Stack(app, 'Stack2', {
env: {
account: '123456789012',
region: 'bermuda-triangle-42',
},
crossRegionReferences: true,
});

// WHEN
const myResource = new MyResource(stack1, 'MyResource');
new CfnOutput(stack2, 'Output', {
value: myResource.name,
});
const aspect = new MyAspect();
Aspects.of(stack1).add(aspect);
Aspects.of(stack2).add(aspect);

// THEN
const assembly = app.synth();
const template1 = assembly.getStackByName(stack1.stackName).template;
const template2 = assembly.getStackByName(stack2.stackName).template;

expect(template1?.Resources).toMatchObject({
'ExportsWriterbermudatriangle42E59594276156AC73': {
'DeletionPolicy': 'Delete',
'Properties': {
'WriterProps': {
'exports': {
'/cdk/exports/Stack2/Stack1bermudatriangle1337RefMyResource6073B41F66B72887': {
'Ref': 'MyResource6073B41F',
},
},
'region': 'bermuda-triangle-42',
},
'ServiceToken': {
'Fn::GetAtt': [
'CustomCrossRegionExportWriterCustomResourceProviderHandlerD8786E8A',
'Arn',
],
},
},
'Type': 'Custom::CrossRegionExportWriter',
'UpdateReplacePolicy': 'Delete',
},
});
expect(template2?.Outputs).toEqual({
'Output': {
'Value': {
'Fn::GetAtt': [
'ExportsReader8B249524',
'/cdk/exports/Stack2/Stack1bermudatriangle1337RefMyResource6073B41F66B72887',
],
},
},
});
expect(aspect.visitedNodes).toEqual(
[
'Stack1',
'Stack1/MyResource',
'Stack1/MyResource/Resource',
'Stack2',
'Stack2/Output',
'Stack1/ExportsWriterbermudatriangle42E5959427',
'Stack1/Custom::CrossRegionExportWriterCustomResourceProvider',
'Stack1/Custom::CrossRegionExportWriterCustomResourceProvider/Staging',
'Stack1/Custom::CrossRegionExportWriterCustomResourceProvider/Role',
'Stack1/Custom::CrossRegionExportWriterCustomResourceProvider/Handler',
'Stack1/ExportsWriterbermudatriangle42E5959427/Resource',
'Stack1/ExportsWriterbermudatriangle42E5959427/Resource/Default',
'Stack2/ExportsReader',
'Stack2/Custom::CrossRegionExportReaderCustomResourceProvider',
'Stack2/Custom::CrossRegionExportReaderCustomResourceProvider/Staging',
'Stack2/Custom::CrossRegionExportReaderCustomResourceProvider/Role',
'Stack2/Custom::CrossRegionExportReaderCustomResourceProvider/Handler',
'Stack2/ExportsReader/Resource',
'Stack2/ExportsReader/Resource/Default',
'Stack1/BootstrapVersion',
'Stack1/CheckBootstrapVersion',
'Stack2/BootstrapVersion',
'Stack2/CheckBootstrapVersion',
],
);
});

test('cannot reference a deploy-time physical name across regions, when crossRegionReferences=false', () => {
// GIVEN
const app = new App();
Expand Down Expand Up @@ -409,3 +503,11 @@ class MyResourceWithConstructedArnAttribute extends Resource {
});
}
}

class MyAspect implements IAspect {

public readonly visitedNodes: string[] = []
public visit(construct: IConstruct): void {
this.visitedNodes.push(construct.node.path);
}
}
2 changes: 2 additions & 0 deletions packages/@aws-cdk/core/test/stage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ describe('stage', () => {
expect(aspect.visits.map(c => c.node.path)).toEqual([
'MyStage/Stack',
'MyStage/Stack/Resource',
'MyStage/Stack/BootstrapVersion',
'MyStage/Stack/CheckBootstrapVersion',
]);
});

Expand Down

0 comments on commit 3eb9c1b

Please sign in to comment.