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