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