Skip to content

Commit

Permalink
Cdk support (ljacobsson#21)
Browse files Browse the repository at this point in the history
* cdk support

* cdk support
  • Loading branch information
ljacobsson authored Jan 31, 2021
1 parent a2d8372 commit 6801cc1
Show file tree
Hide file tree
Showing 17 changed files with 509 additions and 103 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# cfn-diagram
![Node.js CI](https://github.com/mhlabs/cfn-diagram/workflows/Node.js%20CI/badge.svg)

CLI tool to visualise CloudFormation templates as diagrams.
CLI tool to visualise CloudFormation/SAM/CDK templates as diagrams.

## Installation
`npm i -g @mhlabs/cfn-diagram`
Expand Down Expand Up @@ -39,6 +39,7 @@ cfn-dia draw.io -t template.yaml
* Navigate through a new differnet layouts
* Works for both JSON and YAML templates
* Filter on resource type and/or resource names
* Works with CloudFormation, SAM and CDK

### HTML
The HTML output uses [vis.js](https://github.com/visjs/vis-network) to generate an interactive diagram from your template.
Expand All @@ -49,6 +50,10 @@ The HTML output uses [vis.js](https://github.com/visjs/vis-network) to generate
```
cfn-dia html -t template.yaml
```
or, for CDK stacks, go to project directory (where cdk.json is located) and enter
```
cfn-dia html
```

### CI-mode
This functionality lives in its own CLI, [cfn-diagram-ci](https://raw.githubusercontent.com/mhlabs/cfn-diagram-ci). This is beacuse it requires headless Chromium to be installed which makes the package size very large
Expand Down
5 changes: 3 additions & 2 deletions commands/browse/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const program = require("commander");
const template = require("../../shared/template");
const template = require("../../shared/templateParser");
const Vis = require("../../graph/Vis");
const draw = require("../../graph/MxGenerator");
const AWS = require("aws-sdk");
Expand Down Expand Up @@ -52,7 +52,8 @@ program
template,
template.isJson,
cmd.outputPath,
false
false,
true
);
} else {
cmd.outputFile = cmd.outputFile || stack.stackName + ".drawio"
Expand Down
9 changes: 7 additions & 2 deletions commands/draw.io/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,20 @@ program
.alias("d")
.option(
"-t, --template-file [templateFile]",
"Path to template",
"template.yaml"
"Path to template or cdk.json file",
"template.yaml or cdk.json"
)
.option(
"-c, --ci-mode",
"Disable interactivity",
false
)
.option("-o, --output-file [outputFile]", "Output file", "template.drawio")
.option(
"-co, --cdk-output [outputPath]",
"CDK synth output path",
`cdk.out`
)
.description("Generates a draw.io diagram from a CloudFormation template")
.action(async (cmd) => {
await mxGenerator.generate(cmd);
Expand Down
11 changes: 8 additions & 3 deletions commands/html/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const program = require("commander");
const template = require("../../shared/template");
const template = require("../../shared/templateParser");
const path = require("path");
const tempDirectory = require("temp-dir");
const Vis = require("../../graph/Vis");
Expand All @@ -10,8 +10,8 @@ program
.alias("h")
.option(
"-t, --template-file [templateFile]",
"Path to template",
"template.yaml"
"Path to template or cdk.json file",
"template.yaml or cdk.json"
)
.option(
"-c, --ci-mode",
Expand All @@ -23,6 +23,11 @@ program
"Output file",
`${path.join(tempDirectory, "cfn-diagram")}`
)
.option(
"-co, --cdk-output [outputPath]",
"CDK synth output path",
`cdk.out`
)
.description("Generates a vis.js diagram from a CloudFormation template")
.action(async (cmd) => {
ciMode = cmd.ciMode;
Expand Down
98 changes: 69 additions & 29 deletions graph/MxGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@ const iconMap = require("../resources/IconMap");
const filterConfig = require("../resources/FilterConfig");
const fs = require("fs");
const inquirer = require("inquirer");
const templateCache = require("../shared/templateCache");

const prompt = inquirer.createPromptModule();
const templateHelper = require("../shared/template");
const templateHelper = require("../shared/templateParser");
const actionOption = {
FilterResourceTypes: "Filter resources by type",
FilterResourceName: "Filter resources by name",
EdgeLabels: "Edge labels: On",
};
const YAML = require("yaml-cfn");


global.window = dom.window;
global.document = window.document;
global.XMLSerializer = window.XMLSerializer;
Expand Down Expand Up @@ -47,7 +48,7 @@ function reset() {
locationCache = {};
}

function makeGraph(template) {
function makeGraph(template, prefix = "root") {
const layout = new mxgraph[currentLayout](graph, true, 500);
const resources = Object.keys(template.Resources);
layout.orientation = "west";
Expand All @@ -60,32 +61,38 @@ function makeGraph(template) {
graph.getModel().beginUpdate();
try {
for (const resource of resources) {
const type = template.Resources[resource].Type;
const resObj = template.Resources[resource];
const type = resObj.Type;
if (
((filterConfig.resourceTypesToInclude &&
filterConfig.resourceNamesToInclude) &&
(!filterConfig.resourceTypesToInclude.includes(type)) ||
!filterConfig.resourceNamesToInclude.includes(resource))
(filterConfig.resourceTypesToInclude &&
filterConfig.resourceNamesToInclude &&
!filterConfig.resourceTypesToInclude.includes(type)) ||
!filterConfig.resourceNamesToInclude.includes(resource)
) {
updateFilters(type, resource);
updateFilters(type, resource, prefix);
continue;
}
if (resObj.Template) {
makeGraph(resObj.Template, resource);
}

const dependencies = getDependencies(template, resource);
addVertices(resource, dependencies, type);
addVertices(resource, dependencies, type, prefix);
}

for (const sourceVertex of vertices) {
for (const dependencyNode of sourceVertex.dependencies) {
for (const dependency of dependencyNode.value) {
const targets = vertices.filter((p) => p.name === dependency);
const targets = vertices.filter(
(p) => p.name === prefix + "." + dependency.split(".").pop()
);
const targetVertex = targets[0];
if (!targetVertex) {
continue;
}
let from = sourceVertex.vertex;
let to = targetVertex.vertex;
addEdges(from, to, dependencyNode);
addEdges(from, to, dependencyNode, prefix);
}
}
}
Expand Down Expand Up @@ -143,10 +150,10 @@ function addEdges(from, to, dependencyNode) {
}
}

function addVertices(resource, dependencies, type) {
if (vertices.filter((p) => p.name === resource).length === 0) {
function addVertices(resource, dependencies, type, prefix) {
if (vertices.filter((p) => p.name === prefix + "." + resource).length === 0) {
vertices.push({
name: resource,
name: `${prefix}.${resource}`,
dependencies: dependencies,
type: type,
vertex: graph.insertVertex(
Expand All @@ -172,17 +179,27 @@ function getDependencies(template, resource) {
dependencies,
"Fn::GetAtt"
);

jsonUtil.findAllValues(
template.Resources[resource],
dependencies,
"Fn::ImportValue"
);

for (const dependency of dependencies) {
dependency.value = dependency.value.filter(
(p) =>
template.Resources[p] &&
filterConfig.resourceTypesToInclude.includes(template.Resources[p].Type)
);
dependency.value = dependency.value.filter((p) => {
const split = p.split(".");
if (split.length === 2) {
return templateCache.templates[split[0]].Resources[split[1]];
}
return template.Resources[p];
});
}

return dependencies;
}

function updateFilters(type, resource) {
function updateFilters(type, resource, prefix) {
const cells = graph.getModel().cells;
const keys = Object.keys(cells);
keys.map(
Expand All @@ -192,11 +209,11 @@ function updateFilters(type, resource) {
: null)
);
if (vertices.filter((p) => p.type === type).length) {
const item = vertices.filter((p) => p.name === resource)[0];
const item = vertices.filter((p) => p.name === `${prefix}.${resource}`)[0];
if (item) {
graph.removeCells([item.vertex], true);
}
vertices = vertices.filter((p) => p.name != resource);
vertices = vertices.filter((p) => p.name != `${prefix}.${resource}`);
}
}

Expand Down Expand Up @@ -225,11 +242,9 @@ async function generate(cmd, template) {
template = template || templateHelper.get(cmd).template;
jsonUtil.createPseudoResources(template);

const resources = [...Object.keys(template.Resources)].sort();
const resources = iterateResources(template);
let types = [];
for (const resource of resources) {
types.push(template.Resources[resource].Type);
}
addTypesToShow(Object.keys(template.Resources), types, template);
types = [...new Set(types)].sort();
let resourceTypes = { answer: types };
let resourceNames = { answer: resources };
Expand All @@ -245,7 +260,7 @@ async function generate(cmd, template) {
while (true) {
filterConfig.resourceNamesToInclude = resourceNames.answer;
filterConfig.resourceTypesToInclude = resourceTypes.answer;

filterConfig.edgeMode = edgeMode.answer;

const xml = renderTemplate(template);
Expand Down Expand Up @@ -295,10 +310,35 @@ async function generate(cmd, template) {
}
}

function iterateResources(template) {
const resources = [];
for (const resource of Object.keys(template.Resources)) {
resources.push(resource);
if (template.Resources[resource].Template) {
resources.push(
...iterateResources(template.Resources[resource].Template, resource)
);
}
}
return resources;
}

function addTypesToShow(resources, types, template) {
for (const resource of resources) {
types.push(template.Resources[resource].Type);
if (template.Resources[resource].Template) {
addTypesToShow(
Object.keys(template.Resources[resource].Template.Resources),
types,
template.Resources[resource].Template
);
}
}
}

module.exports = {
renderTemplate,
layouts,
reset,
generate
generate,
};
Loading

0 comments on commit 6801cc1

Please sign in to comment.