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 }