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