@ -7,7 +7,7 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/hashicorp/terraform/dag"
"github.com/ hashicorp/terraform/dot "
"github.com/ mitchellh/copystructure "
)
// The NodeDebug method outputs debug information to annotate the graphs
@ -20,14 +20,14 @@ type GraphNodeDebugOrigin interface {
DotOrigin ( ) bool
}
type DebugGraph struct {
// TODO: can we combine this and dot.Graph into a generalized graph representation?
sync . Mutex
Name string
ord int
buf bytes . Buffer
Dot * dot . Graph
Graph * Graph
dotOpts * dag . DotOpts
}
@ -38,14 +38,11 @@ type DebugGraph struct {
func NewDebugGraph ( name string , g * Graph , opts * dag . DotOpts ) ( * DebugGraph , error ) {
dg := & DebugGraph {
Name : name ,
Graph : g ,
dotOpts : opts ,
}
err := dg . build ( g )
if err != nil {
dbug . WriteFile ( dg . Name , [ ] byte ( err . Error ( ) ) )
return nil , err
}
dbug . WriteFile ( dg . Name , g . Dot ( opts ) )
return dg , nil
}
@ -84,7 +81,7 @@ func (dg *DebugGraph) DotBytes() []byte {
}
dg . Lock ( )
defer dg . Unlock ( )
return dg . Dot. Bytes ( )
return dg . Graph. Dot ( dg . dotOpts )
}
func ( dg * DebugGraph ) DebugNode ( v interface { } ) {
@ -98,198 +95,17 @@ func (dg *DebugGraph) DebugNode(v interface{}) {
ord := dg . ord
dg . ord ++
name := graphDotNodeName ( "root" , v )
var node * dot . Node
// TODO: recursive
for _ , sg := range dg . Dot . Subgraphs {
node , _ = sg . GetNode ( name )
if node != nil {
break
}
}
name := dag . VertexName ( v )
vCopy , _ := copystructure . Config { Lock : true } . Copy ( v )
// record as much of the node data structure as we can
spew . Fdump ( & dg . buf , v )
spew . Fdump ( & dg . buf , vCopy )
// for now, record the order of visits in the node label
if node != nil {
node . Attrs [ "label" ] = fmt . Sprintf ( "%s %d" , node . Attrs [ "label" ] , ord )
}
dg . buf . WriteString ( fmt . Sprintf ( "%d visited %s\n" , ord , name ) )
// if the node provides debug output, insert it into the graph, and log it
if nd , ok := v . ( GraphNodeDebugger ) ; ok {
out := nd . NodeDebug ( )
if node != nil {
node . Attrs [ "comment" ] = out
dg . buf . WriteString ( fmt . Sprintf ( "NodeDebug (%s):'%s'\n" , name , out ) )
}
}
}
// takes a Terraform Graph and build the internal debug graph
func ( dg * DebugGraph ) build ( g * Graph ) error {
if dg == nil {
return nil
}
dg . Lock ( )
defer dg . Unlock ( )
dg . Dot = dot . NewGraph ( map [ string ] string {
"compound" : "true" ,
"newrank" : "true" ,
} )
dg . Dot . Directed = true
if dg . dotOpts == nil {
dg . dotOpts = & dag . DotOpts {
DrawCycles : true ,
MaxDepth : - 1 ,
Verbose : true ,
}
}
err := dg . buildSubgraph ( "root" , g , 0 )
if err != nil {
return err
}
return nil
}
func ( dg * DebugGraph ) buildSubgraph ( modName string , g * Graph , modDepth int ) error {
// Respect user-specified module depth
if dg . dotOpts . MaxDepth >= 0 && modDepth > dg . dotOpts . MaxDepth {
return nil
}
// Begin module subgraph
var sg * dot . Subgraph
if modDepth == 0 {
sg = dg . Dot . AddSubgraph ( modName )
} else {
sg = dg . Dot . AddSubgraph ( modName )
sg . Cluster = true
sg . AddAttr ( "label" , modName )
}
origins , err := graphDotFindOrigins ( g )
if err != nil {
return err
}
drawableVertices := make ( map [ dag . Vertex ] struct { } )
toDraw := make ( [ ] dag . Vertex , 0 , len ( g . Vertices ( ) ) )
subgraphVertices := make ( map [ dag . Vertex ] * Graph )
for _ , v := range g . Vertices ( ) {
if sn , ok := v . ( GraphNodeSubgraph ) ; ok {
subgraphVertices [ v ] = sn . Subgraph ( ) . ( * Graph )
}
dg . buf . WriteString ( fmt . Sprintf ( "NodeDebug (%s):'%s'\n" , name , out ) )
}
walk := func ( v dag . Vertex , depth int ) error {
// We only care about nodes that yield non-empty Dot strings.
if dn , ok := v . ( GraphNodeDotter ) ; ! ok {
return nil
} else if dn . DotNode ( "fake" , dg . dotOpts ) == nil {
return nil
}
drawableVertices [ v ] = struct { } { }
toDraw = append ( toDraw , v )
if sn , ok := v . ( GraphNodeSubgraph ) ; ok {
subgraphVertices [ v ] = sn . Subgraph ( ) . ( * Graph )
}
return nil
}
if err := g . ReverseDepthFirstWalk ( origins , walk ) ; err != nil {
return err
}
for _ , v := range toDraw {
dn := v . ( GraphNodeDotter )
nodeName := graphDotNodeName ( modName , v )
sg . AddNode ( dn . DotNode ( nodeName , dg . dotOpts ) )
// Draw all the edges from this vertex to other nodes
targets := dag . AsVertexList ( g . DownEdges ( v ) )
for _ , t := range targets {
target := t . ( dag . Vertex )
// Only want edges where both sides are drawable.
if _ , ok := drawableVertices [ target ] ; ! ok {
continue
}
if err := sg . AddEdgeBetween (
graphDotNodeName ( modName , v ) ,
graphDotNodeName ( modName , target ) ,
map [ string ] string { } ) ; err != nil {
return err
}
}
}
// Recurse into any subgraphs
for _ , v := range toDraw {
subgraph , ok := subgraphVertices [ v ]
if ! ok {
continue
}
err := dg . buildSubgraph ( dag . VertexName ( v ) , subgraph , modDepth + 1 )
if err != nil {
return err
}
}
if dg . dotOpts . DrawCycles {
colors := [ ] string { "red" , "green" , "blue" }
for ci , cycle := range g . Cycles ( ) {
for i , c := range cycle {
// Catch the last wrapping edge of the cycle
if i + 1 >= len ( cycle ) {
i = - 1
}
edgeAttrs := map [ string ] string {
"color" : colors [ ci % len ( colors ) ] ,
"penwidth" : "2.0" ,
}
if err := sg . AddEdgeBetween (
graphDotNodeName ( modName , c ) ,
graphDotNodeName ( modName , cycle [ i + 1 ] ) ,
edgeAttrs ) ; err != nil {
return err
}
}
}
}
return nil
}
func graphDotNodeName ( modName , v dag . Vertex ) string {
return fmt . Sprintf ( "[%s] %s" , modName , dag . VertexName ( v ) )
}
func graphDotFindOrigins ( g * Graph ) ( [ ] dag . Vertex , error ) {
var origin [ ] dag . Vertex
for _ , v := range g . Vertices ( ) {
if dr , ok := v . ( GraphNodeDebugOrigin ) ; ok {
if dr . DotOrigin ( ) {
origin = append ( origin , v )
}
}
}
if len ( origin ) == 0 {
return nil , fmt . Errorf ( "No DOT origin nodes found.\nGraph: %s" , g . String ( ) )
}
return origin , nil
}