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