diff --git a/.gitignore b/.gitignore index 44e3c7e75..72a053db9 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ terraform.tfstate bin/ config/y.go config/y.output +modules-dev/ pkg/ vendor/ website/.vagrant diff --git a/command/graph.go b/command/graph.go index f28f4a82e..f9011943d 100644 --- a/command/graph.go +++ b/command/graph.go @@ -16,9 +16,12 @@ type GraphCommand struct { } func (c *GraphCommand) Run(args []string) int { + var moduleDepth int + args = c.Meta.process(args, false) cmdFlags := flag.NewFlagSet("graph", flag.ContinueOnError) + cmdFlags.IntVar(&moduleDepth, "module-depth", 0, "module-depth") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 @@ -55,7 +58,11 @@ func (c *GraphCommand) Run(args []string) int { return 1 } - c.Ui.Output(terraform.GraphDot(g)) + opts := &terraform.GraphDotOpts{ + ModuleDepth: moduleDepth, + } + + c.Ui.Output(terraform.GraphDot(g, opts)) return 0 } @@ -73,6 +80,11 @@ Usage: terraform graph [options] PATH read this format is GraphViz, but many web services are also available to read this format. +Options: + + -module-depth=n The maximum depth to expand modules. By default this is + zero, which will not expand modules at all. + ` return strings.TrimSpace(helpText) } diff --git a/terraform/graph_dot.go b/terraform/graph_dot.go index 7b0d0d325..ffac4a381 100644 --- a/terraform/graph_dot.go +++ b/terraform/graph_dot.go @@ -2,17 +2,34 @@ package terraform import ( "bytes" + "bufio" "fmt" + "strings" "github.com/hashicorp/terraform/depgraph" "github.com/hashicorp/terraform/digraph" ) +// GraphDotOpts are options for turning a graph into dot format. +type GraphDotOpts struct { + // ModuleDepth is the depth of modules to expand. Zero is no expansion, + // one expands the first set of modules, etc. If this is set to -1, then + // all modules are expanded. + ModuleDepth int + + // Depth is an internal track of what depth we're at within + // the graph, used to control indentation and other such things. + depth int +} + // GraphDot returns the dot formatting of a visual representation of // the given Terraform graph. -func GraphDot(g *depgraph.Graph) string { +func GraphDot(g *depgraph.Graph, opts *GraphDotOpts) string { buf := new(bytes.Buffer) - buf.WriteString("digraph {\n") + + if opts.depth == 0 { + buf.WriteString("digraph {\n") + } // Determine and add the title // graphDotTitle(buf, g) @@ -23,7 +40,13 @@ func GraphDot(g *depgraph.Graph) string { // Add all the resource providers graphDotAddResourceProviders(buf, g) - buf.WriteString("}\n") + // Add all the modules + graphDotAddModules(buf, g, opts) + + if opts.depth == 0 { + buf.WriteString("}\n") + } + return buf.String() } @@ -39,6 +62,65 @@ func graphDotAddRoot(buf *bytes.Buffer, n *depgraph.Noun) { } } +func graphDotAddModules(buf *bytes.Buffer, g *depgraph.Graph, opts *GraphDotOpts) { + for _, n := range g.Nouns { + _, ok := n.Meta.(*GraphNodeModule) + if !ok { + continue + } + + if opts.ModuleDepth == opts.depth { + // We're not expanding, so just add the module on its own + graphDotAddModuleSingle(buf, n, opts) + } else { + // We're expanding + graphDotAddModuleExpand(buf, n, opts) + } + } +} + +func graphDotAddModuleExpand( + buf *bytes.Buffer, n *depgraph.Noun, opts *GraphDotOpts) { + m := n.Meta.(*GraphNodeModule) + tab := strings.Repeat("\t", opts.depth+1) + + // Wrap ourselves in a subgraph + buf.WriteString(fmt.Sprintf("%ssubgraph \"%s\" {\n", tab, n.Name)) + defer buf.WriteString(fmt.Sprintf("%s}\n", tab)) + + // Graph the subgraph just as we would any other graph + subOpts := *opts + subOpts.depth++ + subStr := GraphDot(m.Graph, &subOpts) + + // Tab all the lines of the subgraph + s := bufio.NewScanner(strings.NewReader(subStr)) + for s.Scan() { + buf.WriteString(fmt.Sprintf("%s%s\n", tab, s.Text())) + } +} + +func graphDotAddModuleSingle( + buf *bytes.Buffer, n *depgraph.Noun, opts *GraphDotOpts) { + tab := strings.Repeat("\t", opts.depth+1) + + //m := n.Meta.(*GraphNodeModule) + + // Create this node. + buf.WriteString(fmt.Sprintf("%s\"%s\" [\n", tab, n)) + buf.WriteString(fmt.Sprintf("%s\tshape=component\n", tab)) + buf.WriteString(fmt.Sprintf("%s];\n", tab)) + + for _, e := range n.Edges() { + target := e.Tail() + buf.WriteString(fmt.Sprintf( + "%s\"%s\" -> \"%s\";\n", + tab, + n, + target)) + } +} + func graphDotAddResources(buf *bytes.Buffer, g *depgraph.Graph) { // Determine if we have diffs. If we do, then we're graphing a // plan, which alters our graph a bit.