instances: Expander.AllInstances

In order to precisely implement the validation rules for "moved"
statements we need to be able to test whether particular instances were
declared in the configuration.

The instance expander is the source of record for which instances we
decided while creating a plan, but it's API is far more involved than what
our validation rules need, so this new AllInstances method returns a
wrapper object with a more straightforward API that provides read-only
access to just the question of whether particular instances got registered
in the expander already.

This API covers all three of the kinds of objects that move statements can
refer to. It includes module calls and resources, even though they aren't
_themselves_ "instances" in the sense we usually mean, because the module
instance addresses they are contained within _are_ instances and so we
need to take their dynamic instance keys into account when answering these
queries.
This commit is contained in:
Martin Atkins 2021-07-28 11:05:01 -07:00
parent 57d36c1d9d
commit 51346f0d87
3 changed files with 343 additions and 0 deletions

View File

@ -197,6 +197,18 @@ func (e *Expander) GetResourceInstanceRepetitionData(addr addrs.AbsResourceInsta
return exp.repetitionData(addr.Resource.Key) return exp.repetitionData(addr.Resource.Key)
} }
// AllInstances returns a set of all of the module and resource instances known
// to the expander.
//
// It generally doesn't make sense to call this until everything has already
// been fully expanded by calling the SetModule* and SetResource* functions.
// After that, the returned set is a convenient small API only for querying
// whether particular instance addresses appeared as a result of those
// expansions.
func (e *Expander) AllInstances() Set {
return Set{e}
}
func (e *Expander) findModule(moduleInstAddr addrs.ModuleInstance) *expanderModule { func (e *Expander) findModule(moduleInstAddr addrs.ModuleInstance) *expanderModule {
// We expect that all of the modules on the path to our module instance // We expect that all of the modules on the path to our module instance
// should already have expansions registered. // should already have expansions registered.
@ -241,6 +253,38 @@ func (e *Expander) setResourceExpansion(parentAddr addrs.ModuleInstance, resourc
mod.resources[resourceAddr] = exp mod.resources[resourceAddr] = exp
} }
func (e *Expander) knowsModuleInstance(want addrs.ModuleInstance) bool {
if want.IsRoot() {
return true // root module instance is always present
}
e.mu.Lock()
defer e.mu.Unlock()
return e.exps.knowsModuleInstance(want)
}
func (e *Expander) knowsModuleCall(want addrs.AbsModuleCall) bool {
e.mu.Lock()
defer e.mu.Unlock()
return e.exps.knowsModuleCall(want)
}
func (e *Expander) knowsResourceInstance(want addrs.AbsResourceInstance) bool {
e.mu.Lock()
defer e.mu.Unlock()
return e.exps.knowsResourceInstance(want)
}
func (e *Expander) knowsResource(want addrs.AbsResource) bool {
e.mu.Lock()
defer e.mu.Unlock()
return e.exps.knowsResource(want)
}
type expanderModule struct { type expanderModule struct {
moduleCalls map[addrs.ModuleCall]expansion moduleCalls map[addrs.ModuleCall]expansion
resources map[addrs.Resource]expansion resources map[addrs.Resource]expansion
@ -360,3 +404,54 @@ func (m *expanderModule) onlyResourceInstances(resourceAddr addrs.Resource, pare
} }
return ret return ret
} }
func (m *expanderModule) getModuleInstance(want addrs.ModuleInstance) *expanderModule {
current := m
for _, step := range want {
next := current.childInstances[step]
if next == nil {
return nil
}
current = next
}
return current
}
func (m *expanderModule) knowsModuleInstance(want addrs.ModuleInstance) bool {
return m.getModuleInstance(want) != nil
}
func (m *expanderModule) knowsModuleCall(want addrs.AbsModuleCall) bool {
modInst := m.getModuleInstance(want.Module)
if modInst == nil {
return false
}
_, ret := modInst.moduleCalls[want.Call]
return ret
}
func (m *expanderModule) knowsResourceInstance(want addrs.AbsResourceInstance) bool {
modInst := m.getModuleInstance(want.Module)
if modInst == nil {
return false
}
resourceExp := modInst.resources[want.Resource.Resource]
if resourceExp == nil {
return false
}
for _, key := range resourceExp.instanceKeys() {
if key == want.Resource.Key {
return true
}
}
return false
}
func (m *expanderModule) knowsResource(want addrs.AbsResource) bool {
modInst := m.getModuleInstance(want.Module)
if modInst == nil {
return false
}
_, ret := modInst.resources[want.Resource]
return ret
}

