github.com/paybyphone/terraform@v0.9.5-0.20170613192930-9706042ddd51/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 // ReferenceMap is a structure that can be used to efficiently check 80 // for references on a graph. 81 type 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. 90 func (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. 137 func (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 170 func (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. 190 func 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" 245 func 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. 262 func 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. 275 func 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 304 func 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 313 func 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 }