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