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

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