github.com/sylr/terraform@v0.11.12-beta1/helper/schema/resource_diff.go (about)

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