terraform/internal/instances/expander_test.go

535 lines
19 KiB
Go
Raw Normal View History

package instances
import (
"fmt"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/addrs"
)
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()
// 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)
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"))
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) {
instances: Non-existing module instance has no resource instances Previously we were treating it as a programming error to ask for the instances of a resource inside an instance of a module that is declared but whose declaration doesn't include the given instance key. However, that's actually a valid situation which can arise if, for example, the user has changed the repetition/expansion mode for an existing module call and so now all of the resource instances addresses it previously contained are "orphaned". To represent that, we'll instead say that an invalid instance key of a declared module behaves as if it contains no resource instances at all, regardless of the configurations of any resources nested inside. This then gives the result needed to successfully detect all of the former resource instances as "orphaned" and plan to destroy them. However, this then introduces a new case for NodePlannableResourceInstanceOrphan.deleteActionReason to deal with: the resource configuration still exists (because configuration isn't aware of individual module/resource instances) but the module instance does not. This actually allows us to resolve, at least partially, a previous missing piece of explaining to the user why the resource instances are planned for deletion in that case, finally allowing us to be explicit to the user that it's because of the module instance being removed, which internally we call plans.ResourceInstanceDeleteBecauseNoModule. Co-authored-by: Alisdair McDiarmid <alisdair@users.noreply.github.com>
2021-12-08 21:40:32 +01:00
// 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,
)
instances: Non-existing module instance has no resource instances Previously we were treating it as a programming error to ask for the instances of a resource inside an instance of a module that is declared but whose declaration doesn't include the given instance key. However, that's actually a valid situation which can arise if, for example, the user has changed the repetition/expansion mode for an existing module call and so now all of the resource instances addresses it previously contained are "orphaned". To represent that, we'll instead say that an invalid instance key of a declared module behaves as if it contains no resource instances at all, regardless of the configurations of any resources nested inside. This then gives the result needed to successfully detect all of the former resource instances as "orphaned" and plan to destroy them. However, this then introduces a new case for NodePlannableResourceInstanceOrphan.deleteActionReason to deal with: the resource configuration still exists (because configuration isn't aware of individual module/resource instances) but the module instance does not. This actually allows us to resolve, at least partially, a previous missing piece of explaining to the user why the resource instances are planned for deletion in that case, finally allowing us to be explicit to the user that it's because of the module instance being removed, which internally we call plans.ResourceInstanceDeleteBecauseNoModule. Co-authored-by: Alisdair McDiarmid <alisdair@users.noreply.github.com>
2021-12-08 21:40:32 +01:00
// 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]`),
}
instances: Non-existing module instance has no resource instances Previously we were treating it as a programming error to ask for the instances of a resource inside an instance of a module that is declared but whose declaration doesn't include the given instance key. However, that's actually a valid situation which can arise if, for example, the user has changed the repetition/expansion mode for an existing module call and so now all of the resource instances addresses it previously contained are "orphaned". To represent that, we'll instead say that an invalid instance key of a declared module behaves as if it contains no resource instances at all, regardless of the configurations of any resources nested inside. This then gives the result needed to successfully detect all of the former resource instances as "orphaned" and plan to destroy them. However, this then introduces a new case for NodePlannableResourceInstanceOrphan.deleteActionReason to deal with: the resource configuration still exists (because configuration isn't aware of individual module/resource instances) but the module instance does not. This actually allows us to resolve, at least partially, a previous missing piece of explaining to the user why the resource instances are planned for deletion in that case, finally allowing us to be explicit to the user that it's because of the module instance being removed, which internally we call plans.ResourceInstanceDeleteBecauseNoModule. Co-authored-by: Alisdair McDiarmid <alisdair@users.noreply.github.com>
2021-12-08 21:40:32 +01:00
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`))
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`))
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)
}
})
instances: Non-existing module instance has no resource instances Previously we were treating it as a programming error to ask for the instances of a resource inside an instance of a module that is declared but whose declaration doesn't include the given instance key. However, that's actually a valid situation which can arise if, for example, the user has changed the repetition/expansion mode for an existing module call and so now all of the resource instances addresses it previously contained are "orphaned". To represent that, we'll instead say that an invalid instance key of a declared module behaves as if it contains no resource instances at all, regardless of the configurations of any resources nested inside. This then gives the result needed to successfully detect all of the former resource instances as "orphaned" and plan to destroy them. However, this then introduces a new case for NodePlannableResourceInstanceOrphan.deleteActionReason to deal with: the resource configuration still exists (because configuration isn't aware of individual module/resource instances) but the module instance does not. This actually allows us to resolve, at least partially, a previous missing piece of explaining to the user why the resource instances are planned for deletion in that case, finally allowing us to be explicit to the user that it's because of the module instance being removed, which internally we call plans.ResourceInstanceDeleteBecauseNoModule. Co-authored-by: Alisdair McDiarmid <alisdair@users.noreply.github.com>
2021-12-08 21:40:32 +01:00
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`))
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`))
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 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)
}