Skip to content

Commit

Permalink
Merge pull request #408 from garden-io/get-graph-command
Browse files Browse the repository at this point in the history
feat: added get graph command
  • Loading branch information
edvald authored Dec 4, 2018
2 parents d5371b7 + 010353e commit d46341d
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 7 deletions.
9 changes: 9 additions & 0 deletions docs/reference/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,15 @@ Examples:
| -------- | ----- | ---- | ----------- |
| `--interactive` | | boolean | Set to false to skip interactive mode and just output the command result

### garden get graph

Outputs the dependency relationships specified in this project's garden.yml files.


##### Usage

garden get graph

### garden get config

Outputs the fully resolved configuration for this project and environment.
Expand Down
5 changes: 5 additions & 0 deletions garden-service/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions garden-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"sywac": "^1.2.1",
"tar": "^4.4.6",
"terminal-link": "^1.1.0",
"toposort": "^2.0.2",
"ts-stream": "^1.0.1",
"typescript-memoize": "^1.0.0-alpha.3",
"uniqid": "^5.0.3",
Expand Down
40 changes: 40 additions & 0 deletions garden-service/src/commands/get/get-graph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (C) 2018 Garden Technologies, Inc. <[email protected]>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import * as yaml from "js-yaml"
import { RenderedEdge, RenderedNode } from "../../dependency-graph"
import { highlightYaml } from "../../util/util"
import {
Command,
CommandResult,
CommandParams,
} from "../base"

interface GraphOutput {
nodes: RenderedNode[],
relationships: RenderedEdge[],
}

export class GetGraphCommand extends Command {
name = "graph"
help = "Outputs the dependency relationships specified in this project's garden.yml files."

async action({ garden, log }: CommandParams): Promise<CommandResult<GraphOutput>> {
const dependencyGraph = await garden.getDependencyGraph()
const renderedGraph = dependencyGraph.render()
const output: GraphOutput = { nodes: renderedGraph.nodes, relationships: renderedGraph.relationships }

const yamlGraph = yaml.safeDump(renderedGraph, { noRefs: true, skipInvalid: true })

log.info(highlightYaml(yamlGraph))

return { result: output }

}

}
2 changes: 2 additions & 0 deletions garden-service/src/commands/get/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import { Command } from "../base"
import { GetGraphCommand } from "./get-graph"
import { GetConfigCommand } from "./get-config"
import { GetSecretCommand } from "./get-secret"
import { GetStatusCommand } from "./get-status"
Expand All @@ -16,6 +17,7 @@ export class GetCommand extends Command {
help = "Retrieve and output data and objects, e.g. secrets, status info etc."

subCommands = [
GetGraphCommand,
GetConfigCommand,
GetSecretCommand,
GetStatusCommand,
Expand Down
66 changes: 59 additions & 7 deletions garden-service/src/dependency-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import * as Bluebird from "bluebird"
const toposort = require("toposort")
import { flatten, fromPairs, pick, uniq } from "lodash"
import { Garden } from "./garden"
import { BuildDependencyConfig } from "./config/module"
Expand Down Expand Up @@ -37,6 +38,16 @@ type DependencyRelationNames = {

export type DependencyRelationFilterFn = (DependencyGraphNode) => boolean

// Output types for rendering/logging

export type RenderedGraph = { nodes: RenderedNode[], relationships: RenderedEdge[] }

export type RenderedEdge = { dependant: RenderedNode, dependency: RenderedNode }

export type RenderedNode = { type: RenderedNodeType, name: string }

export type RenderedNodeType = "build" | "deploy" | "runTask" | "test" | "push" | "publish"

/**
* A graph data structure that facilitates querying (recursive or non-recursive) of the project's dependency and
* dependant relationships.
Expand Down Expand Up @@ -312,20 +323,54 @@ export class DependencyGraph {
}
}

// For testing/debugging.
renderGraph() {
render(): RenderedGraph {
const nodes = Object.values(this.index)
const edges: string[][] = []
for (const node of nodes) {
for (const dep of node.dependencies) {
edges.push([nodeKey(node.type, node.name), nodeKey(dep.type, dep.name)])
let edges: { dependant: DependencyGraphNode, dependency: DependencyGraphNode }[] = []
let simpleEdges: string[][] = []
for (const dependant of nodes) {
for (const dependency of dependant.dependencies) {
edges.push({ dependant, dependency })
simpleEdges.push([
nodeKey(dependant.type, dependant.name),
nodeKey(dependency.type, dependency.name),
])
}
}
return edges

const sortedNodeKeys = toposort(simpleEdges)

const edgeSortIndex = (e) => {
return sortedNodeKeys.findIndex(k => k === nodeKey(e.dependency.type, e.dependency.name))
}
edges = edges.sort((e1, e2) => edgeSortIndex(e2) - edgeSortIndex(e1))
const renderedEdges = edges.map(e => ({
dependant: e.dependant.render(),
dependency: e.dependency.render(),
}))

const nodeSortIndex = (n) => {
return sortedNodeKeys.findIndex(k => k === nodeKey(n.type, n.name))
}
const renderedNodes = nodes.sort((n1, n2) => nodeSortIndex(n2) - nodeSortIndex(n1))
.map(n => n.render())

return {
relationships: renderedEdges,
nodes: renderedNodes,
}
}

}

const renderedNodeTypeMap = {
build: "build",
service: "deploy",
task: "runTask",
test: "test",
push: "push",
publish: "publish",
}

export class DependencyGraphNode {

type: DependencyGraphNodeType
Expand All @@ -342,6 +387,13 @@ export class DependencyGraphNode {
this.dependants = []
}

render(): RenderedNode {
return {
type: <RenderedNodeType>renderedNodeTypeMap[this.type],
name: this.name,
}
}

// Idempotent.
addDependency(node: DependencyGraphNode) {
const key = nodeKey(node.type, node.name)
Expand Down
25 changes: 25 additions & 0 deletions garden-service/test/src/commands/get/get-graph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { expect } from "chai"
import { dataDir, makeTestGarden } from "../../../helpers"
import { GetGraphCommand } from "../../../../src/commands/get/get-graph"
import { resolve } from "path"

describe("GetGraphCommand", () => {
const pluginName = "test-plugin"
const provider = pluginName

// TODO: Switch to a stable topological sorting algorithm that's more amenable to testing.
it("should get the project's serialized dependency graph", async () => {
const garden = await makeTestGarden(resolve(dataDir, "test-project-dependants"))
const log = garden.log
const command = new GetGraphCommand()

const res = await command.action({
garden,
log,
args: { provider },
opts: {},
})

expect(Object.keys(res.result!).sort()).to.eql(["nodes", "relationships"])
})
})

0 comments on commit d46341d

Please sign in to comment.