github.com/magodo/terraform@v0.11.12-beta1/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 // 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 158 // ReferenceMap is a structure that can be used to efficiently check 159 // for references on a graph. 160 type 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. 169 func (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) 178 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 191 for _, p := range parents { 192 // don't include self-references 193 if p == v { 194 continue 195 } 196 matches = append(matches, p) 197 } 198 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. 212 func (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 245 func (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. 265 func 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" 320 func 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. 337 func 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. 350 func 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)} 374 case *config.LocalVariable: 375 return []string{fmt.Sprintf("local.%s", v.Name)} 376 default: 377 return nil 378 } 379 } 380 381 func modulePrefixStr(p []string) string { 382 // strip "root" 383 if len(p) > 0 && p[0] == rootModulePath[0] { 384 p = p[1:] 385 } 386 387 parts := make([]string, 0, len(p)*2) 388 for _, p := range p { 389 parts = append(parts, "module", p) 390 } 391 392 return strings.Join(parts, ".") 393 } 394 395 func 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 }