From 361bc5a8df2928cbd95a355416c80a2f4ab8b415 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 13 Sep 2016 10:44:44 -0700 Subject: [PATCH] terraform: parse internal resource addresses used in state/diff --- terraform/resource_address.go | 31 +++++++++++++++ terraform/resource_address_test.go | 60 ++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/terraform/resource_address.go b/terraform/resource_address.go index da22b2321..3d15c7f99 100644 --- a/terraform/resource_address.go +++ b/terraform/resource_address.go @@ -85,6 +85,37 @@ func (r *ResourceAddress) String() string { return strings.Join(result, ".") } +// parseResourceAddressInternal parses the somewhat bespoke resource +// identifier used in states and diffs, such as "instance.name.0". +func parseResourceAddressInternal(s string) (*ResourceAddress, error) { + // Split based on ".". Every resource address should have at least two + // elements (type and name). + parts := strings.Split(s, ".") + if len(parts) < 2 || len(parts) > 3 { + return nil, fmt.Errorf("Invalid internal resource address format: %s", s) + } + + // Build the parts of the resource address that are guaranteed to exist + addr := &ResourceAddress{ + Type: parts[0], + Name: parts[1], + Index: -1, + InstanceType: TypePrimary, + } + + // If we have more parts, then we have an index. Parse that. + if len(parts) > 2 { + idx, err := strconv.ParseInt(parts[2], 0, 0) + if err != nil { + return nil, fmt.Errorf("Error parsing resource address %q: %s", s, err) + } + + addr.Index = int(idx) + } + + return addr, nil +} + func ParseResourceAddress(s string) (*ResourceAddress, error) { matches, err := tokenizeResourceAddress(s) if err != nil { diff --git a/terraform/resource_address_test.go b/terraform/resource_address_test.go index 144d7a9ec..ac0720e4e 100644 --- a/terraform/resource_address_test.go +++ b/terraform/resource_address_test.go @@ -7,6 +7,66 @@ import ( "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]", + }, + } + + for tn, tc := range cases { + t.Run(tc.Input, func(t *testing.T) { + out, err := parseResourceAddressInternal(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) + } + + // 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