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