41
internal/instances/set.go Normal file
View File

@ -0,0 +1,41 @@
package instances
import (
"github.com/hashicorp/terraform/internal/addrs"
)
// Set is a set of instances, intended mainly for the return value of
// Expander.AllInstances, where it therefore represents all of the module
// and resource instances known to the expander.
type Set struct {
// Set currently really just wraps Expander with a reduced API that
// only supports lookups, to make it clear that a holder of a Set should
// not be modifying the expander any further.
exp *Expander
}
// HasModuleInstance returns true if and only if the set contains the module
// instance with the given address.
func (s Set) HasModuleInstance(want addrs.ModuleInstance) bool {
return s.exp.knowsModuleInstance(want)
}
// HasModuleCall returns true if and only if the set contains the module
// call with the given address, even if that module call has no instances.
func (s Set) HasModuleCall(want addrs.AbsModuleCall) bool {
return s.exp.knowsModuleCall(want)
}
// HasResourceInstance returns true if and only if the set contains the resource
// instance with the given address.
// TODO:
func (s Set) HasResourceInstance(want addrs.AbsResourceInstance) bool {
return s.exp.knowsResourceInstance(want)
}
// HasResource returns true if and only if the set contains the resource with
// the given address, even if that resource has no instances.
// TODO:
func (s Set) HasResource(want addrs.AbsResource) bool {
return s.exp.knowsResource(want)
}

View File

