github.com/greysond/terraform@v0.8.5-0.20170124173113-439b5507bbe9/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 // high water mark counter 62 type hwm struct { 63 sync.Mutex 64 val int 65 max int 66 } 67 68 func (t *hwm) Inc() { 69 t.Lock() 70 defer t.Unlock() 71 t.val++ 72 if t.val > t.max { 73 t.max = t.val 74 } 75 } 76 77 func (t *hwm) Dec() { 78 t.Lock() 79 defer t.Unlock() 80 t.val-- 81 } 82 83 func (t *hwm) Max() int { 84 t.Lock() 85 defer t.Unlock() 86 return t.max 87 } 88 89 func TestApply_parallelism(t *testing.T) { 90 provider := testProvider() 91 statePath := testTempFile(t) 92 93 par := 4 94 95 // This blocks all the appy functions. We close it when we exit so 96 // they end quickly after this test finishes. 97 block := make(chan struct{}) 98 // signal how many goroutines have started 99 started := make(chan int, 100) 100 101 runCount := &hwm{} 102 103 provider.ApplyFn = func( 104 i *terraform.InstanceInfo, 105 s *terraform.InstanceState, 106 d *terraform.InstanceDiff) (*terraform.InstanceState, error) { 107 // Increment so we're counting parallelism 108 started <- 1 109 runCount.Inc() 110 defer runCount.Dec() 111 // Block here to stage up our max number of parallel instances 112 <-block 113 114 return nil, nil 115 } 116 117 ui := new(cli.MockUi) 118 c := &ApplyCommand{ 119 Meta: Meta{ 120 ContextOpts: testCtxConfig(provider), 121 Ui: ui, 122 }, 123 } 124 125 args := []string{ 126 "-state", statePath, 127 fmt.Sprintf("-parallelism=%d", par), 128 testFixturePath("parallelism"), 129 } 130 131 // Run in a goroutine. We can get any errors from the ui.OutputWriter 132 doneCh := make(chan int, 1) 133 go func() { 134 doneCh <- c.Run(args) 135 }() 136 137 timeout := time.After(5 * time.Second) 138 139 // ensure things are running 140 for i := 0; i < par; i++ { 141 select { 142 case <-timeout: 143 t.Fatal("timeout waiting for all goroutines to start") 144 case <-started: 145 } 146 } 147 148 // a little extra sleep, since we can't ensure all goroutines from the walk have 149 // really started 150 time.Sleep(100 * time.Millisecond) 151 close(block) 152 153 select { 154 case res := <-doneCh: 155 if res != 0 { 156 t.Fatal(ui.OutputWriter.String()) 157 } 158 case <-timeout: 159 t.Fatal("timeout waiting from Run()") 160 } 161 162 // The total in flight should equal the parallelism 163 if runCount.Max() != par { 164 t.Fatalf("Expected parallelism: %d, got: %d", par, runCount.Max()) 165 } 166 } 167 168 func TestApply_configInvalid(t *testing.T) { 169 p := testProvider() 170 ui := new(cli.MockUi) 171 c := &ApplyCommand{ 172 Meta: Meta{ 173 ContextOpts: testCtxConfig(p), 174 Ui: ui, 175 }, 176 } 177 178 args := []string{ 179 "-state", testTempFile(t), 180 testFixturePath("apply-config-invalid"), 181 } 182 if code := c.Run(args); code != 1 { 183 t.Fatalf("bad: \n%s", ui.OutputWriter.String()) 184 } 185 } 186 187 func TestApply_defaultState(t *testing.T) { 188 td, err := ioutil.TempDir("", "tf") 189 if err != nil { 190 t.Fatalf("err: %s", err) 191 } 192 statePath := filepath.Join(td, DefaultStateFilename) 193 194 // Change to the temporary directory 195 cwd, err := os.Getwd() 196 if err != nil { 197 t.Fatalf("err: %s", err) 198 } 199 if err := os.Chdir(filepath.Dir(statePath)); err != nil { 200 t.Fatalf("err: %s", err) 201 } 202 defer os.Chdir(cwd) 203 204 p := testProvider() 205 ui := new(cli.MockUi) 206 c := &ApplyCommand{ 207 Meta: Meta{ 208 ContextOpts: testCtxConfig(p), 209 Ui: ui, 210 }, 211 } 212 213 args := []string{ 214 testFixturePath("apply"), 215 } 216 if code := c.Run(args); code != 0 { 217 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 218 } 219 220 if _, err := os.Stat(statePath); err != nil { 221 t.Fatalf("err: %s", err) 222 } 223 224 f, err := os.Open(statePath) 225 if err != nil { 226 t.Fatalf("err: %s", err) 227 } 228 defer f.Close() 229 230 state, err := terraform.ReadState(f) 231 if err != nil { 232 t.Fatalf("err: %s", err) 233 } 234 if state == nil { 235 t.Fatal("state should not be nil") 236 } 237 } 238 239 func TestApply_error(t *testing.T) { 240 statePath := testTempFile(t) 241 242 p := testProvider() 243 ui := new(cli.MockUi) 244 c := &ApplyCommand{ 245 Meta: Meta{ 246 ContextOpts: testCtxConfig(p), 247 Ui: ui, 248 }, 249 } 250 251 var lock sync.Mutex 252 errored := false 253 p.ApplyFn = func( 254 info *terraform.InstanceInfo, 255 s *terraform.InstanceState, 256 d *terraform.InstanceDiff) (*terraform.InstanceState, error) { 257 lock.Lock() 258 defer lock.Unlock() 259 260 if !errored { 261 errored = true 262 return nil, fmt.Errorf("error") 263 } 264 265 return &terraform.InstanceState{ID: "foo"}, nil 266 } 267 p.DiffFn = func( 268 *terraform.InstanceInfo, 269 *terraform.InstanceState, 270 *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 271 return &terraform.InstanceDiff{ 272 Attributes: map[string]*terraform.ResourceAttrDiff{ 273 "ami": &terraform.ResourceAttrDiff{ 274 New: "bar", 275 }, 276 }, 277 }, nil 278 } 279 280 args := []string{ 281 "-state", statePath, 282 testFixturePath("apply-error"), 283 } 284 if code := c.Run(args); code != 1 { 285 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 286 } 287 288 if _, err := os.Stat(statePath); err != nil { 289 t.Fatalf("err: %s", err) 290 } 291 292 f, err := os.Open(statePath) 293 if err != nil { 294 t.Fatalf("err: %s", err) 295 } 296 defer f.Close() 297 298 state, err := terraform.ReadState(f) 299 if err != nil { 300 t.Fatalf("err: %s", err) 301 } 302 if state == nil { 303 t.Fatal("state should not be nil") 304 } 305 if len(state.RootModule().Resources) == 0 { 306 t.Fatal("no resources in state") 307 } 308 } 309 310 func TestApply_init(t *testing.T) { 311 // Change to the temporary directory 312 cwd, err := os.Getwd() 313 if err != nil { 314 t.Fatalf("err: %s", err) 315 } 316 dir := tempDir(t) 317 if err := os.MkdirAll(dir, 0755); err != nil { 318 t.Fatalf("err: %s", err) 319 } 320 if err := os.Chdir(dir); err != nil { 321 t.Fatalf("err: %s", err) 322 } 323 defer os.Chdir(cwd) 324 325 // Create the test fixtures 326 statePath := testTempFile(t) 327 ln := testHttpServer(t) 328 defer ln.Close() 329 330 // Initialize the command 331 p := testProvider() 332 ui := new(cli.MockUi) 333 c := &ApplyCommand{ 334 Meta: Meta{ 335 ContextOpts: testCtxConfig(p), 336 Ui: ui, 337 }, 338 } 339 340 // Build the URL to the init 341 var u url.URL 342 u.Scheme = "http" 343 u.Host = ln.Addr().String() 344 u.Path = "/header" 345 346 args := []string{ 347 "-state", statePath, 348 u.String(), 349 } 350 if code := c.Run(args); code != 0 { 351 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 352 } 353 354 if _, err := os.Stat("hello.tf"); err != nil { 355 t.Fatalf("err: %s", err) 356 } 357 358 if _, err := os.Stat(statePath); err != nil { 359 t.Fatalf("err: %s", err) 360 } 361 362 f, err := os.Open(statePath) 363 if err != nil { 364 t.Fatalf("err: %s", err) 365 } 366 defer f.Close() 367 368 state, err := terraform.ReadState(f) 369 if err != nil { 370 t.Fatalf("err: %s", err) 371 } 372 if state == nil { 373 t.Fatal("state should not be nil") 374 } 375 } 376 377 func TestApply_input(t *testing.T) { 378 // Disable test mode so input would be asked 379 test = false 380 defer func() { test = true }() 381 382 // Set some default reader/writers for the inputs 383 defaultInputReader = bytes.NewBufferString("foo\n") 384 defaultInputWriter = new(bytes.Buffer) 385 386 statePath := testTempFile(t) 387 388 p := testProvider() 389 ui := new(cli.MockUi) 390 c := &ApplyCommand{ 391 Meta: Meta{ 392 ContextOpts: testCtxConfig(p), 393 Ui: ui, 394 }, 395 } 396 397 args := []string{ 398 "-state", statePath, 399 testFixturePath("apply-input"), 400 } 401 if code := c.Run(args); code != 0 { 402 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 403 } 404 405 if !p.InputCalled { 406 t.Fatal("input should be called") 407 } 408 } 409 410 // When only a partial set of the variables are set, Terraform 411 // should still ask for the unset ones by default (with -input=true) 412 func TestApply_inputPartial(t *testing.T) { 413 // Disable test mode so input would be asked 414 test = false 415 defer func() { test = true }() 416 417 // Set some default reader/writers for the inputs 418 defaultInputReader = bytes.NewBufferString("one\ntwo\n") 419 defaultInputWriter = new(bytes.Buffer) 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 "-var", "foo=foovalue", 435 testFixturePath("apply-input-partial"), 436 } 437 if code := c.Run(args); code != 0 { 438 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 439 } 440 441 expected := strings.TrimSpace(` 442 <no state> 443 Outputs: 444 445 bar = one 446 foo = foovalue 447 `) 448 testStateOutput(t, statePath, expected) 449 } 450 451 func TestApply_noArgs(t *testing.T) { 452 cwd, err := os.Getwd() 453 if err != nil { 454 t.Fatalf("err: %s", err) 455 } 456 if err := os.Chdir(testFixturePath("plan")); err != nil { 457 t.Fatalf("err: %s", err) 458 } 459 defer os.Chdir(cwd) 460 461 statePath := testTempFile(t) 462 463 p := testProvider() 464 ui := new(cli.MockUi) 465 c := &ApplyCommand{ 466 Meta: Meta{ 467 ContextOpts: testCtxConfig(p), 468 Ui: ui, 469 }, 470 } 471 472 args := []string{ 473 "-state", statePath, 474 } 475 if code := c.Run(args); code != 0 { 476 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 477 } 478 479 if _, err := os.Stat(statePath); err != nil { 480 t.Fatalf("err: %s", err) 481 } 482 483 f, err := os.Open(statePath) 484 if err != nil { 485 t.Fatalf("err: %s", err) 486 } 487 defer f.Close() 488 489 state, err := terraform.ReadState(f) 490 if err != nil { 491 t.Fatalf("err: %s", err) 492 } 493 if state == nil { 494 t.Fatal("state should not be nil") 495 } 496 } 497 498 func TestApply_plan(t *testing.T) { 499 // Disable test mode so input would be asked 500 test = false 501 defer func() { test = true }() 502 503 // Set some default reader/writers for the inputs 504 defaultInputReader = new(bytes.Buffer) 505 defaultInputWriter = new(bytes.Buffer) 506 507 planPath := testPlanFile(t, &terraform.Plan{ 508 Module: testModule(t, "apply"), 509 }) 510 statePath := testTempFile(t) 511 512 p := testProvider() 513 ui := new(cli.MockUi) 514 c := &ApplyCommand{ 515 Meta: Meta{ 516 ContextOpts: testCtxConfig(p), 517 Ui: ui, 518 }, 519 } 520 521 args := []string{ 522 "-state", statePath, 523 planPath, 524 } 525 if code := c.Run(args); code != 0 { 526 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 527 } 528 529 if p.InputCalled { 530 t.Fatalf("input should not be called for plans") 531 } 532 533 if _, err := os.Stat(statePath); err != nil { 534 t.Fatalf("err: %s", err) 535 } 536 537 f, err := os.Open(statePath) 538 if err != nil { 539 t.Fatalf("err: %s", err) 540 } 541 defer f.Close() 542 543 state, err := terraform.ReadState(f) 544 if err != nil { 545 t.Fatalf("err: %s", err) 546 } 547 if state == nil { 548 t.Fatal("state should not be nil") 549 } 550 } 551 552 func TestApply_plan_backup(t *testing.T) { 553 planPath := testPlanFile(t, testPlan(t)) 554 statePath := testTempFile(t) 555 backupPath := testTempFile(t) 556 557 p := testProvider() 558 ui := new(cli.MockUi) 559 c := &ApplyCommand{ 560 Meta: Meta{ 561 ContextOpts: testCtxConfig(p), 562 Ui: ui, 563 }, 564 } 565 566 args := []string{ 567 "-state", statePath, 568 "-backup", backupPath, 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 { 576 // Should have a backup file 577 f, err := os.Open(backupPath) 578 if err != nil { 579 t.Fatalf("err: %s", err) 580 } 581 582 _, err = terraform.ReadState(f) 583 f.Close() 584 if err != nil { 585 t.Fatalf("err: %s", err) 586 } 587 } 588 } 589 590 func TestApply_plan_noBackup(t *testing.T) { 591 planPath := testPlanFile(t, testPlan(t)) 592 statePath := testTempFile(t) 593 594 p := testProvider() 595 ui := new(cli.MockUi) 596 c := &ApplyCommand{ 597 Meta: Meta{ 598 ContextOpts: testCtxConfig(p), 599 Ui: ui, 600 }, 601 } 602 603 args := []string{ 604 "-state", statePath, 605 "-backup", "-", 606 planPath, 607 } 608 if code := c.Run(args); code != 0 { 609 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 610 } 611 612 // Ensure there is no backup 613 _, err := os.Stat(statePath + DefaultBackupExtension) 614 if err == nil || !os.IsNotExist(err) { 615 t.Fatalf("backup should not exist") 616 } 617 618 // Ensure there is no literal "-" 619 _, err = os.Stat("-") 620 if err == nil || !os.IsNotExist(err) { 621 t.Fatalf("backup should not exist") 622 } 623 } 624 625 func TestApply_plan_remoteState(t *testing.T) { 626 // Disable test mode so input would be asked 627 test = false 628 defer func() { test = true }() 629 tmp, cwd := testCwd(t) 630 defer testFixCwd(t, tmp, cwd) 631 remoteStatePath := filepath.Join(tmp, DefaultDataDir, DefaultStateFilename) 632 if err := os.MkdirAll(filepath.Dir(remoteStatePath), 0755); err != nil { 633 t.Fatalf("err: %s", err) 634 } 635 636 // Set some default reader/writers for the inputs 637 defaultInputReader = new(bytes.Buffer) 638 defaultInputWriter = new(bytes.Buffer) 639 640 // Create a remote state 641 state := testState() 642 conf, srv := testRemoteState(t, state, 200) 643 defer srv.Close() 644 state.Remote = conf 645 646 planPath := testPlanFile(t, &terraform.Plan{ 647 Module: testModule(t, "apply"), 648 State: state, 649 }) 650 651 p := testProvider() 652 ui := new(cli.MockUi) 653 c := &ApplyCommand{ 654 Meta: Meta{ 655 ContextOpts: testCtxConfig(p), 656 Ui: ui, 657 }, 658 } 659 660 args := []string{ 661 planPath, 662 } 663 if code := c.Run(args); code != 0 { 664 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 665 } 666 667 if p.InputCalled { 668 t.Fatalf("input should not be called for plans") 669 } 670 671 // State file should be not be installed 672 if _, err := os.Stat(filepath.Join(tmp, DefaultStateFilename)); err == nil { 673 t.Fatalf("State path should not exist") 674 } 675 676 // Check for remote state 677 if _, err := os.Stat(remoteStatePath); err != nil { 678 t.Fatalf("missing remote state: %s", err) 679 } 680 } 681 682 func TestApply_planWithVarFile(t *testing.T) { 683 varFileDir := testTempDir(t) 684 varFilePath := filepath.Join(varFileDir, "terraform.tfvars") 685 if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil { 686 t.Fatalf("err: %s", err) 687 } 688 689 planPath := testPlanFile(t, &terraform.Plan{ 690 Module: testModule(t, "apply"), 691 }) 692 statePath := testTempFile(t) 693 694 cwd, err := os.Getwd() 695 if err != nil { 696 t.Fatalf("err: %s", err) 697 } 698 if err := os.Chdir(varFileDir); err != nil { 699 t.Fatalf("err: %s", err) 700 } 701 defer os.Chdir(cwd) 702 703 p := testProvider() 704 ui := new(cli.MockUi) 705 c := &ApplyCommand{ 706 Meta: Meta{ 707 ContextOpts: testCtxConfig(p), 708 Ui: ui, 709 }, 710 } 711 712 args := []string{ 713 "-state", statePath, 714 planPath, 715 } 716 if code := c.Run(args); code != 0 { 717 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 718 } 719 720 if _, err := os.Stat(statePath); err != nil { 721 t.Fatalf("err: %s", err) 722 } 723 724 f, err := os.Open(statePath) 725 if err != nil { 726 t.Fatalf("err: %s", err) 727 } 728 defer f.Close() 729 730 state, err := terraform.ReadState(f) 731 if err != nil { 732 t.Fatalf("err: %s", err) 733 } 734 if state == nil { 735 t.Fatal("state should not be nil") 736 } 737 } 738 739 func TestApply_planVars(t *testing.T) { 740 planPath := testPlanFile(t, &terraform.Plan{ 741 Module: testModule(t, "apply"), 742 }) 743 statePath := testTempFile(t) 744 745 p := testProvider() 746 ui := new(cli.MockUi) 747 c := &ApplyCommand{ 748 Meta: Meta{ 749 ContextOpts: testCtxConfig(p), 750 Ui: ui, 751 }, 752 } 753 754 args := []string{ 755 "-state", statePath, 756 "-var", "foo=bar", 757 planPath, 758 } 759 if code := c.Run(args); code == 0 { 760 t.Fatal("should've failed") 761 } 762 } 763 764 func TestApply_refresh(t *testing.T) { 765 originalState := &terraform.State{ 766 Modules: []*terraform.ModuleState{ 767 &terraform.ModuleState{ 768 Path: []string{"root"}, 769 Resources: map[string]*terraform.ResourceState{ 770 "test_instance.foo": &terraform.ResourceState{ 771 Type: "test_instance", 772 Primary: &terraform.InstanceState{ 773 ID: "bar", 774 }, 775 }, 776 }, 777 }, 778 }, 779 } 780 781 statePath := testStateFile(t, originalState) 782 783 p := testProvider() 784 ui := new(cli.MockUi) 785 c := &ApplyCommand{ 786 Meta: Meta{ 787 ContextOpts: testCtxConfig(p), 788 Ui: ui, 789 }, 790 } 791 792 args := []string{ 793 "-state", statePath, 794 testFixturePath("apply"), 795 } 796 if code := c.Run(args); code != 0 { 797 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 798 } 799 800 if !p.RefreshCalled { 801 t.Fatal("should call refresh") 802 } 803 804 if _, err := os.Stat(statePath); err != nil { 805 t.Fatalf("err: %s", err) 806 } 807 808 f, err := os.Open(statePath) 809 if err != nil { 810 t.Fatalf("err: %s", err) 811 } 812 defer f.Close() 813 814 state, err := terraform.ReadState(f) 815 if err != nil { 816 t.Fatalf("err: %s", err) 817 } 818 if state == nil { 819 t.Fatal("state should not be nil") 820 } 821 822 // Should have a backup file 823 f, err = os.Open(statePath + DefaultBackupExtension) 824 if err != nil { 825 t.Fatalf("err: %s", err) 826 } 827 828 backupState, err := terraform.ReadState(f) 829 f.Close() 830 if err != nil { 831 t.Fatalf("err: %s", err) 832 } 833 834 actualStr := strings.TrimSpace(backupState.String()) 835 expectedStr := strings.TrimSpace(originalState.String()) 836 if actualStr != expectedStr { 837 t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr) 838 } 839 } 840 841 func TestApply_shutdown(t *testing.T) { 842 stopped := false 843 stopCh := make(chan struct{}) 844 stopReplyCh := make(chan struct{}) 845 846 statePath := testTempFile(t) 847 848 p := testProvider() 849 shutdownCh := make(chan struct{}) 850 ui := new(cli.MockUi) 851 c := &ApplyCommand{ 852 Meta: Meta{ 853 ContextOpts: testCtxConfig(p), 854 Ui: ui, 855 }, 856 857 ShutdownCh: shutdownCh, 858 } 859 860 p.DiffFn = func( 861 *terraform.InstanceInfo, 862 *terraform.InstanceState, 863 *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 864 return &terraform.InstanceDiff{ 865 Attributes: map[string]*terraform.ResourceAttrDiff{ 866 "ami": &terraform.ResourceAttrDiff{ 867 New: "bar", 868 }, 869 }, 870 }, nil 871 } 872 p.ApplyFn = func( 873 *terraform.InstanceInfo, 874 *terraform.InstanceState, 875 *terraform.InstanceDiff) (*terraform.InstanceState, error) { 876 if !stopped { 877 stopped = true 878 close(stopCh) 879 <-stopReplyCh 880 } 881 882 return &terraform.InstanceState{ 883 ID: "foo", 884 Attributes: map[string]string{ 885 "ami": "2", 886 }, 887 }, nil 888 } 889 890 go func() { 891 <-stopCh 892 shutdownCh <- struct{}{} 893 894 // This is really dirty, but we have no other way to assure that 895 // tf.Stop() has been called. This doesn't assure it either, but 896 // it makes it much more certain. 897 time.Sleep(50 * time.Millisecond) 898 899 close(stopReplyCh) 900 }() 901 902 args := []string{ 903 "-state", statePath, 904 testFixturePath("apply-shutdown"), 905 } 906 if code := c.Run(args); code != 0 { 907 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 908 } 909 910 if _, err := os.Stat(statePath); err != nil { 911 t.Fatalf("err: %s", err) 912 } 913 914 f, err := os.Open(statePath) 915 if err != nil { 916 t.Fatalf("err: %s", err) 917 } 918 defer f.Close() 919 920 state, err := terraform.ReadState(f) 921 if err != nil { 922 t.Fatalf("err: %s", err) 923 } 924 if state == nil { 925 t.Fatal("state should not be nil") 926 } 927 928 if len(state.RootModule().Resources) != 1 { 929 t.Fatalf("bad: %d", len(state.RootModule().Resources)) 930 } 931 } 932 933 func TestApply_state(t *testing.T) { 934 originalState := &terraform.State{ 935 Modules: []*terraform.ModuleState{ 936 &terraform.ModuleState{ 937 Path: []string{"root"}, 938 Resources: map[string]*terraform.ResourceState{ 939 "test_instance.foo": &terraform.ResourceState{ 940 Type: "test_instance", 941 Primary: &terraform.InstanceState{ 942 ID: "bar", 943 }, 944 }, 945 }, 946 }, 947 }, 948 } 949 950 statePath := testStateFile(t, originalState) 951 952 p := testProvider() 953 p.DiffReturn = &terraform.InstanceDiff{ 954 Attributes: map[string]*terraform.ResourceAttrDiff{ 955 "ami": &terraform.ResourceAttrDiff{ 956 New: "bar", 957 }, 958 }, 959 } 960 961 ui := new(cli.MockUi) 962 c := &ApplyCommand{ 963 Meta: Meta{ 964 ContextOpts: testCtxConfig(p), 965 Ui: ui, 966 }, 967 } 968 969 // Run the apply command pointing to our existing state 970 args := []string{ 971 "-state", statePath, 972 testFixturePath("apply"), 973 } 974 if code := c.Run(args); code != 0 { 975 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 976 } 977 978 // Verify that the provider was called with the existing state 979 actual := strings.TrimSpace(p.DiffState.String()) 980 expected := strings.TrimSpace(testApplyStateDiffStr) 981 if actual != expected { 982 t.Fatalf("bad:\n\n%s", actual) 983 } 984 985 actual = strings.TrimSpace(p.ApplyState.String()) 986 expected = strings.TrimSpace(testApplyStateStr) 987 if actual != expected { 988 t.Fatalf("bad:\n\n%s", actual) 989 } 990 991 // Verify a new state exists 992 if _, err := os.Stat(statePath); err != nil { 993 t.Fatalf("err: %s", err) 994 } 995 996 f, err := os.Open(statePath) 997 if err != nil { 998 t.Fatalf("err: %s", err) 999 } 1000 defer f.Close() 1001 1002 state, err := terraform.ReadState(f) 1003 if err != nil { 1004 t.Fatalf("err: %s", err) 1005 } 1006 if state == nil { 1007 t.Fatal("state should not be nil") 1008 } 1009 1010 // Should have a backup file 1011 f, err = os.Open(statePath + DefaultBackupExtension) 1012 if err != nil { 1013 t.Fatalf("err: %s", err) 1014 } 1015 1016 backupState, err := terraform.ReadState(f) 1017 f.Close() 1018 if err != nil { 1019 t.Fatalf("err: %s", err) 1020 } 1021 1022 // nil out the ConnInfo since that should not be restored 1023 originalState.RootModule().Resources["test_instance.foo"].Primary.Ephemeral.ConnInfo = nil 1024 1025 actualStr := strings.TrimSpace(backupState.String()) 1026 expectedStr := strings.TrimSpace(originalState.String()) 1027 if actualStr != expectedStr { 1028 t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr) 1029 } 1030 } 1031 1032 func TestApply_stateNoExist(t *testing.T) { 1033 p := testProvider() 1034 ui := new(cli.MockUi) 1035 c := &ApplyCommand{ 1036 Meta: Meta{ 1037 ContextOpts: testCtxConfig(p), 1038 Ui: ui, 1039 }, 1040 } 1041 1042 args := []string{ 1043 "idontexist.tfstate", 1044 testFixturePath("apply"), 1045 } 1046 if code := c.Run(args); code != 1 { 1047 t.Fatalf("bad: \n%s", ui.OutputWriter.String()) 1048 } 1049 } 1050 1051 func TestApply_sensitiveOutput(t *testing.T) { 1052 p := testProvider() 1053 ui := new(cli.MockUi) 1054 c := &ApplyCommand{ 1055 Meta: Meta{ 1056 ContextOpts: testCtxConfig(p), 1057 Ui: ui, 1058 }, 1059 } 1060 1061 statePath := testTempFile(t) 1062 1063 args := []string{ 1064 "-state", statePath, 1065 testFixturePath("apply-sensitive-output"), 1066 } 1067 1068 if code := c.Run(args); code != 0 { 1069 t.Fatalf("bad: \n%s", ui.OutputWriter.String()) 1070 } 1071 1072 output := ui.OutputWriter.String() 1073 if !strings.Contains(output, "notsensitive = Hello world") { 1074 t.Fatalf("bad: output should contain 'notsensitive' output\n%s", output) 1075 } 1076 if !strings.Contains(output, "sensitive = <sensitive>") { 1077 t.Fatalf("bad: output should contain 'sensitive' output\n%s", output) 1078 } 1079 } 1080 1081 func TestApply_stateFuture(t *testing.T) { 1082 originalState := testState() 1083 originalState.TFVersion = "99.99.99" 1084 statePath := testStateFile(t, originalState) 1085 1086 p := testProvider() 1087 ui := new(cli.MockUi) 1088 c := &ApplyCommand{ 1089 Meta: Meta{ 1090 ContextOpts: testCtxConfig(p), 1091 Ui: ui, 1092 }, 1093 } 1094 1095 args := []string{ 1096 "-state", statePath, 1097 testFixturePath("apply"), 1098 } 1099 if code := c.Run(args); code == 0 { 1100 t.Fatal("should fail") 1101 } 1102 1103 f, err := os.Open(statePath) 1104 if err != nil { 1105 t.Fatalf("err: %s", err) 1106 } 1107 1108 newState, err := terraform.ReadState(f) 1109 f.Close() 1110 if err != nil { 1111 t.Fatalf("err: %s", err) 1112 } 1113 1114 if !newState.Equal(originalState) { 1115 t.Fatalf("bad: %#v", newState) 1116 } 1117 if newState.TFVersion != originalState.TFVersion { 1118 t.Fatalf("bad: %#v", newState) 1119 } 1120 } 1121 1122 func TestApply_statePast(t *testing.T) { 1123 originalState := testState() 1124 originalState.TFVersion = "0.1.0" 1125 statePath := testStateFile(t, originalState) 1126 1127 p := testProvider() 1128 ui := new(cli.MockUi) 1129 c := &ApplyCommand{ 1130 Meta: Meta{ 1131 ContextOpts: testCtxConfig(p), 1132 Ui: ui, 1133 }, 1134 } 1135 1136 args := []string{ 1137 "-state", statePath, 1138 testFixturePath("apply"), 1139 } 1140 if code := c.Run(args); code != 0 { 1141 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1142 } 1143 } 1144 1145 func TestApply_vars(t *testing.T) { 1146 statePath := testTempFile(t) 1147 1148 p := testProvider() 1149 ui := new(cli.MockUi) 1150 c := &ApplyCommand{ 1151 Meta: Meta{ 1152 ContextOpts: testCtxConfig(p), 1153 Ui: ui, 1154 }, 1155 } 1156 1157 actual := "" 1158 p.DiffFn = func( 1159 info *terraform.InstanceInfo, 1160 s *terraform.InstanceState, 1161 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 1162 if v, ok := c.Config["value"]; ok { 1163 actual = v.(string) 1164 } 1165 1166 return &terraform.InstanceDiff{}, nil 1167 } 1168 1169 args := []string{ 1170 "-var", "foo=bar", 1171 "-state", statePath, 1172 testFixturePath("apply-vars"), 1173 } 1174 if code := c.Run(args); code != 0 { 1175 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1176 } 1177 1178 if actual != "bar" { 1179 t.Fatal("didn't work") 1180 } 1181 } 1182 1183 func TestApply_varFile(t *testing.T) { 1184 varFilePath := testTempFile(t) 1185 if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil { 1186 t.Fatalf("err: %s", err) 1187 } 1188 1189 statePath := testTempFile(t) 1190 1191 p := testProvider() 1192 ui := new(cli.MockUi) 1193 c := &ApplyCommand{ 1194 Meta: Meta{ 1195 ContextOpts: testCtxConfig(p), 1196 Ui: ui, 1197 }, 1198 } 1199 1200 actual := "" 1201 p.DiffFn = func( 1202 info *terraform.InstanceInfo, 1203 s *terraform.InstanceState, 1204 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 1205 if v, ok := c.Config["value"]; ok { 1206 actual = v.(string) 1207 } 1208 1209 return &terraform.InstanceDiff{}, nil 1210 } 1211 1212 args := []string{ 1213 "-var-file", varFilePath, 1214 "-state", statePath, 1215 testFixturePath("apply-vars"), 1216 } 1217 if code := c.Run(args); code != 0 { 1218 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1219 } 1220 1221 if actual != "bar" { 1222 t.Fatal("didn't work") 1223 } 1224 } 1225 1226 func TestApply_varFileDefault(t *testing.T) { 1227 varFileDir := testTempDir(t) 1228 varFilePath := filepath.Join(varFileDir, "terraform.tfvars") 1229 if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil { 1230 t.Fatalf("err: %s", err) 1231 } 1232 1233 statePath := testTempFile(t) 1234 1235 cwd, err := os.Getwd() 1236 if err != nil { 1237 t.Fatalf("err: %s", err) 1238 } 1239 if err := os.Chdir(varFileDir); err != nil { 1240 t.Fatalf("err: %s", err) 1241 } 1242 defer os.Chdir(cwd) 1243 1244 p := testProvider() 1245 ui := new(cli.MockUi) 1246 c := &ApplyCommand{ 1247 Meta: Meta{ 1248 ContextOpts: testCtxConfig(p), 1249 Ui: ui, 1250 }, 1251 } 1252 1253 actual := "" 1254 p.DiffFn = func( 1255 info *terraform.InstanceInfo, 1256 s *terraform.InstanceState, 1257 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 1258 if v, ok := c.Config["value"]; ok { 1259 actual = v.(string) 1260 } 1261 1262 return &terraform.InstanceDiff{}, nil 1263 } 1264 1265 args := []string{ 1266 "-state", statePath, 1267 testFixturePath("apply-vars"), 1268 } 1269 if code := c.Run(args); code != 0 { 1270 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1271 } 1272 1273 if actual != "bar" { 1274 t.Fatal("didn't work") 1275 } 1276 } 1277 1278 func TestApply_varFileDefaultJSON(t *testing.T) { 1279 varFileDir := testTempDir(t) 1280 varFilePath := filepath.Join(varFileDir, "terraform.tfvars.json") 1281 if err := ioutil.WriteFile(varFilePath, []byte(applyVarFileJSON), 0644); err != nil { 1282 t.Fatalf("err: %s", err) 1283 } 1284 1285 statePath := testTempFile(t) 1286 1287 cwd, err := os.Getwd() 1288 if err != nil { 1289 t.Fatalf("err: %s", err) 1290 } 1291 if err := os.Chdir(varFileDir); err != nil { 1292 t.Fatalf("err: %s", err) 1293 } 1294 defer os.Chdir(cwd) 1295 1296 p := testProvider() 1297 ui := new(cli.MockUi) 1298 c := &ApplyCommand{ 1299 Meta: Meta{ 1300 ContextOpts: testCtxConfig(p), 1301 Ui: ui, 1302 }, 1303 } 1304 1305 actual := "" 1306 p.DiffFn = func( 1307 info *terraform.InstanceInfo, 1308 s *terraform.InstanceState, 1309 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 1310 if v, ok := c.Config["value"]; ok { 1311 actual = v.(string) 1312 } 1313 1314 return &terraform.InstanceDiff{}, nil 1315 } 1316 1317 args := []string{ 1318 "-state", statePath, 1319 testFixturePath("apply-vars"), 1320 } 1321 if code := c.Run(args); code != 0 { 1322 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1323 } 1324 1325 if actual != "bar" { 1326 t.Fatal("didn't work") 1327 } 1328 } 1329 1330 func TestApply_backup(t *testing.T) { 1331 originalState := &terraform.State{ 1332 Modules: []*terraform.ModuleState{ 1333 &terraform.ModuleState{ 1334 Path: []string{"root"}, 1335 Resources: map[string]*terraform.ResourceState{ 1336 "test_instance.foo": &terraform.ResourceState{ 1337 Type: "test_instance", 1338 Primary: &terraform.InstanceState{ 1339 ID: "bar", 1340 }, 1341 }, 1342 }, 1343 }, 1344 }, 1345 } 1346 originalState.Init() 1347 1348 statePath := testStateFile(t, originalState) 1349 backupPath := testTempFile(t) 1350 1351 p := testProvider() 1352 p.DiffReturn = &terraform.InstanceDiff{ 1353 Attributes: map[string]*terraform.ResourceAttrDiff{ 1354 "ami": &terraform.ResourceAttrDiff{ 1355 New: "bar", 1356 }, 1357 }, 1358 } 1359 1360 ui := new(cli.MockUi) 1361 c := &ApplyCommand{ 1362 Meta: Meta{ 1363 ContextOpts: testCtxConfig(p), 1364 Ui: ui, 1365 }, 1366 } 1367 1368 // Run the apply command pointing to our existing state 1369 args := []string{ 1370 "-state", statePath, 1371 "-backup", backupPath, 1372 testFixturePath("apply"), 1373 } 1374 if code := c.Run(args); code != 0 { 1375 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1376 } 1377 1378 // Verify a new state exists 1379 if _, err := os.Stat(statePath); err != nil { 1380 t.Fatalf("err: %s", err) 1381 } 1382 1383 f, err := os.Open(statePath) 1384 if err != nil { 1385 t.Fatalf("err: %s", err) 1386 } 1387 defer f.Close() 1388 1389 state, err := terraform.ReadState(f) 1390 if err != nil { 1391 t.Fatalf("err: %s", err) 1392 } 1393 if state == nil { 1394 t.Fatal("state should not be nil") 1395 } 1396 1397 // Should have a backup file 1398 f, err = os.Open(backupPath) 1399 if err != nil { 1400 t.Fatalf("err: %s", err) 1401 } 1402 1403 backupState, err := terraform.ReadState(f) 1404 f.Close() 1405 if err != nil { 1406 t.Fatalf("err: %s", err) 1407 } 1408 1409 actual := backupState.RootModule().Resources["test_instance.foo"] 1410 expected := originalState.RootModule().Resources["test_instance.foo"] 1411 if !reflect.DeepEqual(actual, expected) { 1412 t.Fatalf("bad: %#v %#v", actual, expected) 1413 } 1414 } 1415 1416 func TestApply_disableBackup(t *testing.T) { 1417 originalState := testState() 1418 statePath := testStateFile(t, originalState) 1419 1420 p := testProvider() 1421 p.DiffReturn = &terraform.InstanceDiff{ 1422 Attributes: map[string]*terraform.ResourceAttrDiff{ 1423 "ami": &terraform.ResourceAttrDiff{ 1424 New: "bar", 1425 }, 1426 }, 1427 } 1428 1429 ui := new(cli.MockUi) 1430 c := &ApplyCommand{ 1431 Meta: Meta{ 1432 ContextOpts: testCtxConfig(p), 1433 Ui: ui, 1434 }, 1435 } 1436 1437 // Run the apply command pointing to our existing state 1438 args := []string{ 1439 "-state", statePath, 1440 "-backup", "-", 1441 testFixturePath("apply"), 1442 } 1443 if code := c.Run(args); code != 0 { 1444 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1445 } 1446 1447 // Verify that the provider was called with the existing state 1448 actual := strings.TrimSpace(p.DiffState.String()) 1449 expected := strings.TrimSpace(testApplyDisableBackupStr) 1450 if actual != expected { 1451 t.Fatalf("bad:\n\n%s", actual) 1452 } 1453 1454 actual = strings.TrimSpace(p.ApplyState.String()) 1455 expected = strings.TrimSpace(testApplyDisableBackupStateStr) 1456 if actual != expected { 1457 t.Fatalf("bad:\n\n%s", actual) 1458 } 1459 1460 // Verify a new state exists 1461 if _, err := os.Stat(statePath); err != nil { 1462 t.Fatalf("err: %s", err) 1463 } 1464 1465 f, err := os.Open(statePath) 1466 if err != nil { 1467 t.Fatalf("err: %s", err) 1468 } 1469 defer f.Close() 1470 1471 state, err := terraform.ReadState(f) 1472 if err != nil { 1473 t.Fatalf("err: %s", err) 1474 } 1475 if state == nil { 1476 t.Fatal("state should not be nil") 1477 } 1478 1479 // Ensure there is no backup 1480 _, err = os.Stat(statePath + DefaultBackupExtension) 1481 if err == nil || !os.IsNotExist(err) { 1482 t.Fatalf("backup should not exist") 1483 } 1484 1485 // Ensure there is no literal "-" 1486 _, err = os.Stat("-") 1487 if err == nil || !os.IsNotExist(err) { 1488 t.Fatalf("backup should not exist") 1489 } 1490 } 1491 1492 // -state-out wasn't taking effect when a plan is supplied. GH-7264 1493 func TestApply_stateOutWithPlan(t *testing.T) { 1494 p := testProvider() 1495 ui := new(cli.MockUi) 1496 1497 tmpDir := testTempDir(t) 1498 defer os.RemoveAll(tmpDir) 1499 1500 statePath := filepath.Join(tmpDir, "state.tfstate") 1501 planPath := filepath.Join(tmpDir, "terraform.tfplan") 1502 1503 args := []string{ 1504 "-state", statePath, 1505 "-out", planPath, 1506 testFixturePath("plan"), 1507 } 1508 1509 // Run plan first to get a current plan file 1510 pc := &PlanCommand{ 1511 Meta: Meta{ 1512 ContextOpts: testCtxConfig(p), 1513 Ui: ui, 1514 }, 1515 } 1516 if code := pc.Run(args); code != 0 { 1517 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1518 } 1519 1520 // now run apply with the generated plan 1521 stateOutPath := filepath.Join(tmpDir, "state-new.tfstate") 1522 1523 args = []string{ 1524 "-state", statePath, 1525 "-state-out", stateOutPath, 1526 planPath, 1527 } 1528 1529 ac := &ApplyCommand{ 1530 Meta: Meta{ 1531 ContextOpts: testCtxConfig(p), 1532 Ui: ui, 1533 }, 1534 } 1535 if code := ac.Run(args); code != 0 { 1536 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 1537 } 1538 1539 // now make sure we wrote out our new state 1540 if _, err := os.Stat(stateOutPath); err != nil { 1541 t.Fatalf("missing new state file: %s", err) 1542 } 1543 } 1544 1545 func testHttpServer(t *testing.T) net.Listener { 1546 ln, err := net.Listen("tcp", "127.0.0.1:0") 1547 if err != nil { 1548 t.Fatalf("err: %s", err) 1549 } 1550 1551 mux := http.NewServeMux() 1552 mux.HandleFunc("/header", testHttpHandlerHeader) 1553 1554 var server http.Server 1555 server.Handler = mux 1556 go server.Serve(ln) 1557 1558 return ln 1559 } 1560 1561 func testHttpHandlerHeader(w http.ResponseWriter, r *http.Request) { 1562 var url url.URL 1563 url.Scheme = "file" 1564 url.Path = filepath.ToSlash(testFixturePath("init")) 1565 1566 w.Header().Add("X-Terraform-Get", url.String()) 1567 w.WriteHeader(200) 1568 } 1569 1570 const applyVarFile = ` 1571 foo = "bar" 1572 ` 1573 1574 const applyVarFileJSON = ` 1575 { "foo": "bar" } 1576 ` 1577 1578 const testApplyDisableBackupStr = ` 1579 ID = bar 1580 Tainted = false 1581 ` 1582 1583 const testApplyDisableBackupStateStr = ` 1584 ID = bar 1585 Tainted = false 1586 ` 1587 1588 const testApplyStateStr = ` 1589 ID = bar 1590 Tainted = false 1591 ` 1592 1593 const testApplyStateDiffStr = ` 1594 ID = bar 1595 Tainted = false 1596 `