github.com/trawler/terraform@v0.10.8-0.20171106022149-4b1c7a1d9b48/terraform/graph.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"runtime/debug"
     7  	"strings"
     8  
     9  	"github.com/hashicorp/terraform/dag"
    10  )
    11  
    12  // RootModuleName is the name given to the root module implicitly.
    13  const RootModuleName = "root"
    14  
    15  // RootModulePath is the path for the root module.
    16  var RootModulePath = []string{RootModuleName}
    17  
    18  // Graph represents the graph that Terraform uses to represent resources
    19  // and their dependencies.
    20  type Graph struct {
    21  	// Graph is the actual DAG. This is embedded so you can call the DAG
    22  	// methods directly.
    23  	dag.AcyclicGraph
    24  
    25  	// Path is the path in the module tree that this Graph represents.
    26  	// The root is represented by a single element list containing
    27  	// RootModuleName
    28  	Path []string
    29  
    30  	// debugName is a name for reference in the debug output. This is usually
    31  	// to indicate what topmost builder was, and if this graph is a shadow or
    32  	// not.
    33  	debugName string
    34  }
    35  
    36  func (g *Graph) DirectedGraph() dag.Grapher {
    37  	return &g.AcyclicGraph
    38  }
    39  
    40  // Walk walks the graph with the given walker for callbacks. The graph
    41  // will be walked with full parallelism, so the walker should expect
    42  // to be called in concurrently.
    43  func (g *Graph) Walk(walker GraphWalker) error {
    44  	return g.walk(walker)
    45  }
    46  
    47  func (g *Graph) walk(walker GraphWalker) error {
    48  	// The callbacks for enter/exiting a graph
    49  	ctx := walker.EnterPath(g.Path)
    50  	defer walker.ExitPath(g.Path)
    51  
    52  	// Get the path for logs
    53  	path := strings.Join(ctx.Path(), ".")
    54  
    55  	// Determine if our walker is a panic wrapper
    56  	panicwrap, ok := walker.(GraphWalkerPanicwrapper)
    57  	if !ok {
    58  		panicwrap = nil // just to be sure
    59  	}
    60  
    61  	debugName := "walk-graph.json"
    62  	if g.debugName != "" {
    63  		debugName = g.debugName + "-" + debugName
    64  	}
    65  
    66  	debugBuf := dbug.NewFileWriter(debugName)
    67  	g.SetDebugWriter(debugBuf)
    68  	defer debugBuf.Close()
    69  
    70  	// Walk the graph.
    71  	var walkFn dag.WalkFunc
    72  	walkFn = func(v dag.Vertex) (rerr error) {
    73  		log.Printf("[TRACE] vertex '%s.%s': walking", path, dag.VertexName(v))
    74  		g.DebugVisitInfo(v, g.debugName)
    75  
    76  		// If we have a panic wrap GraphWalker and a panic occurs, recover
    77  		// and call that. We ensure the return value is an error, however,
    78  		// so that future nodes are not called.
    79  		defer func() {
    80  			// If no panicwrap, do nothing
    81  			if panicwrap == nil {
    82  				return
    83  			}
    84  
    85  			// If no panic, do nothing
    86  			err := recover()
    87  			if err == nil {
    88  				return
    89  			}
    90  
    91  			// Modify the return value to show the error
    92  			rerr = fmt.Errorf("vertex %q captured panic: %s\n\n%s",
    93  				dag.VertexName(v), err, debug.Stack())
    94  
    95  			// Call the panic wrapper
    96  			panicwrap.Panic(v, err)
    97  		}()
    98  
    99  		walker.EnterVertex(v)
   100  		defer walker.ExitVertex(v, rerr)
   101  
   102  		// vertexCtx is the context that we use when evaluating. This
   103  		// is normally the context of our graph but can be overridden
   104  		// with a GraphNodeSubPath impl.
   105  		vertexCtx := ctx
   106  		if pn, ok := v.(GraphNodeSubPath); ok && len(pn.Path()) > 0 {
   107  			vertexCtx = walker.EnterPath(normalizeModulePath(pn.Path()))
   108  			defer walker.ExitPath(pn.Path())
   109  		}
   110  
   111  		// If the node is eval-able, then evaluate it.
   112  		if ev, ok := v.(GraphNodeEvalable); ok {
   113  			tree := ev.EvalTree()
   114  			if tree == nil {
   115  				panic(fmt.Sprintf(
   116  					"%s.%s (%T): nil eval tree", path, dag.VertexName(v), v))
   117  			}
   118  
   119  			// Allow the walker to change our tree if needed. Eval,
   120  			// then callback with the output.
   121  			log.Printf("[TRACE] vertex '%s.%s': evaluating", path, dag.VertexName(v))
   122  
   123  			g.DebugVertexInfo(v, fmt.Sprintf("evaluating %T(%s)", v, path))
   124  
   125  			tree = walker.EnterEvalTree(v, tree)
   126  			output, err := Eval(tree, vertexCtx)
   127  			if rerr = walker.ExitEvalTree(v, output, err); rerr != nil {
   128  				return
   129  			}
   130  		}
   131  
   132  		// If the node is dynamically expanded, then expand it
   133  		if ev, ok := v.(GraphNodeDynamicExpandable); ok {
   134  			log.Printf(
   135  				"[TRACE] vertex '%s.%s': expanding/walking dynamic subgraph",
   136  				path,
   137  				dag.VertexName(v))
   138  
   139  			g.DebugVertexInfo(v, fmt.Sprintf("expanding %T(%s)", v, path))
   140  
   141  			g, err := ev.DynamicExpand(vertexCtx)
   142  			if err != nil {
   143  				rerr = err
   144  				return
   145  			}
   146  			if g != nil {
   147  				// Walk the subgraph
   148  				if rerr = g.walk(walker); rerr != nil {
   149  					return
   150  				}
   151  			}
   152  		}
   153  
   154  		// If the node has a subgraph, then walk the subgraph
   155  		if sn, ok := v.(GraphNodeSubgraph); ok {
   156  			log.Printf(
   157  				"[TRACE] vertex '%s.%s': walking subgraph",
   158  				path,
   159  				dag.VertexName(v))
   160  
   161  			g.DebugVertexInfo(v, fmt.Sprintf("subgraph: %T(%s)", v, path))
   162  
   163  			if rerr = sn.Subgraph().(*Graph).walk(walker); rerr != nil {
   164  				return
   165  			}
   166  		}
   167  
   168  		return nil
   169  	}
   170  
   171  	return g.AcyclicGraph.Walk(walkFn)
   172  }