opentofu/terraform/resource_address_test.go
Mitchell Hashimoto 3b2282ca57
terraform: Destroy node should only include deposed for specific index
Fixes #10338

The destruction step for a resource was included the deposed resources
for _all_ resources with that name (ignoring the "index"). For example:
`aws_instance.foo.0` was including destroying deposed for
`aws_instance.foo.1`.

This changes the config to the deposed transformer to properly include
that index.

This change includes a larger change of changing `stateId` to include
the index. This affected more parts but was ultimately the issue in
question.
2016-11-29 09:16:18 -08:00

606 lines
14 KiB
Go

package terraform
import (
"reflect"
"testing"
"github.com/hashicorp/terraform/config"
)
func TestParseResourceAddressInternal(t *testing.T) {
cases := map[string]struct {
Input string
Expected *ResourceAddress
Output string
}{
"basic resource": {
"aws_instance.foo",
&ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: -1,
},
"aws_instance.foo",
},
"basic resource with count": {
"aws_instance.foo.1",
&ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: 1,
},
"aws_instance.foo[1]",
},
"data resource": {
"data.aws_ami.foo",
&ResourceAddress{
Mode: config.DataResourceMode,
Type: "aws_ami",
Name: "foo",
InstanceType: TypePrimary,
Index: -1,
},
"data.aws_ami.foo",
},
"data resource with count": {
"data.aws_ami.foo.1",
&ResourceAddress{
Mode: config.DataResourceMode,
Type: "aws_ami",
Name: "foo",
InstanceType: TypePrimary,
Index: 1,
},
"data.aws_ami.foo[1]",
},
"non-data resource with 4 elements": {
"aws_instance.foo.bar.1",
nil,
"",
},
}
for tn, tc := range cases {
t.Run(tc.Input, func(t *testing.T) {
out, err := parseResourceAddressInternal(tc.Input)
if (err != nil) != (tc.Expected == nil) {
t.Fatalf("%s: unexpected err: %#v", tn, err)
}
if err != nil {
return
}
if !reflect.DeepEqual(out, tc.Expected) {
t.Fatalf("bad: %q\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.Expected, out)
}
// Compare outputs if those exist
expected := tc.Input
if tc.Output != "" {
expected = tc.Output
}
if out.String() != expected {
t.Fatalf("bad: %q\n\nexpected: %s\n\ngot: %s", tn, expected, out)
}
// Compare equality because the internal parse is used
// to compare equality to equal inputs.
if !out.Equals(tc.Expected) {
t.Fatalf("expected equality:\n\n%#v\n\n%#v", out, tc.Expected)
}
})
}
}
func TestParseResourceAddress(t *testing.T) {
cases := map[string]struct {
Input string
Expected *ResourceAddress
Output string
}{
"implicit primary managed instance, no specific index": {
"aws_instance.foo",
&ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: -1,
},
"",
},
"implicit primary data instance, no specific index": {
"data.aws_instance.foo",
&ResourceAddress{
Mode: config.DataResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: -1,
},
"",
},
"implicit primary, explicit index": {
"aws_instance.foo[2]",
&ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: 2,
},
"",
},
"implicit primary, explicit index over ten": {
"aws_instance.foo[12]",
&ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: 12,
},
"",
},
"explicit primary, explicit index": {
"aws_instance.foo.primary[2]",
&ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
InstanceTypeSet: true,
Index: 2,
},
"",
},
"tainted": {
"aws_instance.foo.tainted",
&ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypeTainted,
InstanceTypeSet: true,
Index: -1,
},
"",
},
"deposed": {
"aws_instance.foo.deposed",
&ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypeDeposed,
InstanceTypeSet: true,
Index: -1,
},
"",
},
"with a hyphen": {
"aws_instance.foo-bar",
&ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo-bar",
InstanceType: TypePrimary,
Index: -1,
},
"",
},
"managed in a module": {
"module.child.aws_instance.foo",
&ResourceAddress{
Path: []string{"child"},
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: -1,
},
"",
},
"data in a module": {
"module.child.data.aws_instance.foo",
&ResourceAddress{
Path: []string{"child"},
Mode: config.DataResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: -1,
},
"",
},
"nested modules": {
"module.a.module.b.module.forever.aws_instance.foo",
&ResourceAddress{
Path: []string{"a", "b", "forever"},
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: -1,
},
"",
},
"just a module": {
"module.a",
&ResourceAddress{
Path: []string{"a"},
Type: "",
Name: "",
InstanceType: TypePrimary,
Index: -1,
},
"",
},
"just a nested module": {
"module.a.module.b",
&ResourceAddress{
Path: []string{"a", "b"},
Type: "",
Name: "",
InstanceType: TypePrimary,
Index: -1,
},
"",
},
}
for tn, tc := range cases {
out, err := ParseResourceAddress(tc.Input)
if err != nil {
t.Fatalf("%s: unexpected err: %#v", tn, err)
}
if !reflect.DeepEqual(out, tc.Expected) {
t.Fatalf("bad: %q\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.Expected, out)
}
expected := tc.Input
if tc.Output != "" {
expected = tc.Output
}
if out.String() != expected {
t.Fatalf("bad: %q\n\nexpected: %s\n\ngot: %s", tn, expected, out)
}
}
}
func TestResourceAddressEquals(t *testing.T) {
cases := map[string]struct {
Address *ResourceAddress
Other interface{}
Expect bool
}{
"basic match": {
Address: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: 0,
},
Other: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: 0,
},
Expect: true,
},
"address does not set index": {
Address: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: -1,
},
Other: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: 3,
},
Expect: true,
},
"other does not set index": {
Address: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: 3,
},
Other: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: -1,
},
Expect: true,
},
"neither sets index": {
Address: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: -1,
},
Other: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: -1,
},
Expect: true,
},
"index over ten": {
Address: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: 1,
},
Other: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: 13,
},
Expect: false,
},
"different type": {
Address: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: 0,
},
Other: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_vpc",
Name: "foo",
InstanceType: TypePrimary,
Index: 0,
},
Expect: false,
},
"different mode": {
Address: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: 0,
},
Other: &ResourceAddress{
Mode: config.DataResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: 0,
},
Expect: false,
},
"different name": {
Address: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: 0,
},
Other: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "bar",
InstanceType: TypePrimary,
Index: 0,
},
Expect: false,
},
"different instance type": {
Address: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: 0,
},
Other: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypeTainted,
Index: 0,
},
Expect: false,
},
"different index": {
Address: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: 0,
},
Other: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: 1,
},
Expect: false,
},
"module address matches address of managed resource inside module": {
Address: &ResourceAddress{
Path: []string{"a", "b"},
Type: "",
Name: "",
InstanceType: TypePrimary,
Index: -1,
},
Other: &ResourceAddress{
Path: []string{"a", "b"},
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: 0,
},
Expect: true,
},
"module address matches address of data resource inside module": {
Address: &ResourceAddress{
Path: []string{"a", "b"},
Type: "",
Name: "",
InstanceType: TypePrimary,
Index: -1,
},
Other: &ResourceAddress{
Path: []string{"a", "b"},
Mode: config.DataResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: 0,
},
Expect: true,
},
"module address doesn't match managed resource outside module": {
Address: &ResourceAddress{
Path: []string{"a", "b"},
Type: "",
Name: "",
InstanceType: TypePrimary,
Index: -1,
},
Other: &ResourceAddress{
Path: []string{"a"},
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: 0,
},
Expect: false,
},
"module address doesn't match data resource outside module": {
Address: &ResourceAddress{
Path: []string{"a", "b"},
Type: "",
Name: "",
InstanceType: TypePrimary,
Index: -1,
},
Other: &ResourceAddress{
Path: []string{"a"},
Mode: config.DataResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: 0,
},
Expect: false,
},
"nil path vs empty path should match": {
Address: &ResourceAddress{
Path: []string{},
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: -1,
},
Other: &ResourceAddress{
Path: nil,
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: 0,
},
Expect: true,
},
}
for tn, tc := range cases {
actual := tc.Address.Equals(tc.Other)
if actual != tc.Expect {
t.Fatalf("%q: expected equals: %t, got %t for:\n%#v\n%#v",
tn, tc.Expect, actual, tc.Address, tc.Other)
}
}
}
func TestResourceAddressStateId(t *testing.T) {
cases := map[string]struct {
Input *ResourceAddress
Expected string
}{
"basic resource": {
&ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: -1,
},
"aws_instance.foo",
},
"basic resource with index": {
&ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: 2,
},
"aws_instance.foo.2",
},
"data resource": {
&ResourceAddress{
Mode: config.DataResourceMode,
Type: "aws_instance",
Name: "foo",
InstanceType: TypePrimary,
Index: -1,
},
"data.aws_instance.foo",
},
}
for tn, tc := range cases {
t.Run(tn, func(t *testing.T) {
actual := tc.Input.stateId()
if actual != tc.Expected {
t.Fatalf("bad: %q\n\nexpected: %s\n\ngot: %s", tn, tc.Expected, actual)
}
})
}
}