github.com/ns1/terraform@v0.7.10-0.20161109153551-8949419bef40/terraform/graph_debug.go (about) 1 package terraform 2 3 import ( 4 "bytes" 5 "fmt" 6 "sync" 7 8 "github.com/davecgh/go-spew/spew" 9 "github.com/hashicorp/terraform/dag" 10 "github.com/hashicorp/terraform/dot" 11 ) 12 13 // The NodeDebug method outputs debug information to annotate the graphs 14 // stored in the DebugInfo 15 type GraphNodeDebugger interface { 16 NodeDebug() string 17 } 18 19 type GraphNodeDebugOrigin interface { 20 DotOrigin() bool 21 } 22 type DebugGraph struct { 23 // TODO: can we combine this and dot.Graph into a generalized graph representation? 24 sync.Mutex 25 Name string 26 27 ord int 28 buf bytes.Buffer 29 30 Dot *dot.Graph 31 dotOpts *GraphDotOpts 32 } 33 34 // DebugGraph holds a dot representation of the Terraform graph, and can be 35 // written out to the DebugInfo log with DebugInfo.WriteGraph. A DebugGraph can 36 // log data to it's internal buffer via the Printf and Write methods, which 37 // will be also be written out to the DebugInfo archive. 38 func NewDebugGraph(name string, g *Graph, opts *GraphDotOpts) (*DebugGraph, error) { 39 dg := &DebugGraph{ 40 Name: name, 41 dotOpts: opts, 42 } 43 44 err := dg.build(g) 45 if err != nil { 46 dbug.WriteFile(dg.Name, []byte(err.Error())) 47 return nil, err 48 } 49 return dg, nil 50 } 51 52 // Printf to the internal buffer 53 func (dg *DebugGraph) Printf(f string, args ...interface{}) (int, error) { 54 if dg == nil { 55 return 0, nil 56 } 57 dg.Lock() 58 defer dg.Unlock() 59 return fmt.Fprintf(&dg.buf, f, args...) 60 } 61 62 // Write to the internal buffer 63 func (dg *DebugGraph) Write(b []byte) (int, error) { 64 if dg == nil { 65 return 0, nil 66 } 67 dg.Lock() 68 defer dg.Unlock() 69 return dg.buf.Write(b) 70 } 71 72 func (dg *DebugGraph) LogBytes() []byte { 73 if dg == nil { 74 return nil 75 } 76 dg.Lock() 77 defer dg.Unlock() 78 return dg.buf.Bytes() 79 } 80 81 func (dg *DebugGraph) DotBytes() []byte { 82 if dg == nil { 83 return nil 84 } 85 dg.Lock() 86 defer dg.Unlock() 87 return dg.Dot.Bytes() 88 } 89 90 func (dg *DebugGraph) DebugNode(v interface{}) { 91 if dg == nil { 92 return 93 } 94 dg.Lock() 95 defer dg.Unlock() 96 97 // record the ordinal value for each node 98 ord := dg.ord 99 dg.ord++ 100 101 name := graphDotNodeName("root", v) 102 103 var node *dot.Node 104 // TODO: recursive 105 for _, sg := range dg.Dot.Subgraphs { 106 node, _ = sg.GetNode(name) 107 if node != nil { 108 break 109 } 110 } 111 112 // record as much of the node data structure as we can 113 spew.Fdump(&dg.buf, v) 114 115 // for now, record the order of visits in the node label 116 if node != nil { 117 node.Attrs["label"] = fmt.Sprintf("%s %d", node.Attrs["label"], ord) 118 } 119 120 // if the node provides debug output, insert it into the graph, and log it 121 if nd, ok := v.(GraphNodeDebugger); ok { 122 out := nd.NodeDebug() 123 if node != nil { 124 node.Attrs["comment"] = out 125 dg.buf.WriteString(fmt.Sprintf("NodeDebug (%s):'%s'\n", name, out)) 126 } 127 } 128 } 129 130 // takes a Terraform Graph and build the internal debug graph 131 func (dg *DebugGraph) build(g *Graph) error { 132 if dg == nil { 133 return nil 134 } 135 dg.Lock() 136 defer dg.Unlock() 137 138 dg.Dot = dot.NewGraph(map[string]string{ 139 "compound": "true", 140 "newrank": "true", 141 }) 142 dg.Dot.Directed = true 143 144 if dg.dotOpts == nil { 145 dg.dotOpts = &GraphDotOpts{ 146 DrawCycles: true, 147 MaxDepth: -1, 148 Verbose: true, 149 } 150 } 151 152 err := dg.buildSubgraph("root", g, 0) 153 if err != nil { 154 return err 155 } 156 157 return nil 158 } 159 160 func (dg *DebugGraph) buildSubgraph(modName string, g *Graph, modDepth int) error { 161 // Respect user-specified module depth 162 if dg.dotOpts.MaxDepth >= 0 && modDepth > dg.dotOpts.MaxDepth { 163 return nil 164 } 165 166 // Begin module subgraph 167 var sg *dot.Subgraph 168 if modDepth == 0 { 169 sg = dg.Dot.AddSubgraph(modName) 170 } else { 171 sg = dg.Dot.AddSubgraph(modName) 172 sg.Cluster = true 173 sg.AddAttr("label", modName) 174 } 175 176 origins, err := graphDotFindOrigins(g) 177 if err != nil { 178 return err 179 } 180 181 drawableVertices := make(map[dag.Vertex]struct{}) 182 toDraw := make([]dag.Vertex, 0, len(g.Vertices())) 183 subgraphVertices := make(map[dag.Vertex]*Graph) 184 185 walk := func(v dag.Vertex, depth int) error { 186 // We only care about nodes that yield non-empty Dot strings. 187 if dn, ok := v.(GraphNodeDotter); !ok { 188 return nil 189 } else if dn.DotNode("fake", dg.dotOpts) == nil { 190 return nil 191 } 192 193 drawableVertices[v] = struct{}{} 194 toDraw = append(toDraw, v) 195 196 if sn, ok := v.(GraphNodeSubgraph); ok { 197 subgraphVertices[v] = sn.Subgraph() 198 } 199 return nil 200 } 201 202 if err := g.ReverseDepthFirstWalk(origins, walk); err != nil { 203 return err 204 } 205 206 for _, v := range toDraw { 207 dn := v.(GraphNodeDotter) 208 nodeName := graphDotNodeName(modName, v) 209 sg.AddNode(dn.DotNode(nodeName, dg.dotOpts)) 210 211 // Draw all the edges from this vertex to other nodes 212 targets := dag.AsVertexList(g.DownEdges(v)) 213 for _, t := range targets { 214 target := t.(dag.Vertex) 215 // Only want edges where both sides are drawable. 216 if _, ok := drawableVertices[target]; !ok { 217 continue 218 } 219 220 if err := sg.AddEdgeBetween( 221 graphDotNodeName(modName, v), 222 graphDotNodeName(modName, target), 223 map[string]string{}); err != nil { 224 return err 225 } 226 } 227 } 228 229 // Recurse into any subgraphs 230 for _, v := range toDraw { 231 subgraph, ok := subgraphVertices[v] 232 if !ok { 233 continue 234 } 235 236 err := dg.buildSubgraph(dag.VertexName(v), subgraph, modDepth+1) 237 if err != nil { 238 return err 239 } 240 } 241 242 if dg.dotOpts.DrawCycles { 243 colors := []string{"red", "green", "blue"} 244 for ci, cycle := range g.Cycles() { 245 for i, c := range cycle { 246 // Catch the last wrapping edge of the cycle 247 if i+1 >= len(cycle) { 248 i = -1 249 } 250 edgeAttrs := map[string]string{ 251 "color": colors[ci%len(colors)], 252 "penwidth": "2.0", 253 } 254 255 if err := sg.AddEdgeBetween( 256 graphDotNodeName(modName, c), 257 graphDotNodeName(modName, cycle[i+1]), 258 edgeAttrs); err != nil { 259 return err 260 } 261 262 } 263 } 264 } 265 266 return nil 267 } 268 269 func graphDotNodeName(modName, v dag.Vertex) string { 270 return fmt.Sprintf("[%s] %s", modName, dag.VertexName(v)) 271 } 272 273 func graphDotFindOrigins(g *Graph) ([]dag.Vertex, error) { 274 var origin []dag.Vertex 275 276 for _, v := range g.Vertices() { 277 if dr, ok := v.(GraphNodeDebugOrigin); ok { 278 if dr.DotOrigin() { 279 origin = append(origin, v) 280 } 281 } 282 } 283 284 if len(origin) == 0 { 285 return nil, fmt.Errorf("No DOT origin nodes found.\nGraph: %s", g.String()) 286 } 287 288 return origin, nil 289 }