15 "github.com/hashicorp/terraform/addrs"
16 "github.com/hashicorp/terraform/config/hcl2shim"
17 "github.com/hashicorp/terraform/configs/configschema"
18 "github.com/zclconf/go-cty/cty"
20 "github.com/mitchellh/copystructure"
23 // DiffChangeType is an enum with the kind of changes a diff has planned.
24 type DiffChangeType byte
27 DiffInvalid DiffChangeType = iota
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
41 // multiVal matches the index key to a flatmapped set, list or map
42 var multiVal = regexp.MustCompile(`\.(#|%)$`)
44 // Diff tracks the changes that are necessary to apply a configuration
45 // to an existing infrastructure.
47 // Modules contains all the modules that have a diff
51 // Prune cleans out unused structures in the diff without affecting
52 // the behavior of the diff at all.
54 // This is not safe to call concurrently. This is safe to call on a
56 func (d *Diff) Prune() {
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
66 newModules = append(newModules, m)
69 if len(newModules) == 0 {
72 d.Modules = newModules
75 // AddModule adds the module with the given path to the diff.
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")
93 legacyPath[i] = step.Name
96 m := &ModuleDiff{Path: legacyPath}
98 d.Modules = append(d.Modules, m)
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 {
109 for _, mod := range d.Modules {
111 panic("missing module path")
113 modPath := normalizeModulePath(mod.Path)
114 if modPath.String() == path.String() {
121 // RootModule returns the ModuleState for the root module
122 func (d *Diff) RootModule() *ModuleDiff {
123 root := d.ModuleByPath(addrs.RootModuleInstance)
125 panic("missing root module")
130 // Empty returns true if the diff has no changes.
131 func (d *Diff) Empty() bool {
136 for _, m := range d.Modules {
145 // Equal compares two diffs for exact equality.
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 {
157 sort.Sort(moduleDiffSort(d.Modules))
158 sort.Sort(moduleDiffSort(d2.Modules))
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 {
168 for _, m := range d2Copy.Modules {
173 return reflect.DeepEqual(dCopy, d2Copy)
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)
187 func (d *Diff) String() string {
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)
195 keys = append(keys, key)
200 for _, key := range keys {
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")
210 buf.WriteString(fmt.Sprintf("%s:\n", key))
212 s := bufio.NewScanner(strings.NewReader(mStr))
214 buf.WriteString(fmt.Sprintf(" %s\n", s.Text()))
218 return strings.TrimSpace(buf.String())
221 func (d *Diff) init() {
222 if d.Modules == nil {
223 rootDiff := &ModuleDiff{Path: rootModulePath}
224 d.Modules = []*ModuleDiff{rootDiff}
226 for _, m := range d.Modules {
231 // ModuleDiff tracks the differences between resources to apply within
233 type ModuleDiff struct {
235 Resources map[string]*InstanceDiff
236 Destroy bool // Set only by the destroy plan
239 func (d *ModuleDiff) init() {
240 if d.Resources == nil {
241 d.Resources = make(map[string]*InstanceDiff)
243 for _, r := range d.Resources {
248 // ChangeType returns the type of changes that the diff for this
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 {
256 for _, r := range d.Resources {
257 change := r.ChangeType()
259 case DiffCreate, DiffDestroy:
260 if result == DiffNone {
263 case DiffDestroyCreate, DiffUpdate:
271 // Empty returns true if the diff has no changes within this module.
272 func (d *ModuleDiff) Empty() bool {
277 if len(d.Resources) == 0 {
281 for _, rd := range d.Resources {
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+".") {
297 result = append(result, diff)
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)
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 {
315 names := make([]string, 0, len(d.Resources))
316 for name, _ := range d.Resources {
317 names = append(names, name)
321 for _, name := range names {
322 rdiff := d.Resources[name]
326 case rdiff.RequiresNew() && (rdiff.GetDestroy() || rdiff.GetDestroyTainted()):
327 crud = "DESTROY/CREATE"
328 case rdiff.GetDestroy() || rdiff.GetDestroyDeposed():
330 case rdiff.RequiresNew():
335 if !rdiff.GetDestroy() && rdiff.GetDestroyDeposed() {
336 extra = " (deposed only)"
339 buf.WriteString(fmt.Sprintf(
346 rdiffAttrs := rdiff.CopyAttributes()
347 keys := make([]string, 0, len(rdiffAttrs))
348 for key, _ := range rdiffAttrs {
353 keys = append(keys, key)
354 if len(key) > keyLen {
360 for _, attrK := range keys {
361 attrDiff, _ := rdiff.GetAttribute(attrK)
365 if attrDiff.NewComputed {
369 if attrDiff.Sensitive {
375 if attrDiff.RequiresNew {
376 updateMsg = " (forces new resource)"
377 } else if attrDiff.Sensitive {
378 updateMsg = " (attribute changed)"
381 buf.WriteString(fmt.Sprintf(
382 " %s:%s %#v => %#v%s\n",
384 strings.Repeat(" ", keyLen-len(attrK)),
394 // InstanceDiff is the diff of a resource from some state to another.
395 type InstanceDiff struct {
397 Attributes map[string]*ResourceAttrDiff
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{}
409 func (d *InstanceDiff) Lock() { d.mu.Lock() }
410 func (d *InstanceDiff) Unlock() { d.mu.Unlock() }
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.
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)
427 val, err := hcl2shim.HCL2ValueFromFlatmap(applied, schema.ImpliedType())
432 return schema.CoerceValue(val)
435 // Apply applies the diff to the provided flatmapped attributes,
436 // returning the new instance attributes.
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.
446 attrs = map[string]string{}
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{}
453 if d.Destroy || d.DestroyDeposed || d.DestroyTainted {
457 return d.applyBlockDiff(nil, attrs, schema)
460 func (d *InstanceDiff) applyBlockDiff(path []string, attrs map[string]string, schema *configschema.Block) (map[string]string, error) {
461 result := map[string]string{}
464 name = path[len(path)-1]
467 // localPrefix is used to build the local result map
470 localPrefix = name + "."
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 {
477 newAttrs, err := d.applyAttrDiff(append(path, n), attrs, attrSchema)
483 for k, v := range newAttrs {
484 result[localPrefix+k] = v
488 blockPrefix := strings.Join(path, ".")
489 if blockPrefix != "" {
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 + "."
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):], ".")
509 nextDot += len(blockKey)
510 candidateKeys[k[len(blockKey):nextDot]] = true
515 for k, diff := range d.Attributes {
516 if strings.HasPrefix(k, blockKey) {
517 nextDot := strings.Index(k[len(blockKey):], ".")
526 nextDot += len(blockKey)
527 candidateKeys[k[len(blockKey):nextDot]] = true
531 // check each set candidate to see if it was removed.
532 // we need to do this, because when entire sets are removed, they may
533 // have the wrong key, and ony show diffs going to ""
534 if block.Nesting == configschema.NestingSet {
535 for k := range candidateKeys {
536 indexPrefix := strings.Join(append(path, n, k), ".") + "."
538 // now check each set element to see if it's a new diff, or one
539 // that we're dropping. Since we're only applying the "New"
540 // portion of the set, we can ignore diffs that only contain "Old"
541 for attr, diff := range d.Attributes {
542 if !strings.HasPrefix(attr, indexPrefix) {
546 // check for empty "count" keys
547 if (strings.HasSuffix(attr, ".#") || strings.HasSuffix(attr, ".%")) && diff.New == "0" {
551 // removed items don't count either
556 // this must be a diff to keep
561 delete(candidateKeys, k)
566 for k := range candidateKeys {
567 newAttrs, err := d.applyBlockDiff(append(path, n, k), attrs, &block.Block)
572 for attr, v := range newAttrs {
573 result[localBlockPrefix+attr] = v
578 // check this block's count diff directly first, since we may not
579 // have candidates because it was removed and only set to "0"
580 if diff, ok := d.Attributes[blockKey+"#"]; ok {
581 if diff.New == "0" || diff.NewRemoved {
586 // if there was no diff at all, then we need to keep the block attributes
587 if len(candidateKeys) == 0 && keepBlock {
588 for k, v := range attrs {
589 if strings.HasPrefix(k, blockKey) {
590 // we need the key relative to this block, so remove the
591 // entire prefix, then re-insert the block name.
592 localKey := localBlockPrefix + k[len(blockKey):]
598 countAddr := strings.Join(append(path, n, "#"), ".")
599 if countDiff, ok := d.Attributes[countAddr]; ok {
600 if countDiff.NewComputed {
601 result[localBlockPrefix+"#"] = hcl2shim.UnknownVariableValue
603 result[localBlockPrefix+"#"] = countDiff.New
605 // While sets are complete, list are not, and we may not have all the
606 // information to track removals. If the list was truncated, we need to
607 // remove the extra items from the result.
608 if block.Nesting == configschema.NestingList &&
609 countDiff.New != "" && countDiff.New != hcl2shim.UnknownVariableValue {
610 length, _ := strconv.Atoi(countDiff.New)
611 for k := range result {
612 if !strings.HasPrefix(k, localBlockPrefix) {
616 index := k[len(localBlockPrefix):]
617 nextDot := strings.Index(index, ".")
621 index = index[:nextDot]
622 i, err := strconv.Atoi(index)
624 // this shouldn't happen since we added these
625 // ourself, but make note of it just in case.
626 log.Printf("[ERROR] bad list index in %q: %s", k, err)
635 } else if origCount, ok := attrs[countAddr]; ok && keepBlock {
636 result[localBlockPrefix+"#"] = origCount
638 result[localBlockPrefix+"#"] = countFlatmapContainerValues(localBlockPrefix+"#", result)
645 func (d *InstanceDiff) applyAttrDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) {
646 ty := attrSchema.Type
648 case ty.IsListType(), ty.IsTupleType(), ty.IsMapType():
649 return d.applyCollectionDiff(path, attrs, attrSchema)
651 return d.applySetDiff(path, attrs, attrSchema)
653 return d.applySingleAttrDiff(path, attrs, attrSchema)
657 func (d *InstanceDiff) applySingleAttrDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) {
658 currentKey := strings.Join(path, ".")
660 attr := path[len(path)-1]
662 result := map[string]string{}
663 diff := d.Attributes[currentKey]
664 old, exists := attrs[currentKey]
666 if diff != nil && diff.NewComputed {
667 result[attr] = hcl2shim.UnknownVariableValue
671 // "id" must exist and not be an empty string, or it must be unknown.
672 // This only applied to top-level "id" fields.
673 if attr == "id" && len(path) == 1 {
675 result[attr] = hcl2shim.UnknownVariableValue
682 // attribute diffs are sometimes missed, so assume no diff means keep the
688 // We need required values, so set those with an empty value. It
689 // must be set in the config, since if it were missing it would have
690 // failed validation.
691 if attrSchema.Required {
692 // we only set a missing string here, since bool or number types
693 // would have distinct zero value which shouldn't have been
695 if attrSchema.Type == cty.String {
703 // check for missmatched diff values
706 old != hcl2shim.UnknownVariableValue &&
707 diff.Old != hcl2shim.UnknownVariableValue {
708 return result, fmt.Errorf("diff apply conflict for %s: diff expects %q, but prior value has %q", attr, diff.Old, old)
712 // don't set anything in the new value
713 return map[string]string{}, nil
716 if diff.Old == diff.New && diff.New == "" {
717 // this can only be a valid empty string
718 if attrSchema.Type == cty.String {
724 if attrSchema.Computed && diff.NewComputed {
725 result[attr] = hcl2shim.UnknownVariableValue
729 result[attr] = diff.New
734 func (d *InstanceDiff) applyCollectionDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) {
735 result := map[string]string{}
739 prefix = strings.Join(path[:len(path)-1], ".") + "."
744 name = path[len(path)-1]
747 currentKey := prefix + name
749 // check the index first for special handling
750 for k, diff := range d.Attributes {
751 // check the index value, which can be set, and 0
752 if k == currentKey+".#" || k == currentKey+".%" || k == currentKey {
757 if diff.NewComputed {
758 result[k[len(prefix):]] = hcl2shim.UnknownVariableValue
762 // do what the diff tells us to here, so that it's consistent with applies
764 result[k[len(prefix):]] = "0"
770 // collect all the keys from the diff and the old state
772 keys := map[string]bool{}
773 for k := range d.Attributes {
774 if !strings.HasPrefix(k, currentKey+".") {
782 for k := range attrs {
783 if !strings.HasPrefix(k, currentKey+".") {
790 // If there's no diff and no attrs, then there's no value at all.
791 // This prevents an unexpected zero-count attribute in the attributes.
792 if noDiff && noAttrs {
797 if attrSchema.Type.IsMapType() {
801 for k := range keys {
802 // generate an schema placeholder for the values
803 elSchema := &configschema.Attribute{
804 Type: attrSchema.Type.ElementType(),
807 res, err := d.applySingleAttrDiff(append(path, k[len(currentKey)+1:]), attrs, elSchema)
812 for k, v := range res {
813 result[name+"."+k] = v
817 // Just like in nested list blocks, for simple lists we may need to fill in
818 // missing empty strings.
819 countKey := name + "." + idx
820 count := result[countKey]
821 length, _ := strconv.Atoi(count)
823 if count != "" && count != hcl2shim.UnknownVariableValue &&
824 attrSchema.Type.Equals(cty.List(cty.String)) {
825 // insert empty strings into missing indexes
826 for i := 0; i < length; i++ {
827 key := fmt.Sprintf("%s.%d", name, i)
828 if _, ok := result[key]; !ok {
834 // now check for truncation in any type of list
835 if attrSchema.Type.IsListType() {
836 for key := range result {
841 if len(key) <= len(name)+1 {
842 // not sure what this is, but don't panic
846 index := key[len(name)+1:]
848 // It is possible to have nested sets or maps, so look for another dot
849 dot := strings.Index(index, ".")
854 // This shouldn't have any more dots, since the element type is only string.
855 num, err := strconv.Atoi(index)
857 log.Printf("[ERROR] bad list index in %q: %s", currentKey, err)
867 // Fill in the count value if it wasn't present in the diff for some reason,
868 // or if there is no count at all.
869 _, countDiff := d.Attributes[countKey]
870 if result[countKey] == "" || (!countDiff && len(keys) != len(result)) {
871 result[countKey] = countFlatmapContainerValues(countKey, result)
877 func (d *InstanceDiff) applySetDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) {
878 // We only need this special behavior for sets of object.
879 if !attrSchema.Type.ElementType().IsObjectType() {
880 // The normal collection apply behavior will work okay for this one, then.
881 return d.applyCollectionDiff(path, attrs, attrSchema)
884 // When we're dealing with a set of an object type we actually want to
885 // use our normal _block type_ apply behaviors, so we'll construct ourselves
886 // a synthetic schema that treats the object type as a block type and
887 // then delegate to our block apply method.
888 synthSchema := &configschema.Block{
889 Attributes: make(map[string]*configschema.Attribute),
892 for name, ty := range attrSchema.Type.ElementType().AttributeTypes() {
893 // We can safely make everything into an attribute here because in the
894 // event that there are nested set attributes we'll end up back in
895 // here again recursively and can then deal with the next level of
897 synthSchema.Attributes[name] = &configschema.Attribute{
903 parentPath := path[:len(path)-1]
904 childName := path[len(path)-1]
905 containerSchema := &configschema.Block{
906 BlockTypes: map[string]*configschema.NestedBlock{
908 Nesting: configschema.NestingSet,
914 return d.applyBlockDiff(parentPath, attrs, containerSchema)
917 // countFlatmapContainerValues returns the number of values in the flatmapped container
918 // (set, map, list) indexed by key. The key argument is expected to include the
919 // trailing ".#", or ".%".
920 func countFlatmapContainerValues(key string, attrs map[string]string) string {
921 if len(key) < 3 || !(strings.HasSuffix(key, ".#") || strings.HasSuffix(key, ".%")) {
922 panic(fmt.Sprintf("invalid index value %q", key))
925 prefix := key[:len(key)-1]
926 items := map[string]int{}
928 for k := range attrs {
932 if !strings.HasPrefix(k, prefix) {
936 suffix := k[len(prefix):]
937 dot := strings.Index(suffix, ".")
939 suffix = suffix[:dot]
944 return strconv.Itoa(len(items))
947 // ResourceAttrDiff is the diff of a single attribute of a resource.
948 type ResourceAttrDiff struct {
949 Old string // Old Value
950 New string // New Value
951 NewComputed bool // True if new value is computed (unknown currently)
952 NewRemoved bool // True if this attribute is being removed
953 NewExtra interface{} // Extra information for the provider
954 RequiresNew bool // True if change requires new resource
955 Sensitive bool // True if the data should not be displayed in UI output
959 // Empty returns true if the diff for this attr is neutral
960 func (d *ResourceAttrDiff) Empty() bool {
961 return d.Old == d.New && !d.NewComputed && !d.NewRemoved
964 func (d *ResourceAttrDiff) GoString() string {
965 return fmt.Sprintf("*%#v", *d)
968 // DiffAttrType is an enum type that says whether a resource attribute
969 // diff is an input attribute (comes from the configuration) or an
970 // output attribute (comes as a result of applying the configuration). An
971 // example input would be "ami" for AWS and an example output would be
973 type DiffAttrType byte
976 DiffAttrUnknown DiffAttrType = iota
981 func (d *InstanceDiff) init() {
982 if d.Attributes == nil {
983 d.Attributes = make(map[string]*ResourceAttrDiff)
987 func NewInstanceDiff() *InstanceDiff {
988 return &InstanceDiff{Attributes: make(map[string]*ResourceAttrDiff)}
991 func (d *InstanceDiff) Copy() (*InstanceDiff, error) {
996 dCopy, err := copystructure.Config{Lock: true}.Copy(d)
1001 return dCopy.(*InstanceDiff), nil
1004 // ChangeType returns the DiffChangeType represented by the diff
1005 // for this single instance.
1006 func (d *InstanceDiff) ChangeType() DiffChangeType {
1011 if d.RequiresNew() && (d.GetDestroy() || d.GetDestroyTainted()) {
1012 return DiffDestroyCreate
1015 if d.GetDestroy() || d.GetDestroyDeposed() {
1019 if d.RequiresNew() {
1026 // Empty returns true if this diff encapsulates no changes.
1027 func (d *InstanceDiff) Empty() bool {
1034 return !d.Destroy &&
1035 !d.DestroyTainted &&
1036 !d.DestroyDeposed &&
1037 len(d.Attributes) == 0
1040 // Equal compares two diffs for exact equality.
1042 // This is different from the Same comparison that is supported which
1043 // checks for operation equality taking into account computed values. Equal
1044 // instead checks for exact equality.
1045 func (d *InstanceDiff) Equal(d2 *InstanceDiff) bool {
1046 // If one is nil, they must both be nil
1047 if d == nil || d2 == nil {
1052 return reflect.DeepEqual(d, d2)
1055 // DeepCopy performs a deep copy of all parts of the InstanceDiff
1056 func (d *InstanceDiff) DeepCopy() *InstanceDiff {
1057 copy, err := copystructure.Config{Lock: true}.Copy(d)
1062 return copy.(*InstanceDiff)
1065 func (d *InstanceDiff) GoString() string {
1066 return fmt.Sprintf("*%#v", InstanceDiff{
1067 Attributes: d.Attributes,
1069 DestroyTainted: d.DestroyTainted,
1070 DestroyDeposed: d.DestroyDeposed,
1074 // RequiresNew returns true if the diff requires the creation of a new
1075 // resource (implying the destruction of the old).
1076 func (d *InstanceDiff) RequiresNew() bool {
1084 return d.requiresNew()
1087 func (d *InstanceDiff) requiresNew() bool {
1092 if d.DestroyTainted {
1096 for _, rd := range d.Attributes {
1097 if rd != nil && rd.RequiresNew {
1105 func (d *InstanceDiff) GetDestroyDeposed() bool {
1109 return d.DestroyDeposed
1112 func (d *InstanceDiff) SetDestroyDeposed(b bool) {
1116 d.DestroyDeposed = b
1119 // These methods are properly locked, for use outside other InstanceDiff
1120 // methods but everywhere else within the terraform package.
1121 // TODO refactor the locking scheme
1122 func (d *InstanceDiff) SetTainted(b bool) {
1126 d.DestroyTainted = b
1129 func (d *InstanceDiff) GetDestroyTainted() bool {
1133 return d.DestroyTainted
1136 func (d *InstanceDiff) SetDestroy(b bool) {
1143 func (d *InstanceDiff) GetDestroy() bool {
1150 func (d *InstanceDiff) SetAttribute(key string, attr *ResourceAttrDiff) {
1154 d.Attributes[key] = attr
1157 func (d *InstanceDiff) DelAttribute(key string) {
1161 delete(d.Attributes, key)
1164 func (d *InstanceDiff) GetAttribute(key string) (*ResourceAttrDiff, bool) {
1168 attr, ok := d.Attributes[key]
1171 func (d *InstanceDiff) GetAttributesLen() int {
1175 return len(d.Attributes)
1178 // Safely copies the Attributes map
1179 func (d *InstanceDiff) CopyAttributes() map[string]*ResourceAttrDiff {
1183 attrs := make(map[string]*ResourceAttrDiff)
1184 for k, v := range d.Attributes {
1191 // Same checks whether or not two InstanceDiff's are the "same". When
1192 // we say "same", it is not necessarily exactly equal. Instead, it is
1193 // just checking that the same attributes are changing, a destroy
1194 // isn't suddenly happening, etc.
1195 func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) {
1196 // we can safely compare the pointers without a lock
1198 case d == nil && d2 == nil:
1200 case d == nil || d2 == nil:
1201 return false, "one nil"
1209 // If we're going from requiring new to NOT requiring new, then we have
1210 // to see if all required news were computed. If so, it is allowed since
1211 // computed may also mean "same value and therefore not new".
1212 oldNew := d.requiresNew()
1213 newNew := d2.RequiresNew()
1214 if oldNew && !newNew {
1217 // This section builds a list of ignorable attributes for requiresNew
1218 // by removing off any elements of collections going to zero elements.
1219 // For collections going to zero, they may not exist at all in the
1220 // new diff (and hence RequiresNew == false).
1221 ignoreAttrs := make(map[string]struct{})
1222 for k, diffOld := range d.Attributes {
1223 if !strings.HasSuffix(k, ".%") && !strings.HasSuffix(k, ".#") {
1227 // This case is in here as a protection measure. The bug that this
1228 // code originally fixed (GH-11349) didn't have to deal with computed
1229 // so I'm not 100% sure what the correct behavior is. Best to leave
1230 // the old behavior.
1231 if diffOld.NewComputed {
1235 // We're looking for the case a map goes to exactly 0.
1236 if diffOld.New != "0" {
1240 // Found it! Ignore all of these. The prefix here is stripping
1241 // off the "%" so it is just "k."
1242 prefix := k[:len(k)-1]
1243 for k2, _ := range d.Attributes {
1244 if strings.HasPrefix(k2, prefix) {
1245 ignoreAttrs[k2] = struct{}{}
1250 for k, rd := range d.Attributes {
1251 if _, ok := ignoreAttrs[k]; ok {
1255 // If the field is requires new and NOT computed, then what
1256 // we have is a diff mismatch for sure. We set that the old
1257 // diff does REQUIRE a ForceNew.
1258 if rd != nil && rd.RequiresNew && !rd.NewComputed {
1265 if oldNew != newNew {
1266 return false, fmt.Sprintf(
1267 "diff RequiresNew; old: %t, new: %t", oldNew, newNew)
1270 // Verify that destroy matches. The second boolean here allows us to
1271 // have mismatching Destroy if we're moving from RequiresNew true
1272 // to false above. Therefore, the second boolean will only pass if
1273 // we're moving from Destroy: true to false as well.
1274 if d.Destroy != d2.GetDestroy() && d.requiresNew() == oldNew {
1275 return false, fmt.Sprintf(
1276 "diff: Destroy; old: %t, new: %t", d.Destroy, d2.GetDestroy())
1279 // Go through the old diff and make sure the new diff has all the
1280 // same attributes. To start, build up the check map to be all the keys.
1281 checkOld := make(map[string]struct{})
1282 checkNew := make(map[string]struct{})
1283 for k, _ := range d.Attributes {
1284 checkOld[k] = struct{}{}
1286 for k, _ := range d2.CopyAttributes() {
1287 checkNew[k] = struct{}{}
1290 // Make an ordered list so we are sure the approximated hashes are left
1291 // to process at the end of the loop
1292 keys := make([]string, 0, len(d.Attributes))
1293 for k, _ := range d.Attributes {
1294 keys = append(keys, k)
1296 sort.StringSlice(keys).Sort()
1298 for _, k := range keys {
1299 diffOld := d.Attributes[k]
1301 if _, ok := checkOld[k]; !ok {
1302 // We're not checking this key for whatever reason (see where
1303 // check is modified).
1307 // Remove this key since we'll never hit it again
1311 _, ok := d2.GetAttribute(k)
1313 // If there's no new attribute, and the old diff expected the attribute
1314 // to be removed, that's just fine.
1315 if diffOld.NewRemoved {
1319 // If the last diff was a computed value then the absense of
1320 // that value is allowed since it may mean the value ended up
1322 if diffOld.NewComputed {
1326 // No exact match, but maybe this is a set containing computed
1327 // values. So check if there is an approximate hash in the key
1328 // and if so, try to match the key.
1329 if strings.Contains(k, "~") {
1330 parts := strings.Split(k, ".")
1331 parts2 := append([]string(nil), parts...)
1333 re := regexp.MustCompile(`^~\d+$`)
1334 for i, part := range parts {
1335 if re.MatchString(part) {
1336 // we're going to consider this the base of a
1337 // computed hash, and remove all longer matching fields
1341 parts2 = parts2[:i+1]
1346 re, err := regexp.Compile("^" + strings.Join(parts2, `\.`))
1348 return false, fmt.Sprintf("regexp failed to compile; err: %#v", err)
1351 for k2, _ := range checkNew {
1352 if re.MatchString(k2) {
1353 delete(checkNew, k2)
1358 // This is a little tricky, but when a diff contains a computed
1359 // list, set, or map that can only be interpolated after the apply
1360 // command has created the dependent resources, it could turn out
1361 // that the result is actually the same as the existing state which
1362 // would remove the key from the diff.
1363 if diffOld.NewComputed && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) {
1367 // Similarly, in a RequiresNew scenario, a list that shows up in the plan
1368 // diff can disappear from the apply diff, which is calculated from an
1370 if d.requiresNew() && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) {
1375 return false, fmt.Sprintf("attribute mismatch: %s", k)
1379 // search for the suffix of the base of a [computed] map, list or set.
1380 match := multiVal.FindStringSubmatch(k)
1382 if diffOld.NewComputed && len(match) == 2 {
1383 matchLen := len(match[1])
1385 // This is a computed list, set, or map, so remove any keys with
1386 // this prefix from the check list.
1387 kprefix := k[:len(k)-matchLen]
1388 for k2, _ := range checkOld {
1389 if strings.HasPrefix(k2, kprefix) {
1390 delete(checkOld, k2)
1393 for k2, _ := range checkNew {
1394 if strings.HasPrefix(k2, kprefix) {
1395 delete(checkNew, k2)
1400 // We don't compare the values because we can't currently actually
1401 // guarantee to generate the same value two two diffs created from
1402 // the same state+config: we have some pesky interpolation functions
1403 // that do not behave as pure functions (uuid, timestamp) and so they
1404 // can be different each time a diff is produced.
1405 // FIXME: Re-organize our config handling so that we don't re-evaluate
1406 // expressions when we produce a second comparison diff during
1407 // apply (for EvalCompareDiff).
1410 // Check for leftover attributes
1411 if len(checkNew) > 0 {
1412 extras := make([]string, 0, len(checkNew))
1413 for attr, _ := range checkNew {
1414 extras = append(extras, attr)
1417 fmt.Sprintf("extra attributes: %s", strings.Join(extras, ", "))
1423 // moduleDiffSort implements sort.Interface to sort module diffs by path.
1424 type moduleDiffSort []*ModuleDiff
1426 func (s moduleDiffSort) Len() int { return len(s) }
1427 func (s moduleDiffSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
1428 func (s moduleDiffSort) Less(i, j int) bool {
1432 // If the lengths are different, then the shorter one always wins
1433 if len(a.Path) != len(b.Path) {
1434 return len(a.Path) < len(b.Path)
1437 // Otherwise, compare lexically
1438 return strings.Join(a.Path, ".") < strings.Join(b.Path, ".")