Skip to content

Commit 5003cad

Browse files
authored
fix(core): some trace info is missing from the validation report (#24889)
Fixes an issue where we were not getting trace information all the way up the construct tree. Also refactored some logic to make it clearer. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 7cbde5b commit 5003cad

File tree

3 files changed

+107
-40
lines changed

3 files changed

+107
-40
lines changed

packages/@aws-cdk/core/lib/private/tree-metadata.ts

+43-18
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,47 @@ export class TreeMetadata extends Construct {
8585
});
8686
}
8787

88+
/**
89+
* Each node will only have 1 level up (node.parent.parent will always be undefined)
90+
* so we need to reconstruct the node making sure the parents are set
91+
*/
92+
private getNodeWithParents(node: Node): Node {
93+
if (!this._tree) {
94+
throw new Error(`attempting to get node branch for ${node.path}, but the tree has not been created yet!`);
95+
}
96+
let tree = node;
97+
if (node.parent) {
98+
tree = {
99+
...node,
100+
parent: this.getNodeWithParents(this._tree[node.parent.path]),
101+
};
102+
}
103+
return tree;
104+
}
105+
106+
/**
107+
* Construct a new tree with only the nodes that we care about.
108+
* Normally each node can contain many child nodes, but we only care about the
109+
* tree that leads to a specific construct so drop any nodes not in that path
110+
*
111+
* @param node Node the current tree node
112+
* @param child Node the previous tree node and the current node's child node
113+
* @returns Node the new tree
114+
*/
115+
private renderTreeWithChildren(node: Node, child?: Node): Node {
116+
if (node.parent) {
117+
return this.renderTreeWithChildren(node.parent, node);
118+
} else if (child) {
119+
return {
120+
...node,
121+
children: {
122+
[child.id]: child,
123+
},
124+
};
125+
}
126+
return node;
127+
}
128+
88129
/**
89130
* This gets a specific "branch" of the tree for a given construct path.
90131
* It will return the root Node of the tree with non-relevant branches filtered
@@ -97,24 +138,8 @@ export class TreeMetadata extends Construct {
97138
throw new Error(`attempting to get node branch for ${constructPath}, but the tree has not been created yet!`);
98139
}
99140
const tree = this._tree[constructPath];
100-
const newTree: Node = {
101-
id: tree.id,
102-
path: tree.path,
103-
attributes: tree.attributes,
104-
constructInfo: tree.constructInfo,
105-
// need to re-add the parent because the current node
106-
// won't have the parent's parent
107-
parent: tree.parent ? this._tree[tree.parent.path] : undefined,
108-
};
109-
// need the properties to be mutable
110-
let branch = newTree as any;
111-
do {
112-
branch.parent.children = {
113-
[branch.id]: branch,
114-
};
115-
branch = branch.parent;
116-
} while (branch.parent);
117-
return branch;
141+
const treeWithParents = this.getNodeWithParents(tree);
142+
return this.renderTreeWithChildren(treeWithParents);
118143
}
119144

120145
private synthAttributes(construct: IConstruct): { [key: string]: any } | undefined {

packages/@aws-cdk/core/lib/validation/private/construct-tree.ts

+19-19
Original file line numberDiff line numberDiff line change
@@ -125,16 +125,28 @@ export class ConstructTree {
125125
} else {
126126
trace = construct.node.defaultChild?.node.metadata.find(meta => !!meta.trace)?.trace ?? [];
127127
}
128-
// the top item is never pointing to anything relevant
129-
trace.shift();
130128
// take just the items we need and reverse it since we are
131-
// displaying to trace bottom up
129+
// displaying the trace bottom up
132130
return Object.create(trace.slice(0, size));
133131
}
134132
}
135133
return [];
136134
}
137135

136+
/**
137+
* Only the `CfnResource` constructs contain the trace information
138+
* So we need to go down the tree and find that resource (its always the last one)
139+
*
140+
* @param node Node the entire tree where the bottom is the violating resource
141+
* @return Node the bottom of the tree which will be the violating resource
142+
*/
143+
private getNodeWithTrace(node: Node): Node {
144+
if (node.children) {
145+
return this.getNodeWithTrace(this.getChild(node.children));
146+
}
147+
return node;
148+
}
149+
138150
/**
139151
* Get a ConstructTrace from the cache for a given construct
140152
*
@@ -149,23 +161,11 @@ export class ConstructTree {
149161

150162
const size = this.nodeSize(node);
151163

152-
// the first time through the node will
153-
// be the root of the tree. We need to go
154-
// down the tree until we get to the bottom which
155-
// will be the resource with the violation and it
156-
// will contain the trace info
157-
let child = node;
158-
if (!locations) {
159-
do {
160-
if (child.children) {
161-
child = this.getChild(child.children);
162-
}
163-
} while (child.children);
164-
}
165-
const metadata = (locations ?? this.getTraceMetadata(size, child));
164+
const nodeWithTrace = this.getNodeWithTrace(node);
165+
const metadata = (locations ?? this.getTraceMetadata(size, nodeWithTrace));
166166
const thisLocation = metadata.pop();
167167

168-
let constructTrace: ConstructTrace = {
168+
const constructTrace: ConstructTrace = {
169169
id: node.id,
170170
path: node.path,
171171
// the "child" trace will be the "parent" node
@@ -190,7 +190,7 @@ export class ConstructTree {
190190
}
191191

192192
/**
193-
* Get the size of a Node
193+
* Get the size of a Node, i.e. how many levels is it
194194
*/
195195
private nodeSize(node: Node): number {
196196
let size = 1;

packages/@aws-cdk/core/test/validation/trace.test.ts

+45-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Construct } from 'constructs';
22
import * as core from '../../lib';
3+
import { Resource, IResource } from '../../lib';
34
import { ConstructTree } from '../../lib/validation/private/construct-tree';
45
import { ReportTrace } from '../../lib/validation/private/trace';
56

