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  }