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  }