Skip to content

Commit

Permalink
core: ResourceAddress.Contains method
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
apparentlymart committed Jun 16, 2017
1 parent 9777174 commit d3eb2b2
Show file tree
Hide file tree
Showing 2 changed files with 348 additions and 1 deletion.
49 changes: 48 additions & 1 deletion terraform/resource_address.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,53 @@ func ParseResourceAddress(s string) (*ResourceAddress, error) {
}, 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 {
other, ok := raw.(*ResourceAddress)
if !ok {
Expand Down Expand Up @@ -324,7 +371,7 @@ func tokenizeResourceAddress(s string) (map[string]string, error) {
// string "aws_instance.web.tainted[1]"
re := regexp.MustCompile(`\A` +
// "module.foo.module.bar" (optional)
`(?P<path>(?:module\.[^.]+\.?)*)` +
`(?P<path>(?:module\.(?P<module_name>[^.]+)\.?)*)` +
// possibly "data.", if targeting is a data resource
`(?P<data_prefix>(?:data\.)?)` +
// "aws_instance.web" (optional when module path specified)
Expand Down
300 changes: 300 additions & 0 deletions terraform/resource_address_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
cases := map[string]struct {
Address *ResourceAddress
Expand Down

0 comments on commit d3eb2b2

Please sign in to comment.