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