github.com/icebourg/terraform@v0.6.5-0.20151015205227-263cc1b85535/command/apply_test.go (about) 1 package command 2 3 import ( 4 "bytes" 5 "fmt" 6 "io/ioutil" 7 "net" 8 "net/http" 9 "net/url" 10 "os" 11 "path/filepath" 12 "reflect" 13 "strings" 14 "sync" 15 "testing" 16 "time" 17 18 "github.com/hashicorp/terraform/terraform" 19 "github.com/mitchellh/cli" 20 ) 21 22 func TestApply(t *testing.T) { 23 statePath := testTempFile(t) 24 25 p := testProvider() 26 ui := new(cli.MockUi) 27 c := &ApplyCommand{ 28 Meta: Meta{ 29 ContextOpts: testCtxConfig(p), 30 Ui: ui, 31 }, 32 } 33 34 args := []string{ 35 "-state", statePath, 36 testFixturePath("apply"), 37 } 38 if code := c.Run(args); code != 0 { 39 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 40 } 41 42 if _, err := os.Stat(statePath); err != nil { 43 t.Fatalf("err: %s", err) 44 } 45 46 f, err := os.Open(statePath) 47 if err != nil { 48 t.Fatalf("err: %s", err) 49 } 50 defer f.Close() 51 52 state, err := terraform.ReadState(f) 53 if err != nil { 54 t.Fatalf("err: %s", err) 55 } 56 if state == nil { 57 t.Fatal("state should not be nil") 58 } 59 } 60 61 func TestApply_parallelism1(t *testing.T) { 62 statePath := testTempFile(t) 63 64 ui := new(cli.MockUi) 65 p := testProvider() 66 pr := new(terraform.MockResourceProvisioner) 67 68 pr.ApplyFn = func(*terraform.InstanceState, *terraform.ResourceConfig) error { 69 time.Sleep(time.Second) 70 return nil 71 } 72 73 args := []string{ 74 "-state", statePath, 75 "-parallelism=1", 76 testFixturePath("parallelism"), 77 } 78 79 c := &ApplyCommand{ 80 Meta: Meta{ 81 ContextOpts: testCtxConfigWithShell(p, pr), 82 Ui: ui, 83 }, 84 } 85 86 start := time.Now() 87 if code := c.Run(args); code != 0 { 88 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 89 } 90 elapsed := time.Since(start).Seconds() 91 92 // This test should take exactly two seconds, plus some minor amount of execution time. 93 if elapsed < 2 || elapsed > 2.2 { 94 t.Fatalf("bad: %f\n\n%s", elapsed, ui.ErrorWriter.String()) 95 } 96 97 } 98 99 func TestApply_parallelism2(t *testing.T) { 100 statePath := testTempFile(t) 101 102 ui := new(cli.MockUi) 103 p := testProvider() 104 pr := new(terraform.MockResourceProvisioner) 105 106 pr.ApplyFn = func(*terraform.InstanceState, *terraform.ResourceConfig) error { 107 time.Sleep(time.Second) 108 return nil 109 } 110 111 args := []string{ 112 "-state", statePath, 113 "-parallelism=2", 114 testFixturePath("parallelism"), 115 } 116 117 c := &ApplyCommand{ 118 Meta: Meta{ 119 ContextOpts: testCtxConfigWithShell(p, pr), 120 Ui: ui, 121 }, 122 } 123 124 start := time.Now() 125 if code := c.Run(args); code != 0 { 126 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 127 } 128 elapsed := time.Since(start).Seconds() 129 130 // This test should take exactly one second, plus some minor amount of execution time. 131 if elapsed < 1 || elapsed > 1.2 { 132 t.Fatalf("bad: %f\n\n%s", elapsed, ui.ErrorWriter.String()) 133 } 134 135 } 136 137 func TestApply_configInvalid(t *testing.T) { 138 p := testProvider() 139 ui := new(cli.MockUi) 140 c := &ApplyCommand{ 141 Meta: Meta{ 142 ContextOpts: testCtxConfig(p), 143 Ui: ui, 144 }, 145 } 146 147 args := []string{ 148 "-state", testTempFile(t), 149 testFixturePath("apply-config-invalid"), 150 } 151 if code := c.Run(args); code != 1 { 152 t.Fatalf("bad: \n%s", ui.OutputWriter.String()) 153 } 154 } 155 156 func TestApply_defaultState(t *testing.T) { 157 td, err := ioutil.TempDir("", "tf") 158 if err != nil { 159 t.Fatalf("err: %s", err) 160 } 161 statePath := filepath.Join(td, DefaultStateFilename) 162 163 // Change to the temporary directory 164 cwd, err := os.Getwd() 165 if err != nil { 166 t.Fatalf("err: %s", err) 167 } 168 if err := os.Chdir(filepath.Dir(statePath)); err != nil { 169 t.Fatalf("err: %s", err) 170 } 171 defer os.Chdir(cwd) 172 173 p := testProvider() 174 ui := new(cli.MockUi) 175 c := &ApplyCommand{ 176 Meta: Meta{ 177 ContextOpts: testCtxConfig(p), 178 Ui: ui, 179 }, 180 } 181 182 args := []string{ 183 testFixturePath("apply"), 184 } 185 if code := c.Run(args); code != 0 { 186 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 187 } 188 189 if _, err := os.Stat(statePath); err != nil { 190 t.Fatalf("err: %s", err) 191 } 192 193 f, err := os.Open(statePath) 194 if err != nil { 195 t.Fatalf("err: %s", err) 196 } 197 defer f.Close() 198 199 state, err := terraform.ReadState(f) 200 if err != nil { 201 t.Fatalf("err: %s", err) 202 } 203 if state == nil { 204 t.Fatal("state should not be nil") 205 } 206 } 207 208 func TestApply_error(t *testing.T) { 209 statePath := testTempFile(t) 210 211 p := testProvider() 212 ui := new(cli.MockUi) 213 c := &ApplyCommand{ 214 Meta: Meta{ 215 ContextOpts: testCtxConfig(p), 216 Ui: ui, 217 }, 218 } 219 220 var lock sync.Mutex 221 errored := false 222 p.ApplyFn = func( 223 info *terraform.InstanceInfo, 224 s *terraform.InstanceState, 225 d *terraform.InstanceDiff) (*terraform.InstanceState, error) { 226 lock.Lock() 227 defer lock.Unlock() 228 229 if !errored { 230 errored = true 231 return nil, fmt.Errorf("error") 232 } 233 234 return &terraform.InstanceState{ID: "foo"}, nil 235 } 236 p.DiffFn = func( 237 *terraform.InstanceInfo, 238 *terraform.InstanceState, 239 *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 240 return &terraform.InstanceDiff{ 241 Attributes: map[string]*terraform.ResourceAttrDiff{ 242 "ami": &terraform.ResourceAttrDiff{ 243 New: "bar", 244 }, 245 }, 246 }, nil 247 } 248 249 args := []string{ 250 "-state", statePath, 251 testFixturePath("apply-error"), 252 } 253 if code := c.Run(args); code != 1 { 254 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 255 } 256 257 if _, err := os.Stat(statePath); err != nil { 258 t.Fatalf("err: %s", err) 259 } 260 261 f, err := os.Open(statePath) 262 if err != nil { 263 t.Fatalf("err: %s", err) 264 } 265 defer f.Close() 266 267 state, err := terraform.ReadState(f) 268 if err != nil { 269 t.Fatalf("err: %s", err) 270 } 271 if state == nil { 272 t.Fatal("state should not be nil") 273 } 274 if len(state.RootModule().Resources) == 0 { 275 t.Fatal("no resources in state") 276 } 277 } 278 279 func TestApply_init(t *testing.T) { 280 // Change to the temporary directory 281 cwd, err := os.Getwd() 282 if err != nil { 283 t.Fatalf("err: %s", err) 284 } 285 dir := tempDir(t) 286 if err := os.MkdirAll(dir, 0755); err != nil { 287 t.Fatalf("err: %s", err) 288 } 289 if err := os.Chdir(dir); err != nil { 290 t.Fatalf("err: %s", err) 291 } 292 defer os.Chdir(cwd) 293 294 // Create the test fixtures 295 statePath := testTempFile(t) 296 ln := testHttpServer(t) 297 defer ln.Close() 298 299 // Initialize the command 300 p := testProvider() 301 ui := new(cli.MockUi) 302 c := &ApplyCommand{ 303 Meta: Meta{ 304 ContextOpts: testCtxConfig(p), 305 Ui: ui, 306 }, 307 } 308 309 // Build the URL to the init 310 var u url.URL 311 u.Scheme = "http" 312 u.Host = ln.Addr().String() 313 u.Path = "/header" 314 315 args := []string{ 316 "-state", statePath, 317 u.String(), 318 } 319 if code := c.Run(args); code != 0 { 320 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 321 } 322 323 if _, err := os.Stat("hello.tf"); err != nil { 324 t.Fatalf("err: %s", err) 325 } 326 327 if _, err := os.Stat(statePath); err != nil { 328 t.Fatalf("err: %s", err) 329 } 330 331 f, err := os.Open(statePath) 332 if err != nil { 333 t.Fatalf("err: %s", err) 334 } 335 defer f.Close() 336 337 state, err := terraform.ReadState(f) 338 if err != nil { 339 t.Fatalf("err: %s", err) 340 } 341 if state == nil { 342 t.Fatal("state should not be nil") 343 } 344 } 345 346 func TestApply_input(t *testing.T) { 347 // Disable test mode so input would be asked 348 test = false 349 defer func() { test = true }() 350 351 // Set some default reader/writers for the inputs 352 defaultInputReader = bytes.NewBufferString("foo\n") 353 defaultInputWriter = new(bytes.Buffer) 354 355 statePath := testTempFile(t) 356 357 p := testProvider() 358 ui := new(cli.MockUi) 359 c := &ApplyCommand{ 360 Meta: Meta{ 361 ContextOpts: testCtxConfig(p), 362 Ui: ui, 363 }, 364 } 365 366 args := []string{ 367 "-state", statePath, 368 testFixturePath("apply-input"), 369 } 370 if code := c.Run(args); code != 0 { 371 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 372 } 373 374 if !p.InputCalled { 375 t.Fatal("input should be called") 376 } 377 } 378 379 func TestApply_noArgs(t *testing.T) { 380 cwd, err := os.Getwd() 381 if err != nil { 382 t.Fatalf("err: %s", err) 383 } 384 if err := os.Chdir(testFixturePath("plan")); err != nil { 385 t.Fatalf("err: %s", err) 386 } 387 defer os.Chdir(cwd) 388 389 statePath := testTempFile(t) 390 391 p := testProvider() 392 ui := new(cli.MockUi) 393 c := &ApplyCommand{ 394 Meta: Meta{ 395 ContextOpts: testCtxConfig(p), 396 Ui: ui, 397 }, 398 } 399 400 args := []string{ 401 "-state", statePath, 402 } 403 if code := c.Run(args); code != 0 { 404 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 405 } 406 407 if _, err := os.Stat(statePath); err != nil { 408 t.Fatalf("err: %s", err) 409 } 410 411 f, err := os.Open(statePath) 412 if err != nil { 413 t.Fatalf("err: %s", err) 414 } 415 defer f.Close() 416 417 state, err := terraform.ReadState(f) 418 if err != nil { 419 t.Fatalf("err: %s", err) 420 } 421 if state == nil { 422 t.Fatal("state should not be nil") 423 } 424 } 425 426 func TestApply_plan(t *testing.T) { 427 // Disable test mode so input would be asked 428 test = false 429 defer func() { test = true }() 430 431 // Set some default reader/writers for the inputs 432 defaultInputReader = new(bytes.Buffer) 433 defaultInputWriter = new(bytes.Buffer) 434 435 planPath := testPlanFile(t, &terraform.Plan{ 436 Module: testModule(t, "apply"), 437 }) 438 statePath := testTempFile(t) 439 440 p := testProvider() 441 ui := new(cli.MockUi) 442 c := &ApplyCommand{ 443 Meta: Meta{ 444 ContextOpts: testCtxConfig(p), 445 Ui: ui, 446 }, 447 } 448 449 args := []string{ 450 "-state", statePath, 451 planPath, 452 } 453 if code := c.Run(args); code != 0 { 454 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 455 } 456 457 if p.InputCalled { 458 t.Fatalf("input should not be called for plans") 459 } 460 461 if _, err := os.Stat(statePath); err != nil { 462 t.Fatalf("err: %s", err) 463 } 464 465 f, err := os.Open(statePath) 466 if err != nil { 467 t.Fatalf("err: %s", err) 468 } 469 defer f.Close() 470 471 state, err := terraform.ReadState(f) 472 if err != nil { 473 t.Fatalf("err: %s", err) 474 } 475 if state == nil { 476 t.Fatal("state should not be nil") 477 } 478 } 479 480 func TestApply_plan_remoteState(t *testing.T) { 481 // Disable test mode so input would be asked 482 test = false 483 defer func() { test = true }() 484 tmp, cwd := testCwd(t) 485 defer testFixCwd(t, tmp, cwd) 486 remoteStatePath := filepath.Join(tmp, DefaultDataDir, DefaultStateFilename) 487 if err := os.MkdirAll(filepath.Dir(remoteStatePath), 0755); err != nil { 488 t.Fatalf("err: %s", err) 489 } 490 491 // Set some default reader/writers for the inputs 492 defaultInputReader = new(bytes.Buffer) 493 defaultInputWriter = new(bytes.Buffer) 494 495 // Create a remote state 496 state := testState() 497 conf, srv := testRemoteState(t, state, 200) 498 defer srv.Close() 499 state.Remote = conf 500 501 planPath := testPlanFile(t, &terraform.Plan{ 502 Module: testModule(t, "apply"), 503 State: state, 504 }) 505 506 p := testProvider() 507 ui := new(cli.MockUi) 508 c := &ApplyCommand{ 509 Meta: Meta{ 510 ContextOpts: testCtxConfig(p), 511 Ui: ui, 512 }, 513 } 514 515 args := []string{ 516 planPath, 517 } 518 if code := c.Run(args); code != 0 { 519 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 520 } 521 522 if p.InputCalled { 523 t.Fatalf("input should not be called for plans") 524 } 525 526 // State file should be not be installed 527 if _, err := os.Stat(filepath.Join(tmp, DefaultStateFilename)); err == nil { 528 t.Fatalf("State path should not exist") 529 } 530 531 // Check for remote state 532 if _, err := os.Stat(remoteStatePath); err != nil { 533 t.Fatalf("missing remote state: %s", err) 534 } 535 } 536 537 func TestApply_planWithVarFile(t *testing.T) { 538 varFileDir := testTempDir(t) 539 varFilePath := filepath.Join(varFileDir, "terraform.tfvars") 540 if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil { 541 t.Fatalf("err: %s", err) 542 } 543 544 planPath := testPlanFile(t, &terraform.Plan{ 545 Module: testModule(t, "apply"), 546 }) 547 statePath := testTempFile(t) 548 549 cwd, err := os.Getwd() 550 if err != nil { 551 t.Fatalf("err: %s", err) 552 } 553 if err := os.Chdir(varFileDir); err != nil { 554 t.Fatalf("err: %s", err) 555 } 556 defer os.Chdir(cwd) 557 558 p := testProvider() 559 ui := new(cli.MockUi) 560 c := &ApplyCommand{ 561 Meta: Meta{ 562 ContextOpts: testCtxConfig(p), 563 Ui: ui, 564 }, 565 } 566 567 args := []string{ 568 "-state", statePath, 569 planPath, 570 } 571 if code := c.Run(args); code != 0 { 572 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 573 } 574 575 if _, err := os.Stat(statePath); err != nil { 576 t.Fatalf("err: %s", err) 577 } 578 579 f, err := os.Open(statePath) 580 if err != nil { 581 t.Fatalf("err: %s", err) 582 } 583 defer f.Close() 584 585 state, err := terraform.ReadState(f) 586 if err != nil { 587 t.Fatalf("err: %s", err) 588 } 589 if state == nil { 590 t.Fatal("state should not be nil") 591 } 592 } 593 594 func TestApply_planVars(t *testing.T) { 595 planPath := testPlanFile(t, &terraform.Plan{ 596 Module: testModule(t, "apply"), 597 }) 598 statePath := testTempFile(t) 599 600 p := testProvider() 601 ui := new(cli.MockUi) 602 c := &ApplyCommand{ 603 Meta: Meta{ 604 ContextOpts: testCtxConfig(p), 605 Ui: ui, 606 }, 607 } 608 609 args := []string{ 610 "-state", statePath, 611 "-var", "foo=bar", 612 planPath, 613 } 614 if code := c.Run(args); code == 0 { 615 t.Fatal("should've failed") 616 } 617 } 618 619 func TestApply_refresh(t *testing.T) { 620 originalState := &terraform.State{ 621 Modules: []*terraform.ModuleState{ 622 &terraform.ModuleState{ 623 Path: []string{"root"}, 624 Resources: map[string]*terraform.ResourceState{ 625 "test_instance.foo": &terraform.ResourceState{ 626 Type: "test_instance", 627 Primary: &terraform.InstanceState{ 628 ID: "bar", 629 }, 630 }, 631 }, 632 }, 633 }, 634 } 635 636 statePath := testStateFile(t, originalState) 637 638 p := testProvider() 639 ui := new(cli.MockUi) 640 c := &ApplyCommand{ 641 Meta: Meta{ 642 ContextOpts: testCtxConfig(p), 643 Ui: ui, 644 }, 645 } 646 647 args := []string{ 648 "-state", statePath, 649 testFixturePath("apply"), 650 } 651 if code := c.Run(args); code != 0 { 652 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 653 } 654 655 if !p.RefreshCalled { 656 t.Fatal("should call refresh") 657 } 658 659 if _, err := os.Stat(statePath); err != nil { 660 t.Fatalf("err: %s", err) 661 } 662 663 f, err := os.Open(statePath) 664 if err != nil { 665 t.Fatalf("err: %s", err) 666 } 667 defer f.Close() 668 669 state, err := terraform.ReadState(f) 670 if err != nil { 671 t.Fatalf("err: %s", err) 672 } 673 if state == nil { 674 t.Fatal("state should not be nil") 675 } 676 677 // Should have a backup file 678 f, err = os.Open(statePath + DefaultBackupExtension) 679 if err != nil { 680 t.Fatalf("err: %s", err) 681 } 682 683 backupState, err := terraform.ReadState(f) 684 f.Close() 685 if err != nil { 686 t.Fatalf("err: %s", err) 687 } 688 689 actualStr := strings.TrimSpace(backupState.String()) 690 expectedStr := strings.TrimSpace(originalState.String()) 691 if actualStr != expectedStr { 692 t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr) 693 } 694 } 695 696 func TestApply_shutdown(t *testing.T) { 697 stopped := false 698 stopCh := make(chan struct{}) 699 stopReplyCh := make(chan struct{}) 700 701 statePath := testTempFile(t) 702 703 p := testProvider() 704 shutdownCh := make(chan struct{}) 705 ui := new(cli.MockUi) 706 c := &ApplyCommand{ 707 Meta: Meta{ 708 ContextOpts: testCtxConfig(p), 709 Ui: ui, 710 }, 711 712 ShutdownCh: shutdownCh, 713 } 714 715 p.DiffFn = func( 716 *terraform.InstanceInfo, 717 *terraform.InstanceState, 718 *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 719 return &terraform.InstanceDiff{ 720 Attributes: map[string]*terraform.ResourceAttrDiff{ 721 "ami": &terraform.ResourceAttrDiff{ 722 New: "bar", 723 }, 724 }, 725 }, nil 726 } 727 p.ApplyFn = func( 728 *terraform.InstanceInfo, 729 *terraform.InstanceState, 730 *terraform.InstanceDiff) (*terraform.InstanceState, error) { 731 if !stopped { 732 stopped = true 733 close(stopCh) 734 <-stopReplyCh 735 } 736 737 return &terraform.InstanceState{ 738 ID: "foo", 739 Attributes: map[string]string{ 740 "ami": "2", 741 }, 742 }, nil 743 } 744 745 go func() { 746 <-stopCh 747 shutdownCh <- struct{}{} 748 749 // This is really dirty, but we have no other way to assure that 750 // tf.Stop() has been called. This doesn't assure it either, but 751 // it makes it much more certain. 752 time.Sleep(50 * time.Millisecond) 753 754 close(stopReplyCh) 755 }() 756 757 args := []string{ 758 "-state", statePath, 759 testFixturePath("apply-shutdown"), 760 } 761 if code := c.Run(args); code != 0 { 762 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 763 } 764 765 if _, err := os.Stat(statePath); err != nil { 766 t.Fatalf("err: %s", err) 767 } 768 769 f, err := os.Open(statePath) 770 if err != nil { 771 t.Fatalf("err: %s", err) 772 } 773 defer f.Close() 774 775 state, err := terraform.ReadState(f) 776 if err != nil { 777 t.Fatalf("err: %s", err) 778 } 779 if state == nil { 780 t.Fatal("state should not be nil") 781 } 782 783 if len(state.RootModule().Resources) != 1 { 784 t.Fatalf("bad: %d", len(state.RootModule().Resources)) 785 } 786 } 787 788 func TestApply_state(t *testing.T) { 789 originalState := &terraform.State{ 790 Modules: []*terraform.ModuleState{ 791 &terraform.ModuleState{ 792 Path: []string{"root"}, 793 Resources: map[string]*terraform.ResourceState{ 794 "test_instance.foo": &terraform.ResourceState{ 795 Type: "test_instance", 796 Primary: &terraform.InstanceState{ 797 ID: "bar", 798 }, 799 }, 800 }, 801 }, 802 }, 803 } 804 805 statePath := testStateFile(t, originalState) 806 807 p := testProvider() 808 p.DiffReturn = &terraform.InstanceDiff{ 809 Attributes: map[string]*terraform.ResourceAttrDiff{ 810 "ami": &terraform.ResourceAttrDiff{ 811 New: "bar", 812 }, 813 }, 814 } 815 816 ui := new(cli.MockUi) 817 c := &ApplyCommand{ 818 Meta: Meta{ 819 ContextOpts: testCtxConfig(p), 820 Ui: ui, 821 }, 822 } 823 824 // Run the apply command pointing to our existing state 825 args := []string{ 826 "-state", statePath, 827 testFixturePath("apply"), 828 } 829 if code := c.Run(args); code != 0 { 830 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 831 } 832 833 // Verify that the provider was called with the existing state 834 actual := strings.TrimSpace(p.DiffState.String()) 835 expected := strings.TrimSpace(testApplyStateDiffStr) 836 if actual != expected { 837 t.Fatalf("bad:\n\n%s", actual) 838 } 839 840 actual = strings.TrimSpace(p.ApplyState.String()) 841 expected = strings.TrimSpace(testApplyStateStr) 842 if actual != expected { 843 t.Fatalf("bad:\n\n%s", actual) 844 } 845 846 // Verify a new state exists 847 if _, err := os.Stat(statePath); err != nil { 848 t.Fatalf("err: %s", err) 849 } 850 851 f, err := os.Open(statePath) 852 if err != nil { 853 t.Fatalf("err: %s", err) 854 } 855 defer f.Close() 856 857 state, err := terraform.ReadState(f) 858 if err != nil { 859 t.Fatalf("err: %s", err) 860 } 861 if state == nil { 862 t.Fatal("state should not be nil") 863 } 864 865 // Should have a backup file 866 f, err = os.Open(statePath + DefaultBackupExtension) 867 if err != nil { 868 t.Fatalf("err: %s", err) 869 } 870 871 backupState, err := terraform.ReadState(f) 872 f.Close() 873 if err != nil { 874 t.Fatalf("err: %s", err) 875 } 876 877 // nil out the ConnInfo since that should not be restored 878 originalState.RootModule().Resources["test_instance.foo"].Primary.Ephemeral.ConnInfo = nil 879 880 actualStr := strings.TrimSpace(backupState.String()) 881 expectedStr := strings.TrimSpace(originalState.String()) 882 if actualStr != expectedStr { 883 t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr) 884 } 885 } 886 887 func TestApply_stateNoExist(t *testing.T) { 888 p := testProvider() 889 ui := new(cli.MockUi) 890 c := &ApplyCommand{ 891 Meta: Meta{ 892 ContextOpts: testCtxConfig(p), 893 Ui: ui, 894 }, 895 } 896 897 args := []string{ 898 "idontexist.tfstate", 899 testFixturePath("apply"), 900 } 901 if code := c.Run(args); code != 1 { 902 t.Fatalf("bad: \n%s", ui.OutputWriter.String()) 903 } 904 } 905 906 func TestApply_vars(t *testing.T) { 907 statePath := testTempFile(t) 908 909 p := testProvider() 910 ui := new(cli.MockUi) 911 c := &ApplyCommand{ 912 Meta: Meta{ 913 ContextOpts: testCtxConfig(p), 914 Ui: ui, 915 }, 916 } 917 918 actual := "" 919 p.DiffFn = func( 920 info *terraform.InstanceInfo, 921 s *terraform.InstanceState, 922 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 923 if v, ok := c.Config["value"]; ok { 924 actual = v.(string) 925 } 926 927 return &terraform.InstanceDiff{}, nil 928 } 929 930 args := []string{ 931 "-var", "foo=bar", 932 "-state", statePath, 933 testFixturePath("apply-vars"), 934 } 935 if code := c.Run(args); code != 0 { 936 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 937 } 938 939 if actual != "bar" { 940 t.Fatal("didn't work") 941 } 942 } 943 944 func TestApply_varFile(t *testing.T) { 945 varFilePath := testTempFile(t) 946 if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil { 947 t.Fatalf("err: %s", err) 948 } 949 950 statePath := testTempFile(t) 951 952 p := testProvider() 953 ui := new(cli.MockUi) 954 c := &ApplyCommand{ 955 Meta: Meta{ 956 ContextOpts: testCtxConfig(p), 957 Ui: ui, 958 }, 959 } 960 961 actual := "" 962 p.DiffFn = func( 963 info *terraform.InstanceInfo, 964 s *terraform.InstanceState, 965 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 966 if v, ok := c.Config["value"]; ok { 967 actual = v.(string) 968 } 969 970 return &terraform.InstanceDiff{}, nil 971 } 972 973 args := []string{ 974 "-var-file", varFilePath, 975 "-state", statePath, 976 testFixturePath("apply-vars"), 977 } 978 if code := c.Run(args); code != 0 { 979 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 980 } 981 982 if actual != "bar" { 983 t.Fatal("didn't work") 984 } 985 } 986 987 func TestApply_varFileDefault(t *testing.T) { 988 varFileDir := testTempDir(t) 989 varFilePath := filepath.Join(varFileDir, "terraform.tfvars") 990 if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil { 991 t.Fatalf("err: %s", err) 992 } 993 994 statePath := testTempFile(t) 995 996 cwd, err := os.Getwd() 997 if err != nil { 998 t.Fatalf("err: %s", err) 999 } 1000 if err := os.Chdir(varFileDir); err != nil { 1001 t.Fatalf("err: %s", err) 1002 } 1003 defer os.Chdir(cwd) 1004 1005 p := testProvider() 1006 ui := new(cli.MockUi) 1007 c := &ApplyCommand{ 1008 Meta: Meta{ 1009 ContextOpts: testCtxConfig(p), 1010 Ui: ui, 1011 }, 1012 } 1013 1014 actual := "" 1015 p.DiffFn = func( 1016 info *terraform.InstanceInfo, 1017 s *terraform.InstanceState, 1018 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 1019 if v, ok := c.Config["value"]; ok { 1020 actual = v.(string) 1021 } 1022 1023 return &terraform.InstanceDiff{}, nil 1024 } 1025 1026 args := []string{ 1027 "-state", statePath, 1028 testFixturePath("apply-vars"), 1029 } 1030 if code := c.Run(args); code != 0 { 1031 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1032 } 1033 1034 if actual != "bar" { 1035 t.Fatal("didn't work") 1036 } 1037 } 1038 1039 func TestApply_varFileDefaultJSON(t *testing.T) { 1040 varFileDir := testTempDir(t) 1041 varFilePath := filepath.Join(varFileDir, "terraform.tfvars.json") 1042 if err := ioutil.WriteFile(varFilePath, []byte(applyVarFileJSON), 0644); err != nil { 1043 t.Fatalf("err: %s", err) 1044 } 1045 1046 statePath := testTempFile(t) 1047 1048 cwd, err := os.Getwd() 1049 if err != nil { 1050 t.Fatalf("err: %s", err) 1051 } 1052 if err := os.Chdir(varFileDir); err != nil { 1053 t.Fatalf("err: %s", err) 1054 } 1055 defer os.Chdir(cwd) 1056 1057 p := testProvider() 1058 ui := new(cli.MockUi) 1059 c := &ApplyCommand{ 1060 Meta: Meta{ 1061 ContextOpts: testCtxConfig(p), 1062 Ui: ui, 1063 }, 1064 } 1065 1066 actual := "" 1067 p.DiffFn = func( 1068 info *terraform.InstanceInfo, 1069 s *terraform.InstanceState, 1070 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 1071 if v, ok := c.Config["value"]; ok { 1072 actual = v.(string) 1073 } 1074 1075 return &terraform.InstanceDiff{}, nil 1076 } 1077 1078 args := []string{ 1079 "-state", statePath, 1080 testFixturePath("apply-vars"), 1081 } 1082 if code := c.Run(args); code != 0 { 1083 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1084 } 1085 1086 if actual != "bar" { 1087 t.Fatal("didn't work") 1088 } 1089 } 1090 1091 func TestApply_backup(t *testing.T) { 1092 originalState := &terraform.State{ 1093 Modules: []*terraform.ModuleState{ 1094 &terraform.ModuleState{ 1095 Path: []string{"root"}, 1096 Resources: map[string]*terraform.ResourceState{ 1097 "test_instance.foo": &terraform.ResourceState{ 1098 Type: "test_instance", 1099 Primary: &terraform.InstanceState{ 1100 ID: "bar", 1101 }, 1102 }, 1103 }, 1104 }, 1105 }, 1106 } 1107 1108 statePath := testStateFile(t, originalState) 1109 backupPath := testTempFile(t) 1110 1111 p := testProvider() 1112 p.DiffReturn = &terraform.InstanceDiff{ 1113 Attributes: map[string]*terraform.ResourceAttrDiff{ 1114 "ami": &terraform.ResourceAttrDiff{ 1115 New: "bar", 1116 }, 1117 }, 1118 } 1119 1120 ui := new(cli.MockUi) 1121 c := &ApplyCommand{ 1122 Meta: Meta{ 1123 ContextOpts: testCtxConfig(p), 1124 Ui: ui, 1125 }, 1126 } 1127 1128 // Run the apply command pointing to our existing state 1129 args := []string{ 1130 "-state", statePath, 1131 "-backup", backupPath, 1132 testFixturePath("apply"), 1133 } 1134 if code := c.Run(args); code != 0 { 1135 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1136 } 1137 1138 // Verify a new state exists 1139 if _, err := os.Stat(statePath); err != nil { 1140 t.Fatalf("err: %s", err) 1141 } 1142 1143 f, err := os.Open(statePath) 1144 if err != nil { 1145 t.Fatalf("err: %s", err) 1146 } 1147 defer f.Close() 1148 1149 state, err := terraform.ReadState(f) 1150 if err != nil { 1151 t.Fatalf("err: %s", err) 1152 } 1153 if state == nil { 1154 t.Fatal("state should not be nil") 1155 } 1156 1157 // Should have a backup file 1158 f, err = os.Open(backupPath) 1159 if err != nil { 1160 t.Fatalf("err: %s", err) 1161 } 1162 1163 backupState, err := terraform.ReadState(f) 1164 f.Close() 1165 if err != nil { 1166 t.Fatalf("err: %s", err) 1167 } 1168 1169 actual := backupState.RootModule().Resources["test_instance.foo"] 1170 expected := originalState.RootModule().Resources["test_instance.foo"] 1171 if !reflect.DeepEqual(actual, expected) { 1172 t.Fatalf("bad: %#v %#v", actual, expected) 1173 } 1174 } 1175 1176 func TestApply_disableBackup(t *testing.T) { 1177 originalState := testState() 1178 statePath := testStateFile(t, originalState) 1179 1180 p := testProvider() 1181 p.DiffReturn = &terraform.InstanceDiff{ 1182 Attributes: map[string]*terraform.ResourceAttrDiff{ 1183 "ami": &terraform.ResourceAttrDiff{ 1184 New: "bar", 1185 }, 1186 }, 1187 } 1188 1189 ui := new(cli.MockUi) 1190 c := &ApplyCommand{ 1191 Meta: Meta{ 1192 ContextOpts: testCtxConfig(p), 1193 Ui: ui, 1194 }, 1195 } 1196 1197 // Run the apply command pointing to our existing state 1198 args := []string{ 1199 "-state", statePath, 1200 "-backup", "-", 1201 testFixturePath("apply"), 1202 } 1203 if code := c.Run(args); code != 0 { 1204 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1205 } 1206 1207 // Verify that the provider was called with the existing state 1208 actual := strings.TrimSpace(p.DiffState.String()) 1209 expected := strings.TrimSpace(testApplyDisableBackupStr) 1210 if actual != expected { 1211 t.Fatalf("bad:\n\n%s", actual) 1212 } 1213 1214 actual = strings.TrimSpace(p.ApplyState.String()) 1215 expected = strings.TrimSpace(testApplyDisableBackupStateStr) 1216 if actual != expected { 1217 t.Fatalf("bad:\n\n%s", actual) 1218 } 1219 1220 // Verify a new state exists 1221 if _, err := os.Stat(statePath); err != nil { 1222 t.Fatalf("err: %s", err) 1223 } 1224 1225 f, err := os.Open(statePath) 1226 if err != nil { 1227 t.Fatalf("err: %s", err) 1228 } 1229 defer f.Close() 1230 1231 state, err := terraform.ReadState(f) 1232 if err != nil { 1233 t.Fatalf("err: %s", err) 1234 } 1235 if state == nil { 1236 t.Fatal("state should not be nil") 1237 } 1238 1239 // Ensure there is no backup 1240 _, err = os.Stat(statePath + DefaultBackupExtension) 1241 if err == nil || !os.IsNotExist(err) { 1242 t.Fatalf("backup should not exist") 1243 } 1244 1245 // Ensure there is no literal "-" 1246 _, err = os.Stat("-") 1247 if err == nil || !os.IsNotExist(err) { 1248 t.Fatalf("backup should not exist") 1249 } 1250 } 1251 1252 func testHttpServer(t *testing.T) net.Listener { 1253 ln, err := net.Listen("tcp", "127.0.0.1:0") 1254 if err != nil { 1255 t.Fatalf("err: %s", err) 1256 } 1257 1258 mux := http.NewServeMux() 1259 mux.HandleFunc("/header", testHttpHandlerHeader) 1260 1261 var server http.Server 1262 server.Handler = mux 1263 go server.Serve(ln) 1264 1265 return ln 1266 } 1267 1268 func testHttpHandlerHeader(w http.ResponseWriter, r *http.Request) { 1269 var url url.URL 1270 url.Scheme = "file" 1271 url.Path = filepath.ToSlash(testFixturePath("init")) 1272 1273 w.Header().Add("X-Terraform-Get", url.String()) 1274 w.WriteHeader(200) 1275 } 1276 1277 const applyVarFile = ` 1278 foo = "bar" 1279 ` 1280 1281 const applyVarFileJSON = ` 1282 { "foo": "bar" } 1283 ` 1284 1285 const testApplyDisableBackupStr = ` 1286 ID = bar 1287 ` 1288 1289 const testApplyDisableBackupStateStr = ` 1290 ID = bar 1291 ` 1292 1293 const testApplyStateStr = ` 1294 ID = bar 1295 ` 1296 1297 const testApplyStateDiffStr = ` 1298 ID = bar 1299 `