Skip to content

Commit

Permalink
terraform test: move override evaluation into expander
Browse files Browse the repository at this point in the history
  • Loading branch information
liamcervante committed Apr 19, 2024
1 parent 2b6af4a commit cd258b2
Show file tree
Hide file tree
Showing 31 changed files with 558 additions and 332 deletions.
46 changes: 8 additions & 38 deletions internal/command/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,10 +342,16 @@ func TestValidateWithInvalidOverrides(t *testing.T) {
Meta: meta,
}

output := done(t)

Check failure on line 345 in internal/command/validate_test.go

View workflow job for this annotation

GitHub Actions / Code Consistency Checks

this value of output is never used (SA4006)
if code := init.Run(nil); code != 0 {
t.Fatalf("expected status code 0 but got %d: %s", code, ui.ErrorWriter)
}

// reset streams for next command
streams, done = terminal.StreamsForTesting(t)
meta.View = views.NewView(streams)
meta.Streams = streams

c := &ValidateCommand{
Meta: meta,
}
Expand All @@ -354,50 +360,14 @@ func TestValidateWithInvalidOverrides(t *testing.T) {
args = append(args, "-no-color")

code := c.Run(args)
output := done(t)
output = done(t)

if code != 0 {
t.Errorf("Should have passed: %d\n\n%s", code, output.Stderr())
}

actual := output.All()
expected := `Initializing the backend...
Initializing modules...
- setup in setup
- test.main.setup in setup
Initializing provider plugins...
- Finding latest version of hashicorp/test...
- Installing hashicorp/test v1.0.0...
- Installed hashicorp/test v1.0.0 (verified checksum)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Warning: Incomplete lock file information for providers
Due to your customized provider installation methods, Terraform was forced to
calculate lock file checksums locally for the following providers:
- hashicorp/test
The current .terraform.lock.hcl file only includes checksums for linux_amd64,
so Terraform running on another platform will fail to install these
providers.
To calculate additional checksums for another platform, run:
terraform providers lock -platform=linux_amd64
(where linux_amd64 is the platform to generate)
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
expected := `
Warning: Invalid override target
on main.tftest.hcl line 4, in mock_provider "test":
Expand Down
71 changes: 57 additions & 14 deletions internal/instances/expander.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"sync"

"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/moduletest/mocking"

"github.com/zclconf/go-cty/cty"
)

Expand Down Expand Up @@ -44,9 +46,9 @@ type Expander struct {
}

