]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/terraform/terraform/transform_reference.go
deps: use go modules for dep mgmt
[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
79// ReferenceMap is a structure that can be used to efficiently check
80// for references on a graph.
81type ReferenceMap struct {
82 // m is the mapping of referenceable name to list of verticies that
83 // implement that name. This is built on initialization.
84 references map[string][]dag.Vertex
85 referencedBy map[string][]dag.Vertex
86}
87
88// References returns the list of vertices that this vertex
89// references along with any missing references.
90func (m *ReferenceMap) References(v dag.Vertex) ([]dag.Vertex, []string) {
91 rn, ok := v.(GraphNodeReferencer)
92 if !ok {
93 return nil, nil
94 }
95
96 var matches []dag.Vertex
97 var missing []string
98 prefix := m.prefix(v)
99 for _, ns := range rn.References() {
100 found := false
101 for _, n := range strings.Split(ns, "/") {
102 n = prefix + n
103 parents, ok := m.references[n]
104 if !ok {
105 continue
106 }
107
108 // Mark that we found a match
109 found = true
110
111 // Make sure this isn't a self reference, which isn't included
112 selfRef := false
113 for _, p := range parents {
114 if p == v {
115 selfRef = true
116 break
117 }
118 }
119 if selfRef {
120 continue
121 }
122
123 matches = append(matches, parents...)
124 break
125 }
126
127 if !found {
128 missing = append(missing, ns)
129 }
130 }
131
132 return matches, missing
133}
134
135// ReferencedBy returns the list of vertices that reference the
136// vertex passed in.
137func (m *ReferenceMap) ReferencedBy(v dag.Vertex) []dag.Vertex {
138 rn, ok := v.(GraphNodeReferenceable)
139 if !ok {
140 return nil
141 }
142
143 var matches []dag.Vertex
144 prefix := m.prefix(v)
145 for _, n := range rn.ReferenceableName() {
146 n = prefix + n
147 children, ok := m.referencedBy[n]
148 if !ok {
149 continue
150 }
151
152 // Make sure this isn't a self reference, which isn't included
153 selfRef := false
154 for _, p := range children {
155 if p == v {
156 selfRef = true
157 break
158 }
159 }
160 if selfRef {
161 continue
162 }
163
164 matches = append(matches, children...)
165 }
166
167 return matches
168}
169
170func (m *ReferenceMap) prefix(v dag.Vertex) string {
171 // If the node is stating it is already fully qualified then
172 // we don't have to create the prefix!
173 if gn, ok := v.(GraphNodeReferenceGlobal); ok && gn.ReferenceGlobal() {
174 return ""
175 }
176
177 // Create the prefix based on the path
178 var prefix string
179 if pn, ok := v.(GraphNodeSubPath); ok {
180 if path := normalizeModulePath(pn.Path()); len(path) > 1 {
181 prefix = modulePrefixStr(path) + "."
182 }
183 }
184
185 return prefix
186}
187
188// NewReferenceMap is used to create a new reference map for the
189// given set of vertices.
190func NewReferenceMap(vs []dag.Vertex) *ReferenceMap {
191 var m ReferenceMap
192
193 // Build the lookup table
194 refMap := make(map[string][]dag.Vertex)
195 for _, v := range vs {
196 // We're only looking for referenceable nodes
197 rn, ok := v.(GraphNodeReferenceable)
198 if !ok {
199 continue
200 }
201
202 // Go through and cache them
203 prefix := m.prefix(v)
204 for _, n := range rn.ReferenceableName() {
205 n = prefix + n
206 refMap[n] = append(refMap[n], v)
207 }
208
209 // If there is a path, it is always referenceable by that. For
210 // example, if this is a referenceable thing at path []string{"foo"},
211 // then it can be referenced at "module.foo"
212 if pn, ok := v.(GraphNodeSubPath); ok {
213 for _, p := range ReferenceModulePath(pn.Path()) {
214 refMap[p] = append(refMap[p], v)
215 }
216 }
217 }
218
219 // Build the lookup table for referenced by
220 refByMap := make(map[string][]dag.Vertex)
221 for _, v := range vs {
222 // We're only looking for referenceable nodes
223 rn, ok := v.(GraphNodeReferencer)
224 if !ok {
225 continue
226 }
227
228 // Go through and cache them
229 prefix := m.prefix(v)
230 for _, n := range rn.References() {
231 n = prefix + n
232 refByMap[n] = append(refByMap[n], v)
233 }
234 }
235
236 m.references = refMap
237 m.referencedBy = refByMap
238 return &m
239}
240
241// Returns the reference name for a module path. The path "foo" would return
242// "module.foo". If this is a deeply nested module, it will be every parent
243// as well. For example: ["foo", "bar"] would return both "module.foo" and
244// "module.foo.module.bar"
245func ReferenceModulePath(p []string) []string {
246 p = normalizeModulePath(p)
247 if len(p) == 1 {
248 // Root, no name
249 return nil
250 }
251
252 result := make([]string, 0, len(p)-1)
253 for i := len(p); i > 1; i-- {
254 result = append(result, modulePrefixStr(p[:i]))
255 }
256
257 return result
258}
259
260// ReferencesFromConfig returns the references that a configuration has
261// based on the interpolated variables in a configuration.
262func ReferencesFromConfig(c *config.RawConfig) []string {
263 var result []string
264 for _, v := range c.Variables {
265 if r := ReferenceFromInterpolatedVar(v); len(r) > 0 {
266 result = append(result, r...)
267 }
268 }
269
270 return result
271}
272
273// ReferenceFromInterpolatedVar returns the reference from this variable,
274// or an empty string if there is no reference.
275func ReferenceFromInterpolatedVar(v config.InterpolatedVariable) []string {
276 switch v := v.(type) {
277 case *config.ModuleVariable:
278 return []string{fmt.Sprintf("module.%s.output.%s", v.Name, v.Field)}
279 case *config.ResourceVariable:
280 id := v.ResourceId()
281
282 // If we have a multi-reference (splat), then we depend on ALL
283 // resources with this type/name.
284 if v.Multi && v.Index == -1 {
285 return []string{fmt.Sprintf("%s.*", id)}
286 }
287
288 // Otherwise, we depend on a specific index.
289 idx := v.Index
290 if !v.Multi || v.Index == -1 {
291 idx = 0
292 }
293
294 // Depend on the index, as well as "N" which represents the
295 // un-expanded set of resources.
296 return []string{fmt.Sprintf("%s.%d/%s.N", id, idx, id)}
297 case *config.UserVariable:
298 return []string{fmt.Sprintf("var.%s", v.Name)}
299 default:
300 return nil
301 }
302}
303
304func modulePrefixStr(p []string) string {
305 parts := make([]string, 0, len(p)*2)
306 for _, p := range p[1:] {
307 parts = append(parts, "module", p)
308 }
309
310 return strings.Join(parts, ".")
311}
312
313func modulePrefixList(result []string, prefix string) []string {
314 if prefix != "" {
315 for i, v := range result {
316 result[i] = fmt.Sprintf("%s.%s", prefix, v)
317 }
318 }
319
320 return result
321}