github.com/chalford/terraform@v0.3.7-0.20150113080010-a78c69a8c81f/terraform/graph_dot.go (about) 1 package terraform 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "strings" 8 9 "github.com/hashicorp/terraform/depgraph" 10 ) 11 12 // GraphDotOpts are options for turning a graph into dot format. 13 type GraphDotOpts struct { 14 // ModuleDepth is the depth of modules to expand. Zero is no expansion, 15 // one expands the first set of modules, etc. If this is set to -1, then 16 // all modules are expanded. 17 ModuleDepth int 18 19 // Depth is an internal track of what depth we're at within 20 // the graph, used to control indentation and other such things. 21 depth int 22 } 23 24 // GraphDot returns the dot formatting of a visual representation of 25 // the given Terraform graph. 26 func GraphDot(g *depgraph.Graph, opts *GraphDotOpts) string { 27 buf := new(bytes.Buffer) 28 29 if opts.depth == 0 { 30 buf.WriteString("digraph {\n") 31 buf.WriteString("\tcompound = true;\n") 32 } 33 34 // Determine and add the title 35 // graphDotTitle(buf, g) 36 37 // Add all the resource. 38 graphDotAddResources(buf, g, opts) 39 40 // Add all the resource providers 41 graphDotAddResourceProviders(buf, g, opts) 42 43 // Add all the modules 44 graphDotAddModules(buf, g, opts) 45 46 if opts.depth == 0 { 47 buf.WriteString("}\n") 48 } 49 50 return buf.String() 51 } 52 53 func graphDotAddRoot(buf *bytes.Buffer, n *depgraph.Noun) { 54 buf.WriteString(fmt.Sprintf("\t\"%s\" [shape=circle];\n", "root")) 55 56 for _, e := range n.Edges() { 57 target := e.Tail() 58 buf.WriteString(fmt.Sprintf( 59 "\t\"%s\" -> \"%s\";\n", 60 "root", 61 target)) 62 } 63 } 64 65 func graphDotAddModules(buf *bytes.Buffer, g *depgraph.Graph, opts *GraphDotOpts) { 66 for _, n := range g.Nouns { 67 _, ok := n.Meta.(*GraphNodeModule) 68 if !ok { 69 continue 70 } 71 72 if graphExpand(opts) { 73 // We're expanding 74 graphDotAddModuleExpand(buf, n, opts) 75 } else { 76 // We're not expanding, so just add the module on its own 77 graphDotAddModuleSingle(buf, n, opts) 78 } 79 80 graphWriteEdges(buf, n, opts) 81 } 82 } 83 84 func graphDotAddModuleExpand( 85 buf *bytes.Buffer, n *depgraph.Noun, opts *GraphDotOpts) { 86 m := n.Meta.(*GraphNodeModule) 87 tab := strings.Repeat("\t", opts.depth+1) 88 uniqueName := graphUniqueName(n, opts) 89 90 // Wrap ourselves in a subgraph 91 buf.WriteString(fmt.Sprintf("%ssubgraph \"cluster_%s\" {\n", tab, uniqueName)) 92 defer buf.WriteString(fmt.Sprintf("%s}\n", tab)) 93 94 // Add our label so that we have the proper name. 95 buf.WriteString(fmt.Sprintf("%s\tlabel = \"%s\";\n", tab, n)) 96 97 // Add a hidden name for edges to point from/to 98 buf.WriteString(fmt.Sprintf("%s\t\"%s_hidden\" [fixedsize=true,width=0,height=0,label=\"\",style=invisible];\n", tab, uniqueName)) 99 100 // Graph the subgraph just as we would any other graph 101 subOpts := *opts 102 subOpts.depth++ 103 subStr := GraphDot(m.Graph, &subOpts) 104 105 // Tab all the lines of the subgraph 106 s := bufio.NewScanner(strings.NewReader(subStr)) 107 for s.Scan() { 108 buf.WriteString(fmt.Sprintf("%s%s\n", tab, s.Text())) 109 } 110 } 111 112 func graphDotAddModuleSingle( 113 buf *bytes.Buffer, n *depgraph.Noun, opts *GraphDotOpts) { 114 tab := strings.Repeat("\t", opts.depth+1) 115 uniqueName := graphUniqueName(n, opts) 116 117 // Create this node. 118 buf.WriteString(fmt.Sprintf("%s\"%s\" [\n", tab, uniqueName)) 119 buf.WriteString(fmt.Sprintf("%s\tlabel=\"%s\"\n", tab, n)) 120 buf.WriteString(fmt.Sprintf("%s\tshape=component\n", tab)) 121 buf.WriteString(fmt.Sprintf("%s];\n", tab)) 122 } 123 124 func graphDotAddResources( 125 buf *bytes.Buffer, g *depgraph.Graph, opts *GraphDotOpts) { 126 // Determine if we have diffs. If we do, then we're graphing a 127 // plan, which alters our graph a bit. 128 hasDiff := false 129 for _, n := range g.Nouns { 130 rn, ok := n.Meta.(*GraphNodeResource) 131 if !ok { 132 continue 133 } 134 if rn.Resource.Diff != nil && !rn.Resource.Diff.Empty() { 135 hasDiff = true 136 break 137 } 138 } 139 140 var edgeBuf bytes.Buffer 141 // Do all the non-destroy resources 142 buf.WriteString("\tsubgraph {\n") 143 for _, n := range g.Nouns { 144 rn, ok := n.Meta.(*GraphNodeResource) 145 if !ok { 146 continue 147 } 148 if rn.Resource.Diff != nil && rn.Resource.Diff.Destroy { 149 continue 150 } 151 152 // If we have diffs then we're graphing a plan. If we don't have 153 // have a diff on this resource, don't graph anything, since the 154 // plan wouldn't do anything to this resource. 155 if hasDiff { 156 if rn.Resource.Diff == nil || rn.Resource.Diff.Empty() { 157 continue 158 } 159 } 160 161 // Determine the colors. White = no change, yellow = change, 162 // green = create. Destroy is in the next section. 163 var color, fillColor string 164 if rn.Resource.Diff != nil && !rn.Resource.Diff.Empty() { 165 if rn.Resource.State != nil && rn.Resource.State.ID != "" { 166 color = "#FFFF00" 167 fillColor = "#FFFF94" 168 } else { 169 color = "#00FF00" 170 fillColor = "#9EFF9E" 171 } 172 } 173 174 uniqueName := fmt.Sprintf("%d_%s", opts.depth, n) 175 176 // Create this node. 177 buf.WriteString(fmt.Sprintf("\t\t\"%s\" [\n", uniqueName)) 178 buf.WriteString(fmt.Sprintf("\t\t\tlabel=\"%s\"\n", n)) 179 buf.WriteString("\t\t\tshape=box\n") 180 if color != "" { 181 buf.WriteString("\t\t\tstyle=filled\n") 182 buf.WriteString(fmt.Sprintf("\t\t\tcolor=\"%s\"\n", color)) 183 buf.WriteString(fmt.Sprintf("\t\t\tfillcolor=\"%s\"\n", fillColor)) 184 } 185 buf.WriteString("\t\t];\n") 186 187 // Build up all the edges in a separate buffer so they're not in the 188 // subgraph. 189 graphWriteEdges(&edgeBuf, n, opts) 190 } 191 buf.WriteString("\t}\n\n") 192 if edgeBuf.Len() > 0 { 193 buf.WriteString(edgeBuf.String()) 194 buf.WriteString("\n") 195 } 196 197 // Do all the destroy resources 198 edgeBuf.Reset() 199 buf.WriteString("\tsubgraph {\n") 200 for _, n := range g.Nouns { 201 rn, ok := n.Meta.(*GraphNodeResource) 202 if !ok { 203 continue 204 } 205 if rn.Resource.Diff == nil || !rn.Resource.Diff.Destroy { 206 continue 207 } 208 209 uniqueName := fmt.Sprintf("%d_%s", opts.depth, n) 210 211 buf.WriteString(fmt.Sprintf( 212 "\t\t\"%s\" [label=\"%s\",shape=box,style=filled,color=\"#FF0000\",fillcolor=\"#FF9494\"];\n", uniqueName, n)) 213 214 graphWriteEdges(&edgeBuf, n, opts) 215 } 216 buf.WriteString("\t}\n\n") 217 if edgeBuf.Len() > 0 { 218 buf.WriteString(edgeBuf.String()) 219 buf.WriteString("\n") 220 } 221 222 // Handle the meta resources 223 /* 224 edgeBuf.Reset() 225 for _, n := range g.Nouns { 226 _, ok := n.Meta.(*GraphNodeResourceMeta) 227 if !ok { 228 continue 229 } 230 231 // Determine which edges to add 232 var edges []digraph.Edge 233 if hasDiff { 234 for _, e := range n.Edges() { 235 rn, ok := e.Tail().(*depgraph.Noun).Meta.(*GraphNodeResource) 236 if !ok { 237 continue 238 } 239 if rn.Resource.Diff == nil || rn.Resource.Diff.Empty() { 240 continue 241 } 242 edges = append(edges, e) 243 } 244 } else { 245 edges = n.Edges() 246 } 247 248 // Do not draw if we have no edges 249 if len(edges) == 0 { 250 continue 251 } 252 253 uniqueName := fmt.Sprintf("%d_%s", opts.depth, n) 254 for _, e := range edges { 255 target := e.Tail() 256 uniqueTarget := fmt.Sprintf("%d_%s", opts.depth, target) 257 edgeBuf.WriteString(fmt.Sprintf( 258 "\t\"%s\" -> \"%s\";\n", 259 uniqueName, 260 uniqueTarget)) 261 } 262 } 263 if edgeBuf.Len() > 0 { 264 buf.WriteString(edgeBuf.String()) 265 buf.WriteString("\n") 266 } 267 */ 268 } 269 270 func graphDotAddResourceProviders( 271 buf *bytes.Buffer, g *depgraph.Graph, opts *GraphDotOpts) { 272 var edgeBuf bytes.Buffer 273 buf.WriteString("\tsubgraph {\n") 274 for _, n := range g.Nouns { 275 _, ok := n.Meta.(*GraphNodeResourceProvider) 276 if !ok { 277 continue 278 } 279 280 uniqueName := fmt.Sprintf("%d_%s", opts.depth, n) 281 282 // Create this node. 283 buf.WriteString(fmt.Sprintf("\t\t\"%s\" [\n", uniqueName)) 284 buf.WriteString(fmt.Sprintf("\t\t\tlabel=\"%s\"\n", n)) 285 buf.WriteString("\t\t\tshape=diamond\n") 286 buf.WriteString("\t\t];\n") 287 288 // Build up all the edges in a separate buffer so they're not in the 289 // subgraph. 290 graphWriteEdges(&edgeBuf, n, opts) 291 } 292 buf.WriteString("\t}\n\n") 293 if edgeBuf.Len() > 0 { 294 buf.WriteString(edgeBuf.String()) 295 buf.WriteString("\n") 296 } 297 } 298 299 func graphDotTitle(buf *bytes.Buffer, g *depgraph.Graph) { 300 // Determine if we have diffs. If we do, then we're graphing a 301 // plan, which alters our graph a bit. 302 hasDiff := false 303 for _, n := range g.Nouns { 304 rn, ok := n.Meta.(*GraphNodeResource) 305 if !ok { 306 continue 307 } 308 if rn.Resource.Diff != nil && !rn.Resource.Diff.Empty() { 309 hasDiff = true 310 break 311 } 312 } 313 314 graphType := "Configuration" 315 if hasDiff { 316 graphType = "Plan" 317 } 318 title := fmt.Sprintf("Terraform %s Resource Graph", graphType) 319 320 buf.WriteString(fmt.Sprintf("\tlabel=\"%s\\n\\n\\n\";\n", title)) 321 buf.WriteString("\tlabelloc=\"t\";\n\n") 322 } 323 324 func graphExpand(opts *GraphDotOpts) bool { 325 return opts.ModuleDepth > opts.depth || opts.ModuleDepth == -1 326 } 327 328 func graphUniqueName(n *depgraph.Noun, opts *GraphDotOpts) string { 329 return fmt.Sprintf("%d_%s", opts.depth, n) 330 } 331 332 func graphWriteEdges( 333 buf *bytes.Buffer, n *depgraph.Noun, opts *GraphDotOpts) { 334 tab := strings.Repeat("\t", opts.depth+1) 335 336 uniqueName := graphUniqueName(n, opts) 337 var ltail string 338 if _, ok := n.Meta.(*GraphNodeModule); ok && graphExpand(opts) { 339 ltail = "cluster_" + uniqueName 340 uniqueName = uniqueName + "_hidden" 341 } 342 343 for _, e := range n.Edges() { 344 target := e.Tail() 345 targetN := target.(*depgraph.Noun) 346 uniqueTarget := graphUniqueName(targetN, opts) 347 348 var lhead string 349 if _, ok := targetN.Meta.(*GraphNodeModule); ok && graphExpand(opts) { 350 lhead = "cluster_" + uniqueTarget 351 uniqueTarget = uniqueTarget + "_hidden" 352 } 353 354 var attrs string 355 if lhead != "" || ltail != "" { 356 var attrList []string 357 if lhead != "" { 358 attrList = append(attrList, fmt.Sprintf( 359 "lhead=\"%s\"", lhead)) 360 } 361 if ltail != "" { 362 attrList = append(attrList, fmt.Sprintf( 363 "ltail=\"%s\"", ltail)) 364 } 365 366 attrs = fmt.Sprintf(" [%s]", strings.Join(attrList, ",")) 367 } 368 369 buf.WriteString(fmt.Sprintf( 370 "%s\"%s\" -> \"%s\"%s;\n", 371 tab, 372 uniqueName, 373 uniqueTarget, 374 attrs)) 375 } 376 }