github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/legacy/helper/schema/resource_diff.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package schema
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"reflect"
    10  	"strings"
    11  	"sync"
    12  
    13  	"github.com/terramate-io/tf/legacy/terraform"
    14  )
    15  
    16  // newValueWriter is a minor re-implementation of MapFieldWriter to include
    17  // keys that should be marked as computed, to represent the new part of a
    18  // pseudo-diff.
    19  type newValueWriter struct {
    20  	*MapFieldWriter
    21  
    22  	// A list of keys that should be marked as computed.
    23  	computedKeys map[string]bool
    24  
    25  	// A lock to prevent races on writes. The underlying writer will have one as
    26  	// well - this is for computed keys.
    27  	lock sync.Mutex
    28  
    29  	// To be used with init.
    30  	once sync.Once
    31  }
    32  
    33  // init performs any initialization tasks for the newValueWriter.
    34  func (w *newValueWriter) init() {
    35  	if w.computedKeys == nil {
    36  		w.computedKeys = make(map[string]bool)
    37  	}
    38  }
    39  
    40  // WriteField overrides MapValueWriter's WriteField, adding the ability to flag
    41  // the address as computed.
    42  func (w *newValueWriter) WriteField(address []string, value interface{}, computed bool) error {
    43  	// Fail the write if we have a non-nil value and computed is true.
    44  	// NewComputed values should not have a value when written.
    45  	if value != nil && computed {
    46  		return errors.New("Non-nil value with computed set")
    47  	}
    48  
    49  	if err := w.MapFieldWriter.WriteField(address, value); err != nil {
    50  		return err
    51  	}
    52  
    53  	w.once.Do(w.init)
    54  
    55  	w.lock.Lock()
    56  	defer w.lock.Unlock()
    57  	if computed {
    58  		w.computedKeys[strings.Join(address, ".")] = true
    59  	}
    60  	return nil
    61  }
    62  
    63  // ComputedKeysMap returns the underlying computed keys map.
    64  func (w *newValueWriter) ComputedKeysMap() map[string]bool {
    65  	w.once.Do(w.init)
    66  	return w.computedKeys
    67  }
    68  
    69  // newValueReader is a minor re-implementation of MapFieldReader and is the
    70  // read counterpart to MapValueWriter, allowing the read of keys flagged as
    71  // computed to accommodate the diff override logic in ResourceDiff.
    72  type newValueReader struct {
    73  	*MapFieldReader
    74  
    75  	// The list of computed keys from a newValueWriter.
    76  	computedKeys map[string]bool
    77  }
    78  
    79  // ReadField reads the values from the underlying writer, returning the
    80  // computed value if it is found as well.
    81  func (r *newValueReader) ReadField(address []string) (FieldReadResult, error) {
    82  	addrKey := strings.Join(address, ".")
    83  	v, err := r.MapFieldReader.ReadField(address)
    84  	if err != nil {
    85  		return FieldReadResult{}, err
    86  	}
    87  	for computedKey := range r.computedKeys {
    88  		if childAddrOf(addrKey, computedKey) {
    89  			if strings.HasSuffix(addrKey, ".#") {
    90  				// This is a count value for a list or set that has been marked as
    91  				// computed, or a sub-list/sub-set of a complex resource that has
    92  				// been marked as computed.  We need to pass through to other readers
    93  				// so that an accurate previous count can be fetched for the diff.
    94  				v.Exists = false
    95  			}
    96  			v.Computed = true
    97  		}
    98  	}
    99  
   100  	return v, nil
   101  }
   102  
   103  // ResourceDiff is used to query and make custom changes to an in-flight diff.
   104  // It can be used to veto particular changes in the diff, customize the diff
   105  // that has been created, or diff values not controlled by config.
   106  //
   107  // The object functions similar to ResourceData, however most notably lacks
   108  // Set, SetPartial, and Partial, as it should be used to change diff values
   109  // only.  Most other first-class ResourceData functions exist, namely Get,
   110  // GetOk, HasChange, and GetChange exist.
   111  //
   112  // All functions in ResourceDiff, save for ForceNew, can only be used on
   113  // computed fields.
   114  type ResourceDiff struct {
   115  	// The schema for the resource being worked on.
   116  	schema map[string]*Schema
   117  
   118  	// The current config for this resource.
   119  	config *terraform.ResourceConfig
   120  
   121  	// The state for this resource as it exists post-refresh, after the initial
   122  	// diff.
   123  	state *terraform.InstanceState
   124  
   125  	// The diff created by Terraform. This diff is used, along with state,
   126  	// config, and custom-set diff data, to provide a multi-level reader
   127  	// experience similar to ResourceData.
   128  	diff *terraform.InstanceDiff
   129  
   130  	// The internal reader structure that contains the state, config, the default
   131  	// diff, and the new diff.
   132  	multiReader *MultiLevelFieldReader
   133  
   134  	// A writer that writes overridden new fields.
   135  	newWriter *newValueWriter
   136  
   137  	// Tracks which keys have been updated by ResourceDiff to ensure that the
   138  	// diff does not get re-run on keys that were not touched, or diffs that were
   139  	// just removed (re-running on the latter would just roll back the removal).
   140  	updatedKeys map[string]bool
   141  
   142  	// Tracks which keys were flagged as forceNew. These keys are not saved in
   143  	// newWriter, but we need to track them so that they can be re-diffed later.
   144  	forcedNewKeys map[string]bool
   145  }
   146  
   147  // newResourceDiff creates a new ResourceDiff instance.
   148  func newResourceDiff(schema map[string]*Schema, config *terraform.ResourceConfig, state *terraform.InstanceState, diff *terraform.InstanceDiff) *ResourceDiff {
   149  	d := &ResourceDiff{
   150  		config: config,
   151  		state:  state,
   152  		diff:   diff,
   153  		schema: schema,
   154  	}
   155  
   156  	d.newWriter = &newValueWriter{
   157  		MapFieldWriter: &MapFieldWriter{Schema: d.schema},
   158  	}
   159  	readers := make(map[string]FieldReader)
   160  	var stateAttributes map[string]string
   161  	if d.state != nil {
   162  		stateAttributes = d.state.Attributes
   163  		readers["state"] = &MapFieldReader{
   164  			Schema: d.schema,
   165  			Map:    BasicMapReader(stateAttributes),
   166  		}
   167  	}
   168  	if d.config != nil {
   169  		readers["config"] = &ConfigFieldReader{
   170  			Schema: d.schema,
   171  			Config: d.config,
   172  		}
   173  	}
   174  	if d.diff != nil {
   175  		readers["diff"] = &DiffFieldReader{
   176  			Schema: d.schema,
   177  			Diff:   d.diff,
   178  			Source: &MultiLevelFieldReader{
   179  				Levels:  []string{"state", "config"},
   180  				Readers: readers,
   181  			},
   182  		}
   183  	}
   184  	readers["newDiff"] = &newValueReader{
   185  		MapFieldReader: &MapFieldReader{
   186  			Schema: d.schema,
   187  			Map:    BasicMapReader(d.newWriter.Map()),
   188  		},
   189  		computedKeys: d.newWriter.ComputedKeysMap(),
   190  	}
   191  	d.multiReader = &MultiLevelFieldReader{
   192  		Levels: []string{
   193  			"state",
   194  			"config",
   195  			"diff",
   196  			"newDiff",
   197  		},
   198  
   199  		Readers: readers,
   200  	}
   201  
   202  	d.updatedKeys = make(map[string]bool)
   203  	d.forcedNewKeys = make(map[string]bool)
   204  
   205  	return d
   206  }
   207  
   208  // UpdatedKeys returns the keys that were updated by this ResourceDiff run.
   209  // These are the only keys that a diff should be re-calculated for.
   210  //
   211  // This is the combined result of both keys for which diff values were updated
   212  // for or cleared, and also keys that were flagged to be re-diffed as a result
   213  // of ForceNew.
   214  func (d *ResourceDiff) UpdatedKeys() []string {
   215  	var s []string
   216  	for k := range d.updatedKeys {
   217  		s = append(s, k)
   218  	}
   219  	for k := range d.forcedNewKeys {
   220  		for _, l := range s {
   221  			if k == l {
   222  				break
   223  			}
   224  		}
   225  		s = append(s, k)
   226  	}
   227  	return s
   228  }
   229  
   230  // Clear wipes the diff for a particular key. It is called by ResourceDiff's
   231  // functionality to remove any possibility of conflicts, but can be called on
   232  // its own to just remove a specific key from the diff completely.
   233  //
   234  // Note that this does not wipe an override. This function is only allowed on
   235  // computed keys.
   236  func (d *ResourceDiff) Clear(key string) error {
   237  	if err := d.checkKey(key, "Clear", true); err != nil {
   238  		return err
   239  	}
   240  
   241  	return d.clear(key)
   242  }
   243  
   244  func (d *ResourceDiff) clear(key string) error {
   245  	// Check the schema to make sure that this key exists first.
   246  	schemaL := addrToSchema(strings.Split(key, "."), d.schema)
   247  	if len(schemaL) == 0 {
   248  		return fmt.Errorf("%s is not a valid key", key)
   249  	}
   250  
   251  	for k := range d.diff.Attributes {
   252  		if strings.HasPrefix(k, key) {
   253  			delete(d.diff.Attributes, k)
   254  		}
   255  	}
   256  	return nil
   257  }
   258  
   259  // GetChangedKeysPrefix helps to implement Resource.CustomizeDiff
   260  // where we need to act on all nested fields
   261  // without calling out each one separately
   262  func (d *ResourceDiff) GetChangedKeysPrefix(prefix string) []string {
   263  	keys := make([]string, 0)
   264  	for k := range d.diff.Attributes {
   265  		if strings.HasPrefix(k, prefix) {
   266  			keys = append(keys, k)
   267  		}
   268  	}
   269  	return keys
   270  }
   271  
   272  // diffChange helps to implement resourceDiffer and derives its change values
   273  // from ResourceDiff's own change data, in addition to existing diff, config, and state.
   274  func (d *ResourceDiff) diffChange(key string) (interface{}, interface{}, bool, bool, bool) {
   275  	old, new, customized := d.getChange(key)
   276  
   277  	if !old.Exists {
   278  		old.Value = nil
   279  	}
   280  	if !new.Exists || d.removed(key) {
   281  		new.Value = nil
   282  	}
   283  
   284  	return old.Value, new.Value, !reflect.DeepEqual(old.Value, new.Value), new.Computed, customized
   285  }
   286  
   287  // SetNew is used to set a new diff value for the mentioned key. The value must
   288  // be correct for the attribute's schema (mostly relevant for maps, lists, and
   289  // sets). The original value from the state is used as the old value.
   290  //
   291  // This function is only allowed on computed attributes.
   292  func (d *ResourceDiff) SetNew(key string, value interface{}) error {
   293  	if err := d.checkKey(key, "SetNew", false); err != nil {
   294  		return err
   295  	}
   296  
   297  	return d.setDiff(key, value, false)
   298  }
   299  
   300  // SetNewComputed functions like SetNew, except that it blanks out a new value
   301  // and marks it as computed.
   302  //
   303  // This function is only allowed on computed attributes.
   304  func (d *ResourceDiff) SetNewComputed(key string) error {
   305  	if err := d.checkKey(key, "SetNewComputed", false); err != nil {
   306  		return err
   307  	}
   308  
   309  	return d.setDiff(key, nil, true)
   310  }
   311  
   312  // setDiff performs common diff setting behaviour.
   313  func (d *ResourceDiff) setDiff(key string, new interface{}, computed bool) error {
   314  	if err := d.clear(key); err != nil {
   315  		return err
   316  	}
   317  
   318  	if err := d.newWriter.WriteField(strings.Split(key, "."), new, computed); err != nil {
   319  		return fmt.Errorf("Cannot set new diff value for key %s: %s", key, err)
   320  	}
   321  
   322  	d.updatedKeys[key] = true
   323  
   324  	return nil
   325  }
   326  
   327  // ForceNew force-flags ForceNew in the schema for a specific key, and
   328  // re-calculates its diff, effectively causing this attribute to force a new
   329  // resource.
   330  //
   331  // Keep in mind that forcing a new resource will force a second run of the
   332  // resource's CustomizeDiff function (with a new ResourceDiff) once the current
   333  // one has completed. This second run is performed without state. This behavior
   334  // will be the same as if a new resource is being created and is performed to
   335  // ensure that the diff looks like the diff for a new resource as much as
   336  // possible. CustomizeDiff should expect such a scenario and act correctly.
   337  //
   338  // This function is a no-op/error if there is no diff.
   339  //
   340  // Note that the change to schema is permanent for the lifecycle of this
   341  // specific ResourceDiff instance.
   342  func (d *ResourceDiff) ForceNew(key string) error {
   343  	if !d.HasChange(key) {
   344  		return fmt.Errorf("ForceNew: No changes for %s", key)
   345  	}
   346  
   347  	keyParts := strings.Split(key, ".")
   348  	var schema *Schema
   349  	schemaL := addrToSchema(keyParts, d.schema)
   350  	if len(schemaL) > 0 {
   351  		schema = schemaL[len(schemaL)-1]
   352  	} else {
   353  		return fmt.Errorf("ForceNew: %s is not a valid key", key)
   354  	}
   355  
   356  	schema.ForceNew = true
   357  
   358  	// Flag this for a re-diff. Don't save any values to guarantee that existing
   359  	// diffs aren't messed with, as this gets messy when dealing with complex
   360  	// structures, zero values, etc.
   361  	d.forcedNewKeys[keyParts[0]] = true
   362  
   363  	return nil
   364  }
   365  
   366  // Get hands off to ResourceData.Get.
   367  func (d *ResourceDiff) Get(key string) interface{} {
   368  	r, _ := d.GetOk(key)
   369  	return r
   370  }
   371  
   372  // GetChange gets the change between the state and diff, checking first to see
   373  // if an overridden diff exists.
   374  //
   375  // This implementation differs from ResourceData's in the way that we first get
   376  // results from the exact levels for the new diff, then from state and diff as
   377  // per normal.
   378  func (d *ResourceDiff) GetChange(key string) (interface{}, interface{}) {
   379  	old, new, _ := d.getChange(key)
   380  	return old.Value, new.Value
   381  }
   382  
   383  // GetOk functions the same way as ResourceData.GetOk, but it also checks the
   384  // new diff levels to provide data consistent with the current state of the
   385  // customized diff.
   386  func (d *ResourceDiff) GetOk(key string) (interface{}, bool) {
   387  	r := d.get(strings.Split(key, "."), "newDiff")
   388  	exists := r.Exists && !r.Computed
   389  	if exists {
   390  		// If it exists, we also want to verify it is not the zero-value.
   391  		value := r.Value
   392  		zero := r.Schema.Type.Zero()
   393  
   394  		if eq, ok := value.(Equal); ok {
   395  			exists = !eq.Equal(zero)
   396  		} else {
   397  			exists = !reflect.DeepEqual(value, zero)
   398  		}
   399  	}
   400  
   401  	return r.Value, exists
   402  }
   403  
   404  // GetOkExists functions the same way as GetOkExists within ResourceData, but
   405  // it also checks the new diff levels to provide data consistent with the
   406  // current state of the customized diff.
   407  //
   408  // This is nearly the same function as GetOk, yet it does not check
   409  // for the zero value of the attribute's type. This allows for attributes
   410  // without a default, to fully check for a literal assignment, regardless
   411  // of the zero-value for that type.
   412  func (d *ResourceDiff) GetOkExists(key string) (interface{}, bool) {
   413  	r := d.get(strings.Split(key, "."), "newDiff")
   414  	exists := r.Exists && !r.Computed
   415  	return r.Value, exists
   416  }
   417  
   418  // NewValueKnown returns true if the new value for the given key is available
   419  // as its final value at diff time. If the return value is false, this means
   420  // either the value is based of interpolation that was unavailable at diff
   421  // time, or that the value was explicitly marked as computed by SetNewComputed.
   422  func (d *ResourceDiff) NewValueKnown(key string) bool {
   423  	r := d.get(strings.Split(key, "."), "newDiff")
   424  	return !r.Computed
   425  }
   426  
   427  // HasChange checks to see if there is a change between state and the diff, or
   428  // in the overridden diff.
   429  func (d *ResourceDiff) HasChange(key string) bool {
   430  	old, new := d.GetChange(key)
   431  
   432  	// If the type implements the Equal interface, then call that
   433  	// instead of just doing a reflect.DeepEqual. An example where this is
   434  	// needed is *Set
   435  	if eq, ok := old.(Equal); ok {
   436  		return !eq.Equal(new)
   437  	}
   438  
   439  	return !reflect.DeepEqual(old, new)
   440  }
   441  
   442  // Id returns the ID of this resource.
   443  //
   444  // Note that technically, ID does not change during diffs (it either has
   445  // already changed in the refresh, or will change on update), hence we do not
   446  // support updating the ID or fetching it from anything else other than state.
   447  func (d *ResourceDiff) Id() string {
   448  	var result string
   449  
   450  	if d.state != nil {
   451  		result = d.state.ID
   452  	}
   453  	return result
   454  }
   455  
   456  // getChange gets values from two different levels, designed for use in
   457  // diffChange, HasChange, and GetChange.
   458  //
   459  // This implementation differs from ResourceData's in the way that we first get
   460  // results from the exact levels for the new diff, then from state and diff as
   461  // per normal.
   462  func (d *ResourceDiff) getChange(key string) (getResult, getResult, bool) {
   463  	old := d.get(strings.Split(key, "."), "state")
   464  	var new getResult
   465  	for p := range d.updatedKeys {
   466  		if childAddrOf(key, p) {
   467  			new = d.getExact(strings.Split(key, "."), "newDiff")
   468  			return old, new, true
   469  		}
   470  	}
   471  	new = d.get(strings.Split(key, "."), "newDiff")
   472  	return old, new, false
   473  }
   474  
   475  // removed checks to see if the key is present in the existing, pre-customized
   476  // diff and if it was marked as NewRemoved.
   477  func (d *ResourceDiff) removed(k string) bool {
   478  	diff, ok := d.diff.Attributes[k]
   479  	if !ok {
   480  		return false
   481  	}
   482  	return diff.NewRemoved
   483  }
   484  
   485  // get performs the appropriate multi-level reader logic for ResourceDiff,
   486  // starting at source. Refer to newResourceDiff for the level order.
   487  func (d *ResourceDiff) get(addr []string, source string) getResult {
   488  	result, err := d.multiReader.ReadFieldMerge(addr, source)
   489  	if err != nil {
   490  		panic(err)
   491  	}
   492  
   493  	return d.finalizeResult(addr, result)
   494  }
   495  
   496  // getExact gets an attribute from the exact level referenced by source.
   497  func (d *ResourceDiff) getExact(addr []string, source string) getResult {
   498  	result, err := d.multiReader.ReadFieldExact(addr, source)
   499  	if err != nil {
   500  		panic(err)
   501  	}
   502  
   503  	return d.finalizeResult(addr, result)
   504  }
   505  
   506  // finalizeResult does some post-processing of the result produced by get and getExact.
   507  func (d *ResourceDiff) finalizeResult(addr []string, result FieldReadResult) getResult {
   508  	// If the result doesn't exist, then we set the value to the zero value
   509  	var schema *Schema
   510  	if schemaL := addrToSchema(addr, d.schema); len(schemaL) > 0 {
   511  		schema = schemaL[len(schemaL)-1]
   512  	}
   513  
   514  	if result.Value == nil && schema != nil {
   515  		result.Value = result.ValueOrZero(schema)
   516  	}
   517  
   518  	// Transform the FieldReadResult into a getResult. It might be worth
   519  	// merging these two structures one day.
   520  	return getResult{
   521  		Value:          result.Value,
   522  		ValueProcessed: result.ValueProcessed,
   523  		Computed:       result.Computed,
   524  		Exists:         result.Exists,
   525  		Schema:         schema,
   526  	}
   527  }
   528  
   529  // childAddrOf does a comparison of two addresses to see if one is the child of
   530  // the other.
   531  func childAddrOf(child, parent string) bool {
   532  	cs := strings.Split(child, ".")
   533  	ps := strings.Split(parent, ".")
   534  	if len(ps) > len(cs) {
   535  		return false
   536  	}
   537  	return reflect.DeepEqual(ps, cs[:len(ps)])
   538  }
   539  
   540  // checkKey checks the key to make sure it exists and is computed.
   541  func (d *ResourceDiff) checkKey(key, caller string, nested bool) error {
   542  	var schema *Schema
   543  	if nested {
   544  		keyParts := strings.Split(key, ".")
   545  		schemaL := addrToSchema(keyParts, d.schema)
   546  		if len(schemaL) > 0 {
   547  			schema = schemaL[len(schemaL)-1]
   548  		}
   549  	} else {
   550  		s, ok := d.schema[key]
   551  		if ok {
   552  			schema = s
   553  		}
   554  	}
   555  	if schema == nil {
   556  		return fmt.Errorf("%s: invalid key: %s", caller, key)
   557  	}
   558  	if !schema.Computed {
   559  		return fmt.Errorf("%s only operates on computed keys - %s is not one", caller, key)
   560  	}
   561  	return nil
   562  }