/
graph.go
135 lines (117 loc) · 4.36 KB
/
graph.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package terraform
import (
"fmt"
"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)
diags = diags.Append(err)
if diags.HasErrors() {
log.Printf("[TRACE] vertex %q: failed expanding dynamic subgraph: %s", dag.VertexName(v), err)
return
}
if g != nil {
// The subgraph should always be valid, per our normal acyclic
// graph validation rules.
if err := g.Validate(); err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Graph node has invalid dynamic subgraph",
fmt.Sprintf("The internal logic for %q generated an invalid dynamic subgraph: %s.\n\nThis is a bug in Terraform. Please report it!", dag.VertexName(v), err),
))
return
}
// If we passed validation then there is exactly one root node.
// That root node should always be "rootNode", the singleton
// root node value.
if n, err := g.Root(); err != nil || n != dag.Vertex(rootNode) {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Graph node has invalid dynamic subgraph",
fmt.Sprintf("The internal logic for %q generated an invalid dynamic subgraph: the root node is %T, which is not a suitable root node type.\n\nThis is a bug in Terraform. Please report it!", dag.VertexName(v), n),
))
return
}
// 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)
}