package terraform import ( "log" "strings" "github.com/hashicorp/terraform/internal/logging" "github.com/hashicorp/terraform/internal/tfdiags" "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/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 } 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.EvalContext() // Walk the graph. walkFn := func(v dag.Vertex) (diags tfdiags.Diagnostics) { // the walkFn is called asynchronously, and needs to be recovered // separately in the case of a panic. defer logging.PanicHandler() log.Printf("[TRACE] vertex %q: starting visit (%T)", dag.VertexName(v), v) defer func() { if diags.HasErrors() { for _, diag := range diags { if diag.Severity() == tfdiags.Error { desc := diag.Description() log.Printf("[ERROR] vertex %q error: %s", dag.VertexName(v), desc.Summary) } } log.Printf("[TRACE] vertex %q: visit complete, with errors", dag.VertexName(v)) } else { log.Printf("[TRACE] vertex %q: visit complete", dag.VertexName(v)) } }() // vertexCtx is the context that we use when evaluating. This // is normally the context of our graph but can be overridden // with a GraphNodeModuleInstance impl. vertexCtx := ctx if pn, ok := v.(GraphNodeModuleInstance); ok { vertexCtx = walker.EnterPath(pn.Path()) defer walker.ExitPath(pn.Path()) } // If the node is exec-able, then execute it. if ev, ok := v.(GraphNodeExecutable); ok { diags = diags.Append(walker.Execute(vertexCtx, ev)) 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, 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() { var errs []string for _, d := range subDiags { errs = append(errs, d.Description().Summary) } log.Printf("[TRACE] vertex %q: dynamic subgraph encountered errors: %s", dag.VertexName(v), strings.Join(errs, ",")) 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)) } } return } return g.AcyclicGraph.Walk(walkFn) }