]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/terraform/terraform/transform_reference.go
Merge pull request #27 from terraform-providers/go-modules-2019-02-22
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / terraform / transform_reference.go
CommitLineData
bae9f6d2
JC
1package terraform
2
3import (
4 "fmt"
5 "log"
6 "strings"
7
8 "github.com/hashicorp/terraform/config"
9 "github.com/hashicorp/terraform/dag"
10)
11
12// GraphNodeReferenceable must be implemented by any node that represents
13// a Terraform thing that can be referenced (resource, module, etc.).
14//
15// Even if the thing has no name, this should return an empty list. By
16// implementing this and returning a non-nil result, you say that this CAN
17// be referenced and other methods of referencing may still be possible (such
18// as by path!)
19type GraphNodeReferenceable interface {
20 // ReferenceableName is the name by which this can be referenced.
21 // This can be either just the type, or include the field. Example:
22 // "aws_instance.bar" or "aws_instance.bar.id".
23 ReferenceableName() []string
24}
25
26// GraphNodeReferencer must be implemented by nodes that reference other
27// Terraform items and therefore depend on them.
28type GraphNodeReferencer interface {
29 // References are the list of things that this node references. This
30 // can include fields or just the type, just like GraphNodeReferenceable
31 // above.
32 References() []string
33}
34
35// GraphNodeReferenceGlobal is an interface that can optionally be
36// implemented. If ReferenceGlobal returns true, then the References()
37// and ReferenceableName() must be _fully qualified_ with "module.foo.bar"
38// etc.
39//
40// This allows a node to reference and be referenced by a specific name
41// that may cross module boundaries. This can be very dangerous so use
42// this wisely.
43//
44// The primary use case for this is module boundaries (variables coming in).
45type GraphNodeReferenceGlobal interface {
46 // Set to true to signal that references and name are fully
47 // qualified. See the above docs for more information.
48 ReferenceGlobal() bool
49}
50
51// ReferenceTransformer is a GraphTransformer that connects all the
52// nodes that reference each other in order to form the proper ordering.
53type ReferenceTransformer struct{}
54
55func (t *ReferenceTransformer) Transform(g *Graph) error {
56 // Build a reference map so we can efficiently look up the references
57 vs := g.Vertices()
58 m := NewReferenceMap(vs)
59
60 // Find the things that reference things and connect them
61 for _, v := range vs {
62 parents, _ := m.References(v)
63 parentsDbg := make([]string, len(parents))
64 for i, v := range parents {
65 parentsDbg[i] = dag.VertexName(v)
66 }
67 log.Printf(
68 "[DEBUG] ReferenceTransformer: %q references: %v",
69 dag.VertexName(v), parentsDbg)
70
71 for _, parent := range parents {
72 g.Connect(dag.BasicEdge(v, parent))
73 }
74 }
75
76 return nil
77}
78
15c0b25d
AP
79// DestroyReferenceTransformer is a GraphTransformer that reverses the edges
80// for locals and outputs that depend on other nodes which will be
81// removed during destroy. If a destroy node is evaluated before the local or
82// output value, it will be removed from the state, and the later interpolation
83// will fail.
84type DestroyValueReferenceTransformer struct{}
85
86func (t *DestroyValueReferenceTransformer) Transform(g *Graph) error {
87 vs := g.Vertices()
88 for _, v := range vs {
89 switch v.(type) {
90 case *NodeApplyableOutput, *NodeLocal:
91 // OK
92 default:
93 continue
94 }
95
96 // reverse any outgoing edges so that the value is evaluated first.
97 for _, e := range g.EdgesFrom(v) {
98 target := e.Target()
99
100 // only destroy nodes will be evaluated in reverse
101 if _, ok := target.(GraphNodeDestroyer); !ok {
102 continue
103 }
104
105 log.Printf("[TRACE] output dep: %s", dag.VertexName(target))
106
107 g.RemoveEdge(e)
108 g.Connect(&DestroyEdge{S: target, T: v})
109 }
110 }
111
112 return nil
113}
114
115// PruneUnusedValuesTransformer is s GraphTransformer that removes local and
116// output values which are not referenced in the graph. Since outputs and
117// locals always need to be evaluated, if they reference a resource that is not
118// available in the state the interpolation could fail.
119type PruneUnusedValuesTransformer struct{}
120
121func (t *PruneUnusedValuesTransformer) Transform(g *Graph) error {
122 // this might need multiple runs in order to ensure that pruning a value
123 // doesn't effect a previously checked value.
124 for removed := 0; ; removed = 0 {
125 for _, v := range g.Vertices() {
126 switch v.(type) {
127 case *NodeApplyableOutput, *NodeLocal:
128 // OK
129 default:
130 continue
131 }
132
133 dependants := g.UpEdges(v)
134
135 switch dependants.Len() {
136 case 0:
137 // nothing at all depends on this
138 g.Remove(v)
139 removed++
140 case 1:
141 // because an output's destroy node always depends on the output,
142 // we need to check for the case of a single destroy node.
143 d := dependants.List()[0]
144 if _, ok := d.(*NodeDestroyableOutput); ok {
145 g.Remove(v)
146 removed++
147 }
148 }
149 }
150 if removed == 0 {
151 break
152 }
153 }
154
155 return nil
156}
157
bae9f6d2
JC
158// ReferenceMap is a structure that can be used to efficiently check
159// for references on a graph.
160type ReferenceMap struct {
161 // m is the mapping of referenceable name to list of verticies that
162 // implement that name. This is built on initialization.
163 references map[string][]dag.Vertex
164 referencedBy map[string][]dag.Vertex
165}
166
167// References returns the list of vertices that this vertex
168// references along with any missing references.
169func (m *ReferenceMap) References(v dag.Vertex) ([]dag.Vertex, []string) {
170 rn, ok := v.(GraphNodeReferencer)
171 if !ok {
172 return nil, nil
173 }
174
175 var matches []dag.Vertex
176 var missing []string
177 prefix := m.prefix(v)
15c0b25d 178
bae9f6d2
JC
179 for _, ns := range rn.References() {
180 found := false
181 for _, n := range strings.Split(ns, "/") {
182 n = prefix + n
183 parents, ok := m.references[n]
184 if !ok {
185 continue
186 }
187
188 // Mark that we found a match
189 found = true
190
bae9f6d2 191 for _, p := range parents {
15c0b25d 192 // don't include self-references
bae9f6d2 193 if p == v {
15c0b25d 194 continue
bae9f6d2 195 }
15c0b25d 196 matches = append(matches, p)
bae9f6d2
JC
197 }
198
bae9f6d2
JC
199 break
200 }
201
202 if !found {
203 missing = append(missing, ns)
204 }
205 }
206
207 return matches, missing
208}
209
210// ReferencedBy returns the list of vertices that reference the
211// vertex passed in.
212func (m *ReferenceMap) ReferencedBy(v dag.Vertex) []dag.Vertex {
213 rn, ok := v.(GraphNodeReferenceable)
214 if !ok {
215 return nil
216 }
217
218 var matches []dag.Vertex
219 prefix := m.prefix(v)
220 for _, n := range rn.ReferenceableName() {
221 n = prefix + n
222 children, ok := m.referencedBy[n]
223 if !ok {
224 continue
225 }
226
227 // Make sure this isn't a self reference, which isn't included
228 selfRef := false
229 for _, p := range children {
230 if p == v {
231 selfRef = true
232 break
233 }
234 }
235 if selfRef {
236 continue
237 }
238
239 matches = append(matches, children...)
240 }
241
242 return matches
243}
244
245func (m *ReferenceMap) prefix(v dag.Vertex) string {
246 // If the node is stating it is already fully qualified then
247 // we don't have to create the prefix!
248 if gn, ok := v.(GraphNodeReferenceGlobal); ok && gn.ReferenceGlobal() {
249 return ""
250 }
251
252 // Create the prefix based on the path
253 var prefix string
254 if pn, ok := v.(GraphNodeSubPath); ok {
255 if path := normalizeModulePath(pn.Path()); len(path) > 1 {
256 prefix = modulePrefixStr(path) + "."
257 }
258 }
259
260 return prefix
261}
262
263// NewReferenceMap is used to create a new reference map for the
264// given set of vertices.
265func NewReferenceMap(vs []dag.Vertex) *ReferenceMap {
266 var m ReferenceMap
267
268 // Build the lookup table
269 refMap := make(map[string][]dag.Vertex)
270 for _, v := range vs {
271 // We're only looking for referenceable nodes
272 rn, ok := v.(GraphNodeReferenceable)
273 if !ok {
274 continue
275 }
276
277 // Go through and cache them
278 prefix := m.prefix(v)
279 for _, n := range rn.ReferenceableName() {
280 n = prefix + n
281 refMap[n] = append(refMap[n], v)
282 }
283
284 // If there is a path, it is always referenceable by that. For
285 // example, if this is a referenceable thing at path []string{"foo"},
286 // then it can be referenced at "module.foo"
287 if pn, ok := v.(GraphNodeSubPath); ok {
288 for _, p := range ReferenceModulePath(pn.Path()) {
289 refMap[p] = append(refMap[p], v)
290 }
291 }
292 }
293
294 // Build the lookup table for referenced by
295 refByMap := make(map[string][]dag.Vertex)
296 for _, v := range vs {
297 // We're only looking for referenceable nodes
298 rn, ok := v.(GraphNodeReferencer)
299 if !ok {
300 continue
301 }
302
303 // Go through and cache them
304 prefix := m.prefix(v)
305 for _, n := range rn.References() {
306 n = prefix + n
307 refByMap[n] = append(refByMap[n], v)
308 }
309 }
310
311 m.references = refMap
312 m.referencedBy = refByMap
313 return &m
314}
315
316// Returns the reference name for a module path. The path "foo" would return
317// "module.foo". If this is a deeply nested module, it will be every parent
318// as well. For example: ["foo", "bar"] would return both "module.foo" and
319// "module.foo.module.bar"
320func ReferenceModulePath(p []string) []string {
321 p = normalizeModulePath(p)
322 if len(p) == 1 {
323 // Root, no name
324 return nil
325 }
326
327 result := make([]string, 0, len(p)-1)
328 for i := len(p); i > 1; i-- {
329 result = append(result, modulePrefixStr(p[:i]))
330 }
331
332 return result
333}
334
335// ReferencesFromConfig returns the references that a configuration has
336// based on the interpolated variables in a configuration.
337func ReferencesFromConfig(c *config.RawConfig) []string {
338 var result []string
339 for _, v := range c.Variables {
340 if r := ReferenceFromInterpolatedVar(v); len(r) > 0 {
341 result = append(result, r...)
342 }
343 }
344
345 return result
346}
347
348// ReferenceFromInterpolatedVar returns the reference from this variable,
349// or an empty string if there is no reference.
350func ReferenceFromInterpolatedVar(v config.InterpolatedVariable) []string {
351 switch v := v.(type) {
352 case *config.ModuleVariable:
353 return []string{fmt.Sprintf("module.%s.output.%s", v.Name, v.Field)}
354 case *config.ResourceVariable:
355 id := v.ResourceId()
356
357 // If we have a multi-reference (splat), then we depend on ALL
358 // resources with this type/name.
359 if v.Multi && v.Index == -1 {
360 return []string{fmt.Sprintf("%s.*", id)}
361 }
362
363 // Otherwise, we depend on a specific index.
364 idx := v.Index
365 if !v.Multi || v.Index == -1 {
366 idx = 0
367 }
368
369 // Depend on the index, as well as "N" which represents the
370 // un-expanded set of resources.
371 return []string{fmt.Sprintf("%s.%d/%s.N", id, idx, id)}
372 case *config.UserVariable:
373 return []string{fmt.Sprintf("var.%s", v.Name)}
15c0b25d
AP
374 case *config.LocalVariable:
375 return []string{fmt.Sprintf("local.%s", v.Name)}
bae9f6d2
JC
376 default:
377 return nil
378 }
379}
380
381func modulePrefixStr(p []string) string {
15c0b25d
AP
382 // strip "root"
383 if len(p) > 0 && p[0] == rootModulePath[0] {
384 p = p[1:]
385 }
386
bae9f6d2 387 parts := make([]string, 0, len(p)*2)
15c0b25d 388 for _, p := range p {
bae9f6d2
JC
389 parts = append(parts, "module", p)
390 }
391
392 return strings.Join(parts, ".")
393}
394
395func modulePrefixList(result []string, prefix string) []string {
396 if prefix != "" {
397 for i, v := range result {
398 result[i] = fmt.Sprintf("%s.%s", prefix, v)
399 }
400 }
401
402 return result
403}