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