github.com/ljosa/terraform@v0.7.0-rc2.0.20160617205345-fe540b408f59/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_sensitiveOutput(t *testing.T) { 890 p := testProvider() 891 ui := new(cli.MockUi) 892 c := &ApplyCommand{ 893 Meta: Meta{ 894 ContextOpts: testCtxConfig(p), 895 Ui: ui, 896 }, 897 } 898 899 statePath := testTempFile(t) 900 901 args := []string{ 902 "-state", statePath, 903 testFixturePath("apply-sensitive-output"), 904 } 905 906 if code := c.Run(args); code != 0 { 907 t.Fatalf("bad: \n%s", ui.OutputWriter.String()) 908 } 909 910 output := ui.OutputWriter.String() 911 if !strings.Contains(output, "notsensitive = Hello world") { 912 t.Fatalf("bad: output should contain 'notsensitive' output\n%s", output) 913 } 914 if !strings.Contains(output, "sensitive = <sensitive>") { 915 t.Fatalf("bad: output should contain 'sensitive' output\n%s", output) 916 } 917 } 918 919 func TestApply_stateFuture(t *testing.T) { 920 originalState := testState() 921 originalState.TFVersion = "99.99.99" 922 statePath := testStateFile(t, originalState) 923 924 p := testProvider() 925 ui := new(cli.MockUi) 926 c := &ApplyCommand{ 927 Meta: Meta{ 928 ContextOpts: testCtxConfig(p), 929 Ui: ui, 930 }, 931 } 932 933 args := []string{ 934 "-state", statePath, 935 testFixturePath("apply"), 936 } 937 if code := c.Run(args); code == 0 { 938 t.Fatal("should fail") 939 } 940 941 f, err := os.Open(statePath) 942 if err != nil { 943 t.Fatalf("err: %s", err) 944 } 945 946 newState, err := terraform.ReadState(f) 947 f.Close() 948 if err != nil { 949 t.Fatalf("err: %s", err) 950 } 951 952 if !newState.Equal(originalState) { 953 t.Fatalf("bad: %#v", newState) 954 } 955 if newState.TFVersion != originalState.TFVersion { 956 t.Fatalf("bad: %#v", newState) 957 } 958 } 959 960 func TestApply_statePast(t *testing.T) { 961 originalState := testState() 962 originalState.TFVersion = "0.1.0" 963 statePath := testStateFile(t, originalState) 964 965 p := testProvider() 966 ui := new(cli.MockUi) 967 c := &ApplyCommand{ 968 Meta: Meta{ 969 ContextOpts: testCtxConfig(p), 970 Ui: ui, 971 }, 972 } 973 974 args := []string{ 975 "-state", statePath, 976 testFixturePath("apply"), 977 } 978 if code := c.Run(args); code != 0 { 979 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 980 } 981 } 982 983 func TestApply_vars(t *testing.T) { 984 statePath := testTempFile(t) 985 986 p := testProvider() 987 ui := new(cli.MockUi) 988 c := &ApplyCommand{ 989 Meta: Meta{ 990 ContextOpts: testCtxConfig(p), 991 Ui: ui, 992 }, 993 } 994 995 actual := "" 996 p.DiffFn = func( 997 info *terraform.InstanceInfo, 998 s *terraform.InstanceState, 999 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 1000 if v, ok := c.Config["value"]; ok { 1001 actual = v.(string) 1002 } 1003 1004 return &terraform.InstanceDiff{}, nil 1005 } 1006 1007 args := []string{ 1008 "-var", "foo=bar", 1009 "-state", statePath, 1010 testFixturePath("apply-vars"), 1011 } 1012 if code := c.Run(args); code != 0 { 1013 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1014 } 1015 1016 if actual != "bar" { 1017 t.Fatal("didn't work") 1018 } 1019 } 1020 1021 func TestApply_varFile(t *testing.T) { 1022 varFilePath := testTempFile(t) 1023 if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil { 1024 t.Fatalf("err: %s", err) 1025 } 1026 1027 statePath := testTempFile(t) 1028 1029 p := testProvider() 1030 ui := new(cli.MockUi) 1031 c := &ApplyCommand{ 1032 Meta: Meta{ 1033 ContextOpts: testCtxConfig(p), 1034 Ui: ui, 1035 }, 1036 } 1037 1038 actual := "" 1039 p.DiffFn = func( 1040 info *terraform.InstanceInfo, 1041 s *terraform.InstanceState, 1042 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 1043 if v, ok := c.Config["value"]; ok { 1044 actual = v.(string) 1045 } 1046 1047 return &terraform.InstanceDiff{}, nil 1048 } 1049 1050 args := []string{ 1051 "-var-file", varFilePath, 1052 "-state", statePath, 1053 testFixturePath("apply-vars"), 1054 } 1055 if code := c.Run(args); code != 0 { 1056 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1057 } 1058 1059 if actual != "bar" { 1060 t.Fatal("didn't work") 1061 } 1062 } 1063 1064 func TestApply_varFileDefault(t *testing.T) { 1065 varFileDir := testTempDir(t) 1066 varFilePath := filepath.Join(varFileDir, "terraform.tfvars") 1067 if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil { 1068 t.Fatalf("err: %s", err) 1069 } 1070 1071 statePath := testTempFile(t) 1072 1073 cwd, err := os.Getwd() 1074 if err != nil { 1075 t.Fatalf("err: %s", err) 1076 } 1077 if err := os.Chdir(varFileDir); err != nil { 1078 t.Fatalf("err: %s", err) 1079 } 1080 defer os.Chdir(cwd) 1081 1082 p := testProvider() 1083 ui := new(cli.MockUi) 1084 c := &ApplyCommand{ 1085 Meta: Meta{ 1086 ContextOpts: testCtxConfig(p), 1087 Ui: ui, 1088 }, 1089 } 1090 1091 actual := "" 1092 p.DiffFn = func( 1093 info *terraform.InstanceInfo, 1094 s *terraform.InstanceState, 1095 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 1096 if v, ok := c.Config["value"]; ok { 1097 actual = v.(string) 1098 } 1099 1100 return &terraform.InstanceDiff{}, nil 1101 } 1102 1103 args := []string{ 1104 "-state", statePath, 1105 testFixturePath("apply-vars"), 1106 } 1107 if code := c.Run(args); code != 0 { 1108 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1109 } 1110 1111 if actual != "bar" { 1112 t.Fatal("didn't work") 1113 } 1114 } 1115 1116 func TestApply_varFileDefaultJSON(t *testing.T) { 1117 varFileDir := testTempDir(t) 1118 varFilePath := filepath.Join(varFileDir, "terraform.tfvars.json") 1119 if err := ioutil.WriteFile(varFilePath, []byte(applyVarFileJSON), 0644); err != nil { 1120 t.Fatalf("err: %s", err) 1121 } 1122 1123 statePath := testTempFile(t) 1124 1125 cwd, err := os.Getwd() 1126 if err != nil { 1127 t.Fatalf("err: %s", err) 1128 } 1129 if err := os.Chdir(varFileDir); err != nil { 1130 t.Fatalf("err: %s", err) 1131 } 1132 defer os.Chdir(cwd) 1133 1134 p := testProvider() 1135 ui := new(cli.MockUi) 1136 c := &ApplyCommand{ 1137 Meta: Meta{ 1138 ContextOpts: testCtxConfig(p), 1139 Ui: ui, 1140 }, 1141 } 1142 1143 actual := "" 1144 p.DiffFn = func( 1145 info *terraform.InstanceInfo, 1146 s *terraform.InstanceState, 1147 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 1148 if v, ok := c.Config["value"]; ok { 1149 actual = v.(string) 1150 } 1151 1152 return &terraform.InstanceDiff{}, nil 1153 } 1154 1155 args := []string{ 1156 "-state", statePath, 1157 testFixturePath("apply-vars"), 1158 } 1159 if code := c.Run(args); code != 0 { 1160 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1161 } 1162 1163 if actual != "bar" { 1164 t.Fatal("didn't work") 1165 } 1166 } 1167 1168 func TestApply_backup(t *testing.T) { 1169 originalState := &terraform.State{ 1170 Modules: []*terraform.ModuleState{ 1171 &terraform.ModuleState{ 1172 Path: []string{"root"}, 1173 Resources: map[string]*terraform.ResourceState{ 1174 "test_instance.foo": &terraform.ResourceState{ 1175 Type: "test_instance", 1176 Primary: &terraform.InstanceState{ 1177 ID: "bar", 1178 }, 1179 }, 1180 }, 1181 }, 1182 }, 1183 } 1184 1185 statePath := testStateFile(t, originalState) 1186 backupPath := testTempFile(t) 1187 1188 p := testProvider() 1189 p.DiffReturn = &terraform.InstanceDiff{ 1190 Attributes: map[string]*terraform.ResourceAttrDiff{ 1191 "ami": &terraform.ResourceAttrDiff{ 1192 New: "bar", 1193 }, 1194 }, 1195 } 1196 1197 ui := new(cli.MockUi) 1198 c := &ApplyCommand{ 1199 Meta: Meta{ 1200 ContextOpts: testCtxConfig(p), 1201 Ui: ui, 1202 }, 1203 } 1204 1205 // Run the apply command pointing to our existing state 1206 args := []string{ 1207 "-state", statePath, 1208 "-backup", backupPath, 1209 testFixturePath("apply"), 1210 } 1211 if code := c.Run(args); code != 0 { 1212 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1213 } 1214 1215 // Verify a new state exists 1216 if _, err := os.Stat(statePath); err != nil { 1217 t.Fatalf("err: %s", err) 1218 } 1219 1220 f, err := os.Open(statePath) 1221 if err != nil { 1222 t.Fatalf("err: %s", err) 1223 } 1224 defer f.Close() 1225 1226 state, err := terraform.ReadState(f) 1227 if err != nil { 1228 t.Fatalf("err: %s", err) 1229 } 1230 if state == nil { 1231 t.Fatal("state should not be nil") 1232 } 1233 1234 // Should have a backup file 1235 f, err = os.Open(backupPath) 1236 if err != nil { 1237 t.Fatalf("err: %s", err) 1238 } 1239 1240 backupState, err := terraform.ReadState(f) 1241 f.Close() 1242 if err != nil { 1243 t.Fatalf("err: %s", err) 1244 } 1245 1246 actual := backupState.RootModule().Resources["test_instance.foo"] 1247 expected := originalState.RootModule().Resources["test_instance.foo"] 1248 if !reflect.DeepEqual(actual, expected) { 1249 t.Fatalf("bad: %#v %#v", actual, expected) 1250 } 1251 } 1252 1253 func TestApply_disableBackup(t *testing.T) { 1254 originalState := testState() 1255 statePath := testStateFile(t, originalState) 1256 1257 p := testProvider() 1258 p.DiffReturn = &terraform.InstanceDiff{ 1259 Attributes: map[string]*terraform.ResourceAttrDiff{ 1260 "ami": &terraform.ResourceAttrDiff{ 1261 New: "bar", 1262 }, 1263 }, 1264 } 1265 1266 ui := new(cli.MockUi) 1267 c := &ApplyCommand{ 1268 Meta: Meta{ 1269 ContextOpts: testCtxConfig(p), 1270 Ui: ui, 1271 }, 1272 } 1273 1274 // Run the apply command pointing to our existing state 1275 args := []string{ 1276 "-state", statePath, 1277 "-backup", "-", 1278 testFixturePath("apply"), 1279 } 1280 if code := c.Run(args); code != 0 { 1281 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1282 } 1283 1284 // Verify that the provider was called with the existing state 1285 actual := strings.TrimSpace(p.DiffState.String()) 1286 expected := strings.TrimSpace(testApplyDisableBackupStr) 1287 if actual != expected { 1288 t.Fatalf("bad:\n\n%s", actual) 1289 } 1290 1291 actual = strings.TrimSpace(p.ApplyState.String()) 1292 expected = strings.TrimSpace(testApplyDisableBackupStateStr) 1293 if actual != expected { 1294 t.Fatalf("bad:\n\n%s", actual) 1295 } 1296 1297 // Verify a new state exists 1298 if _, err := os.Stat(statePath); err != nil { 1299 t.Fatalf("err: %s", err) 1300 } 1301 1302 f, err := os.Open(statePath) 1303 if err != nil { 1304 t.Fatalf("err: %s", err) 1305 } 1306 defer f.Close() 1307 1308 state, err := terraform.ReadState(f) 1309 if err != nil { 1310 t.Fatalf("err: %s", err) 1311 } 1312 if state == nil { 1313 t.Fatal("state should not be nil") 1314 } 1315 1316 // Ensure there is no backup 1317 _, err = os.Stat(statePath + DefaultBackupExtension) 1318 if err == nil || !os.IsNotExist(err) { 1319 t.Fatalf("backup should not exist") 1320 } 1321 1322 // Ensure there is no literal "-" 1323 _, err = os.Stat("-") 1324 if err == nil || !os.IsNotExist(err) { 1325 t.Fatalf("backup should not exist") 1326 } 1327 } 1328 1329 func testHttpServer(t *testing.T) net.Listener { 1330 ln, err := net.Listen("tcp", "127.0.0.1:0") 1331 if err != nil { 1332 t.Fatalf("err: %s", err) 1333 } 1334 1335 mux := http.NewServeMux() 1336 mux.HandleFunc("/header", testHttpHandlerHeader) 1337 1338 var server http.Server 1339 server.Handler = mux 1340 go server.Serve(ln) 1341 1342 return ln 1343 } 1344 1345 func testHttpHandlerHeader(w http.ResponseWriter, r *http.Request) { 1346 var url url.URL 1347 url.Scheme = "file" 1348 url.Path = filepath.ToSlash(testFixturePath("init")) 1349 1350 w.Header().Add("X-Terraform-Get", url.String()) 1351 w.WriteHeader(200) 1352 } 1353 1354 const applyVarFile = ` 1355 foo = "bar" 1356 ` 1357 1358 const applyVarFileJSON = ` 1359 { "foo": "bar" } 1360 ` 1361 1362 const testApplyDisableBackupStr = ` 1363 ID = bar 1364 Tainted = false 1365 ` 1366 1367 const testApplyDisableBackupStateStr = ` 1368 ID = bar 1369 Tainted = false 1370 ` 1371 1372 const testApplyStateStr = ` 1373 ID = bar 1374 Tainted = false 1375 ` 1376 1377 const testApplyStateDiffStr = ` 1378 ID = bar 1379 Tainted = false 1380 `