diff --git a/internal/instances/expander.go b/internal/instances/expander.go index 8dcd378f88..497daae2f6 100644 --- a/internal/instances/expander.go +++ b/internal/instances/expander.go @@ -135,6 +135,45 @@ func (e *Expander) ExpandModule(addr addrs.Module) []addrs.ModuleInstance { return e.expandModule(addr, false) } +// ExpandAbsModuleCall is similar to [Expander.ExpandModule] except that it +// filters the result to include only the instances that belong to the +// given module call instance, and therefore returns just instance keys +// since the rest of the module address is implied by the given argument. +// +// For example, passing an address representing module.a["foo"].module.b +// would include only instances under module.a["foo"], and disregard instances +// under other dynamic paths like module.a["bar"]. +// +// If the requested module call has an unknown expansion (e.g. because it +// had an unknown value for count or for_each) then the second result is +// false and the other results are meaningless. If the second return value is +// true, then the set of module instances is complete, and all of the instances +// have instance keys matching the returned keytype. +// +// The instances are returned in the typical sort order for the returned +// key type: integer keys are sorted numerically, and string keys are sorted +// lexically. +func (e *Expander) ExpandAbsModuleCall(addr addrs.AbsModuleCall) (keyType addrs.InstanceKeyType, insts []addrs.InstanceKey, known bool) { + expParent, ok := e.findModule(addr.Module) + if !ok { + // This module call lives under an unknown-expansion prefix, so we + // cannot answer this question. + return addrs.NoKeyType, nil, false + } + + expCall, ok := expParent.moduleCalls[addr.Call] + if !ok { + // This indicates a bug, since we should've calculated the expansions + // (even if unknown) before any caller asks for the results. + panic(fmt.Sprintf("no expansion has been registered for %s", addr.String())) + } + keyType, instKeys, deferred := expCall.instanceKeys() + if deferred { + return addrs.NoKeyType, nil, false + } + return keyType, instKeys, true +} + // expandModule allows skipping unexpanded module addresses by setting skipUnregistered to true. // This is used by instances.Set, which is only concerned with the expanded // instances, and should not panic when looking up unknown addresses. diff --git a/internal/instances/expander_test.go b/internal/instances/expander_test.go index 74cae227ed..a06a4da132 100644 --- a/internal/instances/expander_test.go +++ b/internal/instances/expander_test.go @@ -297,6 +297,24 @@ func TestExpander(t *testing.T) { t.Errorf("wrong result\n%s", diff) } }) + t.Run("module count2[0] module count2 instances", func(t *testing.T) { + instAddr := mustModuleInstanceAddr(`module.count2[0].module.count2[0]`) + callAddr := instAddr.AbsCall() // discards the final [0] instance key from the above + keyType, got, known := ex.ExpandAbsModuleCall(callAddr) + if !known { + t.Fatal("expansion unknown; want known") + } + if keyType != addrs.IntKeyType { + t.Fatalf("wrong key type %#v; want %#v", keyType, addrs.IntKeyType) + } + want := []addrs.InstanceKey{ + addrs.IntKey(0), + addrs.IntKey(1), + } + if diff := cmp.Diff(want, got); diff != "" { + t.Errorf("wrong result\n%s", diff) + } + }) t.Run("module count2 module count2 GetDeepestExistingModuleInstance", func(t *testing.T) { t.Run("first step invalid", func(t *testing.T) { got := ex.GetDeepestExistingModuleInstance(mustModuleInstanceAddr(`module.count2["nope"].module.count2[0]`)) @@ -596,6 +614,15 @@ func TestExpanderWithUnknowns(t *testing.T) { ex.SetResourceCount(module1Inst1Module2Inst0, resourceAddrKnownExp, 2) ex.SetResourceCountUnknown(module1Inst1Module2Inst0, resourceAddrUnknownExp) + module2Call := addrs.AbsModuleCall{ + Module: module1Inst0, + Call: moduleCallAddr2, + } + _, _, instsKnown := ex.ExpandAbsModuleCall(module2Call) + if instsKnown { + t.Fatalf("instances of %s are known; should be unknown", module2Call.String()) + } + gotKnown := ex.ExpandModule(module2) wantKnown := []addrs.ModuleInstance{ module1Inst1.Child("bar", addrs.IntKey(0)),