github.com/ezbercih/terraform@v0.1.1-0.20140729011846-3c33865e0839/command/apply_test.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 "reflect" 9 "sync" 10 "testing" 11 "time" 12 13 "github.com/hashicorp/terraform/config" 14 "github.com/hashicorp/terraform/terraform" 15 "github.com/mitchellh/cli" 16 ) 17 18 func TestApply(t *testing.T) { 19 statePath := testTempFile(t) 20 21 p := testProvider() 22 ui := new(cli.MockUi) 23 c := &ApplyCommand{ 24 Meta: Meta{ 25 ContextOpts: testCtxConfig(p), 26 Ui: ui, 27 }, 28 } 29 30 args := []string{ 31 "-state", statePath, 32 testFixturePath("apply"), 33 } 34 if code := c.Run(args); code != 0 { 35 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 36 } 37 38 if _, err := os.Stat(statePath); err != nil { 39 t.Fatalf("err: %s", err) 40 } 41 42 f, err := os.Open(statePath) 43 if err != nil { 44 t.Fatalf("err: %s", err) 45 } 46 defer f.Close() 47 48 state, err := terraform.ReadState(f) 49 if err != nil { 50 t.Fatalf("err: %s", err) 51 } 52 if state == nil { 53 t.Fatal("state should not be nil") 54 } 55 } 56 57 func TestApply_configInvalid(t *testing.T) { 58 p := testProvider() 59 ui := new(cli.MockUi) 60 c := &ApplyCommand{ 61 Meta: Meta{ 62 ContextOpts: testCtxConfig(p), 63 Ui: ui, 64 }, 65 } 66 67 args := []string{ 68 "-state", testTempFile(t), 69 testFixturePath("apply-config-invalid"), 70 } 71 if code := c.Run(args); code != 1 { 72 t.Fatalf("bad: \n%s", ui.OutputWriter.String()) 73 } 74 } 75 76 func TestApply_defaultState(t *testing.T) { 77 td, err := ioutil.TempDir("", "tf") 78 if err != nil { 79 t.Fatalf("err: %s", err) 80 } 81 statePath := filepath.Join(td, DefaultStateFilename) 82 83 // Change to the temporary directory 84 cwd, err := os.Getwd() 85 if err != nil { 86 t.Fatalf("err: %s", err) 87 } 88 if err := os.Chdir(filepath.Dir(statePath)); err != nil { 89 t.Fatalf("err: %s", err) 90 } 91 defer os.Chdir(cwd) 92 93 p := testProvider() 94 ui := new(cli.MockUi) 95 c := &ApplyCommand{ 96 Meta: Meta{ 97 ContextOpts: testCtxConfig(p), 98 Ui: ui, 99 }, 100 } 101 102 args := []string{ 103 testFixturePath("apply"), 104 } 105 if code := c.Run(args); code != 0 { 106 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 107 } 108 109 if _, err := os.Stat(statePath); err != nil { 110 t.Fatalf("err: %s", err) 111 } 112 113 f, err := os.Open(statePath) 114 if err != nil { 115 t.Fatalf("err: %s", err) 116 } 117 defer f.Close() 118 119 state, err := terraform.ReadState(f) 120 if err != nil { 121 t.Fatalf("err: %s", err) 122 } 123 if state == nil { 124 t.Fatal("state should not be nil") 125 } 126 } 127 128 func TestApply_error(t *testing.T) { 129 statePath := testTempFile(t) 130 131 p := testProvider() 132 ui := new(cli.MockUi) 133 c := &ApplyCommand{ 134 Meta: Meta{ 135 ContextOpts: testCtxConfig(p), 136 Ui: ui, 137 }, 138 } 139 140 var lock sync.Mutex 141 errored := false 142 p.ApplyFn = func( 143 s *terraform.ResourceState, 144 d *terraform.ResourceDiff) (*terraform.ResourceState, error) { 145 lock.Lock() 146 defer lock.Unlock() 147 148 if !errored { 149 errored = true 150 return nil, fmt.Errorf("error") 151 } 152 153 return &terraform.ResourceState{ID: "foo"}, nil 154 } 155 p.DiffFn = func( 156 *terraform.ResourceState, 157 *terraform.ResourceConfig) (*terraform.ResourceDiff, error) { 158 return &terraform.ResourceDiff{ 159 Attributes: map[string]*terraform.ResourceAttrDiff{ 160 "ami": &terraform.ResourceAttrDiff{ 161 New: "bar", 162 }, 163 }, 164 }, nil 165 } 166 167 args := []string{ 168 "-state", statePath, 169 testFixturePath("apply-error"), 170 } 171 if code := c.Run(args); code != 1 { 172 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 173 } 174 175 if _, err := os.Stat(statePath); err != nil { 176 t.Fatalf("err: %s", err) 177 } 178 179 f, err := os.Open(statePath) 180 if err != nil { 181 t.Fatalf("err: %s", err) 182 } 183 defer f.Close() 184 185 state, err := terraform.ReadState(f) 186 if err != nil { 187 t.Fatalf("err: %s", err) 188 } 189 if state == nil { 190 t.Fatal("state should not be nil") 191 } 192 if len(state.Resources) == 0 { 193 t.Fatal("no resources in state") 194 } 195 } 196 197 func TestApply_noArgs(t *testing.T) { 198 cwd, err := os.Getwd() 199 if err != nil { 200 t.Fatalf("err: %s", err) 201 } 202 if err := os.Chdir(testFixturePath("plan")); err != nil { 203 t.Fatalf("err: %s", err) 204 } 205 defer os.Chdir(cwd) 206 207 statePath := testTempFile(t) 208 209 p := testProvider() 210 ui := new(cli.MockUi) 211 c := &ApplyCommand{ 212 Meta: Meta{ 213 ContextOpts: testCtxConfig(p), 214 Ui: ui, 215 }, 216 } 217 218 args := []string{ 219 "-state", statePath, 220 } 221 if code := c.Run(args); code != 0 { 222 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 223 } 224 225 if _, err := os.Stat(statePath); err != nil { 226 t.Fatalf("err: %s", err) 227 } 228 229 f, err := os.Open(statePath) 230 if err != nil { 231 t.Fatalf("err: %s", err) 232 } 233 defer f.Close() 234 235 state, err := terraform.ReadState(f) 236 if err != nil { 237 t.Fatalf("err: %s", err) 238 } 239 if state == nil { 240 t.Fatal("state should not be nil") 241 } 242 } 243 244 func TestApply_plan(t *testing.T) { 245 planPath := testPlanFile(t, &terraform.Plan{ 246 Config: new(config.Config), 247 }) 248 statePath := testTempFile(t) 249 250 p := testProvider() 251 ui := new(cli.MockUi) 252 c := &ApplyCommand{ 253 Meta: Meta{ 254 ContextOpts: testCtxConfig(p), 255 Ui: ui, 256 }, 257 } 258 259 args := []string{ 260 "-state", statePath, 261 planPath, 262 } 263 if code := c.Run(args); code != 0 { 264 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 265 } 266 267 if _, err := os.Stat(statePath); err != nil { 268 t.Fatalf("err: %s", err) 269 } 270 271 f, err := os.Open(statePath) 272 if err != nil { 273 t.Fatalf("err: %s", err) 274 } 275 defer f.Close() 276 277 state, err := terraform.ReadState(f) 278 if err != nil { 279 t.Fatalf("err: %s", err) 280 } 281 if state == nil { 282 t.Fatal("state should not be nil") 283 } 284 } 285 286 func TestApply_planVars(t *testing.T) { 287 planPath := testPlanFile(t, &terraform.Plan{ 288 Config: new(config.Config), 289 }) 290 statePath := testTempFile(t) 291 292 p := testProvider() 293 ui := new(cli.MockUi) 294 c := &ApplyCommand{ 295 Meta: Meta{ 296 ContextOpts: testCtxConfig(p), 297 Ui: ui, 298 }, 299 } 300 301 args := []string{ 302 "-state", statePath, 303 "-var", "foo=bar", 304 planPath, 305 } 306 if code := c.Run(args); code == 0 { 307 t.Fatal("should've failed") 308 } 309 } 310 311 func TestApply_refresh(t *testing.T) { 312 originalState := &terraform.State{ 313 Resources: map[string]*terraform.ResourceState{ 314 "test_instance.foo": &terraform.ResourceState{ 315 ID: "bar", 316 Type: "test_instance", 317 }, 318 }, 319 } 320 321 statePath := testStateFile(t, originalState) 322 323 p := testProvider() 324 ui := new(cli.MockUi) 325 c := &ApplyCommand{ 326 Meta: Meta{ 327 ContextOpts: testCtxConfig(p), 328 Ui: ui, 329 }, 330 } 331 332 args := []string{ 333 "-state", statePath, 334 testFixturePath("apply"), 335 } 336 if code := c.Run(args); code != 0 { 337 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 338 } 339 340 if !p.RefreshCalled { 341 t.Fatal("should call refresh") 342 } 343 344 if _, err := os.Stat(statePath); err != nil { 345 t.Fatalf("err: %s", err) 346 } 347 348 f, err := os.Open(statePath) 349 if err != nil { 350 t.Fatalf("err: %s", err) 351 } 352 defer f.Close() 353 354 state, err := terraform.ReadState(f) 355 if err != nil { 356 t.Fatalf("err: %s", err) 357 } 358 if state == nil { 359 t.Fatal("state should not be nil") 360 } 361 362 // Should have a backup file 363 f, err = os.Open(statePath + DefaultBackupExtention) 364 if err != nil { 365 t.Fatalf("err: %s", err) 366 } 367 368 backupState, err := terraform.ReadState(f) 369 f.Close() 370 if err != nil { 371 t.Fatalf("err: %s", err) 372 } 373 374 if !reflect.DeepEqual(backupState, originalState) { 375 t.Fatalf("bad: %#v", backupState) 376 } 377 } 378 379 func TestApply_shutdown(t *testing.T) { 380 stopped := false 381 stopCh := make(chan struct{}) 382 stopReplyCh := make(chan struct{}) 383 384 statePath := testTempFile(t) 385 386 p := testProvider() 387 shutdownCh := make(chan struct{}) 388 ui := new(cli.MockUi) 389 c := &ApplyCommand{ 390 Meta: Meta{ 391 ContextOpts: testCtxConfig(p), 392 Ui: ui, 393 }, 394 395 ShutdownCh: shutdownCh, 396 } 397 398 p.DiffFn = func( 399 *terraform.ResourceState, 400 *terraform.ResourceConfig) (*terraform.ResourceDiff, error) { 401 return &terraform.ResourceDiff{ 402 Attributes: map[string]*terraform.ResourceAttrDiff{ 403 "ami": &terraform.ResourceAttrDiff{ 404 New: "bar", 405 }, 406 }, 407 }, nil 408 } 409 p.ApplyFn = func( 410 *terraform.ResourceState, 411 *terraform.ResourceDiff) (*terraform.ResourceState, error) { 412 if !stopped { 413 stopped = true 414 close(stopCh) 415 <-stopReplyCh 416 } 417 418 return &terraform.ResourceState{ 419 ID: "foo", 420 Attributes: map[string]string{ 421 "ami": "2", 422 }, 423 }, nil 424 } 425 426 go func() { 427 <-stopCh 428 shutdownCh <- struct{}{} 429 430 // This is really dirty, but we have no other way to assure that 431 // tf.Stop() has been called. This doesn't assure it either, but 432 // it makes it much more certain. 433 time.Sleep(50 * time.Millisecond) 434 435 close(stopReplyCh) 436 }() 437 438 args := []string{ 439 "-state", statePath, 440 testFixturePath("apply-shutdown"), 441 } 442 if code := c.Run(args); code != 0 { 443 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 444 } 445 446 if _, err := os.Stat(statePath); err != nil { 447 t.Fatalf("err: %s", err) 448 } 449 450 f, err := os.Open(statePath) 451 if err != nil { 452 t.Fatalf("err: %s", err) 453 } 454 defer f.Close() 455 456 state, err := terraform.ReadState(f) 457 if err != nil { 458 t.Fatalf("err: %s", err) 459 } 460 if state == nil { 461 t.Fatal("state should not be nil") 462 } 463 464 if len(state.Resources) != 1 { 465 t.Fatalf("bad: %d", len(state.Resources)) 466 } 467 } 468 469 func TestApply_state(t *testing.T) { 470 originalState := &terraform.State{ 471 Resources: map[string]*terraform.ResourceState{ 472 "test_instance.foo": &terraform.ResourceState{ 473 ID: "bar", 474 Type: "test_instance", 475 ConnInfo: make(map[string]string), 476 }, 477 }, 478 } 479 480 statePath := testStateFile(t, originalState) 481 482 p := testProvider() 483 p.DiffReturn = &terraform.ResourceDiff{ 484 Attributes: map[string]*terraform.ResourceAttrDiff{ 485 "ami": &terraform.ResourceAttrDiff{ 486 New: "bar", 487 }, 488 }, 489 } 490 491 ui := new(cli.MockUi) 492 c := &ApplyCommand{ 493 Meta: Meta{ 494 ContextOpts: testCtxConfig(p), 495 Ui: ui, 496 }, 497 } 498 499 // Run the apply command pointing to our existing state 500 args := []string{ 501 "-state", statePath, 502 testFixturePath("apply"), 503 } 504 if code := c.Run(args); code != 0 { 505 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 506 } 507 508 // Verify that the provider was called with the existing state 509 expectedState := originalState.Resources["test_instance.foo"] 510 if !reflect.DeepEqual(p.DiffState, expectedState) { 511 t.Fatalf("bad: %#v", p.DiffState) 512 } 513 514 if !reflect.DeepEqual(p.ApplyState, expectedState) { 515 t.Fatalf("bad: %#v", p.ApplyState) 516 } 517 518 // Verify a new state exists 519 if _, err := os.Stat(statePath); err != nil { 520 t.Fatalf("err: %s", err) 521 } 522 523 f, err := os.Open(statePath) 524 if err != nil { 525 t.Fatalf("err: %s", err) 526 } 527 defer f.Close() 528 529 state, err := terraform.ReadState(f) 530 if err != nil { 531 t.Fatalf("err: %s", err) 532 } 533 if state == nil { 534 t.Fatal("state should not be nil") 535 } 536 537 // Should have a backup file 538 f, err = os.Open(statePath + DefaultBackupExtention) 539 if err != nil { 540 t.Fatalf("err: %s", err) 541 } 542 543 backupState, err := terraform.ReadState(f) 544 f.Close() 545 if err != nil { 546 t.Fatalf("err: %s", err) 547 } 548 549 // nil out the ConnInfo since that should not be restored 550 originalState.Resources["test_instance.foo"].ConnInfo = nil 551 552 if !reflect.DeepEqual(backupState, originalState) { 553 t.Fatalf("bad: %#v", backupState) 554 } 555 } 556 557 func TestApply_stateNoExist(t *testing.T) { 558 p := testProvider() 559 ui := new(cli.MockUi) 560 c := &ApplyCommand{ 561 Meta: Meta{ 562 ContextOpts: testCtxConfig(p), 563 Ui: ui, 564 }, 565 } 566 567 args := []string{ 568 "idontexist.tfstate", 569 testFixturePath("apply"), 570 } 571 if code := c.Run(args); code != 1 { 572 t.Fatalf("bad: \n%s", ui.OutputWriter.String()) 573 } 574 } 575 576 func TestApply_vars(t *testing.T) { 577 statePath := testTempFile(t) 578 579 p := testProvider() 580 ui := new(cli.MockUi) 581 c := &ApplyCommand{ 582 Meta: Meta{ 583 ContextOpts: testCtxConfig(p), 584 Ui: ui, 585 }, 586 } 587 588 actual := "" 589 p.DiffFn = func( 590 s *terraform.ResourceState, 591 c *terraform.ResourceConfig) (*terraform.ResourceDiff, error) { 592 if v, ok := c.Config["value"]; ok { 593 actual = v.(string) 594 } 595 596 return &terraform.ResourceDiff{}, nil 597 } 598 599 args := []string{ 600 "-var", "foo=bar", 601 "-state", statePath, 602 testFixturePath("apply-vars"), 603 } 604 if code := c.Run(args); code != 0 { 605 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 606 } 607 608 if actual != "bar" { 609 t.Fatal("didn't work") 610 } 611 } 612 613 func TestApply_varFile(t *testing.T) { 614 varFilePath := testTempFile(t) 615 if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil { 616 t.Fatalf("err: %s", err) 617 } 618 619 statePath := testTempFile(t) 620 621 p := testProvider() 622 ui := new(cli.MockUi) 623 c := &ApplyCommand{ 624 Meta: Meta{ 625 ContextOpts: testCtxConfig(p), 626 Ui: ui, 627 }, 628 } 629 630 actual := "" 631 p.DiffFn = func( 632 s *terraform.ResourceState, 633 c *terraform.ResourceConfig) (*terraform.ResourceDiff, error) { 634 if v, ok := c.Config["value"]; ok { 635 actual = v.(string) 636 } 637 638 return &terraform.ResourceDiff{}, nil 639 } 640 641 args := []string{ 642 "-var-file", varFilePath, 643 "-state", statePath, 644 testFixturePath("apply-vars"), 645 } 646 if code := c.Run(args); code != 0 { 647 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 648 } 649 650 if actual != "bar" { 651 t.Fatal("didn't work") 652 } 653 } 654 655 func TestApply_backup(t *testing.T) { 656 originalState := &terraform.State{ 657 Resources: map[string]*terraform.ResourceState{ 658 "test_instance.foo": &terraform.ResourceState{ 659 ID: "bar", 660 Type: "test_instance", 661 }, 662 }, 663 } 664 665 statePath := testStateFile(t, originalState) 666 backupPath := testTempFile(t) 667 668 p := testProvider() 669 p.DiffReturn = &terraform.ResourceDiff{ 670 Attributes: map[string]*terraform.ResourceAttrDiff{ 671 "ami": &terraform.ResourceAttrDiff{ 672 New: "bar", 673 }, 674 }, 675 } 676 677 ui := new(cli.MockUi) 678 c := &ApplyCommand{ 679 Meta: Meta{ 680 ContextOpts: testCtxConfig(p), 681 Ui: ui, 682 }, 683 } 684 685 // Run the apply command pointing to our existing state 686 args := []string{ 687 "-state", statePath, 688 "-backup", backupPath, 689 testFixturePath("apply"), 690 } 691 if code := c.Run(args); code != 0 { 692 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 693 } 694 695 // Verify a new state exists 696 if _, err := os.Stat(statePath); err != nil { 697 t.Fatalf("err: %s", err) 698 } 699 700 f, err := os.Open(statePath) 701 if err != nil { 702 t.Fatalf("err: %s", err) 703 } 704 defer f.Close() 705 706 state, err := terraform.ReadState(f) 707 if err != nil { 708 t.Fatalf("err: %s", err) 709 } 710 if state == nil { 711 t.Fatal("state should not be nil") 712 } 713 714 // Should have a backup file 715 f, err = os.Open(backupPath) 716 if err != nil { 717 t.Fatalf("err: %s", err) 718 } 719 720 backupState, err := terraform.ReadState(f) 721 f.Close() 722 if err != nil { 723 t.Fatalf("err: %s", err) 724 } 725 726 actual := backupState.Resources["test_instance.foo"] 727 expected := originalState.Resources["test_instance.foo"] 728 if !reflect.DeepEqual(actual, expected) { 729 t.Fatalf("bad: %#v %#v", actual, expected) 730 } 731 } 732 733 func TestApply_disableBackup(t *testing.T) { 734 originalState := &terraform.State{ 735 Resources: map[string]*terraform.ResourceState{ 736 "test_instance.foo": &terraform.ResourceState{ 737 ID: "bar", 738 Type: "test_instance", 739 ConnInfo: make(map[string]string), 740 }, 741 }, 742 } 743 744 statePath := testStateFile(t, originalState) 745 746 p := testProvider() 747 p.DiffReturn = &terraform.ResourceDiff{ 748 Attributes: map[string]*terraform.ResourceAttrDiff{ 749 "ami": &terraform.ResourceAttrDiff{ 750 New: "bar", 751 }, 752 }, 753 } 754 755 ui := new(cli.MockUi) 756 c := &ApplyCommand{ 757 Meta: Meta{ 758 ContextOpts: testCtxConfig(p), 759 Ui: ui, 760 }, 761 } 762 763 // Run the apply command pointing to our existing state 764 args := []string{ 765 "-state", statePath, 766 "-backup", "-", 767 testFixturePath("apply"), 768 } 769 if code := c.Run(args); code != 0 { 770 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 771 } 772 773 // Verify that the provider was called with the existing state 774 expectedState := originalState.Resources["test_instance.foo"] 775 if !reflect.DeepEqual(p.DiffState, expectedState) { 776 t.Fatalf("bad: %#v", p.DiffState) 777 } 778 779 if !reflect.DeepEqual(p.ApplyState, expectedState) { 780 t.Fatalf("bad: %#v", p.ApplyState) 781 } 782 783 // Verify a new state exists 784 if _, err := os.Stat(statePath); err != nil { 785 t.Fatalf("err: %s", err) 786 } 787 788 f, err := os.Open(statePath) 789 if err != nil { 790 t.Fatalf("err: %s", err) 791 } 792 defer f.Close() 793 794 state, err := terraform.ReadState(f) 795 if err != nil { 796 t.Fatalf("err: %s", err) 797 } 798 if state == nil { 799 t.Fatal("state should not be nil") 800 } 801 802 // Ensure there is no backup 803 _, err = os.Stat(statePath + DefaultBackupExtention) 804 if err == nil || !os.IsNotExist(err) { 805 t.Fatalf("backup should not exist") 806 } 807 } 808 809 const applyVarFile = ` 810 foo = "bar" 811 `