2015-03-30 19:02:36 -05:00
|
|
|
package terraform
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2015-05-01 11:01:49 -05:00
|
|
|
"reflect"
|
2015-03-30 19:02:36 -05:00
|
|
|
"regexp"
|
|
|
|
"strconv"
|
2015-05-01 11:01:49 -05:00
|
|
|
"strings"
|
2015-03-30 19:02:36 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
// ResourceAddress is a way of identifying an individual resource (or,
|
|
|
|
// eventually, a subset of resources) within the state. It is used for Targets.
|
|
|
|
type ResourceAddress struct {
|
2015-05-01 11:01:49 -05:00
|
|
|
// Addresses a resource falling somewhere in the module path
|
|
|
|
// When specified alone, addresses all resources within a module path
|
|
|
|
Path []string
|
|
|
|
|
|
|
|
// Addresses a specific resource that occurs in a list
|
|
|
|
Index int
|
|
|
|
|
2016-03-30 11:29:20 -05:00
|
|
|
InstanceType InstanceType
|
|
|
|
InstanceTypeSet bool
|
|
|
|
Name string
|
|
|
|
Type string
|
2015-03-30 19:02:36 -05:00
|
|
|
}
|
|
|
|
|
2016-02-05 17:40:35 -06:00
|
|
|
// Copy returns a copy of this ResourceAddress
|
|
|
|
func (r *ResourceAddress) Copy() *ResourceAddress {
|
|
|
|
n := &ResourceAddress{
|
|
|
|
Path: make([]string, 0, len(r.Path)),
|
|
|
|
Index: r.Index,
|
|
|
|
InstanceType: r.InstanceType,
|
|
|
|
Name: r.Name,
|
|
|
|
Type: r.Type,
|
|
|
|
}
|
|
|
|
for _, p := range r.Path {
|
|
|
|
n.Path = append(n.Path, p)
|
|
|
|
}
|
|
|
|
return n
|
|
|
|
}
|
|
|
|
|
2016-03-22 12:41:02 -05:00
|
|
|
// String outputs the address that parses into this address.
|
|
|
|
func (r *ResourceAddress) String() string {
|
|
|
|
var result []string
|
|
|
|
for _, p := range r.Path {
|
|
|
|
result = append(result, "module", p)
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.Type != "" {
|
|
|
|
result = append(result, r.Type)
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.Name != "" {
|
|
|
|
name := r.Name
|
2016-04-11 19:03:25 -05:00
|
|
|
if r.InstanceTypeSet {
|
|
|
|
switch r.InstanceType {
|
|
|
|
case TypePrimary:
|
|
|
|
name += ".primary"
|
|
|
|
case TypeDeposed:
|
|
|
|
name += ".deposed"
|
|
|
|
case TypeTainted:
|
|
|
|
name += ".tainted"
|
|
|
|
}
|
2016-03-22 12:41:02 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if r.Index >= 0 {
|
|
|
|
name += fmt.Sprintf("[%d]", r.Index)
|
|
|
|
}
|
|
|
|
result = append(result, name)
|
|
|
|
}
|
|
|
|
|
|
|
|
return strings.Join(result, ".")
|
|
|
|
}
|
|
|
|
|
2015-03-30 19:02:36 -05:00
|
|
|
func ParseResourceAddress(s string) (*ResourceAddress, error) {
|
|
|
|
matches, err := tokenizeResourceAddress(s)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-05-01 11:01:49 -05:00
|
|
|
resourceIndex, err := ParseResourceIndex(matches["index"])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2015-03-30 19:02:36 -05:00
|
|
|
}
|
2015-05-01 11:01:49 -05:00
|
|
|
instanceType, err := ParseInstanceType(matches["instance_type"])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2015-03-30 19:02:36 -05:00
|
|
|
}
|
2015-05-01 11:01:49 -05:00
|
|
|
path := ParseResourcePath(matches["path"])
|
2015-03-30 19:02:36 -05:00
|
|
|
|
|
|
|
return &ResourceAddress{
|
2016-03-30 11:29:20 -05:00
|
|
|
Path: path,
|
|
|
|
Index: resourceIndex,
|
|
|
|
InstanceType: instanceType,
|
|
|
|
InstanceTypeSet: matches["instance_type"] != "",
|
|
|
|
Name: matches["name"],
|
|
|
|
Type: matches["type"],
|
2015-03-30 19:02:36 -05:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (addr *ResourceAddress) Equals(raw interface{}) bool {
|
|
|
|
other, ok := raw.(*ResourceAddress)
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2015-10-08 07:48:04 -05:00
|
|
|
pathMatch := len(addr.Path) == 0 && len(other.Path) == 0 ||
|
|
|
|
reflect.DeepEqual(addr.Path, other.Path)
|
2015-05-01 11:01:49 -05:00
|
|
|
|
2015-10-08 07:48:04 -05:00
|
|
|
indexMatch := addr.Index == -1 ||
|
2015-03-30 19:02:36 -05:00
|
|
|
other.Index == -1 ||
|
2015-10-08 07:48:04 -05:00
|
|
|
addr.Index == other.Index
|
2015-03-30 19:02:36 -05:00
|
|
|
|
2015-10-08 07:48:04 -05:00
|
|
|
nameMatch := addr.Name == "" ||
|
2015-05-01 11:01:49 -05:00
|
|
|
other.Name == "" ||
|
2015-10-08 07:48:04 -05:00
|
|
|
addr.Name == other.Name
|
2015-05-01 11:01:49 -05:00
|
|
|
|
2015-10-08 07:48:04 -05:00
|
|
|
typeMatch := addr.Type == "" ||
|
2015-05-01 11:01:49 -05:00
|
|
|
other.Type == "" ||
|
2015-10-08 07:48:04 -05:00
|
|
|
addr.Type == other.Type
|
2015-05-01 11:01:49 -05:00
|
|
|
|
2015-10-08 07:48:04 -05:00
|
|
|
return pathMatch &&
|
2015-05-01 11:01:49 -05:00
|
|
|
indexMatch &&
|
|
|
|
addr.InstanceType == other.InstanceType &&
|
|
|
|
nameMatch &&
|
2015-10-08 07:48:04 -05:00
|
|
|
typeMatch
|
2015-05-01 11:01:49 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func ParseResourceIndex(s string) (int, error) {
|
|
|
|
if s == "" {
|
|
|
|
return -1, nil
|
|
|
|
}
|
|
|
|
return strconv.Atoi(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
func ParseResourcePath(s string) []string {
|
|
|
|
if s == "" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
parts := strings.Split(s, ".")
|
|
|
|
path := make([]string, 0, len(parts))
|
|
|
|
for _, s := range parts {
|
|
|
|
// Due to the limitations of the regexp match below, the path match has
|
|
|
|
// some noise in it we have to filter out :|
|
|
|
|
if s == "" || s == "module" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
path = append(path, s)
|
|
|
|
}
|
|
|
|
return path
|
2015-03-30 19:02:36 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func ParseInstanceType(s string) (InstanceType, error) {
|
|
|
|
switch s {
|
2015-05-01 11:01:49 -05:00
|
|
|
case "", "primary":
|
2015-03-30 19:02:36 -05:00
|
|
|
return TypePrimary, nil
|
|
|
|
case "deposed":
|
|
|
|
return TypeDeposed, nil
|
|
|
|
case "tainted":
|
|
|
|
return TypeTainted, nil
|
|
|
|
default:
|
|
|
|
return TypeInvalid, fmt.Errorf("Unexpected value for InstanceType field: %q", s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func tokenizeResourceAddress(s string) (map[string]string, error) {
|
|
|
|
// Example of portions of the regexp below using the
|
|
|
|
// string "aws_instance.web.tainted[1]"
|
|
|
|
re := regexp.MustCompile(`\A` +
|
2015-05-01 11:01:49 -05:00
|
|
|
// "module.foo.module.bar" (optional)
|
|
|
|
`(?P<path>(?:module\.[^.]+\.?)*)` +
|
|
|
|
// "aws_instance.web" (optional when module path specified)
|
|
|
|
`(?:(?P<type>[^.]+)\.(?P<name>[^.[]+))?` +
|
2015-03-30 19:02:36 -05:00
|
|
|
// "tainted" (optional, omission implies: "primary")
|
|
|
|
`(?:\.(?P<instance_type>\w+))?` +
|
|
|
|
// "1" (optional, omission implies: "0")
|
|
|
|
`(?:\[(?P<index>\d+)\])?` +
|
|
|
|
`\z`)
|
|
|
|
groupNames := re.SubexpNames()
|
|
|
|
rawMatches := re.FindAllStringSubmatch(s, -1)
|
|
|
|
if len(rawMatches) != 1 {
|
|
|
|
return nil, fmt.Errorf("Problem parsing address: %q", s)
|
|
|
|
}
|
|
|
|
matches := make(map[string]string)
|
|
|
|
for i, m := range rawMatches[0] {
|
|
|
|
matches[groupNames[i]] = m
|
|
|
|
}
|
|
|
|
return matches, nil
|
|
|
|
}
|