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