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

     1  package terraform
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"log"
     8  	"reflect"
     9  	"regexp"
    10  	"sort"
    11  	"strconv"
    12  	"strings"
    13  	"sync"
    14  
    15  	"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
    16  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs/configschema"
    17  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs/hcl2shim"
    18  	"github.com/zclconf/go-cty/cty"
    19  
    20  	"github.com/mitchellh/copystructure"
    21  )
    22  
    23  // DiffChangeType is an enum with the kind of changes a diff has planned.
    24  type DiffChangeType byte
    25  
    26  const (
    27  	DiffInvalid DiffChangeType = iota
    28  	DiffNone
    29  	DiffCreate
    30  	DiffUpdate
    31  	DiffDestroy
    32  	DiffDestroyCreate
    33  
    34  	// DiffRefresh is only used in the UI for displaying diffs.
    35  	// Managed resource reads never appear in plan, and when data source
    36  	// reads appear they are represented as DiffCreate in core before
    37  	// transforming to DiffRefresh in the UI layer.
    38  	DiffRefresh // TODO: Actually use DiffRefresh in core too, for less confusion
    39  )
    40  
    41  // multiVal matches the index key to a flatmapped set, list or map
    42  var multiVal = regexp.MustCompile(`\.(#|%)$`)
    43  
    44  // Diff tracks the changes that are necessary to apply a configuration
    45  // to an existing infrastructure.
    46  type Diff struct {
    47  	// Modules contains all the modules that have a diff
    48  	Modules []*ModuleDiff
    49  }
    50  
    51  // Prune cleans out unused structures in the diff without affecting
    52  // the behavior of the diff at all.
    53  //
    54  // This is not safe to call concurrently. This is safe to call on a
    55  // nil Diff.
    56  func (d *Diff) Prune() {
    57  	if d == nil {
    58  		return
    59  	}
    60  
    61  	// Prune all empty modules
    62  	newModules := make([]*ModuleDiff, 0, len(d.Modules))
    63  	for _, m := range d.Modules {
    64  		// If the module isn't empty, we keep it
    65  		if !m.Empty() {
    66  			newModules = append(newModules, m)
    67  		}
    68  	}
    69  	if len(newModules) == 0 {
    70  		newModules = nil
    71  	}
    72  	d.Modules = newModules
    73  }
    74  
    75  // AddModule adds the module with the given path to the diff.
    76  //
    77  // This should be the preferred method to add module diffs since it
    78  // allows us to optimize lookups later as well as control sorting.
    79  func (d *Diff) AddModule(path addrs.ModuleInstance) *ModuleDiff {
    80  	// Lower the new-style address into a legacy-style address.
    81  	// This requires that none of the steps have instance keys, which is
    82  	// true for all addresses at the time of implementing this because
    83  	// "count" and "for_each" are not yet implemented for modules.
    84  	legacyPath := make([]string, len(path))
    85  	for i, step := range path {
    86  		if step.InstanceKey != addrs.NoKey {
    87  			// FIXME: Once the rest of Terraform is ready to use count and
    88  			// for_each, remove all of this and just write the addrs.ModuleInstance
    89  			// value itself into the ModuleState.
    90  			panic("diff cannot represent modules with count or for_each keys")
    91  		}
    92  
    93  		legacyPath[i] = step.Name
    94  	}
    95  
    96  	m := &ModuleDiff{Path: legacyPath}
    97  	m.init()
    98  	d.Modules = append(d.Modules, m)
    99  	return m
   100  }
   101  
   102  // ModuleByPath is used to lookup the module diff for the given path.
   103  // This should be the preferred lookup mechanism as it allows for future
   104  // lookup optimizations.
   105  func (d *Diff) ModuleByPath(path addrs.ModuleInstance) *ModuleDiff {
   106  	if d == nil {
   107  		return nil
   108  	}
   109  	for _, mod := range d.Modules {
   110  		if mod.Path == nil {
   111  			panic("missing module path")
   112  		}
   113  		modPath := normalizeModulePath(mod.Path)
   114  		if modPath.String() == path.String() {
   115  			return mod
   116  		}
   117  	}
   118  	return nil
   119  }
   120  
   121  // RootModule returns the ModuleState for the root module
   122  func (d *Diff) RootModule() *ModuleDiff {
   123  	root := d.ModuleByPath(addrs.RootModuleInstance)
   124  	if root == nil {
   125  		panic("missing root module")
   126  	}
   127  	return root
   128  }
   129  
   130  // Empty returns true if the diff has no changes.
   131  func (d *Diff) Empty() bool {
   132  	if d == nil {
   133  		return true
   134  	}
   135  
   136  	for _, m := range d.Modules {
   137  		if !m.Empty() {
   138  			return false
   139  		}
   140  	}
   141  
   142  	return true
   143  }
   144  
   145  // Equal compares two diffs for exact equality.
   146  //
   147  // This is different from the Same comparison that is supported which
   148  // checks for operation equality taking into account computed values. Equal
   149  // instead checks for exact equality.
   150  func (d *Diff) Equal(d2 *Diff) bool {
   151  	// If one is nil, they must both be nil
   152  	if d == nil || d2 == nil {
   153  		return d == d2
   154  	}
   155  
   156  	// Sort the modules
   157  	sort.Sort(moduleDiffSort(d.Modules))
   158  	sort.Sort(moduleDiffSort(d2.Modules))
   159  
   160  	// Copy since we have to modify the module destroy flag to false so
   161  	// we don't compare that. TODO: delete this when we get rid of the
   162  	// destroy flag on modules.
   163  	dCopy := d.DeepCopy()
   164  	d2Copy := d2.DeepCopy()
   165  	for _, m := range dCopy.Modules {
   166  		m.Destroy = false
   167  	}
   168  	for _, m := range d2Copy.Modules {
   169  		m.Destroy = false
   170  	}
   171  
   172  	// Use DeepEqual
   173  	return reflect.DeepEqual(dCopy, d2Copy)
   174  }
   175  
   176  // DeepCopy performs a deep copy of all parts of the Diff, making the
   177  // resulting Diff safe to use without modifying this one.
   178  func (d *Diff) DeepCopy() *Diff {
   179  	copy, err := copystructure.Config{Lock: true}.Copy(d)
   180  	if err != nil {
   181  		panic(err)
   182  	}
   183  
   184  	return copy.(*Diff)
   185  }
   186  
   187  func (d *Diff) String() string {
   188  	var buf bytes.Buffer
   189  
   190  	keys := make([]string, 0, len(d.Modules))
   191  	lookup := make(map[string]*ModuleDiff)
   192  	for _, m := range d.Modules {
   193  		addr := normalizeModulePath(m.Path)
   194  		key := addr.String()
   195  		keys = append(keys, key)
   196  		lookup[key] = m
   197  	}
   198  	sort.Strings(keys)
   199  
   200  	for _, key := range keys {
   201  		m := lookup[key]
   202  		mStr := m.String()
   203  
   204  		// If we're the root module, we just write the output directly.
   205  		if reflect.DeepEqual(m.Path, rootModulePath) {
   206  			buf.WriteString(mStr + "\n")
   207  			continue
   208  		}
   209  
   210  		buf.WriteString(fmt.Sprintf("%s:\n", key))
   211  
   212  		s := bufio.NewScanner(strings.NewReader(mStr))
   213  		for s.Scan() {
   214  			buf.WriteString(fmt.Sprintf("  %s\n", s.Text()))
   215  		}
   216  	}
   217  
   218  	return strings.TrimSpace(buf.String())
   219  }
   220  
   221  // ModuleDiff tracks the differences between resources to apply within
   222  // a single module.
   223  type ModuleDiff struct {
   224  	Path      []string
   225  	Resources map[string]*InstanceDiff
   226  	Destroy   bool // Set only by the destroy plan
   227  }
   228  
   229  func (d *ModuleDiff) init() {
   230  	if d.Resources == nil {
   231  		d.Resources = make(map[string]*InstanceDiff)
   232  	}
   233  	for _, r := range d.Resources {
   234  		r.init()
   235  	}
   236  }
   237  
   238  // ChangeType returns the type of changes that the diff for this
   239  // module includes.
   240  //
   241  // At a module level, this will only be DiffNone, DiffUpdate, DiffDestroy, or
   242  // DiffCreate. If an instance within the module has a DiffDestroyCreate
   243  // then this will register as a DiffCreate for a module.
   244  func (d *ModuleDiff) ChangeType() DiffChangeType {
   245  	result := DiffNone
   246  	for _, r := range d.Resources {
   247  		change := r.ChangeType()
   248  		switch change {
   249  		case DiffCreate, DiffDestroy:
   250  			if result == DiffNone {
   251  				result = change
   252  			}
   253  		case DiffDestroyCreate, DiffUpdate:
   254  			result = DiffUpdate
   255  		}
   256  	}
   257  
   258  	return result
   259  }
   260  
   261  // Empty returns true if the diff has no changes within this module.
   262  func (d *ModuleDiff) Empty() bool {
   263  	if d.Destroy {
   264  		return false
   265  	}
   266  
   267  	if len(d.Resources) == 0 {
   268  		return true
   269  	}
   270  
   271  	for _, rd := range d.Resources {
   272  		if !rd.Empty() {
   273  			return false
   274  		}
   275  	}
   276  
   277  	return true
   278  }
   279  
   280  // Instances returns the instance diffs for the id given. This can return
   281  // multiple instance diffs if there are counts within the resource.
   282  func (d *ModuleDiff) Instances(id string) []*InstanceDiff {
   283  	var result []*InstanceDiff
   284  	for k, diff := range d.Resources {
   285  		if k == id || strings.HasPrefix(k, id+".") {
   286  			if !diff.Empty() {
   287  				result = append(result, diff)
   288  			}
   289  		}
   290  	}
   291  
   292  	return result
   293  }
   294  
   295  // IsRoot says whether or not this module diff is for the root module.
   296  func (d *ModuleDiff) IsRoot() bool {
   297  	return reflect.DeepEqual(d.Path, rootModulePath)
   298  }
   299  
   300  // String outputs the diff in a long but command-line friendly output
   301  // format that users can read to quickly inspect a diff.
   302  func (d *ModuleDiff) String() string {
   303  	var buf bytes.Buffer
   304  
   305  	names := make([]string, 0, len(d.Resources))
   306  	for name := range d.Resources {
   307  		names = append(names, name)
   308  	}
   309  	sort.Strings(names)
   310  
   311  	for _, name := range names {
   312  		rdiff := d.Resources[name]
   313  
   314  		crud := "UPDATE"
   315  		switch {
   316  		case rdiff.RequiresNew() && (rdiff.GetDestroy() || rdiff.GetDestroyTainted()):
   317  			crud = "DESTROY/CREATE"
   318  		case rdiff.GetDestroy() || rdiff.GetDestroyDeposed():
   319  			crud = "DESTROY"
   320  		case rdiff.RequiresNew():
   321  			crud = "CREATE"
   322  		}
   323  
   324  		extra := ""
   325  		if !rdiff.GetDestroy() && rdiff.GetDestroyDeposed() {
   326  			extra = " (deposed only)"
   327  		}
   328  
   329  		buf.WriteString(fmt.Sprintf(
   330  			"%s: %s%s\n",
   331  			crud,
   332  			name,
   333  			extra))
   334  
   335  		keyLen := 0
   336  		rdiffAttrs := rdiff.CopyAttributes()
   337  		keys := make([]string, 0, len(rdiffAttrs))
   338  		for key := range rdiffAttrs {
   339  			if key == "id" {
   340  				continue
   341  			}
   342  
   343  			keys = append(keys, key)
   344  			if len(key) > keyLen {
   345  				keyLen = len(key)
   346  			}
   347  		}
   348  		sort.Strings(keys)
   349  
   350  		for _, attrK := range keys {
   351  			attrDiff, _ := rdiff.GetAttribute(attrK)
   352  
   353  			v := attrDiff.New
   354  			u := attrDiff.Old
   355  			if attrDiff.NewComputed {
   356  				v = "<computed>"
   357  			}
   358  
   359  			if attrDiff.Sensitive {
   360  				u = "<sensitive>"
   361  				v = "<sensitive>"
   362  			}
   363  
   364  			updateMsg := ""
   365  			if attrDiff.RequiresNew {
   366  				updateMsg = " (forces new resource)"
   367  			} else if attrDiff.Sensitive {
   368  				updateMsg = " (attribute changed)"
   369  			}
   370  
   371  			buf.WriteString(fmt.Sprintf(
   372  				"  %s:%s %#v => %#v%s\n",
   373  				attrK,
   374  				strings.Repeat(" ", keyLen-len(attrK)),
   375  				u,
   376  				v,
   377  				updateMsg))
   378  		}
   379  	}
   380  
   381  	return buf.String()
   382  }
   383  
   384  // InstanceDiff is the diff of a resource from some state to another.
   385  type InstanceDiff struct {
   386  	mu             sync.Mutex
   387  	Attributes     map[string]*ResourceAttrDiff
   388  	Destroy        bool
   389  	DestroyDeposed bool
   390  	DestroyTainted bool
   391  
   392  	// Meta is a simple K/V map that is stored in a diff and persisted to
   393  	// plans but otherwise is completely ignored by Terraform core. It is
   394  	// meant to be used for additional data a resource may want to pass through.
   395  	// The value here must only contain Go primitives and collections.
   396  	Meta map[string]interface{}
   397  }
   398  
   399  func (d *InstanceDiff) Lock()   { d.mu.Lock() }
   400  func (d *InstanceDiff) Unlock() { d.mu.Unlock() }
   401  
   402  // ApplyToValue merges the receiver into the given base value, returning a
   403  // new value that incorporates the planned changes. The given value must
   404  // conform to the given schema, or this method will panic.
   405  //
   406  // This method is intended for shimming old subsystems that still use this
   407  // legacy diff type to work with the new-style types.
   408  func (d *InstanceDiff) ApplyToValue(base cty.Value, schema *configschema.Block) (cty.Value, error) {
   409  	// Create an InstanceState attributes from our existing state.
   410  	// We can use this to more easily apply the diff changes.
   411  	attrs := hcl2shim.FlatmapValueFromHCL2(base)
   412  	applied, err := d.Apply(attrs, schema)
   413  	if err != nil {
   414  		return base, err
   415  	}
   416  
   417  	val, err := hcl2shim.HCL2ValueFromFlatmap(applied, schema.ImpliedType())
   418  	if err != nil {
   419  		return base, err
   420  	}
   421  
   422  	return schema.CoerceValue(val)
   423  }
   424  
   425  // Apply applies the diff to the provided flatmapped attributes,
   426  // returning the new instance attributes.
   427  //
   428  // This method is intended for shimming old subsystems that still use this
   429  // legacy diff type to work with the new-style types.
   430  func (d *InstanceDiff) Apply(attrs map[string]string, schema *configschema.Block) (map[string]string, error) {
   431  	// We always build a new value here, even if the given diff is "empty",
   432  	// because we might be planning to create a new instance that happens
   433  	// to have no attributes set, and so we want to produce an empty object
   434  	// rather than just echoing back the null old value.
   435  	if attrs == nil {
   436  		attrs = map[string]string{}
   437  	}
   438  
   439  	// Rather applying the diff to mutate the attrs, we'll copy new values into
   440  	// here to avoid the possibility of leaving stale values.
   441  	result := map[string]string{}
   442  
   443  	if d.Destroy || d.DestroyDeposed || d.DestroyTainted {
   444  		return result, nil
   445  	}
   446  
   447  	return d.applyBlockDiff(nil, attrs, schema)
   448  }
   449  
   450  func (d *InstanceDiff) applyBlockDiff(path []string, attrs map[string]string, schema *configschema.Block) (map[string]string, error) {
   451  	result := map[string]string{}
   452  	name := ""
   453  	if len(path) > 0 {
   454  		name = path[len(path)-1]
   455  	}
   456  
   457  	// localPrefix is used to build the local result map
   458  	localPrefix := ""
   459  	if name != "" {
   460  		localPrefix = name + "."
   461  	}
   462  
   463  	// iterate over the schema rather than the attributes, so we can handle
   464  	// different block types separately from plain attributes
   465  	for n, attrSchema := range schema.Attributes {
   466  		var err error
   467  		newAttrs, err := d.applyAttrDiff(append(path, n), attrs, attrSchema)
   468  
   469  		if err != nil {
   470  			return result, err
   471  		}
   472  
   473  		for k, v := range newAttrs {
   474  			result[localPrefix+k] = v
   475  		}
   476  	}
   477  
   478  	blockPrefix := strings.Join(path, ".")
   479  	if blockPrefix != "" {
   480  		blockPrefix += "."
   481  	}
   482  	for n, block := range schema.BlockTypes {
   483  		// we need to find the set of all keys that traverse this block
   484  		candidateKeys := map[string]bool{}
   485  		blockKey := blockPrefix + n + "."
   486  		localBlockPrefix := localPrefix + n + "."
   487  
   488  		// we can only trust the diff for sets, since the path changes, so don't
   489  		// count existing values as candidate keys. If it turns out we're
   490  		// keeping the attributes, we will catch it down below with "keepBlock"
   491  		// after we check the set count.
   492  		if block.Nesting != configschema.NestingSet {
   493  			for k := range attrs {
   494  				if strings.HasPrefix(k, blockKey) {
   495  					nextDot := strings.Index(k[len(blockKey):], ".")
   496  					if nextDot < 0 {
   497  						continue
   498  					}
   499  					nextDot += len(blockKey)
   500  					candidateKeys[k[len(blockKey):nextDot]] = true
   501  				}
   502  			}
   503  		}
   504  
   505  		for k, diff := range d.Attributes {
   506  			// helper/schema should not insert nil diff values, but don't panic
   507  			// if it does.
   508  			if diff == nil {
   509  				continue
   510  			}
   511  
   512  			if strings.HasPrefix(k, blockKey) {
   513  				nextDot := strings.Index(k[len(blockKey):], ".")
   514  				if nextDot < 0 {
   515  					continue
   516  				}
   517  
   518  				if diff.NewRemoved {
   519  					continue
   520  				}
   521  
   522  				nextDot += len(blockKey)
   523  				candidateKeys[k[len(blockKey):nextDot]] = true
   524  			}
   525  		}
   526  
   527  		// check each set candidate to see if it was removed.
   528  		// we need to do this, because when entire sets are removed, they may
   529  		// have the wrong key, and ony show diffs going to ""
   530  		if block.Nesting == configschema.NestingSet {
   531  			for k := range candidateKeys {
   532  				indexPrefix := strings.Join(append(path, n, k), ".") + "."
   533  				keep := false
   534  				// now check each set element to see if it's a new diff, or one
   535  				// that we're dropping. Since we're only applying the "New"
   536  				// portion of the set, we can ignore diffs that only contain "Old"
   537  				for attr, diff := range d.Attributes {
   538  					// helper/schema should not insert nil diff values, but don't panic
   539  					// if it does.
   540  					if diff == nil {
   541  						continue
   542  					}
   543  
   544  					if !strings.HasPrefix(attr, indexPrefix) {
   545  						continue
   546  					}
   547  
   548  					// check for empty "count" keys
   549  					if (strings.HasSuffix(attr, ".#") || strings.HasSuffix(attr, ".%")) && diff.New == "0" {
   550  						continue
   551  					}
   552  
   553  					// removed items don't count either
   554  					if diff.NewRemoved {
   555  						continue
   556  					}
   557  
   558  					// this must be a diff to keep
   559  					keep = true
   560  					break
   561  				}
   562  				if !keep {
   563  					delete(candidateKeys, k)
   564  				}
   565  			}
   566  		}
   567  
   568  		for k := range candidateKeys {
   569  			newAttrs, err := d.applyBlockDiff(append(path, n, k), attrs, &block.Block)
   570  			if err != nil {
   571  				return result, err
   572  			}
   573  
   574  			for attr, v := range newAttrs {
   575  				result[localBlockPrefix+attr] = v
   576  			}
   577  		}
   578  
   579  		keepBlock := true
   580  		// check this block's count diff directly first, since we may not
   581  		// have candidates because it was removed and only set to "0"
   582  		if diff, ok := d.Attributes[blockKey+"#"]; ok {
   583  			if diff.New == "0" || diff.NewRemoved {
   584  				keepBlock = false
   585  			}
   586  		}
   587  
   588  		// if there was no diff at all, then we need to keep the block attributes
   589  		if len(candidateKeys) == 0 && keepBlock {
   590  			for k, v := range attrs {
   591  				if strings.HasPrefix(k, blockKey) {
   592  					// we need the key relative to this block, so remove the
   593  					// entire prefix, then re-insert the block name.
   594  					localKey := localBlockPrefix + k[len(blockKey):]
   595  					result[localKey] = v
   596  				}
   597  			}
   598  		}
   599  
   600  		countAddr := strings.Join(append(path, n, "#"), ".")
   601  		if countDiff, ok := d.Attributes[countAddr]; ok {
   602  			if countDiff.NewComputed {
   603  				result[localBlockPrefix+"#"] = hcl2shim.UnknownVariableValue
   604  			} else {
   605  				result[localBlockPrefix+"#"] = countDiff.New
   606  
   607  				// While sets are complete, list are not, and we may not have all the
   608  				// information to track removals. If the list was truncated, we need to
   609  				// remove the extra items from the result.
   610  				if block.Nesting == configschema.NestingList &&
   611  					countDiff.New != "" && countDiff.New != hcl2shim.UnknownVariableValue {
   612  					length, _ := strconv.Atoi(countDiff.New)
   613  					for k := range result {
   614  						if !strings.HasPrefix(k, localBlockPrefix) {
   615  							continue
   616  						}
   617  
   618  						index := k[len(localBlockPrefix):]
   619  						nextDot := strings.Index(index, ".")
   620  						if nextDot < 1 {
   621  							continue
   622  						}
   623  						index = index[:nextDot]
   624  						i, err := strconv.Atoi(index)
   625  						if err != nil {
   626  							// this shouldn't happen since we added these
   627  							// ourself, but make note of it just in case.
   628  							log.Printf("[ERROR] bad list index in %q: %s", k, err)
   629  							continue
   630  						}
   631  						if i >= length {
   632  							delete(result, k)
   633  						}
   634  					}
   635  				}
   636  			}
   637  		} else if origCount, ok := attrs[countAddr]; ok && keepBlock {
   638  			result[localBlockPrefix+"#"] = origCount
   639  		} else {
   640  			result[localBlockPrefix+"#"] = countFlatmapContainerValues(localBlockPrefix+"#", result)
   641  		}
   642  	}
   643  
   644  	return result, nil
   645  }
   646  
   647  func (d *InstanceDiff) applyAttrDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) {
   648  	ty := attrSchema.Type
   649  	switch {
   650  	case ty.IsListType(), ty.IsTupleType(), ty.IsMapType():
   651  		return d.applyCollectionDiff(path, attrs, attrSchema)
   652  	case ty.IsSetType():
   653  		return d.applySetDiff(path, attrs, attrSchema)
   654  	default:
   655  		return d.applySingleAttrDiff(path, attrs, attrSchema)
   656  	}
   657  }
   658  
   659  func (d *InstanceDiff) applySingleAttrDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) {
   660  	currentKey := strings.Join(path, ".")
   661  
   662  	attr := path[len(path)-1]
   663  
   664  	result := map[string]string{}
   665  	diff := d.Attributes[currentKey]
   666  	old, exists := attrs[currentKey]
   667  
   668  	if diff != nil && diff.NewComputed {
   669  		result[attr] = hcl2shim.UnknownVariableValue
   670  		return result, nil
   671  	}
   672  
   673  	// "id" must exist and not be an empty string, or it must be unknown.
   674  	// This only applied to top-level "id" fields.
   675  	if attr == "id" && len(path) == 1 {
   676  		if old == "" {
   677  			result[attr] = hcl2shim.UnknownVariableValue
   678  		} else {
   679  			result[attr] = old
   680  		}
   681  		return result, nil
   682  	}
   683  
   684  	// attribute diffs are sometimes missed, so assume no diff means keep the
   685  	// old value
   686  	if diff == nil {
   687  		if exists {
   688  			result[attr] = old
   689  		} else {
   690  			// We need required values, so set those with an empty value. It
   691  			// must be set in the config, since if it were missing it would have
   692  			// failed validation.
   693  			if attrSchema.Required {
   694  				// we only set a missing string here, since bool or number types
   695  				// would have distinct zero value which shouldn't have been
   696  				// lost.
   697  				if attrSchema.Type == cty.String {
   698  					result[attr] = ""
   699  				}
   700  			}
   701  		}
   702  		return result, nil
   703  	}
   704  
   705  	// check for missmatched diff values
   706  	if exists &&
   707  		old != diff.Old &&
   708  		old != hcl2shim.UnknownVariableValue &&
   709  		diff.Old != hcl2shim.UnknownVariableValue {
   710  		return result, fmt.Errorf("diff apply conflict for %s: diff expects %q, but prior value has %q", attr, diff.Old, old)
   711  	}
   712  
   713  	if diff.NewRemoved {
   714  		// don't set anything in the new value
   715  		return map[string]string{}, nil
   716  	}
   717  
   718  	if diff.Old == diff.New && diff.New == "" {
   719  		// this can only be a valid empty string
   720  		if attrSchema.Type == cty.String {
   721  			result[attr] = ""
   722  		}
   723  		return result, nil
   724  	}
   725  
   726  	if attrSchema.Computed && diff.NewComputed {
   727  		result[attr] = hcl2shim.UnknownVariableValue
   728  		return result, nil
   729  	}
   730  
   731  	result[attr] = diff.New
   732  
   733  	return result, nil
   734  }
   735  
   736  func (d *InstanceDiff) applyCollectionDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) {
   737  	result := map[string]string{}
   738  
   739  	prefix := ""
   740  	if len(path) > 1 {
   741  		prefix = strings.Join(path[:len(path)-1], ".") + "."
   742  	}
   743  
   744  	name := ""
   745  	if len(path) > 0 {
   746  		name = path[len(path)-1]
   747  	}
   748  
   749  	currentKey := prefix + name
   750  
   751  	// check the index first for special handling
   752  	for k, diff := range d.Attributes {
   753  		// check the index value, which can be set, and 0
   754  		if k == currentKey+".#" || k == currentKey+".%" || k == currentKey {
   755  			if diff.NewRemoved {
   756  				return result, nil
   757  			}
   758  
   759  			if diff.NewComputed {
   760  				result[k[len(prefix):]] = hcl2shim.UnknownVariableValue
   761  				return result, nil
   762  			}
   763  
   764  			// do what the diff tells us to here, so that it's consistent with applies
   765  			if diff.New == "0" {
   766  				result[k[len(prefix):]] = "0"
   767  				return result, nil
   768  			}
   769  		}
   770  	}
   771  
   772  	// collect all the keys from the diff and the old state
   773  	noDiff := true
   774  	keys := map[string]bool{}
   775  	for k := range d.Attributes {
   776  		if !strings.HasPrefix(k, currentKey+".") {
   777  			continue
   778  		}
   779  		noDiff = false
   780  		keys[k] = true
   781  	}
   782  
   783  	noAttrs := true
   784  	for k := range attrs {
   785  		if !strings.HasPrefix(k, currentKey+".") {
   786  			continue
   787  		}
   788  		noAttrs = false
   789  		keys[k] = true
   790  	}
   791  
   792  	// If there's no diff and no attrs, then there's no value at all.
   793  	// This prevents an unexpected zero-count attribute in the attributes.
   794  	if noDiff && noAttrs {
   795  		return result, nil
   796  	}
   797  
   798  	idx := "#"
   799  	if attrSchema.Type.IsMapType() {
   800  		idx = "%"
   801  	}
   802  
   803  	for k := range keys {
   804  		// generate an schema placeholder for the values
   805  		elSchema := &configschema.Attribute{
   806  			Type: attrSchema.Type.ElementType(),
   807  		}
   808  
   809  		res, err := d.applySingleAttrDiff(append(path, k[len(currentKey)+1:]), attrs, elSchema)
   810  		if err != nil {
   811  			return result, err
   812  		}
   813  
   814  		for k, v := range res {
   815  			result[name+"."+k] = v
   816  		}
   817  	}
   818  
   819  	// Just like in nested list blocks, for simple lists we may need to fill in
   820  	// missing empty strings.
   821  	countKey := name + "." + idx
   822  	count := result[countKey]
   823  	length, _ := strconv.Atoi(count)
   824  
   825  	if count != "" && count != hcl2shim.UnknownVariableValue &&
   826  		attrSchema.Type.Equals(cty.List(cty.String)) {
   827  		// insert empty strings into missing indexes
   828  		for i := 0; i < length; i++ {
   829  			key := fmt.Sprintf("%s.%d", name, i)
   830  			if _, ok := result[key]; !ok {
   831  				result[key] = ""
   832  			}
   833  		}
   834  	}
   835  
   836  	// now check for truncation in any type of list
   837  	if attrSchema.Type.IsListType() {
   838  		for key := range result {
   839  			if key == countKey {
   840  				continue
   841  			}
   842  
   843  			if len(key) <= len(name)+1 {
   844  				// not sure what this is, but don't panic
   845  				continue
   846  			}
   847  
   848  			index := key[len(name)+1:]
   849  
   850  			// It is possible to have nested sets or maps, so look for another dot
   851  			dot := strings.Index(index, ".")
   852  			if dot > 0 {
   853  				index = index[:dot]
   854  			}
   855  
   856  			// This shouldn't have any more dots, since the element type is only string.
   857  			num, err := strconv.Atoi(index)
   858  			if err != nil {
   859  				log.Printf("[ERROR] bad list index in %q: %s", currentKey, err)
   860  				continue
   861  			}
   862  
   863  			if num >= length {
   864  				delete(result, key)
   865  			}
   866  		}
   867  	}
   868  
   869  	// Fill in the count value if it wasn't present in the diff for some reason,
   870  	// or if there is no count at all.
   871  	_, countDiff := d.Attributes[countKey]
   872  	if result[countKey] == "" || (!countDiff && len(keys) != len(result)) {
   873  		result[countKey] = countFlatmapContainerValues(countKey, result)
   874  	}
   875  
   876  	return result, nil
   877  }
   878  
   879  func (d *InstanceDiff) applySetDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) {
   880  	// We only need this special behavior for sets of object.
   881  	if !attrSchema.Type.ElementType().IsObjectType() {
   882  		// The normal collection apply behavior will work okay for this one, then.
   883  		return d.applyCollectionDiff(path, attrs, attrSchema)
   884  	}
   885  
   886  	// When we're dealing with a set of an object type we actually want to
   887  	// use our normal _block type_ apply behaviors, so we'll construct ourselves
   888  	// a synthetic schema that treats the object type as a block type and
   889  	// then delegate to our block apply method.
   890  	synthSchema := &configschema.Block{
   891  		Attributes: make(map[string]*configschema.Attribute),
   892  	}
   893  
   894  	for name, ty := range attrSchema.Type.ElementType().AttributeTypes() {
   895  		// We can safely make everything into an attribute here because in the
   896  		// event that there are nested set attributes we'll end up back in
   897  		// here again recursively and can then deal with the next level of
   898  		// expansion.
   899  		synthSchema.Attributes[name] = &configschema.Attribute{
   900  			Type:     ty,
   901  			Optional: true,
   902  		}
   903  	}
   904  
   905  	parentPath := path[:len(path)-1]
   906  	childName := path[len(path)-1]
   907  	containerSchema := &configschema.Block{
   908  		BlockTypes: map[string]*configschema.NestedBlock{
   909  			childName: {
   910  				Nesting: configschema.NestingSet,
   911  				Block:   *synthSchema,
   912  			},
   913  		},
   914  	}
   915  
   916  	return d.applyBlockDiff(parentPath, attrs, containerSchema)
   917  }
   918  
   919  // countFlatmapContainerValues returns the number of values in the flatmapped container
   920  // (set, map, list) indexed by key. The key argument is expected to include the
   921  // trailing ".#", or ".%".
   922  func countFlatmapContainerValues(key string, attrs map[string]string) string {
   923  	if len(key) < 3 || !(strings.HasSuffix(key, ".#") || strings.HasSuffix(key, ".%")) {
   924  		panic(fmt.Sprintf("invalid index value %q", key))
   925  	}
   926  
   927  	prefix := key[:len(key)-1]
   928  	items := map[string]int{}
   929  
   930  	for k := range attrs {
   931  		if k == key {
   932  			continue
   933  		}
   934  		if !strings.HasPrefix(k, prefix) {
   935  			continue
   936  		}
   937  
   938  		suffix := k[len(prefix):]
   939  		dot := strings.Index(suffix, ".")
   940  		if dot > 0 {
   941  			suffix = suffix[:dot]
   942  		}
   943  
   944  		items[suffix]++
   945  	}
   946  	return strconv.Itoa(len(items))
   947  }
   948  
   949  // ResourceAttrDiff is the diff of a single attribute of a resource.
   950  type ResourceAttrDiff struct {
   951  	Old         string      // Old Value
   952  	New         string      // New Value
   953  	NewComputed bool        // True if new value is computed (unknown currently)
   954  	NewRemoved  bool        // True if this attribute is being removed
   955  	NewExtra    interface{} // Extra information for the provider
   956  	RequiresNew bool        // True if change requires new resource
   957  	Sensitive   bool        // True if the data should not be displayed in UI output
   958  	Type        DiffAttrType
   959  }
   960  
   961  // Empty returns true if the diff for this attr is neutral
   962  func (d *ResourceAttrDiff) Empty() bool {
   963  	return d.Old == d.New && !d.NewComputed && !d.NewRemoved
   964  }
   965  
   966  func (d *ResourceAttrDiff) GoString() string {
   967  	return fmt.Sprintf("*%#v", *d)
   968  }
   969  
   970  // DiffAttrType is an enum type that says whether a resource attribute
   971  // diff is an input attribute (comes from the configuration) or an
   972  // output attribute (comes as a result of applying the configuration). An
   973  // example input would be "ami" for AWS and an example output would be
   974  // "private_ip".
   975  type DiffAttrType byte
   976  
   977  const (
   978  	DiffAttrUnknown DiffAttrType = iota
   979  	DiffAttrInput
   980  	DiffAttrOutput
   981  )
   982  
   983  func (d *InstanceDiff) init() {
   984  	if d.Attributes == nil {
   985  		d.Attributes = make(map[string]*ResourceAttrDiff)
   986  	}
   987  }
   988  
   989  func NewInstanceDiff() *InstanceDiff {
   990  	return &InstanceDiff{Attributes: make(map[string]*ResourceAttrDiff)}
   991  }
   992  
   993  func (d *InstanceDiff) Copy() (*InstanceDiff, error) {
   994  	if d == nil {
   995  		return nil, nil
   996  	}
   997  
   998  	dCopy, err := copystructure.Config{Lock: true}.Copy(d)
   999  	if err != nil {
  1000  		return nil, err
  1001  	}
  1002  
  1003  	return dCopy.(*InstanceDiff), nil
  1004  }
  1005  
  1006  // ChangeType returns the DiffChangeType represented by the diff
  1007  // for this single instance.
  1008  func (d *InstanceDiff) ChangeType() DiffChangeType {
  1009  	if d.Empty() {
  1010  		return DiffNone
  1011  	}
  1012  
  1013  	if d.RequiresNew() && (d.GetDestroy() || d.GetDestroyTainted()) {
  1014  		return DiffDestroyCreate
  1015  	}
  1016  
  1017  	if d.GetDestroy() || d.GetDestroyDeposed() {
  1018  		return DiffDestroy
  1019  	}
  1020  
  1021  	if d.RequiresNew() {
  1022  		return DiffCreate
  1023  	}
  1024  
  1025  	return DiffUpdate
  1026  }
  1027  
  1028  // Empty returns true if this diff encapsulates no changes.
  1029  func (d *InstanceDiff) Empty() bool {
  1030  	if d == nil {
  1031  		return true
  1032  	}
  1033  
  1034  	d.mu.Lock()
  1035  	defer d.mu.Unlock()
  1036  	return !d.Destroy &&
  1037  		!d.DestroyTainted &&
  1038  		!d.DestroyDeposed &&
  1039  		len(d.Attributes) == 0
  1040  }
  1041  
  1042  // Equal compares two diffs for exact equality.
  1043  //
  1044  // This is different from the Same comparison that is supported which
  1045  // checks for operation equality taking into account computed values. Equal
  1046  // instead checks for exact equality.
  1047  func (d *InstanceDiff) Equal(d2 *InstanceDiff) bool {
  1048  	// If one is nil, they must both be nil
  1049  	if d == nil || d2 == nil {
  1050  		return d == d2
  1051  	}
  1052  
  1053  	// Use DeepEqual
  1054  	return reflect.DeepEqual(d, d2)
  1055  }
  1056  
  1057  // DeepCopy performs a deep copy of all parts of the InstanceDiff
  1058  func (d *InstanceDiff) DeepCopy() *InstanceDiff {
  1059  	copy, err := copystructure.Config{Lock: true}.Copy(d)
  1060  	if err != nil {
  1061  		panic(err)
  1062  	}
  1063  
  1064  	return copy.(*InstanceDiff)
  1065  }
  1066  
  1067  func (d *InstanceDiff) GoString() string {
  1068  	return fmt.Sprintf("*%#v", InstanceDiff{
  1069  		Attributes:     d.Attributes,
  1070  		Destroy:        d.Destroy,
  1071  		DestroyTainted: d.DestroyTainted,
  1072  		DestroyDeposed: d.DestroyDeposed,
  1073  	})
  1074  }
  1075  
  1076  // RequiresNew returns true if the diff requires the creation of a new
  1077  // resource (implying the destruction of the old).
  1078  func (d *InstanceDiff) RequiresNew() bool {
  1079  	if d == nil {
  1080  		return false
  1081  	}
  1082  
  1083  	d.mu.Lock()
  1084  	defer d.mu.Unlock()
  1085  
  1086  	return d.requiresNew()
  1087  }
  1088  
  1089  func (d *InstanceDiff) requiresNew() bool {
  1090  	if d == nil {
  1091  		return false
  1092  	}
  1093  
  1094  	if d.DestroyTainted {
  1095  		return true
  1096  	}
  1097  
  1098  	for _, rd := range d.Attributes {
  1099  		if rd != nil && rd.RequiresNew {
  1100  			return true
  1101  		}
  1102  	}
  1103  
  1104  	return false
  1105  }
  1106  
  1107  func (d *InstanceDiff) GetDestroyDeposed() bool {
  1108  	d.mu.Lock()
  1109  	defer d.mu.Unlock()
  1110  
  1111  	return d.DestroyDeposed
  1112  }
  1113  
  1114  func (d *InstanceDiff) SetDestroyDeposed(b bool) {
  1115  	d.mu.Lock()
  1116  	defer d.mu.Unlock()
  1117  
  1118  	d.DestroyDeposed = b
  1119  }
  1120  
  1121  // These methods are properly locked, for use outside other InstanceDiff
  1122  // methods but everywhere else within the terraform package.
  1123  // TODO refactor the locking scheme
  1124  func (d *InstanceDiff) SetTainted(b bool) {
  1125  	d.mu.Lock()
  1126  	defer d.mu.Unlock()
  1127  
  1128  	d.DestroyTainted = b
  1129  }
  1130  
  1131  func (d *InstanceDiff) GetDestroyTainted() bool {
  1132  	d.mu.Lock()
  1133  	defer d.mu.Unlock()
  1134  
  1135  	return d.DestroyTainted
  1136  }
  1137  
  1138  func (d *InstanceDiff) SetDestroy(b bool) {
  1139  	d.mu.Lock()
  1140  	defer d.mu.Unlock()
  1141  
  1142  	d.Destroy = b
  1143  }
  1144  
  1145  func (d *InstanceDiff) GetDestroy() bool {
  1146  	d.mu.Lock()
  1147  	defer d.mu.Unlock()
  1148  
  1149  	return d.Destroy
  1150  }
  1151  
  1152  func (d *InstanceDiff) SetAttribute(key string, attr *ResourceAttrDiff) {
  1153  	d.mu.Lock()
  1154  	defer d.mu.Unlock()
  1155  
  1156  	d.Attributes[key] = attr
  1157  }
  1158  
  1159  func (d *InstanceDiff) DelAttribute(key string) {
  1160  	d.mu.Lock()
  1161  	defer d.mu.Unlock()
  1162  
  1163  	delete(d.Attributes, key)
  1164  }
  1165  
  1166  func (d *InstanceDiff) GetAttribute(key string) (*ResourceAttrDiff, bool) {
  1167  	d.mu.Lock()
  1168  	defer d.mu.Unlock()
  1169  
  1170  	attr, ok := d.Attributes[key]
  1171  	return attr, ok
  1172  }
  1173  func (d *InstanceDiff) GetAttributesLen() int {
  1174  	d.mu.Lock()
  1175  	defer d.mu.Unlock()
  1176  
  1177  	return len(d.Attributes)
  1178  }
  1179  
  1180  // Safely copies the Attributes map
  1181  func (d *InstanceDiff) CopyAttributes() map[string]*ResourceAttrDiff {
  1182  	d.mu.Lock()
  1183  	defer d.mu.Unlock()
  1184  
  1185  	attrs := make(map[string]*ResourceAttrDiff)
  1186  	for k, v := range d.Attributes {
  1187  		attrs[k] = v
  1188  	}
  1189  
  1190  	return attrs
  1191  }
  1192  
  1193  // Same checks whether or not two InstanceDiff's are the "same". When
  1194  // we say "same", it is not necessarily exactly equal. Instead, it is
  1195  // just checking that the same attributes are changing, a destroy
  1196  // isn't suddenly happening, etc.
  1197  func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) {
  1198  	// we can safely compare the pointers without a lock
  1199  	switch {
  1200  	case d == nil && d2 == nil:
  1201  		return true, ""
  1202  	case d == nil || d2 == nil:
  1203  		return false, "one nil"
  1204  	case d == d2:
  1205  		return true, ""
  1206  	}
  1207  
  1208  	d.mu.Lock()
  1209  	defer d.mu.Unlock()
  1210  
  1211  	// If we're going from requiring new to NOT requiring new, then we have
  1212  	// to see if all required news were computed. If so, it is allowed since
  1213  	// computed may also mean "same value and therefore not new".
  1214  	oldNew := d.requiresNew()
  1215  	newNew := d2.RequiresNew()
  1216  	if oldNew && !newNew {
  1217  		oldNew = false
  1218  
  1219  		// This section builds a list of ignorable attributes for requiresNew
  1220  		// by removing off any elements of collections going to zero elements.
  1221  		// For collections going to zero, they may not exist at all in the
  1222  		// new diff (and hence RequiresNew == false).
  1223  		ignoreAttrs := make(map[string]struct{})
  1224  		for k, diffOld := range d.Attributes {
  1225  			if !strings.HasSuffix(k, ".%") && !strings.HasSuffix(k, ".#") {
  1226  				continue
  1227  			}
  1228  
  1229  			// This case is in here as a protection measure. The bug that this
  1230  			// code originally fixed (GH-11349) didn't have to deal with computed
  1231  			// so I'm not 100% sure what the correct behavior is. Best to leave
  1232  			// the old behavior.
  1233  			if diffOld.NewComputed {
  1234  				continue
  1235  			}
  1236  
  1237  			// We're looking for the case a map goes to exactly 0.
  1238  			if diffOld.New != "0" {
  1239  				continue
  1240  			}
  1241  
  1242  			// Found it! Ignore all of these. The prefix here is stripping
  1243  			// off the "%" so it is just "k."
  1244  			prefix := k[:len(k)-1]
  1245  			for k2 := range d.Attributes {
  1246  				if strings.HasPrefix(k2, prefix) {
  1247  					ignoreAttrs[k2] = struct{}{}
  1248  				}
  1249  			}
  1250  		}
  1251  
  1252  		for k, rd := range d.Attributes {
  1253  			if _, ok := ignoreAttrs[k]; ok {
  1254  				continue
  1255  			}
  1256  
  1257  			// If the field is requires new and NOT computed, then what
  1258  			// we have is a diff mismatch for sure. We set that the old
  1259  			// diff does REQUIRE a ForceNew.
  1260  			if rd != nil && rd.RequiresNew && !rd.NewComputed {
  1261  				oldNew = true
  1262  				break
  1263  			}
  1264  		}
  1265  	}
  1266  
  1267  	if oldNew != newNew {
  1268  		return false, fmt.Sprintf(
  1269  			"diff RequiresNew; old: %t, new: %t", oldNew, newNew)
  1270  	}
  1271  
  1272  	// Verify that destroy matches. The second boolean here allows us to
  1273  	// have mismatching Destroy if we're moving from RequiresNew true
  1274  	// to false above. Therefore, the second boolean will only pass if
  1275  	// we're moving from Destroy: true to false as well.
  1276  	if d.Destroy != d2.GetDestroy() && d.requiresNew() == oldNew {
  1277  		return false, fmt.Sprintf(
  1278  			"diff: Destroy; old: %t, new: %t", d.Destroy, d2.GetDestroy())
  1279  	}
  1280  
  1281  	// Go through the old diff and make sure the new diff has all the
  1282  	// same attributes. To start, build up the check map to be all the keys.
  1283  	checkOld := make(map[string]struct{})
  1284  	checkNew := make(map[string]struct{})
  1285  	for k := range d.Attributes {
  1286  		checkOld[k] = struct{}{}
  1287  	}
  1288  	for k := range d2.CopyAttributes() {
  1289  		checkNew[k] = struct{}{}
  1290  	}
  1291  
  1292  	// Make an ordered list so we are sure the approximated hashes are left
  1293  	// to process at the end of the loop
  1294  	keys := make([]string, 0, len(d.Attributes))
  1295  	for k := range d.Attributes {
  1296  		keys = append(keys, k)
  1297  	}
  1298  	sort.StringSlice(keys).Sort()
  1299  
  1300  	for _, k := range keys {
  1301  		diffOld := d.Attributes[k]
  1302  
  1303  		if _, ok := checkOld[k]; !ok {
  1304  			// We're not checking this key for whatever reason (see where
  1305  			// check is modified).
  1306  			continue
  1307  		}
  1308  
  1309  		// Remove this key since we'll never hit it again
  1310  		delete(checkOld, k)
  1311  		delete(checkNew, k)
  1312  
  1313  		_, ok := d2.GetAttribute(k)
  1314  		if !ok {
  1315  			// If there's no new attribute, and the old diff expected the attribute
  1316  			// to be removed, that's just fine.
  1317  			if diffOld.NewRemoved {
  1318  				continue
  1319  			}
  1320  
  1321  			// If the last diff was a computed value then the absense of
  1322  			// that value is allowed since it may mean the value ended up
  1323  			// being the same.
  1324  			if diffOld.NewComputed {
  1325  				ok = true
  1326  			}
  1327  
  1328  			// No exact match, but maybe this is a set containing computed
  1329  			// values. So check if there is an approximate hash in the key
  1330  			// and if so, try to match the key.
  1331  			if strings.Contains(k, "~") {
  1332  				parts := strings.Split(k, ".")
  1333  				parts2 := append([]string(nil), parts...)
  1334  
  1335  				re := regexp.MustCompile(`^~\d+$`)
  1336  				for i, part := range parts {
  1337  					if re.MatchString(part) {
  1338  						// we're going to consider this the base of a
  1339  						// computed hash, and remove all longer matching fields
  1340  						ok = true
  1341  
  1342  						parts2[i] = `\d+`
  1343  						parts2 = parts2[:i+1]
  1344  						break
  1345  					}
  1346  				}
  1347  
  1348  				re, err := regexp.Compile("^" + strings.Join(parts2, `\.`))
  1349  				if err != nil {
  1350  					return false, fmt.Sprintf("regexp failed to compile; err: %#v", err)
  1351  				}
  1352  
  1353  				for k2 := range checkNew {
  1354  					if re.MatchString(k2) {
  1355  						delete(checkNew, k2)
  1356  					}
  1357  				}
  1358  			}
  1359  
  1360  			// This is a little tricky, but when a diff contains a computed
  1361  			// list, set, or map that can only be interpolated after the apply
  1362  			// command has created the dependent resources, it could turn out
  1363  			// that the result is actually the same as the existing state which
  1364  			// would remove the key from the diff.
  1365  			if diffOld.NewComputed && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) {
  1366  				ok = true
  1367  			}
  1368  
  1369  			// Similarly, in a RequiresNew scenario, a list that shows up in the plan
  1370  			// diff can disappear from the apply diff, which is calculated from an
  1371  			// empty state.
  1372  			if d.requiresNew() && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) {
  1373  				ok = true
  1374  			}
  1375  
  1376  			if !ok {
  1377  				return false, fmt.Sprintf("attribute mismatch: %s", k)
  1378  			}
  1379  		}
  1380  
  1381  		// search for the suffix of the base of a [computed] map, list or set.
  1382  		match := multiVal.FindStringSubmatch(k)
  1383  
  1384  		if diffOld.NewComputed && len(match) == 2 {
  1385  			matchLen := len(match[1])
  1386  
  1387  			// This is a computed list, set, or map, so remove any keys with
  1388  			// this prefix from the check list.
  1389  			kprefix := k[:len(k)-matchLen]
  1390  			for k2 := range checkOld {
  1391  				if strings.HasPrefix(k2, kprefix) {
  1392  					delete(checkOld, k2)
  1393  				}
  1394  			}
  1395  			for k2 := range checkNew {
  1396  				if strings.HasPrefix(k2, kprefix) {
  1397  					delete(checkNew, k2)
  1398  				}
  1399  			}
  1400  		}
  1401  
  1402  		// We don't compare the values because we can't currently actually
  1403  		// guarantee to generate the same value two two diffs created from
  1404  		// the same state+config: we have some pesky interpolation functions
  1405  		// that do not behave as pure functions (uuid, timestamp) and so they
  1406  		// can be different each time a diff is produced.
  1407  		// FIXME: Re-organize our config handling so that we don't re-evaluate
  1408  		// expressions when we produce a second comparison diff during
  1409  		// apply (for EvalCompareDiff).
  1410  	}
  1411  
  1412  	// Check for leftover attributes
  1413  	if len(checkNew) > 0 {
  1414  		extras := make([]string, 0, len(checkNew))
  1415  		for attr := range checkNew {
  1416  			extras = append(extras, attr)
  1417  		}
  1418  		return false,
  1419  			fmt.Sprintf("extra attributes: %s", strings.Join(extras, ", "))
  1420  	}
  1421  
  1422  	return true, ""
  1423  }
  1424  
  1425  // moduleDiffSort implements sort.Interface to sort module diffs by path.
  1426  type moduleDiffSort []*ModuleDiff
  1427  
  1428  func (s moduleDiffSort) Len() int      { return len(s) }
  1429  func (s moduleDiffSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
  1430  func (s moduleDiffSort) Less(i, j int) bool {
  1431  	a := s[i]
  1432  	b := s[j]
  1433  
  1434  	// If the lengths are different, then the shorter one always wins
  1435  	if len(a.Path) != len(b.Path) {
  1436  		return len(a.Path) < len(b.Path)
  1437  	}
  1438  
  1439  	// Otherwise, compare lexically
  1440  	return strings.Join(a.Path, ".") < strings.Join(b.Path, ".")
  1441  }