in packages/cdk-graph/src/core/compute.ts [52:324]
export function computeGraph(root: IConstruct): Graph.Store {
const store = new Graph.Store();
// List of nodes that should not be processed (extraneous nodes detected during compute)
// NB: example is stack template construct which cdk creates as sibling of nested stacks,
// we care about the stack and not the marshalled construct.
const nodesToIgnore: UUID[] = [];
// List of all unresolved referenced detected during compute, which are resolved after all nodes have been stored
const allUnresolvedReferences: SerializedGraph.SGUnresolvedReference[] = [];
// List of all unresolved dependencies detected during compute, which are resolved after all nodes have been stored
const allUnresolvedDependencies: UnresolvedDependency[] = [];
const visit = (construct: IConstruct, parent: Graph.Node): void => {
// Do not graph the CdkGraph itself
if (construct.node.id === GRAPH_ID) {
return;
}
// Do not graph the Tree construct (synthesizes tree.json)
if (Graph.AppNode.isAppNode(parent) && construct.node.id === "Tree") {
return;
}
// Do not graph stack CDKMetadata, BootstrapVersion, and similar constructs
if (
Graph.StackNode.isStackNode(parent) &&
IGNORED_STACK_CHILDREN.includes(construct.node.id)
) {
return;
}
const {
uuid,
attributes = {},
metadata = [],
tags = {},
logicalId,
cfnType,
constructInfo,
dependencies,
unresolvedReferences,
flags,
} = inferNodeProps(construct);
if (nodesToIgnore.includes(uuid)) {
return;
}
// Infer the stack this construct belongs to
let stack: Graph.StackNode | undefined;
try {
if (construct.node.scope) {
const stackUUID = getConstructUUID(Stack.of(construct));
stack = store.getStack(stackUUID);
}
} catch {
// ignore - expected to throw if construct is not contained in a stack
}
const nodeProps: Graph.ITypedNodeProps = {
store,
stack,
parent,
uuid,
id: construct.node.id,
path: construct.node.path,
attributes,
metadata,
tags,
constructInfo,
logicalId,
cfnType,
flags,
};
let node: Graph.Node;
switch (constructInfo?.fqn as ConstructInfoFqnEnum) {
case ConstructInfoFqnEnum.PDKAPP_MONO:
case ConstructInfoFqnEnum.PDKAPP:
case ConstructInfoFqnEnum.APP: {
node = new Graph.AppNode({
store,
parent,
attributes,
metadata,
tags,
constructInfo,
logicalId,
flags,
});
break;
}
case ConstructInfoFqnEnum.STAGE: {
node = new Graph.StageNode(nodeProps);
break;
}
case ConstructInfoFqnEnum.STACK: {
node = new Graph.StackNode(nodeProps);
break;
}
case ConstructInfoFqnEnum.NESTED_STACK: {
// NB: handle NestedStack<->CfnStack as single Node with NestedStack construct as source
// https://github.com/aws/aws-cdk/blob/119c92f65bf26c3fdf4bb818a4a4812022a3744a/packages/%40aws-cdk/core/lib/nested-stack.ts#L119
const parentStack = inferNodeProps(
(construct as NestedStack).nestedStackParent!
);
const _nestedStack = construct.node.scope!.node.findChild(
construct.node.id + ".NestedStack"
);
const cfnStackWrapper = inferNodeProps(_nestedStack);
const cfnStack = inferNodeProps(
_nestedStack.node.findChild(
construct.node.id + ".NestedStackResource"
) as CfnStack
);
// ignore parent scope cfn stack (template) constructs
nodesToIgnore.push(cfnStackWrapper.uuid, cfnStackWrapper.uuid);
node = new Graph.NestedStackNode({
...nodeProps,
logicalId: cfnStack.logicalId,
attributes: merge(
{},
attributes,
cfnStackWrapper.attributes,
cfnStack.attributes
),
metadata: [
...metadata,
...(cfnStackWrapper.metadata || []),
...(cfnStack.metadata || []),
],
parentStack: store.getStack(parentStack.uuid),
});
// Only add uniq dependencies as wrapper and stack may duplicate
dependencies.push(
...uniq([...cfnStackWrapper.dependencies, ...cfnStack.dependencies])
);
// Only add uniq references as wrapper and stack may duplicate
unresolvedReferences.push(
...uniqBy(
[
...cfnStackWrapper.unresolvedReferences.map((ref) => ({
...ref,
source: uuid,
})),
...cfnStack.unresolvedReferences.map((ref) => ({
...ref,
source: uuid,
})),
],
(v) =>
`${v.referenceType}::${v.source}::${v.target}::${JSON.stringify(
v.value || ""
)}`
)
);
break;
}
case ConstructInfoFqnEnum.CFN_STACK: {
// CfnStack always proceeds NestedStack, based on above case we merge CfnStack into
// NestedStack (mirror developer expectations) and then ignore CfnStack.
throw new Error(`CfnStack should be ignored by NestedStack: ${uuid}`);
}
case ConstructInfoFqnEnum.CFN_OUTPUT: {
node = new Graph.OutputNode({
...nodeProps,
value: Stack.of(construct).resolve((construct as CfnOutput).value),
description: Stack.of(construct).resolve(
(construct as CfnOutput).description
),
exportName: Stack.of(construct).resolve(
(construct as CfnOutput).exportName
),
});
// extract unresolved references from value
unresolvedReferences.push(
...extractUnresolvedReferences(
node.uuid,
(node as Graph.OutputNode).value || {}
)
);
break;
}
case ConstructInfoFqnEnum.CFN_PARAMETER: {
const cfnParameter = construct as CfnParameter;
node = new Graph.ParameterNode({
...nodeProps,
value: Stack.of(construct).resolve(cfnParameter.value),
description: Stack.of(construct).resolve(cfnParameter.description),
parameterType: cfnParameter.type,
});
break;
}
default: {
if (flags?.includes(FlagEnum.IMPORT)) {
node = new Graph.CfnResourceNode({
...nodeProps,
cfnType: inferImportCfnType(construct, constructInfo),
importArnToken: resolveImportedConstructArnToken(construct),
});
} else if (Resource.isResource(construct)) {
node = new Graph.ResourceNode({
...nodeProps,
cdkOwned: Resource.isOwnedResource(construct),
});
} else if (cfnType) {
node = new Graph.CfnResourceNode(nodeProps);
} else {
node = new Graph.Node({
nodeType: NodeTypeEnum.DEFAULT,
...nodeProps,
});
// Cdk Stack.Exports is proxy to actual Cfn exports and extraneous in the graph
if (
construct.node.id === CdkConstructIds.EXPORTS &&
Stack.isStack(construct.node.scope)
) {
node.addFlag(FlagEnum.EXTRANEOUS);
}
}
}
}
// Track unresolved dependencies, since nodes might not be created in the store yet
allUnresolvedDependencies.push(
...dependencies.map((dep) => [uuid, dep] as [string, string])
);
// Track all logicalId references in the nodes attributes
// this will get resolved after all nodes have been stored
allUnresolvedReferences.push(...unresolvedReferences);
// Visit all child to compute the tree
for (const child of construct.node.children) {
try {
visit(child, node);
} catch (e) {
Annotations.of(root).addWarning(
`Failed to render graph for node ${child.node.path}. Reason: ${e}`
);
throw e;
}
}
};
visit(root, store.root);
// Resolve all references - now that the tree is stored
for (const unresolved of allUnresolvedReferences) {
try {
resolveReference(store, unresolved);
} catch (e) {
IS_DEBUG && console.warn(e, unresolved);
// TODO: consider saving unresolved references if become valuable.
}
}
// Resolve all dependencies - now that the tree is stored
for (const unresolved of allUnresolvedDependencies) {
resolveDependency(store, unresolved);
}
return store;
}