// NewExpander initializes and returns a new Expander, empty and ready to use.
func NewExpander() *Expander {
func NewExpander(overrides *mocking.Overrides) *Expander {
return &Expander{
exps: newExpanderModule(),
exps: newExpanderModule(overrides),
}
}

Expand Down Expand Up @@ -131,8 +133,10 @@ func (e *Expander) SetResourceForEachUnknown(moduleAddr addrs.ModuleInstance, re
// 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 {
return e.expandModule(addr, false)
//
// Any overridden modules will not be included in the result here.
func (e *Expander) ExpandModule(addr addrs.Module, includeDirectOverrides bool) []addrs.ModuleInstance {
return e.expandModule(addr, false, includeDirectOverrides)
}

// ExpandAbsModuleCall is similar to [Expander.ExpandModule] except that it
Expand Down Expand Up @@ -180,7 +184,7 @@ func (e *Expander) ExpandAbsModuleCall(addr addrs.AbsModuleCall) (keyType addrs.
// 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.
func (e *Expander) expandModule(addr addrs.Module, skipUnregistered bool) []addrs.ModuleInstance {
func (e *Expander) expandModule(addr addrs.Module, skipUnregistered, includeDirectOverrides bool) []addrs.ModuleInstance {
if len(addr) == 0 {
// Root module is always a singleton.
return singletonRootModule
Expand All @@ -195,7 +199,7 @@ func (e *Expander) expandModule(addr addrs.Module, skipUnregistered bool) []addr
// (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, skipUnregistered)
ret := e.exps.moduleInstances(addr, parentAddr, skipUnregistered, includeDirectOverrides)
sort.SliceStable(ret, func(i, j int) bool {
return ret[i].Less(ret[j])
})
Expand All @@ -215,7 +219,7 @@ func (e *Expander) expandModule(addr addrs.Module, skipUnregistered bool) []addr
// considered as the union of all of those sets but we return it as a set of
// sets because the inner sets are of infinite size while the outer set is
// finite.
func (e *Expander) UnknownModuleInstances(addr addrs.Module) addrs.Set[addrs.PartialExpandedModule] {
func (e *Expander) UnknownModuleInstances(addr addrs.Module, includeDirectOverrides bool) addrs.Set[addrs.PartialExpandedModule] {
if len(addr) == 0 {
// The root module is always "expanded" because it's always a singleton,
// so we have nothing to return in that case.
Expand All @@ -227,7 +231,7 @@ func (e *Expander) UnknownModuleInstances(addr addrs.Module) addrs.Set[addrs.Par

ret := addrs.MakeSet[addrs.PartialExpandedModule]()
parentAddr := make(addrs.ModuleInstance, 0, 4)
e.exps.partialExpandedModuleInstances(addr, parentAddr, ret)
e.exps.partialExpandedModuleInstances(addr, parentAddr, includeDirectOverrides, ret)
return ret
}

Expand Down Expand Up @@ -494,7 +498,7 @@ func (e *Expander) setModuleExpansion(parentAddr addrs.ModuleInstance, callAddr
_, knownKeys, _ := exp.instanceKeys()
for _, key := range knownKeys {
step := addrs.ModuleInstanceStep{Name: callAddr.Name, InstanceKey: key}
mod.childInstances[step] = newExpanderModule()
mod.childInstances[step] = newExpanderModule(e.exps.overrides)
}
}
mod.moduleCalls[callAddr] = exp
Expand Down Expand Up @@ -550,13 +554,19 @@ type expanderModule struct {
moduleCalls map[addrs.ModuleCall]expansion
resources map[addrs.Resource]expansion
childInstances map[addrs.ModuleInstanceStep]*expanderModule

// overrides ensures that any overriden modules instances will not be
// returned as options for expansion. A nil overrides indicates there are
// no overrides and we're not operating within the testing framework.
overrides *mocking.Overrides
}

func newExpanderModule() *expanderModule {
func newExpanderModule(overrides *mocking.Overrides) *expanderModule {
return &expanderModule{
moduleCalls: make(map[addrs.ModuleCall]expansion),
resources: make(map[addrs.Resource]expansion),
childInstances: make(map[addrs.ModuleInstanceStep]*expanderModule),
overrides: overrides,
}
}

Expand All @@ -566,8 +576,16 @@ var singletonRootModule = []addrs.ModuleInstance{addrs.RootModuleInstance}
// expansions have been done, set skipUnregistered to true which allows addrs
// which may not have been seen to return with no instances rather than
// panicking.
func (m *expanderModule) moduleInstances(addr addrs.Module, parentAddr addrs.ModuleInstance, skipUnregistered bool) []addrs.ModuleInstance {
func (m *expanderModule) moduleInstances(addr addrs.Module, parentAddr addrs.ModuleInstance, skipUnregistered, includeDirectOverrides bool) []addrs.ModuleInstance {
callName := addr[0]

// If the parent module is overridden then this module should not be
// expanded. Note, we don't check includeDirectOverrides because if the
// parent module is overridden then this module isn't "directly" overridden.
if _, overridden := m.overrides.GetModuleOverride(parentAddr); overridden {
return nil
}

exp, ok := m.moduleCalls[addrs.ModuleCall{Name: callName}]
if !ok {
if skipUnregistered {
Expand All @@ -593,15 +611,26 @@ func (m *expanderModule) moduleInstances(addr addrs.Module, parentAddr addrs.Mod
continue
}
instAddr := append(parentAddr, step)
ret = append(ret, inst.moduleInstances(addr[1:], instAddr, skipUnregistered)...)
ret = append(ret, inst.moduleInstances(addr[1:], instAddr, skipUnregistered, includeDirectOverrides)...)
}
return ret
}

if _, overridden := m.overrides.GetModuleOverride(parentAddr.Child(callName, addrs.NoKey)); !includeDirectOverrides && overridden {
// Then all the potential instances of this module have been
// overridden so we don't want to do any expansion for them.
return ret
}

// Otherwise, we'll use the expansion from the final step to produce
// a sequence of addresses under this prefix.
_, knownKeys, _ := exp.instanceKeys()
for _, k := range knownKeys {
if _, overridden := m.overrides.GetModuleOverride(parentAddr.Child(callName, k)); !includeDirectOverrides && overridden {
// This specific instance is overridden, so we won't return it.
continue
}

// 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.
Expand All @@ -613,8 +642,16 @@ func (m *expanderModule) moduleInstances(addr addrs.Module, parentAddr addrs.Mod
return ret
}

func (m *expanderModule) partialExpandedModuleInstances(addr addrs.Module, parentAddr addrs.ModuleInstance, into addrs.Set[addrs.PartialExpandedModule]) {
func (m *expanderModule) partialExpandedModuleInstances(addr addrs.Module, parentAddr addrs.ModuleInstance, includeDirectOverrides bool, into addrs.Set[addrs.PartialExpandedModule]) {
callName := addr[0]

// If the parent module is overridden then this module should not be
// expanded. Note, we don't check includeDirectOverrides because if the
// parent module is overridden then this module isn't "directly" overridden.
if _, overridden := m.overrides.GetModuleOverride(parentAddr); overridden {
return
}

exp, ok := m.moduleCalls[addrs.ModuleCall{Name: callName}]
if !ok {
// This is a bug in the caller, because it should always register
Expand All @@ -623,6 +660,12 @@ func (m *expanderModule) partialExpandedModuleInstances(addr addrs.Module, paren
panic(fmt.Sprintf("no expansion has been registered for %s", parentAddr.Child(callName, addrs.NoKey)))
}
if expansionIsDeferred(exp) {
if _, overridden := m.overrides.GetModuleOverride(parentAddr.Child(callName, addrs.NoKey)); !includeDirectOverrides && overridden {
// Then all the potential instances of this module have been
// overridden so we don't want to do any expansion for them.
return
}

// We've found a deferred expansion, so we're done searching this
// subtree and can just treat the whole of "addr" as unexpanded
// calls.
Expand All @@ -642,7 +685,7 @@ func (m *expanderModule) partialExpandedModuleInstances(addr addrs.Module, paren
continue
}
instAddr := append(parentAddr, step)
inst.partialExpandedModuleInstances(addr[1:], instAddr, into)
inst.partialExpandedModuleInstances(addr[1:], instAddr, includeDirectOverrides, into)
}
}
}
Expand Down

0 comments on commit cd258b2

Please sign in to comment.