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,
Name: n.Resource.Name,
Type: n.Resource.Type,
Mode: n.Resource.Mode,
}
}

View File

@ -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<path>(?:module\.[^.]+\.?)*)` +
// possibly "data.", if targeting is a data resource
`(?P<data_prefix>(?:data\.)?)` +
// "aws_instance.web" (optional when module path specified)
`(?:(?P<type>[^.]+)\.(?P<name>[^.[]+))?` +
// "tainted" (optional, omission implies: "primary")

View File

@ -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,

View File

@ -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,
}
}

View File

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