Skip to content

Commit

Permalink
chore(core): resolve tokens before publishing tree.json (#4984)
Browse files Browse the repository at this point in the history
* chore(core): resolve tokens before publishing tree.json

* try-catchall block

* Rename getAttributes() to synthAttributes()

* Continue rendering the tree on errors

* Fixed up a length bug + test
  • Loading branch information
nija-at authored and mergify[bot] committed Nov 13, 2019
1 parent c02c9e5 commit cf54c51
Show file tree
Hide file tree
Showing 2 changed files with 232 additions and 21 deletions.
25 changes: 18 additions & 7 deletions packages/@aws-cdk/core/lib/private/tree-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import path = require('path');

import { ArtifactType } from '@aws-cdk/cx-api';
import { Construct, IConstruct, ISynthesisSession } from '../construct';
import { IInspectable, TreeInspector } from "../tree";
import { Stack } from '../stack';
import { IInspectable, TreeInspector } from '../tree';

const FILE_PATH = 'tree.json';

Expand All @@ -23,13 +24,23 @@ export class TreeMetadata extends Construct {
const lookup: { [path: string]: Node } = { };

const visit = (construct: IConstruct): Node => {
const children = construct.node.children.map(visit);
const childrenMap = children.reduce((map, child) => Object.assign(map, { [child.id]: child }), {});
const children = construct.node.children.map((c) => {
try {
return visit(c);
} catch (e) {
this.node.addWarning(`Failed to render tree metadata for node [${c.node.id}]. Reason: ${e}`);
return undefined;
}
});
const childrenMap = children
.filter((child) => child !== undefined)
.reduce((map, child) => Object.assign(map, { [child!.id]: child }), {});

const node: Node = {
id: construct.node.id || 'App',
path: construct.node.path,
children: children.length === 0 ? undefined : childrenMap,
attributes: this.getAttributes(construct)
children: Object.keys(childrenMap).length === 0 ? undefined : childrenMap,
attributes: this.synthAttributes(construct)
};

lookup[node.path] = node;
Expand All @@ -53,7 +64,7 @@ export class TreeMetadata extends Construct {
});
}

private getAttributes(construct: IConstruct): { [key: string]: any } | undefined {
private synthAttributes(construct: IConstruct): { [key: string]: any } | undefined {
// check if a construct implements IInspectable
function canInspect(inspectable: any): inspectable is IInspectable {
return inspectable.inspect !== undefined;
Expand All @@ -64,7 +75,7 @@ export class TreeMetadata extends Construct {
// get attributes from the inspector
if (canInspect(construct)) {
construct.inspect(inspector);
return inspector.attributes;
return Stack.of(construct).resolve(inspector.attributes);
}
return undefined;
}
Expand Down
228 changes: 214 additions & 14 deletions packages/@aws-cdk/core/test/private/test.tree-metadata.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
import cxapi = require('@aws-cdk/cx-api');
import fs = require('fs');
import { Test } from 'nodeunit';
import path = require('path');
import { App, CfnResource, Construct, Stack, TreeInspector } from '../../lib/index';
import { App, CfnParameter, CfnResource, Construct, Lazy, Stack, TreeInspector } from '../../lib/index';

abstract class AbstractCfnResource extends CfnResource {
constructor(scope: Construct, id: string) {
super(scope, id, {
type: 'CDK::UnitTest::MyCfnResource'
});
}

public inspect(inspector: TreeInspector) {
inspector.addAttribute('aws:cdk:cloudformation:type', 'CDK::UnitTest::MyCfnResource');
inspector.addAttribute('aws:cdk:cloudformation:props', this.cfnProperties);
}

protected abstract get cfnProperties(): { [key: string]: any };
}

export = {
'tree metadata is generated as expected'(test: Test) {
Expand Down Expand Up @@ -41,18 +57,7 @@ export = {
},

'tree metadata for a Cfn resource'(test: Test) {
class MyCfnResource extends CfnResource {
constructor(scope: Construct, id: string) {
super(scope, id, {
type: 'CDK::UnitTest::MyCfnResource'
});
}

public inspect(inspector: TreeInspector) {
inspector.addAttribute('aws:cdk:cloudformation:type', 'CDK::UnitTest::MyCfnResource');
inspector.addAttribute('aws:cdk:cloudformation:props', this.cfnProperties);
}

class MyCfnResource extends AbstractCfnResource {
protected get cfnProperties(): { [key: string]: any } {
return {
mystringpropkey: 'mystringpropval',
Expand Down Expand Up @@ -108,7 +113,202 @@ export = {
}
});
test.done();
}
},

'token resolution & cfn parameter'(test: Test) {
const app = new App();
const stack = new Stack(app, 'mystack');
const cfnparam = new CfnParameter(stack, 'mycfnparam');

class MyCfnResource extends AbstractCfnResource {
protected get cfnProperties(): { [key: string]: any } {
return {
lazykey: Lazy.stringValue({ produce: () => 'LazyResolved!' }),
cfnparamkey: cfnparam
};
}
}

new MyCfnResource(stack, 'mycfnresource');

const assembly = app.synth();
const treeArtifact = assembly.tree();
test.ok(treeArtifact);

test.deepEqual(readJson(assembly.directory, treeArtifact!.file), {
version: 'tree-0.1',
tree: {
id: 'App',
path: '',
children: {
Tree: {
id: 'Tree',
path: 'Tree'
},
mystack: {
id: 'mystack',
path: 'mystack',
children: {
mycfnparam: {
id: 'mycfnparam',
path: 'mystack/mycfnparam'
},
mycfnresource: {
id: 'mycfnresource',
path: 'mystack/mycfnresource',
attributes: {
'aws:cdk:cloudformation:type': 'CDK::UnitTest::MyCfnResource',
'aws:cdk:cloudformation:props': {
lazykey: 'LazyResolved!',
cfnparamkey: { Ref: 'mycfnparam' }
}
}
}
}
}
}
}
});
test.done();
},

'cross-stack tokens'(test: Test) {
class MyFirstResource extends AbstractCfnResource {
public readonly lazykey: string;

constructor(scope: Construct, id: string) {
super(scope, id);
this.lazykey = Lazy.stringValue({ produce: () => 'LazyResolved!' });
}

protected get cfnProperties(): { [key: string]: any } {
return {
lazykey: this.lazykey
};
}
}

class MySecondResource extends AbstractCfnResource {
public readonly myprop: string;

constructor(scope: Construct, id: string, myprop: string) {
super(scope, id);
this.myprop = myprop;
}

protected get cfnProperties(): { [key: string]: any } {
return {
myprop: this.myprop
};
}
}

const app = new App();
const firststack = new Stack(app, 'myfirststack');
const firstres = new MyFirstResource(firststack, 'myfirstresource');
const secondstack = new Stack(app, 'mysecondstack');
new MySecondResource(secondstack, 'mysecondresource', firstres.lazykey);

const assembly = app.synth();
const treeArtifact = assembly.tree();
test.ok(treeArtifact);

test.deepEqual(readJson(assembly.directory, treeArtifact!.file), {
version: 'tree-0.1',
tree: {
id: 'App',
path: '',
children: {
Tree: {
id: 'Tree',
path: 'Tree'
},
myfirststack: {
id: 'myfirststack',
path: 'myfirststack',
children: {
myfirstresource: {
id: 'myfirstresource',
path: 'myfirststack/myfirstresource',
attributes: {
'aws:cdk:cloudformation:type': 'CDK::UnitTest::MyCfnResource',
'aws:cdk:cloudformation:props': {
lazykey: 'LazyResolved!'
}
}
}
}
},
mysecondstack: {
id: 'mysecondstack',
path: 'mysecondstack',
children: {
mysecondresource: {
id: 'mysecondresource',
path: 'mysecondstack/mysecondresource',
attributes: {
'aws:cdk:cloudformation:type': 'CDK::UnitTest::MyCfnResource',
'aws:cdk:cloudformation:props': {
myprop: 'LazyResolved!'
}
}
}
}
}
}
}
});

test.done();
},

'failing nodes'(test: Test) {
class MyCfnResource extends CfnResource {
public inspect(_: TreeInspector) {
throw new Error('Forcing an inspect error');
}
}

const app = new App();
const stack = new Stack(app, 'mystack');
new MyCfnResource(stack, 'mycfnresource', {
type: 'CDK::UnitTest::MyCfnResource'
});

const assembly = app.synth();
const treeArtifact = assembly.tree();
test.ok(treeArtifact);

const treenode = app.node.findChild('Tree');

const warn = treenode.node.metadata.find((md) => {
return md.type === cxapi.WARNING_METADATA_KEY
&& /Forcing an inspect error/.test(md.data as string)
&& /mycfnresource/.test(md.data as string);
});
test.ok(warn);

// assert that the rest of the construct tree is rendered
test.deepEqual(readJson(assembly.directory, treeArtifact!.file), {
version: 'tree-0.1',
tree: {
id: 'App',
path: '',
children: {
Tree: {
id: 'Tree',
path: 'Tree'
},
mystack: {
id: 'mystack',
path: 'mystack'
}
}
}
});

test.done();
},
};

function readJson(outdir: string, file: string) {
Expand Down

0 comments on commit cf54c51

Please sign in to comment.