core: ResourceAddress supports data resources

The ResourceAddress struct grows a new "Mode" field to match with
Resource, and its parser learns to recognize the "data." prefix so it
can set that field.

Allows -target to be applied to data sources, although that is arguably
not a very useful thing to do. Other future uses of resource addressing,
like the state plumbing commands, may be better uses of this.
This commit is contained in:
Martin Atkins 2016-05-08 02:14:13 -07:00
parent afc7ec5ac0
commit 61ab8bf39a
5 changed files with 149 additions and 6 deletions

View File

@ -219,6 +219,7 @@ func (n *GraphNodeConfigResource) ResourceAddress() *ResourceAddress {
InstanceType: TypePrimary, InstanceType: TypePrimary,
Name: n.Resource.Name, Name: n.Resource.Name,
Type: n.Resource.Type, Type: n.Resource.Type,
Mode: n.Resource.Mode,
} }
} }

View File

@ -6,6 +6,8 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"github.com/hashicorp/terraform/config"
) )
// ResourceAddress is a way of identifying an individual resource (or, // ResourceAddress is a way of identifying an individual resource (or,
@ -22,6 +24,7 @@ type ResourceAddress struct {
InstanceTypeSet bool InstanceTypeSet bool
Name string Name string
Type string Type string
Mode config.ResourceMode // significant only if InstanceTypeSet
} }
// Copy returns a copy of this ResourceAddress // Copy returns a copy of this ResourceAddress
@ -32,6 +35,7 @@ func (r *ResourceAddress) Copy() *ResourceAddress {
InstanceType: r.InstanceType, InstanceType: r.InstanceType,
Name: r.Name, Name: r.Name,
Type: r.Type, Type: r.Type,
Mode: r.Mode,
} }
for _, p := range r.Path { for _, p := range r.Path {
n.Path = append(n.Path, p) n.Path = append(n.Path, p)
@ -46,6 +50,15 @@ func (r *ResourceAddress) String() string {
result = append(result, "module", p) 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 != "" { if r.Type != "" {
result = append(result, r.Type) result = append(result, r.Type)
} }
@ -77,6 +90,10 @@ func ParseResourceAddress(s string) (*ResourceAddress, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
mode := config.ManagedResourceMode
if matches["data_prefix"] != "" {
mode = config.DataResourceMode
}
resourceIndex, err := ParseResourceIndex(matches["index"]) resourceIndex, err := ParseResourceIndex(matches["index"])
if err != nil { if err != nil {
return nil, err return nil, err
@ -87,6 +104,11 @@ func ParseResourceAddress(s string) (*ResourceAddress, error) {
} }
path := ParseResourcePath(matches["path"]) 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{ return &ResourceAddress{
Path: path, Path: path,
Index: resourceIndex, Index: resourceIndex,
@ -94,6 +116,7 @@ func ParseResourceAddress(s string) (*ResourceAddress, error) {
InstanceTypeSet: matches["instance_type"] != "", InstanceTypeSet: matches["instance_type"] != "",
Name: matches["name"], Name: matches["name"],
Type: matches["type"], Type: matches["type"],
Mode: mode,
}, nil }, nil
} }
@ -118,11 +141,17 @@ func (addr *ResourceAddress) Equals(raw interface{}) bool {
other.Type == "" || other.Type == "" ||
addr.Type == 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 && return pathMatch &&
indexMatch && indexMatch &&
addr.InstanceType == other.InstanceType && addr.InstanceType == other.InstanceType &&
nameMatch && nameMatch &&
typeMatch typeMatch &&
modeMatch
} }
func ParseResourceIndex(s string) (int, error) { func ParseResourceIndex(s string) (int, error) {
@ -168,6 +197,8 @@ func tokenizeResourceAddress(s string) (map[string]string, error) {
re := regexp.MustCompile(`\A` + re := regexp.MustCompile(`\A` +
// "module.foo.module.bar" (optional) // "module.foo.module.bar" (optional)
`(?P<path>(?:module\.[^.]+\.?)*)` + `(?P<path>(?:module\.[^.]+\.?)*)` +
// possibly "data.", if targeting is a data resource
`(?P<data_prefix>(?:data\.)?)` +
// "aws_instance.web" (optional when module path specified) // "aws_instance.web" (optional when module path specified)
`(?:(?P<type>[^.]+)\.(?P<name>[^.[]+))?` + `(?:(?P<type>[^.]+)\.(?P<name>[^.[]+))?` +
// "tainted" (optional, omission implies: "primary") // "tainted" (optional, omission implies: "primary")

View File

@ -3,6 +3,8 @@ package terraform
import ( import (
"reflect" "reflect"
"testing" "testing"
"github.com/hashicorp/terraform/config"
) )
func TestParseResourceAddress(t *testing.T) { func TestParseResourceAddress(t *testing.T) {
@ -11,9 +13,21 @@ func TestParseResourceAddress(t *testing.T) {
Expected *ResourceAddress Expected *ResourceAddress
Output string Output string
}{ }{
"implicit primary, no specific index": { "implicit primary managed instance, no specific index": {
"aws_instance.foo", "aws_instance.foo",
&ResourceAddress{ &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", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,
@ -24,6 +38,7 @@ func TestParseResourceAddress(t *testing.T) {
"implicit primary, explicit index": { "implicit primary, explicit index": {
"aws_instance.foo[2]", "aws_instance.foo[2]",
&ResourceAddress{ &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,
@ -34,6 +49,7 @@ func TestParseResourceAddress(t *testing.T) {
"implicit primary, explicit index over ten": { "implicit primary, explicit index over ten": {
"aws_instance.foo[12]", "aws_instance.foo[12]",
&ResourceAddress{ &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,
@ -44,6 +60,7 @@ func TestParseResourceAddress(t *testing.T) {
"explicit primary, explicit index": { "explicit primary, explicit index": {
"aws_instance.foo.primary[2]", "aws_instance.foo.primary[2]",
&ResourceAddress{ &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,
@ -55,6 +72,7 @@ func TestParseResourceAddress(t *testing.T) {
"tainted": { "tainted": {
"aws_instance.foo.tainted", "aws_instance.foo.tainted",
&ResourceAddress{ &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypeTainted, InstanceType: TypeTainted,
@ -66,6 +84,7 @@ func TestParseResourceAddress(t *testing.T) {
"deposed": { "deposed": {
"aws_instance.foo.deposed", "aws_instance.foo.deposed",
&ResourceAddress{ &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypeDeposed, InstanceType: TypeDeposed,
@ -77,6 +96,7 @@ func TestParseResourceAddress(t *testing.T) {
"with a hyphen": { "with a hyphen": {
"aws_instance.foo-bar", "aws_instance.foo-bar",
&ResourceAddress{ &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo-bar", Name: "foo-bar",
InstanceType: TypePrimary, InstanceType: TypePrimary,
@ -84,10 +104,23 @@ func TestParseResourceAddress(t *testing.T) {
}, },
"", "",
}, },
"in a module": { "managed in a module": {
"module.child.aws_instance.foo", "module.child.aws_instance.foo",
&ResourceAddress{ &ResourceAddress{
Path: []string{"child"}, 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", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,
@ -99,6 +132,7 @@ func TestParseResourceAddress(t *testing.T) {
"module.a.module.b.module.forever.aws_instance.foo", "module.a.module.b.module.forever.aws_instance.foo",
&ResourceAddress{ &ResourceAddress{
Path: []string{"a", "b", "forever"}, Path: []string{"a", "b", "forever"},
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,
@ -133,7 +167,7 @@ func TestParseResourceAddress(t *testing.T) {
for tn, tc := range cases { for tn, tc := range cases {
out, err := ParseResourceAddress(tc.Input) out, err := ParseResourceAddress(tc.Input)
if err != nil { if err != nil {
t.Fatalf("unexpected err: %#v", err) t.Fatalf("%s: unexpected err: %#v", tn, err)
} }
if !reflect.DeepEqual(out, tc.Expected) { if !reflect.DeepEqual(out, tc.Expected) {
@ -158,12 +192,14 @@ func TestResourceAddressEquals(t *testing.T) {
}{ }{
"basic match": { "basic match": {
Address: &ResourceAddress{ Address: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,
Index: 0, Index: 0,
}, },
Other: &ResourceAddress{ Other: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,
@ -173,12 +209,14 @@ func TestResourceAddressEquals(t *testing.T) {
}, },
"address does not set index": { "address does not set index": {
Address: &ResourceAddress{ Address: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,
Index: -1, Index: -1,
}, },
Other: &ResourceAddress{ Other: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,
@ -188,12 +226,14 @@ func TestResourceAddressEquals(t *testing.T) {
}, },
"other does not set index": { "other does not set index": {
Address: &ResourceAddress{ Address: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,
Index: 3, Index: 3,
}, },
Other: &ResourceAddress{ Other: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,
@ -203,12 +243,14 @@ func TestResourceAddressEquals(t *testing.T) {
}, },
"neither sets index": { "neither sets index": {
Address: &ResourceAddress{ Address: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,
Index: -1, Index: -1,
}, },
Other: &ResourceAddress{ Other: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,
@ -218,12 +260,14 @@ func TestResourceAddressEquals(t *testing.T) {
}, },
"index over ten": { "index over ten": {
Address: &ResourceAddress{ Address: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,
Index: 1, Index: 1,
}, },
Other: &ResourceAddress{ Other: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,
@ -233,12 +277,14 @@ func TestResourceAddressEquals(t *testing.T) {
}, },
"different type": { "different type": {
Address: &ResourceAddress{ Address: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,
Index: 0, Index: 0,
}, },
Other: &ResourceAddress{ Other: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_vpc", Type: "aws_vpc",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,
@ -246,14 +292,33 @@ func TestResourceAddressEquals(t *testing.T) {
}, },
Expect: false, 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": { "different name": {
Address: &ResourceAddress{ Address: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,
Index: 0, Index: 0,
}, },
Other: &ResourceAddress{ Other: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "bar", Name: "bar",
InstanceType: TypePrimary, InstanceType: TypePrimary,
@ -263,12 +328,14 @@ func TestResourceAddressEquals(t *testing.T) {
}, },
"different instance type": { "different instance type": {
Address: &ResourceAddress{ Address: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,
Index: 0, Index: 0,
}, },
Other: &ResourceAddress{ Other: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypeTainted, InstanceType: TypeTainted,
@ -278,12 +345,14 @@ func TestResourceAddressEquals(t *testing.T) {
}, },
"different index": { "different index": {
Address: &ResourceAddress{ Address: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,
Index: 0, Index: 0,
}, },
Other: &ResourceAddress{ Other: &ResourceAddress{
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,
@ -291,7 +360,7 @@ func TestResourceAddressEquals(t *testing.T) {
}, },
Expect: false, Expect: false,
}, },
"module address matches address of resource inside module": { "module address matches address of managed resource inside module": {
Address: &ResourceAddress{ Address: &ResourceAddress{
Path: []string{"a", "b"}, Path: []string{"a", "b"},
Type: "", Type: "",
@ -301,6 +370,7 @@ func TestResourceAddressEquals(t *testing.T) {
}, },
Other: &ResourceAddress{ Other: &ResourceAddress{
Path: []string{"a", "b"}, Path: []string{"a", "b"},
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,
@ -308,7 +378,25 @@ func TestResourceAddressEquals(t *testing.T) {
}, },
Expect: true, 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{ Address: &ResourceAddress{
Path: []string{"a", "b"}, Path: []string{"a", "b"},
Type: "", Type: "",
@ -318,6 +406,25 @@ func TestResourceAddressEquals(t *testing.T) {
}, },
Other: &ResourceAddress{ Other: &ResourceAddress{
Path: []string{"a"}, 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", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,
@ -328,6 +435,7 @@ func TestResourceAddressEquals(t *testing.T) {
"nil path vs empty path should match": { "nil path vs empty path should match": {
Address: &ResourceAddress{ Address: &ResourceAddress{
Path: []string{}, Path: []string{},
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,
@ -335,6 +443,7 @@ func TestResourceAddressEquals(t *testing.T) {
}, },
Other: &ResourceAddress{ Other: &ResourceAddress{
Path: nil, Path: nil,
Mode: config.ManagedResourceMode,
Type: "aws_instance", Type: "aws_instance",
Name: "foo", Name: "foo",
InstanceType: TypePrimary, InstanceType: TypePrimary,

View File

@ -175,6 +175,7 @@ func (n *graphNodeOrphanResource) ResourceAddress() *ResourceAddress {
Name: n.ResourceKey.Name, Name: n.ResourceKey.Name,
Path: n.Path[1:], Path: n.Path[1:],
Type: n.ResourceKey.Type, Type: n.ResourceKey.Type,
Mode: n.ResourceKey.Mode,
} }
} }

View File

@ -119,6 +119,7 @@ func (n *graphNodeExpandedResource) ResourceAddress() *ResourceAddress {
InstanceType: TypePrimary, InstanceType: TypePrimary,
Name: n.Resource.Name, Name: n.Resource.Name,
Type: n.Resource.Type, Type: n.Resource.Type,
Mode: n.Resource.Mode,
} }
} }