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