github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/resource.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"sort"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/mitchellh/copystructure"
    11  	"github.com/mitchellh/reflectwalk"
    12  	"github.com/zclconf/go-cty/cty"
    13  
    14  	"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
    15  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs/configschema"
    16  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs/hcl2shim"
    17  )
    18  
    19  // Resource is a legacy way to identify a particular resource instance.
    20  //
    21  // New code should use addrs.ResourceInstance instead. This is still here
    22  // only for codepaths that haven't been updated yet.
    23  type Resource struct {
    24  	// These are all used by the new EvalNode stuff.
    25  	Name       string
    26  	Type       string
    27  	CountIndex int
    28  
    29  	// These aren't really used anymore anywhere, but we keep them around
    30  	// since we haven't done a proper cleanup yet.
    31  	Id           string
    32  	Info         *InstanceInfo
    33  	Config       *ResourceConfig
    34  	Dependencies []string
    35  	Diff         *InstanceDiff
    36  	Provider     ResourceProvider
    37  	State        *InstanceState
    38  	Flags        ResourceFlag
    39  }
    40  
    41  // NewResource constructs a legacy Resource object from an
    42  // addrs.ResourceInstance value.
    43  //
    44  // This is provided to shim to old codepaths that haven't been updated away
    45  // from this type yet. Since this old type is not able to represent instances
    46  // that have string keys, this function will panic if given a resource address
    47  // that has a string key.
    48  func NewResource(addr addrs.ResourceInstance) *Resource {
    49  	ret := &Resource{
    50  		Name: addr.Resource.Name,
    51  		Type: addr.Resource.Type,
    52  	}
    53  
    54  	if addr.Key != addrs.NoKey {
    55  		switch tk := addr.Key.(type) {
    56  		case addrs.IntKey:
    57  			ret.CountIndex = int(tk)
    58  		default:
    59  			panic(fmt.Errorf("resource instance with key %#v is not supported", addr.Key))
    60  		}
    61  	}
    62  
    63  	return ret
    64  }
    65  
    66  // ResourceKind specifies what kind of instance we're working with, whether
    67  // its a primary instance, a tainted instance, or an orphan.
    68  type ResourceFlag byte
    69  
    70  // InstanceInfo is used to hold information about the instance and/or
    71  // resource being modified.
    72  type InstanceInfo struct {
    73  	// Id is a unique name to represent this instance. This is not related
    74  	// to InstanceState.ID in any way.
    75  	Id string
    76  
    77  	// ModulePath is the complete path of the module containing this
    78  	// instance.
    79  	ModulePath []string
    80  
    81  	// Type is the resource type of this instance
    82  	Type string
    83  }
    84  
    85  // NewInstanceInfo constructs an InstanceInfo from an addrs.AbsResourceInstance.
    86  //
    87  // InstanceInfo is a legacy type, and uses of it should be gradually replaced
    88  // by direct use of addrs.AbsResource or addrs.AbsResourceInstance as
    89  // appropriate.
    90  //
    91  // The legacy InstanceInfo type cannot represent module instances with instance
    92  // keys, so this function will panic if given such a path. Uses of this type
    93  // should all be removed or replaced before implementing "count" and "for_each"
    94  // arguments on modules in order to avoid such panics.
    95  //
    96  // This legacy type also cannot represent resource instances with string
    97  // instance keys. It will panic if the given key is not either NoKey or an
    98  // IntKey.
    99  func NewInstanceInfo(addr addrs.AbsResourceInstance) *InstanceInfo {
   100  	// We need an old-style []string module path for InstanceInfo.
   101  	path := make([]string, len(addr.Module))
   102  	for i, step := range addr.Module {
   103  		if step.InstanceKey != addrs.NoKey {
   104  			panic("NewInstanceInfo cannot convert module instance with key")
   105  		}
   106  		path[i] = step.Name
   107  	}
   108  
   109  	// This is a funny old meaning of "id" that is no longer current. It should
   110  	// not be used for anything users might see. Note that it does not include
   111  	// a representation of the resource mode, and so it's impossible to
   112  	// determine from an InstanceInfo alone whether it is a managed or data
   113  	// resource that is being referred to.
   114  	id := fmt.Sprintf("%s.%s", addr.Resource.Resource.Type, addr.Resource.Resource.Name)
   115  	if addr.Resource.Resource.Mode == addrs.DataResourceMode {
   116  		id = "data." + id
   117  	}
   118  	if addr.Resource.Key != addrs.NoKey {
   119  		switch k := addr.Resource.Key.(type) {
   120  		case addrs.IntKey:
   121  			id = id + fmt.Sprintf(".%d", int(k))
   122  		default:
   123  			panic(fmt.Sprintf("NewInstanceInfo cannot convert resource instance with %T instance key", addr.Resource.Key))
   124  		}
   125  	}
   126  
   127  	return &InstanceInfo{
   128  		Id:         id,
   129  		ModulePath: path,
   130  		Type:       addr.Resource.Resource.Type,
   131  	}
   132  }
   133  
   134  // ResourceAddress returns the address of the resource that the receiver is describing.
   135  func (i *InstanceInfo) ResourceAddress() *ResourceAddress {
   136  	// GROSS: for tainted and deposed instances, their status gets appended
   137  	// to i.Id to create a unique id for the graph node. Historically these
   138  	// ids were displayed to the user, so it's designed to be human-readable:
   139  	//   "aws_instance.bar.0 (deposed #0)"
   140  	//
   141  	// So here we detect such suffixes and try to interpret them back to
   142  	// their original meaning so we can then produce a ResourceAddress
   143  	// with a suitable InstanceType.
   144  	id := i.Id
   145  	instanceType := TypeInvalid
   146  	if idx := strings.Index(id, " ("); idx != -1 {
   147  		remain := id[idx:]
   148  		id = id[:idx]
   149  
   150  		switch {
   151  		case strings.Contains(remain, "tainted"):
   152  			instanceType = TypeTainted
   153  		case strings.Contains(remain, "deposed"):
   154  			instanceType = TypeDeposed
   155  		}
   156  	}
   157  
   158  	addr, err := parseResourceAddressInternal(id)
   159  	if err != nil {
   160  		// should never happen, since that would indicate a bug in the
   161  		// code that constructed this InstanceInfo.
   162  		panic(fmt.Errorf("InstanceInfo has invalid Id %s", id))
   163  	}
   164  	if len(i.ModulePath) > 1 {
   165  		addr.Path = i.ModulePath[1:] // trim off "root" prefix, which is implied
   166  	}
   167  	if instanceType != TypeInvalid {
   168  		addr.InstanceTypeSet = true
   169  		addr.InstanceType = instanceType
   170  	}
   171  	return addr
   172  }
   173  
   174  // ResourceConfig is a legacy type that was formerly used to represent
   175  // interpolatable configuration blocks. It is now only used to shim to old
   176  // APIs that still use this type, via NewResourceConfigShimmed.
   177  type ResourceConfig struct {
   178  	ComputedKeys []string
   179  	Raw          map[string]interface{}
   180  	Config       map[string]interface{}
   181  }
   182  
   183  // NewResourceConfigRaw constructs a ResourceConfig whose content is exactly
   184  // the given value.
   185  //
   186  // The given value may contain hcl2shim.UnknownVariableValue to signal that
   187  // something is computed, but it must not contain unprocessed interpolation
   188  // sequences as we might've seen in Terraform v0.11 and prior.
   189  func NewResourceConfigRaw(raw map[string]interface{}) *ResourceConfig {
   190  	v := hcl2shim.HCL2ValueFromConfigValue(raw)
   191  
   192  	// This is a little weird but we round-trip the value through the hcl2shim
   193  	// package here for two reasons: firstly, because that reduces the risk
   194  	// of it including something unlike what NewResourceConfigShimmed would
   195  	// produce, and secondly because it creates a copy of "raw" just in case
   196  	// something is relying on the fact that in the old world the raw and
   197  	// config maps were always distinct, and thus you could in principle mutate
   198  	// one without affecting the other. (I sure hope nobody was doing that, though!)
   199  	cfg := hcl2shim.ConfigValueFromHCL2(v).(map[string]interface{})
   200  
   201  	return &ResourceConfig{
   202  		Raw:    raw,
   203  		Config: cfg,
   204  
   205  		ComputedKeys: newResourceConfigShimmedComputedKeys(v, ""),
   206  	}
   207  }
   208  
   209  // NewResourceConfigShimmed wraps a cty.Value of object type in a legacy
   210  // ResourceConfig object, so that it can be passed to older APIs that expect
   211  // this wrapping.
   212  //
   213  // The returned ResourceConfig is already interpolated and cannot be
   214  // re-interpolated. It is, therefore, useful only to functions that expect
   215  // an already-populated ResourceConfig which they then treat as read-only.
   216  //
   217  // If the given value is not of an object type that conforms to the given
   218  // schema then this function will panic.
   219  func NewResourceConfigShimmed(val cty.Value, schema *configschema.Block) *ResourceConfig {
   220  	if !val.Type().IsObjectType() {
   221  		panic(fmt.Errorf("NewResourceConfigShimmed given %#v; an object type is required", val.Type()))
   222  	}
   223  	ret := &ResourceConfig{}
   224  
   225  	legacyVal := hcl2shim.ConfigValueFromHCL2Block(val, schema)
   226  	if legacyVal != nil {
   227  		ret.Config = legacyVal
   228  
   229  		// Now we need to walk through our structure and find any unknown values,
   230  		// producing the separate list ComputedKeys to represent these. We use the
   231  		// schema here so that we can preserve the expected invariant
   232  		// that an attribute is always either wholly known or wholly unknown, while
   233  		// a child block can be partially unknown.
   234  		ret.ComputedKeys = newResourceConfigShimmedComputedKeys(val, "")
   235  	} else {
   236  		ret.Config = make(map[string]interface{})
   237  	}
   238  	ret.Raw = ret.Config
   239  
   240  	return ret
   241  }
   242  
   243  // Record the any config values in ComputedKeys. This field had been unused in
   244  // helper/schema, but in the new protocol we're using this so that the SDK can
   245  // now handle having an unknown collection. The legacy diff code doesn't
   246  // properly handle the unknown, because it can't be expressed in the same way
   247  // between the config and diff.
   248  func newResourceConfigShimmedComputedKeys(val cty.Value, path string) []string {
   249  	var ret []string
   250  	ty := val.Type()
   251  
   252  	if val.IsNull() {
   253  		return ret
   254  	}
   255  
   256  	if !val.IsKnown() {
   257  		// we shouldn't have an entirely unknown resource, but prevent empty
   258  		// strings just in case
   259  		if len(path) > 0 {
   260  			ret = append(ret, path)
   261  		}
   262  		return ret
   263  	}
   264  
   265  	if path != "" {
   266  		path += "."
   267  	}
   268  	switch {
   269  	case ty.IsListType(), ty.IsTupleType(), ty.IsSetType():
   270  		i := 0
   271  		for it := val.ElementIterator(); it.Next(); i++ {
   272  			_, subVal := it.Element()
   273  			keys := newResourceConfigShimmedComputedKeys(subVal, fmt.Sprintf("%s%d", path, i))
   274  			ret = append(ret, keys...)
   275  		}
   276  
   277  	case ty.IsMapType(), ty.IsObjectType():
   278  		for it := val.ElementIterator(); it.Next(); {
   279  			subK, subVal := it.Element()
   280  			keys := newResourceConfigShimmedComputedKeys(subVal, fmt.Sprintf("%s%s", path, subK.AsString()))
   281  			ret = append(ret, keys...)
   282  		}
   283  	}
   284  
   285  	return ret
   286  }
   287  
   288  // DeepCopy performs a deep copy of the configuration. This makes it safe
   289  // to modify any of the structures that are part of the resource config without
   290  // affecting the original configuration.
   291  func (c *ResourceConfig) DeepCopy() *ResourceConfig {
   292  	// DeepCopying a nil should return a nil to avoid panics
   293  	if c == nil {
   294  		return nil
   295  	}
   296  
   297  	// Copy, this will copy all the exported attributes
   298  	copy, err := copystructure.Config{Lock: true}.Copy(c)
   299  	if err != nil {
   300  		panic(err)
   301  	}
   302  
   303  	// Force the type
   304  	result := copy.(*ResourceConfig)
   305  
   306  	return result
   307  }
   308  
   309  // Equal checks the equality of two resource configs.
   310  func (c *ResourceConfig) Equal(c2 *ResourceConfig) bool {
   311  	// If either are nil, then they're only equal if they're both nil
   312  	if c == nil || c2 == nil {
   313  		return c == c2
   314  	}
   315  
   316  	// Sort the computed keys so they're deterministic
   317  	sort.Strings(c.ComputedKeys)
   318  	sort.Strings(c2.ComputedKeys)
   319  
   320  	// Two resource configs if their exported properties are equal.
   321  	// We don't compare "raw" because it is never used again after
   322  	// initialization and for all intents and purposes they are equal
   323  	// if the exported properties are equal.
   324  	check := [][2]interface{}{
   325  		{c.ComputedKeys, c2.ComputedKeys},
   326  		{c.Raw, c2.Raw},
   327  		{c.Config, c2.Config},
   328  	}
   329  	for _, pair := range check {
   330  		if !reflect.DeepEqual(pair[0], pair[1]) {
   331  			return false
   332  		}
   333  	}
   334  
   335  	return true
   336  }
   337  
   338  // CheckSet checks that the given list of configuration keys is
   339  // properly set. If not, errors are returned for each unset key.
   340  //
   341  // This is useful to be called in the Validate method of a ResourceProvider.
   342  func (c *ResourceConfig) CheckSet(keys []string) []error {
   343  	var errs []error
   344  
   345  	for _, k := range keys {
   346  		if !c.IsSet(k) {
   347  			errs = append(errs, fmt.Errorf("%s must be set", k))
   348  		}
   349  	}
   350  
   351  	return errs
   352  }
   353  
   354  // Get looks up a configuration value by key and returns the value.
   355  //
   356  // The second return value is true if the get was successful. Get will
   357  // return the raw value if the key is computed, so you should pair this
   358  // with IsComputed.
   359  func (c *ResourceConfig) Get(k string) (interface{}, bool) {
   360  	// We aim to get a value from the configuration. If it is computed,
   361  	// then we return the pure raw value.
   362  	source := c.Config
   363  	if c.IsComputed(k) {
   364  		source = c.Raw
   365  	}
   366  
   367  	return c.get(k, source)
   368  }
   369  
   370  // GetRaw looks up a configuration value by key and returns the value,
   371  // from the raw, uninterpolated config.
   372  //
   373  // The second return value is true if the get was successful. Get will
   374  // not succeed if the value is being computed.
   375  func (c *ResourceConfig) GetRaw(k string) (interface{}, bool) {
   376  	return c.get(k, c.Raw)
   377  }
   378  
   379  // IsComputed returns whether the given key is computed or not.
   380  func (c *ResourceConfig) IsComputed(k string) bool {
   381  	// The next thing we do is check the config if we get a computed
   382  	// value out of it.
   383  	v, ok := c.get(k, c.Config)
   384  	if !ok {
   385  		return false
   386  	}
   387  
   388  	// If value is nil, then it isn't computed
   389  	if v == nil {
   390  		return false
   391  	}
   392  
   393  	// Test if the value contains an unknown value
   394  	var w unknownCheckWalker
   395  	if err := reflectwalk.Walk(v, &w); err != nil {
   396  		panic(err)
   397  	}
   398  
   399  	return w.Unknown
   400  }
   401  
   402  // IsSet checks if the key in the configuration is set. A key is set if
   403  // it has a value or the value is being computed (is unknown currently).
   404  //
   405  // This function should be used rather than checking the keys of the
   406  // raw configuration itself, since a key may be omitted from the raw
   407  // configuration if it is being computed.
   408  func (c *ResourceConfig) IsSet(k string) bool {
   409  	if c == nil {
   410  		return false
   411  	}
   412  
   413  	if c.IsComputed(k) {
   414  		return true
   415  	}
   416  
   417  	if _, ok := c.Get(k); ok {
   418  		return true
   419  	}
   420  
   421  	return false
   422  }
   423  
   424  func (c *ResourceConfig) get(
   425  	k string, raw map[string]interface{}) (interface{}, bool) {
   426  	parts := strings.Split(k, ".")
   427  	if len(parts) == 1 && parts[0] == "" {
   428  		parts = nil
   429  	}
   430  
   431  	var current interface{} = raw
   432  	var previous interface{} = nil
   433  	for i, part := range parts {
   434  		if current == nil {
   435  			return nil, false
   436  		}
   437  
   438  		cv := reflect.ValueOf(current)
   439  		switch cv.Kind() {
   440  		case reflect.Map:
   441  			previous = current
   442  			v := cv.MapIndex(reflect.ValueOf(part))
   443  			if !v.IsValid() {
   444  				if i > 0 && i != (len(parts)-1) {
   445  					tryKey := strings.Join(parts[i:], ".")
   446  					v := cv.MapIndex(reflect.ValueOf(tryKey))
   447  					if !v.IsValid() {
   448  						return nil, false
   449  					}
   450  
   451  					return v.Interface(), true
   452  				}
   453  
   454  				return nil, false
   455  			}
   456  
   457  			current = v.Interface()
   458  		case reflect.Slice:
   459  			previous = current
   460  
   461  			if part == "#" {
   462  				// If any value in a list is computed, this whole thing
   463  				// is computed and we can't read any part of it.
   464  				for i := 0; i < cv.Len(); i++ {
   465  					if v := cv.Index(i).Interface(); v == hcl2shim.UnknownVariableValue {
   466  						return v, true
   467  					}
   468  				}
   469  
   470  				current = cv.Len()
   471  			} else {
   472  				i, err := strconv.ParseInt(part, 0, 0)
   473  				if err != nil {
   474  					return nil, false
   475  				}
   476  				if int(i) < 0 || int(i) >= cv.Len() {
   477  					return nil, false
   478  				}
   479  				current = cv.Index(int(i)).Interface()
   480  			}
   481  		case reflect.String:
   482  			// This happens when map keys contain "." and have a common
   483  			// prefix so were split as path components above.
   484  			actualKey := strings.Join(parts[i-1:], ".")
   485  			if prevMap, ok := previous.(map[string]interface{}); ok {
   486  				v, ok := prevMap[actualKey]
   487  				return v, ok
   488  			}
   489  
   490  			return nil, false
   491  		default:
   492  			panic(fmt.Sprintf("Unknown kind: %s", cv.Kind()))
   493  		}
   494  	}
   495  
   496  	return current, true
   497  }
   498  
   499  // unknownCheckWalker
   500  type unknownCheckWalker struct {
   501  	Unknown bool
   502  }
   503  
   504  func (w *unknownCheckWalker) Primitive(v reflect.Value) error {
   505  	if v.Interface() == hcl2shim.UnknownVariableValue {
   506  		w.Unknown = true
   507  	}
   508  
   509  	return nil
   510  }