diff --git a/terraform/resource_address.go b/terraform/resource_address.go index 06fbced8d..ca3f61b38 100644 --- a/terraform/resource_address.go +++ b/terraform/resource_address.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/config/module" ) // 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 // 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. diff --git a/terraform/resource_address_test.go b/terraform/resource_address_test.go index bcf871c5e..4cc2c013b 100644 --- a/terraform/resource_address_test.go +++ b/terraform/resource_address_test.go @@ -1,10 +1,12 @@ package terraform import ( + "fmt" "reflect" "testing" "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/config/module" ) 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, + ) + } + }) + } +} diff --git a/terraform/test-fixtures/empty-with-child-module/child/child.tf b/terraform/test-fixtures/empty-with-child-module/child/child.tf new file mode 100644 index 000000000..05e29577e --- /dev/null +++ b/terraform/test-fixtures/empty-with-child-module/child/child.tf @@ -0,0 +1,3 @@ +module "grandchild" { + source = "../grandchild" +} diff --git a/terraform/test-fixtures/empty-with-child-module/grandchild/grandchild.tf b/terraform/test-fixtures/empty-with-child-module/grandchild/grandchild.tf new file mode 100644 index 000000000..4b41c9fcf --- /dev/null +++ b/terraform/test-fixtures/empty-with-child-module/grandchild/grandchild.tf @@ -0,0 +1 @@ +# Nothing here! diff --git a/terraform/test-fixtures/empty-with-child-module/root.tf b/terraform/test-fixtures/empty-with-child-module/root.tf new file mode 100644 index 000000000..1f95749fa --- /dev/null +++ b/terraform/test-fixtures/empty-with-child-module/root.tf @@ -0,0 +1,3 @@ +module "child" { + source = "./child" +}