@ -0,0 +1,207 @@
package instances
import (
"testing"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/zclconf/go-cty/cty"
)
func TestSet(t *testing.T) {
exp := NewExpander()
// The following constructs the following imaginary module/resource tree:
// - root module
// - test_thing.single: no repetition
// - test_thing.count: count = 1
// - test_thing.for_each: for_each = { c = "C" }
// - module.single: no repetition
// - test_thing.single: no repetition
// - module.nested_single: no repetition
// - module.zero_count: count = 0
// - module.count: count = 2
// - module.nested_for_each: [0] for_each = {}, [1] for_each = { e = "E" }
// - module.for_each: for_each = { a = "A", b = "B" }
// - test_thing.count: ["a"] count = 0, ["b"] count = 1
exp.SetModuleSingle(addrs.RootModuleInstance, addrs.ModuleCall{Name: "single"})
exp.SetModuleCount(addrs.RootModuleInstance, addrs.ModuleCall{Name: "count"}, 2)
exp.SetModuleForEach(addrs.RootModuleInstance, addrs.ModuleCall{Name: "for_each"}, map[string]cty.Value{
"a": cty.StringVal("A"),
"b": cty.StringVal("B"),
})
exp.SetModuleSingle(addrs.RootModuleInstance.Child("single", addrs.NoKey), addrs.ModuleCall{Name: "nested_single"})
exp.SetModuleForEach(addrs.RootModuleInstance.Child("count", addrs.IntKey(0)), addrs.ModuleCall{Name: "nested_for_each"}, nil)
exp.SetModuleForEach(addrs.RootModuleInstance.Child("count", addrs.IntKey(1)), addrs.ModuleCall{Name: "nested_for_each"}, map[string]cty.Value{
"e": cty.StringVal("E"),
})
exp.SetModuleCount(
addrs.RootModuleInstance.Child("single", addrs.NoKey).Child("nested_single", addrs.NoKey),
addrs.ModuleCall{Name: "zero_count"},
0,
)
rAddr := func(name string) addrs.Resource {
return addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: name,
}
}
exp.SetResourceSingle(addrs.RootModuleInstance, rAddr("single"))
exp.SetResourceCount(addrs.RootModuleInstance, rAddr("count"), 1)
exp.SetResourceForEach(addrs.RootModuleInstance, rAddr("for_each"), map[string]cty.Value{
"c": cty.StringVal("C"),
})
exp.SetResourceSingle(addrs.RootModuleInstance.Child("single", addrs.NoKey), rAddr("single"))
exp.SetResourceCount(addrs.RootModuleInstance.Child("for_each", addrs.StringKey("a")), rAddr("count"), 0)
exp.SetResourceCount(addrs.RootModuleInstance.Child("for_each", addrs.StringKey("b")), rAddr("count"), 1)
set := exp.AllInstances()
// HasModuleInstance tests
if input := addrs.RootModuleInstance; !set.HasModuleInstance(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := addrs.RootModuleInstance.Child("single", addrs.NoKey); !set.HasModuleInstance(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := addrs.RootModuleInstance.Child("single", addrs.NoKey).Child("nested_single", addrs.NoKey); !set.HasModuleInstance(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := addrs.RootModuleInstance.Child("count", addrs.IntKey(0)); !set.HasModuleInstance(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := addrs.RootModuleInstance.Child("count", addrs.IntKey(1)); !set.HasModuleInstance(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := addrs.RootModuleInstance.Child("count", addrs.IntKey(1)).Child("nested_for_each", addrs.StringKey("e")); !set.HasModuleInstance(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := addrs.RootModuleInstance.Child("for_each", addrs.StringKey("a")); !set.HasModuleInstance(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := addrs.RootModuleInstance.Child("for_each", addrs.StringKey("b")); !set.HasModuleInstance(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := addrs.RootModuleInstance.Child("single", addrs.IntKey(0)); set.HasModuleInstance(input) {
t.Errorf("unexpected %T %s", input, input.String())
}
if input := addrs.RootModuleInstance.Child("single", addrs.StringKey("a")); set.HasModuleInstance(input) {
t.Errorf("unexpected %T %s", input, input.String())
}
if input := addrs.RootModuleInstance.Child("single", addrs.NoKey).Child("nonexist", addrs.NoKey); set.HasModuleInstance(input) {
t.Errorf("unexpected %T %s", input, input.String())
}
if input := addrs.RootModuleInstance.Child("count", addrs.NoKey); set.HasModuleInstance(input) {
t.Errorf("unexpected %T %s", input, input.String())
}
if input := addrs.RootModuleInstance.Child("count", addrs.IntKey(2)); set.HasModuleInstance(input) {
t.Errorf("unexpected %T %s", input, input.String())
}
if input := addrs.RootModuleInstance.Child("count", addrs.StringKey("a")); set.HasModuleInstance(input) {
t.Errorf("unexpected %T %s", input, input.String())
}
if input := addrs.RootModuleInstance.Child("count", addrs.IntKey(0)).Child("nested_for_each", addrs.StringKey("e")); set.HasModuleInstance(input) {
t.Errorf("unexpected %T %s", input, input.String())
}
if input := addrs.RootModuleInstance.Child("single", addrs.NoKey).Child("nested_single", addrs.NoKey).Child("zero_count", addrs.NoKey); set.HasModuleInstance(input) {
t.Errorf("unexpected %T %s", input, input.String())
}
if input := addrs.RootModuleInstance.Child("single", addrs.NoKey).Child("nested_single", addrs.NoKey).Child("zero_count", addrs.IntKey(0)); set.HasModuleInstance(input) {
t.Errorf("unexpected %T %s", input, input.String())
}
// HasModuleCall tests
if input := addrs.RootModuleInstance.ChildCall("single"); !set.HasModuleCall(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := addrs.RootModuleInstance.Child("single", addrs.NoKey).ChildCall("nested_single"); !set.HasModuleCall(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := addrs.RootModuleInstance.ChildCall("count"); !set.HasModuleCall(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := addrs.RootModuleInstance.Child("count", addrs.IntKey(0)).ChildCall("nested_for_each"); !set.HasModuleCall(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := addrs.RootModuleInstance.Child("count", addrs.IntKey(1)).ChildCall("nested_for_each"); !set.HasModuleCall(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := addrs.RootModuleInstance.ChildCall("for_each"); !set.HasModuleCall(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := addrs.RootModuleInstance.Child("single", addrs.NoKey).Child("nested_single", addrs.NoKey).ChildCall("zero_count"); !set.HasModuleCall(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := addrs.RootModuleInstance.ChildCall("nonexist"); set.HasModuleCall(input) {
t.Errorf("unexpected %T %s", input, input.String())
}
if input := addrs.RootModuleInstance.Child("single", addrs.NoKey).ChildCall("nonexist"); set.HasModuleCall(input) {
t.Errorf("unexpected %T %s", input, input.String())
}
// HasResourceInstance tests
if input := rAddr("single").Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance); !set.HasResourceInstance(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := rAddr("count").Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance); !set.HasResourceInstance(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := rAddr("for_each").Instance(addrs.StringKey("c")).Absolute(addrs.RootModuleInstance); !set.HasResourceInstance(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := rAddr("single").Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance.Child("single", addrs.NoKey)); !set.HasResourceInstance(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := rAddr("count").Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance.Child("for_each", addrs.StringKey("b"))); !set.HasResourceInstance(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := rAddr("single").Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance); set.HasResourceInstance(input) {
t.Errorf("unexpected %T %s", input, input.String())
}
if input := rAddr("single").Instance(addrs.StringKey("")).Absolute(addrs.RootModuleInstance); set.HasResourceInstance(input) {
t.Errorf("unexpected %T %s", input, input.String())
}
if input := rAddr("count").Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance); set.HasResourceInstance(input) {
t.Errorf("unexpected %T %s", input, input.String())
}
if input := rAddr("count").Instance(addrs.StringKey("")).Absolute(addrs.RootModuleInstance); set.HasResourceInstance(input) {
t.Errorf("unexpected %T %s", input, input.String())
}
if input := rAddr("count").Instance(addrs.IntKey(1)).Absolute(addrs.RootModuleInstance); set.HasResourceInstance(input) {
t.Errorf("unexpected %T %s", input, input.String())
}
if input := rAddr("single").Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance.Child("single", addrs.IntKey(0))); set.HasResourceInstance(input) {
t.Errorf("unexpected %T %s", input, input.String())
}
if input := rAddr("count").Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance.Child("for_each", addrs.StringKey("a"))); set.HasResourceInstance(input) {
t.Errorf("unexpected %T %s", input, input.String())
}
// HasResource tests
if input := rAddr("single").Absolute(addrs.RootModuleInstance); !set.HasResource(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := rAddr("count").Absolute(addrs.RootModuleInstance); !set.HasResource(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := rAddr("for_each").Absolute(addrs.RootModuleInstance); !set.HasResource(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := rAddr("single").Absolute(addrs.RootModuleInstance.Child("single", addrs.NoKey)); !set.HasResource(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := rAddr("count").Absolute(addrs.RootModuleInstance.Child("for_each", addrs.StringKey("a"))); !set.HasResource(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := rAddr("count").Absolute(addrs.RootModuleInstance.Child("for_each", addrs.StringKey("b"))); !set.HasResource(input) {
t.Errorf("missing %T %s", input, input.String())
}
if input := rAddr("nonexist").Absolute(addrs.RootModuleInstance); set.HasResource(input) {
t.Errorf("unexpected %T %s", input, input.String())
}
if input := rAddr("count").Absolute(addrs.RootModuleInstance.Child("for_each", addrs.StringKey("nonexist"))); set.HasResource(input) {
t.Errorf("unexpected %T %s", input, input.String())
}
}