diff --git a/command/graph.go b/command/graph.go index f0c4bcf3f..42e87da49 100644 --- a/command/graph.go +++ b/command/graph.go @@ -1,13 +1,12 @@ package command import ( - "bytes" "flag" "fmt" "os" "strings" - "github.com/hashicorp/terraform/digraph" + "github.com/hashicorp/terraform/terraform" ) // GraphCommand is a Command implementation that takes a Terraform @@ -53,14 +52,7 @@ func (c *GraphCommand) Run(args []string) int { return 1 } - buf := new(bytes.Buffer) - nodes := make([]digraph.Node, len(g.Nouns)) - for i, n := range g.Nouns { - nodes[i] = n - } - digraph.GenerateDot(nodes, buf) - - c.Ui.Output(buf.String()) + c.Ui.Output(terraform.GraphDot(g)) return 0 } diff --git a/terraform/graph_dot.go b/terraform/graph_dot.go new file mode 100644 index 000000000..66057dd7d --- /dev/null +++ b/terraform/graph_dot.go @@ -0,0 +1,200 @@ +package terraform + +import ( + "bytes" + "fmt" + + "github.com/hashicorp/terraform/depgraph" +) + +// GraphDot returns the dot formatting of a visual representation of +// the given Terraform graph. +func GraphDot(g *depgraph.Graph) string { + buf := new(bytes.Buffer) + buf.WriteString("digraph {\n") + + // Determine and add the title + // graphDotTitle(buf, g) + + // Add all the resource. + graphDotAddResources(buf, g) + + // Add all the resource providers + graphDotAddResourceProviders(buf, g) + + buf.WriteString("}\n") + return buf.String() +} + +func graphDotAddRoot(buf *bytes.Buffer, n *depgraph.Noun) { + buf.WriteString(fmt.Sprintf("\t\"%s\" [shape=circle];\n", "root")) + + for _, e := range n.Edges() { + target := e.Tail() + buf.WriteString(fmt.Sprintf( + "\t\"%s\" -> \"%s\";\n", + "root", + 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. + hasDiff := false + for _, n := range g.Nouns { + rn, ok := n.Meta.(*GraphNodeResource) + if !ok { + continue + } + if rn.Resource.Diff != nil && !rn.Resource.Diff.Empty() { + hasDiff = true + break + } + } + + var edgeBuf bytes.Buffer + // Do all the non-destroy resources + buf.WriteString("\tsubgraph {\n") + for _, n := range g.Nouns { + rn, ok := n.Meta.(*GraphNodeResource) + if !ok { + continue + } + if rn.Resource.Diff != nil && rn.Resource.Diff.Destroy { + continue + } + + // If we have diffs then we're graphing a plan. If we don't have + // have a diff on this resource, don't graph anything, since the + // plan wouldn't do anything to this resource. + if hasDiff { + if rn.Resource.Diff == nil || rn.Resource.Diff.Empty() { + continue + } + } + + // Determine the colors. White = no change, yellow = change, + // green = create. Destroy is in the next section. + var color, fillColor string + if rn.Resource.Diff != nil && !rn.Resource.Diff.Empty() { + if rn.Resource.State != nil && rn.Resource.State.ID != "" { + color = "#FFFF00" + fillColor = "#FFFF94" + } else { + color = "#00FF00" + fillColor = "#9EFF9E" + } + } + + // Create this node. + buf.WriteString(fmt.Sprintf("\t\t\"%s\" [\n", n)) + buf.WriteString("\t\t\tshape=box\n") + if color != "" { + buf.WriteString("\t\t\tstyle=filled\n") + buf.WriteString(fmt.Sprintf("\t\t\tcolor=\"%s\"\n", color)) + buf.WriteString(fmt.Sprintf("\t\t\tfillcolor=\"%s\"\n", fillColor)) + } + buf.WriteString("\t\t];\n") + + // Build up all the edges in a separate buffer so they're not in the + // subgraph. + for _, e := range n.Edges() { + target := e.Tail() + edgeBuf.WriteString(fmt.Sprintf( + "\t\"%s\" -> \"%s\";\n", + n, + target)) + } + } + buf.WriteString("\t}\n\n") + if edgeBuf.Len() > 0 { + buf.WriteString(edgeBuf.String()) + buf.WriteString("\n") + } + + // Do all the destroy resources + edgeBuf.Reset() + buf.WriteString("\tsubgraph {\n") + for _, n := range g.Nouns { + rn, ok := n.Meta.(*GraphNodeResource) + if !ok { + continue + } + if rn.Resource.Diff == nil || !rn.Resource.Diff.Destroy { + continue + } + + buf.WriteString(fmt.Sprintf( + "\t\t\"%s\" [shape=box,style=filled,color=\"#FF0000\",fillcolor=\"#FF9494\"];\n", n)) + + for _, e := range n.Edges() { + target := e.Tail() + edgeBuf.WriteString(fmt.Sprintf( + "\t\"%s\" -> \"%s\";\n", + n, + target)) + } + } + buf.WriteString("\t}\n\n") + if edgeBuf.Len() > 0 { + buf.WriteString(edgeBuf.String()) + buf.WriteString("\n") + } +} + +func graphDotAddResourceProviders(buf *bytes.Buffer, g *depgraph.Graph) { + var edgeBuf bytes.Buffer + buf.WriteString("\tsubgraph {\n") + for _, n := range g.Nouns { + _, ok := n.Meta.(*GraphNodeResourceProvider) + if !ok { + continue + } + + // Create this node. + buf.WriteString(fmt.Sprintf("\t\t\"%s\" [\n", n)) + buf.WriteString("\t\t\tshape=diamond\n") + buf.WriteString("\t\t];\n") + + // Build up all the edges in a separate buffer so they're not in the + // subgraph. + for _, e := range n.Edges() { + target := e.Tail() + edgeBuf.WriteString(fmt.Sprintf( + "\t\"%s\" -> \"%s\";\n", + n, + target)) + } + } + buf.WriteString("\t}\n\n") + if edgeBuf.Len() > 0 { + buf.WriteString(edgeBuf.String()) + buf.WriteString("\n") + } +} + +func graphDotTitle(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. + hasDiff := false + for _, n := range g.Nouns { + rn, ok := n.Meta.(*GraphNodeResource) + if !ok { + continue + } + if rn.Resource.Diff != nil && !rn.Resource.Diff.Empty() { + hasDiff = true + break + } + } + + graphType := "Configuration" + if hasDiff { + graphType = "Plan" + } + title := fmt.Sprintf("Terraform %s Resource Graph", graphType) + + buf.WriteString(fmt.Sprintf("\tlabel=\"%s\\n\\n\\n\";\n", title)) + buf.WriteString("\tlabelloc=\"t\";\n\n") +}