From 0c3a581ff016a62ebaefc0335e8207c3decc8ce9 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Thu, 18 Jan 2024 16:49:27 -0800 Subject: [PATCH] instances: Expander.ExpandAbsModuleCall This is a similar idea to Expander.ExpandModule but for situations where we already know exactly which containing module we're evaluating inside, and thus we can just pluck out the leaf instance keys of that specific dynamic call, rather than the full recursive expansion of every containing module. Expander.ExpandModule is useful when implementing DynamicExpand for a graph node because main graph nodes always represent static configuration objects, but this new Expander.ExpandAbsModuleCall is more appropriate for use during expression evaluation of references to a module call because in that case we'll know which specific module instance address we're using as the evaluation scope. A future commit will actually use this new method from the expression evaluator in the modules runtime. --- internal/instances/expander.go | 39 +++++++++++++++++++++++++++++ internal/instances/expander_test.go | 27 ++++++++++++++++++++ 2 files changed, 66 insertions(+) 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)),