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  }