github.com/pmcatominey/terraform@v0.7.0-rc2.0.20160708105029-1401a52a5cc5/terraform/resource_address.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"regexp"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/hashicorp/terraform/config"
    11  )
    12  
    13  // ResourceAddress is a way of identifying an individual resource (or,
    14  // eventually, a subset of resources) within the state. It is used for Targets.
    15  type ResourceAddress struct {
    16  	// Addresses a resource falling somewhere in the module path
    17  	// When specified alone, addresses all resources within a module path
    18  	Path []string
    19  
    20  	// Addresses a specific resource that occurs in a list
    21  	Index int
    22  
    23  	InstanceType    InstanceType
    24  	InstanceTypeSet bool
    25  	Name            string
    26  	Type            string
    27  	Mode            config.ResourceMode // significant only if InstanceTypeSet
    28  }
    29  
    30  // Copy returns a copy of this ResourceAddress
    31  func (r *ResourceAddress) Copy() *ResourceAddress {
    32  	n := &ResourceAddress{
    33  		Path:         make([]string, 0, len(r.Path)),
    34  		Index:        r.Index,
    35  		InstanceType: r.InstanceType,
    36  		Name:         r.Name,
    37  		Type:         r.Type,
    38  		Mode:         r.Mode,
    39  	}
    40  	for _, p := range r.Path {
    41  		n.Path = append(n.Path, p)
    42  	}
    43  	return n
    44  }
    45  
    46  // String outputs the address that parses into this address.
    47  func (r *ResourceAddress) String() string {
    48  	var result []string
    49  	for _, p := range r.Path {
    50  		result = append(result, "module", p)
    51  	}
    52  
    53  	switch r.Mode {
    54  	case config.ManagedResourceMode:
    55  		// nothing to do
    56  	case config.DataResourceMode:
    57  		result = append(result, "data")
    58  	default:
    59  		panic(fmt.Errorf("unsupported resource mode %s", r.Mode))
    60  	}
    61  
    62  	if r.Type != "" {
    63  		result = append(result, r.Type)
    64  	}
    65  
    66  	if r.Name != "" {
    67  		name := r.Name
    68  		if r.InstanceTypeSet {
    69  			switch r.InstanceType {
    70  			case TypePrimary:
    71  				name += ".primary"
    72  			case TypeDeposed:
    73  				name += ".deposed"
    74  			case TypeTainted:
    75  				name += ".tainted"
    76  			}
    77  		}
    78  
    79  		if r.Index >= 0 {
    80  			name += fmt.Sprintf("[%d]", r.Index)
    81  		}
    82  		result = append(result, name)
    83  	}
    84  
    85  	return strings.Join(result, ".")
    86  }
    87  
    88  func ParseResourceAddress(s string) (*ResourceAddress, error) {
    89  	matches, err := tokenizeResourceAddress(s)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  	mode := config.ManagedResourceMode
    94  	if matches["data_prefix"] != "" {
    95  		mode = config.DataResourceMode
    96  	}
    97  	resourceIndex, err := ParseResourceIndex(matches["index"])
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  	instanceType, err := ParseInstanceType(matches["instance_type"])
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  	path := ParseResourcePath(matches["path"])
   106  
   107  	// not allowed to say "data." without a type following
   108  	if mode == config.DataResourceMode && matches["type"] == "" {
   109  		return nil, fmt.Errorf("must target specific data instance")
   110  	}
   111  
   112  	return &ResourceAddress{
   113  		Path:            path,
   114  		Index:           resourceIndex,
   115  		InstanceType:    instanceType,
   116  		InstanceTypeSet: matches["instance_type"] != "",
   117  		Name:            matches["name"],
   118  		Type:            matches["type"],
   119  		Mode:            mode,
   120  	}, nil
   121  }
   122  
   123  func (addr *ResourceAddress) Equals(raw interface{}) bool {
   124  	other, ok := raw.(*ResourceAddress)
   125  	if !ok {
   126  		return false
   127  	}
   128  
   129  	pathMatch := len(addr.Path) == 0 && len(other.Path) == 0 ||
   130  		reflect.DeepEqual(addr.Path, other.Path)
   131  
   132  	indexMatch := addr.Index == -1 ||
   133  		other.Index == -1 ||
   134  		addr.Index == other.Index
   135  
   136  	nameMatch := addr.Name == "" ||
   137  		other.Name == "" ||
   138  		addr.Name == other.Name
   139  
   140  	typeMatch := addr.Type == "" ||
   141  		other.Type == "" ||
   142  		addr.Type == other.Type
   143  
   144  	// mode is significant only when type is set
   145  	modeMatch := addr.Type == "" ||
   146  		other.Type == "" ||
   147  		addr.Mode == other.Mode
   148  
   149  	return pathMatch &&
   150  		indexMatch &&
   151  		addr.InstanceType == other.InstanceType &&
   152  		nameMatch &&
   153  		typeMatch &&
   154  		modeMatch
   155  }
   156  
   157  func ParseResourceIndex(s string) (int, error) {
   158  	if s == "" {
   159  		return -1, nil
   160  	}
   161  	return strconv.Atoi(s)
   162  }
   163  
   164  func ParseResourcePath(s string) []string {
   165  	if s == "" {
   166  		return nil
   167  	}
   168  	parts := strings.Split(s, ".")
   169  	path := make([]string, 0, len(parts))
   170  	for _, s := range parts {
   171  		// Due to the limitations of the regexp match below, the path match has
   172  		// some noise in it we have to filter out :|
   173  		if s == "" || s == "module" {
   174  			continue
   175  		}
   176  		path = append(path, s)
   177  	}
   178  	return path
   179  }
   180  
   181  func ParseInstanceType(s string) (InstanceType, error) {
   182  	switch s {
   183  	case "", "primary":
   184  		return TypePrimary, nil
   185  	case "deposed":
   186  		return TypeDeposed, nil
   187  	case "tainted":
   188  		return TypeTainted, nil
   189  	default:
   190  		return TypeInvalid, fmt.Errorf("Unexpected value for InstanceType field: %q", s)
   191  	}
   192  }
   193  
   194  func tokenizeResourceAddress(s string) (map[string]string, error) {
   195  	// Example of portions of the regexp below using the
   196  	// string "aws_instance.web.tainted[1]"
   197  	re := regexp.MustCompile(`\A` +
   198  		// "module.foo.module.bar" (optional)
   199  		`(?P<path>(?:module\.[^.]+\.?)*)` +
   200  		// possibly "data.", if targeting is a data resource
   201  		`(?P<data_prefix>(?:data\.)?)` +
   202  		// "aws_instance.web" (optional when module path specified)
   203  		`(?:(?P<type>[^.]+)\.(?P<name>[^.[]+))?` +
   204  		// "tainted" (optional, omission implies: "primary")
   205  		`(?:\.(?P<instance_type>\w+))?` +
   206  		// "1" (optional, omission implies: "0")
   207  		`(?:\[(?P<index>\d+)\])?` +
   208  		`\z`)
   209  	groupNames := re.SubexpNames()
   210  	rawMatches := re.FindAllStringSubmatch(s, -1)
   211  	if len(rawMatches) != 1 {
   212  		return nil, fmt.Errorf("Problem parsing address: %q", s)
   213  	}
   214  	matches := make(map[string]string)
   215  	for i, m := range rawMatches[0] {
   216  		matches[groupNames[i]] = m
   217  	}
   218  	return matches, nil
   219  }