github.com/opentofu/opentofu@v1.7.1/internal/legacy/tofu/resource.go (about)

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