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