package terraform import ( "fmt" "log" "github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/dag" ) // Graph represents the graph that Terraform uses to represent resources // and their dependencies. type Graph struct { // Graph is the actual DAG. This is embedded so you can call the DAG // methods directly. dag.AcyclicGraph // Path is the path in the module tree that this Graph represents. Path addrs.ModuleInstance // debugName is a name for reference in the debug output. This is usually // to indicate what topmost builder was, and if this graph is a shadow or // not. debugName string } func (g *Graph) DirectedGraph() dag.Grapher { return &g.AcyclicGraph } // Walk walks the graph with the given walker for callbacks. The graph // will be walked with full parallelism, so the walker should expect // to be called in concurrently. func (g *Graph) Walk(walker GraphWalker) tfdiags.Diagnostics { return g.walk(walker) } func (g *Graph) walk(walker GraphWalker) tfdiags.Diagnostics { // The callbacks for enter/exiting a graph ctx := walker.EnterPath(g.Path) defer walker.ExitPath(g.Path) // Get the path for logs path := ctx.Path().String() debugName := "walk-graph.json" if g.debugName != "" { debugName = g.debugName + "-" + debugName } // Walk the graph. var walkFn dag.WalkFunc walkFn = func(v dag.Vertex) (diags tfdiags.Diagnostics) { log.Printf("[TRACE] vertex %q: starting visit (%T)", dag.VertexName(v), v) g.DebugVisitInfo(v, g.debugName) defer func() { log.Printf("[TRACE] vertex %q: visit complete", dag.VertexName(v)) }() walker.EnterVertex(v) defer walker.ExitVertex(v, diags) // vertexCtx is the context that we use when evaluating. This // is normally the context of our graph but can be overridden // with a GraphNodeSubPath impl. vertexCtx := ctx if pn, ok := v.(GraphNodeSubPath); ok && len(pn.Path()) > 0 { vertexCtx = walker.EnterPath(pn.Path()) defer walker.ExitPath(pn.Path()) } // If the node is eval-able, then evaluate it. if ev, ok := v.(GraphNodeEvalable); ok { tree := ev.EvalTree() if tree == nil { panic(fmt.Sprintf("%q (%T): nil eval tree", dag.VertexName(v), v)) } // Allow the walker to change our tree if needed. Eval, // then callback with the output. log.Printf("[TRACE] vertex %q: evaluating", dag.VertexName(v)) g.DebugVertexInfo(v, fmt.Sprintf("evaluating %T(%s)", v, path)) tree = walker.EnterEvalTree(v, tree) output, err := Eval(tree, vertexCtx) diags = diags.Append(walker.ExitEvalTree(v, output, err)) if diags.HasErrors() { return } } // If the node is dynamically expanded, then expand it if ev, ok := v.(GraphNodeDynamicExpandable); ok { log.Printf("[TRACE] vertex %q: expanding dynamic subgraph", dag.VertexName(v)) g.DebugVertexInfo(v, fmt.Sprintf("expanding %T(%s)", v, path)) g, err := ev.DynamicExpand(vertexCtx) if err != nil { diags = diags.Append(err) return } if g != nil { // Walk the subgraph log.Printf("[TRACE] vertex %q: entering dynamic subgraph", dag.VertexName(v)) subDiags := g.walk(walker) diags = diags.Append(subDiags) if subDiags.HasErrors() { log.Printf("[TRACE] vertex %q: dynamic subgraph encountered errors", dag.VertexName(v)) return } log.Printf("[TRACE] vertex %q: dynamic subgraph completed successfully", dag.VertexName(v)) } else { log.Printf("[TRACE] vertex %q: produced no dynamic subgraph", dag.VertexName(v)) } } // If the node has a subgraph, then walk the subgraph if sn, ok := v.(GraphNodeSubgraph); ok { log.Printf("[TRACE] vertex %q: entering static subgraph", dag.VertexName(v)) g.DebugVertexInfo(v, fmt.Sprintf("subgraph: %T(%s)", v, path)) subDiags := sn.Subgraph().(*Graph).walk(walker) if subDiags.HasErrors() { log.Printf("[TRACE] vertex %q: static subgraph encountered errors", dag.VertexName(v)) return } log.Printf("[TRACE] vertex %q: static subgraph completed successfully", dag.VertexName(v)) } return } return g.AcyclicGraph.Walk(walkFn) }