github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/durgaform/graph.go (about) 1 package durgaform 2 3 import ( 4 "log" 5 "strings" 6 7 "github.com/eliastor/durgaform/internal/logging" 8 "github.com/eliastor/durgaform/internal/tfdiags" 9 10 "github.com/eliastor/durgaform/internal/addrs" 11 12 "github.com/eliastor/durgaform/internal/dag" 13 ) 14 15 // Graph represents the graph that Durgaform uses to represent resources 16 // and their dependencies. 17 type Graph struct { 18 // Graph is the actual DAG. This is embedded so you can call the DAG 19 // methods directly. 20 dag.AcyclicGraph 21 22 // Path is the path in the module tree that this Graph represents. 23 Path addrs.ModuleInstance 24 } 25 26 func (g *Graph) DirectedGraph() dag.Grapher { 27 return &g.AcyclicGraph 28 } 29 30 // Walk walks the graph with the given walker for callbacks. The graph 31 // will be walked with full parallelism, so the walker should expect 32 // to be called in concurrently. 33 func (g *Graph) Walk(walker GraphWalker) tfdiags.Diagnostics { 34 return g.walk(walker) 35 } 36 37 func (g *Graph) walk(walker GraphWalker) tfdiags.Diagnostics { 38 // The callbacks for enter/exiting a graph 39 ctx := walker.EvalContext() 40 41 // Walk the graph. 42 walkFn := func(v dag.Vertex) (diags tfdiags.Diagnostics) { 43 // the walkFn is called asynchronously, and needs to be recovered 44 // separately in the case of a panic. 45 defer logging.PanicHandler() 46 47 log.Printf("[TRACE] vertex %q: starting visit (%T)", dag.VertexName(v), v) 48 49 defer func() { 50 if diags.HasErrors() { 51 for _, diag := range diags { 52 if diag.Severity() == tfdiags.Error { 53 desc := diag.Description() 54 log.Printf("[ERROR] vertex %q error: %s", dag.VertexName(v), desc.Summary) 55 } 56 } 57 log.Printf("[TRACE] vertex %q: visit complete, with errors", dag.VertexName(v)) 58 } else { 59 log.Printf("[TRACE] vertex %q: visit complete", dag.VertexName(v)) 60 } 61 }() 62 63 // vertexCtx is the context that we use when evaluating. This 64 // is normally the context of our graph but can be overridden 65 // with a GraphNodeModuleInstance impl. 66 vertexCtx := ctx 67 if pn, ok := v.(GraphNodeModuleInstance); ok { 68 vertexCtx = walker.EnterPath(pn.Path()) 69 defer walker.ExitPath(pn.Path()) 70 } 71 72 // If the node is exec-able, then execute it. 73 if ev, ok := v.(GraphNodeExecutable); ok { 74 diags = diags.Append(walker.Execute(vertexCtx, ev)) 75 if diags.HasErrors() { 76 return 77 } 78 } 79 80 // If the node is dynamically expanded, then expand it 81 if ev, ok := v.(GraphNodeDynamicExpandable); ok { 82 log.Printf("[TRACE] vertex %q: expanding dynamic subgraph", dag.VertexName(v)) 83 84 g, err := ev.DynamicExpand(vertexCtx) 85 if err != nil { 86 diags = diags.Append(err) 87 return 88 } 89 if g != nil { 90 // Walk the subgraph 91 log.Printf("[TRACE] vertex %q: entering dynamic subgraph", dag.VertexName(v)) 92 subDiags := g.walk(walker) 93 diags = diags.Append(subDiags) 94 if subDiags.HasErrors() { 95 var errs []string 96 for _, d := range subDiags { 97 errs = append(errs, d.Description().Summary) 98 } 99 log.Printf("[TRACE] vertex %q: dynamic subgraph encountered errors: %s", dag.VertexName(v), strings.Join(errs, ",")) 100 return 101 } 102 log.Printf("[TRACE] vertex %q: dynamic subgraph completed successfully", dag.VertexName(v)) 103 } else { 104 log.Printf("[TRACE] vertex %q: produced no dynamic subgraph", dag.VertexName(v)) 105 } 106 } 107 return 108 } 109 110 return g.AcyclicGraph.Walk(walkFn) 111 }