mirror of https://github.com/hashicorp/terraform
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
860 lines
31 KiB
860 lines
31 KiB
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package instances
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/zclconf/go-cty-debug/ctydebug"
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
"github.com/hashicorp/terraform/internal/configs"
|
|
"github.com/hashicorp/terraform/internal/moduletest/mocking"
|
|
)
|
|
|
|
func TestExpanderWithOverrides(t *testing.T) {
|
|
|
|
mustModuleInstance := func(t *testing.T, s string) addrs.ModuleInstance {
|
|
if len(s) == 0 {
|
|
return addrs.RootModuleInstance
|
|
}
|
|
|
|
addr, diags := addrs.ParseModuleInstanceStr(s)
|
|
if diags.HasErrors() {
|
|
t.Fatalf("unexpected error: %s", diags.Err())
|
|
}
|
|
return addr
|
|
}
|
|
|
|
tcs := map[string]struct {
|
|
// Hook to install chosen overrides.
|
|
overrides mocking.InitLocalOverrides
|
|
|
|
// Hook to initialise the expander with the desired state.
|
|
expander func(*Expander)
|
|
|
|
// The target module instance to inspect.
|
|
target string
|
|
|
|
// Set to true to include overrides in the result.
|
|
includeOverrides bool
|
|
|
|
// The expected result.
|
|
wantModules []addrs.ModuleInstance
|
|
|
|
// The expected result for partial modules.
|
|
wantPartials map[string]bool
|
|
}{
|
|
"root module": {
|
|
wantModules: singletonRootModule,
|
|
wantPartials: make(map[string]bool),
|
|
},
|
|
"instanced child module not overridden": {
|
|
expander: func(expander *Expander) {
|
|
expander.SetModuleCount(addrs.RootModuleInstance, addrs.ModuleCall{Name: "double"}, 2)
|
|
},
|
|
target: "module.double",
|
|
wantModules: []addrs.ModuleInstance{
|
|
mustModuleInstance(t, "module.double[0]"),
|
|
mustModuleInstance(t, "module.double[1]"),
|
|
},
|
|
wantPartials: make(map[string]bool),
|
|
},
|
|
"instanced child module single instance overridden": {
|
|
overrides: func(overrides addrs.Map[addrs.Targetable, *configs.Override]) {
|
|
overrides.Put(mustModuleInstance(t, "module.double[0]"), &configs.Override{})
|
|
},
|
|
expander: func(expander *Expander) {
|
|
expander.SetModuleCount(addrs.RootModuleInstance, addrs.ModuleCall{Name: "double"}, 2)
|
|
},
|
|
target: "module.double",
|
|
wantModules: []addrs.ModuleInstance{
|
|
mustModuleInstance(t, "module.double[1]"),
|
|
},
|
|
wantPartials: make(map[string]bool),
|
|
},
|
|
"instanced child module single instance overridden includes overrides": {
|
|
overrides: func(overrides addrs.Map[addrs.Targetable, *configs.Override]) {
|
|
overrides.Put(mustModuleInstance(t, "module.double[0]"), &configs.Override{})
|
|
},
|
|
expander: func(expander *Expander) {
|
|
expander.SetModuleCount(addrs.RootModuleInstance, addrs.ModuleCall{Name: "double"}, 2)
|
|
},
|
|
target: "module.double",
|
|
includeOverrides: true,
|
|
wantModules: []addrs.ModuleInstance{
|
|
mustModuleInstance(t, "module.double[0]"),
|
|
mustModuleInstance(t, "module.double[1]"),
|
|
},
|
|
wantPartials: make(map[string]bool),
|
|
},
|
|
"deeply nested child module with parent overridden": {
|
|
overrides: func(overrides addrs.Map[addrs.Targetable, *configs.Override]) {
|
|
overrides.Put(mustModuleInstance(t, "module.double[0]"), &configs.Override{})
|
|
},
|
|
expander: func(expander *Expander) {
|
|
expander.SetModuleCount(addrs.RootModuleInstance, addrs.ModuleCall{Name: "double"}, 2)
|
|
expander.SetModuleSingle(mustModuleInstance(t, "module.double[1]"), addrs.ModuleCall{Name: "single"})
|
|
},
|
|
target: "module.double.module.single",
|
|
wantModules: []addrs.ModuleInstance{mustModuleInstance(t, "module.double[1].module.single")},
|
|
wantPartials: make(map[string]bool),
|
|
},
|
|
"unknown child module overridden by instanced module": {
|
|
overrides: func(overrides addrs.Map[addrs.Targetable, *configs.Override]) {
|
|
overrides.Put(mustModuleInstance(t, "module.unknown[0]"), &configs.Override{})
|
|
},
|
|
expander: func(expander *Expander) {
|
|
expander.SetModuleCountUnknown(addrs.RootModuleInstance, addrs.ModuleCall{Name: "unknown"})
|
|
},
|
|
target: "module.unknown",
|
|
wantPartials: map[string]bool{
|
|
"module.unknown[*]": true,
|
|
},
|
|
},
|
|
"unknown child module overridden by instanced module includes overrides": {
|
|
overrides: func(overrides addrs.Map[addrs.Targetable, *configs.Override]) {
|
|
overrides.Put(mustModuleInstance(t, "module.unknown"), &configs.Override{})
|
|
},
|
|
expander: func(expander *Expander) {
|
|
expander.SetModuleCountUnknown(addrs.RootModuleInstance, addrs.ModuleCall{Name: "unknown"})
|
|
},
|
|
target: "module.unknown",
|
|
wantPartials: make(map[string]bool), // This time it's empty, as we overrode all instances.
|
|
},
|
|
}
|
|
|
|
for name, tc := range tcs {
|
|
t.Run(name, func(t *testing.T) {
|
|
overrides := mocking.OverridesForTesting(nil, tc.overrides)
|
|
|
|
expander := NewExpander(overrides)
|
|
if tc.expander != nil {
|
|
tc.expander(expander)
|
|
}
|
|
|
|
target := mustModuleInstance(t, tc.target).Module()
|
|
|
|
gotModules := expander.ExpandModule(target, tc.includeOverrides)
|
|
gotPartials := expander.UnknownModuleInstances(target, tc.includeOverrides)
|
|
|
|
if diff := cmp.Diff(tc.wantModules, gotModules); len(diff) > 0 {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
|
|
// Convert the gotPartials into strings to make cmp.Diff work.
|
|
gotPartialsStr := make(map[string]bool, len(gotPartials))
|
|
for _, partial := range gotPartials {
|
|
gotPartialsStr[partial.String()] = true
|
|
}
|
|
|
|
if diff := cmp.Diff(tc.wantPartials, gotPartialsStr, ctydebug.CmpOptions); len(diff) > 0 {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
func TestExpander(t *testing.T) {
|
|
// Some module and resource addresses and values we'll use repeatedly below.
|
|
singleModuleAddr := addrs.ModuleCall{Name: "single"}
|
|
count2ModuleAddr := addrs.ModuleCall{Name: "count2"}
|
|
count0ModuleAddr := addrs.ModuleCall{Name: "count0"}
|
|
forEachModuleAddr := addrs.ModuleCall{Name: "for_each"}
|
|
singleResourceAddr := addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test",
|
|
Name: "single",
|
|
}
|
|
count2ResourceAddr := addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test",
|
|
Name: "count2",
|
|
}
|
|
count0ResourceAddr := addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test",
|
|
Name: "count0",
|
|
}
|
|
forEachResourceAddr := addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test",
|
|
Name: "for_each",
|
|
}
|
|
eachMap := map[string]cty.Value{
|
|
"a": cty.NumberIntVal(1),
|
|
"b": cty.NumberIntVal(2),
|
|
}
|
|
|
|
// In normal use, Expander would be called in the context of a graph
|
|
// traversal to ensure that information is registered/requested in the
|
|
// correct sequence, but to keep this test self-contained we'll just
|
|
// manually write out the steps here.
|
|
//
|
|
// The steps below are assuming a configuration tree like the following:
|
|
// - root module
|
|
// - resource test.single with no count or for_each
|
|
// - resource test.count2 with count = 2
|
|
// - resource test.count0 with count = 0
|
|
// - resource test.for_each with for_each = { a = 1, b = 2 }
|
|
// - child module "single" with no count or for_each
|
|
// - resource test.single with no count or for_each
|
|
// - resource test.count2 with count = 2
|
|
// - child module "count2" with count = 2
|
|
// - resource test.single with no count or for_each
|
|
// - resource test.count2 with count = 2
|
|
// - child module "count2" with count = 2
|
|
// - resource test.count2 with count = 2
|
|
// - child module "count0" with count = 0
|
|
// - resource test.single with no count or for_each
|
|
// - child module for_each with for_each = { a = 1, b = 2 }
|
|
// - resource test.single with no count or for_each
|
|
// - resource test.count2 with count = 2
|
|
|
|
ex := NewExpander(nil)
|
|
|
|
// We don't register the root module, because it's always implied to exist.
|
|
//
|
|
// Below we're going to use braces and indentation just to help visually
|
|
// reflect the tree structure from the tree in the above comment, in the
|
|
// hope that the following is easier to follow.
|
|
//
|
|
// The Expander API requires that we register containing modules before
|
|
// registering anything inside them, so we'll work through the above
|
|
// in a depth-first order in the registration steps that follow.
|
|
{
|
|
ex.SetResourceSingle(addrs.RootModuleInstance, singleResourceAddr)
|
|
ex.SetResourceCount(addrs.RootModuleInstance, count2ResourceAddr, 2)
|
|
ex.SetResourceCount(addrs.RootModuleInstance, count0ResourceAddr, 0)
|
|
ex.SetResourceForEach(addrs.RootModuleInstance, forEachResourceAddr, eachMap)
|
|
|
|
ex.SetModuleSingle(addrs.RootModuleInstance, singleModuleAddr)
|
|
{
|
|
// The single instance of the module
|
|
moduleInstanceAddr := addrs.RootModuleInstance.Child("single", addrs.NoKey)
|
|
ex.SetResourceSingle(moduleInstanceAddr, singleResourceAddr)
|
|
ex.SetResourceCount(moduleInstanceAddr, count2ResourceAddr, 2)
|
|
}
|
|
|
|
ex.SetModuleCount(addrs.RootModuleInstance, count2ModuleAddr, 2)
|
|
for i1 := 0; i1 < 2; i1++ {
|
|
moduleInstanceAddr := addrs.RootModuleInstance.Child("count2", addrs.IntKey(i1))
|
|
ex.SetResourceSingle(moduleInstanceAddr, singleResourceAddr)
|
|
ex.SetResourceCount(moduleInstanceAddr, count2ResourceAddr, 2)
|
|
ex.SetModuleCount(moduleInstanceAddr, count2ModuleAddr, 2)
|
|
for i2 := 0; i2 < 2; i2++ {
|
|
moduleInstanceAddr := moduleInstanceAddr.Child("count2", addrs.IntKey(i2))
|
|
ex.SetResourceCount(moduleInstanceAddr, count2ResourceAddr, 2)
|
|
}
|
|
}
|
|
|
|
ex.SetModuleCount(addrs.RootModuleInstance, count0ModuleAddr, 0)
|
|
{
|
|
// There are no instances of module "count0", so our nested module
|
|
// would never actually get registered here: the expansion node
|
|
// for the resource would see that its containing module has no
|
|
// instances and so do nothing.
|
|
}
|
|
|
|
ex.SetModuleForEach(addrs.RootModuleInstance, forEachModuleAddr, eachMap)
|
|
for k := range eachMap {
|
|
moduleInstanceAddr := addrs.RootModuleInstance.Child("for_each", addrs.StringKey(k))
|
|
ex.SetResourceSingle(moduleInstanceAddr, singleResourceAddr)
|
|
ex.SetResourceCount(moduleInstanceAddr, count2ResourceAddr, 2)
|
|
}
|
|
}
|
|
|
|
t.Run("root module", func(t *testing.T) {
|
|
// Requesting expansion of the root module doesn't really mean anything
|
|
// since it's always a singleton, but for consistency it should work.
|
|
got := ex.ExpandModule(addrs.RootModule, false)
|
|
want := []addrs.ModuleInstance{addrs.RootModuleInstance}
|
|
if diff := cmp.Diff(want, got); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
t.Run("resource single", func(t *testing.T) {
|
|
got := ex.ExpandModuleResource(
|
|
addrs.RootModule,
|
|
singleResourceAddr,
|
|
)
|
|
want := []addrs.AbsResourceInstance{
|
|
mustAbsResourceInstanceAddr(`test.single`),
|
|
}
|
|
if diff := cmp.Diff(want, got); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
t.Run("resource count2", func(t *testing.T) {
|
|
got := ex.ExpandModuleResource(
|
|
addrs.RootModule,
|
|
count2ResourceAddr,
|
|
)
|
|
want := []addrs.AbsResourceInstance{
|
|
mustAbsResourceInstanceAddr(`test.count2[0]`),
|
|
mustAbsResourceInstanceAddr(`test.count2[1]`),
|
|
}
|
|
if diff := cmp.Diff(want, got); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
t.Run("resource count0", func(t *testing.T) {
|
|
got := ex.ExpandModuleResource(
|
|
addrs.RootModule,
|
|
count0ResourceAddr,
|
|
)
|
|
want := []addrs.AbsResourceInstance(nil)
|
|
if diff := cmp.Diff(want, got); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
t.Run("resource for_each", func(t *testing.T) {
|
|
got := ex.ExpandModuleResource(
|
|
addrs.RootModule,
|
|
forEachResourceAddr,
|
|
)
|
|
want := []addrs.AbsResourceInstance{
|
|
mustAbsResourceInstanceAddr(`test.for_each["a"]`),
|
|
mustAbsResourceInstanceAddr(`test.for_each["b"]`),
|
|
}
|
|
if diff := cmp.Diff(want, got); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
t.Run("module single", func(t *testing.T) {
|
|
got := ex.ExpandModule(addrs.RootModule.Child("single"), false)
|
|
want := []addrs.ModuleInstance{
|
|
mustModuleInstanceAddr(`module.single`),
|
|
}
|
|
if diff := cmp.Diff(want, got); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
t.Run("module single resource single", func(t *testing.T) {
|
|
got := ex.ExpandModuleResource(
|
|
mustModuleAddr("single"),
|
|
singleResourceAddr,
|
|
)
|
|
want := []addrs.AbsResourceInstance{
|
|
mustAbsResourceInstanceAddr("module.single.test.single"),
|
|
}
|
|
if diff := cmp.Diff(want, got); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
t.Run("module single resource count2", func(t *testing.T) {
|
|
// Two different ways of asking the same question, which should
|
|
// both produce the same result.
|
|
// First: nested expansion of all instances of the resource across
|
|
// all instances of the module, but it's a single-instance module
|
|
// so the first level is a singleton.
|
|
got1 := ex.ExpandModuleResource(
|
|
mustModuleAddr(`single`),
|
|
count2ResourceAddr,
|
|
)
|
|
// Second: expansion of only instances belonging to a specific
|
|
// instance of the module, but again it's a single-instance module
|
|
// so there's only one to ask about.
|
|
got2 := ex.ExpandResource(
|
|
count2ResourceAddr.Absolute(
|
|
addrs.RootModuleInstance.Child("single", addrs.NoKey),
|
|
),
|
|
)
|
|
want := []addrs.AbsResourceInstance{
|
|
mustAbsResourceInstanceAddr(`module.single.test.count2[0]`),
|
|
mustAbsResourceInstanceAddr(`module.single.test.count2[1]`),
|
|
}
|
|
if diff := cmp.Diff(want, got1); diff != "" {
|
|
t.Errorf("wrong ExpandModuleResource result\n%s", diff)
|
|
}
|
|
if diff := cmp.Diff(want, got2); diff != "" {
|
|
t.Errorf("wrong ExpandResource result\n%s", diff)
|
|
}
|
|
})
|
|
t.Run("module single resource count2 with non-existing module instance", func(t *testing.T) {
|
|
got := ex.ExpandResource(
|
|
count2ResourceAddr.Absolute(
|
|
// Note: This is intentionally an invalid instance key,
|
|
// so we're asking about module.single[1].test.count2
|
|
// even though module.single doesn't have count set and
|
|
// therefore there is no module.single[1].
|
|
addrs.RootModuleInstance.Child("single", addrs.IntKey(1)),
|
|
),
|
|
)
|
|
// If the containing module instance doesn't exist then it can't
|
|
// possibly have any resource instances inside it.
|
|
want := ([]addrs.AbsResourceInstance)(nil)
|
|
if diff := cmp.Diff(want, got); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
t.Run("module count2", func(t *testing.T) {
|
|
got := ex.ExpandModule(mustModuleAddr(`count2`), false)
|
|
want := []addrs.ModuleInstance{
|
|
mustModuleInstanceAddr(`module.count2[0]`),
|
|
mustModuleInstanceAddr(`module.count2[1]`),
|
|
}
|
|
if diff := cmp.Diff(want, got); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
t.Run("module count2 resource single", func(t *testing.T) {
|
|
got := ex.ExpandModuleResource(
|
|
mustModuleAddr(`count2`),
|
|
singleResourceAddr,
|
|
)
|
|
want := []addrs.AbsResourceInstance{
|
|
mustAbsResourceInstanceAddr(`module.count2[0].test.single`),
|
|
mustAbsResourceInstanceAddr(`module.count2[1].test.single`),
|
|
}
|
|
if diff := cmp.Diff(want, got); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
t.Run("module count2 resource count2", func(t *testing.T) {
|
|
got := ex.ExpandModuleResource(
|
|
mustModuleAddr(`count2`),
|
|
count2ResourceAddr,
|
|
)
|
|
want := []addrs.AbsResourceInstance{
|
|
mustAbsResourceInstanceAddr(`module.count2[0].test.count2[0]`),
|
|
mustAbsResourceInstanceAddr(`module.count2[0].test.count2[1]`),
|
|
mustAbsResourceInstanceAddr(`module.count2[1].test.count2[0]`),
|
|
mustAbsResourceInstanceAddr(`module.count2[1].test.count2[1]`),
|
|
}
|
|
if diff := cmp.Diff(want, got); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
t.Run("module count2 module count2", func(t *testing.T) {
|
|
got := ex.ExpandModule(mustModuleAddr(`count2.count2`), false)
|
|
want := []addrs.ModuleInstance{
|
|
mustModuleInstanceAddr(`module.count2[0].module.count2[0]`),
|
|
mustModuleInstanceAddr(`module.count2[0].module.count2[1]`),
|
|
mustModuleInstanceAddr(`module.count2[1].module.count2[0]`),
|
|
mustModuleInstanceAddr(`module.count2[1].module.count2[1]`),
|
|
}
|
|
if diff := cmp.Diff(want, got); diff != "" {
|
|
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]`))
|
|
want := addrs.RootModuleInstance
|
|
if !want.Equal(got) {
|
|
t.Errorf("wrong result\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
})
|
|
t.Run("second step invalid", func(t *testing.T) {
|
|
got := ex.GetDeepestExistingModuleInstance(mustModuleInstanceAddr(`module.count2[1].module.count2`))
|
|
want := mustModuleInstanceAddr(`module.count2[1]`)
|
|
if !want.Equal(got) {
|
|
t.Errorf("wrong result\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
})
|
|
t.Run("neither step valid", func(t *testing.T) {
|
|
got := ex.GetDeepestExistingModuleInstance(mustModuleInstanceAddr(`module.count2.module.count2["nope"]`))
|
|
want := addrs.RootModuleInstance
|
|
if !want.Equal(got) {
|
|
t.Errorf("wrong result\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
})
|
|
t.Run("both steps valid", func(t *testing.T) {
|
|
got := ex.GetDeepestExistingModuleInstance(mustModuleInstanceAddr(`module.count2[1].module.count2[0]`))
|
|
want := mustModuleInstanceAddr(`module.count2[1].module.count2[0]`)
|
|
if !want.Equal(got) {
|
|
t.Errorf("wrong result\ngot: %s\nwant: %s", got, want)
|
|
}
|
|
})
|
|
})
|
|
t.Run("module count2 resource count2 resource count2", func(t *testing.T) {
|
|
got := ex.ExpandModuleResource(
|
|
mustModuleAddr(`count2.count2`),
|
|
count2ResourceAddr,
|
|
)
|
|
want := []addrs.AbsResourceInstance{
|
|
mustAbsResourceInstanceAddr(`module.count2[0].module.count2[0].test.count2[0]`),
|
|
mustAbsResourceInstanceAddr(`module.count2[0].module.count2[0].test.count2[1]`),
|
|
mustAbsResourceInstanceAddr(`module.count2[0].module.count2[1].test.count2[0]`),
|
|
mustAbsResourceInstanceAddr(`module.count2[0].module.count2[1].test.count2[1]`),
|
|
mustAbsResourceInstanceAddr(`module.count2[1].module.count2[0].test.count2[0]`),
|
|
mustAbsResourceInstanceAddr(`module.count2[1].module.count2[0].test.count2[1]`),
|
|
mustAbsResourceInstanceAddr(`module.count2[1].module.count2[1].test.count2[0]`),
|
|
mustAbsResourceInstanceAddr(`module.count2[1].module.count2[1].test.count2[1]`),
|
|
}
|
|
if diff := cmp.Diff(want, got); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
t.Run("module count2 resource count2 resource count2", func(t *testing.T) {
|
|
got := ex.ExpandResource(
|
|
count2ResourceAddr.Absolute(mustModuleInstanceAddr(`module.count2[0].module.count2[1]`)),
|
|
)
|
|
want := []addrs.AbsResourceInstance{
|
|
mustAbsResourceInstanceAddr(`module.count2[0].module.count2[1].test.count2[0]`),
|
|
mustAbsResourceInstanceAddr(`module.count2[0].module.count2[1].test.count2[1]`),
|
|
}
|
|
if diff := cmp.Diff(want, got); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
t.Run("module count0", func(t *testing.T) {
|
|
got := ex.ExpandModule(mustModuleAddr(`count0`), false)
|
|
want := []addrs.ModuleInstance(nil)
|
|
if diff := cmp.Diff(want, got); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
t.Run("module count0 resource single", func(t *testing.T) {
|
|
got := ex.ExpandModuleResource(
|
|
mustModuleAddr(`count0`),
|
|
singleResourceAddr,
|
|
)
|
|
// The containing module has zero instances, so therefore there
|
|
// are zero instances of this resource even though it doesn't have
|
|
// count = 0 set itself.
|
|
want := []addrs.AbsResourceInstance(nil)
|
|
if diff := cmp.Diff(want, got); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
t.Run("module for_each", func(t *testing.T) {
|
|
got := ex.ExpandModule(mustModuleAddr(`for_each`), false)
|
|
want := []addrs.ModuleInstance{
|
|
mustModuleInstanceAddr(`module.for_each["a"]`),
|
|
mustModuleInstanceAddr(`module.for_each["b"]`),
|
|
}
|
|
if diff := cmp.Diff(want, got); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
t.Run("module for_each resource single", func(t *testing.T) {
|
|
got := ex.ExpandModuleResource(
|
|
mustModuleAddr(`for_each`),
|
|
singleResourceAddr,
|
|
)
|
|
want := []addrs.AbsResourceInstance{
|
|
mustAbsResourceInstanceAddr(`module.for_each["a"].test.single`),
|
|
mustAbsResourceInstanceAddr(`module.for_each["b"].test.single`),
|
|
}
|
|
if diff := cmp.Diff(want, got); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
t.Run("module for_each resource count2", func(t *testing.T) {
|
|
got := ex.ExpandModuleResource(
|
|
mustModuleAddr(`for_each`),
|
|
count2ResourceAddr,
|
|
)
|
|
want := []addrs.AbsResourceInstance{
|
|
mustAbsResourceInstanceAddr(`module.for_each["a"].test.count2[0]`),
|
|
mustAbsResourceInstanceAddr(`module.for_each["a"].test.count2[1]`),
|
|
mustAbsResourceInstanceAddr(`module.for_each["b"].test.count2[0]`),
|
|
mustAbsResourceInstanceAddr(`module.for_each["b"].test.count2[1]`),
|
|
}
|
|
if diff := cmp.Diff(want, got); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
t.Run("module for_each resource count2", func(t *testing.T) {
|
|
got := ex.ExpandResource(
|
|
count2ResourceAddr.Absolute(mustModuleInstanceAddr(`module.for_each["a"]`)),
|
|
)
|
|
want := []addrs.AbsResourceInstance{
|
|
mustAbsResourceInstanceAddr(`module.for_each["a"].test.count2[0]`),
|
|
mustAbsResourceInstanceAddr(`module.for_each["a"].test.count2[1]`),
|
|
}
|
|
if diff := cmp.Diff(want, got); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
|
|
t.Run(`module.for_each["b"] repetitiondata`, func(t *testing.T) {
|
|
got := ex.GetModuleInstanceRepetitionData(
|
|
mustModuleInstanceAddr(`module.for_each["b"]`),
|
|
)
|
|
want := RepetitionData{
|
|
EachKey: cty.StringVal("b"),
|
|
EachValue: cty.NumberIntVal(2),
|
|
}
|
|
if diff := cmp.Diff(want, got, cmp.Comparer(valueEquals)); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
t.Run(`module.count2[0].module.count2[1] repetitiondata`, func(t *testing.T) {
|
|
got := ex.GetModuleInstanceRepetitionData(
|
|
mustModuleInstanceAddr(`module.count2[0].module.count2[1]`),
|
|
)
|
|
want := RepetitionData{
|
|
CountIndex: cty.NumberIntVal(1),
|
|
}
|
|
if diff := cmp.Diff(want, got, cmp.Comparer(valueEquals)); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
t.Run(`module.for_each["a"] repetitiondata`, func(t *testing.T) {
|
|
got := ex.GetModuleInstanceRepetitionData(
|
|
mustModuleInstanceAddr(`module.for_each["a"]`),
|
|
)
|
|
want := RepetitionData{
|
|
EachKey: cty.StringVal("a"),
|
|
EachValue: cty.NumberIntVal(1),
|
|
}
|
|
if diff := cmp.Diff(want, got, cmp.Comparer(valueEquals)); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
|
|
t.Run(`test.for_each["a"] repetitiondata`, func(t *testing.T) {
|
|
got := ex.GetResourceInstanceRepetitionData(
|
|
mustAbsResourceInstanceAddr(`test.for_each["a"]`),
|
|
)
|
|
want := RepetitionData{
|
|
EachKey: cty.StringVal("a"),
|
|
EachValue: cty.NumberIntVal(1),
|
|
}
|
|
if diff := cmp.Diff(want, got, cmp.Comparer(valueEquals)); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
t.Run(`module.for_each["a"].test.single repetitiondata`, func(t *testing.T) {
|
|
got := ex.GetResourceInstanceRepetitionData(
|
|
mustAbsResourceInstanceAddr(`module.for_each["a"].test.single`),
|
|
)
|
|
want := RepetitionData{}
|
|
if diff := cmp.Diff(want, got, cmp.Comparer(valueEquals)); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
t.Run(`module.for_each["a"].test.count2[1] repetitiondata`, func(t *testing.T) {
|
|
got := ex.GetResourceInstanceRepetitionData(
|
|
mustAbsResourceInstanceAddr(`module.for_each["a"].test.count2[1]`),
|
|
)
|
|
want := RepetitionData{
|
|
CountIndex: cty.NumberIntVal(1),
|
|
}
|
|
if diff := cmp.Diff(want, got, cmp.Comparer(valueEquals)); diff != "" {
|
|
t.Errorf("wrong result\n%s", diff)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestExpanderWithUnknowns(t *testing.T) {
|
|
t.Run("resource in root module with unknown for_each", func(t *testing.T) {
|
|
resourceAddr := addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test",
|
|
Name: "foo",
|
|
}
|
|
ex := NewExpander(nil)
|
|
ex.SetResourceForEachUnknown(addrs.RootModuleInstance, resourceAddr)
|
|
|
|
got := ex.ExpandModuleResource(addrs.RootModule, resourceAddr)
|
|
if len(got) != 0 {
|
|
t.Errorf("unexpected known addresses: %#v", got)
|
|
}
|
|
})
|
|
t.Run("resource in root module with unknown count", func(t *testing.T) {
|
|
resourceAddr := addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test",
|
|
Name: "foo",
|
|
}
|
|
ex := NewExpander(nil)
|
|
ex.SetResourceCountUnknown(addrs.RootModuleInstance, resourceAddr)
|
|
|
|
got := ex.ExpandModuleResource(addrs.RootModule, resourceAddr)
|
|
if len(got) != 0 {
|
|
t.Errorf("unexpected known addresses: %#v", got)
|
|
}
|
|
})
|
|
t.Run("module with unknown for_each", func(t *testing.T) {
|
|
moduleCallAddr := addrs.ModuleCall{Name: "foo"}
|
|
ex := NewExpander(nil)
|
|
ex.SetModuleForEachUnknown(addrs.RootModuleInstance, moduleCallAddr)
|
|
|
|
got := ex.ExpandModule(addrs.Module{moduleCallAddr.Name}, false)
|
|
if len(got) != 0 {
|
|
t.Errorf("unexpected known addresses: %#v", got)
|
|
}
|
|
|
|
gotUnknown := ex.UnknownModuleInstances(addrs.Module{moduleCallAddr.Name}, false)
|
|
if len(gotUnknown) != 1 {
|
|
t.Errorf("unexpected unknown addresses: %#v", gotUnknown)
|
|
}
|
|
wantUnknownCall := addrs.RootModuleInstance.UnexpandedChild(moduleCallAddr)
|
|
if !gotUnknown.Has(wantUnknownCall) {
|
|
t.Errorf("unknown should have %s, but it doesn't", wantUnknownCall)
|
|
}
|
|
})
|
|
t.Run("module with unknown count", func(t *testing.T) {
|
|
moduleCallAddr := addrs.ModuleCall{Name: "foo"}
|
|
ex := NewExpander(nil)
|
|
ex.SetModuleCountUnknown(addrs.RootModuleInstance, moduleCallAddr)
|
|
|
|
gotKnown := ex.ExpandModule(addrs.Module{moduleCallAddr.Name}, false)
|
|
if len(gotKnown) != 0 {
|
|
t.Errorf("unexpected known addresses: %#v", gotKnown)
|
|
}
|
|
|
|
gotUnknown := ex.UnknownModuleInstances(addrs.Module{moduleCallAddr.Name}, false)
|
|
if len(gotUnknown) != 1 {
|
|
t.Errorf("unexpected unknown addresses: %#v", gotUnknown)
|
|
}
|
|
wantUnknownCall := addrs.RootModuleInstance.UnexpandedChild(moduleCallAddr)
|
|
if !gotUnknown.Has(wantUnknownCall) {
|
|
t.Errorf("unknown should have %s, but it doesn't", wantUnknownCall)
|
|
}
|
|
})
|
|
t.Run("nested module with unknown count", func(t *testing.T) {
|
|
moduleCallAddr1 := addrs.ModuleCall{Name: "foo"}
|
|
moduleCallAddr2 := addrs.ModuleCall{Name: "bar"}
|
|
module1 := addrs.RootModule.Child(moduleCallAddr1.Name)
|
|
module2 := module1.Child(moduleCallAddr2.Name)
|
|
module1Inst0 := addrs.RootModuleInstance.Child("foo", addrs.IntKey(0))
|
|
module1Inst1 := addrs.RootModuleInstance.Child("foo", addrs.IntKey(1))
|
|
module1Inst2 := addrs.RootModuleInstance.Child("foo", addrs.IntKey(2))
|
|
ex := NewExpander(nil)
|
|
ex.SetModuleCount(addrs.RootModuleInstance, moduleCallAddr1, 3)
|
|
ex.SetModuleCountUnknown(module1Inst0, moduleCallAddr2)
|
|
ex.SetModuleCount(module1Inst1, moduleCallAddr2, 1)
|
|
ex.SetModuleCountUnknown(module1Inst2, moduleCallAddr2)
|
|
|
|
// We'll also put some resources inside module.foo[1].module.bar[0]
|
|
// so that we can test requesting unknown resource instance sets.
|
|
resourceAddrKnownExp := addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test",
|
|
Name: "known_expansion",
|
|
}
|
|
resourceAddrUnknownExp := addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test",
|
|
Name: "unknown_expansion",
|
|
}
|
|
module1Inst1Module2Inst0 := module1Inst1.Child("bar", addrs.IntKey(0))
|
|
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, false)
|
|
wantKnown := []addrs.ModuleInstance{
|
|
module1Inst1.Child("bar", addrs.IntKey(0)),
|
|
}
|
|
if diff := cmp.Diff(wantKnown, gotKnown); diff != "" {
|
|
t.Errorf("unexpected known addresses\n%s", diff)
|
|
}
|
|
|
|
gotUnknown := ex.UnknownModuleInstances(module2, false)
|
|
if len(gotUnknown) != 2 {
|
|
t.Errorf("unexpected unknown addresses: %#v", gotUnknown)
|
|
}
|
|
if wantUnknownCall := module1Inst0.UnexpandedChild(moduleCallAddr2); !gotUnknown.Has(wantUnknownCall) {
|
|
t.Errorf("unknown should have %s, but it doesn't", wantUnknownCall)
|
|
}
|
|
if unwantUnknownCall := module1Inst1.UnexpandedChild(moduleCallAddr2); gotUnknown.Has(unwantUnknownCall) {
|
|
t.Errorf("unknown should not have %s, but does", unwantUnknownCall)
|
|
}
|
|
if wantUnknownCall := module1Inst2.UnexpandedChild(moduleCallAddr2); !gotUnknown.Has(wantUnknownCall) {
|
|
t.Errorf("unknown should have %s, but it doesn't", wantUnknownCall)
|
|
}
|
|
|
|
gotKnownResource := ex.ExpandResource(module1Inst1Module2Inst0.Resource(
|
|
resourceAddrKnownExp.Mode, resourceAddrKnownExp.Type, resourceAddrKnownExp.Name,
|
|
))
|
|
wantKnownResource := []addrs.AbsResourceInstance{
|
|
mustAbsResourceInstanceAddr("module.foo[1].module.bar[0].test.known_expansion[0]"),
|
|
mustAbsResourceInstanceAddr("module.foo[1].module.bar[0].test.known_expansion[1]"),
|
|
}
|
|
if diff := cmp.Diff(wantKnownResource, gotKnownResource); diff != "" {
|
|
t.Errorf("unexpected known addresses\n%s", diff)
|
|
}
|
|
|
|
gotUnknownResource := ex.UnknownResourceInstances(module2.Resource(
|
|
resourceAddrUnknownExp.Mode, resourceAddrUnknownExp.Type, resourceAddrUnknownExp.Name,
|
|
))
|
|
if len(gotUnknownResource) != 3 {
|
|
t.Errorf("unexpected unknown addresses: %#v", gotUnknownResource)
|
|
}
|
|
if wantResInst := module1Inst0.UnexpandedChild(moduleCallAddr2).Resource(resourceAddrUnknownExp); !gotUnknownResource.Has(wantResInst) {
|
|
t.Errorf("unknown should have %s, but it doesn't", wantResInst)
|
|
}
|
|
if wantResInst := module1Inst1Module2Inst0.UnexpandedResource(resourceAddrUnknownExp); !gotUnknownResource.Has(wantResInst) {
|
|
t.Errorf("unknown should have %s, but it doesn't", wantResInst)
|
|
}
|
|
if wantResInst := module1Inst2.UnexpandedChild(moduleCallAddr2).Resource(resourceAddrUnknownExp); !gotUnknownResource.Has(wantResInst) {
|
|
t.Errorf("unknown should have %s, but it doesn't", wantResInst)
|
|
}
|
|
})
|
|
}
|
|
|
|
func mustAbsResourceInstanceAddr(str string) addrs.AbsResourceInstance {
|
|
addr, diags := addrs.ParseAbsResourceInstanceStr(str)
|
|
if diags.HasErrors() {
|
|
panic(fmt.Sprintf("invalid absolute resource instance address: %s", diags.Err()))
|
|
}
|
|
return addr
|
|
}
|
|
|
|
func mustModuleAddr(str string) addrs.Module {
|
|
if len(str) == 0 {
|
|
return addrs.RootModule
|
|
}
|
|
// We don't have a real parser for these because they don't appear in the
|
|
// language anywhere, but this interpretation mimics the format we
|
|
// produce from the String method on addrs.Module.
|
|
parts := strings.Split(str, ".")
|
|
return addrs.Module(parts)
|
|
}
|
|
|
|
func mustModuleInstanceAddr(str string) addrs.ModuleInstance {
|
|
if len(str) == 0 {
|
|
return addrs.RootModuleInstance
|
|
}
|
|
addr, diags := addrs.ParseModuleInstanceStr(str)
|
|
if diags.HasErrors() {
|
|
panic(fmt.Sprintf("invalid module instance address: %s", diags.Err()))
|
|
}
|
|
return addr
|
|
}
|
|
|
|
func valueEquals(a, b cty.Value) bool {
|
|
if a == cty.NilVal || b == cty.NilVal {
|
|
return a == b
|
|
}
|
|
return a.RawEquals(b)
|
|
}
|