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