github.com/Hashicorp/terraform@v0.11.12-beta1/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  	"github.com/hashicorp/terraform/config/module"
    12  )
    13  
    14  // ResourceAddress is a way of identifying an individual resource (or,
    15  // eventually, a subset of resources) within the state. It is used for Targets.
    16  type ResourceAddress struct {
    17  	// Addresses a resource falling somewhere in the module path
    18  	// When specified alone, addresses all resources within a module path
    19  	Path []string
    20  
    21  	// Addresses a specific resource that occurs in a list
    22  	Index int
    23  
    24  	InstanceType    InstanceType
    25  	InstanceTypeSet bool
    26  	Name            string
    27  	Type            string
    28  	Mode            config.ResourceMode // significant only if InstanceTypeSet
    29  }
    30  
    31  // Copy returns a copy of this ResourceAddress
    32  func (r *ResourceAddress) Copy() *ResourceAddress {
    33  	if r == nil {
    34  		return nil
    35  	}
    36  
    37  	n := &ResourceAddress{
    38  		Path:         make([]string, 0, len(r.Path)),
    39  		Index:        r.Index,
    40  		InstanceType: r.InstanceType,
    41  		Name:         r.Name,
    42  		Type:         r.Type,
    43  		Mode:         r.Mode,
    44  	}
    45  
    46  	n.Path = append(n.Path, r.Path...)
    47  
    48  	return n
    49  }
    50  
    51  // String outputs the address that parses into this address.
    52  func (r *ResourceAddress) String() string {
    53  	var result []string
    54  	for _, p := range r.Path {
    55  		result = append(result, "module", p)
    56  	}
    57  
    58  	switch r.Mode {
    59  	case config.ManagedResourceMode:
    60  		// nothing to do
    61  	case config.DataResourceMode:
    62  		result = append(result, "data")
    63  	default:
    64  		panic(fmt.Errorf("unsupported resource mode %s", r.Mode))
    65  	}
    66  
    67  	if r.Type != "" {
    68  		result = append(result, r.Type)
    69  	}
    70  
    71  	if r.Name != "" {
    72  		name := r.Name
    73  		if r.InstanceTypeSet {
    74  			switch r.InstanceType {
    75  			case TypePrimary:
    76  				name += ".primary"
    77  			case TypeDeposed:
    78  				name += ".deposed"
    79  			case TypeTainted:
    80  				name += ".tainted"
    81  			}
    82  		}
    83  
    84  		if r.Index >= 0 {
    85  			name += fmt.Sprintf("[%d]", r.Index)
    86  		}
    87  		result = append(result, name)
    88  	}
    89  
    90  	return strings.Join(result, ".")
    91  }
    92  
    93  // HasResourceSpec returns true if the address has a resource spec, as
    94  // defined in the documentation:
    95  //    https://www.terraform.io/docs/internals/resource-addressing.html
    96  // In particular, this returns false if the address contains only
    97  // a module path, thus addressing the entire module.
    98  func (r *ResourceAddress) HasResourceSpec() bool {
    99  	return r.Type != "" && r.Name != ""
   100  }
   101  
   102  // WholeModuleAddress returns the resource address that refers to all
   103  // resources in the same module as the receiver address.
   104  func (r *ResourceAddress) WholeModuleAddress() *ResourceAddress {
   105  	return &ResourceAddress{
   106  		Path:            r.Path,
   107  		Index:           -1,
   108  		InstanceTypeSet: false,
   109  	}
   110  }
   111  
   112  // MatchesConfig returns true if the receiver matches the given
   113  // configuration resource within the given configuration module.
   114  //
   115  // Since resource configuration blocks represent all of the instances of
   116  // a multi-instance resource, the index of the address (if any) is not
   117  // considered.
   118  func (r *ResourceAddress) MatchesConfig(mod *module.Tree, rc *config.Resource) bool {
   119  	if r.HasResourceSpec() {
   120  		if r.Mode != rc.Mode || r.Type != rc.Type || r.Name != rc.Name {
   121  			return false
   122  		}
   123  	}
   124  
   125  	addrPath := r.Path
   126  	cfgPath := mod.Path()
   127  
   128  	// normalize
   129  	if len(addrPath) == 0 {
   130  		addrPath = nil
   131  	}
   132  	if len(cfgPath) == 0 {
   133  		cfgPath = nil
   134  	}
   135  	return reflect.DeepEqual(addrPath, cfgPath)
   136  }
   137  
   138  // stateId returns the ID that this resource should be entered with
   139  // in the state. This is also used for diffs. In the future, we'd like to
   140  // move away from this string field so I don't export this.
   141  func (r *ResourceAddress) stateId() string {
   142  	result := fmt.Sprintf("%s.%s", r.Type, r.Name)
   143  	switch r.Mode {
   144  	case config.ManagedResourceMode:
   145  		// Done
   146  	case config.DataResourceMode:
   147  		result = fmt.Sprintf("data.%s", result)
   148  	default:
   149  		panic(fmt.Errorf("unknown resource mode: %s", r.Mode))
   150  	}
   151  	if r.Index >= 0 {
   152  		result += fmt.Sprintf(".%d", r.Index)
   153  	}
   154  
   155  	return result
   156  }
   157  
   158  // parseResourceAddressConfig creates a resource address from a config.Resource
   159  func parseResourceAddressConfig(r *config.Resource) (*ResourceAddress, error) {
   160  	return &ResourceAddress{
   161  		Type:         r.Type,
   162  		Name:         r.Name,
   163  		Index:        -1,
   164  		InstanceType: TypePrimary,
   165  		Mode:         r.Mode,
   166  	}, nil
   167  }
   168  
   169  // parseResourceAddressInternal parses the somewhat bespoke resource
   170  // identifier used in states and diffs, such as "instance.name.0".
   171  func parseResourceAddressInternal(s string) (*ResourceAddress, error) {
   172  	// Split based on ".". Every resource address should have at least two
   173  	// elements (type and name).
   174  	parts := strings.Split(s, ".")
   175  	if len(parts) < 2 || len(parts) > 4 {
   176  		return nil, fmt.Errorf("Invalid internal resource address format: %s", s)
   177  	}
   178  
   179  	// Data resource if we have at least 3 parts and the first one is data
   180  	mode := config.ManagedResourceMode
   181  	if len(parts) > 2 && parts[0] == "data" {
   182  		mode = config.DataResourceMode
   183  		parts = parts[1:]
   184  	}
   185  
   186  	// If we're not a data resource and we have more than 3, then it is an error
   187  	if len(parts) > 3 && mode != config.DataResourceMode {
   188  		return nil, fmt.Errorf("Invalid internal resource address format: %s", s)
   189  	}
   190  
   191  	// Build the parts of the resource address that are guaranteed to exist
   192  	addr := &ResourceAddress{
   193  		Type:         parts[0],
   194  		Name:         parts[1],
   195  		Index:        -1,
   196  		InstanceType: TypePrimary,
   197  		Mode:         mode,
   198  	}
   199  
   200  	// If we have more parts, then we have an index. Parse that.
   201  	if len(parts) > 2 {
   202  		idx, err := strconv.ParseInt(parts[2], 0, 0)
   203  		if err != nil {
   204  			return nil, fmt.Errorf("Error parsing resource address %q: %s", s, err)
   205  		}
   206  
   207  		addr.Index = int(idx)
   208  	}
   209  
   210  	return addr, nil
   211  }
   212  
   213  func ParseResourceAddress(s string) (*ResourceAddress, error) {
   214  	matches, err := tokenizeResourceAddress(s)
   215  	if err != nil {
   216  		return nil, err
   217  	}
   218  	mode := config.ManagedResourceMode
   219  	if matches["data_prefix"] != "" {
   220  		mode = config.DataResourceMode
   221  	}
   222  	resourceIndex, err := ParseResourceIndex(matches["index"])
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  	instanceType, err := ParseInstanceType(matches["instance_type"])
   227  	if err != nil {
   228  		return nil, err
   229  	}
   230  	path := ParseResourcePath(matches["path"])
   231  
   232  	// not allowed to say "data." without a type following
   233  	if mode == config.DataResourceMode && matches["type"] == "" {
   234  		return nil, fmt.Errorf(
   235  			"invalid resource address %q: must target specific data instance",
   236  			s,
   237  		)
   238  	}
   239  
   240  	return &ResourceAddress{
   241  		Path:            path,
   242  		Index:           resourceIndex,
   243  		InstanceType:    instanceType,
   244  		InstanceTypeSet: matches["instance_type"] != "",
   245  		Name:            matches["name"],
   246  		Type:            matches["type"],
   247  		Mode:            mode,
   248  	}, nil
   249  }
   250  
   251  // ParseResourceAddressForInstanceDiff creates a ResourceAddress for a
   252  // resource name as described in a module diff.
   253  //
   254  // For historical reasons a different addressing format is used in this
   255  // context. The internal format should not be shown in the UI and instead
   256  // this function should be used to translate to a ResourceAddress and
   257  // then, where appropriate, use the String method to produce a canonical
   258  // resource address string for display in the UI.
   259  //
   260  // The given path slice must be empty (or nil) for the root module, and
   261  // otherwise consist of a sequence of module names traversing down into
   262  // the module tree. If a non-nil path is provided, the caller must not
   263  // modify its underlying array after passing it to this function.
   264  func ParseResourceAddressForInstanceDiff(path []string, key string) (*ResourceAddress, error) {
   265  	addr, err := parseResourceAddressInternal(key)
   266  	if err != nil {
   267  		return nil, err
   268  	}
   269  	addr.Path = path
   270  	return addr, nil
   271  }
   272  
   273  // Contains returns true if and only if the given node is contained within
   274  // the receiver.
   275  //
   276  // Containment is defined in terms of the module and resource heirarchy:
   277  // a resource is contained within its module and any ancestor modules,
   278  // an indexed resource instance is contained with the unindexed resource, etc.
   279  func (addr *ResourceAddress) Contains(other *ResourceAddress) bool {
   280  	ourPath := addr.Path
   281  	givenPath := other.Path
   282  	if len(givenPath) < len(ourPath) {
   283  		return false
   284  	}
   285  	for i := range ourPath {
   286  		if ourPath[i] != givenPath[i] {
   287  			return false
   288  		}
   289  	}
   290  
   291  	// If the receiver is a whole-module address then the path prefix
   292  	// matching is all we need.
   293  	if !addr.HasResourceSpec() {
   294  		return true
   295  	}
   296  
   297  	if addr.Type != other.Type || addr.Name != other.Name || addr.Mode != other.Mode {
   298  		return false
   299  	}
   300  
   301  	if addr.Index != -1 && addr.Index != other.Index {
   302  		return false
   303  	}
   304  
   305  	if addr.InstanceTypeSet && (addr.InstanceTypeSet != other.InstanceTypeSet || addr.InstanceType != other.InstanceType) {
   306  		return false
   307  	}
   308  
   309  	return true
   310  }
   311  
   312  // Equals returns true if the receiver matches the given address.
   313  //
   314  // The name of this method is a misnomer, since it doesn't test for exact
   315  // equality. Instead, it tests that the _specified_ parts of each
   316  // address match, treating any unspecified parts as wildcards.
   317  //
   318  // See also Contains, which takes a more heirarchical approach to comparing
   319  // addresses.
   320  func (addr *ResourceAddress) Equals(raw interface{}) bool {
   321  	other, ok := raw.(*ResourceAddress)
   322  	if !ok {
   323  		return false
   324  	}
   325  
   326  	pathMatch := len(addr.Path) == 0 && len(other.Path) == 0 ||
   327  		reflect.DeepEqual(addr.Path, other.Path)
   328  
   329  	indexMatch := addr.Index == -1 ||
   330  		other.Index == -1 ||
   331  		addr.Index == other.Index
   332  
   333  	nameMatch := addr.Name == "" ||
   334  		other.Name == "" ||
   335  		addr.Name == other.Name
   336  
   337  	typeMatch := addr.Type == "" ||
   338  		other.Type == "" ||
   339  		addr.Type == other.Type
   340  
   341  	// mode is significant only when type is set
   342  	modeMatch := addr.Type == "" ||
   343  		other.Type == "" ||
   344  		addr.Mode == other.Mode
   345  
   346  	return pathMatch &&
   347  		indexMatch &&
   348  		addr.InstanceType == other.InstanceType &&
   349  		nameMatch &&
   350  		typeMatch &&
   351  		modeMatch
   352  }
   353  
   354  // Less returns true if and only if the receiver should be sorted before
   355  // the given address when presenting a list of resource addresses to
   356  // an end-user.
   357  //
   358  // This sort uses lexicographic sorting for most components, but uses
   359  // numeric sort for indices, thus causing index 10 to sort after
   360  // index 9, rather than after index 1.
   361  func (addr *ResourceAddress) Less(other *ResourceAddress) bool {
   362  
   363  	switch {
   364  
   365  	case len(addr.Path) != len(other.Path):
   366  		return len(addr.Path) < len(other.Path)
   367  
   368  	case !reflect.DeepEqual(addr.Path, other.Path):
   369  		// If the two paths are the same length but don't match, we'll just
   370  		// cheat and compare the string forms since it's easier than
   371  		// comparing all of the path segments in turn, and lexicographic
   372  		// comparison is correct for the module path portion.
   373  		addrStr := addr.String()
   374  		otherStr := other.String()
   375  		return addrStr < otherStr
   376  
   377  	case addr.Mode != other.Mode:
   378  		return addr.Mode == config.DataResourceMode
   379  
   380  	case addr.Type != other.Type:
   381  		return addr.Type < other.Type
   382  
   383  	case addr.Name != other.Name:
   384  		return addr.Name < other.Name
   385  
   386  	case addr.Index != other.Index:
   387  		// Since "Index" is -1 for an un-indexed address, this also conveniently
   388  		// sorts unindexed addresses before indexed ones, should they both
   389  		// appear for some reason.
   390  		return addr.Index < other.Index
   391  
   392  	case addr.InstanceTypeSet != other.InstanceTypeSet:
   393  		return !addr.InstanceTypeSet
   394  
   395  	case addr.InstanceType != other.InstanceType:
   396  		// InstanceType is actually an enum, so this is just an arbitrary
   397  		// sort based on the enum numeric values, and thus not particularly
   398  		// meaningful.
   399  		return addr.InstanceType < other.InstanceType
   400  
   401  	default:
   402  		return false
   403  
   404  	}
   405  }
   406  
   407  func ParseResourceIndex(s string) (int, error) {
   408  	if s == "" {
   409  		return -1, nil
   410  	}
   411  	return strconv.Atoi(s)
   412  }
   413  
   414  func ParseResourcePath(s string) []string {
   415  	if s == "" {
   416  		return nil
   417  	}
   418  	parts := strings.Split(s, ".")
   419  	path := make([]string, 0, len(parts))
   420  	for _, s := range parts {
   421  		// Due to the limitations of the regexp match below, the path match has
   422  		// some noise in it we have to filter out :|
   423  		if s == "" || s == "module" {
   424  			continue
   425  		}
   426  		path = append(path, s)
   427  	}
   428  	return path
   429  }
   430  
   431  func ParseInstanceType(s string) (InstanceType, error) {
   432  	switch s {
   433  	case "", "primary":
   434  		return TypePrimary, nil
   435  	case "deposed":
   436  		return TypeDeposed, nil
   437  	case "tainted":
   438  		return TypeTainted, nil
   439  	default:
   440  		return TypeInvalid, fmt.Errorf("Unexpected value for InstanceType field: %q", s)
   441  	}
   442  }
   443  
   444  func tokenizeResourceAddress(s string) (map[string]string, error) {
   445  	// Example of portions of the regexp below using the
   446  	// string "aws_instance.web.tainted[1]"
   447  	re := regexp.MustCompile(`\A` +
   448  		// "module.foo.module.bar" (optional)
   449  		`(?P<path>(?:module\.(?P<module_name>[^.]+)\.?)*)` +
   450  		// possibly "data.", if targeting is a data resource
   451  		`(?P<data_prefix>(?:data\.)?)` +
   452  		// "aws_instance.web" (optional when module path specified)
   453  		`(?:(?P<type>[^.]+)\.(?P<name>[^.[]+))?` +
   454  		// "tainted" (optional, omission implies: "primary")
   455  		`(?:\.(?P<instance_type>\w+))?` +
   456  		// "1" (optional, omission implies: "0")
   457  		`(?:\[(?P<index>\d+)\])?` +
   458  		`\z`)
   459  
   460  	groupNames := re.SubexpNames()
   461  	rawMatches := re.FindAllStringSubmatch(s, -1)
   462  	if len(rawMatches) != 1 {
   463  		return nil, fmt.Errorf("invalid resource address %q", s)
   464  	}
   465  
   466  	matches := make(map[string]string)
   467  	for i, m := range rawMatches[0] {
   468  		matches[groupNames[i]] = m
   469  	}
   470  
   471  	return matches, nil
   472  }