github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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  	if r == nil {
    33  		return nil
    34  	}
    35  
    36  	n := &ResourceAddress{
    37  		Path:         make([]string, 0, len(r.Path)),
    38  		Index:        r.Index,
    39  		InstanceType: r.InstanceType,
    40  		Name:         r.Name,
    41  		Type:         r.Type,
    42  		Mode:         r.Mode,
    43  	}
    44  	for _, p := range r.Path {
    45  		n.Path = append(n.Path, p)
    46  	}
    47  	return n
    48  }
    49  
    50  // String outputs the address that parses into this address.
    51  func (r *ResourceAddress) String() string {
    52  	var result []string
    53  	for _, p := range r.Path {
    54  		result = append(result, "module", p)
    55  	}
    56  
    57  	switch r.Mode {
    58  	case config.ManagedResourceMode:
    59  		// nothing to do
    60  	case config.DataResourceMode:
    61  		result = append(result, "data")
    62  	default:
    63  		panic(fmt.Errorf("unsupported resource mode %s", r.Mode))
    64  	}
    65  
    66  	if r.Type != "" {
    67  		result = append(result, r.Type)
    68  	}
    69  
    70  	if r.Name != "" {
    71  		name := r.Name
    72  		if r.InstanceTypeSet {
    73  			switch r.InstanceType {
    74  			case TypePrimary:
    75  				name += ".primary"
    76  			case TypeDeposed:
    77  				name += ".deposed"
    78  			case TypeTainted:
    79  				name += ".tainted"
    80  			}
    81  		}
    82  
    83  		if r.Index >= 0 {
    84  			name += fmt.Sprintf("[%d]", r.Index)
    85  		}
    86  		result = append(result, name)
    87  	}
    88  
    89  	return strings.Join(result, ".")
    90  }
    91  
    92  // stateId returns the ID that this resource should be entered with
    93  // in the state. This is also used for diffs. In the future, we'd like to
    94  // move away from this string field so I don't export this.
    95  func (r *ResourceAddress) stateId() string {
    96  	result := fmt.Sprintf("%s.%s", r.Type, r.Name)
    97  	switch r.Mode {
    98  	case config.ManagedResourceMode:
    99  		// Done
   100  	case config.DataResourceMode:
   101  		result = fmt.Sprintf("data.%s", result)
   102  	default:
   103  		panic(fmt.Errorf("unknown resource mode: %s", r.Mode))
   104  	}
   105  	if r.Index >= 0 {
   106  		result += fmt.Sprintf(".%d", r.Index)
   107  	}
   108  
   109  	return result
   110  }
   111  
   112  // parseResourceAddressConfig creates a resource address from a config.Resource
   113  func parseResourceAddressConfig(r *config.Resource) (*ResourceAddress, error) {
   114  	return &ResourceAddress{
   115  		Type:         r.Type,
   116  		Name:         r.Name,
   117  		Index:        -1,
   118  		InstanceType: TypePrimary,
   119  		Mode:         r.Mode,
   120  	}, nil
   121  }
   122  
   123  // parseResourceAddressInternal parses the somewhat bespoke resource
   124  // identifier used in states and diffs, such as "instance.name.0".
   125  func parseResourceAddressInternal(s string) (*ResourceAddress, error) {
   126  	// Split based on ".". Every resource address should have at least two
   127  	// elements (type and name).
   128  	parts := strings.Split(s, ".")
   129  	if len(parts) < 2 || len(parts) > 4 {
   130  		return nil, fmt.Errorf("Invalid internal resource address format: %s", s)
   131  	}
   132  
   133  	// Data resource if we have at least 3 parts and the first one is data
   134  	mode := config.ManagedResourceMode
   135  	if len(parts) > 2 && parts[0] == "data" {
   136  		mode = config.DataResourceMode
   137  		parts = parts[1:]
   138  	}
   139  
   140  	// If we're not a data resource and we have more than 3, then it is an error
   141  	if len(parts) > 3 && mode != config.DataResourceMode {
   142  		return nil, fmt.Errorf("Invalid internal resource address format: %s", s)
   143  	}
   144  
   145  	// Build the parts of the resource address that are guaranteed to exist
   146  	addr := &ResourceAddress{
   147  		Type:         parts[0],
   148  		Name:         parts[1],
   149  		Index:        -1,
   150  		InstanceType: TypePrimary,
   151  		Mode:         mode,
   152  	}
   153  
   154  	// If we have more parts, then we have an index. Parse that.
   155  	if len(parts) > 2 {
   156  		idx, err := strconv.ParseInt(parts[2], 0, 0)
   157  		if err != nil {
   158  			return nil, fmt.Errorf("Error parsing resource address %q: %s", s, err)
   159  		}
   160  
   161  		addr.Index = int(idx)
   162  	}
   163  
   164  	return addr, nil
   165  }
   166  
   167  func ParseResourceAddress(s string) (*ResourceAddress, error) {
   168  	matches, err := tokenizeResourceAddress(s)
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  	mode := config.ManagedResourceMode
   173  	if matches["data_prefix"] != "" {
   174  		mode = config.DataResourceMode
   175  	}
   176  	resourceIndex, err := ParseResourceIndex(matches["index"])
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  	instanceType, err := ParseInstanceType(matches["instance_type"])
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  	path := ParseResourcePath(matches["path"])
   185  
   186  	// not allowed to say "data." without a type following
   187  	if mode == config.DataResourceMode && matches["type"] == "" {
   188  		return nil, fmt.Errorf("must target specific data instance")
   189  	}
   190  
   191  	return &ResourceAddress{
   192  		Path:            path,
   193  		Index:           resourceIndex,
   194  		InstanceType:    instanceType,
   195  		InstanceTypeSet: matches["instance_type"] != "",
   196  		Name:            matches["name"],
   197  		Type:            matches["type"],
   198  		Mode:            mode,
   199  	}, nil
   200  }
   201  
   202  func (addr *ResourceAddress) Equals(raw interface{}) bool {
   203  	other, ok := raw.(*ResourceAddress)
   204  	if !ok {
   205  		return false
   206  	}
   207  
   208  	pathMatch := len(addr.Path) == 0 && len(other.Path) == 0 ||
   209  		reflect.DeepEqual(addr.Path, other.Path)
   210  
   211  	indexMatch := addr.Index == -1 ||
   212  		other.Index == -1 ||
   213  		addr.Index == other.Index
   214  
   215  	nameMatch := addr.Name == "" ||
   216  		other.Name == "" ||
   217  		addr.Name == other.Name
   218  
   219  	typeMatch := addr.Type == "" ||
   220  		other.Type == "" ||
   221  		addr.Type == other.Type
   222  
   223  	// mode is significant only when type is set
   224  	modeMatch := addr.Type == "" ||
   225  		other.Type == "" ||
   226  		addr.Mode == other.Mode
   227  
   228  	return pathMatch &&
   229  		indexMatch &&
   230  		addr.InstanceType == other.InstanceType &&
   231  		nameMatch &&
   232  		typeMatch &&
   233  		modeMatch
   234  }
   235  
   236  func ParseResourceIndex(s string) (int, error) {
   237  	if s == "" {
   238  		return -1, nil
   239  	}
   240  	return strconv.Atoi(s)
   241  }
   242  
   243  func ParseResourcePath(s string) []string {
   244  	if s == "" {
   245  		return nil
   246  	}
   247  	parts := strings.Split(s, ".")
   248  	path := make([]string, 0, len(parts))
   249  	for _, s := range parts {
   250  		// Due to the limitations of the regexp match below, the path match has
   251  		// some noise in it we have to filter out :|
   252  		if s == "" || s == "module" {
   253  			continue
   254  		}
   255  		path = append(path, s)
   256  	}
   257  	return path
   258  }
   259  
   260  func ParseInstanceType(s string) (InstanceType, error) {
   261  	switch s {
   262  	case "", "primary":
   263  		return TypePrimary, nil
   264  	case "deposed":
   265  		return TypeDeposed, nil
   266  	case "tainted":
   267  		return TypeTainted, nil
   268  	default:
   269  		return TypeInvalid, fmt.Errorf("Unexpected value for InstanceType field: %q", s)
   270  	}
   271  }
   272  
   273  func tokenizeResourceAddress(s string) (map[string]string, error) {
   274  	// Example of portions of the regexp below using the
   275  	// string "aws_instance.web.tainted[1]"
   276  	re := regexp.MustCompile(`\A` +
   277  		// "module.foo.module.bar" (optional)
   278  		`(?P<path>(?:module\.[^.]+\.?)*)` +
   279  		// possibly "data.", if targeting is a data resource
   280  		`(?P<data_prefix>(?:data\.)?)` +
   281  		// "aws_instance.web" (optional when module path specified)
   282  		`(?:(?P<type>[^.]+)\.(?P<name>[^.[]+))?` +
   283  		// "tainted" (optional, omission implies: "primary")
   284  		`(?:\.(?P<instance_type>\w+))?` +
   285  		// "1" (optional, omission implies: "0")
   286  		`(?:\[(?P<index>\d+)\])?` +
   287  		`\z`)
   288  
   289  	groupNames := re.SubexpNames()
   290  	rawMatches := re.FindAllStringSubmatch(s, -1)
   291  	if len(rawMatches) != 1 {
   292  		return nil, fmt.Errorf("Problem parsing address: %q", s)
   293  	}
   294  
   295  	matches := make(map[string]string)
   296  	for i, m := range rawMatches[0] {
   297  		matches[groupNames[i]] = m
   298  	}
   299  
   300  	return matches, nil
   301  }