github.com/acm1/terraform@v0.6.2-0.20150729164239-1f314444f45c/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/hashicorp/terraform/config" 11 ) 12 13 func TestStateAddModule(t *testing.T) { 14 cases := []struct { 15 In [][]string 16 Out [][]string 17 }{ 18 { 19 [][]string{ 20 []string{"root"}, 21 []string{"root", "child"}, 22 }, 23 [][]string{ 24 []string{"root"}, 25 []string{"root", "child"}, 26 }, 27 }, 28 29 { 30 [][]string{ 31 []string{"root", "foo", "bar"}, 32 []string{"root", "foo"}, 33 []string{"root"}, 34 []string{"root", "bar"}, 35 }, 36 [][]string{ 37 []string{"root"}, 38 []string{"root", "bar"}, 39 []string{"root", "foo"}, 40 []string{"root", "foo", "bar"}, 41 }, 42 }, 43 } 44 45 for _, tc := range cases { 46 s := new(State) 47 for _, p := range tc.In { 48 s.AddModule(p) 49 } 50 51 actual := make([][]string, 0, len(tc.In)) 52 for _, m := range s.Modules { 53 actual = append(actual, m.Path) 54 } 55 56 if !reflect.DeepEqual(actual, tc.Out) { 57 t.Fatalf("In: %#v\n\nOut: %#v", tc.In, actual) 58 } 59 } 60 } 61 62 func TestStateModuleOrphans(t *testing.T) { 63 state := &State{ 64 Modules: []*ModuleState{ 65 &ModuleState{ 66 Path: RootModulePath, 67 }, 68 &ModuleState{ 69 Path: []string{RootModuleName, "foo"}, 70 }, 71 &ModuleState{ 72 Path: []string{RootModuleName, "bar"}, 73 }, 74 }, 75 } 76 77 config := testModule(t, "state-module-orphans").Config() 78 actual := state.ModuleOrphans(RootModulePath, config) 79 expected := [][]string{ 80 []string{RootModuleName, "foo"}, 81 } 82 83 if !reflect.DeepEqual(actual, expected) { 84 t.Fatalf("bad: %#v", actual) 85 } 86 } 87 88 func TestStateModuleOrphans_nested(t *testing.T) { 89 state := &State{ 90 Modules: []*ModuleState{ 91 &ModuleState{ 92 Path: RootModulePath, 93 }, 94 &ModuleState{ 95 Path: []string{RootModuleName, "foo", "bar"}, 96 }, 97 }, 98 } 99 100 actual := state.ModuleOrphans(RootModulePath, nil) 101 expected := [][]string{ 102 []string{RootModuleName, "foo"}, 103 } 104 105 if !reflect.DeepEqual(actual, expected) { 106 t.Fatalf("bad: %#v", actual) 107 } 108 } 109 110 func TestStateModuleOrphans_nilConfig(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 actual := state.ModuleOrphans(RootModulePath, nil) 126 expected := [][]string{ 127 []string{RootModuleName, "foo"}, 128 []string{RootModuleName, "bar"}, 129 } 130 131 if !reflect.DeepEqual(actual, expected) { 132 t.Fatalf("bad: %#v", actual) 133 } 134 } 135 136 func TestStateEqual(t *testing.T) { 137 cases := []struct { 138 Result bool 139 One, Two *State 140 }{ 141 // Nils 142 { 143 false, 144 nil, 145 &State{Version: 2}, 146 }, 147 148 { 149 true, 150 nil, 151 nil, 152 }, 153 154 // Different versions 155 { 156 false, 157 &State{Version: 5}, 158 &State{Version: 2}, 159 }, 160 161 // Different modules 162 { 163 false, 164 &State{ 165 Modules: []*ModuleState{ 166 &ModuleState{ 167 Path: RootModulePath, 168 }, 169 }, 170 }, 171 &State{}, 172 }, 173 174 { 175 true, 176 &State{ 177 Modules: []*ModuleState{ 178 &ModuleState{ 179 Path: RootModulePath, 180 }, 181 }, 182 }, 183 &State{ 184 Modules: []*ModuleState{ 185 &ModuleState{ 186 Path: RootModulePath, 187 }, 188 }, 189 }, 190 }, 191 } 192 193 for i, tc := range cases { 194 if tc.One.Equal(tc.Two) != tc.Result { 195 t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String()) 196 } 197 if tc.Two.Equal(tc.One) != tc.Result { 198 t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String()) 199 } 200 } 201 } 202 203 func TestStateIncrementSerialMaybe(t *testing.T) { 204 cases := map[string]struct { 205 S1, S2 *State 206 Serial int64 207 }{ 208 "S2 is nil": { 209 &State{}, 210 nil, 211 0, 212 }, 213 "S2 is identical": { 214 &State{}, 215 &State{}, 216 0, 217 }, 218 "S2 is different": { 219 &State{}, 220 &State{ 221 Modules: []*ModuleState{ 222 &ModuleState{Path: rootModulePath}, 223 }, 224 }, 225 1, 226 }, 227 "S1 serial is higher": { 228 &State{Serial: 5}, 229 &State{ 230 Serial: 3, 231 Modules: []*ModuleState{ 232 &ModuleState{Path: rootModulePath}, 233 }, 234 }, 235 5, 236 }, 237 } 238 239 for name, tc := range cases { 240 tc.S1.IncrementSerialMaybe(tc.S2) 241 if tc.S1.Serial != tc.Serial { 242 t.Fatalf("Bad: %s\nGot: %d", name, tc.S1.Serial) 243 } 244 } 245 } 246 247 func TestResourceStateEqual(t *testing.T) { 248 cases := []struct { 249 Result bool 250 One, Two *ResourceState 251 }{ 252 // Different types 253 { 254 false, 255 &ResourceState{Type: "foo"}, 256 &ResourceState{Type: "bar"}, 257 }, 258 259 // Different dependencies 260 { 261 false, 262 &ResourceState{Dependencies: []string{"foo"}}, 263 &ResourceState{Dependencies: []string{"bar"}}, 264 }, 265 266 { 267 false, 268 &ResourceState{Dependencies: []string{"foo", "bar"}}, 269 &ResourceState{Dependencies: []string{"foo"}}, 270 }, 271 272 { 273 true, 274 &ResourceState{Dependencies: []string{"bar", "foo"}}, 275 &ResourceState{Dependencies: []string{"foo", "bar"}}, 276 }, 277 278 // Different primaries 279 { 280 false, 281 &ResourceState{Primary: nil}, 282 &ResourceState{Primary: &InstanceState{ID: "foo"}}, 283 }, 284 285 { 286 true, 287 &ResourceState{Primary: &InstanceState{ID: "foo"}}, 288 &ResourceState{Primary: &InstanceState{ID: "foo"}}, 289 }, 290 291 // Different tainted 292 { 293 false, 294 &ResourceState{ 295 Tainted: nil, 296 }, 297 &ResourceState{ 298 Tainted: []*InstanceState{ 299 &InstanceState{ID: "foo"}, 300 }, 301 }, 302 }, 303 304 { 305 true, 306 &ResourceState{ 307 Tainted: []*InstanceState{ 308 &InstanceState{ID: "foo"}, 309 }, 310 }, 311 &ResourceState{ 312 Tainted: []*InstanceState{ 313 &InstanceState{ID: "foo"}, 314 }, 315 }, 316 }, 317 318 { 319 true, 320 &ResourceState{ 321 Tainted: []*InstanceState{ 322 &InstanceState{ID: "foo"}, 323 nil, 324 }, 325 }, 326 &ResourceState{ 327 Tainted: []*InstanceState{ 328 &InstanceState{ID: "foo"}, 329 }, 330 }, 331 }, 332 } 333 334 for i, tc := range cases { 335 if tc.One.Equal(tc.Two) != tc.Result { 336 t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String()) 337 } 338 if tc.Two.Equal(tc.One) != tc.Result { 339 t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String()) 340 } 341 } 342 } 343 344 func TestResourceStateTaint(t *testing.T) { 345 cases := map[string]struct { 346 Input *ResourceState 347 Output *ResourceState 348 }{ 349 "no primary": { 350 &ResourceState{}, 351 &ResourceState{}, 352 }, 353 354 "primary, no tainted": { 355 &ResourceState{ 356 Primary: &InstanceState{ID: "foo"}, 357 }, 358 &ResourceState{ 359 Tainted: []*InstanceState{ 360 &InstanceState{ID: "foo"}, 361 }, 362 }, 363 }, 364 365 "primary, with tainted": { 366 &ResourceState{ 367 Primary: &InstanceState{ID: "foo"}, 368 Tainted: []*InstanceState{ 369 &InstanceState{ID: "bar"}, 370 }, 371 }, 372 &ResourceState{ 373 Tainted: []*InstanceState{ 374 &InstanceState{ID: "bar"}, 375 &InstanceState{ID: "foo"}, 376 }, 377 }, 378 }, 379 } 380 381 for k, tc := range cases { 382 tc.Input.Taint() 383 if !reflect.DeepEqual(tc.Input, tc.Output) { 384 t.Fatalf( 385 "Failure: %s\n\nExpected: %#v\n\nGot: %#v", 386 k, tc.Output, tc.Input) 387 } 388 } 389 } 390 391 func TestInstanceStateEmpty(t *testing.T) { 392 cases := map[string]struct { 393 In *InstanceState 394 Result bool 395 }{ 396 "nil is empty": { 397 nil, 398 true, 399 }, 400 "non-nil but without ID is empty": { 401 &InstanceState{}, 402 true, 403 }, 404 "with ID is not empty": { 405 &InstanceState{ 406 ID: "i-abc123", 407 }, 408 false, 409 }, 410 } 411 412 for tn, tc := range cases { 413 if tc.In.Empty() != tc.Result { 414 t.Fatalf("%q expected %#v to be empty: %#v", tn, tc.In, tc.Result) 415 } 416 } 417 } 418 419 func TestInstanceStateEqual(t *testing.T) { 420 cases := []struct { 421 Result bool 422 One, Two *InstanceState 423 }{ 424 // Nils 425 { 426 false, 427 nil, 428 &InstanceState{}, 429 }, 430 431 { 432 false, 433 &InstanceState{}, 434 nil, 435 }, 436 437 // Different IDs 438 { 439 false, 440 &InstanceState{ID: "foo"}, 441 &InstanceState{ID: "bar"}, 442 }, 443 444 // Different Attributes 445 { 446 false, 447 &InstanceState{Attributes: map[string]string{"foo": "bar"}}, 448 &InstanceState{Attributes: map[string]string{"foo": "baz"}}, 449 }, 450 451 // Different Attribute keys 452 { 453 false, 454 &InstanceState{Attributes: map[string]string{"foo": "bar"}}, 455 &InstanceState{Attributes: map[string]string{"bar": "baz"}}, 456 }, 457 458 { 459 false, 460 &InstanceState{Attributes: map[string]string{"bar": "baz"}}, 461 &InstanceState{Attributes: map[string]string{"foo": "bar"}}, 462 }, 463 } 464 465 for i, tc := range cases { 466 if tc.One.Equal(tc.Two) != tc.Result { 467 t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String()) 468 } 469 } 470 } 471 472 func TestStateEmpty(t *testing.T) { 473 cases := []struct { 474 In *State 475 Result bool 476 }{ 477 { 478 nil, 479 true, 480 }, 481 { 482 &State{}, 483 true, 484 }, 485 { 486 &State{ 487 Remote: &RemoteState{Type: "foo"}, 488 }, 489 true, 490 }, 491 { 492 &State{ 493 Modules: []*ModuleState{ 494 &ModuleState{}, 495 }, 496 }, 497 false, 498 }, 499 } 500 501 for i, tc := range cases { 502 if tc.In.Empty() != tc.Result { 503 t.Fatalf("bad %d %#v:\n\n%#v", i, tc.Result, tc.In) 504 } 505 } 506 } 507 508 func TestStateIsRemote(t *testing.T) { 509 cases := []struct { 510 In *State 511 Result bool 512 }{ 513 { 514 nil, 515 false, 516 }, 517 { 518 &State{}, 519 false, 520 }, 521 { 522 &State{ 523 Remote: &RemoteState{Type: "foo"}, 524 }, 525 true, 526 }, 527 } 528 529 for i, tc := range cases { 530 if tc.In.IsRemote() != tc.Result { 531 t.Fatalf("bad %d %#v:\n\n%#v", i, tc.Result, tc.In) 532 } 533 } 534 } 535 536 func TestInstanceState_MergeDiff(t *testing.T) { 537 is := InstanceState{ 538 ID: "foo", 539 Attributes: map[string]string{ 540 "foo": "bar", 541 "port": "8000", 542 }, 543 } 544 545 diff := &InstanceDiff{ 546 Attributes: map[string]*ResourceAttrDiff{ 547 "foo": &ResourceAttrDiff{ 548 Old: "bar", 549 New: "baz", 550 }, 551 "bar": &ResourceAttrDiff{ 552 Old: "", 553 New: "foo", 554 }, 555 "baz": &ResourceAttrDiff{ 556 Old: "", 557 New: "foo", 558 NewComputed: true, 559 }, 560 "port": &ResourceAttrDiff{ 561 NewRemoved: true, 562 }, 563 }, 564 } 565 566 is2 := is.MergeDiff(diff) 567 568 expected := map[string]string{ 569 "foo": "baz", 570 "bar": "foo", 571 "baz": config.UnknownVariableValue, 572 } 573 574 if !reflect.DeepEqual(expected, is2.Attributes) { 575 t.Fatalf("bad: %#v", is2.Attributes) 576 } 577 } 578 579 func TestInstanceState_MergeDiff_nil(t *testing.T) { 580 var is *InstanceState = nil 581 582 diff := &InstanceDiff{ 583 Attributes: map[string]*ResourceAttrDiff{ 584 "foo": &ResourceAttrDiff{ 585 Old: "", 586 New: "baz", 587 }, 588 }, 589 } 590 591 is2 := is.MergeDiff(diff) 592 593 expected := map[string]string{ 594 "foo": "baz", 595 } 596 597 if !reflect.DeepEqual(expected, is2.Attributes) { 598 t.Fatalf("bad: %#v", is2.Attributes) 599 } 600 } 601 602 func TestInstanceState_MergeDiff_nilDiff(t *testing.T) { 603 is := InstanceState{ 604 ID: "foo", 605 Attributes: map[string]string{ 606 "foo": "bar", 607 }, 608 } 609 610 is2 := is.MergeDiff(nil) 611 612 expected := map[string]string{ 613 "foo": "bar", 614 } 615 616 if !reflect.DeepEqual(expected, is2.Attributes) { 617 t.Fatalf("bad: %#v", is2.Attributes) 618 } 619 } 620 621 func TestReadUpgradeState(t *testing.T) { 622 state := &StateV1{ 623 Resources: map[string]*ResourceStateV1{ 624 "foo": &ResourceStateV1{ 625 ID: "bar", 626 }, 627 }, 628 } 629 buf := new(bytes.Buffer) 630 if err := testWriteStateV1(state, buf); err != nil { 631 t.Fatalf("err: %s", err) 632 } 633 634 // ReadState should transparently detect the old 635 // version and upgrade up so the latest. 636 actual, err := ReadState(buf) 637 if err != nil { 638 t.Fatalf("err: %s", err) 639 } 640 641 upgraded, err := upgradeV1State(state) 642 if err != nil { 643 t.Fatalf("err: %s", err) 644 } 645 646 if !reflect.DeepEqual(actual, upgraded) { 647 t.Fatalf("bad: %#v", actual) 648 } 649 } 650 651 func TestReadWriteState(t *testing.T) { 652 state := &State{ 653 Serial: 9, 654 Remote: &RemoteState{ 655 Type: "http", 656 Config: map[string]string{ 657 "url": "http://my-cool-server.com/", 658 }, 659 }, 660 Modules: []*ModuleState{ 661 &ModuleState{ 662 Path: rootModulePath, 663 Dependencies: []string{ 664 "aws_instance.bar", 665 }, 666 Resources: map[string]*ResourceState{ 667 "foo": &ResourceState{ 668 Primary: &InstanceState{ 669 ID: "bar", 670 Ephemeral: EphemeralState{ 671 ConnInfo: map[string]string{ 672 "type": "ssh", 673 "user": "root", 674 "password": "supersecret", 675 }, 676 }, 677 }, 678 }, 679 }, 680 }, 681 }, 682 } 683 684 // Checksum before the write 685 chksum := checksumStruct(t, state) 686 687 buf := new(bytes.Buffer) 688 if err := WriteState(state, buf); err != nil { 689 t.Fatalf("err: %s", err) 690 } 691 692 // Verify that the version and serial are set 693 if state.Version != StateVersion { 694 t.Fatalf("bad version number: %d", state.Version) 695 } 696 697 // Checksum after the write 698 chksumAfter := checksumStruct(t, state) 699 if chksumAfter != chksum { 700 t.Fatalf("structure changed during serialization!") 701 } 702 703 actual, err := ReadState(buf) 704 if err != nil { 705 t.Fatalf("err: %s", err) 706 } 707 708 // ReadState should not restore sensitive information! 709 mod := state.RootModule() 710 mod.Resources["foo"].Primary.Ephemeral = EphemeralState{} 711 712 if !reflect.DeepEqual(actual, state) { 713 t.Fatalf("bad: %#v", actual) 714 } 715 } 716 717 func TestReadStateNewVersion(t *testing.T) { 718 type out struct { 719 Version int 720 } 721 722 buf, err := json.Marshal(&out{StateVersion + 1}) 723 if err != nil { 724 t.Fatalf("err: %v", err) 725 } 726 727 s, err := ReadState(bytes.NewReader(buf)) 728 if s != nil { 729 t.Fatalf("unexpected: %#v", s) 730 } 731 if !strings.Contains(err.Error(), "not supported") { 732 t.Fatalf("err: %v", err) 733 } 734 } 735 736 func TestUpgradeV1State(t *testing.T) { 737 old := &StateV1{ 738 Outputs: map[string]string{ 739 "ip": "127.0.0.1", 740 }, 741 Resources: map[string]*ResourceStateV1{ 742 "foo": &ResourceStateV1{ 743 Type: "test_resource", 744 ID: "bar", 745 Attributes: map[string]string{ 746 "key": "val", 747 }, 748 }, 749 "bar": &ResourceStateV1{ 750 Type: "test_resource", 751 ID: "1234", 752 Attributes: map[string]string{ 753 "a": "b", 754 }, 755 }, 756 }, 757 Tainted: map[string]struct{}{ 758 "bar": struct{}{}, 759 }, 760 } 761 state, err := upgradeV1State(old) 762 if err != nil { 763 t.Fatalf("err: %v", err) 764 } 765 766 if len(state.Modules) != 1 { 767 t.Fatalf("should only have root module: %#v", state.Modules) 768 } 769 root := state.RootModule() 770 771 if len(root.Outputs) != 1 { 772 t.Fatalf("bad outputs: %v", root.Outputs) 773 } 774 if root.Outputs["ip"] != "127.0.0.1" { 775 t.Fatalf("bad outputs: %v", root.Outputs) 776 } 777 778 if len(root.Resources) != 2 { 779 t.Fatalf("bad resources: %v", root.Resources) 780 } 781 782 foo := root.Resources["foo"] 783 if foo.Type != "test_resource" { 784 t.Fatalf("bad: %#v", foo) 785 } 786 if foo.Primary == nil || foo.Primary.ID != "bar" || 787 foo.Primary.Attributes["key"] != "val" { 788 t.Fatalf("bad: %#v", foo) 789 } 790 if len(foo.Tainted) > 0 { 791 t.Fatalf("bad: %#v", foo) 792 } 793 794 bar := root.Resources["bar"] 795 if bar.Type != "test_resource" { 796 t.Fatalf("bad: %#v", bar) 797 } 798 if bar.Primary != nil { 799 t.Fatalf("bad: %#v", bar) 800 } 801 if len(bar.Tainted) != 1 { 802 t.Fatalf("bad: %#v", bar) 803 } 804 bt := bar.Tainted[0] 805 if bt.ID != "1234" || bt.Attributes["a"] != "b" { 806 t.Fatalf("bad: %#v", bt) 807 } 808 }