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