core: ResourceAddress.Contains method
This is similar in purpose to Equals but it takes a hierarchical approach where modules contain their child modules, resources are contained by their modules, and indexed resource instances are contained by their resource names. Unlike "Equals", Contains is intended to be transitive, so if A contains B and B contains C, then C necessarily contains A. It is also directional: if A contains B then B does not also contain A unless A and B are identical. This results in more intuitive behavior for use-cases where the goal is to select a portion of the address space for an operation.
This commit is contained in:
parent
9777174be1
commit
d3eb2b2d28
|
@ -248,6 +248,53 @@ func ParseResourceAddress(s string) (*ResourceAddress, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Contains returns true if and only if the given node is contained within
|
||||||
|
// the receiver.
|
||||||
|
//
|
||||||
|
// Containment is defined in terms of the module and resource heirarchy:
|
||||||
|
// a resource is contained within its module and any ancestor modules,
|
||||||
|
// an indexed resource instance is contained with the unindexed resource, etc.
|
||||||
|
func (addr *ResourceAddress) Contains(other *ResourceAddress) bool {
|
||||||
|
ourPath := addr.Path
|
||||||
|
givenPath := other.Path
|
||||||
|
if len(givenPath) < len(ourPath) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range ourPath {
|
||||||
|
if ourPath[i] != givenPath[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the receiver is a whole-module address then the path prefix
|
||||||
|
// matching is all we need.
|
||||||
|
if !addr.HasResourceSpec() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if addr.Type != other.Type || addr.Name != other.Name || addr.Mode != other.Mode {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if addr.Index != -1 && addr.Index != other.Index {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if addr.InstanceTypeSet && (addr.InstanceTypeSet != other.InstanceTypeSet || addr.InstanceType != other.InstanceType) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals returns true if the receiver matches the given address.
|
||||||
|
//
|
||||||
|
// The name of this method is a misnomer, since it doesn't test for exact
|
||||||
|
// equality. Instead, it tests that the _specified_ parts of each
|
||||||
|
// address match, treating any unspecified parts as wildcards.
|
||||||
|
//
|
||||||
|
// See also Contains, which takes a more heirarchical approach to comparing
|
||||||
|
// addresses.
|
||||||
func (addr *ResourceAddress) Equals(raw interface{}) bool {
|
func (addr *ResourceAddress) Equals(raw interface{}) bool {
|
||||||
other, ok := raw.(*ResourceAddress)
|
other, ok := raw.(*ResourceAddress)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -324,7 +371,7 @@ func tokenizeResourceAddress(s string) (map[string]string, error) {
|
||||||
// string "aws_instance.web.tainted[1]"
|
// string "aws_instance.web.tainted[1]"
|
||||||
re := regexp.MustCompile(`\A` +
|
re := regexp.MustCompile(`\A` +
|
||||||
// "module.foo.module.bar" (optional)
|
// "module.foo.module.bar" (optional)
|
||||||
`(?P<path>(?:module\.[^.]+\.?)*)` +
|
`(?P<path>(?:module\.(?P<module_name>[^.]+)\.?)*)` +
|
||||||
// possibly "data.", if targeting is a data resource
|
// possibly "data.", if targeting is a data resource
|
||||||
`(?P<data_prefix>(?:data\.)?)` +
|
`(?P<data_prefix>(?:data\.)?)` +
|
||||||
// "aws_instance.web" (optional when module path specified)
|
// "aws_instance.web" (optional when module path specified)
|
||||||
|
|
|
@ -304,6 +304,306 @@ func TestParseResourceAddress(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestResourceAddressContains(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Address *ResourceAddress
|
||||||
|
Other *ResourceAddress
|
||||||
|
Want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
&ResourceAddress{
|
||||||
|
Mode: config.ManagedResourceMode,
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceTypeSet: true,
|
||||||
|
InstanceType: TypePrimary,
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
&ResourceAddress{
|
||||||
|
Mode: config.ManagedResourceMode,
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceTypeSet: true,
|
||||||
|
InstanceType: TypePrimary,
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&ResourceAddress{
|
||||||
|
Mode: config.ManagedResourceMode,
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceTypeSet: false,
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
&ResourceAddress{
|
||||||
|
Mode: config.ManagedResourceMode,
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceTypeSet: true,
|
||||||
|
InstanceType: TypePrimary,
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&ResourceAddress{
|
||||||
|
Mode: config.ManagedResourceMode,
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceTypeSet: false,
|
||||||
|
Index: -1,
|
||||||
|
},
|
||||||
|
&ResourceAddress{
|
||||||
|
Mode: config.ManagedResourceMode,
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceTypeSet: true,
|
||||||
|
InstanceType: TypePrimary,
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&ResourceAddress{
|
||||||
|
Mode: config.ManagedResourceMode,
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceTypeSet: false,
|
||||||
|
Index: -1,
|
||||||
|
},
|
||||||
|
&ResourceAddress{
|
||||||
|
Mode: config.ManagedResourceMode,
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceTypeSet: false,
|
||||||
|
Index: -1,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&ResourceAddress{
|
||||||
|
InstanceTypeSet: false,
|
||||||
|
Index: -1,
|
||||||
|
},
|
||||||
|
&ResourceAddress{
|
||||||
|
Mode: config.ManagedResourceMode,
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceTypeSet: false,
|
||||||
|
Index: -1,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&ResourceAddress{
|
||||||
|
InstanceTypeSet: false,
|
||||||
|
Index: -1,
|
||||||
|
},
|
||||||
|
&ResourceAddress{
|
||||||
|
Path: []string{"bar"},
|
||||||
|
Mode: config.ManagedResourceMode,
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceTypeSet: false,
|
||||||
|
Index: -1,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&ResourceAddress{
|
||||||
|
Path: []string{"bar"},
|
||||||
|
InstanceTypeSet: false,
|
||||||
|
Index: -1,
|
||||||
|
},
|
||||||
|
&ResourceAddress{
|
||||||
|
Path: []string{"bar"},
|
||||||
|
Mode: config.ManagedResourceMode,
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceTypeSet: false,
|
||||||
|
Index: -1,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&ResourceAddress{
|
||||||
|
Path: []string{"bar"},
|
||||||
|
InstanceTypeSet: false,
|
||||||
|
Index: -1,
|
||||||
|
},
|
||||||
|
&ResourceAddress{
|
||||||
|
Path: []string{"bar", "baz"},
|
||||||
|
Mode: config.ManagedResourceMode,
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceTypeSet: false,
|
||||||
|
Index: -1,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&ResourceAddress{
|
||||||
|
Path: []string{"bar"},
|
||||||
|
InstanceTypeSet: false,
|
||||||
|
Index: -1,
|
||||||
|
},
|
||||||
|
&ResourceAddress{
|
||||||
|
Path: []string{"bar", "baz"},
|
||||||
|
InstanceTypeSet: false,
|
||||||
|
Index: -1,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&ResourceAddress{
|
||||||
|
Path: []string{"bar"},
|
||||||
|
InstanceTypeSet: false,
|
||||||
|
Index: -1,
|
||||||
|
},
|
||||||
|
&ResourceAddress{
|
||||||
|
Path: []string{"bar", "baz", "foo", "pizza"},
|
||||||
|
InstanceTypeSet: false,
|
||||||
|
Index: -1,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
&ResourceAddress{
|
||||||
|
Mode: config.ManagedResourceMode,
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "bar",
|
||||||
|
InstanceTypeSet: true,
|
||||||
|
InstanceType: TypePrimary,
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
&ResourceAddress{
|
||||||
|
Mode: config.ManagedResourceMode,
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceTypeSet: true,
|
||||||
|
InstanceType: TypePrimary,
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&ResourceAddress{
|
||||||
|
Mode: config.ManagedResourceMode,
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceTypeSet: true,
|
||||||
|
InstanceType: TypePrimary,
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
&ResourceAddress{
|
||||||
|
Mode: config.DataResourceMode,
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceTypeSet: true,
|
||||||
|
InstanceType: TypePrimary,
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&ResourceAddress{
|
||||||
|
Path: []string{"bar"},
|
||||||
|
InstanceTypeSet: false,
|
||||||
|
Index: -1,
|
||||||
|
},
|
||||||
|
&ResourceAddress{
|
||||||
|
Path: []string{"baz"},
|
||||||
|
Mode: config.ManagedResourceMode,
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceTypeSet: false,
|
||||||
|
Index: -1,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&ResourceAddress{
|
||||||
|
Path: []string{"bar"},
|
||||||
|
InstanceTypeSet: false,
|
||||||
|
Index: -1,
|
||||||
|
},
|
||||||
|
&ResourceAddress{
|
||||||
|
Path: []string{"baz", "bar"},
|
||||||
|
Mode: config.ManagedResourceMode,
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceTypeSet: false,
|
||||||
|
Index: -1,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&ResourceAddress{
|
||||||
|
Mode: config.ManagedResourceMode,
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceTypeSet: true,
|
||||||
|
InstanceType: TypePrimary,
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
&ResourceAddress{
|
||||||
|
Mode: config.ManagedResourceMode,
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
InstanceTypeSet: false,
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&ResourceAddress{
|
||||||
|
Path: []string{"bar", "baz"},
|
||||||
|
InstanceTypeSet: false,
|
||||||
|
Index: -1,
|
||||||
|
},
|
||||||
|
&ResourceAddress{
|
||||||
|
Path: []string{"bar"},
|
||||||
|
InstanceTypeSet: false,
|
||||||
|
Index: -1,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&ResourceAddress{
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
Index: 1,
|
||||||
|
InstanceType: TypePrimary,
|
||||||
|
Mode: config.ManagedResourceMode,
|
||||||
|
},
|
||||||
|
&ResourceAddress{
|
||||||
|
Type: "aws_instance",
|
||||||
|
Name: "foo",
|
||||||
|
Index: -1,
|
||||||
|
InstanceType: TypePrimary,
|
||||||
|
Mode: config.ManagedResourceMode,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%s contains %s", test.Address, test.Other), func(t *testing.T) {
|
||||||
|
got := test.Address.Contains(test.Other)
|
||||||
|
if got != test.Want {
|
||||||
|
t.Errorf(
|
||||||
|
"wrong result\nrecv: %s\ngiven: %s\ngot: %#v\nwant: %#v",
|
||||||
|
test.Address, test.Other,
|
||||||
|
got, test.Want,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestResourceAddressEquals(t *testing.T) {
|
func TestResourceAddressEquals(t *testing.T) {
|
||||||
cases := map[string]struct {
|
cases := map[string]struct {
|
||||||
Address *ResourceAddress
|
Address *ResourceAddress
|
||||||
|
|
Loading…
Reference in New Issue