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