diff --git a/internal/addrs/instance_key.go b/internal/addrs/instance_key.go index 0f85ab9918..bb2ac0149b 100644 --- a/internal/addrs/instance_key.go +++ b/internal/addrs/instance_key.go @@ -138,6 +138,11 @@ const ( NoKeyType InstanceKeyType = 0 IntKeyType InstanceKeyType = 'I' StringKeyType InstanceKeyType = 'S' + + // UnknownKeyType is a placeholder key type for situations where Terraform + // doesn't yet know which key type to use. There are no [InstanceKey] + // values of this type. + UnknownKeyType InstanceKeyType = '?' ) // toHCLQuotedString is a helper which formats the given string in a way that diff --git a/internal/instances/expander.go b/internal/instances/expander.go index 355d6d60d0..8f8cce3f6e 100644 --- a/internal/instances/expander.go +++ b/internal/instances/expander.go @@ -314,7 +314,12 @@ func (e *Expander) GetModuleInstanceRepetitionData(addr addrs.ModuleInstance) Re e.mu.RLock() defer e.mu.RUnlock() - parentMod := e.findModule(addr[:len(addr)-1]) + parentMod, known := e.findModule(addr[:len(addr)-1]) + if !known { + // If we're nested inside something unexpanded then we don't even + // know what type of expansion we're doing. + return TotallyUnknownRepetitionData + } lastStep := addr[len(addr)-1] exp, ok := parentMod.moduleCalls[addrs.ModuleCall{Name: lastStep.Name}] if !ok { @@ -323,6 +328,34 @@ func (e *Expander) GetModuleInstanceRepetitionData(addr addrs.ModuleInstance) Re return exp.repetitionData(lastStep.InstanceKey) } +// GetModuleCallInstanceKeys determines the child instance keys for one specific +// instance of a module call. +// +// keyType describes the expected type of all keys in knownKeys, which typically +// also implies what data type would be used to describe the full set of +// instances: [addrs.IntKeyType] as a list or tuple, [addrs.StringKeyType] as +// a map or object, and [addrs.NoKeyType] as just a single value. +// +// If unknownKeys is true then there might be additional keys that we can't know +// yet because the call's expansion isn't known. +func (e *Expander) GetModuleCallInstanceKeys(addr addrs.AbsModuleCall) (keyType addrs.InstanceKeyType, knownKeys []addrs.InstanceKey, unknownKeys bool) { + e.mu.RLock() + defer e.mu.RUnlock() + + parentMod, known := e.findModule(addr.Module) + if !known { + // If we're nested inside something unexpanded then we don't even + // know yet what kind of instance key to expect. (The caller might + // be able to infer this itself using configuration info, though.) + return addrs.UnknownKeyType, nil, true + } + exp, ok := parentMod.moduleCalls[addr.Call] + if !ok { + panic(fmt.Sprintf("no expansion has been registered for %s", addr)) + } + return exp.instanceKeys() +} + // 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. @@ -330,7 +363,12 @@ func (e *Expander) GetResourceInstanceRepetitionData(addr addrs.AbsResourceInsta e.mu.RLock() defer e.mu.RUnlock() - parentMod := e.findModule(addr.Module) + parentMod, known := e.findModule(addr.Module) + if !known { + // If we're nested inside something unexpanded then we don't even + // know what type of expansion we're doing. + return TotallyUnknownRepetitionData + } exp, ok := parentMod.resources[addr.Resource.Resource] if !ok { panic(fmt.Sprintf("no expansion has been registered for %s", addr.ContainingResource())) @@ -338,6 +376,34 @@ func (e *Expander) GetResourceInstanceRepetitionData(addr addrs.AbsResourceInsta return exp.repetitionData(addr.Resource.Key) } +// GetModuleCallInstanceKeys determines the child instance keys for one specific +// instance of a module call. +// +// keyType describes the expected type of all keys in knownKeys, which typically +// also implies what data type would be used to describe the full set of +// instances: [addrs.IntKeyType] as a list or tuple, [addrs.StringKeyType] as +// a map or object, and [addrs.NoKeyType] as just a single value. +// +// If unknownKeys is true then there might be additional keys that we can't know +// yet because the call's expansion isn't known. +func (e *Expander) ResourceInstanceKeys(addr addrs.AbsResource) (keyType addrs.InstanceKeyType, knownKeys []addrs.InstanceKey, unknownKeys bool) { + e.mu.RLock() + defer e.mu.RUnlock() + + parentMod, known := e.findModule(addr.Module) + if !known { + // If we're nested inside something unexpanded then we don't even + // know yet what kind of instance key to expect. (The caller might + // be able to infer this itself using configuration info, though.) + return addrs.UnknownKeyType, nil, true + } + exp, ok := parentMod.resources[addr.Resource] + if !ok { + panic(fmt.Sprintf("no expansion has been registered for %s", addr)) + } + return exp.instanceKeys() +} + // AllInstances returns a set of all of the module and resource instances known // to the expander. // @@ -350,11 +416,14 @@ func (e *Expander) AllInstances() Set { return Set{e} } -func (e *Expander) findModule(moduleInstAddr addrs.ModuleInstance) *expanderModule { +func (e *Expander) findModule(moduleInstAddr addrs.ModuleInstance) (expMod *expanderModule, known bool) { // 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 { + if expansionIsDeferred(mod.moduleCalls[addrs.ModuleCall{Name: step.Name}]) { + return nil, false + } next, ok := mod.childInstances[step] if !ok { // Top-down ordering of registration is part of the contract of @@ -363,21 +432,25 @@ func (e *Expander) findModule(moduleInstAddr addrs.ModuleInstance) *expanderModu } mod = next } - return mod + return mod, true } func (e *Expander) setModuleExpansion(parentAddr addrs.ModuleInstance, callAddr addrs.ModuleCall, exp expansion) { e.mu.Lock() defer e.mu.Unlock() - mod := e.findModule(parentAddr) + mod, known := e.findModule(parentAddr) + if !known { + panic(fmt.Sprintf("can't register expansion for call in %s beneath unexpanded parent", parentAddr)) + } if _, exists := mod.moduleCalls[callAddr]; exists { panic(fmt.Sprintf("expansion already registered for %s", parentAddr.Child(callAddr.Name, addrs.NoKey))) } if !expansionIsDeferred(exp) { // 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() { + _, knownKeys, _ := exp.instanceKeys() + for _, key := range knownKeys { step := addrs.ModuleInstanceStep{Name: callAddr.Name, InstanceKey: key} mod.childInstances[step] = newExpanderModule() } @@ -389,7 +462,10 @@ func (e *Expander) setResourceExpansion(parentAddr addrs.ModuleInstance, resourc e.mu.Lock() defer e.mu.Unlock() - mod := e.findModule(parentAddr) + mod, known := e.findModule(parentAddr) + if !known { + panic(fmt.Sprintf("can't register expansion in %s where path includes unknown expansion", parentAddr)) + } if _, exists := mod.resources[resourceAddr]; exists { panic(fmt.Sprintf("expansion already registered for %s", resourceAddr.Absolute(parentAddr))) } @@ -481,7 +557,8 @@ func (m *expanderModule) moduleInstances(addr addrs.Module, parentAddr addrs.Mod // Otherwise, we'll use the expansion from the final step to produce // a sequence of addresses under this prefix. - for _, k := range exp.instanceKeys() { + _, knownKeys, _ := exp.instanceKeys() + for _, k := range knownKeys { // 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. @@ -665,7 +742,8 @@ func (m *expanderModule) onlyResourceInstances(resourceAddr addrs.Resource, pare return nil } - for _, k := range exp.instanceKeys() { + _, knownKeys, _ := exp.instanceKeys() + for _, k := range knownKeys { // 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. @@ -710,7 +788,8 @@ func (m *expanderModule) knowsResourceInstance(want addrs.AbsResourceInstance) b if resourceExp == nil { return false } - for _, key := range resourceExp.instanceKeys() { + _, knownKeys, _ := resourceExp.instanceKeys() + for _, key := range knownKeys { if key == want.Resource.Key { return true } diff --git a/internal/instances/expansion_mode.go b/internal/instances/expansion_mode.go index c039759190..4f396e20d3 100644 --- a/internal/instances/expansion_mode.go +++ b/internal/instances/expansion_mode.go @@ -16,7 +16,7 @@ import ( // ways expansion can operate depending on how repetition is configured for // an object. type expansion interface { - instanceKeys() []addrs.InstanceKey + instanceKeys() (addrs.InstanceKeyType, []addrs.InstanceKey, bool) repetitionData(addrs.InstanceKey) RepetitionData } @@ -29,8 +29,8 @@ type expansionSingle uintptr var singleKeys = []addrs.InstanceKey{addrs.NoKey} var expansionSingleVal expansionSingle -func (e expansionSingle) instanceKeys() []addrs.InstanceKey { - return singleKeys +func (e expansionSingle) instanceKeys() (addrs.InstanceKeyType, []addrs.InstanceKey, bool) { + return addrs.NoKeyType, singleKeys, false } func (e expansionSingle) repetitionData(key addrs.InstanceKey) RepetitionData { @@ -43,12 +43,12 @@ func (e expansionSingle) repetitionData(key addrs.InstanceKey) RepetitionData { // expansionCount is the expansion corresponding to the "count" argument. type expansionCount int -func (e expansionCount) instanceKeys() []addrs.InstanceKey { +func (e expansionCount) instanceKeys() (addrs.InstanceKeyType, []addrs.InstanceKey, bool) { ret := make([]addrs.InstanceKey, int(e)) for i := range ret { ret[i] = addrs.IntKey(i) } - return ret + return addrs.IntKeyType, ret, false } func (e expansionCount) repetitionData(key addrs.InstanceKey) RepetitionData { @@ -64,7 +64,7 @@ func (e expansionCount) repetitionData(key addrs.InstanceKey) RepetitionData { // expansionForEach is the expansion corresponding to the "for_each" argument. type expansionForEach map[string]cty.Value -func (e expansionForEach) instanceKeys() []addrs.InstanceKey { +func (e expansionForEach) instanceKeys() (addrs.InstanceKeyType, []addrs.InstanceKey, bool) { ret := make([]addrs.InstanceKey, 0, len(e)) for k := range e { ret = append(ret, addrs.StringKey(k)) @@ -72,7 +72,7 @@ func (e expansionForEach) instanceKeys() []addrs.InstanceKey { sort.Slice(ret, func(i, j int) bool { return ret[i].(addrs.StringKey) < ret[j].(addrs.StringKey) }) - return ret + return addrs.StringKeyType, ret, false } func (e expansionForEach) repetitionData(key addrs.InstanceKey) RepetitionData { @@ -98,12 +98,12 @@ type expansionDeferred rune const expansionDeferredIntKey = expansionDeferred(addrs.IntKeyType) const expansionDeferredStringKey = expansionDeferred(addrs.StringKeyType) -func (e expansionDeferred) instanceKeys() []addrs.InstanceKey { - panic("cannot expand when expansion was deferred") +func (e expansionDeferred) instanceKeys() (addrs.InstanceKeyType, []addrs.InstanceKey, bool) { + return addrs.InstanceKeyType(e), nil, true } func (e expansionDeferred) repetitionData(key addrs.InstanceKey) RepetitionData { - panic("cannot expand when expansion was deferred") + panic("no known instances for object with deferred expansion") } func expansionIsDeferred(exp expansion) bool { diff --git a/internal/instances/instance_key_data.go b/internal/instances/instance_key_data.go index bd5dcd264d..8744d71123 100644 --- a/internal/instances/instance_key_data.go +++ b/internal/instances/instance_key_data.go @@ -30,6 +30,14 @@ type RepetitionData struct { EachKey, EachValue cty.Value } +// TotallyUnknownRepetitionData is a [RepetitionData] value for situations +// where don't even know yet what type of repetition will be used. +var TotallyUnknownRepetitionData = RepetitionData{ + CountIndex: cty.UnknownVal(cty.Number), + EachKey: cty.UnknownVal(cty.String), + EachValue: cty.DynamicVal, +} + // UnknownCountRepetitionData is a suitable [RepetitionData] value to use when // evaluating the configuration of an object which has a count argument that // is currently unknown.