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