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