core: ResourceAddress.MatchesConfig method

This is a useful building block for filtering configuration based on a
resource address. It is similar in principle to state filtering, but for
specific resource configuration blocks.
This commit is contained in:
Martin Atkins 2017-05-16 18:06:33 -07:00
parent f695e8b330
commit b82ef2e30e
5 changed files with 212 additions and 0 deletions

View File

@ -8,6 +8,7 @@ import (
"strings" "strings"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/config/module"
) )
// ResourceAddress is a way of identifying an individual resource (or, // ResourceAddress is a way of identifying an individual resource (or,
@ -108,6 +109,32 @@ func (r *ResourceAddress) WholeModuleAddress() *ResourceAddress {
} }
} }
// MatchesConfig returns true if the receiver matches the given
// configuration resource within the given configuration module.
//
// Since resource configuration blocks represent all of the instances of
// a multi-instance resource, the index of the address (if any) is not
// considered.
func (r *ResourceAddress) MatchesConfig(mod *module.Tree, rc *config.Resource) bool {
if r.HasResourceSpec() {
if r.Mode != rc.Mode || r.Type != rc.Type || r.Name != rc.Name {
return false
}
}
addrPath := r.Path
cfgPath := mod.Path()
// normalize
if len(addrPath) == 0 {
addrPath = nil
}
if len(cfgPath) == 0 {
cfgPath = nil
}
return reflect.DeepEqual(addrPath, cfgPath)
}
// stateId returns the ID that this resource should be entered with // stateId returns the ID that this resource should be entered with
// in the state. This is also used for diffs. In the future, we'd like to // in the state. This is also used for diffs. In the future, we'd like to
// move away from this string field so I don't export this. // move away from this string field so I don't export this.

View File

@ -1,10 +1,12 @@
package terraform package terraform
import ( import (
"fmt"
"reflect" "reflect"
"testing" "testing"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/config/module"
) )
func TestParseResourceAddressInternal(t *testing.T) { func TestParseResourceAddressInternal(t *testing.T) {
@ -743,3 +745,179 @@ func TestResourceAddressWholeModuleAddress(t *testing.T) {
}) })
} }
} }
func TestResourceAddressMatchesConfig(t *testing.T) {
root := testModule(t, "empty-with-child-module")
child := root.Child([]string{"child"})
grandchild := root.Child([]string{"child", "grandchild"})
tests := []struct {
Addr *ResourceAddress
Module *module.Tree
Resource *config.Resource
Want bool
}{
{
&ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "null_resource",
Name: "baz",
Index: -1,
},
root,
&config.Resource{
Mode: config.ManagedResourceMode,
Type: "null_resource",
Name: "baz",
},
true,
},
{
&ResourceAddress{
Path: []string{"child"},
Mode: config.ManagedResourceMode,
Type: "null_resource",
Name: "baz",
Index: -1,
},
child,
&config.Resource{
Mode: config.ManagedResourceMode,
Type: "null_resource",
Name: "baz",
},
true,
},
{
&ResourceAddress{
Path: []string{"child", "grandchild"},
Mode: config.ManagedResourceMode,
Type: "null_resource",
Name: "baz",
Index: -1,
},
grandchild,
&config.Resource{
Mode: config.ManagedResourceMode,
Type: "null_resource",
Name: "baz",
},
true,
},
{
&ResourceAddress{
Path: []string{"child"},
Index: -1,
},
child,
&config.Resource{
Mode: config.ManagedResourceMode,
Type: "null_resource",
Name: "baz",
},
true,
},
{
&ResourceAddress{
Path: []string{"child", "grandchild"},
Index: -1,
},
grandchild,
&config.Resource{
Mode: config.ManagedResourceMode,
Type: "null_resource",
Name: "baz",
},
true,
},
{
&ResourceAddress{
Mode: config.DataResourceMode,
Type: "null_resource",
Name: "baz",
Index: -1,
},
module.NewEmptyTree(),
&config.Resource{
Mode: config.ManagedResourceMode,
Type: "null_resource",
Name: "baz",
},
false,
},
{
&ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "null_resource",
Name: "baz",
Index: -1,
},
module.NewEmptyTree(),
&config.Resource{
Mode: config.ManagedResourceMode,
Type: "null_resource",
Name: "pizza",
},
false,
},
{
&ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "null_resource",
Name: "baz",
Index: -1,
},
module.NewEmptyTree(),
&config.Resource{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "baz",
},
false,
},
{
&ResourceAddress{
Path: []string{"child", "grandchild"},
Mode: config.ManagedResourceMode,
Type: "null_resource",
Name: "baz",
Index: -1,
},
child,
&config.Resource{
Mode: config.ManagedResourceMode,
Type: "null_resource",
Name: "baz",
},
false,
},
{
&ResourceAddress{
Path: []string{"child"},
Mode: config.ManagedResourceMode,
Type: "null_resource",
Name: "baz",
Index: -1,
},
grandchild,
&config.Resource{
Mode: config.ManagedResourceMode,
Type: "null_resource",
Name: "baz",
},
false,
},
}
for i, test := range tests {
t.Run(fmt.Sprintf("%02d-%s", i, test.Addr), func(t *testing.T) {
got := test.Addr.MatchesConfig(test.Module, test.Resource)
if got != test.Want {
t.Errorf(
"wrong result\naddr: %s\nmod: %#v\nrsrc: %#v\ngot: %#v\nwant: %#v",
test.Addr, test.Module.Path(), test.Resource, got, test.Want,
)
}
})
}
}

View File

@ -0,0 +1,3 @@
module "grandchild" {
source = "../grandchild"
}

View File

@ -0,0 +1 @@
# Nothing here!

View File

@ -0,0 +1,3 @@
module "child" {
source = "./child"
}