github.com/wikibal01/hashicorp-terraform@v0.11.12-beta1/terraform/diff.go (about) 1 package terraform 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "reflect" 8 "regexp" 9 "sort" 10 "strings" 11 "sync" 12 13 "github.com/mitchellh/copystructure" 14 ) 15 16 // DiffChangeType is an enum with the kind of changes a diff has planned. 17 type DiffChangeType byte 18 19 const ( 20 DiffInvalid DiffChangeType = iota 21 DiffNone 22 DiffCreate 23 DiffUpdate 24 DiffDestroy 25 DiffDestroyCreate 26 27 // DiffRefresh is only used in the UI for displaying diffs. 28 // Managed resource reads never appear in plan, and when data source 29 // reads appear they are represented as DiffCreate in core before 30 // transforming to DiffRefresh in the UI layer. 31 DiffRefresh // TODO: Actually use DiffRefresh in core too, for less confusion 32 ) 33 34 // multiVal matches the index key to a flatmapped set, list or map 35 var multiVal = regexp.MustCompile(`\.(#|%)$`) 36 37 // Diff tracks the changes that are necessary to apply a configuration 38 // to an existing infrastructure. 39 type Diff struct { 40 // Modules contains all the modules that have a diff 41 Modules []*ModuleDiff 42 } 43 44 // Prune cleans out unused structures in the diff without affecting 45 // the behavior of the diff at all. 46 // 47 // This is not safe to call concurrently. This is safe to call on a 48 // nil Diff. 49 func (d *Diff) Prune() { 50 if d == nil { 51 return 52 } 53 54 // Prune all empty modules 55 newModules := make([]*ModuleDiff, 0, len(d.Modules)) 56 for _, m := range d.Modules { 57 // If the module isn't empty, we keep it 58 if !m.Empty() { 59 newModules = append(newModules, m) 60 } 61 } 62 if len(newModules) == 0 { 63 newModules = nil 64 } 65 d.Modules = newModules 66 } 67 68 // AddModule adds the module with the given path to the diff. 69 // 70 // This should be the preferred method to add module diffs since it 71 // allows us to optimize lookups later as well as control sorting. 72 func (d *Diff) AddModule(path []string) *ModuleDiff { 73 m := &ModuleDiff{Path: path} 74 m.init() 75 d.Modules = append(d.Modules, m) 76 return m 77 } 78 79 // ModuleByPath is used to lookup the module diff for the given path. 80 // This should be the preferred lookup mechanism as it allows for future 81 // lookup optimizations. 82 func (d *Diff) ModuleByPath(path []string) *ModuleDiff { 83 if d == nil { 84 return nil 85 } 86 for _, mod := range d.Modules { 87 if mod.Path == nil { 88 panic("missing module path") 89 } 90 if reflect.DeepEqual(mod.Path, path) { 91 return mod 92 } 93 } 94 return nil 95 } 96 97 // RootModule returns the ModuleState for the root module 98 func (d *Diff) RootModule() *ModuleDiff { 99 root := d.ModuleByPath(rootModulePath) 100 if root == nil { 101 panic("missing root module") 102 } 103 return root 104 } 105 106 // Empty returns true if the diff has no changes. 107 func (d *Diff) Empty() bool { 108 if d == nil { 109 return true 110 } 111 112 for _, m := range d.Modules { 113 if !m.Empty() { 114 return false 115 } 116 } 117 118 return true 119 } 120 121 // Equal compares two diffs for exact equality. 122 // 123 // This is different from the Same comparison that is supported which 124 // checks for operation equality taking into account computed values. Equal 125 // instead checks for exact equality. 126 func (d *Diff) Equal(d2 *Diff) bool { 127 // If one is nil, they must both be nil 128 if d == nil || d2 == nil { 129 return d == d2 130 } 131 132 // Sort the modules 133 sort.Sort(moduleDiffSort(d.Modules)) 134 sort.Sort(moduleDiffSort(d2.Modules)) 135 136 // Copy since we have to modify the module destroy flag to false so 137 // we don't compare that. TODO: delete this when we get rid of the 138 // destroy flag on modules. 139 dCopy := d.DeepCopy() 140 d2Copy := d2.DeepCopy() 141 for _, m := range dCopy.Modules { 142 m.Destroy = false 143 } 144 for _, m := range d2Copy.Modules { 145 m.Destroy = false 146 } 147 148 // Use DeepEqual 149 return reflect.DeepEqual(dCopy, d2Copy) 150 } 151 152 // DeepCopy performs a deep copy of all parts of the Diff, making the 153 // resulting Diff safe to use without modifying this one. 154 func (d *Diff) DeepCopy() *Diff { 155 copy, err := copystructure.Config{Lock: true}.Copy(d) 156 if err != nil { 157 panic(err) 158 } 159 160 return copy.(*Diff) 161 } 162 163 func (d *Diff) String() string { 164 var buf bytes.Buffer 165 166 keys := make([]string, 0, len(d.Modules)) 167 lookup := make(map[string]*ModuleDiff) 168 for _, m := range d.Modules { 169 key := fmt.Sprintf("module.%s", strings.Join(m.Path[1:], ".")) 170 keys = append(keys, key) 171 lookup[key] = m 172 } 173 sort.Strings(keys) 174 175 for _, key := range keys { 176 m := lookup[key] 177 mStr := m.String() 178 179 // If we're the root module, we just write the output directly. 180 if reflect.DeepEqual(m.Path, rootModulePath) { 181 buf.WriteString(mStr + "\n") 182 continue 183 } 184 185 buf.WriteString(fmt.Sprintf("%s:\n", key)) 186 187 s := bufio.NewScanner(strings.NewReader(mStr)) 188 for s.Scan() { 189 buf.WriteString(fmt.Sprintf(" %s\n", s.Text())) 190 } 191 } 192 193 return strings.TrimSpace(buf.String()) 194 } 195 196 func (d *Diff) init() { 197 if d.Modules == nil { 198 rootDiff := &ModuleDiff{Path: rootModulePath} 199 d.Modules = []*ModuleDiff{rootDiff} 200 } 201 for _, m := range d.Modules { 202 m.init() 203 } 204 } 205 206 // ModuleDiff tracks the differences between resources to apply within 207 // a single module. 208 type ModuleDiff struct { 209 Path []string 210 Resources map[string]*InstanceDiff 211 Destroy bool // Set only by the destroy plan 212 } 213 214 func (d *ModuleDiff) init() { 215 if d.Resources == nil { 216 d.Resources = make(map[string]*InstanceDiff) 217 } 218 for _, r := range d.Resources { 219 r.init() 220 } 221 } 222 223 // ChangeType returns the type of changes that the diff for this 224 // module includes. 225 // 226 // At a module level, this will only be DiffNone, DiffUpdate, DiffDestroy, or 227 // DiffCreate. If an instance within the module has a DiffDestroyCreate 228 // then this will register as a DiffCreate for a module. 229 func (d *ModuleDiff) ChangeType() DiffChangeType { 230 result := DiffNone 231 for _, r := range d.Resources { 232 change := r.ChangeType() 233 switch change { 234 case DiffCreate, DiffDestroy: 235 if result == DiffNone { 236 result = change 237 } 238 case DiffDestroyCreate, DiffUpdate: 239 result = DiffUpdate 240 } 241 } 242 243 return result 244 } 245 246 // Empty returns true if the diff has no changes within this module. 247 func (d *ModuleDiff) Empty() bool { 248 if d.Destroy { 249 return false 250 } 251 252 if len(d.Resources) == 0 { 253 return true 254 } 255 256 for _, rd := range d.Resources { 257 if !rd.Empty() { 258 return false 259 } 260 } 261 262 return true 263 } 264 265 // Instances returns the instance diffs for the id given. This can return 266 // multiple instance diffs if there are counts within the resource. 267 func (d *ModuleDiff) Instances(id string) []*InstanceDiff { 268 var result []*InstanceDiff 269 for k, diff := range d.Resources { 270 if k == id || strings.HasPrefix(k, id+".") { 271 if !diff.Empty() { 272 result = append(result, diff) 273 } 274 } 275 } 276 277 return result 278 } 279 280 // IsRoot says whether or not this module diff is for the root module. 281 func (d *ModuleDiff) IsRoot() bool { 282 return reflect.DeepEqual(d.Path, rootModulePath) 283 } 284 285 // String outputs the diff in a long but command-line friendly output 286 // format that users can read to quickly inspect a diff. 287 func (d *ModuleDiff) String() string { 288 var buf bytes.Buffer 289 290 names := make([]string, 0, len(d.Resources)) 291 for name, _ := range d.Resources { 292 names = append(names, name) 293 } 294 sort.Strings(names) 295 296 for _, name := range names { 297 rdiff := d.Resources[name] 298 299 crud := "UPDATE" 300 switch { 301 case rdiff.RequiresNew() && (rdiff.GetDestroy() || rdiff.GetDestroyTainted()): 302 crud = "DESTROY/CREATE" 303 case rdiff.GetDestroy() || rdiff.GetDestroyDeposed(): 304 crud = "DESTROY" 305 case rdiff.RequiresNew(): 306 crud = "CREATE" 307 } 308 309 extra := "" 310 if !rdiff.GetDestroy() && rdiff.GetDestroyDeposed() { 311 extra = " (deposed only)" 312 } 313 314 buf.WriteString(fmt.Sprintf( 315 "%s: %s%s\n", 316 crud, 317 name, 318 extra)) 319 320 keyLen := 0 321 rdiffAttrs := rdiff.CopyAttributes() 322 keys := make([]string, 0, len(rdiffAttrs)) 323 for key, _ := range rdiffAttrs { 324 if key == "id" { 325 continue 326 } 327 328 keys = append(keys, key) 329 if len(key) > keyLen { 330 keyLen = len(key) 331 } 332 } 333 sort.Strings(keys) 334 335 for _, attrK := range keys { 336 attrDiff, _ := rdiff.GetAttribute(attrK) 337 338 v := attrDiff.New 339 u := attrDiff.Old 340 if attrDiff.NewComputed { 341 v = "<computed>" 342 } 343 344 if attrDiff.Sensitive { 345 u = "<sensitive>" 346 v = "<sensitive>" 347 } 348 349 updateMsg := "" 350 if attrDiff.RequiresNew { 351 updateMsg = " (forces new resource)" 352 } else if attrDiff.Sensitive { 353 updateMsg = " (attribute changed)" 354 } 355 356 buf.WriteString(fmt.Sprintf( 357 " %s:%s %#v => %#v%s\n", 358 attrK, 359 strings.Repeat(" ", keyLen-len(attrK)), 360 u, 361 v, 362 updateMsg)) 363 } 364 } 365 366 return buf.String() 367 } 368 369 // InstanceDiff is the diff of a resource from some state to another. 370 type InstanceDiff struct { 371 mu sync.Mutex 372 Attributes map[string]*ResourceAttrDiff 373 Destroy bool 374 DestroyDeposed bool 375 DestroyTainted bool 376 377 // Meta is a simple K/V map that is stored in a diff and persisted to 378 // plans but otherwise is completely ignored by Terraform core. It is 379 // meant to be used for additional data a resource may want to pass through. 380 // The value here must only contain Go primitives and collections. 381 Meta map[string]interface{} 382 } 383 384 func (d *InstanceDiff) Lock() { d.mu.Lock() } 385 func (d *InstanceDiff) Unlock() { d.mu.Unlock() } 386 387 // ResourceAttrDiff is the diff of a single attribute of a resource. 388 type ResourceAttrDiff struct { 389 Old string // Old Value 390 New string // New Value 391 NewComputed bool // True if new value is computed (unknown currently) 392 NewRemoved bool // True if this attribute is being removed 393 NewExtra interface{} // Extra information for the provider 394 RequiresNew bool // True if change requires new resource 395 Sensitive bool // True if the data should not be displayed in UI output 396 Type DiffAttrType 397 } 398 399 // Empty returns true if the diff for this attr is neutral 400 func (d *ResourceAttrDiff) Empty() bool { 401 return d.Old == d.New && !d.NewComputed && !d.NewRemoved 402 } 403 404 func (d *ResourceAttrDiff) GoString() string { 405 return fmt.Sprintf("*%#v", *d) 406 } 407 408 // DiffAttrType is an enum type that says whether a resource attribute 409 // diff is an input attribute (comes from the configuration) or an 410 // output attribute (comes as a result of applying the configuration). An 411 // example input would be "ami" for AWS and an example output would be 412 // "private_ip". 413 type DiffAttrType byte 414 415 const ( 416 DiffAttrUnknown DiffAttrType = iota 417 DiffAttrInput 418 DiffAttrOutput 419 ) 420 421 func (d *InstanceDiff) init() { 422 if d.Attributes == nil { 423 d.Attributes = make(map[string]*ResourceAttrDiff) 424 } 425 } 426 427 func NewInstanceDiff() *InstanceDiff { 428 return &InstanceDiff{Attributes: make(map[string]*ResourceAttrDiff)} 429 } 430 431 func (d *InstanceDiff) Copy() (*InstanceDiff, error) { 432 if d == nil { 433 return nil, nil 434 } 435 436 dCopy, err := copystructure.Config{Lock: true}.Copy(d) 437 if err != nil { 438 return nil, err 439 } 440 441 return dCopy.(*InstanceDiff), nil 442 } 443 444 // ChangeType returns the DiffChangeType represented by the diff 445 // for this single instance. 446 func (d *InstanceDiff) ChangeType() DiffChangeType { 447 if d.Empty() { 448 return DiffNone 449 } 450 451 if d.RequiresNew() && (d.GetDestroy() || d.GetDestroyTainted()) { 452 return DiffDestroyCreate 453 } 454 455 if d.GetDestroy() || d.GetDestroyDeposed() { 456 return DiffDestroy 457 } 458 459 if d.RequiresNew() { 460 return DiffCreate 461 } 462 463 return DiffUpdate 464 } 465 466 // Empty returns true if this diff encapsulates no changes. 467 func (d *InstanceDiff) Empty() bool { 468 if d == nil { 469 return true 470 } 471 472 d.mu.Lock() 473 defer d.mu.Unlock() 474 return !d.Destroy && 475 !d.DestroyTainted && 476 !d.DestroyDeposed && 477 len(d.Attributes) == 0 478 } 479 480 // Equal compares two diffs for exact equality. 481 // 482 // This is different from the Same comparison that is supported which 483 // checks for operation equality taking into account computed values. Equal 484 // instead checks for exact equality. 485 func (d *InstanceDiff) Equal(d2 *InstanceDiff) bool { 486 // If one is nil, they must both be nil 487 if d == nil || d2 == nil { 488 return d == d2 489 } 490 491 // Use DeepEqual 492 return reflect.DeepEqual(d, d2) 493 } 494 495 // DeepCopy performs a deep copy of all parts of the InstanceDiff 496 func (d *InstanceDiff) DeepCopy() *InstanceDiff { 497 copy, err := copystructure.Config{Lock: true}.Copy(d) 498 if err != nil { 499 panic(err) 500 } 501 502 return copy.(*InstanceDiff) 503 } 504 505 func (d *InstanceDiff) GoString() string { 506 return fmt.Sprintf("*%#v", InstanceDiff{ 507 Attributes: d.Attributes, 508 Destroy: d.Destroy, 509 DestroyTainted: d.DestroyTainted, 510 DestroyDeposed: d.DestroyDeposed, 511 }) 512 } 513 514 // RequiresNew returns true if the diff requires the creation of a new 515 // resource (implying the destruction of the old). 516 func (d *InstanceDiff) RequiresNew() bool { 517 if d == nil { 518 return false 519 } 520 521 d.mu.Lock() 522 defer d.mu.Unlock() 523 524 return d.requiresNew() 525 } 526 527 func (d *InstanceDiff) requiresNew() bool { 528 if d == nil { 529 return false 530 } 531 532 if d.DestroyTainted { 533 return true 534 } 535 536 for _, rd := range d.Attributes { 537 if rd != nil && rd.RequiresNew { 538 return true 539 } 540 } 541 542 return false 543 } 544 545 func (d *InstanceDiff) GetDestroyDeposed() bool { 546 d.mu.Lock() 547 defer d.mu.Unlock() 548 549 return d.DestroyDeposed 550 } 551 552 func (d *InstanceDiff) SetDestroyDeposed(b bool) { 553 d.mu.Lock() 554 defer d.mu.Unlock() 555 556 d.DestroyDeposed = b 557 } 558 559 // These methods are properly locked, for use outside other InstanceDiff 560 // methods but everywhere else within the terraform package. 561 // TODO refactor the locking scheme 562 func (d *InstanceDiff) SetTainted(b bool) { 563 d.mu.Lock() 564 defer d.mu.Unlock() 565 566 d.DestroyTainted = b 567 } 568 569 func (d *InstanceDiff) GetDestroyTainted() bool { 570 d.mu.Lock() 571 defer d.mu.Unlock() 572 573 return d.DestroyTainted 574 } 575 576 func (d *InstanceDiff) SetDestroy(b bool) { 577 d.mu.Lock() 578 defer d.mu.Unlock() 579 580 d.Destroy = b 581 } 582 583 func (d *InstanceDiff) GetDestroy() bool { 584 d.mu.Lock() 585 defer d.mu.Unlock() 586 587 return d.Destroy 588 } 589 590 func (d *InstanceDiff) SetAttribute(key string, attr *ResourceAttrDiff) { 591 d.mu.Lock() 592 defer d.mu.Unlock() 593 594 d.Attributes[key] = attr 595 } 596 597 func (d *InstanceDiff) DelAttribute(key string) { 598 d.mu.Lock() 599 defer d.mu.Unlock() 600 601 delete(d.Attributes, key) 602 } 603 604 func (d *InstanceDiff) GetAttribute(key string) (*ResourceAttrDiff, bool) { 605 d.mu.Lock() 606 defer d.mu.Unlock() 607 608 attr, ok := d.Attributes[key] 609 return attr, ok 610 } 611 func (d *InstanceDiff) GetAttributesLen() int { 612 d.mu.Lock() 613 defer d.mu.Unlock() 614 615 return len(d.Attributes) 616 } 617 618 // Safely copies the Attributes map 619 func (d *InstanceDiff) CopyAttributes() map[string]*ResourceAttrDiff { 620 d.mu.Lock() 621 defer d.mu.Unlock() 622 623 attrs := make(map[string]*ResourceAttrDiff) 624 for k, v := range d.Attributes { 625 attrs[k] = v 626 } 627 628 return attrs 629 } 630 631 // Same checks whether or not two InstanceDiff's are the "same". When 632 // we say "same", it is not necessarily exactly equal. Instead, it is 633 // just checking that the same attributes are changing, a destroy 634 // isn't suddenly happening, etc. 635 func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) { 636 // we can safely compare the pointers without a lock 637 switch { 638 case d == nil && d2 == nil: 639 return true, "" 640 case d == nil || d2 == nil: 641 return false, "one nil" 642 case d == d2: 643 return true, "" 644 } 645 646 d.mu.Lock() 647 defer d.mu.Unlock() 648 649 // If we're going from requiring new to NOT requiring new, then we have 650 // to see if all required news were computed. If so, it is allowed since 651 // computed may also mean "same value and therefore not new". 652 oldNew := d.requiresNew() 653 newNew := d2.RequiresNew() 654 if oldNew && !newNew { 655 oldNew = false 656 657 // This section builds a list of ignorable attributes for requiresNew 658 // by removing off any elements of collections going to zero elements. 659 // For collections going to zero, they may not exist at all in the 660 // new diff (and hence RequiresNew == false). 661 ignoreAttrs := make(map[string]struct{}) 662 for k, diffOld := range d.Attributes { 663 if !strings.HasSuffix(k, ".%") && !strings.HasSuffix(k, ".#") { 664 continue 665 } 666 667 // This case is in here as a protection measure. The bug that this 668 // code originally fixed (GH-11349) didn't have to deal with computed 669 // so I'm not 100% sure what the correct behavior is. Best to leave 670 // the old behavior. 671 if diffOld.NewComputed { 672 continue 673 } 674 675 // We're looking for the case a map goes to exactly 0. 676 if diffOld.New != "0" { 677 continue 678 } 679 680 // Found it! Ignore all of these. The prefix here is stripping 681 // off the "%" so it is just "k." 682 prefix := k[:len(k)-1] 683 for k2, _ := range d.Attributes { 684 if strings.HasPrefix(k2, prefix) { 685 ignoreAttrs[k2] = struct{}{} 686 } 687 } 688 } 689 690 for k, rd := range d.Attributes { 691 if _, ok := ignoreAttrs[k]; ok { 692 continue 693 } 694 695 // If the field is requires new and NOT computed, then what 696 // we have is a diff mismatch for sure. We set that the old 697 // diff does REQUIRE a ForceNew. 698 if rd != nil && rd.RequiresNew && !rd.NewComputed { 699 oldNew = true 700 break 701 } 702 } 703 } 704 705 if oldNew != newNew { 706 return false, fmt.Sprintf( 707 "diff RequiresNew; old: %t, new: %t", oldNew, newNew) 708 } 709 710 // Verify that destroy matches. The second boolean here allows us to 711 // have mismatching Destroy if we're moving from RequiresNew true 712 // to false above. Therefore, the second boolean will only pass if 713 // we're moving from Destroy: true to false as well. 714 if d.Destroy != d2.GetDestroy() && d.requiresNew() == oldNew { 715 return false, fmt.Sprintf( 716 "diff: Destroy; old: %t, new: %t", d.Destroy, d2.GetDestroy()) 717 } 718 719 // Go through the old diff and make sure the new diff has all the 720 // same attributes. To start, build up the check map to be all the keys. 721 checkOld := make(map[string]struct{}) 722 checkNew := make(map[string]struct{}) 723 for k, _ := range d.Attributes { 724 checkOld[k] = struct{}{} 725 } 726 for k, _ := range d2.CopyAttributes() { 727 checkNew[k] = struct{}{} 728 } 729 730 // Make an ordered list so we are sure the approximated hashes are left 731 // to process at the end of the loop 732 keys := make([]string, 0, len(d.Attributes)) 733 for k, _ := range d.Attributes { 734 keys = append(keys, k) 735 } 736 sort.StringSlice(keys).Sort() 737 738 for _, k := range keys { 739 diffOld := d.Attributes[k] 740 741 if _, ok := checkOld[k]; !ok { 742 // We're not checking this key for whatever reason (see where 743 // check is modified). 744 continue 745 } 746 747 // Remove this key since we'll never hit it again 748 delete(checkOld, k) 749 delete(checkNew, k) 750 751 _, ok := d2.GetAttribute(k) 752 if !ok { 753 // If there's no new attribute, and the old diff expected the attribute 754 // to be removed, that's just fine. 755 if diffOld.NewRemoved { 756 continue 757 } 758 759 // If the last diff was a computed value then the absense of 760 // that value is allowed since it may mean the value ended up 761 // being the same. 762 if diffOld.NewComputed { 763 ok = true 764 } 765 766 // No exact match, but maybe this is a set containing computed 767 // values. So check if there is an approximate hash in the key 768 // and if so, try to match the key. 769 if strings.Contains(k, "~") { 770 parts := strings.Split(k, ".") 771 parts2 := append([]string(nil), parts...) 772 773 re := regexp.MustCompile(`^~\d+$`) 774 for i, part := range parts { 775 if re.MatchString(part) { 776 // we're going to consider this the base of a 777 // computed hash, and remove all longer matching fields 778 ok = true 779 780 parts2[i] = `\d+` 781 parts2 = parts2[:i+1] 782 break 783 } 784 } 785 786 re, err := regexp.Compile("^" + strings.Join(parts2, `\.`)) 787 if err != nil { 788 return false, fmt.Sprintf("regexp failed to compile; err: %#v", err) 789 } 790 791 for k2, _ := range checkNew { 792 if re.MatchString(k2) { 793 delete(checkNew, k2) 794 } 795 } 796 } 797 798 // This is a little tricky, but when a diff contains a computed 799 // list, set, or map that can only be interpolated after the apply 800 // command has created the dependent resources, it could turn out 801 // that the result is actually the same as the existing state which 802 // would remove the key from the diff. 803 if diffOld.NewComputed && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) { 804 ok = true 805 } 806 807 // Similarly, in a RequiresNew scenario, a list that shows up in the plan 808 // diff can disappear from the apply diff, which is calculated from an 809 // empty state. 810 if d.requiresNew() && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) { 811 ok = true 812 } 813 814 if !ok { 815 return false, fmt.Sprintf("attribute mismatch: %s", k) 816 } 817 } 818 819 // search for the suffix of the base of a [computed] map, list or set. 820 match := multiVal.FindStringSubmatch(k) 821 822 if diffOld.NewComputed && len(match) == 2 { 823 matchLen := len(match[1]) 824 825 // This is a computed list, set, or map, so remove any keys with 826 // this prefix from the check list. 827 kprefix := k[:len(k)-matchLen] 828 for k2, _ := range checkOld { 829 if strings.HasPrefix(k2, kprefix) { 830 delete(checkOld, k2) 831 } 832 } 833 for k2, _ := range checkNew { 834 if strings.HasPrefix(k2, kprefix) { 835 delete(checkNew, k2) 836 } 837 } 838 } 839 840 // We don't compare the values because we can't currently actually 841 // guarantee to generate the same value two two diffs created from 842 // the same state+config: we have some pesky interpolation functions 843 // that do not behave as pure functions (uuid, timestamp) and so they 844 // can be different each time a diff is produced. 845 // FIXME: Re-organize our config handling so that we don't re-evaluate 846 // expressions when we produce a second comparison diff during 847 // apply (for EvalCompareDiff). 848 } 849 850 // Check for leftover attributes 851 if len(checkNew) > 0 { 852 extras := make([]string, 0, len(checkNew)) 853 for attr, _ := range checkNew { 854 extras = append(extras, attr) 855 } 856 return false, 857 fmt.Sprintf("extra attributes: %s", strings.Join(extras, ", ")) 858 } 859 860 return true, "" 861 } 862 863 // moduleDiffSort implements sort.Interface to sort module diffs by path. 864 type moduleDiffSort []*ModuleDiff 865 866 func (s moduleDiffSort) Len() int { return len(s) } 867 func (s moduleDiffSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 868 func (s moduleDiffSort) Less(i, j int) bool { 869 a := s[i] 870 b := s[j] 871 872 // If the lengths are different, then the shorter one always wins 873 if len(a.Path) != len(b.Path) { 874 return len(a.Path) < len(b.Path) 875 } 876 877 // Otherwise, compare lexically 878 return strings.Join(a.Path, ".") < strings.Join(b.Path, ".") 879 }