]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "log" | |
6 | "runtime/debug" | |
7 | "strings" | |
8 | ||
9 | "github.com/hashicorp/terraform/dag" | |
10 | ) | |
11 | ||
12 | // RootModuleName is the name given to the root module implicitly. | |
13 | const RootModuleName = "root" | |
14 | ||
15 | // RootModulePath is the path for the root module. | |
16 | var RootModulePath = []string{RootModuleName} | |
17 | ||
18 | // Graph represents the graph that Terraform uses to represent resources | |
19 | // and their dependencies. | |
20 | type Graph struct { | |
21 | // Graph is the actual DAG. This is embedded so you can call the DAG | |
22 | // methods directly. | |
23 | dag.AcyclicGraph | |
24 | ||
25 | // Path is the path in the module tree that this Graph represents. | |
26 | // The root is represented by a single element list containing | |
27 | // RootModuleName | |
28 | Path []string | |
29 | ||
30 | // debugName is a name for reference in the debug output. This is usually | |
31 | // to indicate what topmost builder was, and if this graph is a shadow or | |
32 | // not. | |
33 | debugName string | |
34 | } | |
35 | ||
36 | func (g *Graph) DirectedGraph() dag.Grapher { | |
37 | return &g.AcyclicGraph | |
38 | } | |
39 | ||
40 | // Walk walks the graph with the given walker for callbacks. The graph | |
41 | // will be walked with full parallelism, so the walker should expect | |
42 | // to be called in concurrently. | |
43 | func (g *Graph) Walk(walker GraphWalker) error { | |
44 | return g.walk(walker) | |
45 | } | |
46 | ||
47 | func (g *Graph) walk(walker GraphWalker) error { | |
48 | // The callbacks for enter/exiting a graph | |
49 | ctx := walker.EnterPath(g.Path) | |
50 | defer walker.ExitPath(g.Path) | |
51 | ||
52 | // Get the path for logs | |
53 | path := strings.Join(ctx.Path(), ".") | |
54 | ||
55 | // Determine if our walker is a panic wrapper | |
56 | panicwrap, ok := walker.(GraphWalkerPanicwrapper) | |
57 | if !ok { | |
58 | panicwrap = nil // just to be sure | |
59 | } | |
60 | ||
61 | debugName := "walk-graph.json" | |
62 | if g.debugName != "" { | |
63 | debugName = g.debugName + "-" + debugName | |
64 | } | |
65 | ||
66 | debugBuf := dbug.NewFileWriter(debugName) | |
67 | g.SetDebugWriter(debugBuf) | |
68 | defer debugBuf.Close() | |
69 | ||
70 | // Walk the graph. | |
71 | var walkFn dag.WalkFunc | |
72 | walkFn = func(v dag.Vertex) (rerr error) { | |
73 | log.Printf("[DEBUG] vertex '%s.%s': walking", path, dag.VertexName(v)) | |
74 | g.DebugVisitInfo(v, g.debugName) | |
75 | ||
76 | // If we have a panic wrap GraphWalker and a panic occurs, recover | |
77 | // and call that. We ensure the return value is an error, however, | |
78 | // so that future nodes are not called. | |
79 | defer func() { | |
80 | // If no panicwrap, do nothing | |
81 | if panicwrap == nil { | |
82 | return | |
83 | } | |
84 | ||
85 | // If no panic, do nothing | |
86 | err := recover() | |
87 | if err == nil { | |
88 | return | |
89 | } | |
90 | ||
91 | // Modify the return value to show the error | |
92 | rerr = fmt.Errorf("vertex %q captured panic: %s\n\n%s", | |
93 | dag.VertexName(v), err, debug.Stack()) | |
94 | ||
95 | // Call the panic wrapper | |
96 | panicwrap.Panic(v, err) | |
97 | }() | |
98 | ||
99 | walker.EnterVertex(v) | |
100 | defer walker.ExitVertex(v, rerr) | |
101 | ||
102 | // vertexCtx is the context that we use when evaluating. This | |
103 | // is normally the context of our graph but can be overridden | |
104 | // with a GraphNodeSubPath impl. | |
105 | vertexCtx := ctx | |
106 | if pn, ok := v.(GraphNodeSubPath); ok && len(pn.Path()) > 0 { | |
107 | vertexCtx = walker.EnterPath(normalizeModulePath(pn.Path())) | |
108 | defer walker.ExitPath(pn.Path()) | |
109 | } | |
110 | ||
111 | // If the node is eval-able, then evaluate it. | |
112 | if ev, ok := v.(GraphNodeEvalable); ok { | |
113 | tree := ev.EvalTree() | |
114 | if tree == nil { | |
115 | panic(fmt.Sprintf( | |
116 | "%s.%s (%T): nil eval tree", path, dag.VertexName(v), v)) | |
117 | } | |
118 | ||
119 | // Allow the walker to change our tree if needed. Eval, | |
120 | // then callback with the output. | |
121 | log.Printf("[DEBUG] vertex '%s.%s': evaluating", path, dag.VertexName(v)) | |
122 | ||
123 | g.DebugVertexInfo(v, fmt.Sprintf("evaluating %T(%s)", v, path)) | |
124 | ||
125 | tree = walker.EnterEvalTree(v, tree) | |
126 | output, err := Eval(tree, vertexCtx) | |
127 | if rerr = walker.ExitEvalTree(v, output, err); rerr != nil { | |
128 | return | |
129 | } | |
130 | } | |
131 | ||
132 | // If the node is dynamically expanded, then expand it | |
133 | if ev, ok := v.(GraphNodeDynamicExpandable); ok { | |
134 | log.Printf( | |
135 | "[DEBUG] vertex '%s.%s': expanding/walking dynamic subgraph", | |
136 | path, | |
137 | dag.VertexName(v)) | |
138 | ||
139 | g.DebugVertexInfo(v, fmt.Sprintf("expanding %T(%s)", v, path)) | |
140 | ||
141 | g, err := ev.DynamicExpand(vertexCtx) | |
142 | if err != nil { | |
143 | rerr = err | |
144 | return | |
145 | } | |
146 | if g != nil { | |
147 | // Walk the subgraph | |
148 | if rerr = g.walk(walker); rerr != nil { | |
149 | return | |
150 | } | |
151 | } | |
152 | } | |
153 | ||
154 | // If the node has a subgraph, then walk the subgraph | |
155 | if sn, ok := v.(GraphNodeSubgraph); ok { | |
156 | log.Printf( | |
157 | "[DEBUG] vertex '%s.%s': walking subgraph", | |
158 | path, | |
159 | dag.VertexName(v)) | |
160 | ||
161 | g.DebugVertexInfo(v, fmt.Sprintf("subgraph: %T(%s)", v, path)) | |
162 | ||
163 | if rerr = sn.Subgraph().(*Graph).walk(walker); rerr != nil { | |
164 | return | |
165 | } | |
166 | } | |
167 | ||
168 | return nil | |
169 | } | |
170 | ||
171 | return g.AcyclicGraph.Walk(walkFn) | |
172 | } |