github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/legacy/terraform/diff_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package terraform 5 6 import ( 7 "fmt" 8 "reflect" 9 "strconv" 10 "strings" 11 "testing" 12 13 "github.com/terramate-io/tf/addrs" 14 ) 15 16 func TestDiffEmpty(t *testing.T) { 17 var diff *Diff 18 if !diff.Empty() { 19 t.Fatal("should be empty") 20 } 21 22 diff = new(Diff) 23 if !diff.Empty() { 24 t.Fatal("should be empty") 25 } 26 27 mod := diff.AddModule(addrs.RootModuleInstance) 28 mod.Resources["nodeA"] = &InstanceDiff{ 29 Attributes: map[string]*ResourceAttrDiff{ 30 "foo": &ResourceAttrDiff{ 31 Old: "foo", 32 New: "bar", 33 }, 34 }, 35 } 36 37 if diff.Empty() { 38 t.Fatal("should not be empty") 39 } 40 } 41 42 func TestDiffEmpty_taintedIsNotEmpty(t *testing.T) { 43 diff := new(Diff) 44 45 mod := diff.AddModule(addrs.RootModuleInstance) 46 mod.Resources["nodeA"] = &InstanceDiff{ 47 DestroyTainted: true, 48 } 49 50 if diff.Empty() { 51 t.Fatal("should not be empty, since DestroyTainted was set") 52 } 53 } 54 55 func TestDiffEqual(t *testing.T) { 56 cases := map[string]struct { 57 D1, D2 *Diff 58 Equal bool 59 }{ 60 "nil": { 61 nil, 62 new(Diff), 63 false, 64 }, 65 66 "empty": { 67 new(Diff), 68 new(Diff), 69 true, 70 }, 71 72 "different module order": { 73 &Diff{ 74 Modules: []*ModuleDiff{ 75 &ModuleDiff{Path: []string{"root", "foo"}}, 76 &ModuleDiff{Path: []string{"root", "bar"}}, 77 }, 78 }, 79 &Diff{ 80 Modules: []*ModuleDiff{ 81 &ModuleDiff{Path: []string{"root", "bar"}}, 82 &ModuleDiff{Path: []string{"root", "foo"}}, 83 }, 84 }, 85 true, 86 }, 87 88 "different module diff destroys": { 89 &Diff{ 90 Modules: []*ModuleDiff{ 91 &ModuleDiff{Path: []string{"root", "foo"}, Destroy: true}, 92 }, 93 }, 94 &Diff{ 95 Modules: []*ModuleDiff{ 96 &ModuleDiff{Path: []string{"root", "foo"}, Destroy: false}, 97 }, 98 }, 99 true, 100 }, 101 } 102 103 for name, tc := range cases { 104 t.Run(name, func(t *testing.T) { 105 actual := tc.D1.Equal(tc.D2) 106 if actual != tc.Equal { 107 t.Fatalf("expected: %v\n\n%#v\n\n%#v", tc.Equal, tc.D1, tc.D2) 108 } 109 }) 110 } 111 } 112 113 func TestDiffPrune(t *testing.T) { 114 cases := map[string]struct { 115 D1, D2 *Diff 116 }{ 117 "nil": { 118 nil, 119 nil, 120 }, 121 122 "empty": { 123 new(Diff), 124 new(Diff), 125 }, 126 127 "empty module": { 128 &Diff{ 129 Modules: []*ModuleDiff{ 130 &ModuleDiff{Path: []string{"root", "foo"}}, 131 }, 132 }, 133 &Diff{}, 134 }, 135 136 "destroy module": { 137 &Diff{ 138 Modules: []*ModuleDiff{ 139 &ModuleDiff{Path: []string{"root", "foo"}, Destroy: true}, 140 }, 141 }, 142 &Diff{ 143 Modules: []*ModuleDiff{ 144 &ModuleDiff{Path: []string{"root", "foo"}, Destroy: true}, 145 }, 146 }, 147 }, 148 } 149 150 for name, tc := range cases { 151 t.Run(name, func(t *testing.T) { 152 tc.D1.Prune() 153 if !tc.D1.Equal(tc.D2) { 154 t.Fatalf("bad:\n\n%#v\n\n%#v", tc.D1, tc.D2) 155 } 156 }) 157 } 158 } 159 160 func TestModuleDiff_ChangeType(t *testing.T) { 161 cases := []struct { 162 Diff *ModuleDiff 163 Result DiffChangeType 164 }{ 165 { 166 &ModuleDiff{}, 167 DiffNone, 168 }, 169 { 170 &ModuleDiff{ 171 Resources: map[string]*InstanceDiff{ 172 "foo": &InstanceDiff{Destroy: true}, 173 }, 174 }, 175 DiffDestroy, 176 }, 177 { 178 &ModuleDiff{ 179 Resources: map[string]*InstanceDiff{ 180 "foo": &InstanceDiff{ 181 Attributes: map[string]*ResourceAttrDiff{ 182 "foo": &ResourceAttrDiff{ 183 Old: "", 184 New: "bar", 185 }, 186 }, 187 }, 188 }, 189 }, 190 DiffUpdate, 191 }, 192 { 193 &ModuleDiff{ 194 Resources: map[string]*InstanceDiff{ 195 "foo": &InstanceDiff{ 196 Attributes: map[string]*ResourceAttrDiff{ 197 "foo": &ResourceAttrDiff{ 198 Old: "", 199 New: "bar", 200 RequiresNew: true, 201 }, 202 }, 203 }, 204 }, 205 }, 206 DiffCreate, 207 }, 208 { 209 &ModuleDiff{ 210 Resources: map[string]*InstanceDiff{ 211 "foo": &InstanceDiff{ 212 Destroy: true, 213 Attributes: map[string]*ResourceAttrDiff{ 214 "foo": &ResourceAttrDiff{ 215 Old: "", 216 New: "bar", 217 RequiresNew: true, 218 }, 219 }, 220 }, 221 }, 222 }, 223 DiffUpdate, 224 }, 225 } 226 227 for i, tc := range cases { 228 actual := tc.Diff.ChangeType() 229 if actual != tc.Result { 230 t.Fatalf("%d: %#v", i, actual) 231 } 232 } 233 } 234 235 func TestDiff_DeepCopy(t *testing.T) { 236 cases := map[string]*Diff{ 237 "empty": &Diff{}, 238 239 "basic diff": &Diff{ 240 Modules: []*ModuleDiff{ 241 &ModuleDiff{ 242 Path: []string{"root"}, 243 Resources: map[string]*InstanceDiff{ 244 "aws_instance.foo": &InstanceDiff{ 245 Attributes: map[string]*ResourceAttrDiff{ 246 "num": &ResourceAttrDiff{ 247 Old: "0", 248 New: "2", 249 }, 250 }, 251 }, 252 }, 253 }, 254 }, 255 }, 256 } 257 258 for name, tc := range cases { 259 t.Run(name, func(t *testing.T) { 260 dup := tc.DeepCopy() 261 if !reflect.DeepEqual(dup, tc) { 262 t.Fatalf("\n%#v\n\n%#v", dup, tc) 263 } 264 }) 265 } 266 } 267 268 func TestModuleDiff_Empty(t *testing.T) { 269 diff := new(ModuleDiff) 270 if !diff.Empty() { 271 t.Fatal("should be empty") 272 } 273 274 diff.Resources = map[string]*InstanceDiff{ 275 "nodeA": &InstanceDiff{}, 276 } 277 278 if !diff.Empty() { 279 t.Fatal("should be empty") 280 } 281 282 diff.Resources["nodeA"].Attributes = map[string]*ResourceAttrDiff{ 283 "foo": &ResourceAttrDiff{ 284 Old: "foo", 285 New: "bar", 286 }, 287 } 288 289 if diff.Empty() { 290 t.Fatal("should not be empty") 291 } 292 293 diff.Resources["nodeA"].Attributes = nil 294 diff.Resources["nodeA"].Destroy = true 295 296 if diff.Empty() { 297 t.Fatal("should not be empty") 298 } 299 } 300 301 func TestModuleDiff_String(t *testing.T) { 302 diff := &ModuleDiff{ 303 Resources: map[string]*InstanceDiff{ 304 "nodeA": &InstanceDiff{ 305 Attributes: map[string]*ResourceAttrDiff{ 306 "foo": &ResourceAttrDiff{ 307 Old: "foo", 308 New: "bar", 309 }, 310 "bar": &ResourceAttrDiff{ 311 Old: "foo", 312 NewComputed: true, 313 }, 314 "longfoo": &ResourceAttrDiff{ 315 Old: "foo", 316 New: "bar", 317 RequiresNew: true, 318 }, 319 "secretfoo": &ResourceAttrDiff{ 320 Old: "foo", 321 New: "bar", 322 Sensitive: true, 323 }, 324 }, 325 }, 326 }, 327 } 328 329 actual := strings.TrimSpace(diff.String()) 330 expected := strings.TrimSpace(moduleDiffStrBasic) 331 if actual != expected { 332 t.Fatalf("bad:\n%s", actual) 333 } 334 } 335 336 func TestInstanceDiff_ChangeType(t *testing.T) { 337 cases := []struct { 338 Diff *InstanceDiff 339 Result DiffChangeType 340 }{ 341 { 342 &InstanceDiff{}, 343 DiffNone, 344 }, 345 { 346 &InstanceDiff{Destroy: true}, 347 DiffDestroy, 348 }, 349 { 350 &InstanceDiff{ 351 Attributes: map[string]*ResourceAttrDiff{ 352 "foo": &ResourceAttrDiff{ 353 Old: "", 354 New: "bar", 355 }, 356 }, 357 }, 358 DiffUpdate, 359 }, 360 { 361 &InstanceDiff{ 362 Attributes: map[string]*ResourceAttrDiff{ 363 "foo": &ResourceAttrDiff{ 364 Old: "", 365 New: "bar", 366 RequiresNew: true, 367 }, 368 }, 369 }, 370 DiffCreate, 371 }, 372 { 373 &InstanceDiff{ 374 Destroy: true, 375 Attributes: map[string]*ResourceAttrDiff{ 376 "foo": &ResourceAttrDiff{ 377 Old: "", 378 New: "bar", 379 RequiresNew: true, 380 }, 381 }, 382 }, 383 DiffDestroyCreate, 384 }, 385 { 386 &InstanceDiff{ 387 DestroyTainted: true, 388 Attributes: map[string]*ResourceAttrDiff{ 389 "foo": &ResourceAttrDiff{ 390 Old: "", 391 New: "bar", 392 RequiresNew: true, 393 }, 394 }, 395 }, 396 DiffDestroyCreate, 397 }, 398 } 399 400 for i, tc := range cases { 401 actual := tc.Diff.ChangeType() 402 if actual != tc.Result { 403 t.Fatalf("%d: %#v", i, actual) 404 } 405 } 406 } 407 408 func TestInstanceDiff_Empty(t *testing.T) { 409 var rd *InstanceDiff 410 411 if !rd.Empty() { 412 t.Fatal("should be empty") 413 } 414 415 rd = new(InstanceDiff) 416 417 if !rd.Empty() { 418 t.Fatal("should be empty") 419 } 420 421 rd = &InstanceDiff{Destroy: true} 422 423 if rd.Empty() { 424 t.Fatal("should not be empty") 425 } 426 427 rd = &InstanceDiff{ 428 Attributes: map[string]*ResourceAttrDiff{ 429 "foo": &ResourceAttrDiff{ 430 New: "bar", 431 }, 432 }, 433 } 434 435 if rd.Empty() { 436 t.Fatal("should not be empty") 437 } 438 } 439 440 func TestModuleDiff_Instances(t *testing.T) { 441 yesDiff := &InstanceDiff{Destroy: true} 442 noDiff := &InstanceDiff{Destroy: true, DestroyTainted: true} 443 444 cases := []struct { 445 Diff *ModuleDiff 446 Id string 447 Result []*InstanceDiff 448 }{ 449 { 450 &ModuleDiff{ 451 Resources: map[string]*InstanceDiff{ 452 "foo": yesDiff, 453 "bar": noDiff, 454 }, 455 }, 456 "foo", 457 []*InstanceDiff{ 458 yesDiff, 459 }, 460 }, 461 462 { 463 &ModuleDiff{ 464 Resources: map[string]*InstanceDiff{ 465 "foo": yesDiff, 466 "foo.0": yesDiff, 467 "bar": noDiff, 468 }, 469 }, 470 "foo", 471 []*InstanceDiff{ 472 yesDiff, 473 yesDiff, 474 }, 475 }, 476 477 { 478 &ModuleDiff{ 479 Resources: map[string]*InstanceDiff{ 480 "foo": yesDiff, 481 "foo.0": yesDiff, 482 "foo_bar": noDiff, 483 "bar": noDiff, 484 }, 485 }, 486 "foo", 487 []*InstanceDiff{ 488 yesDiff, 489 yesDiff, 490 }, 491 }, 492 } 493 494 for i, tc := range cases { 495 actual := tc.Diff.Instances(tc.Id) 496 if !reflect.DeepEqual(actual, tc.Result) { 497 t.Fatalf("%d: %#v", i, actual) 498 } 499 } 500 } 501 502 func TestInstanceDiff_RequiresNew(t *testing.T) { 503 rd := &InstanceDiff{ 504 Attributes: map[string]*ResourceAttrDiff{ 505 "foo": &ResourceAttrDiff{}, 506 }, 507 } 508 509 if rd.RequiresNew() { 510 t.Fatal("should not require new") 511 } 512 513 rd.Attributes["foo"].RequiresNew = true 514 515 if !rd.RequiresNew() { 516 t.Fatal("should require new") 517 } 518 } 519 520 func TestInstanceDiff_RequiresNew_nil(t *testing.T) { 521 var rd *InstanceDiff 522 523 if rd.RequiresNew() { 524 t.Fatal("should not require new") 525 } 526 } 527 528 func TestInstanceDiffSame(t *testing.T) { 529 cases := []struct { 530 One, Two *InstanceDiff 531 Same bool 532 Reason string 533 }{ 534 { 535 &InstanceDiff{}, 536 &InstanceDiff{}, 537 true, 538 "", 539 }, 540 541 { 542 nil, 543 nil, 544 true, 545 "", 546 }, 547 548 { 549 &InstanceDiff{Destroy: false}, 550 &InstanceDiff{Destroy: true}, 551 false, 552 "diff: Destroy; old: false, new: true", 553 }, 554 555 { 556 &InstanceDiff{Destroy: true}, 557 &InstanceDiff{Destroy: true}, 558 true, 559 "", 560 }, 561 562 { 563 &InstanceDiff{ 564 Attributes: map[string]*ResourceAttrDiff{ 565 "foo": &ResourceAttrDiff{}, 566 }, 567 }, 568 &InstanceDiff{ 569 Attributes: map[string]*ResourceAttrDiff{ 570 "foo": &ResourceAttrDiff{}, 571 }, 572 }, 573 true, 574 "", 575 }, 576 577 { 578 &InstanceDiff{ 579 Attributes: map[string]*ResourceAttrDiff{ 580 "bar": &ResourceAttrDiff{}, 581 }, 582 }, 583 &InstanceDiff{ 584 Attributes: map[string]*ResourceAttrDiff{ 585 "foo": &ResourceAttrDiff{}, 586 }, 587 }, 588 false, 589 "attribute mismatch: bar", 590 }, 591 592 // Extra attributes 593 { 594 &InstanceDiff{ 595 Attributes: map[string]*ResourceAttrDiff{ 596 "foo": &ResourceAttrDiff{}, 597 }, 598 }, 599 &InstanceDiff{ 600 Attributes: map[string]*ResourceAttrDiff{ 601 "foo": &ResourceAttrDiff{}, 602 "bar": &ResourceAttrDiff{}, 603 }, 604 }, 605 false, 606 "extra attributes: bar", 607 }, 608 609 { 610 &InstanceDiff{ 611 Attributes: map[string]*ResourceAttrDiff{ 612 "foo": &ResourceAttrDiff{RequiresNew: true}, 613 }, 614 }, 615 &InstanceDiff{ 616 Attributes: map[string]*ResourceAttrDiff{ 617 "foo": &ResourceAttrDiff{RequiresNew: false}, 618 }, 619 }, 620 false, 621 "diff RequiresNew; old: true, new: false", 622 }, 623 624 // NewComputed on primitive 625 { 626 &InstanceDiff{ 627 Attributes: map[string]*ResourceAttrDiff{ 628 "foo": &ResourceAttrDiff{ 629 Old: "", 630 New: "${var.foo}", 631 NewComputed: true, 632 }, 633 }, 634 }, 635 &InstanceDiff{ 636 Attributes: map[string]*ResourceAttrDiff{ 637 "foo": &ResourceAttrDiff{ 638 Old: "0", 639 New: "1", 640 }, 641 }, 642 }, 643 true, 644 "", 645 }, 646 647 // NewComputed on primitive, removed 648 { 649 &InstanceDiff{ 650 Attributes: map[string]*ResourceAttrDiff{ 651 "foo": &ResourceAttrDiff{ 652 Old: "", 653 New: "${var.foo}", 654 NewComputed: true, 655 }, 656 }, 657 }, 658 &InstanceDiff{ 659 Attributes: map[string]*ResourceAttrDiff{}, 660 }, 661 true, 662 "", 663 }, 664 665 // NewComputed on set, removed 666 { 667 &InstanceDiff{ 668 Attributes: map[string]*ResourceAttrDiff{ 669 "foo.#": &ResourceAttrDiff{ 670 Old: "", 671 New: "", 672 NewComputed: true, 673 }, 674 }, 675 }, 676 &InstanceDiff{ 677 Attributes: map[string]*ResourceAttrDiff{ 678 "foo.1": &ResourceAttrDiff{ 679 Old: "foo", 680 New: "", 681 NewRemoved: true, 682 }, 683 "foo.2": &ResourceAttrDiff{ 684 Old: "", 685 New: "bar", 686 }, 687 }, 688 }, 689 true, 690 "", 691 }, 692 693 { 694 &InstanceDiff{ 695 Attributes: map[string]*ResourceAttrDiff{ 696 "foo.#": &ResourceAttrDiff{NewComputed: true}, 697 }, 698 }, 699 &InstanceDiff{ 700 Attributes: map[string]*ResourceAttrDiff{ 701 "foo.#": &ResourceAttrDiff{ 702 Old: "0", 703 New: "1", 704 }, 705 "foo.0": &ResourceAttrDiff{ 706 Old: "", 707 New: "12", 708 }, 709 }, 710 }, 711 true, 712 "", 713 }, 714 715 { 716 &InstanceDiff{ 717 Attributes: map[string]*ResourceAttrDiff{ 718 "foo.#": &ResourceAttrDiff{ 719 Old: "0", 720 New: "1", 721 }, 722 "foo.~35964334.bar": &ResourceAttrDiff{ 723 Old: "", 724 New: "${var.foo}", 725 }, 726 }, 727 }, 728 &InstanceDiff{ 729 Attributes: map[string]*ResourceAttrDiff{ 730 "foo.#": &ResourceAttrDiff{ 731 Old: "0", 732 New: "1", 733 }, 734 "foo.87654323.bar": &ResourceAttrDiff{ 735 Old: "", 736 New: "12", 737 }, 738 }, 739 }, 740 true, 741 "", 742 }, 743 744 { 745 &InstanceDiff{ 746 Attributes: map[string]*ResourceAttrDiff{ 747 "foo.#": &ResourceAttrDiff{ 748 Old: "0", 749 NewComputed: true, 750 }, 751 }, 752 }, 753 &InstanceDiff{ 754 Attributes: map[string]*ResourceAttrDiff{}, 755 }, 756 true, 757 "", 758 }, 759 760 // Computed can change RequiresNew by removal, and that's okay 761 { 762 &InstanceDiff{ 763 Attributes: map[string]*ResourceAttrDiff{ 764 "foo.#": &ResourceAttrDiff{ 765 Old: "0", 766 NewComputed: true, 767 RequiresNew: true, 768 }, 769 }, 770 }, 771 &InstanceDiff{ 772 Attributes: map[string]*ResourceAttrDiff{}, 773 }, 774 true, 775 "", 776 }, 777 778 // Computed can change Destroy by removal, and that's okay 779 { 780 &InstanceDiff{ 781 Attributes: map[string]*ResourceAttrDiff{ 782 "foo.#": &ResourceAttrDiff{ 783 Old: "0", 784 NewComputed: true, 785 RequiresNew: true, 786 }, 787 }, 788 789 Destroy: true, 790 }, 791 &InstanceDiff{ 792 Attributes: map[string]*ResourceAttrDiff{}, 793 }, 794 true, 795 "", 796 }, 797 798 // Computed can change Destroy by elements 799 { 800 &InstanceDiff{ 801 Attributes: map[string]*ResourceAttrDiff{ 802 "foo.#": &ResourceAttrDiff{ 803 Old: "0", 804 NewComputed: true, 805 RequiresNew: true, 806 }, 807 }, 808 809 Destroy: true, 810 }, 811 &InstanceDiff{ 812 Attributes: map[string]*ResourceAttrDiff{ 813 "foo.#": &ResourceAttrDiff{ 814 Old: "1", 815 New: "1", 816 }, 817 "foo.12": &ResourceAttrDiff{ 818 Old: "4", 819 New: "12", 820 RequiresNew: true, 821 }, 822 }, 823 824 Destroy: true, 825 }, 826 true, 827 "", 828 }, 829 830 // Computed sets may not contain all fields in the original diff, and 831 // because multiple entries for the same set can compute to the same 832 // hash before the values are computed or interpolated, the overall 833 // count can change as well. 834 { 835 &InstanceDiff{ 836 Attributes: map[string]*ResourceAttrDiff{ 837 "foo.#": &ResourceAttrDiff{ 838 Old: "0", 839 New: "1", 840 }, 841 "foo.~35964334.bar": &ResourceAttrDiff{ 842 Old: "", 843 New: "${var.foo}", 844 }, 845 }, 846 }, 847 &InstanceDiff{ 848 Attributes: map[string]*ResourceAttrDiff{ 849 "foo.#": &ResourceAttrDiff{ 850 Old: "0", 851 New: "2", 852 }, 853 "foo.87654323.bar": &ResourceAttrDiff{ 854 Old: "", 855 New: "12", 856 }, 857 "foo.87654325.bar": &ResourceAttrDiff{ 858 Old: "", 859 New: "12", 860 }, 861 "foo.87654325.baz": &ResourceAttrDiff{ 862 Old: "", 863 New: "12", 864 }, 865 }, 866 }, 867 true, 868 "", 869 }, 870 871 // Computed values in maps will fail the "Same" check as well 872 { 873 &InstanceDiff{ 874 Attributes: map[string]*ResourceAttrDiff{ 875 "foo.%": &ResourceAttrDiff{ 876 Old: "", 877 New: "", 878 NewComputed: true, 879 }, 880 }, 881 }, 882 &InstanceDiff{ 883 Attributes: map[string]*ResourceAttrDiff{ 884 "foo.%": &ResourceAttrDiff{ 885 Old: "0", 886 New: "1", 887 NewComputed: false, 888 }, 889 "foo.val": &ResourceAttrDiff{ 890 Old: "", 891 New: "something", 892 }, 893 }, 894 }, 895 true, 896 "", 897 }, 898 899 // In a DESTROY/CREATE scenario, the plan diff will be run against the 900 // state of the old instance, while the apply diff will be run against an 901 // empty state (because the state is cleared when the destroy runs.) 902 // For complex attributes, this can result in keys that seem to disappear 903 // between the two diffs, when in reality everything is working just fine. 904 // 905 // Same() needs to take into account this scenario by analyzing NewRemoved 906 // and treating as "Same" a diff that does indeed have that key removed. 907 { 908 &InstanceDiff{ 909 Attributes: map[string]*ResourceAttrDiff{ 910 "somemap.oldkey": &ResourceAttrDiff{ 911 Old: "long ago", 912 New: "", 913 NewRemoved: true, 914 }, 915 "somemap.newkey": &ResourceAttrDiff{ 916 Old: "", 917 New: "brave new world", 918 }, 919 }, 920 }, 921 &InstanceDiff{ 922 Attributes: map[string]*ResourceAttrDiff{ 923 "somemap.newkey": &ResourceAttrDiff{ 924 Old: "", 925 New: "brave new world", 926 }, 927 }, 928 }, 929 true, 930 "", 931 }, 932 933 // Another thing that can occur in DESTROY/CREATE scenarios is that list 934 // values that are going to zero have diffs that show up at plan time but 935 // are gone at apply time. The NewRemoved handling catches the fields and 936 // treats them as OK, but it also needs to treat the .# field itself as 937 // okay to be present in the old diff but not in the new one. 938 { 939 &InstanceDiff{ 940 Attributes: map[string]*ResourceAttrDiff{ 941 "reqnew": &ResourceAttrDiff{ 942 Old: "old", 943 New: "new", 944 RequiresNew: true, 945 }, 946 "somemap.#": &ResourceAttrDiff{ 947 Old: "1", 948 New: "0", 949 }, 950 "somemap.oldkey": &ResourceAttrDiff{ 951 Old: "long ago", 952 New: "", 953 NewRemoved: true, 954 }, 955 }, 956 }, 957 &InstanceDiff{ 958 Attributes: map[string]*ResourceAttrDiff{ 959 "reqnew": &ResourceAttrDiff{ 960 Old: "", 961 New: "new", 962 RequiresNew: true, 963 }, 964 }, 965 }, 966 true, 967 "", 968 }, 969 970 { 971 &InstanceDiff{ 972 Attributes: map[string]*ResourceAttrDiff{ 973 "reqnew": &ResourceAttrDiff{ 974 Old: "old", 975 New: "new", 976 RequiresNew: true, 977 }, 978 "somemap.%": &ResourceAttrDiff{ 979 Old: "1", 980 New: "0", 981 }, 982 "somemap.oldkey": &ResourceAttrDiff{ 983 Old: "long ago", 984 New: "", 985 NewRemoved: true, 986 }, 987 }, 988 }, 989 &InstanceDiff{ 990 Attributes: map[string]*ResourceAttrDiff{ 991 "reqnew": &ResourceAttrDiff{ 992 Old: "", 993 New: "new", 994 RequiresNew: true, 995 }, 996 }, 997 }, 998 true, 999 "", 1000 }, 1001 1002 // Innner computed set should allow outer change in key 1003 { 1004 &InstanceDiff{ 1005 Attributes: map[string]*ResourceAttrDiff{ 1006 "foo.#": &ResourceAttrDiff{ 1007 Old: "0", 1008 New: "1", 1009 }, 1010 "foo.~1.outer_val": &ResourceAttrDiff{ 1011 Old: "", 1012 New: "foo", 1013 }, 1014 "foo.~1.inner.#": &ResourceAttrDiff{ 1015 Old: "0", 1016 New: "1", 1017 }, 1018 "foo.~1.inner.~2.value": &ResourceAttrDiff{ 1019 Old: "", 1020 New: "${var.bar}", 1021 NewComputed: true, 1022 }, 1023 }, 1024 }, 1025 &InstanceDiff{ 1026 Attributes: map[string]*ResourceAttrDiff{ 1027 "foo.#": &ResourceAttrDiff{ 1028 Old: "0", 1029 New: "1", 1030 }, 1031 "foo.12.outer_val": &ResourceAttrDiff{ 1032 Old: "", 1033 New: "foo", 1034 }, 1035 "foo.12.inner.#": &ResourceAttrDiff{ 1036 Old: "0", 1037 New: "1", 1038 }, 1039 "foo.12.inner.42.value": &ResourceAttrDiff{ 1040 Old: "", 1041 New: "baz", 1042 }, 1043 }, 1044 }, 1045 true, 1046 "", 1047 }, 1048 1049 // Innner computed list should allow outer change in key 1050 { 1051 &InstanceDiff{ 1052 Attributes: map[string]*ResourceAttrDiff{ 1053 "foo.#": &ResourceAttrDiff{ 1054 Old: "0", 1055 New: "1", 1056 }, 1057 "foo.~1.outer_val": &ResourceAttrDiff{ 1058 Old: "", 1059 New: "foo", 1060 }, 1061 "foo.~1.inner.#": &ResourceAttrDiff{ 1062 Old: "0", 1063 New: "1", 1064 }, 1065 "foo.~1.inner.0.value": &ResourceAttrDiff{ 1066 Old: "", 1067 New: "${var.bar}", 1068 NewComputed: true, 1069 }, 1070 }, 1071 }, 1072 &InstanceDiff{ 1073 Attributes: map[string]*ResourceAttrDiff{ 1074 "foo.#": &ResourceAttrDiff{ 1075 Old: "0", 1076 New: "1", 1077 }, 1078 "foo.12.outer_val": &ResourceAttrDiff{ 1079 Old: "", 1080 New: "foo", 1081 }, 1082 "foo.12.inner.#": &ResourceAttrDiff{ 1083 Old: "0", 1084 New: "1", 1085 }, 1086 "foo.12.inner.0.value": &ResourceAttrDiff{ 1087 Old: "", 1088 New: "baz", 1089 }, 1090 }, 1091 }, 1092 true, 1093 "", 1094 }, 1095 1096 // When removing all collection items, the diff is allowed to contain 1097 // nothing when re-creating the resource. This should be the "Same" 1098 // since we said we were going from 1 to 0. 1099 { 1100 &InstanceDiff{ 1101 Attributes: map[string]*ResourceAttrDiff{ 1102 "foo.%": &ResourceAttrDiff{ 1103 Old: "1", 1104 New: "0", 1105 RequiresNew: true, 1106 }, 1107 "foo.bar": &ResourceAttrDiff{ 1108 Old: "baz", 1109 New: "", 1110 NewRemoved: true, 1111 RequiresNew: true, 1112 }, 1113 }, 1114 }, 1115 &InstanceDiff{}, 1116 true, 1117 "", 1118 }, 1119 1120 { 1121 &InstanceDiff{ 1122 Attributes: map[string]*ResourceAttrDiff{ 1123 "foo.#": &ResourceAttrDiff{ 1124 Old: "1", 1125 New: "0", 1126 RequiresNew: true, 1127 }, 1128 "foo.0": &ResourceAttrDiff{ 1129 Old: "baz", 1130 New: "", 1131 NewRemoved: true, 1132 RequiresNew: true, 1133 }, 1134 }, 1135 }, 1136 &InstanceDiff{}, 1137 true, 1138 "", 1139 }, 1140 1141 // Make sure that DestroyTainted diffs pass as well, especially when diff 1142 // two works off of no state. 1143 { 1144 &InstanceDiff{ 1145 DestroyTainted: true, 1146 Attributes: map[string]*ResourceAttrDiff{ 1147 "foo": &ResourceAttrDiff{ 1148 Old: "foo", 1149 New: "foo", 1150 }, 1151 }, 1152 }, 1153 &InstanceDiff{ 1154 DestroyTainted: true, 1155 Attributes: map[string]*ResourceAttrDiff{ 1156 "foo": &ResourceAttrDiff{ 1157 Old: "", 1158 New: "foo", 1159 }, 1160 }, 1161 }, 1162 true, 1163 "", 1164 }, 1165 // RequiresNew in different attribute 1166 { 1167 &InstanceDiff{ 1168 Attributes: map[string]*ResourceAttrDiff{ 1169 "foo": &ResourceAttrDiff{ 1170 Old: "foo", 1171 New: "foo", 1172 }, 1173 "bar": &ResourceAttrDiff{ 1174 Old: "bar", 1175 New: "baz", 1176 RequiresNew: true, 1177 }, 1178 }, 1179 }, 1180 &InstanceDiff{ 1181 Attributes: map[string]*ResourceAttrDiff{ 1182 "foo": &ResourceAttrDiff{ 1183 Old: "", 1184 New: "foo", 1185 }, 1186 "bar": &ResourceAttrDiff{ 1187 Old: "", 1188 New: "baz", 1189 RequiresNew: true, 1190 }, 1191 }, 1192 }, 1193 true, 1194 "", 1195 }, 1196 } 1197 1198 for i, tc := range cases { 1199 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 1200 same, reason := tc.One.Same(tc.Two) 1201 if same != tc.Same { 1202 t.Fatalf("%d: expected same: %t, got %t (%s)\n\n one: %#v\n\ntwo: %#v", 1203 i, tc.Same, same, reason, tc.One, tc.Two) 1204 } 1205 if reason != tc.Reason { 1206 t.Fatalf( 1207 "%d: bad reason\n\nexpected: %#v\n\ngot: %#v", i, tc.Reason, reason) 1208 } 1209 }) 1210 } 1211 } 1212 1213 const moduleDiffStrBasic = ` 1214 CREATE: nodeA 1215 bar: "foo" => "<computed>" 1216 foo: "foo" => "bar" 1217 longfoo: "foo" => "bar" (forces new resource) 1218 secretfoo: "<sensitive>" => "<sensitive>" (attribute changed) 1219 ` 1220 1221 func TestCountFlatmapContainerValues(t *testing.T) { 1222 for i, tc := range []struct { 1223 attrs map[string]string 1224 key string 1225 count string 1226 }{ 1227 { 1228 attrs: map[string]string{"set.2.list.#": "9999", "set.2.list.0": "x", "set.2.list.0.z": "y", "set.2.attr": "bar", "set.#": "9999"}, 1229 key: "set.2.list.#", 1230 count: "1", 1231 }, 1232 { 1233 attrs: map[string]string{"set.2.list.#": "9999", "set.2.list.0": "x", "set.2.list.0.z": "y", "set.2.attr": "bar", "set.#": "9999"}, 1234 key: "set.#", 1235 count: "1", 1236 }, 1237 { 1238 attrs: map[string]string{"set.2.list.0": "x", "set.2.list.0.z": "y", "set.2.attr": "bar", "set.#": "9999"}, 1239 key: "set.#", 1240 count: "1", 1241 }, 1242 { 1243 attrs: map[string]string{"map.#": "3", "map.a": "b", "map.a.#": "0", "map.b": "4"}, 1244 key: "map.#", 1245 count: "2", 1246 }, 1247 } { 1248 t.Run(strconv.Itoa(i), func(t *testing.T) { 1249 count := countFlatmapContainerValues(tc.key, tc.attrs) 1250 if count != tc.count { 1251 t.Fatalf("expected %q, got %q", tc.count, count) 1252 } 1253 }) 1254 } 1255 }