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