github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/legacy/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/internal/addrs"
    11  	"github.com/hashicorp/terraform/internal/configs"
    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            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 ManagedResourceMode:
    60  		// nothing to do
    61  	case 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  //
    96  //	https://www.terraform.io/docs/cli/state/resource-addressing.html
    97  //
    98  // In particular, this returns false if the address contains only
    99  // a module path, thus addressing the entire module.
   100  func (r *ResourceAddress) HasResourceSpec() bool {
   101  	return r.Type != "" && r.Name != ""
   102  }
   103  
   104  // WholeModuleAddress returns the resource address that refers to all
   105  // resources in the same module as the receiver address.
   106  func (r *ResourceAddress) WholeModuleAddress() *ResourceAddress {
   107  	return &ResourceAddress{
   108  		Path:            r.Path,
   109  		Index:           -1,
   110  		InstanceTypeSet: false,
   111  	}
   112  }
   113  
   114  // MatchesResourceConfig returns true if the receiver matches the given
   115  // configuration resource within the given _static_ module path. Note that
   116  // the module path in a resource address is a _dynamic_ module path, and
   117  // multiple dynamic resource paths may map to a single static path if
   118  // count and for_each are in use on module calls.
   119  //
   120  // Since resource configuration blocks represent all of the instances of
   121  // a multi-instance resource, the index of the address (if any) is not
   122  // considered.
   123  func (r *ResourceAddress) MatchesResourceConfig(path addrs.Module, rc *configs.Resource) bool {
   124  	if r.HasResourceSpec() {
   125  		// FIXME: Some ugliness while we are between worlds. Functionality
   126  		// in "addrs" should eventually replace this ResourceAddress idea
   127  		// completely, but for now we'll need to translate to the old
   128  		// way of representing resource modes.
   129  		switch r.Mode {
   130  		case ManagedResourceMode:
   131  			if rc.Mode != addrs.ManagedResourceMode {
   132  				return false
   133  			}
   134  		case DataResourceMode:
   135  			if rc.Mode != addrs.DataResourceMode {
   136  				return false
   137  			}
   138  		}
   139  		if r.Type != rc.Type || r.Name != rc.Name {
   140  			return false
   141  		}
   142  	}
   143  
   144  	addrPath := r.Path
   145  
   146  	// normalize
   147  	if len(addrPath) == 0 {
   148  		addrPath = nil
   149  	}
   150  	if len(path) == 0 {
   151  		path = nil
   152  	}
   153  	rawPath := []string(path)
   154  	return reflect.DeepEqual(addrPath, rawPath)
   155  }
   156  
   157  // stateId returns the ID that this resource should be entered with
   158  // in the state. This is also used for diffs. In the future, we'd like to
   159  // move away from this string field so I don't export this.
   160  func (r *ResourceAddress) stateId() string {
   161  	result := fmt.Sprintf("%s.%s", r.Type, r.Name)
   162  	switch r.Mode {
   163  	case ManagedResourceMode:
   164  		// Done
   165  	case DataResourceMode:
   166  		result = fmt.Sprintf("data.%s", result)
   167  	default:
   168  		panic(fmt.Errorf("unknown resource mode: %s", r.Mode))
   169  	}
   170  	if r.Index >= 0 {
   171  		result += fmt.Sprintf(".%d", r.Index)
   172  	}
   173  
   174  	return result
   175  }
   176  
   177  // parseResourceAddressInternal parses the somewhat bespoke resource
   178  // identifier used in states and diffs, such as "instance.name.0".
   179  func parseResourceAddressInternal(s string) (*ResourceAddress, error) {
   180  	// Split based on ".". Every resource address should have at least two
   181  	// elements (type and name).
   182  	parts := strings.Split(s, ".")
   183  	if len(parts) < 2 || len(parts) > 4 {
   184  		return nil, fmt.Errorf("Invalid internal resource address format: %s", s)
   185  	}
   186  
   187  	// Data resource if we have at least 3 parts and the first one is data
   188  	mode := ManagedResourceMode
   189  	if len(parts) > 2 && parts[0] == "data" {
   190  		mode = DataResourceMode
   191  		parts = parts[1:]
   192  	}
   193  
   194  	// If we're not a data resource and we have more than 3, then it is an error
   195  	if len(parts) > 3 && mode != DataResourceMode {
   196  		return nil, fmt.Errorf("Invalid internal resource address format: %s", s)
   197  	}
   198  
   199  	// Build the parts of the resource address that are guaranteed to exist
   200  	addr := &ResourceAddress{
   201  		Type:         parts[0],
   202  		Name:         parts[1],
   203  		Index:        -1,
   204  		InstanceType: TypePrimary,
   205  		Mode:         mode,
   206  	}
   207  
   208  	// If we have more parts, then we have an index. Parse that.
   209  	if len(parts) > 2 {
   210  		idx, err := strconv.ParseInt(parts[2], 0, 0)
   211  		if err != nil {
   212  			return nil, fmt.Errorf("Error parsing resource address %q: %s", s, err)
   213  		}
   214  
   215  		addr.Index = int(idx)
   216  	}
   217  
   218  	return addr, nil
   219  }
   220  
   221  func ParseResourceAddress(s string) (*ResourceAddress, error) {
   222  	matches, err := tokenizeResourceAddress(s)
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  	mode := ManagedResourceMode
   227  	if matches["data_prefix"] != "" {
   228  		mode = DataResourceMode
   229  	}
   230  	resourceIndex, err := ParseResourceIndex(matches["index"])
   231  	if err != nil {
   232  		return nil, err
   233  	}
   234  	instanceType, err := ParseInstanceType(matches["instance_type"])
   235  	if err != nil {
   236  		return nil, err
   237  	}
   238  	path := ParseResourcePath(matches["path"])
   239  
   240  	// not allowed to say "data." without a type following
   241  	if mode == DataResourceMode && matches["type"] == "" {
   242  		return nil, fmt.Errorf(
   243  			"invalid resource address %q: must target specific data instance",
   244  			s,
   245  		)
   246  	}
   247  
   248  	return &ResourceAddress{
   249  		Path:            path,
   250  		Index:           resourceIndex,
   251  		InstanceType:    instanceType,
   252  		InstanceTypeSet: matches["instance_type"] != "",
   253  		Name:            matches["name"],
   254  		Type:            matches["type"],
   255  		Mode:            mode,
   256  	}, nil
   257  }
   258  
   259  // ParseResourceAddressForInstanceDiff creates a ResourceAddress for a
   260  // resource name as described in a module diff.
   261  //
   262  // For historical reasons a different addressing format is used in this
   263  // context. The internal format should not be shown in the UI and instead
   264  // this function should be used to translate to a ResourceAddress and
   265  // then, where appropriate, use the String method to produce a canonical
   266  // resource address string for display in the UI.
   267  //
   268  // The given path slice must be empty (or nil) for the root module, and
   269  // otherwise consist of a sequence of module names traversing down into
   270  // the module tree. If a non-nil path is provided, the caller must not
   271  // modify its underlying array after passing it to this function.
   272  func ParseResourceAddressForInstanceDiff(path []string, key string) (*ResourceAddress, error) {
   273  	addr, err := parseResourceAddressInternal(key)
   274  	if err != nil {
   275  		return nil, err
   276  	}
   277  	addr.Path = path
   278  	return addr, nil
   279  }
   280  
   281  // NewLegacyResourceAddress creates a ResourceAddress from a new-style
   282  // addrs.AbsResource value.
   283  //
   284  // This is provided for shimming purposes so that we can still easily call into
   285  // older functions that expect the ResourceAddress type.
   286  func NewLegacyResourceAddress(addr addrs.AbsResource) *ResourceAddress {
   287  	ret := &ResourceAddress{
   288  		Type: addr.Resource.Type,
   289  		Name: addr.Resource.Name,
   290  	}
   291  
   292  	switch addr.Resource.Mode {
   293  	case addrs.ManagedResourceMode:
   294  		ret.Mode = ManagedResourceMode
   295  	case addrs.DataResourceMode:
   296  		ret.Mode = DataResourceMode
   297  	default:
   298  		panic(fmt.Errorf("cannot shim %s to legacy ResourceMode value", addr.Resource.Mode))
   299  	}
   300  
   301  	path := make([]string, len(addr.Module))
   302  	for i, step := range addr.Module {
   303  		if step.InstanceKey != addrs.NoKey {
   304  			// At the time of writing this can't happen because we don't
   305  			// ket generate keyed module instances. This legacy codepath must
   306  			// be removed before we can support "count" and "for_each" for
   307  			// modules.
   308  			panic(fmt.Errorf("cannot shim module instance step with key %#v to legacy ResourceAddress.Path", step.InstanceKey))
   309  		}
   310  
   311  		path[i] = step.Name
   312  	}
   313  	ret.Path = path
   314  	ret.Index = -1
   315  
   316  	return ret
   317  }
   318  
   319  // NewLegacyResourceInstanceAddress creates a ResourceAddress from a new-style
   320  // addrs.AbsResource value.
   321  //
   322  // This is provided for shimming purposes so that we can still easily call into
   323  // older functions that expect the ResourceAddress type.
   324  func NewLegacyResourceInstanceAddress(addr addrs.AbsResourceInstance) *ResourceAddress {
   325  	ret := &ResourceAddress{
   326  		Type: addr.Resource.Resource.Type,
   327  		Name: addr.Resource.Resource.Name,
   328  	}
   329  
   330  	switch addr.Resource.Resource.Mode {
   331  	case addrs.ManagedResourceMode:
   332  		ret.Mode = ManagedResourceMode
   333  	case addrs.DataResourceMode:
   334  		ret.Mode = DataResourceMode
   335  	default:
   336  		panic(fmt.Errorf("cannot shim %s to legacy ResourceMode value", addr.Resource.Resource.Mode))
   337  	}
   338  
   339  	path := make([]string, len(addr.Module))
   340  	for i, step := range addr.Module {
   341  		if step.InstanceKey != addrs.NoKey {
   342  			// At the time of writing this can't happen because we don't
   343  			// ket generate keyed module instances. This legacy codepath must
   344  			// be removed before we can support "count" and "for_each" for
   345  			// modules.
   346  			panic(fmt.Errorf("cannot shim module instance step with key %#v to legacy ResourceAddress.Path", step.InstanceKey))
   347  		}
   348  
   349  		path[i] = step.Name
   350  	}
   351  	ret.Path = path
   352  
   353  	if addr.Resource.Key == addrs.NoKey {
   354  		ret.Index = -1
   355  	} else if ik, ok := addr.Resource.Key.(addrs.IntKey); ok {
   356  		ret.Index = int(ik)
   357  	} else if _, ok := addr.Resource.Key.(addrs.StringKey); ok {
   358  		ret.Index = -1
   359  	} else {
   360  		panic(fmt.Errorf("cannot shim resource instance with key %#v to legacy ResourceAddress.Index", addr.Resource.Key))
   361  	}
   362  
   363  	return ret
   364  }
   365  
   366  // AbsResourceInstanceAddr converts the receiver, a legacy resource address, to
   367  // the new resource address type addrs.AbsResourceInstance.
   368  //
   369  // This method can be used only on an address that has a resource specification.
   370  // It will panic if called on a module-path-only ResourceAddress. Use
   371  // method HasResourceSpec to check before calling, in contexts where it is
   372  // unclear.
   373  //
   374  // addrs.AbsResourceInstance does not represent the "tainted" and "deposed"
   375  // states, and so if these are present on the receiver then they are discarded.
   376  //
   377  // This is provided for shimming purposes so that we can easily adapt functions
   378  // that are returning the legacy ResourceAddress type, for situations where
   379  // the new type is required.
   380  func (addr *ResourceAddress) AbsResourceInstanceAddr() addrs.AbsResourceInstance {
   381  	if !addr.HasResourceSpec() {
   382  		panic("AbsResourceInstanceAddr called on ResourceAddress with no resource spec")
   383  	}
   384  
   385  	ret := addrs.AbsResourceInstance{
   386  		Module: addr.ModuleInstanceAddr(),
   387  		Resource: addrs.ResourceInstance{
   388  			Resource: addrs.Resource{
   389  				Type: addr.Type,
   390  				Name: addr.Name,
   391  			},
   392  		},
   393  	}
   394  
   395  	switch addr.Mode {
   396  	case ManagedResourceMode:
   397  		ret.Resource.Resource.Mode = addrs.ManagedResourceMode
   398  	case DataResourceMode:
   399  		ret.Resource.Resource.Mode = addrs.DataResourceMode
   400  	default:
   401  		panic(fmt.Errorf("cannot shim %s to addrs.ResourceMode value", addr.Mode))
   402  	}
   403  
   404  	if addr.Index != -1 {
   405  		ret.Resource.Key = addrs.IntKey(addr.Index)
   406  	}
   407  
   408  	return ret
   409  }
   410  
   411  // ModuleInstanceAddr returns the module path portion of the receiver as a
   412  // addrs.ModuleInstance value.
   413  func (addr *ResourceAddress) ModuleInstanceAddr() addrs.ModuleInstance {
   414  	path := make(addrs.ModuleInstance, len(addr.Path))
   415  	for i, name := range addr.Path {
   416  		path[i] = addrs.ModuleInstanceStep{Name: name}
   417  	}
   418  	return path
   419  }
   420  
   421  // Contains returns true if and only if the given node is contained within
   422  // the receiver.
   423  //
   424  // Containment is defined in terms of the module and resource hierarchy:
   425  // a resource is contained within its module and any ancestor modules,
   426  // an indexed resource instance is contained with the unindexed resource, etc.
   427  func (addr *ResourceAddress) Contains(other *ResourceAddress) bool {
   428  	ourPath := addr.Path
   429  	givenPath := other.Path
   430  	if len(givenPath) < len(ourPath) {
   431  		return false
   432  	}
   433  	for i := range ourPath {
   434  		if ourPath[i] != givenPath[i] {
   435  			return false
   436  		}
   437  	}
   438  
   439  	// If the receiver is a whole-module address then the path prefix
   440  	// matching is all we need.
   441  	if !addr.HasResourceSpec() {
   442  		return true
   443  	}
   444  
   445  	if addr.Type != other.Type || addr.Name != other.Name || addr.Mode != other.Mode {
   446  		return false
   447  	}
   448  
   449  	if addr.Index != -1 && addr.Index != other.Index {
   450  		return false
   451  	}
   452  
   453  	if addr.InstanceTypeSet && (addr.InstanceTypeSet != other.InstanceTypeSet || addr.InstanceType != other.InstanceType) {
   454  		return false
   455  	}
   456  
   457  	return true
   458  }
   459  
   460  // Equals returns true if the receiver matches the given address.
   461  //
   462  // The name of this method is a misnomer, since it doesn't test for exact
   463  // equality. Instead, it tests that the _specified_ parts of each
   464  // address match, treating any unspecified parts as wildcards.
   465  //
   466  // See also Contains, which takes a more hierarchical approach to comparing
   467  // addresses.
   468  func (addr *ResourceAddress) Equals(raw interface{}) bool {
   469  	other, ok := raw.(*ResourceAddress)
   470  	if !ok {
   471  		return false
   472  	}
   473  
   474  	pathMatch := len(addr.Path) == 0 && len(other.Path) == 0 ||
   475  		reflect.DeepEqual(addr.Path, other.Path)
   476  
   477  	indexMatch := addr.Index == -1 ||
   478  		other.Index == -1 ||
   479  		addr.Index == other.Index
   480  
   481  	nameMatch := addr.Name == "" ||
   482  		other.Name == "" ||
   483  		addr.Name == other.Name
   484  
   485  	typeMatch := addr.Type == "" ||
   486  		other.Type == "" ||
   487  		addr.Type == other.Type
   488  
   489  	// mode is significant only when type is set
   490  	modeMatch := addr.Type == "" ||
   491  		other.Type == "" ||
   492  		addr.Mode == other.Mode
   493  
   494  	return pathMatch &&
   495  		indexMatch &&
   496  		addr.InstanceType == other.InstanceType &&
   497  		nameMatch &&
   498  		typeMatch &&
   499  		modeMatch
   500  }
   501  
   502  // Less returns true if and only if the receiver should be sorted before
   503  // the given address when presenting a list of resource addresses to
   504  // an end-user.
   505  //
   506  // This sort uses lexicographic sorting for most components, but uses
   507  // numeric sort for indices, thus causing index 10 to sort after
   508  // index 9, rather than after index 1.
   509  func (addr *ResourceAddress) Less(other *ResourceAddress) bool {
   510  
   511  	switch {
   512  
   513  	case len(addr.Path) != len(other.Path):
   514  		return len(addr.Path) < len(other.Path)
   515  
   516  	case !reflect.DeepEqual(addr.Path, other.Path):
   517  		// If the two paths are the same length but don't match, we'll just
   518  		// cheat and compare the string forms since it's easier than
   519  		// comparing all of the path segments in turn, and lexicographic
   520  		// comparison is correct for the module path portion.
   521  		addrStr := addr.String()
   522  		otherStr := other.String()
   523  		return addrStr < otherStr
   524  
   525  	case addr.Mode != other.Mode:
   526  		return addr.Mode == DataResourceMode
   527  
   528  	case addr.Type != other.Type:
   529  		return addr.Type < other.Type
   530  
   531  	case addr.Name != other.Name:
   532  		return addr.Name < other.Name
   533  
   534  	case addr.Index != other.Index:
   535  		// Since "Index" is -1 for an un-indexed address, this also conveniently
   536  		// sorts unindexed addresses before indexed ones, should they both
   537  		// appear for some reason.
   538  		return addr.Index < other.Index
   539  
   540  	case addr.InstanceTypeSet != other.InstanceTypeSet:
   541  		return !addr.InstanceTypeSet
   542  
   543  	case addr.InstanceType != other.InstanceType:
   544  		// InstanceType is actually an enum, so this is just an arbitrary
   545  		// sort based on the enum numeric values, and thus not particularly
   546  		// meaningful.
   547  		return addr.InstanceType < other.InstanceType
   548  
   549  	default:
   550  		return false
   551  
   552  	}
   553  }
   554  
   555  func ParseResourceIndex(s string) (int, error) {
   556  	if s == "" {
   557  		return -1, nil
   558  	}
   559  	return strconv.Atoi(s)
   560  }
   561  
   562  func ParseResourcePath(s string) []string {
   563  	if s == "" {
   564  		return nil
   565  	}
   566  	parts := strings.Split(s, ".")
   567  	path := make([]string, 0, len(parts))
   568  	for _, s := range parts {
   569  		// Due to the limitations of the regexp match below, the path match has
   570  		// some noise in it we have to filter out :|
   571  		if s == "" || s == "module" {
   572  			continue
   573  		}
   574  		path = append(path, s)
   575  	}
   576  	return path
   577  }
   578  
   579  func ParseInstanceType(s string) (InstanceType, error) {
   580  	switch s {
   581  	case "", "primary":
   582  		return TypePrimary, nil
   583  	case "deposed":
   584  		return TypeDeposed, nil
   585  	case "tainted":
   586  		return TypeTainted, nil
   587  	default:
   588  		return TypeInvalid, fmt.Errorf("Unexpected value for InstanceType field: %q", s)
   589  	}
   590  }
   591  
   592  func tokenizeResourceAddress(s string) (map[string]string, error) {
   593  	// Example of portions of the regexp below using the
   594  	// string "aws_instance.web.tainted[1]"
   595  	re := regexp.MustCompile(`\A` +
   596  		// "module.foo.module.bar" (optional)
   597  		`(?P<path>(?:module\.(?P<module_name>[^.]+)\.?)*)` +
   598  		// possibly "data.", if targeting is a data resource
   599  		`(?P<data_prefix>(?:data\.)?)` +
   600  		// "aws_instance.web" (optional when module path specified)
   601  		`(?:(?P<type>[^.]+)\.(?P<name>[^.[]+))?` +
   602  		// "tainted" (optional, omission implies: "primary")
   603  		`(?:\.(?P<instance_type>\w+))?` +
   604  		// "1" (optional, omission implies: "0")
   605  		`(?:\[(?P<index>\d+)\])?` +
   606  		`\z`)
   607  
   608  	groupNames := re.SubexpNames()
   609  	rawMatches := re.FindAllStringSubmatch(s, -1)
   610  	if len(rawMatches) != 1 {
   611  		return nil, fmt.Errorf("invalid resource address %q", s)
   612  	}
   613  
   614  	matches := make(map[string]string)
   615  	for i, m := range rawMatches[0] {
   616  		matches[groupNames[i]] = m
   617  	}
   618  
   619  	return matches, nil
   620  }