forked from hashicorp/terraform
/
graph.go
374 lines (307 loc) · 9.9 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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
package terraform
import (
"fmt"
"log"
"runtime/debug"
"strings"
"sync"
"github.com/hashicorp/terraform/dag"
)
// RootModuleName is the name given to the root module implicitly.
const RootModuleName = "root"
// RootModulePath is the path for the root module.
var RootModulePath = []string{RootModuleName}
// Graph represents the graph that Terraform uses to represent resources
// and their dependencies. Each graph represents only one module, but it
// can contain further modules, which themselves have their own graph.
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.
// The root is represented by a single element list containing
// RootModuleName
Path []string
// annotations are the annotations that are added to vertices. Annotations
// are arbitrary metadata taht is used for various logic. Annotations
// should have unique keys that are referenced via constants.
annotations map[dag.Vertex]map[string]interface{}
// dependableMap is a lookaside table for fast lookups for connecting
// dependencies by their GraphNodeDependable value to avoid O(n^3)-like
// situations and turn them into O(1) with respect to the number of new
// edges.
dependableMap map[string]dag.Vertex
// 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
once sync.Once
}
func (g *Graph) DirectedGraph() dag.Grapher {
return &g.AcyclicGraph
}
// Annotations returns the annotations that are configured for the
// given vertex. The map is guaranteed to be non-nil but may be empty.
//
// The returned map may be modified to modify the annotations of the
// vertex.
func (g *Graph) Annotations(v dag.Vertex) map[string]interface{} {
g.once.Do(g.init)
// If this vertex isn't in the graph, then just return an empty map
if !g.HasVertex(v) {
return map[string]interface{}{}
}
// Get the map, if it doesn't exist yet then initialize it
m, ok := g.annotations[v]
if !ok {
m = make(map[string]interface{})
g.annotations[v] = m
}
return m
}
// Add is the same as dag.Graph.Add.
func (g *Graph) Add(v dag.Vertex) dag.Vertex {
g.once.Do(g.init)
// Call upwards to add it to the actual graph
g.Graph.Add(v)
// If this is a depend-able node, then store the lookaside info
if dv, ok := v.(GraphNodeDependable); ok {
for _, n := range dv.DependableName() {
g.dependableMap[n] = v
}
}
// If this initializes annotations, then do that
if av, ok := v.(GraphNodeAnnotationInit); ok {
as := g.Annotations(v)
for k, v := range av.AnnotationInit() {
as[k] = v
}
}
return v
}
// Remove is the same as dag.Graph.Remove
func (g *Graph) Remove(v dag.Vertex) dag.Vertex {
g.once.Do(g.init)
// If this is a depend-able node, then remove the lookaside info
if dv, ok := v.(GraphNodeDependable); ok {
for _, n := range dv.DependableName() {
delete(g.dependableMap, n)
}
}
// Remove the annotations
delete(g.annotations, v)
// Call upwards to remove it from the actual graph
return g.Graph.Remove(v)
}
// Replace is the same as dag.Graph.Replace
func (g *Graph) Replace(o, n dag.Vertex) bool {
g.once.Do(g.init)
// Go through and update our lookaside to point to the new vertex
for k, v := range g.dependableMap {
if v == o {
if _, ok := n.(GraphNodeDependable); ok {
g.dependableMap[k] = n
} else {
delete(g.dependableMap, k)
}
}
}
// Move the annotation if it exists
if m, ok := g.annotations[o]; ok {
g.annotations[n] = m
delete(g.annotations, o)
}
return g.Graph.Replace(o, n)
}
// ConnectDependent connects a GraphNodeDependent to all of its
// GraphNodeDependables. It returns the list of dependents it was
// unable to connect to.
func (g *Graph) ConnectDependent(raw dag.Vertex) []string {
v, ok := raw.(GraphNodeDependent)
if !ok {
return nil
}
return g.ConnectTo(v, v.DependentOn())
}
// ConnectDependents goes through the graph, connecting all the
// GraphNodeDependents to GraphNodeDependables. This is safe to call
// multiple times.
//
// To get details on whether dependencies could be found/made, the more
// specific ConnectDependent should be used.
func (g *Graph) ConnectDependents() {
for _, v := range g.Vertices() {
if dv, ok := v.(GraphNodeDependent); ok {
g.ConnectDependent(dv)
}
}
}
// ConnectFrom creates an edge by finding the source from a DependableName
// and connecting it to the specific vertex.
func (g *Graph) ConnectFrom(source string, target dag.Vertex) {
g.once.Do(g.init)
if source := g.dependableMap[source]; source != nil {
g.Connect(dag.BasicEdge(source, target))
}
}
// ConnectTo connects a vertex to a raw string of targets that are the
// result of DependableName, and returns the list of targets that are missing.
func (g *Graph) ConnectTo(v dag.Vertex, targets []string) []string {
g.once.Do(g.init)
var missing []string
for _, t := range targets {
if dest := g.dependableMap[t]; dest != nil {
g.Connect(dag.BasicEdge(v, dest))
} else {
missing = append(missing, t)
}
}
return missing
}
// Dependable finds the vertices in the graph that have the given dependable
// names and returns them.
func (g *Graph) Dependable(n string) dag.Vertex {
// TODO: do we need this?
return nil
}
// 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) error {
return g.walk(walker)
}
func (g *Graph) init() {
if g.annotations == nil {
g.annotations = make(map[dag.Vertex]map[string]interface{})
}
if g.dependableMap == nil {
g.dependableMap = make(map[string]dag.Vertex)
}
}
func (g *Graph) walk(walker GraphWalker) error {
// The callbacks for enter/exiting a graph
ctx := walker.EnterPath(g.Path)
defer walker.ExitPath(g.Path)
// Get the path for logs
path := strings.Join(ctx.Path(), ".")
// Determine if our walker is a panic wrapper
panicwrap, ok := walker.(GraphWalkerPanicwrapper)
if !ok {
panicwrap = nil // just to be sure
}
debugName := "walk-graph.json"
if g.debugName != "" {
debugName = g.debugName + "-" + debugName
}
debugBuf := dbug.NewFileWriter(debugName)
g.SetDebugWriter(debugBuf)
defer debugBuf.Close()
// Walk the graph.
var walkFn dag.WalkFunc
walkFn = func(v dag.Vertex) (rerr error) {
log.Printf("[DEBUG] vertex '%s.%s': walking", path, dag.VertexName(v))
g.DebugVisitInfo(v, g.debugName)
// If we have a panic wrap GraphWalker and a panic occurs, recover
// and call that. We ensure the return value is an error, however,
// so that future nodes are not called.
defer func() {
// If no panicwrap, do nothing
if panicwrap == nil {
return
}
// If no panic, do nothing
err := recover()
if err == nil {
return
}
// Modify the return value to show the error
rerr = fmt.Errorf("vertex %q captured panic: %s\n\n%s",
dag.VertexName(v), err, debug.Stack())
// Call the panic wrapper
panicwrap.Panic(v, err)
}()
walker.EnterVertex(v)
defer walker.ExitVertex(v, rerr)
// 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(normalizeModulePath(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(
"%s.%s (%T): nil eval tree", path, dag.VertexName(v), v))
}
// Allow the walker to change our tree if needed. Eval,
// then callback with the output.
log.Printf("[DEBUG] vertex '%s.%s': evaluating", path, dag.VertexName(v))
g.DebugVertexInfo(v, fmt.Sprintf("evaluating %T(%s)", v, path))
tree = walker.EnterEvalTree(v, tree)
output, err := Eval(tree, vertexCtx)
if rerr = walker.ExitEvalTree(v, output, err); rerr != nil {
return
}
}
// If the node is dynamically expanded, then expand it
if ev, ok := v.(GraphNodeDynamicExpandable); ok {
log.Printf(
"[DEBUG] vertex '%s.%s': expanding/walking dynamic subgraph",
path,
dag.VertexName(v))
g.DebugVertexInfo(v, fmt.Sprintf("expanding %T(%s)", v, path))
g, err := ev.DynamicExpand(vertexCtx)
if err != nil {
rerr = err
return
}
if g != nil {
// Walk the subgraph
if rerr = g.walk(walker); rerr != nil {
return
}
}
}
// If the node has a subgraph, then walk the subgraph
if sn, ok := v.(GraphNodeSubgraph); ok {
log.Printf(
"[DEBUG] vertex '%s.%s': walking subgraph",
path,
dag.VertexName(v))
g.DebugVertexInfo(v, fmt.Sprintf("subgraph: %T(%s)", v, path))
if rerr = sn.Subgraph().(*Graph).walk(walker); rerr != nil {
return
}
}
return nil
}
return g.AcyclicGraph.Walk(walkFn)
}
// GraphNodeAnnotationInit is an interface that allows a node to
// initialize it's annotations.
//
// AnnotationInit will be called _once_ when the node is added to a
// graph for the first time and is expected to return it's initial
// annotations.
type GraphNodeAnnotationInit interface {
AnnotationInit() map[string]interface{}
}
// GraphNodeDependable is an interface which says that a node can be
// depended on (an edge can be placed between this node and another) according
// to the well-known name returned by DependableName.
//
// DependableName can return multiple names it is known by.
type GraphNodeDependable interface {
DependableName() []string
}
// GraphNodeDependent is an interface which says that a node depends
// on another GraphNodeDependable by some name. By implementing this
// interface, Graph.ConnectDependents() can be called multiple times
// safely and efficiently.
type GraphNodeDependent interface {
DependentOn() []string
}