@@ -40,6 +41,20 @@ describe('ReportTrace', () => {
4041
libraryVersion: expect.any(String),
4142
location: expect.stringMatching(/new MyStack \(.*\/trace.test.ts:[0-9]+:[0-9]+\)/),
4243
path: 'MyStack/MyConstruct',
44+
child: {
45+
id: 'MyL2Resource',
46+
construct: expect.stringMatching(/Resource/),
47+
libraryVersion: expect.any(String),
48+
location: expect.stringMatching(/new MyConstruct \(.*\/trace.test.ts:[0-9]+:[0-9]+\)/),
49+
path: 'MyStack/MyConstruct/MyL2Resource',
50+
child: {
51+
id: 'Resource',
52+
construct: expect.stringMatching(/CfnResource/),
53+
libraryVersion: expect.any(String),
54+
location: expect.stringMatching(/new MyL2Resource \(.*\/trace.test.ts:[0-9]+:[0-9]+\)/),
55+
path: 'MyStack/MyConstruct/MyL2Resource/Resource',
56+
},
57+
},
4358
},
4459
});
4560
} finally {
@@ -73,20 +88,47 @@ describe('ReportTrace', () => {
7388
libraryVersion: expect.any(String),
7489
location: "Run with '--debug' to include location info",
7590
path: 'MyStack/MyConstruct',
91+
child: {
92+
id: 'MyL2Resource',
93+
construct: expect.stringMatching(/Resource/),
94+
libraryVersion: expect.any(String),
95+
location: "Run with '--debug' to include location info",
96+
path: 'MyStack/MyConstruct/MyL2Resource',
97+
child: {
98+
id: 'Resource',
99+
construct: expect.stringMatching(/CfnResource/),
100+
libraryVersion: expect.any(String),
101+
location: "Run with '--debug' to include location info",
102+
path: 'MyStack/MyConstruct/MyL2Resource/Resource',
103+
},
104+
},
76105
},
77106
});
78107
});
79108
});
80109

81-
class MyConstruct extends Construct {
110+
interface IMyL2Resource extends IResource {}
111+
112+
class MyL2Resource extends Resource implements IMyL2Resource {
113+
public readonly constructPath: string;
82114
constructor(scope: Construct, id: string) {
83115
super(scope, id);
84-
new core.CfnResource(this, 'Resource', {
116+
const resource = new core.CfnResource(this, 'Resource', {
85117
type: 'AWS::CDK::TestResource',
86118
properties: {
87119
testProp1: 'testValue',
88120
},
89121
});
122+
this.constructPath = resource.node.path;
123+
}
124+
}
125+
126+
class MyConstruct extends Construct {
127+
public readonly constructPath: string;
128+
constructor(scope: Construct, id: string) {
129+
super(scope, id);
130+
const myResource = new MyL2Resource(this, 'MyL2Resource');
131+
this.constructPath = myResource.constructPath;
90132
}
91133
}
92134

@@ -95,6 +137,6 @@ class MyStack extends core.Stack {
95137
constructor(scope: Construct, id: string, props?: core.StackProps) {
96138
super(scope, id, props);
97139
const myConstruct = new MyConstruct(this, 'MyConstruct');
98-
this.constructPath = myConstruct.node.path;
140+
this.constructPath = myConstruct.constructPath;
99141
}
100142
}

0 commit comments

Comments
 (0)