github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/instances/expander.go (about) 1 package instances 2 3 import ( 4 "fmt" 5 "sort" 6 "sync" 7 8 "github.com/eliastor/durgaform/internal/addrs" 9 "github.com/zclconf/go-cty/cty" 10 ) 11 12 // Expander instances serve as a coordination point for gathering object 13 // repetition values (count and for_each in configuration) and then later 14 // making use of them to fully enumerate all of the instances of an object. 15 // 16 // The two repeatable object types in Durgaform are modules and resources. 17 // Because resources belong to modules and modules can nest inside other 18 // modules, module expansion in particular has a recursive effect that can 19 // cause deep objects to expand exponentially. Expander assumes that all 20 // instances of a module have the same static objects inside, and that they 21 // differ only in the repetition count for some of those objects. 22 // 23 // Expander is a synchronized object whose methods can be safely called 24 // from concurrent threads of execution. However, it does expect a certain 25 // sequence of operations which is normally obtained by the caller traversing 26 // a dependency graph: each object must have its repetition mode set exactly 27 // once, and this must be done before any calls that depend on the repetition 28 // mode. In other words, the count or for_each expression value for a module 29 // must be provided before any object nested directly or indirectly inside 30 // that module can be expanded. If this ordering is violated, the methods 31 // will panic to enforce internal consistency. 32 // 33 // The Expand* methods of Expander only work directly with modules and with 34 // resources. Addresses for other objects that nest within modules but 35 // do not themselves support repetition can be obtained by calling ExpandModule 36 // with the containing module path and then producing one absolute instance 37 // address per module instance address returned. 38 type Expander struct { 39 mu sync.RWMutex 40 exps *expanderModule 41 } 42 43 // NewExpander initializes and returns a new Expander, empty and ready to use. 44 func NewExpander() *Expander { 45 return &Expander{ 46 exps: newExpanderModule(), 47 } 48 } 49 50 // SetModuleSingle records that the given module call inside the given parent 51 // module does not use any repetition arguments and is therefore a singleton. 52 func (e *Expander) SetModuleSingle(parentAddr addrs.ModuleInstance, callAddr addrs.ModuleCall) { 53 e.setModuleExpansion(parentAddr, callAddr, expansionSingleVal) 54 } 55 56 // SetModuleCount records that the given module call inside the given parent 57 // module instance uses the "count" repetition argument, with the given value. 58 func (e *Expander) SetModuleCount(parentAddr addrs.ModuleInstance, callAddr addrs.ModuleCall, count int) { 59 e.setModuleExpansion(parentAddr, callAddr, expansionCount(count)) 60 } 61 62 // SetModuleForEach records that the given module call inside the given parent 63 // module instance uses the "for_each" repetition argument, with the given 64 // map value. 65 // 66 // In the configuration language the for_each argument can also accept a set. 67 // It's the caller's responsibility to convert that into an identity map before 68 // calling this method. 69 func (e *Expander) SetModuleForEach(parentAddr addrs.ModuleInstance, callAddr addrs.ModuleCall, mapping map[string]cty.Value) { 70 e.setModuleExpansion(parentAddr, callAddr, expansionForEach(mapping)) 71 } 72 73 // SetResourceSingle records that the given resource inside the given module 74 // does not use any repetition arguments and is therefore a singleton. 75 func (e *Expander) SetResourceSingle(moduleAddr addrs.ModuleInstance, resourceAddr addrs.Resource) { 76 e.setResourceExpansion(moduleAddr, resourceAddr, expansionSingleVal) 77 } 78 79 // SetResourceCount records that the given resource inside the given module 80 // uses the "count" repetition argument, with the given value. 81 func (e *Expander) SetResourceCount(moduleAddr addrs.ModuleInstance, resourceAddr addrs.Resource, count int) { 82 e.setResourceExpansion(moduleAddr, resourceAddr, expansionCount(count)) 83 } 84 85 // SetResourceForEach records that the given resource inside the given module 86 // uses the "for_each" repetition argument, with the given map value. 87 // 88 // In the configuration language the for_each argument can also accept a set. 89 // It's the caller's responsibility to convert that into an identity map before 90 // calling this method. 91 func (e *Expander) SetResourceForEach(moduleAddr addrs.ModuleInstance, resourceAddr addrs.Resource, mapping map[string]cty.Value) { 92 e.setResourceExpansion(moduleAddr, resourceAddr, expansionForEach(mapping)) 93 } 94 95 // ExpandModule finds the exhaustive set of module instances resulting from 96 // the expansion of the given module and all of its ancestor modules. 97 // 98 // All of the modules on the path to the identified module must already have 99 // had their expansion registered using one of the SetModule* methods before 100 // calling, or this method will panic. 101 func (e *Expander) ExpandModule(addr addrs.Module) []addrs.ModuleInstance { 102 return e.expandModule(addr, false) 103 } 104 105 // expandModule allows skipping unexpanded module addresses by setting skipUnknown to true. 106 // This is used by instances.Set, which is only concerned with the expanded 107 // instances, and should not panic when looking up unknown addresses. 108 func (e *Expander) expandModule(addr addrs.Module, skipUnknown bool) []addrs.ModuleInstance { 109 if len(addr) == 0 { 110 // Root module is always a singleton. 111 return singletonRootModule 112 } 113 114 e.mu.RLock() 115 defer e.mu.RUnlock() 116 117 // We're going to be dynamically growing ModuleInstance addresses, so 118 // we'll preallocate some space to do it so that for typical shallow 119 // module trees we won't need to reallocate this. 120 // (moduleInstances does plenty of allocations itself, so the benefit of 121 // pre-allocating this is marginal but it's not hard to do.) 122 parentAddr := make(addrs.ModuleInstance, 0, 4) 123 ret := e.exps.moduleInstances(addr, parentAddr, skipUnknown) 124 sort.SliceStable(ret, func(i, j int) bool { 125 return ret[i].Less(ret[j]) 126 }) 127 return ret 128 } 129 130 // GetDeepestExistingModuleInstance is a funny specialized function for 131 // determining how many steps we can traverse through the given module instance 132 // address before encountering an undeclared instance of a declared module. 133 // 134 // The result is the longest prefix of the given address which steps only 135 // through module instances that exist. 136 // 137 // All of the modules on the given path must already have had their 138 // expansion registered using one of the SetModule* methods before calling, 139 // or this method will panic. 140 func (e *Expander) GetDeepestExistingModuleInstance(given addrs.ModuleInstance) addrs.ModuleInstance { 141 exps := e.exps // start with the root module expansions 142 for i := 0; i < len(given); i++ { 143 step := given[i] 144 callName := step.Name 145 if _, ok := exps.moduleCalls[addrs.ModuleCall{Name: callName}]; !ok { 146 // This is a bug in the caller, because it should always register 147 // expansions for an object and all of its ancestors before requesting 148 // expansion of it. 149 panic(fmt.Sprintf("no expansion has been registered for %s", given[:i].Child(callName, addrs.NoKey))) 150 } 151 152 var ok bool 153 exps, ok = exps.childInstances[step] 154 if !ok { 155 // We've found a non-existing instance, so we're done. 156 return given[:i] 157 } 158 } 159 160 // If we complete the loop above without returning early then the entire 161 // given address refers to a declared module instance. 162 return given 163 } 164 165 // ExpandModuleResource finds the exhaustive set of resource instances resulting from 166 // the expansion of the given resource and all of its containing modules. 167 // 168 // All of the modules on the path to the identified resource and the resource 169 // itself must already have had their expansion registered using one of the 170 // SetModule*/SetResource* methods before calling, or this method will panic. 171 func (e *Expander) ExpandModuleResource(moduleAddr addrs.Module, resourceAddr addrs.Resource) []addrs.AbsResourceInstance { 172 e.mu.RLock() 173 defer e.mu.RUnlock() 174 175 // We're going to be dynamically growing ModuleInstance addresses, so 176 // we'll preallocate some space to do it so that for typical shallow 177 // module trees we won't need to reallocate this. 178 // (moduleInstances does plenty of allocations itself, so the benefit of 179 // pre-allocating this is marginal but it's not hard to do.) 180 moduleInstanceAddr := make(addrs.ModuleInstance, 0, 4) 181 ret := e.exps.moduleResourceInstances(moduleAddr, resourceAddr, moduleInstanceAddr) 182 sort.SliceStable(ret, func(i, j int) bool { 183 return ret[i].Less(ret[j]) 184 }) 185 return ret 186 } 187 188 // ExpandResource finds the set of resource instances resulting from 189 // the expansion of the given resource within its module instance. 190 // 191 // All of the modules on the path to the identified resource and the resource 192 // itself must already have had their expansion registered using one of the 193 // SetModule*/SetResource* methods before calling, or this method will panic. 194 // 195 // ExpandModuleResource returns all instances of a resource across all 196 // instances of its containing module, whereas this ExpandResource function 197 // is more specific and only expands within a single module instance. If 198 // any of the module instances selected in the module path of the given address 199 // aren't valid for that module's expansion then ExpandResource returns an 200 // empty result, reflecting that a non-existing module instance can never 201 // contain any existing resource instances. 202 func (e *Expander) ExpandResource(resourceAddr addrs.AbsResource) []addrs.AbsResourceInstance { 203 e.mu.RLock() 204 defer e.mu.RUnlock() 205 206 moduleInstanceAddr := make(addrs.ModuleInstance, 0, 4) 207 ret := e.exps.resourceInstances(resourceAddr.Module, resourceAddr.Resource, moduleInstanceAddr) 208 sort.SliceStable(ret, func(i, j int) bool { 209 return ret[i].Less(ret[j]) 210 }) 211 return ret 212 } 213 214 // GetModuleInstanceRepetitionData returns an object describing the values 215 // that should be available for each.key, each.value, and count.index within 216 // the call block for the given module instance. 217 func (e *Expander) GetModuleInstanceRepetitionData(addr addrs.ModuleInstance) RepetitionData { 218 if len(addr) == 0 { 219 // The root module is always a singleton, so it has no repetition data. 220 return RepetitionData{} 221 } 222 223 e.mu.RLock() 224 defer e.mu.RUnlock() 225 226 parentMod := e.findModule(addr[:len(addr)-1]) 227 lastStep := addr[len(addr)-1] 228 exp, ok := parentMod.moduleCalls[addrs.ModuleCall{Name: lastStep.Name}] 229 if !ok { 230 panic(fmt.Sprintf("no expansion has been registered for %s", addr)) 231 } 232 return exp.repetitionData(lastStep.InstanceKey) 233 } 234 235 // GetResourceInstanceRepetitionData returns an object describing the values 236 // that should be available for each.key, each.value, and count.index within 237 // the definition block for the given resource instance. 238 func (e *Expander) GetResourceInstanceRepetitionData(addr addrs.AbsResourceInstance) RepetitionData { 239 e.mu.RLock() 240 defer e.mu.RUnlock() 241 242 parentMod := e.findModule(addr.Module) 243 exp, ok := parentMod.resources[addr.Resource.Resource] 244 if !ok { 245 panic(fmt.Sprintf("no expansion has been registered for %s", addr.ContainingResource())) 246 } 247 return exp.repetitionData(addr.Resource.Key) 248 } 249 250 // AllInstances returns a set of all of the module and resource instances known 251 // to the expander. 252 // 253 // It generally doesn't make sense to call this until everything has already 254 // been fully expanded by calling the SetModule* and SetResource* functions. 255 // After that, the returned set is a convenient small API only for querying 256 // whether particular instance addresses appeared as a result of those 257 // expansions. 258 func (e *Expander) AllInstances() Set { 259 return Set{e} 260 } 261 262 func (e *Expander) findModule(moduleInstAddr addrs.ModuleInstance) *expanderModule { 263 // We expect that all of the modules on the path to our module instance 264 // should already have expansions registered. 265 mod := e.exps 266 for i, step := range moduleInstAddr { 267 next, ok := mod.childInstances[step] 268 if !ok { 269 // Top-down ordering of registration is part of the contract of 270 // Expander, so this is always indicative of a bug in the caller. 271 panic(fmt.Sprintf("no expansion has been registered for ancestor module %s", moduleInstAddr[:i+1])) 272 } 273 mod = next 274 } 275 return mod 276 } 277 278 func (e *Expander) setModuleExpansion(parentAddr addrs.ModuleInstance, callAddr addrs.ModuleCall, exp expansion) { 279 e.mu.Lock() 280 defer e.mu.Unlock() 281 282 mod := e.findModule(parentAddr) 283 if _, exists := mod.moduleCalls[callAddr]; exists { 284 panic(fmt.Sprintf("expansion already registered for %s", parentAddr.Child(callAddr.Name, addrs.NoKey))) 285 } 286 // We'll also pre-register the child instances so that later calls can 287 // populate them as the caller traverses the configuration tree. 288 for _, key := range exp.instanceKeys() { 289 step := addrs.ModuleInstanceStep{Name: callAddr.Name, InstanceKey: key} 290 mod.childInstances[step] = newExpanderModule() 291 } 292 mod.moduleCalls[callAddr] = exp 293 } 294 295 func (e *Expander) setResourceExpansion(parentAddr addrs.ModuleInstance, resourceAddr addrs.Resource, exp expansion) { 296 e.mu.Lock() 297 defer e.mu.Unlock() 298 299 mod := e.findModule(parentAddr) 300 if _, exists := mod.resources[resourceAddr]; exists { 301 panic(fmt.Sprintf("expansion already registered for %s", resourceAddr.Absolute(parentAddr))) 302 } 303 mod.resources[resourceAddr] = exp 304 } 305 306 func (e *Expander) knowsModuleInstance(want addrs.ModuleInstance) bool { 307 if want.IsRoot() { 308 return true // root module instance is always present 309 } 310 311 e.mu.Lock() 312 defer e.mu.Unlock() 313 314 return e.exps.knowsModuleInstance(want) 315 } 316 317 func (e *Expander) knowsModuleCall(want addrs.AbsModuleCall) bool { 318 e.mu.Lock() 319 defer e.mu.Unlock() 320 321 return e.exps.knowsModuleCall(want) 322 } 323 324 func (e *Expander) knowsResourceInstance(want addrs.AbsResourceInstance) bool { 325 e.mu.Lock() 326 defer e.mu.Unlock() 327 328 return e.exps.knowsResourceInstance(want) 329 } 330 331 func (e *Expander) knowsResource(want addrs.AbsResource) bool { 332 e.mu.Lock() 333 defer e.mu.Unlock() 334 335 return e.exps.knowsResource(want) 336 } 337 338 type expanderModule struct { 339 moduleCalls map[addrs.ModuleCall]expansion 340 resources map[addrs.Resource]expansion 341 childInstances map[addrs.ModuleInstanceStep]*expanderModule 342 } 343 344 func newExpanderModule() *expanderModule { 345 return &expanderModule{ 346 moduleCalls: make(map[addrs.ModuleCall]expansion), 347 resources: make(map[addrs.Resource]expansion), 348 childInstances: make(map[addrs.ModuleInstanceStep]*expanderModule), 349 } 350 } 351 352 var singletonRootModule = []addrs.ModuleInstance{addrs.RootModuleInstance} 353 354 // if moduleInstances is being used to lookup known instances after all 355 // expansions have been done, set skipUnknown to true which allows addrs which 356 // may not have been seen to return with no instances rather than panicking. 357 func (m *expanderModule) moduleInstances(addr addrs.Module, parentAddr addrs.ModuleInstance, skipUnknown bool) []addrs.ModuleInstance { 358 callName := addr[0] 359 exp, ok := m.moduleCalls[addrs.ModuleCall{Name: callName}] 360 if !ok { 361 if skipUnknown { 362 return nil 363 } 364 // This is a bug in the caller, because it should always register 365 // expansions for an object and all of its ancestors before requesting 366 // expansion of it. 367 panic(fmt.Sprintf("no expansion has been registered for %s", parentAddr.Child(callName, addrs.NoKey))) 368 } 369 370 var ret []addrs.ModuleInstance 371 372 // If there's more than one step remaining then we need to traverse deeper. 373 if len(addr) > 1 { 374 for step, inst := range m.childInstances { 375 if step.Name != callName { 376 continue 377 } 378 instAddr := append(parentAddr, step) 379 ret = append(ret, inst.moduleInstances(addr[1:], instAddr, skipUnknown)...) 380 } 381 return ret 382 } 383 384 // Otherwise, we'll use the expansion from the final step to produce 385 // a sequence of addresses under this prefix. 386 for _, k := range exp.instanceKeys() { 387 // We're reusing the buffer under parentAddr as we recurse through 388 // the structure, so we need to copy it here to produce a final 389 // immutable slice to return. 390 full := make(addrs.ModuleInstance, 0, len(parentAddr)+1) 391 full = append(full, parentAddr...) 392 full = full.Child(callName, k) 393 ret = append(ret, full) 394 } 395 return ret 396 } 397 398 func (m *expanderModule) moduleResourceInstances(moduleAddr addrs.Module, resourceAddr addrs.Resource, parentAddr addrs.ModuleInstance) []addrs.AbsResourceInstance { 399 if len(moduleAddr) > 0 { 400 var ret []addrs.AbsResourceInstance 401 // We need to traverse through the module levels first, so we can 402 // then iterate resource expansions in the context of each module 403 // path leading to them. 404 callName := moduleAddr[0] 405 if _, ok := m.moduleCalls[addrs.ModuleCall{Name: callName}]; !ok { 406 // This is a bug in the caller, because it should always register 407 // expansions for an object and all of its ancestors before requesting 408 // expansion of it. 409 panic(fmt.Sprintf("no expansion has been registered for %s", parentAddr.Child(callName, addrs.NoKey))) 410 } 411 412 for step, inst := range m.childInstances { 413 if step.Name != callName { 414 continue 415 } 416 moduleInstAddr := append(parentAddr, step) 417 ret = append(ret, inst.moduleResourceInstances(moduleAddr[1:], resourceAddr, moduleInstAddr)...) 418 } 419 return ret 420 } 421 422 return m.onlyResourceInstances(resourceAddr, parentAddr) 423 } 424 425 func (m *expanderModule) resourceInstances(moduleAddr addrs.ModuleInstance, resourceAddr addrs.Resource, parentAddr addrs.ModuleInstance) []addrs.AbsResourceInstance { 426 if len(moduleAddr) > 0 { 427 // We need to traverse through the module levels first, using only the 428 // module instances for our specific resource, as the resource may not 429 // yet be expanded in all module instances. 430 step := moduleAddr[0] 431 callName := step.Name 432 if _, ok := m.moduleCalls[addrs.ModuleCall{Name: callName}]; !ok { 433 // This is a bug in the caller, because it should always register 434 // expansions for an object and all of its ancestors before requesting 435 // expansion of it. 436 panic(fmt.Sprintf("no expansion has been registered for %s", parentAddr.Child(callName, addrs.NoKey))) 437 } 438 439 if inst, ok := m.childInstances[step]; ok { 440 moduleInstAddr := append(parentAddr, step) 441 return inst.resourceInstances(moduleAddr[1:], resourceAddr, moduleInstAddr) 442 } else { 443 // If we have the module _call_ registered (as we checked above) 444 // but we don't have the given module _instance_ registered, that 445 // suggests that the module instance key in "step" is not declared 446 // by the current definition of this module call. That means the 447 // module instance doesn't exist at all, and therefore it can't 448 // possibly declare any resource instances either. 449 // 450 // For example, if we were asked about module.foo[0].aws_instance.bar 451 // but module.foo doesn't currently have count set, then there is no 452 // module.foo[0] at all, and therefore no aws_instance.bar 453 // instances inside it. 454 return nil 455 } 456 } 457 return m.onlyResourceInstances(resourceAddr, parentAddr) 458 } 459 460 func (m *expanderModule) onlyResourceInstances(resourceAddr addrs.Resource, parentAddr addrs.ModuleInstance) []addrs.AbsResourceInstance { 461 var ret []addrs.AbsResourceInstance 462 exp, ok := m.resources[resourceAddr] 463 if !ok { 464 panic(fmt.Sprintf("no expansion has been registered for %s", resourceAddr.Absolute(parentAddr))) 465 } 466 467 for _, k := range exp.instanceKeys() { 468 // We're reusing the buffer under parentAddr as we recurse through 469 // the structure, so we need to copy it here to produce a final 470 // immutable slice to return. 471 moduleAddr := make(addrs.ModuleInstance, len(parentAddr)) 472 copy(moduleAddr, parentAddr) 473 ret = append(ret, resourceAddr.Instance(k).Absolute(moduleAddr)) 474 } 475 return ret 476 } 477 478 func (m *expanderModule) getModuleInstance(want addrs.ModuleInstance) *expanderModule { 479 current := m 480 for _, step := range want { 481 next := current.childInstances[step] 482 if next == nil { 483 return nil 484 } 485 current = next 486 } 487 return current 488 } 489 490 func (m *expanderModule) knowsModuleInstance(want addrs.ModuleInstance) bool { 491 return m.getModuleInstance(want) != nil 492 } 493 494 func (m *expanderModule) knowsModuleCall(want addrs.AbsModuleCall) bool { 495 modInst := m.getModuleInstance(want.Module) 496 if modInst == nil { 497 return false 498 } 499 _, ret := modInst.moduleCalls[want.Call] 500 return ret 501 } 502 503 func (m *expanderModule) knowsResourceInstance(want addrs.AbsResourceInstance) bool { 504 modInst := m.getModuleInstance(want.Module) 505 if modInst == nil { 506 return false 507 } 508 resourceExp := modInst.resources[want.Resource.Resource] 509 if resourceExp == nil { 510 return false 511 } 512 for _, key := range resourceExp.instanceKeys() { 513 if key == want.Resource.Key { 514 return true 515 } 516 } 517 return false 518 } 519 520 func (m *expanderModule) knowsResource(want addrs.AbsResource) bool { 521 modInst := m.getModuleInstance(want.Module) 522 if modInst == nil { 523 return false 524 } 525 _, ret := modInst.resources[want.Resource] 526 return ret 527 }