github.com/heimweh/terraform@v0.7.4/terraform/graph_dot.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/terraform/dag" 7 "github.com/hashicorp/terraform/dot" 8 ) 9 10 // GraphNodeDotter can be implemented by a node to cause it to be included 11 // in the dot graph. The Dot method will be called which is expected to 12 // return a representation of this node. 13 type GraphNodeDotter interface { 14 // Dot is called to return the dot formatting for the node. 15 // The first parameter is the title of the node. 16 // The second parameter includes user-specified options that affect the dot 17 // graph. See GraphDotOpts below for details. 18 DotNode(string, *GraphDotOpts) *dot.Node 19 } 20 21 type GraphNodeDotOrigin interface { 22 DotOrigin() bool 23 } 24 25 // GraphDotOpts are the options for generating a dot formatted Graph. 26 type GraphDotOpts struct { 27 // Allows some nodes to decide to only show themselves when the user has 28 // requested the "verbose" graph. 29 Verbose bool 30 31 // Highlight Cycles 32 DrawCycles bool 33 34 // How many levels to expand modules as we draw 35 MaxDepth int 36 } 37 38 // GraphDot returns the dot formatting of a visual representation of 39 // the given Terraform graph. 40 func GraphDot(g *Graph, opts *GraphDotOpts) (string, error) { 41 dg := dot.NewGraph(map[string]string{ 42 "compound": "true", 43 "newrank": "true", 44 }) 45 dg.Directed = true 46 47 err := graphDotSubgraph(dg, "root", g, opts, 0) 48 if err != nil { 49 return "", err 50 } 51 52 return dg.String(), nil 53 } 54 55 func graphDotSubgraph( 56 dg *dot.Graph, modName string, g *Graph, opts *GraphDotOpts, modDepth int) error { 57 // Respect user-specified module depth 58 if opts.MaxDepth >= 0 && modDepth > opts.MaxDepth { 59 return nil 60 } 61 62 // Begin module subgraph 63 var sg *dot.Subgraph 64 if modDepth == 0 { 65 sg = dg.AddSubgraph(modName) 66 } else { 67 sg = dg.AddSubgraph(modName) 68 sg.Cluster = true 69 sg.AddAttr("label", modName) 70 } 71 72 origins, err := graphDotFindOrigins(g) 73 if err != nil { 74 return err 75 } 76 77 drawableVertices := make(map[dag.Vertex]struct{}) 78 toDraw := make([]dag.Vertex, 0, len(g.Vertices())) 79 subgraphVertices := make(map[dag.Vertex]*Graph) 80 81 walk := func(v dag.Vertex, depth int) error { 82 // We only care about nodes that yield non-empty Dot strings. 83 if dn, ok := v.(GraphNodeDotter); !ok { 84 return nil 85 } else if dn.DotNode("fake", opts) == nil { 86 return nil 87 } 88 89 drawableVertices[v] = struct{}{} 90 toDraw = append(toDraw, v) 91 92 if sn, ok := v.(GraphNodeSubgraph); ok { 93 subgraphVertices[v] = sn.Subgraph() 94 } 95 return nil 96 } 97 98 if err := g.ReverseDepthFirstWalk(origins, walk); err != nil { 99 return err 100 } 101 102 for _, v := range toDraw { 103 dn := v.(GraphNodeDotter) 104 nodeName := graphDotNodeName(modName, v) 105 sg.AddNode(dn.DotNode(nodeName, opts)) 106 107 // Draw all the edges from this vertex to other nodes 108 targets := dag.AsVertexList(g.DownEdges(v)) 109 for _, t := range targets { 110 target := t.(dag.Vertex) 111 // Only want edges where both sides are drawable. 112 if _, ok := drawableVertices[target]; !ok { 113 continue 114 } 115 116 if err := sg.AddEdgeBetween( 117 graphDotNodeName(modName, v), 118 graphDotNodeName(modName, target), 119 map[string]string{}); err != nil { 120 return err 121 } 122 } 123 } 124 125 // Recurse into any subgraphs 126 for _, v := range toDraw { 127 subgraph, ok := subgraphVertices[v] 128 if !ok { 129 continue 130 } 131 132 err := graphDotSubgraph(dg, dag.VertexName(v), subgraph, opts, modDepth+1) 133 if err != nil { 134 return err 135 } 136 } 137 138 if opts.DrawCycles { 139 colors := []string{"red", "green", "blue"} 140 for ci, cycle := range g.Cycles() { 141 for i, c := range cycle { 142 // Catch the last wrapping edge of the cycle 143 if i+1 >= len(cycle) { 144 i = -1 145 } 146 edgeAttrs := map[string]string{ 147 "color": colors[ci%len(colors)], 148 "penwidth": "2.0", 149 } 150 151 if err := sg.AddEdgeBetween( 152 graphDotNodeName(modName, c), 153 graphDotNodeName(modName, cycle[i+1]), 154 edgeAttrs); err != nil { 155 return err 156 } 157 158 } 159 } 160 } 161 162 return nil 163 } 164 165 func graphDotNodeName(modName, v dag.Vertex) string { 166 return fmt.Sprintf("[%s] %s", modName, dag.VertexName(v)) 167 } 168 169 func graphDotFindOrigins(g *Graph) ([]dag.Vertex, error) { 170 var origin []dag.Vertex 171 172 for _, v := range g.Vertices() { 173 if dr, ok := v.(GraphNodeDotOrigin); ok { 174 if dr.DotOrigin() { 175 origin = append(origin, v) 176 } 177 } 178 } 179 180 if len(origin) == 0 { 181 return nil, fmt.Errorf("No DOT origin nodes found.\nGraph: %s", g.String()) 182 } 183 184 return origin, nil 185 }