]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "log" | |
bae9f6d2 | 6 | |
107c1cdb ND |
7 | "github.com/hashicorp/hcl2/hcl" |
8 | "github.com/hashicorp/terraform/configs/configschema" | |
9 | "github.com/hashicorp/terraform/lang" | |
10 | ||
11 | "github.com/hashicorp/terraform/addrs" | |
bae9f6d2 JC |
12 | "github.com/hashicorp/terraform/config" |
13 | "github.com/hashicorp/terraform/dag" | |
14 | ) | |
15 | ||
16 | // GraphNodeReferenceable must be implemented by any node that represents | |
17 | // a Terraform thing that can be referenced (resource, module, etc.). | |
18 | // | |
19 | // Even if the thing has no name, this should return an empty list. By | |
20 | // implementing this and returning a non-nil result, you say that this CAN | |
21 | // be referenced and other methods of referencing may still be possible (such | |
22 | // as by path!) | |
23 | type GraphNodeReferenceable interface { | |
107c1cdb ND |
24 | GraphNodeSubPath |
25 | ||
26 | // ReferenceableAddrs returns a list of addresses through which this can be | |
27 | // referenced. | |
28 | ReferenceableAddrs() []addrs.Referenceable | |
bae9f6d2 JC |
29 | } |
30 | ||
31 | // GraphNodeReferencer must be implemented by nodes that reference other | |
32 | // Terraform items and therefore depend on them. | |
33 | type GraphNodeReferencer interface { | |
107c1cdb ND |
34 | GraphNodeSubPath |
35 | ||
36 | // References returns a list of references made by this node, which | |
37 | // include both a referenced address and source location information for | |
38 | // the reference. | |
39 | References() []*addrs.Reference | |
bae9f6d2 JC |
40 | } |
41 | ||
107c1cdb ND |
42 | // GraphNodeReferenceOutside is an interface that can optionally be implemented. |
43 | // A node that implements it can specify that its own referenceable addresses | |
44 | // and/or the addresses it references are in a different module than the | |
45 | // node itself. | |
46 | // | |
47 | // Any referenceable addresses returned by ReferenceableAddrs are interpreted | |
48 | // relative to the returned selfPath. | |
bae9f6d2 | 49 | // |
107c1cdb ND |
50 | // Any references returned by References are interpreted relative to the |
51 | // returned referencePath. | |
bae9f6d2 | 52 | // |
107c1cdb ND |
53 | // It is valid but not required for either of these paths to match what is |
54 | // returned by method Path, though if both match the main Path then there | |
55 | // is no reason to implement this method. | |
56 | // | |
57 | // The primary use-case for this is the nodes representing module input | |
58 | // variables, since their expressions are resolved in terms of their calling | |
59 | // module, but they are still referenced from their own module. | |
60 | type GraphNodeReferenceOutside interface { | |
61 | // ReferenceOutside returns a path in which any references from this node | |
62 | // are resolved. | |
63 | ReferenceOutside() (selfPath, referencePath addrs.ModuleInstance) | |
bae9f6d2 JC |
64 | } |
65 | ||
66 | // ReferenceTransformer is a GraphTransformer that connects all the | |
67 | // nodes that reference each other in order to form the proper ordering. | |
68 | type ReferenceTransformer struct{} | |
69 | ||
70 | func (t *ReferenceTransformer) Transform(g *Graph) error { | |
71 | // Build a reference map so we can efficiently look up the references | |
72 | vs := g.Vertices() | |
73 | m := NewReferenceMap(vs) | |
74 | ||
75 | // Find the things that reference things and connect them | |
76 | for _, v := range vs { | |
77 | parents, _ := m.References(v) | |
78 | parentsDbg := make([]string, len(parents)) | |
79 | for i, v := range parents { | |
80 | parentsDbg[i] = dag.VertexName(v) | |
81 | } | |
82 | log.Printf( | |
83 | "[DEBUG] ReferenceTransformer: %q references: %v", | |
84 | dag.VertexName(v), parentsDbg) | |
85 | ||
86 | for _, parent := range parents { | |
87 | g.Connect(dag.BasicEdge(v, parent)) | |
88 | } | |
89 | } | |
90 | ||
91 | return nil | |
92 | } | |
93 | ||
15c0b25d AP |
94 | // DestroyReferenceTransformer is a GraphTransformer that reverses the edges |
95 | // for locals and outputs that depend on other nodes which will be | |
96 | // removed during destroy. If a destroy node is evaluated before the local or | |
97 | // output value, it will be removed from the state, and the later interpolation | |
98 | // will fail. | |
99 | type DestroyValueReferenceTransformer struct{} | |
100 | ||
101 | func (t *DestroyValueReferenceTransformer) Transform(g *Graph) error { | |
102 | vs := g.Vertices() | |
103 | for _, v := range vs { | |
104 | switch v.(type) { | |
105 | case *NodeApplyableOutput, *NodeLocal: | |
106 | // OK | |
107 | default: | |
108 | continue | |
109 | } | |
110 | ||
111 | // reverse any outgoing edges so that the value is evaluated first. | |
112 | for _, e := range g.EdgesFrom(v) { | |
113 | target := e.Target() | |
114 | ||
115 | // only destroy nodes will be evaluated in reverse | |
116 | if _, ok := target.(GraphNodeDestroyer); !ok { | |
117 | continue | |
118 | } | |
119 | ||
120 | log.Printf("[TRACE] output dep: %s", dag.VertexName(target)) | |
121 | ||
122 | g.RemoveEdge(e) | |
123 | g.Connect(&DestroyEdge{S: target, T: v}) | |
124 | } | |
125 | } | |
126 | ||
127 | return nil | |
128 | } | |
129 | ||
130 | // PruneUnusedValuesTransformer is s GraphTransformer that removes local and | |
131 | // output values which are not referenced in the graph. Since outputs and | |
132 | // locals always need to be evaluated, if they reference a resource that is not | |
133 | // available in the state the interpolation could fail. | |
134 | type PruneUnusedValuesTransformer struct{} | |
135 | ||
136 | func (t *PruneUnusedValuesTransformer) Transform(g *Graph) error { | |
137 | // this might need multiple runs in order to ensure that pruning a value | |
138 | // doesn't effect a previously checked value. | |
139 | for removed := 0; ; removed = 0 { | |
140 | for _, v := range g.Vertices() { | |
141 | switch v.(type) { | |
142 | case *NodeApplyableOutput, *NodeLocal: | |
143 | // OK | |
144 | default: | |
145 | continue | |
146 | } | |
147 | ||
148 | dependants := g.UpEdges(v) | |
149 | ||
150 | switch dependants.Len() { | |
151 | case 0: | |
152 | // nothing at all depends on this | |
153 | g.Remove(v) | |
154 | removed++ | |
155 | case 1: | |
156 | // because an output's destroy node always depends on the output, | |
157 | // we need to check for the case of a single destroy node. | |
158 | d := dependants.List()[0] | |
159 | if _, ok := d.(*NodeDestroyableOutput); ok { | |
160 | g.Remove(v) | |
161 | removed++ | |
162 | } | |
163 | } | |
164 | } | |
165 | if removed == 0 { | |
166 | break | |
167 | } | |
168 | } | |
169 | ||
170 | return nil | |
171 | } | |
172 | ||
bae9f6d2 JC |
173 | // ReferenceMap is a structure that can be used to efficiently check |
174 | // for references on a graph. | |
175 | type ReferenceMap struct { | |
107c1cdb ND |
176 | // vertices is a map from internal reference keys (as produced by the |
177 | // mapKey method) to one or more vertices that are identified by each key. | |
178 | // | |
179 | // A particular reference key might actually identify multiple vertices, | |
180 | // e.g. in situations where one object is contained inside another. | |
181 | vertices map[string][]dag.Vertex | |
182 | ||
183 | // edges is a map whose keys are a subset of the internal reference keys | |
184 | // from "vertices", and whose values are the nodes that refer to each | |
185 | // key. The values in this map are the referrers, while values in | |
186 | // "verticies" are the referents. The keys in both cases are referents. | |
187 | edges map[string][]dag.Vertex | |
bae9f6d2 JC |
188 | } |
189 | ||
107c1cdb ND |
190 | // References returns the set of vertices that the given vertex refers to, |
191 | // and any referenced addresses that do not have corresponding vertices. | |
192 | func (m *ReferenceMap) References(v dag.Vertex) ([]dag.Vertex, []addrs.Referenceable) { | |
bae9f6d2 JC |
193 | rn, ok := v.(GraphNodeReferencer) |
194 | if !ok { | |
195 | return nil, nil | |
196 | } | |
107c1cdb ND |
197 | if _, ok := v.(GraphNodeSubPath); !ok { |
198 | return nil, nil | |
199 | } | |
bae9f6d2 JC |
200 | |
201 | var matches []dag.Vertex | |
107c1cdb ND |
202 | var missing []addrs.Referenceable |
203 | ||
204 | for _, ref := range rn.References() { | |
205 | subject := ref.Subject | |
206 | ||
207 | key := m.referenceMapKey(v, subject) | |
208 | if _, exists := m.vertices[key]; !exists { | |
209 | // If what we were looking for was a ResourceInstance then we | |
210 | // might be in a resource-oriented graph rather than an | |
211 | // instance-oriented graph, and so we'll see if we have the | |
212 | // resource itself instead. | |
213 | switch ri := subject.(type) { | |
214 | case addrs.ResourceInstance: | |
215 | subject = ri.ContainingResource() | |
216 | case addrs.ResourceInstancePhase: | |
217 | subject = ri.ContainingResource() | |
bae9f6d2 | 218 | } |
107c1cdb ND |
219 | key = m.referenceMapKey(v, subject) |
220 | } | |
bae9f6d2 | 221 | |
107c1cdb ND |
222 | vertices := m.vertices[key] |
223 | for _, rv := range vertices { | |
224 | // don't include self-references | |
225 | if rv == v { | |
226 | continue | |
bae9f6d2 | 227 | } |
107c1cdb | 228 | matches = append(matches, rv) |
bae9f6d2 | 229 | } |
107c1cdb ND |
230 | if len(vertices) == 0 { |
231 | missing = append(missing, ref.Subject) | |
bae9f6d2 JC |
232 | } |
233 | } | |
234 | ||
235 | return matches, missing | |
236 | } | |
237 | ||
107c1cdb ND |
238 | // Referrers returns the set of vertices that refer to the given vertex. |
239 | func (m *ReferenceMap) Referrers(v dag.Vertex) []dag.Vertex { | |
bae9f6d2 JC |
240 | rn, ok := v.(GraphNodeReferenceable) |
241 | if !ok { | |
242 | return nil | |
243 | } | |
107c1cdb ND |
244 | sp, ok := v.(GraphNodeSubPath) |
245 | if !ok { | |
246 | return nil | |
247 | } | |
bae9f6d2 JC |
248 | |
249 | var matches []dag.Vertex | |
107c1cdb ND |
250 | for _, addr := range rn.ReferenceableAddrs() { |
251 | key := m.mapKey(sp.Path(), addr) | |
252 | referrers, ok := m.edges[key] | |
bae9f6d2 JC |
253 | if !ok { |
254 | continue | |
255 | } | |
256 | ||
107c1cdb ND |
257 | // If the referrer set includes our own given vertex then we skip, |
258 | // since we don't want to return self-references. | |
bae9f6d2 | 259 | selfRef := false |
107c1cdb | 260 | for _, p := range referrers { |
bae9f6d2 JC |
261 | if p == v { |
262 | selfRef = true | |
263 | break | |
264 | } | |
265 | } | |
266 | if selfRef { | |
267 | continue | |
268 | } | |
269 | ||
107c1cdb | 270 | matches = append(matches, referrers...) |
bae9f6d2 JC |
271 | } |
272 | ||
273 | return matches | |
274 | } | |
275 | ||
107c1cdb ND |
276 | func (m *ReferenceMap) mapKey(path addrs.ModuleInstance, addr addrs.Referenceable) string { |
277 | return fmt.Sprintf("%s|%s", path.String(), addr.String()) | |
278 | } | |
279 | ||
280 | // vertexReferenceablePath returns the path in which the given vertex can be | |
281 | // referenced. This is the path that its results from ReferenceableAddrs | |
282 | // are considered to be relative to. | |
283 | // | |
284 | // Only GraphNodeSubPath implementations can be referenced, so this method will | |
285 | // panic if the given vertex does not implement that interface. | |
286 | func (m *ReferenceMap) vertexReferenceablePath(v dag.Vertex) addrs.ModuleInstance { | |
287 | sp, ok := v.(GraphNodeSubPath) | |
288 | if !ok { | |
289 | // Only nodes with paths can participate in a reference map. | |
290 | panic(fmt.Errorf("vertexMapKey on vertex type %T which doesn't implement GraphNodeSubPath", sp)) | |
bae9f6d2 JC |
291 | } |
292 | ||
107c1cdb ND |
293 | if outside, ok := v.(GraphNodeReferenceOutside); ok { |
294 | // Vertex is referenced from a different module than where it was | |
295 | // declared. | |
296 | path, _ := outside.ReferenceOutside() | |
297 | return path | |
298 | } | |
299 | ||
300 | // Vertex is referenced from the same module as where it was declared. | |
301 | return sp.Path() | |
302 | } | |
303 | ||
304 | // vertexReferencePath returns the path in which references _from_ the given | |
305 | // vertex must be interpreted. | |
306 | // | |
307 | // Only GraphNodeSubPath implementations can have references, so this method | |
308 | // will panic if the given vertex does not implement that interface. | |
309 | func vertexReferencePath(referrer dag.Vertex) addrs.ModuleInstance { | |
310 | sp, ok := referrer.(GraphNodeSubPath) | |
311 | if !ok { | |
312 | // Only nodes with paths can participate in a reference map. | |
313 | panic(fmt.Errorf("vertexReferencePath on vertex type %T which doesn't implement GraphNodeSubPath", sp)) | |
314 | } | |
315 | ||
316 | var path addrs.ModuleInstance | |
317 | if outside, ok := referrer.(GraphNodeReferenceOutside); ok { | |
318 | // Vertex makes references to objects in a different module than where | |
319 | // it was declared. | |
320 | _, path = outside.ReferenceOutside() | |
321 | return path | |
bae9f6d2 JC |
322 | } |
323 | ||
107c1cdb ND |
324 | // Vertex makes references to objects in the same module as where it |
325 | // was declared. | |
326 | return sp.Path() | |
327 | } | |
328 | ||
329 | // referenceMapKey produces keys for the "edges" map. "referrer" is the vertex | |
330 | // that the reference is from, and "addr" is the address of the object being | |
331 | // referenced. | |
332 | // | |
333 | // The result is an opaque string that includes both the address of the given | |
334 | // object and the address of the module instance that object belongs to. | |
335 | // | |
336 | // Only GraphNodeSubPath implementations can be referrers, so this method will | |
337 | // panic if the given vertex does not implement that interface. | |
338 | func (m *ReferenceMap) referenceMapKey(referrer dag.Vertex, addr addrs.Referenceable) string { | |
339 | path := vertexReferencePath(referrer) | |
340 | return m.mapKey(path, addr) | |
bae9f6d2 JC |
341 | } |
342 | ||
343 | // NewReferenceMap is used to create a new reference map for the | |
344 | // given set of vertices. | |
345 | func NewReferenceMap(vs []dag.Vertex) *ReferenceMap { | |
346 | var m ReferenceMap | |
347 | ||
348 | // Build the lookup table | |
107c1cdb | 349 | vertices := make(map[string][]dag.Vertex) |
bae9f6d2 | 350 | for _, v := range vs { |
107c1cdb ND |
351 | _, ok := v.(GraphNodeSubPath) |
352 | if !ok { | |
353 | // Only nodes with paths can participate in a reference map. | |
354 | continue | |
355 | } | |
356 | ||
bae9f6d2 JC |
357 | // We're only looking for referenceable nodes |
358 | rn, ok := v.(GraphNodeReferenceable) | |
359 | if !ok { | |
360 | continue | |
361 | } | |
362 | ||
107c1cdb ND |
363 | path := m.vertexReferenceablePath(v) |
364 | ||
bae9f6d2 | 365 | // Go through and cache them |
107c1cdb ND |
366 | for _, addr := range rn.ReferenceableAddrs() { |
367 | key := m.mapKey(path, addr) | |
368 | vertices[key] = append(vertices[key], v) | |
bae9f6d2 JC |
369 | } |
370 | ||
107c1cdb ND |
371 | // Any node can be referenced by the address of the module it belongs |
372 | // to or any of that module's ancestors. | |
373 | for _, addr := range path.Ancestors()[1:] { | |
374 | // Can be referenced either as the specific call instance (with | |
375 | // an instance key) or as the bare module call itself (the "module" | |
376 | // block in the parent module that created the instance). | |
377 | callPath, call := addr.Call() | |
378 | callInstPath, callInst := addr.CallInstance() | |
379 | callKey := m.mapKey(callPath, call) | |
380 | callInstKey := m.mapKey(callInstPath, callInst) | |
381 | vertices[callKey] = append(vertices[callKey], v) | |
382 | vertices[callInstKey] = append(vertices[callInstKey], v) | |
bae9f6d2 JC |
383 | } |
384 | } | |
385 | ||
386 | // Build the lookup table for referenced by | |
107c1cdb | 387 | edges := make(map[string][]dag.Vertex) |
bae9f6d2 | 388 | for _, v := range vs { |
107c1cdb ND |
389 | _, ok := v.(GraphNodeSubPath) |
390 | if !ok { | |
391 | // Only nodes with paths can participate in a reference map. | |
392 | continue | |
393 | } | |
394 | ||
bae9f6d2 JC |
395 | rn, ok := v.(GraphNodeReferencer) |
396 | if !ok { | |
107c1cdb | 397 | // We're only looking for referenceable nodes |
bae9f6d2 JC |
398 | continue |
399 | } | |
400 | ||
401 | // Go through and cache them | |
107c1cdb ND |
402 | for _, ref := range rn.References() { |
403 | if ref.Subject == nil { | |
404 | // Should never happen | |
405 | panic(fmt.Sprintf("%T.References returned reference with nil subject", rn)) | |
406 | } | |
407 | key := m.referenceMapKey(v, ref.Subject) | |
408 | edges[key] = append(edges[key], v) | |
bae9f6d2 JC |
409 | } |
410 | } | |
411 | ||
107c1cdb ND |
412 | m.vertices = vertices |
413 | m.edges = edges | |
bae9f6d2 JC |
414 | return &m |
415 | } | |
416 | ||
bae9f6d2 JC |
417 | // ReferencesFromConfig returns the references that a configuration has |
418 | // based on the interpolated variables in a configuration. | |
107c1cdb ND |
419 | func ReferencesFromConfig(body hcl.Body, schema *configschema.Block) []*addrs.Reference { |
420 | if body == nil { | |
421 | return nil | |
bae9f6d2 | 422 | } |
107c1cdb ND |
423 | refs, _ := lang.ReferencesInBlock(body, schema) |
424 | return refs | |
bae9f6d2 JC |
425 | } |
426 | ||
427 | // ReferenceFromInterpolatedVar returns the reference from this variable, | |
428 | // or an empty string if there is no reference. | |
429 | func ReferenceFromInterpolatedVar(v config.InterpolatedVariable) []string { | |
430 | switch v := v.(type) { | |
431 | case *config.ModuleVariable: | |
432 | return []string{fmt.Sprintf("module.%s.output.%s", v.Name, v.Field)} | |
433 | case *config.ResourceVariable: | |
434 | id := v.ResourceId() | |
435 | ||
436 | // If we have a multi-reference (splat), then we depend on ALL | |
437 | // resources with this type/name. | |
438 | if v.Multi && v.Index == -1 { | |
439 | return []string{fmt.Sprintf("%s.*", id)} | |
440 | } | |
441 | ||
442 | // Otherwise, we depend on a specific index. | |
443 | idx := v.Index | |
444 | if !v.Multi || v.Index == -1 { | |
445 | idx = 0 | |
446 | } | |
447 | ||
448 | // Depend on the index, as well as "N" which represents the | |
449 | // un-expanded set of resources. | |
450 | return []string{fmt.Sprintf("%s.%d/%s.N", id, idx, id)} | |
451 | case *config.UserVariable: | |
452 | return []string{fmt.Sprintf("var.%s", v.Name)} | |
15c0b25d AP |
453 | case *config.LocalVariable: |
454 | return []string{fmt.Sprintf("local.%s", v.Name)} | |
bae9f6d2 JC |
455 | default: |
456 | return nil | |
457 | } | |
458 | } | |
459 | ||
107c1cdb ND |
460 | // appendResourceDestroyReferences identifies resource and resource instance |
461 | // references in the given slice and appends to it the "destroy-phase" | |
462 | // equivalents of those references, returning the result. | |
463 | // | |
464 | // This can be used in the References implementation for a node which must also | |
465 | // depend on the destruction of anything it references. | |
466 | func appendResourceDestroyReferences(refs []*addrs.Reference) []*addrs.Reference { | |
467 | given := refs | |
468 | for _, ref := range given { | |
469 | switch tr := ref.Subject.(type) { | |
470 | case addrs.Resource: | |
471 | newRef := *ref // shallow copy | |
472 | newRef.Subject = tr.Phase(addrs.ResourceInstancePhaseDestroy) | |
473 | refs = append(refs, &newRef) | |
474 | case addrs.ResourceInstance: | |
475 | newRef := *ref // shallow copy | |
476 | newRef.Subject = tr.Phase(addrs.ResourceInstancePhaseDestroy) | |
477 | refs = append(refs, &newRef) | |
478 | } | |
bae9f6d2 | 479 | } |
107c1cdb ND |
480 | return refs |
481 | } | |
bae9f6d2 | 482 | |
107c1cdb ND |
483 | func modulePrefixStr(p addrs.ModuleInstance) string { |
484 | return p.String() | |
bae9f6d2 JC |
485 | } |
486 | ||
487 | func modulePrefixList(result []string, prefix string) []string { | |
488 | if prefix != "" { | |
489 | for i, v := range result { | |
490 | result[i] = fmt.Sprintf("%s.%s", prefix, v) | |
491 | } | |
492 | } | |
493 | ||
494 | return result | |
495 | } |