github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/diff.go (about) 1 package terraform 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "log" 8 "reflect" 9 "regexp" 10 "sort" 11 "strconv" 12 "strings" 13 "sync" 14 15 "github.com/hashicorp/terraform-plugin-sdk/internal/addrs" 16 "github.com/hashicorp/terraform-plugin-sdk/internal/configs/configschema" 17 "github.com/hashicorp/terraform-plugin-sdk/internal/configs/hcl2shim" 18 "github.com/zclconf/go-cty/cty" 19 20 "github.com/mitchellh/copystructure" 21 ) 22 23 // DiffChangeType is an enum with the kind of changes a diff has planned. 24 type DiffChangeType byte 25 26 const ( 27 DiffInvalid DiffChangeType = iota 28 DiffNone 29 DiffCreate 30 DiffUpdate 31 DiffDestroy 32 DiffDestroyCreate 33 34 // DiffRefresh is only used in the UI for displaying diffs. 35 // Managed resource reads never appear in plan, and when data source 36 // reads appear they are represented as DiffCreate in core before 37 // transforming to DiffRefresh in the UI layer. 38 DiffRefresh // TODO: Actually use DiffRefresh in core too, for less confusion 39 ) 40 41 // multiVal matches the index key to a flatmapped set, list or map 42 var multiVal = regexp.MustCompile(`\.(#|%)$`) 43 44 // Diff tracks the changes that are necessary to apply a configuration 45 // to an existing infrastructure. 46 type Diff struct { 47 // Modules contains all the modules that have a diff 48 Modules []*ModuleDiff 49 } 50 51 // Prune cleans out unused structures in the diff without affecting 52 // the behavior of the diff at all. 53 // 54 // This is not safe to call concurrently. This is safe to call on a 55 // nil Diff. 56 func (d *Diff) Prune() { 57 if d == nil { 58 return 59 } 60 61 // Prune all empty modules 62 newModules := make([]*ModuleDiff, 0, len(d.Modules)) 63 for _, m := range d.Modules { 64 // If the module isn't empty, we keep it 65 if !m.Empty() { 66 newModules = append(newModules, m) 67 } 68 } 69 if len(newModules) == 0 { 70 newModules = nil 71 } 72 d.Modules = newModules 73 } 74 75 // AddModule adds the module with the given path to the diff. 76 // 77 // This should be the preferred method to add module diffs since it 78 // allows us to optimize lookups later as well as control sorting. 79 func (d *Diff) AddModule(path addrs.ModuleInstance) *ModuleDiff { 80 // Lower the new-style address into a legacy-style address. 81 // This requires that none of the steps have instance keys, which is 82 // true for all addresses at the time of implementing this because 83 // "count" and "for_each" are not yet implemented for modules. 84 legacyPath := make([]string, len(path)) 85 for i, step := range path { 86 if step.InstanceKey != addrs.NoKey { 87 // FIXME: Once the rest of Terraform is ready to use count and 88 // for_each, remove all of this and just write the addrs.ModuleInstance 89 // value itself into the ModuleState. 90 panic("diff cannot represent modules with count or for_each keys") 91 } 92 93 legacyPath[i] = step.Name 94 } 95 96 m := &ModuleDiff{Path: legacyPath} 97 m.init() 98 d.Modules = append(d.Modules, m) 99 return m 100 } 101 102 // ModuleByPath is used to lookup the module diff for the given path. 103 // This should be the preferred lookup mechanism as it allows for future 104 // lookup optimizations. 105 func (d *Diff) ModuleByPath(path addrs.ModuleInstance) *ModuleDiff { 106 if d == nil { 107 return nil 108 } 109 for _, mod := range d.Modules { 110 if mod.Path == nil { 111 panic("missing module path") 112 } 113 modPath := normalizeModulePath(mod.Path) 114 if modPath.String() == path.String() { 115 return mod 116 } 117 } 118 return nil 119 } 120 121 // RootModule returns the ModuleState for the root module 122 func (d *Diff) RootModule() *ModuleDiff { 123 root := d.ModuleByPath(addrs.RootModuleInstance) 124 if root == nil { 125 panic("missing root module") 126 } 127 return root 128 } 129 130 // Empty returns true if the diff has no changes. 131 func (d *Diff) Empty() bool { 132 if d == nil { 133 return true 134 } 135 136 for _, m := range d.Modules { 137 if !m.Empty() { 138 return false 139 } 140 } 141 142 return true 143 } 144 145 // Equal compares two diffs for exact equality. 146 // 147 // This is different from the Same comparison that is supported which 148 // checks for operation equality taking into account computed values. Equal 149 // instead checks for exact equality. 150 func (d *Diff) Equal(d2 *Diff) bool { 151 // If one is nil, they must both be nil 152 if d == nil || d2 == nil { 153 return d == d2 154 } 155 156 // Sort the modules 157 sort.Sort(moduleDiffSort(d.Modules)) 158 sort.Sort(moduleDiffSort(d2.Modules)) 159 160 // Copy since we have to modify the module destroy flag to false so 161 // we don't compare that. TODO: delete this when we get rid of the 162 // destroy flag on modules. 163 dCopy := d.DeepCopy() 164 d2Copy := d2.DeepCopy() 165 for _, m := range dCopy.Modules { 166 m.Destroy = false 167 } 168 for _, m := range d2Copy.Modules { 169 m.Destroy = false 170 } 171 172 // Use DeepEqual 173 return reflect.DeepEqual(dCopy, d2Copy) 174 } 175 176 // DeepCopy performs a deep copy of all parts of the Diff, making the 177 // resulting Diff safe to use without modifying this one. 178 func (d *Diff) DeepCopy() *Diff { 179 copy, err := copystructure.Config{Lock: true}.Copy(d) 180 if err != nil { 181 panic(err) 182 } 183 184 return copy.(*Diff) 185 } 186 187 func (d *Diff) String() string { 188 var buf bytes.Buffer 189 190 keys := make([]string, 0, len(d.Modules)) 191 lookup := make(map[string]*ModuleDiff) 192 for _, m := range d.Modules { 193 addr := normalizeModulePath(m.Path) 194 key := addr.String() 195 keys = append(keys, key) 196 lookup[key] = m 197 } 198 sort.Strings(keys) 199 200 for _, key := range keys { 201 m := lookup[key] 202 mStr := m.String() 203 204 // If we're the root module, we just write the output directly. 205 if reflect.DeepEqual(m.Path, rootModulePath) { 206 buf.WriteString(mStr + "\n") 207 continue 208 } 209 210 buf.WriteString(fmt.Sprintf("%s:\n", key)) 211 212 s := bufio.NewScanner(strings.NewReader(mStr)) 213 for s.Scan() { 214 buf.WriteString(fmt.Sprintf(" %s\n", s.Text())) 215 } 216 } 217 218 return strings.TrimSpace(buf.String()) 219 } 220 221 // ModuleDiff tracks the differences between resources to apply within 222 // a single module. 223 type ModuleDiff struct { 224 Path []string 225 Resources map[string]*InstanceDiff 226 Destroy bool // Set only by the destroy plan 227 } 228 229 func (d *ModuleDiff) init() { 230 if d.Resources == nil { 231 d.Resources = make(map[string]*InstanceDiff) 232 } 233 for _, r := range d.Resources { 234 r.init() 235 } 236 } 237 238 // ChangeType returns the type of changes that the diff for this 239 // module includes. 240 // 241 // At a module level, this will only be DiffNone, DiffUpdate, DiffDestroy, or 242 // DiffCreate. If an instance within the module has a DiffDestroyCreate 243 // then this will register as a DiffCreate for a module. 244 func (d *ModuleDiff) ChangeType() DiffChangeType { 245 result := DiffNone 246 for _, r := range d.Resources { 247 change := r.ChangeType() 248 switch change { 249 case DiffCreate, DiffDestroy: 250 if result == DiffNone { 251 result = change 252 } 253 case DiffDestroyCreate, DiffUpdate: 254 result = DiffUpdate 255 } 256 } 257 258 return result 259 } 260 261 // Empty returns true if the diff has no changes within this module. 262 func (d *ModuleDiff) Empty() bool { 263 if d.Destroy { 264 return false 265 } 266 267 if len(d.Resources) == 0 { 268 return true 269 } 270 271 for _, rd := range d.Resources { 272 if !rd.Empty() { 273 return false 274 } 275 } 276 277 return true 278 } 279 280 // Instances returns the instance diffs for the id given. This can return 281 // multiple instance diffs if there are counts within the resource. 282 func (d *ModuleDiff) Instances(id string) []*InstanceDiff { 283 var result []*InstanceDiff 284 for k, diff := range d.Resources { 285 if k == id || strings.HasPrefix(k, id+".") { 286 if !diff.Empty() { 287 result = append(result, diff) 288 } 289 } 290 } 291 292 return result 293 } 294 295 // IsRoot says whether or not this module diff is for the root module. 296 func (d *ModuleDiff) IsRoot() bool { 297 return reflect.DeepEqual(d.Path, rootModulePath) 298 } 299 300 // String outputs the diff in a long but command-line friendly output 301 // format that users can read to quickly inspect a diff. 302 func (d *ModuleDiff) String() string { 303 var buf bytes.Buffer 304 305 names := make([]string, 0, len(d.Resources)) 306 for name := range d.Resources { 307 names = append(names, name) 308 } 309 sort.Strings(names) 310 311 for _, name := range names { 312 rdiff := d.Resources[name] 313 314 crud := "UPDATE" 315 switch { 316 case rdiff.RequiresNew() && (rdiff.GetDestroy() || rdiff.GetDestroyTainted()): 317 crud = "DESTROY/CREATE" 318 case rdiff.GetDestroy() || rdiff.GetDestroyDeposed(): 319 crud = "DESTROY" 320 case rdiff.RequiresNew(): 321 crud = "CREATE" 322 } 323 324 extra := "" 325 if !rdiff.GetDestroy() && rdiff.GetDestroyDeposed() { 326 extra = " (deposed only)" 327 } 328 329 buf.WriteString(fmt.Sprintf( 330 "%s: %s%s\n", 331 crud, 332 name, 333 extra)) 334 335 keyLen := 0 336 rdiffAttrs := rdiff.CopyAttributes() 337 keys := make([]string, 0, len(rdiffAttrs)) 338 for key := range rdiffAttrs { 339 if key == "id" { 340 continue 341 } 342 343 keys = append(keys, key) 344 if len(key) > keyLen { 345 keyLen = len(key) 346 } 347 } 348 sort.Strings(keys) 349 350 for _, attrK := range keys { 351 attrDiff, _ := rdiff.GetAttribute(attrK) 352 353 v := attrDiff.New 354 u := attrDiff.Old 355 if attrDiff.NewComputed { 356 v = "<computed>" 357 } 358 359 if attrDiff.Sensitive { 360 u = "<sensitive>" 361 v = "<sensitive>" 362 } 363 364 updateMsg := "" 365 if attrDiff.RequiresNew { 366 updateMsg = " (forces new resource)" 367 } else if attrDiff.Sensitive { 368 updateMsg = " (attribute changed)" 369 } 370 371 buf.WriteString(fmt.Sprintf( 372 " %s:%s %#v => %#v%s\n", 373 attrK, 374 strings.Repeat(" ", keyLen-len(attrK)), 375 u, 376 v, 377 updateMsg)) 378 } 379 } 380 381 return buf.String() 382 } 383 384 // InstanceDiff is the diff of a resource from some state to another. 385 type InstanceDiff struct { 386 mu sync.Mutex 387 Attributes map[string]*ResourceAttrDiff 388 Destroy bool 389 DestroyDeposed bool 390 DestroyTainted bool 391 392 // Meta is a simple K/V map that is stored in a diff and persisted to 393 // plans but otherwise is completely ignored by Terraform core. It is 394 // meant to be used for additional data a resource may want to pass through. 395 // The value here must only contain Go primitives and collections. 396 Meta map[string]interface{} 397 } 398 399 func (d *InstanceDiff) Lock() { d.mu.Lock() } 400 func (d *InstanceDiff) Unlock() { d.mu.Unlock() } 401 402 // ApplyToValue merges the receiver into the given base value, returning a 403 // new value that incorporates the planned changes. The given value must 404 // conform to the given schema, or this method will panic. 405 // 406 // This method is intended for shimming old subsystems that still use this 407 // legacy diff type to work with the new-style types. 408 func (d *InstanceDiff) ApplyToValue(base cty.Value, schema *configschema.Block) (cty.Value, error) { 409 // Create an InstanceState attributes from our existing state. 410 // We can use this to more easily apply the diff changes. 411 attrs := hcl2shim.FlatmapValueFromHCL2(base) 412 applied, err := d.Apply(attrs, schema) 413 if err != nil { 414 return base, err 415 } 416 417 val, err := hcl2shim.HCL2ValueFromFlatmap(applied, schema.ImpliedType()) 418 if err != nil { 419 return base, err 420 } 421 422 return schema.CoerceValue(val) 423 } 424 425 // Apply applies the diff to the provided flatmapped attributes, 426 // returning the new instance attributes. 427 // 428 // This method is intended for shimming old subsystems that still use this 429 // legacy diff type to work with the new-style types. 430 func (d *InstanceDiff) Apply(attrs map[string]string, schema *configschema.Block) (map[string]string, error) { 431 // We always build a new value here, even if the given diff is "empty", 432 // because we might be planning to create a new instance that happens 433 // to have no attributes set, and so we want to produce an empty object 434 // rather than just echoing back the null old value. 435 if attrs == nil { 436 attrs = map[string]string{} 437 } 438 439 // Rather applying the diff to mutate the attrs, we'll copy new values into 440 // here to avoid the possibility of leaving stale values. 441 result := map[string]string{} 442 443 if d.Destroy || d.DestroyDeposed || d.DestroyTainted { 444 return result, nil 445 } 446 447 return d.applyBlockDiff(nil, attrs, schema) 448 } 449 450 func (d *InstanceDiff) applyBlockDiff(path []string, attrs map[string]string, schema *configschema.Block) (map[string]string, error) { 451 result := map[string]string{} 452 name := "" 453 if len(path) > 0 { 454 name = path[len(path)-1] 455 } 456 457 // localPrefix is used to build the local result map 458 localPrefix := "" 459 if name != "" { 460 localPrefix = name + "." 461 } 462 463 // iterate over the schema rather than the attributes, so we can handle 464 // different block types separately from plain attributes 465 for n, attrSchema := range schema.Attributes { 466 var err error 467 newAttrs, err := d.applyAttrDiff(append(path, n), attrs, attrSchema) 468 469 if err != nil { 470 return result, err 471 } 472 473 for k, v := range newAttrs { 474 result[localPrefix+k] = v 475 } 476 } 477 478 blockPrefix := strings.Join(path, ".") 479 if blockPrefix != "" { 480 blockPrefix += "." 481 } 482 for n, block := range schema.BlockTypes { 483 // we need to find the set of all keys that traverse this block 484 candidateKeys := map[string]bool{} 485 blockKey := blockPrefix + n + "." 486 localBlockPrefix := localPrefix + n + "." 487 488 // we can only trust the diff for sets, since the path changes, so don't 489 // count existing values as candidate keys. If it turns out we're 490 // keeping the attributes, we will catch it down below with "keepBlock" 491 // after we check the set count. 492 if block.Nesting != configschema.NestingSet { 493 for k := range attrs { 494 if strings.HasPrefix(k, blockKey) { 495 nextDot := strings.Index(k[len(blockKey):], ".") 496 if nextDot < 0 { 497 continue 498 } 499 nextDot += len(blockKey) 500 candidateKeys[k[len(blockKey):nextDot]] = true 501 } 502 } 503 } 504 505 for k, diff := range d.Attributes { 506 // helper/schema should not insert nil diff values, but don't panic 507 // if it does. 508 if diff == nil { 509 continue 510 } 511 512 if strings.HasPrefix(k, blockKey) { 513 nextDot := strings.Index(k[len(blockKey):], ".") 514 if nextDot < 0 { 515 continue 516 } 517 518 if diff.NewRemoved { 519 continue 520 } 521 522 nextDot += len(blockKey) 523 candidateKeys[k[len(blockKey):nextDot]] = true 524 } 525 } 526 527 // check each set candidate to see if it was removed. 528 // we need to do this, because when entire sets are removed, they may 529 // have the wrong key, and ony show diffs going to "" 530 if block.Nesting == configschema.NestingSet { 531 for k := range candidateKeys { 532 indexPrefix := strings.Join(append(path, n, k), ".") + "." 533 keep := false 534 // now check each set element to see if it's a new diff, or one 535 // that we're dropping. Since we're only applying the "New" 536 // portion of the set, we can ignore diffs that only contain "Old" 537 for attr, diff := range d.Attributes { 538 // helper/schema should not insert nil diff values, but don't panic 539 // if it does. 540 if diff == nil { 541 continue 542 } 543 544 if !strings.HasPrefix(attr, indexPrefix) { 545 continue 546 } 547 548 // check for empty "count" keys 549 if (strings.HasSuffix(attr, ".#") || strings.HasSuffix(attr, ".%")) && diff.New == "0" { 550 continue 551 } 552 553 // removed items don't count either 554 if diff.NewRemoved { 555 continue 556 } 557 558 // this must be a diff to keep 559 keep = true 560 break 561 } 562 if !keep { 563 delete(candidateKeys, k) 564 } 565 } 566 } 567 568 for k := range candidateKeys { 569 newAttrs, err := d.applyBlockDiff(append(path, n, k), attrs, &block.Block) 570 if err != nil { 571 return result, err 572 } 573 574 for attr, v := range newAttrs { 575 result[localBlockPrefix+attr] = v 576 } 577 } 578 579 keepBlock := true 580 // check this block's count diff directly first, since we may not 581 // have candidates because it was removed and only set to "0" 582 if diff, ok := d.Attributes[blockKey+"#"]; ok { 583 if diff.New == "0" || diff.NewRemoved { 584 keepBlock = false 585 } 586 } 587 588 // if there was no diff at all, then we need to keep the block attributes 589 if len(candidateKeys) == 0 && keepBlock { 590 for k, v := range attrs { 591 if strings.HasPrefix(k, blockKey) { 592 // we need the key relative to this block, so remove the 593 // entire prefix, then re-insert the block name. 594 localKey := localBlockPrefix + k[len(blockKey):] 595 result[localKey] = v 596 } 597 } 598 } 599 600 countAddr := strings.Join(append(path, n, "#"), ".") 601 if countDiff, ok := d.Attributes[countAddr]; ok { 602 if countDiff.NewComputed { 603 result[localBlockPrefix+"#"] = hcl2shim.UnknownVariableValue 604 } else { 605 result[localBlockPrefix+"#"] = countDiff.New 606 607 // While sets are complete, list are not, and we may not have all the 608 // information to track removals. If the list was truncated, we need to 609 // remove the extra items from the result. 610 if block.Nesting == configschema.NestingList && 611 countDiff.New != "" && countDiff.New != hcl2shim.UnknownVariableValue { 612 length, _ := strconv.Atoi(countDiff.New) 613 for k := range result { 614 if !strings.HasPrefix(k, localBlockPrefix) { 615 continue 616 } 617 618 index := k[len(localBlockPrefix):] 619 nextDot := strings.Index(index, ".") 620 if nextDot < 1 { 621 continue 622 } 623 index = index[:nextDot] 624 i, err := strconv.Atoi(index) 625 if err != nil { 626 // this shouldn't happen since we added these 627 // ourself, but make note of it just in case. 628 log.Printf("[ERROR] bad list index in %q: %s", k, err) 629 continue 630 } 631 if i >= length { 632 delete(result, k) 633 } 634 } 635 } 636 } 637 } else if origCount, ok := attrs[countAddr]; ok && keepBlock { 638 result[localBlockPrefix+"#"] = origCount 639 } else { 640 result[localBlockPrefix+"#"] = countFlatmapContainerValues(localBlockPrefix+"#", result) 641 } 642 } 643 644 return result, nil 645 } 646 647 func (d *InstanceDiff) applyAttrDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) { 648 ty := attrSchema.Type 649 switch { 650 case ty.IsListType(), ty.IsTupleType(), ty.IsMapType(): 651 return d.applyCollectionDiff(path, attrs, attrSchema) 652 case ty.IsSetType(): 653 return d.applySetDiff(path, attrs, attrSchema) 654 default: 655 return d.applySingleAttrDiff(path, attrs, attrSchema) 656 } 657 } 658 659 func (d *InstanceDiff) applySingleAttrDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) { 660 currentKey := strings.Join(path, ".") 661 662 attr := path[len(path)-1] 663 664 result := map[string]string{} 665 diff := d.Attributes[currentKey] 666 old, exists := attrs[currentKey] 667 668 if diff != nil && diff.NewComputed { 669 result[attr] = hcl2shim.UnknownVariableValue 670 return result, nil 671 } 672 673 // "id" must exist and not be an empty string, or it must be unknown. 674 // This only applied to top-level "id" fields. 675 if attr == "id" && len(path) == 1 { 676 if old == "" { 677 result[attr] = hcl2shim.UnknownVariableValue 678 } else { 679 result[attr] = old 680 } 681 return result, nil 682 } 683 684 // attribute diffs are sometimes missed, so assume no diff means keep the 685 // old value 686 if diff == nil { 687 if exists { 688 result[attr] = old 689 } else { 690 // We need required values, so set those with an empty value. It 691 // must be set in the config, since if it were missing it would have 692 // failed validation. 693 if attrSchema.Required { 694 // we only set a missing string here, since bool or number types 695 // would have distinct zero value which shouldn't have been 696 // lost. 697 if attrSchema.Type == cty.String { 698 result[attr] = "" 699 } 700 } 701 } 702 return result, nil 703 } 704 705 // check for missmatched diff values 706 if exists && 707 old != diff.Old && 708 old != hcl2shim.UnknownVariableValue && 709 diff.Old != hcl2shim.UnknownVariableValue { 710 return result, fmt.Errorf("diff apply conflict for %s: diff expects %q, but prior value has %q", attr, diff.Old, old) 711 } 712 713 if diff.NewRemoved { 714 // don't set anything in the new value 715 return map[string]string{}, nil 716 } 717 718 if diff.Old == diff.New && diff.New == "" { 719 // this can only be a valid empty string 720 if attrSchema.Type == cty.String { 721 result[attr] = "" 722 } 723 return result, nil 724 } 725 726 if attrSchema.Computed && diff.NewComputed { 727 result[attr] = hcl2shim.UnknownVariableValue 728 return result, nil 729 } 730 731 result[attr] = diff.New 732 733 return result, nil 734 } 735 736 func (d *InstanceDiff) applyCollectionDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) { 737 result := map[string]string{} 738 739 prefix := "" 740 if len(path) > 1 { 741 prefix = strings.Join(path[:len(path)-1], ".") + "." 742 } 743 744 name := "" 745 if len(path) > 0 { 746 name = path[len(path)-1] 747 } 748 749 currentKey := prefix + name 750 751 // check the index first for special handling 752 for k, diff := range d.Attributes { 753 // check the index value, which can be set, and 0 754 if k == currentKey+".#" || k == currentKey+".%" || k == currentKey { 755 if diff.NewRemoved { 756 return result, nil 757 } 758 759 if diff.NewComputed { 760 result[k[len(prefix):]] = hcl2shim.UnknownVariableValue 761 return result, nil 762 } 763 764 // do what the diff tells us to here, so that it's consistent with applies 765 if diff.New == "0" { 766 result[k[len(prefix):]] = "0" 767 return result, nil 768 } 769 } 770 } 771 772 // collect all the keys from the diff and the old state 773 noDiff := true 774 keys := map[string]bool{} 775 for k := range d.Attributes { 776 if !strings.HasPrefix(k, currentKey+".") { 777 continue 778 } 779 noDiff = false 780 keys[k] = true 781 } 782 783 noAttrs := true 784 for k := range attrs { 785 if !strings.HasPrefix(k, currentKey+".") { 786 continue 787 } 788 noAttrs = false 789 keys[k] = true 790 } 791 792 // If there's no diff and no attrs, then there's no value at all. 793 // This prevents an unexpected zero-count attribute in the attributes. 794 if noDiff && noAttrs { 795 return result, nil 796 } 797 798 idx := "#" 799 if attrSchema.Type.IsMapType() { 800 idx = "%" 801 } 802 803 for k := range keys { 804 // generate an schema placeholder for the values 805 elSchema := &configschema.Attribute{ 806 Type: attrSchema.Type.ElementType(), 807 } 808 809 res, err := d.applySingleAttrDiff(append(path, k[len(currentKey)+1:]), attrs, elSchema) 810 if err != nil { 811 return result, err 812 } 813 814 for k, v := range res { 815 result[name+"."+k] = v 816 } 817 } 818 819 // Just like in nested list blocks, for simple lists we may need to fill in 820 // missing empty strings. 821 countKey := name + "." + idx 822 count := result[countKey] 823 length, _ := strconv.Atoi(count) 824 825 if count != "" && count != hcl2shim.UnknownVariableValue && 826 attrSchema.Type.Equals(cty.List(cty.String)) { 827 // insert empty strings into missing indexes 828 for i := 0; i < length; i++ { 829 key := fmt.Sprintf("%s.%d", name, i) 830 if _, ok := result[key]; !ok { 831 result[key] = "" 832 } 833 } 834 } 835 836 // now check for truncation in any type of list 837 if attrSchema.Type.IsListType() { 838 for key := range result { 839 if key == countKey { 840 continue 841 } 842 843 if len(key) <= len(name)+1 { 844 // not sure what this is, but don't panic 845 continue 846 } 847 848 index := key[len(name)+1:] 849 850 // It is possible to have nested sets or maps, so look for another dot 851 dot := strings.Index(index, ".") 852 if dot > 0 { 853 index = index[:dot] 854 } 855 856 // This shouldn't have any more dots, since the element type is only string. 857 num, err := strconv.Atoi(index) 858 if err != nil { 859 log.Printf("[ERROR] bad list index in %q: %s", currentKey, err) 860 continue 861 } 862 863 if num >= length { 864 delete(result, key) 865 } 866 } 867 } 868 869 // Fill in the count value if it wasn't present in the diff for some reason, 870 // or if there is no count at all. 871 _, countDiff := d.Attributes[countKey] 872 if result[countKey] == "" || (!countDiff && len(keys) != len(result)) { 873 result[countKey] = countFlatmapContainerValues(countKey, result) 874 } 875 876 return result, nil 877 } 878 879 func (d *InstanceDiff) applySetDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) { 880 // We only need this special behavior for sets of object. 881 if !attrSchema.Type.ElementType().IsObjectType() { 882 // The normal collection apply behavior will work okay for this one, then. 883 return d.applyCollectionDiff(path, attrs, attrSchema) 884 } 885 886 // When we're dealing with a set of an object type we actually want to 887 // use our normal _block type_ apply behaviors, so we'll construct ourselves 888 // a synthetic schema that treats the object type as a block type and 889 // then delegate to our block apply method. 890 synthSchema := &configschema.Block{ 891 Attributes: make(map[string]*configschema.Attribute), 892 } 893 894 for name, ty := range attrSchema.Type.ElementType().AttributeTypes() { 895 // We can safely make everything into an attribute here because in the 896 // event that there are nested set attributes we'll end up back in 897 // here again recursively and can then deal with the next level of 898 // expansion. 899 synthSchema.Attributes[name] = &configschema.Attribute{ 900 Type: ty, 901 Optional: true, 902 } 903 } 904 905 parentPath := path[:len(path)-1] 906 childName := path[len(path)-1] 907 containerSchema := &configschema.Block{ 908 BlockTypes: map[string]*configschema.NestedBlock{ 909 childName: { 910 Nesting: configschema.NestingSet, 911 Block: *synthSchema, 912 }, 913 }, 914 } 915 916 return d.applyBlockDiff(parentPath, attrs, containerSchema) 917 } 918 919 // countFlatmapContainerValues returns the number of values in the flatmapped container 920 // (set, map, list) indexed by key. The key argument is expected to include the 921 // trailing ".#", or ".%". 922 func countFlatmapContainerValues(key string, attrs map[string]string) string { 923 if len(key) < 3 || !(strings.HasSuffix(key, ".#") || strings.HasSuffix(key, ".%")) { 924 panic(fmt.Sprintf("invalid index value %q", key)) 925 } 926 927 prefix := key[:len(key)-1] 928 items := map[string]int{} 929 930 for k := range attrs { 931 if k == key { 932 continue 933 } 934 if !strings.HasPrefix(k, prefix) { 935 continue 936 } 937 938 suffix := k[len(prefix):] 939 dot := strings.Index(suffix, ".") 940 if dot > 0 { 941 suffix = suffix[:dot] 942 } 943 944 items[suffix]++ 945 } 946 return strconv.Itoa(len(items)) 947 } 948 949 // ResourceAttrDiff is the diff of a single attribute of a resource. 950 type ResourceAttrDiff struct { 951 Old string // Old Value 952 New string // New Value 953 NewComputed bool // True if new value is computed (unknown currently) 954 NewRemoved bool // True if this attribute is being removed 955 NewExtra interface{} // Extra information for the provider 956 RequiresNew bool // True if change requires new resource 957 Sensitive bool // True if the data should not be displayed in UI output 958 Type DiffAttrType 959 } 960 961 // Empty returns true if the diff for this attr is neutral 962 func (d *ResourceAttrDiff) Empty() bool { 963 return d.Old == d.New && !d.NewComputed && !d.NewRemoved 964 } 965 966 func (d *ResourceAttrDiff) GoString() string { 967 return fmt.Sprintf("*%#v", *d) 968 } 969 970 // DiffAttrType is an enum type that says whether a resource attribute 971 // diff is an input attribute (comes from the configuration) or an 972 // output attribute (comes as a result of applying the configuration). An 973 // example input would be "ami" for AWS and an example output would be 974 // "private_ip". 975 type DiffAttrType byte 976 977 const ( 978 DiffAttrUnknown DiffAttrType = iota 979 DiffAttrInput 980 DiffAttrOutput 981 ) 982 983 func (d *InstanceDiff) init() { 984 if d.Attributes == nil { 985 d.Attributes = make(map[string]*ResourceAttrDiff) 986 } 987 } 988 989 func NewInstanceDiff() *InstanceDiff { 990 return &InstanceDiff{Attributes: make(map[string]*ResourceAttrDiff)} 991 } 992 993 func (d *InstanceDiff) Copy() (*InstanceDiff, error) { 994 if d == nil { 995 return nil, nil 996 } 997 998 dCopy, err := copystructure.Config{Lock: true}.Copy(d) 999 if err != nil { 1000 return nil, err 1001 } 1002 1003 return dCopy.(*InstanceDiff), nil 1004 } 1005 1006 // ChangeType returns the DiffChangeType represented by the diff 1007 // for this single instance. 1008 func (d *InstanceDiff) ChangeType() DiffChangeType { 1009 if d.Empty() { 1010 return DiffNone 1011 } 1012 1013 if d.RequiresNew() && (d.GetDestroy() || d.GetDestroyTainted()) { 1014 return DiffDestroyCreate 1015 } 1016 1017 if d.GetDestroy() || d.GetDestroyDeposed() { 1018 return DiffDestroy 1019 } 1020 1021 if d.RequiresNew() { 1022 return DiffCreate 1023 } 1024 1025 return DiffUpdate 1026 } 1027 1028 // Empty returns true if this diff encapsulates no changes. 1029 func (d *InstanceDiff) Empty() bool { 1030 if d == nil { 1031 return true 1032 } 1033 1034 d.mu.Lock() 1035 defer d.mu.Unlock() 1036 return !d.Destroy && 1037 !d.DestroyTainted && 1038 !d.DestroyDeposed && 1039 len(d.Attributes) == 0 1040 } 1041 1042 // Equal compares two diffs for exact equality. 1043 // 1044 // This is different from the Same comparison that is supported which 1045 // checks for operation equality taking into account computed values. Equal 1046 // instead checks for exact equality. 1047 func (d *InstanceDiff) Equal(d2 *InstanceDiff) bool { 1048 // If one is nil, they must both be nil 1049 if d == nil || d2 == nil { 1050 return d == d2 1051 } 1052 1053 // Use DeepEqual 1054 return reflect.DeepEqual(d, d2) 1055 } 1056 1057 // DeepCopy performs a deep copy of all parts of the InstanceDiff 1058 func (d *InstanceDiff) DeepCopy() *InstanceDiff { 1059 copy, err := copystructure.Config{Lock: true}.Copy(d) 1060 if err != nil { 1061 panic(err) 1062 } 1063 1064 return copy.(*InstanceDiff) 1065 } 1066 1067 func (d *InstanceDiff) GoString() string { 1068 return fmt.Sprintf("*%#v", InstanceDiff{ 1069 Attributes: d.Attributes, 1070 Destroy: d.Destroy, 1071 DestroyTainted: d.DestroyTainted, 1072 DestroyDeposed: d.DestroyDeposed, 1073 }) 1074 } 1075 1076 // RequiresNew returns true if the diff requires the creation of a new 1077 // resource (implying the destruction of the old). 1078 func (d *InstanceDiff) RequiresNew() bool { 1079 if d == nil { 1080 return false 1081 } 1082 1083 d.mu.Lock() 1084 defer d.mu.Unlock() 1085 1086 return d.requiresNew() 1087 } 1088 1089 func (d *InstanceDiff) requiresNew() bool { 1090 if d == nil { 1091 return false 1092 } 1093 1094 if d.DestroyTainted { 1095 return true 1096 } 1097 1098 for _, rd := range d.Attributes { 1099 if rd != nil && rd.RequiresNew { 1100 return true 1101 } 1102 } 1103 1104 return false 1105 } 1106 1107 func (d *InstanceDiff) GetDestroyDeposed() bool { 1108 d.mu.Lock() 1109 defer d.mu.Unlock() 1110 1111 return d.DestroyDeposed 1112 } 1113 1114 func (d *InstanceDiff) SetDestroyDeposed(b bool) { 1115 d.mu.Lock() 1116 defer d.mu.Unlock() 1117 1118 d.DestroyDeposed = b 1119 } 1120 1121 // These methods are properly locked, for use outside other InstanceDiff 1122 // methods but everywhere else within the terraform package. 1123 // TODO refactor the locking scheme 1124 func (d *InstanceDiff) SetTainted(b bool) { 1125 d.mu.Lock() 1126 defer d.mu.Unlock() 1127 1128 d.DestroyTainted = b 1129 } 1130 1131 func (d *InstanceDiff) GetDestroyTainted() bool { 1132 d.mu.Lock() 1133 defer d.mu.Unlock() 1134 1135 return d.DestroyTainted 1136 } 1137 1138 func (d *InstanceDiff) SetDestroy(b bool) { 1139 d.mu.Lock() 1140 defer d.mu.Unlock() 1141 1142 d.Destroy = b 1143 } 1144 1145 func (d *InstanceDiff) GetDestroy() bool { 1146 d.mu.Lock() 1147 defer d.mu.Unlock() 1148 1149 return d.Destroy 1150 } 1151 1152 func (d *InstanceDiff) SetAttribute(key string, attr *ResourceAttrDiff) { 1153 d.mu.Lock() 1154 defer d.mu.Unlock() 1155 1156 d.Attributes[key] = attr 1157 } 1158 1159 func (d *InstanceDiff) DelAttribute(key string) { 1160 d.mu.Lock() 1161 defer d.mu.Unlock() 1162 1163 delete(d.Attributes, key) 1164 } 1165 1166 func (d *InstanceDiff) GetAttribute(key string) (*ResourceAttrDiff, bool) { 1167 d.mu.Lock() 1168 defer d.mu.Unlock() 1169 1170 attr, ok := d.Attributes[key] 1171 return attr, ok 1172 } 1173 func (d *InstanceDiff) GetAttributesLen() int { 1174 d.mu.Lock() 1175 defer d.mu.Unlock() 1176 1177 return len(d.Attributes) 1178 } 1179 1180 // Safely copies the Attributes map 1181 func (d *InstanceDiff) CopyAttributes() map[string]*ResourceAttrDiff { 1182 d.mu.Lock() 1183 defer d.mu.Unlock() 1184 1185 attrs := make(map[string]*ResourceAttrDiff) 1186 for k, v := range d.Attributes { 1187 attrs[k] = v 1188 } 1189 1190 return attrs 1191 } 1192 1193 // Same checks whether or not two InstanceDiff's are the "same". When 1194 // we say "same", it is not necessarily exactly equal. Instead, it is 1195 // just checking that the same attributes are changing, a destroy 1196 // isn't suddenly happening, etc. 1197 func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) { 1198 // we can safely compare the pointers without a lock 1199 switch { 1200 case d == nil && d2 == nil: 1201 return true, "" 1202 case d == nil || d2 == nil: 1203 return false, "one nil" 1204 case d == d2: 1205 return true, "" 1206 } 1207 1208 d.mu.Lock() 1209 defer d.mu.Unlock() 1210 1211 // If we're going from requiring new to NOT requiring new, then we have 1212 // to see if all required news were computed. If so, it is allowed since 1213 // computed may also mean "same value and therefore not new". 1214 oldNew := d.requiresNew() 1215 newNew := d2.RequiresNew() 1216 if oldNew && !newNew { 1217 oldNew = false 1218 1219 // This section builds a list of ignorable attributes for requiresNew 1220 // by removing off any elements of collections going to zero elements. 1221 // For collections going to zero, they may not exist at all in the 1222 // new diff (and hence RequiresNew == false). 1223 ignoreAttrs := make(map[string]struct{}) 1224 for k, diffOld := range d.Attributes { 1225 if !strings.HasSuffix(k, ".%") && !strings.HasSuffix(k, ".#") { 1226 continue 1227 } 1228 1229 // This case is in here as a protection measure. The bug that this 1230 // code originally fixed (GH-11349) didn't have to deal with computed 1231 // so I'm not 100% sure what the correct behavior is. Best to leave 1232 // the old behavior. 1233 if diffOld.NewComputed { 1234 continue 1235 } 1236 1237 // We're looking for the case a map goes to exactly 0. 1238 if diffOld.New != "0" { 1239 continue 1240 } 1241 1242 // Found it! Ignore all of these. The prefix here is stripping 1243 // off the "%" so it is just "k." 1244 prefix := k[:len(k)-1] 1245 for k2 := range d.Attributes { 1246 if strings.HasPrefix(k2, prefix) { 1247 ignoreAttrs[k2] = struct{}{} 1248 } 1249 } 1250 } 1251 1252 for k, rd := range d.Attributes { 1253 if _, ok := ignoreAttrs[k]; ok { 1254 continue 1255 } 1256 1257 // If the field is requires new and NOT computed, then what 1258 // we have is a diff mismatch for sure. We set that the old 1259 // diff does REQUIRE a ForceNew. 1260 if rd != nil && rd.RequiresNew && !rd.NewComputed { 1261 oldNew = true 1262 break 1263 } 1264 } 1265 } 1266 1267 if oldNew != newNew { 1268 return false, fmt.Sprintf( 1269 "diff RequiresNew; old: %t, new: %t", oldNew, newNew) 1270 } 1271 1272 // Verify that destroy matches. The second boolean here allows us to 1273 // have mismatching Destroy if we're moving from RequiresNew true 1274 // to false above. Therefore, the second boolean will only pass if 1275 // we're moving from Destroy: true to false as well. 1276 if d.Destroy != d2.GetDestroy() && d.requiresNew() == oldNew { 1277 return false, fmt.Sprintf( 1278 "diff: Destroy; old: %t, new: %t", d.Destroy, d2.GetDestroy()) 1279 } 1280 1281 // Go through the old diff and make sure the new diff has all the 1282 // same attributes. To start, build up the check map to be all the keys. 1283 checkOld := make(map[string]struct{}) 1284 checkNew := make(map[string]struct{}) 1285 for k := range d.Attributes { 1286 checkOld[k] = struct{}{} 1287 } 1288 for k := range d2.CopyAttributes() { 1289 checkNew[k] = struct{}{} 1290 } 1291 1292 // Make an ordered list so we are sure the approximated hashes are left 1293 // to process at the end of the loop 1294 keys := make([]string, 0, len(d.Attributes)) 1295 for k := range d.Attributes { 1296 keys = append(keys, k) 1297 } 1298 sort.StringSlice(keys).Sort() 1299 1300 for _, k := range keys { 1301 diffOld := d.Attributes[k] 1302 1303 if _, ok := checkOld[k]; !ok { 1304 // We're not checking this key for whatever reason (see where 1305 // check is modified). 1306 continue 1307 } 1308 1309 // Remove this key since we'll never hit it again 1310 delete(checkOld, k) 1311 delete(checkNew, k) 1312 1313 _, ok := d2.GetAttribute(k) 1314 if !ok { 1315 // If there's no new attribute, and the old diff expected the attribute 1316 // to be removed, that's just fine. 1317 if diffOld.NewRemoved { 1318 continue 1319 } 1320 1321 // If the last diff was a computed value then the absense of 1322 // that value is allowed since it may mean the value ended up 1323 // being the same. 1324 if diffOld.NewComputed { 1325 ok = true 1326 } 1327 1328 // No exact match, but maybe this is a set containing computed 1329 // values. So check if there is an approximate hash in the key 1330 // and if so, try to match the key. 1331 if strings.Contains(k, "~") { 1332 parts := strings.Split(k, ".") 1333 parts2 := append([]string(nil), parts...) 1334 1335 re := regexp.MustCompile(`^~\d+$`) 1336 for i, part := range parts { 1337 if re.MatchString(part) { 1338 // we're going to consider this the base of a 1339 // computed hash, and remove all longer matching fields 1340 ok = true 1341 1342 parts2[i] = `\d+` 1343 parts2 = parts2[:i+1] 1344 break 1345 } 1346 } 1347 1348 re, err := regexp.Compile("^" + strings.Join(parts2, `\.`)) 1349 if err != nil { 1350 return false, fmt.Sprintf("regexp failed to compile; err: %#v", err) 1351 } 1352 1353 for k2 := range checkNew { 1354 if re.MatchString(k2) { 1355 delete(checkNew, k2) 1356 } 1357 } 1358 } 1359 1360 // This is a little tricky, but when a diff contains a computed 1361 // list, set, or map that can only be interpolated after the apply 1362 // command has created the dependent resources, it could turn out 1363 // that the result is actually the same as the existing state which 1364 // would remove the key from the diff. 1365 if diffOld.NewComputed && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) { 1366 ok = true 1367 } 1368 1369 // Similarly, in a RequiresNew scenario, a list that shows up in the plan 1370 // diff can disappear from the apply diff, which is calculated from an 1371 // empty state. 1372 if d.requiresNew() && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) { 1373 ok = true 1374 } 1375 1376 if !ok { 1377 return false, fmt.Sprintf("attribute mismatch: %s", k) 1378 } 1379 } 1380 1381 // search for the suffix of the base of a [computed] map, list or set. 1382 match := multiVal.FindStringSubmatch(k) 1383 1384 if diffOld.NewComputed && len(match) == 2 { 1385 matchLen := len(match[1]) 1386 1387 // This is a computed list, set, or map, so remove any keys with 1388 // this prefix from the check list. 1389 kprefix := k[:len(k)-matchLen] 1390 for k2 := range checkOld { 1391 if strings.HasPrefix(k2, kprefix) { 1392 delete(checkOld, k2) 1393 } 1394 } 1395 for k2 := range checkNew { 1396 if strings.HasPrefix(k2, kprefix) { 1397 delete(checkNew, k2) 1398 } 1399 } 1400 } 1401 1402 // We don't compare the values because we can't currently actually 1403 // guarantee to generate the same value two two diffs created from 1404 // the same state+config: we have some pesky interpolation functions 1405 // that do not behave as pure functions (uuid, timestamp) and so they 1406 // can be different each time a diff is produced. 1407 // FIXME: Re-organize our config handling so that we don't re-evaluate 1408 // expressions when we produce a second comparison diff during 1409 // apply (for EvalCompareDiff). 1410 } 1411 1412 // Check for leftover attributes 1413 if len(checkNew) > 0 { 1414 extras := make([]string, 0, len(checkNew)) 1415 for attr := range checkNew { 1416 extras = append(extras, attr) 1417 } 1418 return false, 1419 fmt.Sprintf("extra attributes: %s", strings.Join(extras, ", ")) 1420 } 1421 1422 return true, "" 1423 } 1424 1425 // moduleDiffSort implements sort.Interface to sort module diffs by path. 1426 type moduleDiffSort []*ModuleDiff 1427 1428 func (s moduleDiffSort) Len() int { return len(s) } 1429 func (s moduleDiffSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 1430 func (s moduleDiffSort) Less(i, j int) bool { 1431 a := s[i] 1432 b := s[j] 1433 1434 // If the lengths are different, then the shorter one always wins 1435 if len(a.Path) != len(b.Path) { 1436 return len(a.Path) < len(b.Path) 1437 } 1438 1439 // Otherwise, compare lexically 1440 return strings.Join(a.Path, ".") < strings.Join(b.Path, ".") 1441 }