github.com/rhenning/terraform@v0.8.0-beta2/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(): 295 crud = "DESTROY" 296 case rdiff.RequiresNew(): 297 crud = "CREATE" 298 } 299 300 buf.WriteString(fmt.Sprintf( 301 "%s: %s\n", 302 crud, 303 name)) 304 305 keyLen := 0 306 rdiffAttrs := rdiff.CopyAttributes() 307 keys := make([]string, 0, len(rdiffAttrs)) 308 for key, _ := range rdiffAttrs { 309 if key == "id" { 310 continue 311 } 312 313 keys = append(keys, key) 314 if len(key) > keyLen { 315 keyLen = len(key) 316 } 317 } 318 sort.Strings(keys) 319 320 for _, attrK := range keys { 321 attrDiff, _ := rdiff.GetAttribute(attrK) 322 323 v := attrDiff.New 324 u := attrDiff.Old 325 if attrDiff.NewComputed { 326 v = "<computed>" 327 } 328 329 if attrDiff.Sensitive { 330 u = "<sensitive>" 331 v = "<sensitive>" 332 } 333 334 updateMsg := "" 335 if attrDiff.RequiresNew { 336 updateMsg = " (forces new resource)" 337 } else if attrDiff.Sensitive { 338 updateMsg = " (attribute changed)" 339 } 340 341 buf.WriteString(fmt.Sprintf( 342 " %s:%s %#v => %#v%s\n", 343 attrK, 344 strings.Repeat(" ", keyLen-len(attrK)), 345 u, 346 v, 347 updateMsg)) 348 } 349 } 350 351 return buf.String() 352 } 353 354 // InstanceDiff is the diff of a resource from some state to another. 355 type InstanceDiff struct { 356 mu sync.Mutex 357 Attributes map[string]*ResourceAttrDiff 358 Destroy bool 359 DestroyTainted bool 360 } 361 362 func (d *InstanceDiff) Lock() { d.mu.Lock() } 363 func (d *InstanceDiff) Unlock() { d.mu.Unlock() } 364 365 // ResourceAttrDiff is the diff of a single attribute of a resource. 366 type ResourceAttrDiff struct { 367 Old string // Old Value 368 New string // New Value 369 NewComputed bool // True if new value is computed (unknown currently) 370 NewRemoved bool // True if this attribute is being removed 371 NewExtra interface{} // Extra information for the provider 372 RequiresNew bool // True if change requires new resource 373 Sensitive bool // True if the data should not be displayed in UI output 374 Type DiffAttrType 375 } 376 377 // Empty returns true if the diff for this attr is neutral 378 func (d *ResourceAttrDiff) Empty() bool { 379 return d.Old == d.New && !d.NewComputed && !d.NewRemoved 380 } 381 382 func (d *ResourceAttrDiff) GoString() string { 383 return fmt.Sprintf("*%#v", *d) 384 } 385 386 // DiffAttrType is an enum type that says whether a resource attribute 387 // diff is an input attribute (comes from the configuration) or an 388 // output attribute (comes as a result of applying the configuration). An 389 // example input would be "ami" for AWS and an example output would be 390 // "private_ip". 391 type DiffAttrType byte 392 393 const ( 394 DiffAttrUnknown DiffAttrType = iota 395 DiffAttrInput 396 DiffAttrOutput 397 ) 398 399 func (d *InstanceDiff) init() { 400 if d.Attributes == nil { 401 d.Attributes = make(map[string]*ResourceAttrDiff) 402 } 403 } 404 405 func NewInstanceDiff() *InstanceDiff { 406 return &InstanceDiff{Attributes: make(map[string]*ResourceAttrDiff)} 407 } 408 409 func (d *InstanceDiff) Copy() (*InstanceDiff, error) { 410 if d == nil { 411 return nil, nil 412 } 413 414 dCopy, err := copystructure.Config{Lock: true}.Copy(d) 415 if err != nil { 416 return nil, err 417 } 418 419 return dCopy.(*InstanceDiff), nil 420 } 421 422 // ChangeType returns the DiffChangeType represented by the diff 423 // for this single instance. 424 func (d *InstanceDiff) ChangeType() DiffChangeType { 425 if d.Empty() { 426 return DiffNone 427 } 428 429 if d.RequiresNew() && (d.GetDestroy() || d.GetDestroyTainted()) { 430 return DiffDestroyCreate 431 } 432 433 if d.GetDestroy() { 434 return DiffDestroy 435 } 436 437 if d.RequiresNew() { 438 return DiffCreate 439 } 440 441 return DiffUpdate 442 } 443 444 // Empty returns true if this diff encapsulates no changes. 445 func (d *InstanceDiff) Empty() bool { 446 if d == nil { 447 return true 448 } 449 450 d.mu.Lock() 451 defer d.mu.Unlock() 452 return !d.Destroy && !d.DestroyTainted && len(d.Attributes) == 0 453 } 454 455 // Equal compares two diffs for exact equality. 456 // 457 // This is different from the Same comparison that is supported which 458 // checks for operation equality taking into account computed values. Equal 459 // instead checks for exact equality. 460 func (d *InstanceDiff) Equal(d2 *InstanceDiff) bool { 461 // If one is nil, they must both be nil 462 if d == nil || d2 == nil { 463 return d == d2 464 } 465 466 // Use DeepEqual 467 return reflect.DeepEqual(d, d2) 468 } 469 470 // DeepCopy performs a deep copy of all parts of the InstanceDiff 471 func (d *InstanceDiff) DeepCopy() *InstanceDiff { 472 copy, err := copystructure.Config{Lock: true}.Copy(d) 473 if err != nil { 474 panic(err) 475 } 476 477 return copy.(*InstanceDiff) 478 } 479 480 func (d *InstanceDiff) GoString() string { 481 return fmt.Sprintf("*%#v", InstanceDiff{ 482 Attributes: d.Attributes, 483 Destroy: d.Destroy, 484 DestroyTainted: d.DestroyTainted, 485 }) 486 } 487 488 // RequiresNew returns true if the diff requires the creation of a new 489 // resource (implying the destruction of the old). 490 func (d *InstanceDiff) RequiresNew() bool { 491 if d == nil { 492 return false 493 } 494 495 d.mu.Lock() 496 defer d.mu.Unlock() 497 498 return d.requiresNew() 499 } 500 501 func (d *InstanceDiff) requiresNew() bool { 502 if d == nil { 503 return false 504 } 505 506 if d.DestroyTainted { 507 return true 508 } 509 510 for _, rd := range d.Attributes { 511 if rd != nil && rd.RequiresNew { 512 return true 513 } 514 } 515 516 return false 517 } 518 519 // These methods are properly locked, for use outside other InstanceDiff 520 // methods but everywhere else within in the terraform package. 521 // TODO refactor the locking scheme 522 func (d *InstanceDiff) SetTainted(b bool) { 523 d.mu.Lock() 524 defer d.mu.Unlock() 525 526 d.DestroyTainted = b 527 } 528 529 func (d *InstanceDiff) GetDestroyTainted() bool { 530 d.mu.Lock() 531 defer d.mu.Unlock() 532 533 return d.DestroyTainted 534 } 535 536 func (d *InstanceDiff) SetDestroy(b bool) { 537 d.mu.Lock() 538 defer d.mu.Unlock() 539 540 d.Destroy = b 541 } 542 543 func (d *InstanceDiff) GetDestroy() bool { 544 d.mu.Lock() 545 defer d.mu.Unlock() 546 547 return d.Destroy 548 } 549 550 func (d *InstanceDiff) SetAttribute(key string, attr *ResourceAttrDiff) { 551 d.mu.Lock() 552 defer d.mu.Unlock() 553 554 d.Attributes[key] = attr 555 } 556 557 func (d *InstanceDiff) DelAttribute(key string) { 558 d.mu.Lock() 559 defer d.mu.Unlock() 560 561 delete(d.Attributes, key) 562 } 563 564 func (d *InstanceDiff) GetAttribute(key string) (*ResourceAttrDiff, bool) { 565 d.mu.Lock() 566 defer d.mu.Unlock() 567 568 attr, ok := d.Attributes[key] 569 return attr, ok 570 } 571 func (d *InstanceDiff) GetAttributesLen() int { 572 d.mu.Lock() 573 defer d.mu.Unlock() 574 575 return len(d.Attributes) 576 } 577 578 // Safely copies the Attributes map 579 func (d *InstanceDiff) CopyAttributes() map[string]*ResourceAttrDiff { 580 d.mu.Lock() 581 defer d.mu.Unlock() 582 583 attrs := make(map[string]*ResourceAttrDiff) 584 for k, v := range d.Attributes { 585 attrs[k] = v 586 } 587 588 return attrs 589 } 590 591 // Same checks whether or not two InstanceDiff's are the "same". When 592 // we say "same", it is not necessarily exactly equal. Instead, it is 593 // just checking that the same attributes are changing, a destroy 594 // isn't suddenly happening, etc. 595 func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) { 596 // we can safely compare the pointers without a lock 597 switch { 598 case d == nil && d2 == nil: 599 return true, "" 600 case d == nil || d2 == nil: 601 return false, "one nil" 602 case d == d2: 603 return true, "" 604 } 605 606 d.mu.Lock() 607 defer d.mu.Unlock() 608 609 // If we're going from requiring new to NOT requiring new, then we have 610 // to see if all required news were computed. If so, it is allowed since 611 // computed may also mean "same value and therefore not new". 612 oldNew := d.requiresNew() 613 newNew := d2.RequiresNew() 614 if oldNew && !newNew { 615 oldNew = false 616 for _, rd := range d.Attributes { 617 // If the field is requires new and NOT computed, then what 618 // we have is a diff mismatch for sure. We set that the old 619 // diff does REQUIRE a ForceNew. 620 if rd != nil && rd.RequiresNew && !rd.NewComputed { 621 oldNew = true 622 break 623 } 624 } 625 } 626 627 if oldNew != newNew { 628 return false, fmt.Sprintf( 629 "diff RequiresNew; old: %t, new: %t", oldNew, newNew) 630 } 631 632 // Verify that destroy matches. The second boolean here allows us to 633 // have mismatching Destroy if we're moving from RequiresNew true 634 // to false above. Therefore, the second boolean will only pass if 635 // we're moving from Destroy: true to false as well. 636 if d.Destroy != d2.GetDestroy() && d.requiresNew() == oldNew { 637 return false, fmt.Sprintf( 638 "diff: Destroy; old: %t, new: %t", d.Destroy, d2.GetDestroy()) 639 } 640 641 // Go through the old diff and make sure the new diff has all the 642 // same attributes. To start, build up the check map to be all the keys. 643 checkOld := make(map[string]struct{}) 644 checkNew := make(map[string]struct{}) 645 for k, _ := range d.Attributes { 646 checkOld[k] = struct{}{} 647 } 648 for k, _ := range d2.CopyAttributes() { 649 checkNew[k] = struct{}{} 650 } 651 652 // Make an ordered list so we are sure the approximated hashes are left 653 // to process at the end of the loop 654 keys := make([]string, 0, len(d.Attributes)) 655 for k, _ := range d.Attributes { 656 keys = append(keys, k) 657 } 658 sort.StringSlice(keys).Sort() 659 660 for _, k := range keys { 661 diffOld := d.Attributes[k] 662 663 if _, ok := checkOld[k]; !ok { 664 // We're not checking this key for whatever reason (see where 665 // check is modified). 666 continue 667 } 668 669 // Remove this key since we'll never hit it again 670 delete(checkOld, k) 671 delete(checkNew, k) 672 673 _, ok := d2.GetAttribute(k) 674 if !ok { 675 // If there's no new attribute, and the old diff expected the attribute 676 // to be removed, that's just fine. 677 if diffOld.NewRemoved { 678 continue 679 } 680 681 // If the last diff was a computed value then the absense of 682 // that value is allowed since it may mean the value ended up 683 // being the same. 684 if diffOld.NewComputed { 685 ok = true 686 } 687 688 // No exact match, but maybe this is a set containing computed 689 // values. So check if there is an approximate hash in the key 690 // and if so, try to match the key. 691 if strings.Contains(k, "~") { 692 parts := strings.Split(k, ".") 693 parts2 := append([]string(nil), parts...) 694 695 re := regexp.MustCompile(`^~\d+$`) 696 for i, part := range parts { 697 if re.MatchString(part) { 698 // we're going to consider this the base of a 699 // computed hash, and remove all longer matching fields 700 ok = true 701 702 parts2[i] = `\d+` 703 parts2 = parts2[:i+1] 704 break 705 } 706 } 707 708 re, err := regexp.Compile("^" + strings.Join(parts2, `\.`)) 709 if err != nil { 710 return false, fmt.Sprintf("regexp failed to compile; err: %#v", err) 711 } 712 713 for k2, _ := range checkNew { 714 if re.MatchString(k2) { 715 delete(checkNew, k2) 716 } 717 } 718 } 719 720 // This is a little tricky, but when a diff contains a computed 721 // list, set, or map that can only be interpolated after the apply 722 // command has created the dependent resources, it could turn out 723 // that the result is actually the same as the existing state which 724 // would remove the key from the diff. 725 if diffOld.NewComputed && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) { 726 ok = true 727 } 728 729 // Similarly, in a RequiresNew scenario, a list that shows up in the plan 730 // diff can disappear from the apply diff, which is calculated from an 731 // empty state. 732 if d.requiresNew() && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) { 733 ok = true 734 } 735 736 if !ok { 737 return false, fmt.Sprintf("attribute mismatch: %s", k) 738 } 739 } 740 741 // search for the suffix of the base of a [computed] map, list or set. 742 multiVal := regexp.MustCompile(`\.(#|~#|%)$`) 743 match := multiVal.FindStringSubmatch(k) 744 745 if diffOld.NewComputed && len(match) == 2 { 746 matchLen := len(match[1]) 747 748 // This is a computed list, set, or map, so remove any keys with 749 // this prefix from the check list. 750 kprefix := k[:len(k)-matchLen] 751 for k2, _ := range checkOld { 752 if strings.HasPrefix(k2, kprefix) { 753 delete(checkOld, k2) 754 } 755 } 756 for k2, _ := range checkNew { 757 if strings.HasPrefix(k2, kprefix) { 758 delete(checkNew, k2) 759 } 760 } 761 } 762 763 // TODO: check for the same value if not computed 764 } 765 766 // Check for leftover attributes 767 if len(checkNew) > 0 { 768 extras := make([]string, 0, len(checkNew)) 769 for attr, _ := range checkNew { 770 extras = append(extras, attr) 771 } 772 return false, 773 fmt.Sprintf("extra attributes: %s", strings.Join(extras, ", ")) 774 } 775 776 return true, "" 777 } 778 779 // moduleDiffSort implements sort.Interface to sort module diffs by path. 780 type moduleDiffSort []*ModuleDiff 781 782 func (s moduleDiffSort) Len() int { return len(s) } 783 func (s moduleDiffSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 784 func (s moduleDiffSort) Less(i, j int) bool { 785 a := s[i] 786 b := s[j] 787 788 // If the lengths are different, then the shorter one always wins 789 if len(a.Path) != len(b.Path) { 790 return len(a.Path) < len(b.Path) 791 } 792 793 // Otherwise, compare lexically 794 return strings.Join(a.Path, ".") < strings.Join(b.Path, ".") 795 }