github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/state_test.go (about) 1 package terraform 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "os" 8 "reflect" 9 "sort" 10 "strings" 11 "testing" 12 13 "github.com/davecgh/go-spew/spew" 14 "github.com/hashicorp/terraform-plugin-sdk/internal/addrs" 15 "github.com/hashicorp/terraform-plugin-sdk/internal/configs/hcl2shim" 16 ) 17 18 func TestStateValidate(t *testing.T) { 19 cases := map[string]struct { 20 In *State 21 Err bool 22 }{ 23 "empty state": { 24 &State{}, 25 false, 26 }, 27 28 "multiple modules": { 29 &State{ 30 Modules: []*ModuleState{ 31 { 32 Path: []string{"root", "foo"}, 33 }, 34 { 35 Path: []string{"root", "foo"}, 36 }, 37 }, 38 }, 39 true, 40 }, 41 } 42 43 for name, tc := range cases { 44 // Init the state 45 tc.In.init() 46 47 err := tc.In.Validate() 48 if (err != nil) != tc.Err { 49 t.Fatalf("%s: err: %s", name, err) 50 } 51 } 52 } 53 54 func TestStateAddModule(t *testing.T) { 55 cases := []struct { 56 In []addrs.ModuleInstance 57 Out [][]string 58 }{ 59 { 60 []addrs.ModuleInstance{ 61 addrs.RootModuleInstance, 62 addrs.RootModuleInstance.Child("child", addrs.NoKey), 63 }, 64 [][]string{ 65 {"root"}, 66 {"root", "child"}, 67 }, 68 }, 69 70 { 71 []addrs.ModuleInstance{ 72 addrs.RootModuleInstance.Child("foo", addrs.NoKey).Child("bar", addrs.NoKey), 73 addrs.RootModuleInstance.Child("foo", addrs.NoKey), 74 addrs.RootModuleInstance, 75 addrs.RootModuleInstance.Child("bar", addrs.NoKey), 76 }, 77 [][]string{ 78 {"root"}, 79 {"root", "bar"}, 80 {"root", "foo"}, 81 {"root", "foo", "bar"}, 82 }, 83 }, 84 // Same last element, different middle element 85 { 86 []addrs.ModuleInstance{ 87 addrs.RootModuleInstance.Child("foo", addrs.NoKey).Child("bar", addrs.NoKey), // This one should sort after... 88 addrs.RootModuleInstance.Child("foo", addrs.NoKey), 89 addrs.RootModuleInstance, 90 addrs.RootModuleInstance.Child("bar", addrs.NoKey).Child("bar", addrs.NoKey), // ...this one. 91 addrs.RootModuleInstance.Child("bar", addrs.NoKey), 92 }, 93 [][]string{ 94 {"root"}, 95 {"root", "bar"}, 96 {"root", "foo"}, 97 {"root", "bar", "bar"}, 98 {"root", "foo", "bar"}, 99 }, 100 }, 101 } 102 103 for _, tc := range cases { 104 s := new(State) 105 for _, p := range tc.In { 106 s.AddModule(p) 107 } 108 109 actual := make([][]string, 0, len(tc.In)) 110 for _, m := range s.Modules { 111 actual = append(actual, m.Path) 112 } 113 114 if !reflect.DeepEqual(actual, tc.Out) { 115 t.Fatalf("wrong result\ninput: %sgot: %#v\nwant: %#v", spew.Sdump(tc.In), actual, tc.Out) 116 } 117 } 118 } 119 120 func TestStateOutputTypeRoundTrip(t *testing.T) { 121 state := &State{ 122 Modules: []*ModuleState{ 123 { 124 Path: []string{"root"}, 125 Outputs: map[string]*OutputState{ 126 "string_output": { 127 Value: "String Value", 128 Type: "string", 129 }, 130 }, 131 }, 132 }, 133 } 134 state.init() 135 136 buf := new(bytes.Buffer) 137 if err := WriteState(state, buf); err != nil { 138 t.Fatalf("err: %s", err) 139 } 140 141 roundTripped, err := ReadState(buf) 142 if err != nil { 143 t.Fatalf("err: %s", err) 144 } 145 146 if !reflect.DeepEqual(state, roundTripped) { 147 t.Logf("expected:\n%#v", state) 148 t.Fatalf("got:\n%#v", roundTripped) 149 } 150 } 151 152 func TestStateDeepCopy(t *testing.T) { 153 cases := []struct { 154 State *State 155 }{ 156 // Nil 157 {nil}, 158 159 // Version 160 { 161 &State{Version: 5}, 162 }, 163 // TFVersion 164 { 165 &State{TFVersion: "5"}, 166 }, 167 // Modules 168 { 169 &State{ 170 Version: 6, 171 Modules: []*ModuleState{ 172 { 173 Path: rootModulePath, 174 Resources: map[string]*ResourceState{ 175 "test_instance.foo": { 176 Primary: &InstanceState{ 177 Meta: map[string]interface{}{}, 178 }, 179 }, 180 }, 181 }, 182 }, 183 }, 184 }, 185 // Deposed 186 // The nil values shouldn't be there if the State was properly init'ed, 187 // but the Copy should still work anyway. 188 { 189 &State{ 190 Version: 6, 191 Modules: []*ModuleState{ 192 { 193 Path: rootModulePath, 194 Resources: map[string]*ResourceState{ 195 "test_instance.foo": { 196 Primary: &InstanceState{ 197 Meta: map[string]interface{}{}, 198 }, 199 Deposed: []*InstanceState{ 200 {ID: "test"}, 201 nil, 202 }, 203 }, 204 }, 205 }, 206 }, 207 }, 208 }, 209 } 210 211 for i, tc := range cases { 212 t.Run(fmt.Sprintf("copy-%d", i), func(t *testing.T) { 213 actual := tc.State.DeepCopy() 214 expected := tc.State 215 if !reflect.DeepEqual(actual, expected) { 216 t.Fatalf("Expected: %#v\nRecevied: %#v\n", expected, actual) 217 } 218 }) 219 } 220 } 221 222 func TestStateEqual(t *testing.T) { 223 cases := []struct { 224 Name string 225 Result bool 226 One, Two *State 227 }{ 228 // Nils 229 { 230 "one nil", 231 false, 232 nil, 233 &State{Version: 2}, 234 }, 235 236 { 237 "both nil", 238 true, 239 nil, 240 nil, 241 }, 242 243 // Different versions 244 { 245 "different state versions", 246 false, 247 &State{Version: 5}, 248 &State{Version: 2}, 249 }, 250 251 // Different modules 252 { 253 "different module states", 254 false, 255 &State{ 256 Modules: []*ModuleState{ 257 { 258 Path: []string{"root"}, 259 }, 260 }, 261 }, 262 &State{}, 263 }, 264 265 { 266 "same module states", 267 true, 268 &State{ 269 Modules: []*ModuleState{ 270 { 271 Path: []string{"root"}, 272 }, 273 }, 274 }, 275 &State{ 276 Modules: []*ModuleState{ 277 { 278 Path: []string{"root"}, 279 }, 280 }, 281 }, 282 }, 283 284 // Meta differs 285 { 286 "differing meta values with primitives", 287 false, 288 &State{ 289 Modules: []*ModuleState{ 290 { 291 Path: rootModulePath, 292 Resources: map[string]*ResourceState{ 293 "test_instance.foo": { 294 Primary: &InstanceState{ 295 Meta: map[string]interface{}{ 296 "schema_version": "1", 297 }, 298 }, 299 }, 300 }, 301 }, 302 }, 303 }, 304 &State{ 305 Modules: []*ModuleState{ 306 { 307 Path: rootModulePath, 308 Resources: map[string]*ResourceState{ 309 "test_instance.foo": { 310 Primary: &InstanceState{ 311 Meta: map[string]interface{}{ 312 "schema_version": "2", 313 }, 314 }, 315 }, 316 }, 317 }, 318 }, 319 }, 320 }, 321 322 // Meta with complex types 323 { 324 "same meta with complex types", 325 true, 326 &State{ 327 Modules: []*ModuleState{ 328 { 329 Path: rootModulePath, 330 Resources: map[string]*ResourceState{ 331 "test_instance.foo": { 332 Primary: &InstanceState{ 333 Meta: map[string]interface{}{ 334 "timeouts": map[string]interface{}{ 335 "create": 42, 336 "read": "27", 337 }, 338 }, 339 }, 340 }, 341 }, 342 }, 343 }, 344 }, 345 &State{ 346 Modules: []*ModuleState{ 347 { 348 Path: rootModulePath, 349 Resources: map[string]*ResourceState{ 350 "test_instance.foo": { 351 Primary: &InstanceState{ 352 Meta: map[string]interface{}{ 353 "timeouts": map[string]interface{}{ 354 "create": 42, 355 "read": "27", 356 }, 357 }, 358 }, 359 }, 360 }, 361 }, 362 }, 363 }, 364 }, 365 366 // Meta with complex types that have been altered during serialization 367 { 368 "same meta with complex types that have been json-ified", 369 true, 370 &State{ 371 Modules: []*ModuleState{ 372 { 373 Path: rootModulePath, 374 Resources: map[string]*ResourceState{ 375 "test_instance.foo": { 376 Primary: &InstanceState{ 377 Meta: map[string]interface{}{ 378 "timeouts": map[string]interface{}{ 379 "create": int(42), 380 "read": "27", 381 }, 382 }, 383 }, 384 }, 385 }, 386 }, 387 }, 388 }, 389 &State{ 390 Modules: []*ModuleState{ 391 { 392 Path: rootModulePath, 393 Resources: map[string]*ResourceState{ 394 "test_instance.foo": { 395 Primary: &InstanceState{ 396 Meta: map[string]interface{}{ 397 "timeouts": map[string]interface{}{ 398 "create": float64(42), 399 "read": "27", 400 }, 401 }, 402 }, 403 }, 404 }, 405 }, 406 }, 407 }, 408 }, 409 } 410 411 for i, tc := range cases { 412 t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { 413 if tc.One.Equal(tc.Two) != tc.Result { 414 t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String()) 415 } 416 if tc.Two.Equal(tc.One) != tc.Result { 417 t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String()) 418 } 419 }) 420 } 421 } 422 423 func TestStateCompareAges(t *testing.T) { 424 cases := []struct { 425 Result StateAgeComparison 426 Err bool 427 One, Two *State 428 }{ 429 { 430 StateAgeEqual, false, 431 &State{ 432 Lineage: "1", 433 Serial: 2, 434 }, 435 &State{ 436 Lineage: "1", 437 Serial: 2, 438 }, 439 }, 440 { 441 StateAgeReceiverOlder, false, 442 &State{ 443 Lineage: "1", 444 Serial: 2, 445 }, 446 &State{ 447 Lineage: "1", 448 Serial: 3, 449 }, 450 }, 451 { 452 StateAgeReceiverNewer, false, 453 &State{ 454 Lineage: "1", 455 Serial: 3, 456 }, 457 &State{ 458 Lineage: "1", 459 Serial: 2, 460 }, 461 }, 462 { 463 StateAgeEqual, true, 464 &State{ 465 Lineage: "1", 466 Serial: 2, 467 }, 468 &State{ 469 Lineage: "2", 470 Serial: 2, 471 }, 472 }, 473 { 474 StateAgeEqual, true, 475 &State{ 476 Lineage: "1", 477 Serial: 3, 478 }, 479 &State{ 480 Lineage: "2", 481 Serial: 2, 482 }, 483 }, 484 } 485 486 for i, tc := range cases { 487 result, err := tc.One.CompareAges(tc.Two) 488 489 if err != nil && !tc.Err { 490 t.Errorf( 491 "%d: got error, but want success\n\n%s\n\n%s", 492 i, tc.One, tc.Two, 493 ) 494 continue 495 } 496 497 if err == nil && tc.Err { 498 t.Errorf( 499 "%d: got success, but want error\n\n%s\n\n%s", 500 i, tc.One, tc.Two, 501 ) 502 continue 503 } 504 505 if result != tc.Result { 506 t.Errorf( 507 "%d: got result %d, but want %d\n\n%s\n\n%s", 508 i, result, tc.Result, tc.One, tc.Two, 509 ) 510 continue 511 } 512 } 513 } 514 515 func TestStateSameLineage(t *testing.T) { 516 cases := []struct { 517 Result bool 518 One, Two *State 519 }{ 520 { 521 true, 522 &State{ 523 Lineage: "1", 524 }, 525 &State{ 526 Lineage: "1", 527 }, 528 }, 529 { 530 // Empty lineage is compatible with all 531 true, 532 &State{ 533 Lineage: "", 534 }, 535 &State{ 536 Lineage: "1", 537 }, 538 }, 539 { 540 // Empty lineage is compatible with all 541 true, 542 &State{ 543 Lineage: "1", 544 }, 545 &State{ 546 Lineage: "", 547 }, 548 }, 549 { 550 false, 551 &State{ 552 Lineage: "1", 553 }, 554 &State{ 555 Lineage: "2", 556 }, 557 }, 558 } 559 560 for i, tc := range cases { 561 result := tc.One.SameLineage(tc.Two) 562 563 if result != tc.Result { 564 t.Errorf( 565 "%d: got %v, but want %v\n\n%s\n\n%s", 566 i, result, tc.Result, tc.One, tc.Two, 567 ) 568 continue 569 } 570 } 571 } 572 573 func TestStateMarshalEqual(t *testing.T) { 574 tests := map[string]struct { 575 S1, S2 *State 576 Want bool 577 }{ 578 "both nil": { 579 nil, 580 nil, 581 true, 582 }, 583 "first zero, second nil": { 584 &State{}, 585 nil, 586 false, 587 }, 588 "first nil, second zero": { 589 nil, 590 &State{}, 591 false, 592 }, 593 "both zero": { 594 // These are not equal because they both implicitly init with 595 // different lineage. 596 &State{}, 597 &State{}, 598 false, 599 }, 600 "both set, same lineage": { 601 &State{ 602 Lineage: "abc123", 603 }, 604 &State{ 605 Lineage: "abc123", 606 }, 607 true, 608 }, 609 "both set, same lineage, different serial": { 610 &State{ 611 Lineage: "abc123", 612 Serial: 1, 613 }, 614 &State{ 615 Lineage: "abc123", 616 Serial: 2, 617 }, 618 false, 619 }, 620 "both set, same lineage, same serial, same resources": { 621 &State{ 622 Lineage: "abc123", 623 Serial: 1, 624 Modules: []*ModuleState{ 625 { 626 Path: []string{"root"}, 627 Resources: map[string]*ResourceState{ 628 "foo_bar.baz": {}, 629 }, 630 }, 631 }, 632 }, 633 &State{ 634 Lineage: "abc123", 635 Serial: 1, 636 Modules: []*ModuleState{ 637 { 638 Path: []string{"root"}, 639 Resources: map[string]*ResourceState{ 640 "foo_bar.baz": {}, 641 }, 642 }, 643 }, 644 }, 645 true, 646 }, 647 "both set, same lineage, same serial, different resources": { 648 &State{ 649 Lineage: "abc123", 650 Serial: 1, 651 Modules: []*ModuleState{ 652 { 653 Path: []string{"root"}, 654 Resources: map[string]*ResourceState{ 655 "foo_bar.baz": {}, 656 }, 657 }, 658 }, 659 }, 660 &State{ 661 Lineage: "abc123", 662 Serial: 1, 663 Modules: []*ModuleState{ 664 { 665 Path: []string{"root"}, 666 Resources: map[string]*ResourceState{ 667 "pizza_crust.tasty": {}, 668 }, 669 }, 670 }, 671 }, 672 false, 673 }, 674 } 675 676 for name, test := range tests { 677 t.Run(name, func(t *testing.T) { 678 got := test.S1.MarshalEqual(test.S2) 679 if got != test.Want { 680 t.Errorf("wrong result %#v; want %#v", got, test.Want) 681 s1Buf := &bytes.Buffer{} 682 s2Buf := &bytes.Buffer{} 683 _ = WriteState(test.S1, s1Buf) 684 _ = WriteState(test.S2, s2Buf) 685 t.Logf("\nState 1: %s\nState 2: %s", s1Buf.Bytes(), s2Buf.Bytes()) 686 } 687 }) 688 } 689 } 690 691 func TestStateRemove(t *testing.T) { 692 cases := map[string]struct { 693 Address string 694 One, Two *State 695 }{ 696 "simple resource": { 697 "test_instance.foo", 698 &State{ 699 Modules: []*ModuleState{ 700 { 701 Path: rootModulePath, 702 Resources: map[string]*ResourceState{ 703 "test_instance.foo": { 704 Type: "test_instance", 705 Primary: &InstanceState{ 706 ID: "foo", 707 }, 708 }, 709 710 "test_instance.bar": { 711 Type: "test_instance", 712 Primary: &InstanceState{ 713 ID: "foo", 714 }, 715 }, 716 }, 717 }, 718 }, 719 }, 720 &State{ 721 Modules: []*ModuleState{ 722 { 723 Path: rootModulePath, 724 Resources: map[string]*ResourceState{ 725 "test_instance.bar": { 726 Type: "test_instance", 727 Primary: &InstanceState{ 728 ID: "foo", 729 }, 730 }, 731 }, 732 }, 733 }, 734 }, 735 }, 736 737 "single instance": { 738 "test_instance.foo.primary", 739 &State{ 740 Modules: []*ModuleState{ 741 { 742 Path: rootModulePath, 743 Resources: map[string]*ResourceState{ 744 "test_instance.foo": { 745 Type: "test_instance", 746 Primary: &InstanceState{ 747 ID: "foo", 748 }, 749 }, 750 }, 751 }, 752 }, 753 }, 754 &State{ 755 Modules: []*ModuleState{ 756 { 757 Path: rootModulePath, 758 Resources: map[string]*ResourceState{}, 759 }, 760 }, 761 }, 762 }, 763 764 "single instance in multi-count": { 765 "test_instance.foo[0]", 766 &State{ 767 Modules: []*ModuleState{ 768 { 769 Path: rootModulePath, 770 Resources: map[string]*ResourceState{ 771 "test_instance.foo.0": { 772 Type: "test_instance", 773 Primary: &InstanceState{ 774 ID: "foo", 775 }, 776 }, 777 778 "test_instance.foo.1": { 779 Type: "test_instance", 780 Primary: &InstanceState{ 781 ID: "foo", 782 }, 783 }, 784 }, 785 }, 786 }, 787 }, 788 &State{ 789 Modules: []*ModuleState{ 790 { 791 Path: rootModulePath, 792 Resources: map[string]*ResourceState{ 793 "test_instance.foo.1": { 794 Type: "test_instance", 795 Primary: &InstanceState{ 796 ID: "foo", 797 }, 798 }, 799 }, 800 }, 801 }, 802 }, 803 }, 804 805 "single resource, multi-count": { 806 "test_instance.foo", 807 &State{ 808 Modules: []*ModuleState{ 809 { 810 Path: rootModulePath, 811 Resources: map[string]*ResourceState{ 812 "test_instance.foo.0": { 813 Type: "test_instance", 814 Primary: &InstanceState{ 815 ID: "foo", 816 }, 817 }, 818 819 "test_instance.foo.1": { 820 Type: "test_instance", 821 Primary: &InstanceState{ 822 ID: "foo", 823 }, 824 }, 825 }, 826 }, 827 }, 828 }, 829 &State{ 830 Modules: []*ModuleState{ 831 { 832 Path: rootModulePath, 833 Resources: map[string]*ResourceState{}, 834 }, 835 }, 836 }, 837 }, 838 839 "full module": { 840 "module.foo", 841 &State{ 842 Modules: []*ModuleState{ 843 { 844 Path: rootModulePath, 845 Resources: map[string]*ResourceState{ 846 "test_instance.foo": { 847 Type: "test_instance", 848 Primary: &InstanceState{ 849 ID: "foo", 850 }, 851 }, 852 }, 853 }, 854 855 { 856 Path: []string{"root", "foo"}, 857 Resources: map[string]*ResourceState{ 858 "test_instance.foo": { 859 Type: "test_instance", 860 Primary: &InstanceState{ 861 ID: "foo", 862 }, 863 }, 864 865 "test_instance.bar": { 866 Type: "test_instance", 867 Primary: &InstanceState{ 868 ID: "foo", 869 }, 870 }, 871 }, 872 }, 873 }, 874 }, 875 &State{ 876 Modules: []*ModuleState{ 877 { 878 Path: rootModulePath, 879 Resources: map[string]*ResourceState{ 880 "test_instance.foo": { 881 Type: "test_instance", 882 Primary: &InstanceState{ 883 ID: "foo", 884 }, 885 }, 886 }, 887 }, 888 }, 889 }, 890 }, 891 892 "module and children": { 893 "module.foo", 894 &State{ 895 Modules: []*ModuleState{ 896 { 897 Path: rootModulePath, 898 Resources: map[string]*ResourceState{ 899 "test_instance.foo": { 900 Type: "test_instance", 901 Primary: &InstanceState{ 902 ID: "foo", 903 }, 904 }, 905 }, 906 }, 907 908 { 909 Path: []string{"root", "foo"}, 910 Resources: map[string]*ResourceState{ 911 "test_instance.foo": { 912 Type: "test_instance", 913 Primary: &InstanceState{ 914 ID: "foo", 915 }, 916 }, 917 918 "test_instance.bar": { 919 Type: "test_instance", 920 Primary: &InstanceState{ 921 ID: "foo", 922 }, 923 }, 924 }, 925 }, 926 927 { 928 Path: []string{"root", "foo", "bar"}, 929 Resources: map[string]*ResourceState{ 930 "test_instance.foo": { 931 Type: "test_instance", 932 Primary: &InstanceState{ 933 ID: "foo", 934 }, 935 }, 936 937 "test_instance.bar": { 938 Type: "test_instance", 939 Primary: &InstanceState{ 940 ID: "foo", 941 }, 942 }, 943 }, 944 }, 945 }, 946 }, 947 &State{ 948 Modules: []*ModuleState{ 949 { 950 Path: rootModulePath, 951 Resources: map[string]*ResourceState{ 952 "test_instance.foo": { 953 Type: "test_instance", 954 Primary: &InstanceState{ 955 ID: "foo", 956 }, 957 }, 958 }, 959 }, 960 }, 961 }, 962 }, 963 } 964 965 for k, tc := range cases { 966 if err := tc.One.Remove(tc.Address); err != nil { 967 t.Fatalf("bad: %s\n\n%s", k, err) 968 } 969 970 if !tc.One.Equal(tc.Two) { 971 t.Fatalf("Bad: %s\n\n%s\n\n%s", k, tc.One.String(), tc.Two.String()) 972 } 973 } 974 } 975 976 func TestResourceStateEqual(t *testing.T) { 977 cases := []struct { 978 Result bool 979 One, Two *ResourceState 980 }{ 981 // Different types 982 { 983 false, 984 &ResourceState{Type: "foo"}, 985 &ResourceState{Type: "bar"}, 986 }, 987 988 // Different dependencies 989 { 990 false, 991 &ResourceState{Dependencies: []string{"foo"}}, 992 &ResourceState{Dependencies: []string{"bar"}}, 993 }, 994 995 { 996 false, 997 &ResourceState{Dependencies: []string{"foo", "bar"}}, 998 &ResourceState{Dependencies: []string{"foo"}}, 999 }, 1000 1001 { 1002 true, 1003 &ResourceState{Dependencies: []string{"bar", "foo"}}, 1004 &ResourceState{Dependencies: []string{"foo", "bar"}}, 1005 }, 1006 1007 // Different primaries 1008 { 1009 false, 1010 &ResourceState{Primary: nil}, 1011 &ResourceState{Primary: &InstanceState{ID: "foo"}}, 1012 }, 1013 1014 { 1015 true, 1016 &ResourceState{Primary: &InstanceState{ID: "foo"}}, 1017 &ResourceState{Primary: &InstanceState{ID: "foo"}}, 1018 }, 1019 1020 // Different tainted 1021 { 1022 false, 1023 &ResourceState{ 1024 Primary: &InstanceState{ 1025 ID: "foo", 1026 }, 1027 }, 1028 &ResourceState{ 1029 Primary: &InstanceState{ 1030 ID: "foo", 1031 Tainted: true, 1032 }, 1033 }, 1034 }, 1035 1036 { 1037 true, 1038 &ResourceState{ 1039 Primary: &InstanceState{ 1040 ID: "foo", 1041 Tainted: true, 1042 }, 1043 }, 1044 &ResourceState{ 1045 Primary: &InstanceState{ 1046 ID: "foo", 1047 Tainted: true, 1048 }, 1049 }, 1050 }, 1051 } 1052 1053 for i, tc := range cases { 1054 if tc.One.Equal(tc.Two) != tc.Result { 1055 t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String()) 1056 } 1057 if tc.Two.Equal(tc.One) != tc.Result { 1058 t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String()) 1059 } 1060 } 1061 } 1062 1063 func TestResourceStateTaint(t *testing.T) { 1064 cases := map[string]struct { 1065 Input *ResourceState 1066 Output *ResourceState 1067 }{ 1068 "no primary": { 1069 &ResourceState{}, 1070 &ResourceState{}, 1071 }, 1072 1073 "primary, not tainted": { 1074 &ResourceState{ 1075 Primary: &InstanceState{ID: "foo"}, 1076 }, 1077 &ResourceState{ 1078 Primary: &InstanceState{ 1079 ID: "foo", 1080 Tainted: true, 1081 }, 1082 }, 1083 }, 1084 1085 "primary, tainted": { 1086 &ResourceState{ 1087 Primary: &InstanceState{ 1088 ID: "foo", 1089 Tainted: true, 1090 }, 1091 }, 1092 &ResourceState{ 1093 Primary: &InstanceState{ 1094 ID: "foo", 1095 Tainted: true, 1096 }, 1097 }, 1098 }, 1099 } 1100 1101 for k, tc := range cases { 1102 tc.Input.Taint() 1103 if !reflect.DeepEqual(tc.Input, tc.Output) { 1104 t.Fatalf( 1105 "Failure: %s\n\nExpected: %#v\n\nGot: %#v", 1106 k, tc.Output, tc.Input) 1107 } 1108 } 1109 } 1110 1111 func TestResourceStateUntaint(t *testing.T) { 1112 cases := map[string]struct { 1113 Input *ResourceState 1114 ExpectedOutput *ResourceState 1115 }{ 1116 "no primary, err": { 1117 Input: &ResourceState{}, 1118 ExpectedOutput: &ResourceState{}, 1119 }, 1120 1121 "primary, not tainted": { 1122 Input: &ResourceState{ 1123 Primary: &InstanceState{ID: "foo"}, 1124 }, 1125 ExpectedOutput: &ResourceState{ 1126 Primary: &InstanceState{ID: "foo"}, 1127 }, 1128 }, 1129 "primary, tainted": { 1130 Input: &ResourceState{ 1131 Primary: &InstanceState{ 1132 ID: "foo", 1133 Tainted: true, 1134 }, 1135 }, 1136 ExpectedOutput: &ResourceState{ 1137 Primary: &InstanceState{ID: "foo"}, 1138 }, 1139 }, 1140 } 1141 1142 for k, tc := range cases { 1143 tc.Input.Untaint() 1144 if !reflect.DeepEqual(tc.Input, tc.ExpectedOutput) { 1145 t.Fatalf( 1146 "Failure: %s\n\nExpected: %#v\n\nGot: %#v", 1147 k, tc.ExpectedOutput, tc.Input) 1148 } 1149 } 1150 } 1151 1152 func TestInstanceStateEmpty(t *testing.T) { 1153 cases := map[string]struct { 1154 In *InstanceState 1155 Result bool 1156 }{ 1157 "nil is empty": { 1158 nil, 1159 true, 1160 }, 1161 "non-nil but without ID is empty": { 1162 &InstanceState{}, 1163 true, 1164 }, 1165 "with ID is not empty": { 1166 &InstanceState{ 1167 ID: "i-abc123", 1168 }, 1169 false, 1170 }, 1171 } 1172 1173 for tn, tc := range cases { 1174 if tc.In.Empty() != tc.Result { 1175 t.Fatalf("%q expected %#v to be empty: %#v", tn, tc.In, tc.Result) 1176 } 1177 } 1178 } 1179 1180 func TestInstanceStateEqual(t *testing.T) { 1181 cases := []struct { 1182 Result bool 1183 One, Two *InstanceState 1184 }{ 1185 // Nils 1186 { 1187 false, 1188 nil, 1189 &InstanceState{}, 1190 }, 1191 1192 { 1193 false, 1194 &InstanceState{}, 1195 nil, 1196 }, 1197 1198 // Different IDs 1199 { 1200 false, 1201 &InstanceState{ID: "foo"}, 1202 &InstanceState{ID: "bar"}, 1203 }, 1204 1205 // Different Attributes 1206 { 1207 false, 1208 &InstanceState{Attributes: map[string]string{"foo": "bar"}}, 1209 &InstanceState{Attributes: map[string]string{"foo": "baz"}}, 1210 }, 1211 1212 // Different Attribute keys 1213 { 1214 false, 1215 &InstanceState{Attributes: map[string]string{"foo": "bar"}}, 1216 &InstanceState{Attributes: map[string]string{"bar": "baz"}}, 1217 }, 1218 1219 { 1220 false, 1221 &InstanceState{Attributes: map[string]string{"bar": "baz"}}, 1222 &InstanceState{Attributes: map[string]string{"foo": "bar"}}, 1223 }, 1224 } 1225 1226 for i, tc := range cases { 1227 if tc.One.Equal(tc.Two) != tc.Result { 1228 t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String()) 1229 } 1230 } 1231 } 1232 1233 func TestStateEmpty(t *testing.T) { 1234 cases := []struct { 1235 In *State 1236 Result bool 1237 }{ 1238 { 1239 nil, 1240 true, 1241 }, 1242 { 1243 &State{}, 1244 true, 1245 }, 1246 { 1247 &State{ 1248 Remote: &RemoteState{Type: "foo"}, 1249 }, 1250 true, 1251 }, 1252 { 1253 &State{ 1254 Modules: []*ModuleState{ 1255 {}, 1256 }, 1257 }, 1258 false, 1259 }, 1260 } 1261 1262 for i, tc := range cases { 1263 if tc.In.Empty() != tc.Result { 1264 t.Fatalf("bad %d %#v:\n\n%#v", i, tc.Result, tc.In) 1265 } 1266 } 1267 } 1268 1269 func TestStateHasResources(t *testing.T) { 1270 cases := []struct { 1271 In *State 1272 Result bool 1273 }{ 1274 { 1275 nil, 1276 false, 1277 }, 1278 { 1279 &State{}, 1280 false, 1281 }, 1282 { 1283 &State{ 1284 Remote: &RemoteState{Type: "foo"}, 1285 }, 1286 false, 1287 }, 1288 { 1289 &State{ 1290 Modules: []*ModuleState{ 1291 {}, 1292 }, 1293 }, 1294 false, 1295 }, 1296 { 1297 &State{ 1298 Modules: []*ModuleState{ 1299 {}, 1300 {}, 1301 }, 1302 }, 1303 false, 1304 }, 1305 { 1306 &State{ 1307 Modules: []*ModuleState{ 1308 {}, 1309 { 1310 Resources: map[string]*ResourceState{ 1311 "foo.foo": {}, 1312 }, 1313 }, 1314 }, 1315 }, 1316 true, 1317 }, 1318 } 1319 1320 for i, tc := range cases { 1321 if tc.In.HasResources() != tc.Result { 1322 t.Fatalf("bad %d %#v:\n\n%#v", i, tc.Result, tc.In) 1323 } 1324 } 1325 } 1326 1327 func TestStateFromFutureTerraform(t *testing.T) { 1328 cases := []struct { 1329 In string 1330 Result bool 1331 }{ 1332 { 1333 "", 1334 false, 1335 }, 1336 { 1337 "0.1", 1338 false, 1339 }, 1340 { 1341 "999.15.1", 1342 true, 1343 }, 1344 } 1345 1346 for _, tc := range cases { 1347 state := &State{TFVersion: tc.In} 1348 actual := state.FromFutureTerraform() 1349 if actual != tc.Result { 1350 t.Fatalf("%s: bad: %v", tc.In, actual) 1351 } 1352 } 1353 } 1354 1355 func TestStateIsRemote(t *testing.T) { 1356 cases := []struct { 1357 In *State 1358 Result bool 1359 }{ 1360 { 1361 nil, 1362 false, 1363 }, 1364 { 1365 &State{}, 1366 false, 1367 }, 1368 { 1369 &State{ 1370 Remote: &RemoteState{Type: "foo"}, 1371 }, 1372 true, 1373 }, 1374 } 1375 1376 for i, tc := range cases { 1377 if tc.In.IsRemote() != tc.Result { 1378 t.Fatalf("bad %d %#v:\n\n%#v", i, tc.Result, tc.In) 1379 } 1380 } 1381 } 1382 1383 func TestInstanceState_MergeDiff(t *testing.T) { 1384 is := InstanceState{ 1385 ID: "foo", 1386 Attributes: map[string]string{ 1387 "foo": "bar", 1388 "port": "8000", 1389 }, 1390 } 1391 1392 diff := &InstanceDiff{ 1393 Attributes: map[string]*ResourceAttrDiff{ 1394 "foo": { 1395 Old: "bar", 1396 New: "baz", 1397 }, 1398 "bar": { 1399 Old: "", 1400 New: "foo", 1401 }, 1402 "baz": { 1403 Old: "", 1404 New: "foo", 1405 NewComputed: true, 1406 }, 1407 "port": { 1408 NewRemoved: true, 1409 }, 1410 }, 1411 } 1412 1413 is2 := is.MergeDiff(diff) 1414 1415 expected := map[string]string{ 1416 "foo": "baz", 1417 "bar": "foo", 1418 "baz": hcl2shim.UnknownVariableValue, 1419 } 1420 1421 if !reflect.DeepEqual(expected, is2.Attributes) { 1422 t.Fatalf("bad: %#v", is2.Attributes) 1423 } 1424 } 1425 1426 // GH-12183. This tests that a list with a computed set generates the 1427 // right partial state. This never failed but is put here for completion 1428 // of the test case for GH-12183. 1429 func TestInstanceState_MergeDiff_computedSet(t *testing.T) { 1430 is := InstanceState{} 1431 1432 diff := &InstanceDiff{ 1433 Attributes: map[string]*ResourceAttrDiff{ 1434 "config.#": { 1435 Old: "0", 1436 New: "1", 1437 RequiresNew: true, 1438 }, 1439 1440 "config.0.name": { 1441 Old: "", 1442 New: "hello", 1443 }, 1444 1445 "config.0.rules.#": { 1446 Old: "", 1447 NewComputed: true, 1448 }, 1449 }, 1450 } 1451 1452 is2 := is.MergeDiff(diff) 1453 1454 expected := map[string]string{ 1455 "config.#": "1", 1456 "config.0.name": "hello", 1457 "config.0.rules.#": hcl2shim.UnknownVariableValue, 1458 } 1459 1460 if !reflect.DeepEqual(expected, is2.Attributes) { 1461 t.Fatalf("bad: %#v", is2.Attributes) 1462 } 1463 } 1464 1465 func TestInstanceState_MergeDiff_nil(t *testing.T) { 1466 var is *InstanceState 1467 1468 diff := &InstanceDiff{ 1469 Attributes: map[string]*ResourceAttrDiff{ 1470 "foo": { 1471 Old: "", 1472 New: "baz", 1473 }, 1474 }, 1475 } 1476 1477 is2 := is.MergeDiff(diff) 1478 1479 expected := map[string]string{ 1480 "foo": "baz", 1481 } 1482 1483 if !reflect.DeepEqual(expected, is2.Attributes) { 1484 t.Fatalf("bad: %#v", is2.Attributes) 1485 } 1486 } 1487 1488 func TestInstanceState_MergeDiff_nilDiff(t *testing.T) { 1489 is := InstanceState{ 1490 ID: "foo", 1491 Attributes: map[string]string{ 1492 "foo": "bar", 1493 }, 1494 } 1495 1496 is2 := is.MergeDiff(nil) 1497 1498 expected := map[string]string{ 1499 "foo": "bar", 1500 } 1501 1502 if !reflect.DeepEqual(expected, is2.Attributes) { 1503 t.Fatalf("bad: %#v", is2.Attributes) 1504 } 1505 } 1506 1507 func TestReadWriteState(t *testing.T) { 1508 state := &State{ 1509 Serial: 9, 1510 Lineage: "5d1ad1a1-4027-4665-a908-dbe6adff11d8", 1511 Remote: &RemoteState{ 1512 Type: "http", 1513 Config: map[string]string{ 1514 "url": "http://my-cool-server.com/", 1515 }, 1516 }, 1517 Modules: []*ModuleState{ 1518 { 1519 Path: rootModulePath, 1520 Dependencies: []string{ 1521 "aws_instance.bar", 1522 }, 1523 Resources: map[string]*ResourceState{ 1524 "foo": { 1525 Primary: &InstanceState{ 1526 ID: "bar", 1527 Ephemeral: EphemeralState{ 1528 ConnInfo: map[string]string{ 1529 "type": "ssh", 1530 "user": "root", 1531 "password": "supersecret", 1532 }, 1533 }, 1534 }, 1535 }, 1536 }, 1537 }, 1538 }, 1539 } 1540 state.init() 1541 1542 buf := new(bytes.Buffer) 1543 if err := WriteState(state, buf); err != nil { 1544 t.Fatalf("err: %s", err) 1545 } 1546 1547 // Verify that the version and serial are set 1548 if state.Version != StateVersion { 1549 t.Fatalf("bad version number: %d", state.Version) 1550 } 1551 1552 actual, err := ReadState(buf) 1553 if err != nil { 1554 t.Fatalf("err: %s", err) 1555 } 1556 1557 // ReadState should not restore sensitive information! 1558 mod := state.RootModule() 1559 mod.Resources["foo"].Primary.Ephemeral = EphemeralState{} 1560 mod.Resources["foo"].Primary.Ephemeral.init() 1561 1562 if !reflect.DeepEqual(actual, state) { 1563 t.Logf("expected:\n%#v", state) 1564 t.Fatalf("got:\n%#v", actual) 1565 } 1566 } 1567 1568 func TestReadStateNewVersion(t *testing.T) { 1569 type out struct { 1570 Version int 1571 } 1572 1573 buf, err := json.Marshal(&out{StateVersion + 1}) 1574 if err != nil { 1575 t.Fatalf("err: %v", err) 1576 } 1577 1578 s, err := ReadState(bytes.NewReader(buf)) 1579 if s != nil { 1580 t.Fatalf("unexpected: %#v", s) 1581 } 1582 if !strings.Contains(err.Error(), "does not support state version") { 1583 t.Fatalf("err: %v", err) 1584 } 1585 } 1586 1587 func TestReadStateEmptyOrNilFile(t *testing.T) { 1588 var emptyState bytes.Buffer 1589 _, err := ReadState(&emptyState) 1590 if err != ErrNoState { 1591 t.Fatal("expected ErrNostate, got", err) 1592 } 1593 1594 var nilFile *os.File 1595 _, err = ReadState(nilFile) 1596 if err != ErrNoState { 1597 t.Fatal("expected ErrNostate, got", err) 1598 } 1599 } 1600 1601 func TestReadStateTFVersion(t *testing.T) { 1602 type tfVersion struct { 1603 Version int `json:"version"` 1604 TFVersion string `json:"terraform_version"` 1605 } 1606 1607 cases := []struct { 1608 Written string 1609 Read string 1610 Err bool 1611 }{ 1612 { 1613 "0.0.0", 1614 "0.0.0", 1615 false, 1616 }, 1617 { 1618 "", 1619 "", 1620 false, 1621 }, 1622 { 1623 "bad", 1624 "", 1625 true, 1626 }, 1627 } 1628 1629 for _, tc := range cases { 1630 buf, err := json.Marshal(&tfVersion{ 1631 Version: 2, 1632 TFVersion: tc.Written, 1633 }) 1634 if err != nil { 1635 t.Fatalf("err: %v", err) 1636 } 1637 1638 s, err := ReadState(bytes.NewReader(buf)) 1639 if (err != nil) != tc.Err { 1640 t.Fatalf("%s: err: %s", tc.Written, err) 1641 } 1642 if err != nil { 1643 continue 1644 } 1645 1646 if s.TFVersion != tc.Read { 1647 t.Fatalf("%s: bad: %s", tc.Written, s.TFVersion) 1648 } 1649 } 1650 } 1651 1652 func TestWriteStateTFVersion(t *testing.T) { 1653 cases := []struct { 1654 Write string 1655 Read string 1656 Err bool 1657 }{ 1658 { 1659 "0.0.0", 1660 "0.0.0", 1661 false, 1662 }, 1663 { 1664 "", 1665 "", 1666 false, 1667 }, 1668 { 1669 "bad", 1670 "", 1671 true, 1672 }, 1673 } 1674 1675 for _, tc := range cases { 1676 var buf bytes.Buffer 1677 err := WriteState(&State{TFVersion: tc.Write}, &buf) 1678 if (err != nil) != tc.Err { 1679 t.Fatalf("%s: err: %s", tc.Write, err) 1680 } 1681 if err != nil { 1682 continue 1683 } 1684 1685 s, err := ReadState(&buf) 1686 if err != nil { 1687 t.Fatalf("%s: err: %s", tc.Write, err) 1688 } 1689 1690 if s.TFVersion != tc.Read { 1691 t.Fatalf("%s: bad: %s", tc.Write, s.TFVersion) 1692 } 1693 } 1694 } 1695 1696 func TestParseResourceStateKey(t *testing.T) { 1697 cases := []struct { 1698 Input string 1699 Expected *ResourceStateKey 1700 ExpectedErr bool 1701 }{ 1702 { 1703 Input: "aws_instance.foo.3", 1704 Expected: &ResourceStateKey{ 1705 Mode: ManagedResourceMode, 1706 Type: "aws_instance", 1707 Name: "foo", 1708 Index: 3, 1709 }, 1710 }, 1711 { 1712 Input: "aws_instance.foo.0", 1713 Expected: &ResourceStateKey{ 1714 Mode: ManagedResourceMode, 1715 Type: "aws_instance", 1716 Name: "foo", 1717 Index: 0, 1718 }, 1719 }, 1720 { 1721 Input: "aws_instance.foo", 1722 Expected: &ResourceStateKey{ 1723 Mode: ManagedResourceMode, 1724 Type: "aws_instance", 1725 Name: "foo", 1726 Index: -1, 1727 }, 1728 }, 1729 { 1730 Input: "data.aws_ami.foo", 1731 Expected: &ResourceStateKey{ 1732 Mode: DataResourceMode, 1733 Type: "aws_ami", 1734 Name: "foo", 1735 Index: -1, 1736 }, 1737 }, 1738 { 1739 Input: "aws_instance.foo.malformed", 1740 ExpectedErr: true, 1741 }, 1742 { 1743 Input: "aws_instance.foo.malformedwithnumber.123", 1744 ExpectedErr: true, 1745 }, 1746 { 1747 Input: "malformed", 1748 ExpectedErr: true, 1749 }, 1750 } 1751 for _, tc := range cases { 1752 rsk, err := ParseResourceStateKey(tc.Input) 1753 if rsk != nil && tc.Expected != nil && !rsk.Equal(tc.Expected) { 1754 t.Fatalf("%s: expected %s, got %s", tc.Input, tc.Expected, rsk) 1755 } 1756 if (err != nil) != tc.ExpectedErr { 1757 t.Fatalf("%s: expected err: %t, got %s", tc.Input, tc.ExpectedErr, err) 1758 } 1759 } 1760 } 1761 1762 func TestReadState_prune(t *testing.T) { 1763 state := &State{ 1764 Modules: []*ModuleState{ 1765 {Path: rootModulePath}, 1766 nil, 1767 }, 1768 } 1769 state.init() 1770 1771 buf := new(bytes.Buffer) 1772 if err := WriteState(state, buf); err != nil { 1773 t.Fatalf("err: %s", err) 1774 } 1775 1776 actual, err := ReadState(buf) 1777 if err != nil { 1778 t.Fatalf("err: %s", err) 1779 } 1780 1781 expected := &State{ 1782 Version: state.Version, 1783 Lineage: state.Lineage, 1784 } 1785 expected.init() 1786 1787 if !reflect.DeepEqual(actual, expected) { 1788 t.Fatalf("got:\n%#v", actual) 1789 } 1790 } 1791 1792 func TestReadState_pruneDependencies(t *testing.T) { 1793 state := &State{ 1794 Serial: 9, 1795 Lineage: "5d1ad1a1-4027-4665-a908-dbe6adff11d8", 1796 Remote: &RemoteState{ 1797 Type: "http", 1798 Config: map[string]string{ 1799 "url": "http://my-cool-server.com/", 1800 }, 1801 }, 1802 Modules: []*ModuleState{ 1803 { 1804 Path: rootModulePath, 1805 Dependencies: []string{ 1806 "aws_instance.bar", 1807 "aws_instance.bar", 1808 }, 1809 Resources: map[string]*ResourceState{ 1810 "foo": { 1811 Dependencies: []string{ 1812 "aws_instance.baz", 1813 "aws_instance.baz", 1814 }, 1815 Primary: &InstanceState{ 1816 ID: "bar", 1817 }, 1818 }, 1819 }, 1820 }, 1821 }, 1822 } 1823 state.init() 1824 1825 buf := new(bytes.Buffer) 1826 if err := WriteState(state, buf); err != nil { 1827 t.Fatalf("err: %s", err) 1828 } 1829 1830 actual, err := ReadState(buf) 1831 if err != nil { 1832 t.Fatalf("err: %s", err) 1833 } 1834 1835 // make sure the duplicate Dependencies are filtered 1836 modDeps := actual.Modules[0].Dependencies 1837 resourceDeps := actual.Modules[0].Resources["foo"].Dependencies 1838 1839 if len(modDeps) > 1 || modDeps[0] != "aws_instance.bar" { 1840 t.Fatalf("expected 1 module depends_on entry, got %q", modDeps) 1841 } 1842 1843 if len(resourceDeps) > 1 || resourceDeps[0] != "aws_instance.baz" { 1844 t.Fatalf("expected 1 resource depends_on entry, got %q", resourceDeps) 1845 } 1846 } 1847 1848 func TestReadState_bigHash(t *testing.T) { 1849 expected := uint64(14885267135666261723) 1850 s := strings.NewReader(`{"version": 3, "backend":{"hash":14885267135666261723}}`) 1851 1852 actual, err := ReadState(s) 1853 if err != nil { 1854 t.Fatal(err) 1855 } 1856 1857 if actual.Backend.Hash != expected { 1858 t.Fatalf("expected backend hash %d, got %d", expected, actual.Backend.Hash) 1859 } 1860 } 1861 1862 func TestResourceNameSort(t *testing.T) { 1863 names := []string{ 1864 "a", 1865 "b", 1866 "a.0", 1867 "a.c", 1868 "a.d", 1869 "c", 1870 "a.b.0", 1871 "a.b.1", 1872 "a.b.10", 1873 "a.b.2", 1874 } 1875 1876 sort.Sort(resourceNameSort(names)) 1877 1878 expected := []string{ 1879 "a", 1880 "a.0", 1881 "a.b.0", 1882 "a.b.1", 1883 "a.b.2", 1884 "a.b.10", 1885 "a.c", 1886 "a.d", 1887 "b", 1888 "c", 1889 } 1890 1891 if !reflect.DeepEqual(names, expected) { 1892 t.Fatalf("got: %q\nexpected: %q\n", names, expected) 1893 } 1894 }