github.com/paultyng/terraform@v0.6.11-0.20180227224804-66ff8f8bed40/terraform/transform_reference.go (about) 1 package terraform 2 3 import ( 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!) 19 type 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. 28 type 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). 45 type 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. 53 type ReferenceTransformer struct{} 54 55 func (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 // 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. 84 type DestroyValueReferenceTransformer struct{} 85 86 func (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. 119 type PruneUnusedValuesTransformer struct{} 120 121 func (t *PruneUnusedValuesTransformer) Transform(g *Graph) error { 122 vs := g.Vertices() 123 for _, v := range vs { 124 switch v.(type) { 125 case *NodeApplyableOutput, *NodeLocal: 126 // OK 127 default: 128 continue 129 } 130 131 if len(g.EdgesTo(v)) == 0 { 132 g.Remove(v) 133 } 134 } 135 136 return nil 137 } 138 139 // ReferenceMap is a structure that can be used to efficiently check 140 // for references on a graph. 141 type ReferenceMap struct { 142 // m is the mapping of referenceable name to list of verticies that 143 // implement that name. This is built on initialization. 144 references map[string][]dag.Vertex 145 referencedBy map[string][]dag.Vertex 146 } 147 148 // References returns the list of vertices that this vertex 149 // references along with any missing references. 150 func (m *ReferenceMap) References(v dag.Vertex) ([]dag.Vertex, []string) { 151 rn, ok := v.(GraphNodeReferencer) 152 if !ok { 153 return nil, nil 154 } 155 156 var matches []dag.Vertex 157 var missing []string 158 prefix := m.prefix(v) 159 160 for _, ns := range rn.References() { 161 found := false 162 for _, n := range strings.Split(ns, "/") { 163 n = prefix + n 164 parents, ok := m.references[n] 165 if !ok { 166 continue 167 } 168 169 // Mark that we found a match 170 found = true 171 172 for _, p := range parents { 173 // don't include self-references 174 if p == v { 175 continue 176 } 177 matches = append(matches, p) 178 } 179 180 break 181 } 182 183 if !found { 184 missing = append(missing, ns) 185 } 186 } 187 188 return matches, missing 189 } 190 191 // ReferencedBy returns the list of vertices that reference the 192 // vertex passed in. 193 func (m *ReferenceMap) ReferencedBy(v dag.Vertex) []dag.Vertex { 194 rn, ok := v.(GraphNodeReferenceable) 195 if !ok { 196 return nil 197 } 198 199 var matches []dag.Vertex 200 prefix := m.prefix(v) 201 for _, n := range rn.ReferenceableName() { 202 n = prefix + n 203 children, ok := m.referencedBy[n] 204 if !ok { 205 continue 206 } 207 208 // Make sure this isn't a self reference, which isn't included 209 selfRef := false 210 for _, p := range children { 211 if p == v { 212 selfRef = true 213 break 214 } 215 } 216 if selfRef { 217 continue 218 } 219 220 matches = append(matches, children...) 221 } 222 223 return matches 224 } 225 226 func (m *ReferenceMap) prefix(v dag.Vertex) string { 227 // If the node is stating it is already fully qualified then 228 // we don't have to create the prefix! 229 if gn, ok := v.(GraphNodeReferenceGlobal); ok && gn.ReferenceGlobal() { 230 return "" 231 } 232 233 // Create the prefix based on the path 234 var prefix string 235 if pn, ok := v.(GraphNodeSubPath); ok { 236 if path := normalizeModulePath(pn.Path()); len(path) > 1 { 237 prefix = modulePrefixStr(path) + "." 238 } 239 } 240 241 return prefix 242 } 243 244 // NewReferenceMap is used to create a new reference map for the 245 // given set of vertices. 246 func NewReferenceMap(vs []dag.Vertex) *ReferenceMap { 247 var m ReferenceMap 248 249 // Build the lookup table 250 refMap := make(map[string][]dag.Vertex) 251 for _, v := range vs { 252 // We're only looking for referenceable nodes 253 rn, ok := v.(GraphNodeReferenceable) 254 if !ok { 255 continue 256 } 257 258 // Go through and cache them 259 prefix := m.prefix(v) 260 for _, n := range rn.ReferenceableName() { 261 n = prefix + n 262 refMap[n] = append(refMap[n], v) 263 } 264 265 // If there is a path, it is always referenceable by that. For 266 // example, if this is a referenceable thing at path []string{"foo"}, 267 // then it can be referenced at "module.foo" 268 if pn, ok := v.(GraphNodeSubPath); ok { 269 for _, p := range ReferenceModulePath(pn.Path()) { 270 refMap[p] = append(refMap[p], v) 271 } 272 } 273 } 274 275 // Build the lookup table for referenced by 276 refByMap := make(map[string][]dag.Vertex) 277 for _, v := range vs { 278 // We're only looking for referenceable nodes 279 rn, ok := v.(GraphNodeReferencer) 280 if !ok { 281 continue 282 } 283 284 // Go through and cache them 285 prefix := m.prefix(v) 286 for _, n := range rn.References() { 287 n = prefix + n 288 refByMap[n] = append(refByMap[n], v) 289 } 290 } 291 292 m.references = refMap 293 m.referencedBy = refByMap 294 return &m 295 } 296 297 // Returns the reference name for a module path. The path "foo" would return 298 // "module.foo". If this is a deeply nested module, it will be every parent 299 // as well. For example: ["foo", "bar"] would return both "module.foo" and 300 // "module.foo.module.bar" 301 func ReferenceModulePath(p []string) []string { 302 p = normalizeModulePath(p) 303 if len(p) == 1 { 304 // Root, no name 305 return nil 306 } 307 308 result := make([]string, 0, len(p)-1) 309 for i := len(p); i > 1; i-- { 310 result = append(result, modulePrefixStr(p[:i])) 311 } 312 313 return result 314 } 315 316 // ReferencesFromConfig returns the references that a configuration has 317 // based on the interpolated variables in a configuration. 318 func ReferencesFromConfig(c *config.RawConfig) []string { 319 var result []string 320 for _, v := range c.Variables { 321 if r := ReferenceFromInterpolatedVar(v); len(r) > 0 { 322 result = append(result, r...) 323 } 324 } 325 326 return result 327 } 328 329 // ReferenceFromInterpolatedVar returns the reference from this variable, 330 // or an empty string if there is no reference. 331 func ReferenceFromInterpolatedVar(v config.InterpolatedVariable) []string { 332 switch v := v.(type) { 333 case *config.ModuleVariable: 334 return []string{fmt.Sprintf("module.%s.output.%s", v.Name, v.Field)} 335 case *config.ResourceVariable: 336 id := v.ResourceId() 337 338 // If we have a multi-reference (splat), then we depend on ALL 339 // resources with this type/name. 340 if v.Multi && v.Index == -1 { 341 return []string{fmt.Sprintf("%s.*", id)} 342 } 343 344 // Otherwise, we depend on a specific index. 345 idx := v.Index 346 if !v.Multi || v.Index == -1 { 347 idx = 0 348 } 349 350 // Depend on the index, as well as "N" which represents the 351 // un-expanded set of resources. 352 return []string{fmt.Sprintf("%s.%d/%s.N", id, idx, id)} 353 case *config.UserVariable: 354 return []string{fmt.Sprintf("var.%s", v.Name)} 355 case *config.LocalVariable: 356 return []string{fmt.Sprintf("local.%s", v.Name)} 357 default: 358 return nil 359 } 360 } 361 362 func modulePrefixStr(p []string) string { 363 // strip "root" 364 if len(p) > 0 && p[0] == rootModulePath[0] { 365 p = p[1:] 366 } 367 368 parts := make([]string, 0, len(p)*2) 369 for _, p := range p { 370 parts = append(parts, "module", p) 371 } 372 373 return strings.Join(parts, ".") 374 } 375 376 func modulePrefixList(result []string, prefix string) []string { 377 if prefix != "" { 378 for i, v := range result { 379 result[i] = fmt.Sprintf("%s.%s", prefix, v) 380 } 381 } 382 383 return result 384 }