diff --git a/terraform/graph_config_node_resource.go b/terraform/graph_config_node_resource.go index 7c189b605..a2c688da4 100644 --- a/terraform/graph_config_node_resource.go +++ b/terraform/graph_config_node_resource.go @@ -219,6 +219,7 @@ func (n *GraphNodeConfigResource) ResourceAddress() *ResourceAddress { InstanceType: TypePrimary, Name: n.Resource.Name, Type: n.Resource.Type, + Mode: n.Resource.Mode, } } diff --git a/terraform/resource_address.go b/terraform/resource_address.go index 90f0eafd3..da22b2321 100644 --- a/terraform/resource_address.go +++ b/terraform/resource_address.go @@ -6,6 +6,8 @@ import ( "regexp" "strconv" "strings" + + "github.com/hashicorp/terraform/config" ) // ResourceAddress is a way of identifying an individual resource (or, @@ -22,6 +24,7 @@ type ResourceAddress struct { InstanceTypeSet bool Name string Type string + Mode config.ResourceMode // significant only if InstanceTypeSet } // Copy returns a copy of this ResourceAddress @@ -32,6 +35,7 @@ func (r *ResourceAddress) Copy() *ResourceAddress { InstanceType: r.InstanceType, Name: r.Name, Type: r.Type, + Mode: r.Mode, } for _, p := range r.Path { n.Path = append(n.Path, p) @@ -46,6 +50,15 @@ func (r *ResourceAddress) String() string { result = append(result, "module", p) } + switch r.Mode { + case config.ManagedResourceMode: + // nothing to do + case config.DataResourceMode: + result = append(result, "data") + default: + panic(fmt.Errorf("unsupported resource mode %s", r.Mode)) + } + if r.Type != "" { result = append(result, r.Type) } @@ -77,6 +90,10 @@ func ParseResourceAddress(s string) (*ResourceAddress, error) { if err != nil { return nil, err } + mode := config.ManagedResourceMode + if matches["data_prefix"] != "" { + mode = config.DataResourceMode + } resourceIndex, err := ParseResourceIndex(matches["index"]) if err != nil { return nil, err @@ -87,6 +104,11 @@ func ParseResourceAddress(s string) (*ResourceAddress, error) { } path := ParseResourcePath(matches["path"]) + // not allowed to say "data." without a type following + if mode == config.DataResourceMode && matches["type"] == "" { + return nil, fmt.Errorf("must target specific data instance") + } + return &ResourceAddress{ Path: path, Index: resourceIndex, @@ -94,6 +116,7 @@ func ParseResourceAddress(s string) (*ResourceAddress, error) { InstanceTypeSet: matches["instance_type"] != "", Name: matches["name"], Type: matches["type"], + Mode: mode, }, nil } @@ -118,11 +141,17 @@ func (addr *ResourceAddress) Equals(raw interface{}) bool { other.Type == "" || addr.Type == other.Type + // mode is significant only when type is set + modeMatch := addr.Type == "" || + other.Type == "" || + addr.Mode == other.Mode + return pathMatch && indexMatch && addr.InstanceType == other.InstanceType && nameMatch && - typeMatch + typeMatch && + modeMatch } func ParseResourceIndex(s string) (int, error) { @@ -168,6 +197,8 @@ func tokenizeResourceAddress(s string) (map[string]string, error) { re := regexp.MustCompile(`\A` + // "module.foo.module.bar" (optional) `(?P(?:module\.[^.]+\.?)*)` + + // possibly "data.", if targeting is a data resource + `(?P(?:data\.)?)` + // "aws_instance.web" (optional when module path specified) `(?:(?P[^.]+)\.(?P[^.[]+))?` + // "tainted" (optional, omission implies: "primary") diff --git a/terraform/resource_address_test.go b/terraform/resource_address_test.go index 17dc92367..144d7a9ec 100644 --- a/terraform/resource_address_test.go +++ b/terraform/resource_address_test.go @@ -3,6 +3,8 @@ package terraform import ( "reflect" "testing" + + "github.com/hashicorp/terraform/config" ) func TestParseResourceAddress(t *testing.T) { @@ -11,9 +13,21 @@ func TestParseResourceAddress(t *testing.T) { Expected *ResourceAddress Output string }{ - "implicit primary, no specific index": { + "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, @@ -24,6 +38,7 @@ func TestParseResourceAddress(t *testing.T) { "implicit primary, explicit index": { "aws_instance.foo[2]", &ResourceAddress{ + Mode: config.ManagedResourceMode, Type: "aws_instance", Name: "foo", InstanceType: TypePrimary, @@ -34,6 +49,7 @@ func TestParseResourceAddress(t *testing.T) { "implicit primary, explicit index over ten": { "aws_instance.foo[12]", &ResourceAddress{ + Mode: config.ManagedResourceMode, Type: "aws_instance", Name: "foo", InstanceType: TypePrimary, @@ -44,6 +60,7 @@ func TestParseResourceAddress(t *testing.T) { "explicit primary, explicit index": { "aws_instance.foo.primary[2]", &ResourceAddress{ + Mode: config.ManagedResourceMode, Type: "aws_instance", Name: "foo", InstanceType: TypePrimary, @@ -55,6 +72,7 @@ func TestParseResourceAddress(t *testing.T) { "tainted": { "aws_instance.foo.tainted", &ResourceAddress{ + Mode: config.ManagedResourceMode, Type: "aws_instance", Name: "foo", InstanceType: TypeTainted, @@ -66,6 +84,7 @@ func TestParseResourceAddress(t *testing.T) { "deposed": { "aws_instance.foo.deposed", &ResourceAddress{ + Mode: config.ManagedResourceMode, Type: "aws_instance", Name: "foo", InstanceType: TypeDeposed, @@ -77,6 +96,7 @@ func TestParseResourceAddress(t *testing.T) { "with a hyphen": { "aws_instance.foo-bar", &ResourceAddress{ + Mode: config.ManagedResourceMode, Type: "aws_instance", Name: "foo-bar", InstanceType: TypePrimary, @@ -84,10 +104,23 @@ func TestParseResourceAddress(t *testing.T) { }, "", }, - "in a module": { + "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, @@ -99,6 +132,7 @@ func TestParseResourceAddress(t *testing.T) { "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, @@ -133,7 +167,7 @@ func TestParseResourceAddress(t *testing.T) { for tn, tc := range cases { out, err := ParseResourceAddress(tc.Input) if err != nil { - t.Fatalf("unexpected err: %#v", err) + t.Fatalf("%s: unexpected err: %#v", tn, err) } if !reflect.DeepEqual(out, tc.Expected) { @@ -158,12 +192,14 @@ func TestResourceAddressEquals(t *testing.T) { }{ "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, @@ -173,12 +209,14 @@ func TestResourceAddressEquals(t *testing.T) { }, "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, @@ -188,12 +226,14 @@ func TestResourceAddressEquals(t *testing.T) { }, "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, @@ -203,12 +243,14 @@ func TestResourceAddressEquals(t *testing.T) { }, "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, @@ -218,12 +260,14 @@ func TestResourceAddressEquals(t *testing.T) { }, "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, @@ -233,12 +277,14 @@ func TestResourceAddressEquals(t *testing.T) { }, "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, @@ -246,14 +292,33 @@ func TestResourceAddressEquals(t *testing.T) { }, 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, @@ -263,12 +328,14 @@ func TestResourceAddressEquals(t *testing.T) { }, "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, @@ -278,12 +345,14 @@ func TestResourceAddressEquals(t *testing.T) { }, "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, @@ -291,7 +360,7 @@ func TestResourceAddressEquals(t *testing.T) { }, Expect: false, }, - "module address matches address of resource inside module": { + "module address matches address of managed resource inside module": { Address: &ResourceAddress{ Path: []string{"a", "b"}, Type: "", @@ -301,6 +370,7 @@ func TestResourceAddressEquals(t *testing.T) { }, Other: &ResourceAddress{ Path: []string{"a", "b"}, + Mode: config.ManagedResourceMode, Type: "aws_instance", Name: "foo", InstanceType: TypePrimary, @@ -308,7 +378,25 @@ func TestResourceAddressEquals(t *testing.T) { }, Expect: true, }, - "module address doesn't match resource outside module": { + "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: "", @@ -318,6 +406,25 @@ func TestResourceAddressEquals(t *testing.T) { }, 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, @@ -328,6 +435,7 @@ func TestResourceAddressEquals(t *testing.T) { "nil path vs empty path should match": { Address: &ResourceAddress{ Path: []string{}, + Mode: config.ManagedResourceMode, Type: "aws_instance", Name: "foo", InstanceType: TypePrimary, @@ -335,6 +443,7 @@ func TestResourceAddressEquals(t *testing.T) { }, Other: &ResourceAddress{ Path: nil, + Mode: config.ManagedResourceMode, Type: "aws_instance", Name: "foo", InstanceType: TypePrimary, diff --git a/terraform/transform_orphan.go b/terraform/transform_orphan.go index 4addbda0f..540bd2cc6 100644 --- a/terraform/transform_orphan.go +++ b/terraform/transform_orphan.go @@ -175,6 +175,7 @@ func (n *graphNodeOrphanResource) ResourceAddress() *ResourceAddress { Name: n.ResourceKey.Name, Path: n.Path[1:], Type: n.ResourceKey.Type, + Mode: n.ResourceKey.Mode, } } diff --git a/terraform/transform_resource.go b/terraform/transform_resource.go index da1471f87..14cd54f54 100644 --- a/terraform/transform_resource.go +++ b/terraform/transform_resource.go @@ -119,6 +119,7 @@ func (n *graphNodeExpandedResource) ResourceAddress() *ResourceAddress { InstanceType: TypePrimary, Name: n.Resource.Name, Type: n.Resource.Type, + Mode: n.Resource.Mode, } }