package instances import ( "fmt" "sort" "sync" "github.com/hashicorp/terraform/internal/addrs" "github.com/zclconf/go-cty/cty" ) // Expander instances serve as a coordination point for gathering object // repetition values (count and for_each in configuration) and then later // making use of them to fully enumerate all of the instances of an object. // // The two repeatable object types in Terraform are modules and resources. // Because resources belong to modules and modules can nest inside other // modules, module expansion in particular has a recursive effect that can // cause deep objects to expand exponentially. Expander assumes that all // instances of a module have the same static objects inside, and that they // differ only in the repetition count for some of those objects. // // Expander is a synchronized object whose methods can be safely called // from concurrent threads of execution. However, it does expect a certain // sequence of operations which is normally obtained by the caller traversing // a dependency graph: each object must have its repetition mode set exactly // once, and this must be done before any calls that depend on the repetition // mode. In other words, the count or for_each expression value for a module // must be provided before any object nested directly or indirectly inside // that module can be expanded. If this ordering is violated, the methods // will panic to enforce internal consistency. // // The Expand* methods of Expander only work directly with modules and with // resources. Addresses for other objects that nest within modules but // do not themselves support repetition can be obtained by calling ExpandModule // with the containing module path and then producing one absolute instance // address per module instance address returned. type Expander struct { mu sync.RWMutex exps *expanderModule } // NewExpander initializes and returns a new Expander, empty and ready to use. func NewExpander() *Expander { return &Expander{ exps: newExpanderModule(), } } // SetModuleSingle records that the given module call inside the given parent // module does not use any repetition arguments and is therefore a singleton. func (e *Expander) SetModuleSingle(parentAddr addrs.ModuleInstance, callAddr addrs.ModuleCall) { e.setModuleExpansion(parentAddr, callAddr, expansionSingleVal) } // SetModuleCount records that the given module call inside the given parent // module instance uses the "count" repetition argument, with the given value. func (e *Expander) SetModuleCount(parentAddr addrs.ModuleInstance, callAddr addrs.ModuleCall, count int) { e.setModuleExpansion(parentAddr, callAddr, expansionCount(count)) } // SetModuleForEach records that the given module call inside the given parent // module instance uses the "for_each" repetition argument, with the given // map value. // // In the configuration language the for_each argument can also accept a set. // It's the caller's responsibility to convert that into an identity map before // calling this method. func (e *Expander) SetModuleForEach(parentAddr addrs.ModuleInstance, callAddr addrs.ModuleCall, mapping map[string]cty.Value) { e.setModuleExpansion(parentAddr, callAddr, expansionForEach(mapping)) } // SetResourceSingle records that the given resource inside the given module // does not use any repetition arguments and is therefore a singleton. func (e *Expander) SetResourceSingle(moduleAddr addrs.ModuleInstance, resourceAddr addrs.Resource) { e.setResourceExpansion(moduleAddr, resourceAddr, expansionSingleVal) } // SetResourceCount records that the given resource inside the given module // uses the "count" repetition argument, with the given value. func (e *Expander) SetResourceCount(moduleAddr addrs.ModuleInstance, resourceAddr addrs.Resource, count int) { e.setResourceExpansion(moduleAddr, resourceAddr, expansionCount(count)) } // SetResourceForEach records that the given resource inside the given module // uses the "for_each" repetition argument, with the given map value. // // In the configuration language the for_each argument can also accept a set. // It's the caller's responsibility to convert that into an identity map before // calling this method. func (e *Expander) SetResourceForEach(moduleAddr addrs.ModuleInstance, resourceAddr addrs.Resource, mapping map[string]cty.Value) { e.setResourceExpansion(moduleAddr, resourceAddr, expansionForEach(mapping)) } // ExpandModule finds the exhaustive set of module instances resulting from // the expansion of the given module and all of its ancestor modules. // // All of the modules on the path to the identified module must already have // had their expansion registered using one of the SetModule* methods before // calling, or this method will panic. func (e *Expander) ExpandModule(addr addrs.Module) []addrs.ModuleInstance { if len(addr) == 0 { // Root module is always a singleton. return singletonRootModule } e.mu.RLock() defer e.mu.RUnlock() // We're going to be dynamically growing ModuleInstance addresses, so // we'll preallocate some space to do it so that for typical shallow // module trees we won't need to reallocate this. // (moduleInstances does plenty of allocations itself, so the benefit of // pre-allocating this is marginal but it's not hard to do.) parentAddr := make(addrs.ModuleInstance, 0, 4) ret := e.exps.moduleInstances(addr, parentAddr) sort.SliceStable(ret, func(i, j int) bool { return ret[i].Less(ret[j]) }) return ret } // ExpandModuleResource finds the exhaustive set of resource instances resulting from // the expansion of the given resource and all of its containing modules. // // All of the modules on the path to the identified resource and the resource // itself must already have had their expansion registered using one of the // SetModule*/SetResource* methods before calling, or this method will panic. func (e *Expander) ExpandModuleResource(moduleAddr addrs.Module, resourceAddr addrs.Resource) []addrs.AbsResourceInstance { e.mu.RLock() defer e.mu.RUnlock() // We're going to be dynamically growing ModuleInstance addresses, so // we'll preallocate some space to do it so that for typical shallow // module trees we won't need to reallocate this. // (moduleInstances does plenty of allocations itself, so the benefit of // pre-allocating this is marginal but it's not hard to do.) moduleInstanceAddr := make(addrs.ModuleInstance, 0, 4) ret := e.exps.moduleResourceInstances(moduleAddr, resourceAddr, moduleInstanceAddr) sort.SliceStable(ret, func(i, j int) bool { return ret[i].Less(ret[j]) }) return ret } // ExpandResource finds the set of resource instances resulting from // the expansion of the given resource within its module instance. // // All of the modules on the path to the identified resource and the resource // itself must already have had their expansion registered using one of the // SetModule*/SetResource* methods before calling, or this method will panic. func (e *Expander) ExpandResource(resourceAddr addrs.AbsResource) []addrs.AbsResourceInstance { e.mu.RLock() defer e.mu.RUnlock() moduleInstanceAddr := make(addrs.ModuleInstance, 0, 4) ret := e.exps.resourceInstances(resourceAddr.Module, resourceAddr.Resource, moduleInstanceAddr) sort.SliceStable(ret, func(i, j int) bool { return ret[i].Less(ret[j]) }) return ret } // GetModuleInstanceRepetitionData returns an object describing the values // that should be available for each.key, each.value, and count.index within // the call block for the given module instance. func (e *Expander) GetModuleInstanceRepetitionData(addr addrs.ModuleInstance) RepetitionData { if len(addr) == 0 { // The root module is always a singleton, so it has no repetition data. return RepetitionData{} } e.mu.RLock() defer e.mu.RUnlock() parentMod := e.findModule(addr[:len(addr)-1]) lastStep := addr[len(addr)-1] exp, ok := parentMod.moduleCalls[addrs.ModuleCall{Name: lastStep.Name}] if !ok { panic(fmt.Sprintf("no expansion has been registered for %s", addr)) } return exp.repetitionData(lastStep.InstanceKey) } // GetResourceInstanceRepetitionData returns an object describing the values // that should be available for each.key, each.value, and count.index within // the definition block for the given resource instance. func (e *Expander) GetResourceInstanceRepetitionData(addr addrs.AbsResourceInstance) RepetitionData { e.mu.RLock() defer e.mu.RUnlock() parentMod := e.findModule(addr.Module) exp, ok := parentMod.resources[addr.Resource.Resource] if !ok { panic(fmt.Sprintf("no expansion has been registered for %s", addr.ContainingResource())) } return exp.repetitionData(addr.Resource.Key) } func (e *Expander) findModule(moduleInstAddr addrs.ModuleInstance) *expanderModule { // We expect that all of the modules on the path to our module instance // should already have expansions registered. mod := e.exps for i, step := range moduleInstAddr { next, ok := mod.childInstances[step] if !ok { // Top-down ordering of registration is part of the contract of // Expander, so this is always indicative of a bug in the caller. panic(fmt.Sprintf("no expansion has been registered for ancestor module %s", moduleInstAddr[:i+1])) } mod = next } return mod } func (e *Expander) setModuleExpansion(parentAddr addrs.ModuleInstance, callAddr addrs.ModuleCall, exp expansion) { e.mu.Lock() defer e.mu.Unlock() mod := e.findModule(parentAddr) if _, exists := mod.moduleCalls[callAddr]; exists { panic(fmt.Sprintf("expansion already registered for %s", parentAddr.Child(callAddr.Name, addrs.NoKey))) } // We'll also pre-register the child instances so that later calls can // populate them as the caller traverses the configuration tree. for _, key := range exp.instanceKeys() { step := addrs.ModuleInstanceStep{Name: callAddr.Name, InstanceKey: key} mod.childInstances[step] = newExpanderModule() } mod.moduleCalls[callAddr] = exp } func (e *Expander) setResourceExpansion(parentAddr addrs.ModuleInstance, resourceAddr addrs.Resource, exp expansion) { e.mu.Lock() defer e.mu.Unlock() mod := e.findModule(parentAddr) if _, exists := mod.resources[resourceAddr]; exists { panic(fmt.Sprintf("expansion already registered for %s", resourceAddr.Absolute(parentAddr))) } mod.resources[resourceAddr] = exp } type expanderModule struct { moduleCalls map[addrs.ModuleCall]expansion resources map[addrs.Resource]expansion childInstances map[addrs.ModuleInstanceStep]*expanderModule } func newExpanderModule() *expanderModule { return &expanderModule{ moduleCalls: make(map[addrs.ModuleCall]expansion), resources: make(map[addrs.Resource]expansion), childInstances: make(map[addrs.ModuleInstanceStep]*expanderModule), } } var singletonRootModule = []addrs.ModuleInstance{addrs.RootModuleInstance} func (m *expanderModule) moduleInstances(addr addrs.Module, parentAddr addrs.ModuleInstance) []addrs.ModuleInstance { callName := addr[0] exp, ok := m.moduleCalls[addrs.ModuleCall{Name: callName}] if !ok { // This is a bug in the caller, because it should always register // expansions for an object and all of its ancestors before requesting // expansion of it. panic(fmt.Sprintf("no expansion has been registered for %s", parentAddr.Child(callName, addrs.NoKey))) } var ret []addrs.ModuleInstance // If there's more than one step remaining then we need to traverse deeper. if len(addr) > 1 { for step, inst := range m.childInstances { if step.Name != callName { continue } instAddr := append(parentAddr, step) ret = append(ret, inst.moduleInstances(addr[1:], instAddr)...) } return ret } // Otherwise, we'll use the expansion from the final step to produce // a sequence of addresses under this prefix. for _, k := range exp.instanceKeys() { // We're reusing the buffer under parentAddr as we recurse through // the structure, so we need to copy it here to produce a final // immutable slice to return. full := make(addrs.ModuleInstance, 0, len(parentAddr)+1) full = append(full, parentAddr...) full = full.Child(callName, k) ret = append(ret, full) } return ret } func (m *expanderModule) moduleResourceInstances(moduleAddr addrs.Module, resourceAddr addrs.Resource, parentAddr addrs.ModuleInstance) []addrs.AbsResourceInstance { if len(moduleAddr) > 0 { var ret []addrs.AbsResourceInstance // We need to traverse through the module levels first, so we can // then iterate resource expansions in the context of each module // path leading to them. callName := moduleAddr[0] if _, ok := m.moduleCalls[addrs.ModuleCall{Name: callName}]; !ok { // This is a bug in the caller, because it should always register // expansions for an object and all of its ancestors before requesting // expansion of it. panic(fmt.Sprintf("no expansion has been registered for %s", parentAddr.Child(callName, addrs.NoKey))) } for step, inst := range m.childInstances { if step.Name != callName { continue } moduleInstAddr := append(parentAddr, step) ret = append(ret, inst.moduleResourceInstances(moduleAddr[1:], resourceAddr, moduleInstAddr)...) } return ret } return m.onlyResourceInstances(resourceAddr, parentAddr) } func (m *expanderModule) resourceInstances(moduleAddr addrs.ModuleInstance, resourceAddr addrs.Resource, parentAddr addrs.ModuleInstance) []addrs.AbsResourceInstance { if len(moduleAddr) > 0 { // We need to traverse through the module levels first, using only the // module instances for our specific resource, as the resource may not // yet be expanded in all module instances. step := moduleAddr[0] callName := step.Name if _, ok := m.moduleCalls[addrs.ModuleCall{Name: callName}]; !ok { // This is a bug in the caller, because it should always register // expansions for an object and all of its ancestors before requesting // expansion of it. panic(fmt.Sprintf("no expansion has been registered for %s", parentAddr.Child(callName, addrs.NoKey))) } inst := m.childInstances[step] moduleInstAddr := append(parentAddr, step) return inst.resourceInstances(moduleAddr[1:], resourceAddr, moduleInstAddr) } return m.onlyResourceInstances(resourceAddr, parentAddr) } func (m *expanderModule) onlyResourceInstances(resourceAddr addrs.Resource, parentAddr addrs.ModuleInstance) []addrs.AbsResourceInstance { var ret []addrs.AbsResourceInstance exp, ok := m.resources[resourceAddr] if !ok { panic(fmt.Sprintf("no expansion has been registered for %s", resourceAddr.Absolute(parentAddr))) } for _, k := range exp.instanceKeys() { // We're reusing the buffer under parentAddr as we recurse through // the structure, so we need to copy it here to produce a final // immutable slice to return. moduleAddr := make(addrs.ModuleInstance, len(parentAddr)) copy(moduleAddr, parentAddr) ret = append(ret, resourceAddr.Instance(k).Absolute(moduleAddr)) } return ret }