]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "log" | |
bae9f6d2 | 6 | |
107c1cdb | 7 | "github.com/hashicorp/terraform/tfdiags" |
bae9f6d2 | 8 | |
107c1cdb | 9 | "github.com/hashicorp/terraform/addrs" |
bae9f6d2 | 10 | |
107c1cdb ND |
11 | "github.com/hashicorp/terraform/dag" |
12 | ) | |
bae9f6d2 JC |
13 | |
14 | // Graph represents the graph that Terraform uses to represent resources | |
15 | // and their dependencies. | |
16 | type Graph struct { | |
17 | // Graph is the actual DAG. This is embedded so you can call the DAG | |
18 | // methods directly. | |
19 | dag.AcyclicGraph | |
20 | ||
21 | // Path is the path in the module tree that this Graph represents. | |
107c1cdb | 22 | Path addrs.ModuleInstance |
bae9f6d2 JC |
23 | |
24 | // debugName is a name for reference in the debug output. This is usually | |
25 | // to indicate what topmost builder was, and if this graph is a shadow or | |
26 | // not. | |
27 | debugName string | |
28 | } | |
29 | ||
30 | func (g *Graph) DirectedGraph() dag.Grapher { | |
31 | return &g.AcyclicGraph | |
32 | } | |
33 | ||
34 | // Walk walks the graph with the given walker for callbacks. The graph | |
35 | // will be walked with full parallelism, so the walker should expect | |
36 | // to be called in concurrently. | |
107c1cdb | 37 | func (g *Graph) Walk(walker GraphWalker) tfdiags.Diagnostics { |
bae9f6d2 JC |
38 | return g.walk(walker) |
39 | } | |
40 | ||
107c1cdb | 41 | func (g *Graph) walk(walker GraphWalker) tfdiags.Diagnostics { |
bae9f6d2 JC |
42 | // The callbacks for enter/exiting a graph |
43 | ctx := walker.EnterPath(g.Path) | |
44 | defer walker.ExitPath(g.Path) | |
45 | ||
46 | // Get the path for logs | |
107c1cdb | 47 | path := ctx.Path().String() |
bae9f6d2 JC |
48 | |
49 | debugName := "walk-graph.json" | |
50 | if g.debugName != "" { | |
51 | debugName = g.debugName + "-" + debugName | |
52 | } | |
53 | ||
bae9f6d2 JC |
54 | // Walk the graph. |
55 | var walkFn dag.WalkFunc | |
107c1cdb ND |
56 | walkFn = func(v dag.Vertex) (diags tfdiags.Diagnostics) { |
57 | log.Printf("[TRACE] vertex %q: starting visit (%T)", dag.VertexName(v), v) | |
bae9f6d2 JC |
58 | g.DebugVisitInfo(v, g.debugName) |
59 | ||
bae9f6d2 | 60 | defer func() { |
107c1cdb | 61 | log.Printf("[TRACE] vertex %q: visit complete", dag.VertexName(v)) |
bae9f6d2 JC |
62 | }() |
63 | ||
64 | walker.EnterVertex(v) | |
107c1cdb | 65 | defer walker.ExitVertex(v, diags) |
bae9f6d2 JC |
66 | |
67 | // vertexCtx is the context that we use when evaluating. This | |
68 | // is normally the context of our graph but can be overridden | |
69 | // with a GraphNodeSubPath impl. | |
70 | vertexCtx := ctx | |
71 | if pn, ok := v.(GraphNodeSubPath); ok && len(pn.Path()) > 0 { | |
107c1cdb | 72 | vertexCtx = walker.EnterPath(pn.Path()) |
bae9f6d2 JC |
73 | defer walker.ExitPath(pn.Path()) |
74 | } | |
75 | ||
76 | // If the node is eval-able, then evaluate it. | |
77 | if ev, ok := v.(GraphNodeEvalable); ok { | |
78 | tree := ev.EvalTree() | |
79 | if tree == nil { | |
107c1cdb | 80 | panic(fmt.Sprintf("%q (%T): nil eval tree", dag.VertexName(v), v)) |
bae9f6d2 JC |
81 | } |
82 | ||
83 | // Allow the walker to change our tree if needed. Eval, | |
84 | // then callback with the output. | |
107c1cdb | 85 | log.Printf("[TRACE] vertex %q: evaluating", dag.VertexName(v)) |
bae9f6d2 JC |
86 | |
87 | g.DebugVertexInfo(v, fmt.Sprintf("evaluating %T(%s)", v, path)) | |
88 | ||
89 | tree = walker.EnterEvalTree(v, tree) | |
90 | output, err := Eval(tree, vertexCtx) | |
107c1cdb ND |
91 | diags = diags.Append(walker.ExitEvalTree(v, output, err)) |
92 | if diags.HasErrors() { | |
bae9f6d2 JC |
93 | return |
94 | } | |
95 | } | |
96 | ||
97 | // If the node is dynamically expanded, then expand it | |
98 | if ev, ok := v.(GraphNodeDynamicExpandable); ok { | |
107c1cdb | 99 | log.Printf("[TRACE] vertex %q: expanding dynamic subgraph", dag.VertexName(v)) |
bae9f6d2 JC |
100 | |
101 | g.DebugVertexInfo(v, fmt.Sprintf("expanding %T(%s)", v, path)) | |
102 | ||
103 | g, err := ev.DynamicExpand(vertexCtx) | |
104 | if err != nil { | |
107c1cdb | 105 | diags = diags.Append(err) |
bae9f6d2 JC |
106 | return |
107 | } | |
108 | if g != nil { | |
109 | // Walk the subgraph | |
107c1cdb ND |
110 | log.Printf("[TRACE] vertex %q: entering dynamic subgraph", dag.VertexName(v)) |
111 | subDiags := g.walk(walker) | |
112 | diags = diags.Append(subDiags) | |
113 | if subDiags.HasErrors() { | |
114 | log.Printf("[TRACE] vertex %q: dynamic subgraph encountered errors", dag.VertexName(v)) | |
bae9f6d2 JC |
115 | return |
116 | } | |
107c1cdb ND |
117 | log.Printf("[TRACE] vertex %q: dynamic subgraph completed successfully", dag.VertexName(v)) |
118 | } else { | |
119 | log.Printf("[TRACE] vertex %q: produced no dynamic subgraph", dag.VertexName(v)) | |
bae9f6d2 JC |
120 | } |
121 | } | |
122 | ||
123 | // If the node has a subgraph, then walk the subgraph | |
124 | if sn, ok := v.(GraphNodeSubgraph); ok { | |
107c1cdb | 125 | log.Printf("[TRACE] vertex %q: entering static subgraph", dag.VertexName(v)) |
bae9f6d2 JC |
126 | |
127 | g.DebugVertexInfo(v, fmt.Sprintf("subgraph: %T(%s)", v, path)) | |
128 | ||
107c1cdb ND |
129 | subDiags := sn.Subgraph().(*Graph).walk(walker) |
130 | if subDiags.HasErrors() { | |
131 | log.Printf("[TRACE] vertex %q: static subgraph encountered errors", dag.VertexName(v)) | |
bae9f6d2 JC |
132 | return |
133 | } | |
107c1cdb | 134 | log.Printf("[TRACE] vertex %q: static subgraph completed successfully", dag.VertexName(v)) |
bae9f6d2 JC |
135 | } |
136 | ||
107c1cdb | 137 | return |
bae9f6d2 JC |
138 | } |
139 | ||
140 | return g.AcyclicGraph.Walk(walkFn) | |
141 | } |