From 2c4c027a9777339867de706786f54d687773debd Mon Sep 17 00:00:00 2001 From: James Bardin Date: Mon, 13 Apr 2020 17:59:09 -0400 Subject: [PATCH] Add ModuleOutputs method to states In order to efficiently build the module objects for evaluation, we need to collect the outputs from a set of module instances. The ModuleOutputs method will return a copy of the state outputs, while not requiring the unnecessary copying of each entire module. --- states/state.go | 29 ++++++++++++++++++++++ states/state_test.go | 57 ++++++++++++++++++++++++++++++++++++++++++++ states/sync.go | 12 ++++++++++ 3 files changed, 98 insertions(+) diff --git a/states/state.go b/states/state.go index ed77eb5f0..6d0cec148 100644 --- a/states/state.go +++ b/states/state.go @@ -82,6 +82,35 @@ func (s *State) ModuleInstances(addr addrs.Module) []*Module { return ms } +// ModuleOutputs returns all outputs for the given module call under the +// parentAddr instance. +func (s *State) ModuleOutputs(parentAddr addrs.ModuleInstance, module addrs.ModuleCall) []*OutputValue { + var os []*OutputValue + for _, m := range s.Modules { + // can't get outputs from the root module + if m.Addr.IsRoot() { + continue + } + + parent, call := m.Addr.Call() + // make sure this is a descendent in the correct path + if !parentAddr.Equal(parent) { + continue + } + + // and check if this is the correct child + if call.Name != module.Name { + continue + } + + for _, o := range m.OutputValues { + os = append(os, o) + } + } + + return os +} + // RemoveModule removes the module with the given address from the state, // unless it is the root module. The root module cannot be deleted, and so // this method will panic if that is attempted. diff --git a/states/state_test.go b/states/state_test.go index 8fe191d57..cd57757cb 100644 --- a/states/state_test.go +++ b/states/state_test.go @@ -43,6 +43,10 @@ func TestState(t *testing.T) { childModule := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) childModule.SetOutputValue("pizza", cty.StringVal("hawaiian"), false) + multiModA := state.EnsureModule(addrs.RootModuleInstance.Child("multi", addrs.StringKey("a"))) + multiModA.SetOutputValue("pizza", cty.StringVal("cheese"), false) + multiModB := state.EnsureModule(addrs.RootModuleInstance.Child("multi", addrs.StringKey("b"))) + multiModB.SetOutputValue("pizza", cty.StringVal("sausage"), false) want := &State{ Modules: map[string]*Module{ @@ -114,6 +118,40 @@ func TestState(t *testing.T) { }, Resources: map[string]*Resource{}, }, + `module.multi["a"]`: { + Addr: addrs.RootModuleInstance.Child("multi", addrs.StringKey("a")), + LocalValues: map[string]cty.Value{}, + OutputValues: map[string]*OutputValue{ + "pizza": { + Addr: addrs.AbsOutputValue{ + Module: addrs.RootModuleInstance.Child("multi", addrs.StringKey("a")), + OutputValue: addrs.OutputValue{ + Name: "pizza", + }, + }, + Value: cty.StringVal("cheese"), + Sensitive: false, + }, + }, + Resources: map[string]*Resource{}, + }, + `module.multi["b"]`: { + Addr: addrs.RootModuleInstance.Child("multi", addrs.StringKey("b")), + LocalValues: map[string]cty.Value{}, + OutputValues: map[string]*OutputValue{ + "pizza": { + Addr: addrs.AbsOutputValue{ + Module: addrs.RootModuleInstance.Child("multi", addrs.StringKey("b")), + OutputValue: addrs.OutputValue{ + Name: "pizza", + }, + }, + Value: cty.StringVal("sausage"), + Sensitive: false, + }, + }, + Resources: map[string]*Resource{}, + }, }, } @@ -133,6 +171,25 @@ func TestState(t *testing.T) { for _, problem := range deep.Equal(state, want) { t.Error(problem) } + + expectedOutputs := map[string]string{ + `module.multi["a"].output.pizza`: "cheese", + `module.multi["b"].output.pizza`: "sausage", + } + + for _, o := range state.ModuleOutputs(addrs.RootModuleInstance, addrs.ModuleCall{Name: "multi"}) { + addr := o.Addr.String() + expected := expectedOutputs[addr] + delete(expectedOutputs, addr) + + if expected != o.Value.AsString() { + t.Fatalf("expected %q:%q, got %q", addr, expected, o.Value.AsString()) + } + } + + for addr, o := range expectedOutputs { + t.Fatalf("missing output %q:%q", addr, o) + } } func TestStateDeepCopy(t *testing.T) { diff --git a/states/sync.go b/states/sync.go index af391e55f..4b82c69fb 100644 --- a/states/sync.go +++ b/states/sync.go @@ -48,6 +48,18 @@ func (s *SyncState) Module(addr addrs.ModuleInstance) *Module { return ret } +// ModuleOutputs returns the set of OutputValues that matches the given path. +func (s *SyncState) ModuleOutputs(parentAddr addrs.ModuleInstance, module addrs.ModuleCall) []*OutputValue { + s.lock.RLock() + defer s.lock.RUnlock() + var os []*OutputValue + + for _, o := range s.state.ModuleOutputs(parentAddr, module) { + os = append(os, o.DeepCopy()) + } + return os +} + // RemoveModule removes the entire state for the given module, taking with // it any resources associated with the module. This should generally be // called only for modules whose resources have all been destroyed, but