github.com/odise/terraform@v0.6.9-0.20160401223921-f7d1e0390da7/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 p := testProvider() 112 ui := new(cli.MockUi) 113 c := &PlanCommand{ 114 Meta: Meta{ 115 ContextOpts: testCtxConfig(p), 116 Ui: ui, 117 }, 118 } 119 120 args := []string{ 121 testFixturePath("plan"), 122 } 123 if code := c.Run(args); code != 0 { 124 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 125 } 126 127 // Verify that refresh was called 128 if p.RefreshCalled { 129 t.Fatal("refresh should not be called") 130 } 131 132 // Verify that the provider was called with the existing state 133 actual := strings.TrimSpace(p.DiffState.String()) 134 expected := strings.TrimSpace(testPlanNoStateStr) 135 if actual != expected { 136 t.Fatalf("bad:\n\n%s", actual) 137 } 138 } 139 140 func TestPlan_outPath(t *testing.T) { 141 tf, err := ioutil.TempFile("", "tf") 142 if err != nil { 143 t.Fatalf("err: %s", err) 144 } 145 outPath := tf.Name() 146 os.Remove(tf.Name()) 147 148 p := testProvider() 149 ui := new(cli.MockUi) 150 c := &PlanCommand{ 151 Meta: Meta{ 152 ContextOpts: testCtxConfig(p), 153 Ui: ui, 154 }, 155 } 156 157 p.DiffReturn = &terraform.InstanceDiff{ 158 Destroy: true, 159 } 160 161 args := []string{ 162 "-out", outPath, 163 testFixturePath("plan"), 164 } 165 if code := c.Run(args); code != 0 { 166 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 167 } 168 169 f, err := os.Open(outPath) 170 if err != nil { 171 t.Fatalf("err: %s", err) 172 } 173 defer f.Close() 174 175 if _, err := terraform.ReadPlan(f); err != nil { 176 t.Fatalf("err: %s", err) 177 } 178 } 179 180 func TestPlan_outPathNoChange(t *testing.T) { 181 originalState := &terraform.State{ 182 Modules: []*terraform.ModuleState{ 183 &terraform.ModuleState{ 184 Path: []string{"root"}, 185 Resources: map[string]*terraform.ResourceState{ 186 "test_instance.foo": &terraform.ResourceState{ 187 Type: "test_instance", 188 Primary: &terraform.InstanceState{ 189 ID: "bar", 190 }, 191 }, 192 }, 193 }, 194 }, 195 } 196 statePath := testStateFile(t, originalState) 197 198 tf, err := ioutil.TempFile("", "tf") 199 if err != nil { 200 t.Fatalf("err: %s", err) 201 } 202 outPath := tf.Name() 203 os.Remove(tf.Name()) 204 205 p := testProvider() 206 ui := new(cli.MockUi) 207 c := &PlanCommand{ 208 Meta: Meta{ 209 ContextOpts: testCtxConfig(p), 210 Ui: ui, 211 }, 212 } 213 214 args := []string{ 215 "-out", outPath, 216 "-state", statePath, 217 testFixturePath("plan"), 218 } 219 if code := c.Run(args); code != 0 { 220 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 221 } 222 223 plan := testReadPlan(t, outPath) 224 if !plan.Diff.Empty() { 225 t.Fatalf("Expected empty plan to be written to plan file, got: %s", plan) 226 } 227 } 228 229 func TestPlan_refresh(t *testing.T) { 230 p := testProvider() 231 ui := new(cli.MockUi) 232 c := &PlanCommand{ 233 Meta: Meta{ 234 ContextOpts: testCtxConfig(p), 235 Ui: ui, 236 }, 237 } 238 239 args := []string{ 240 "-refresh=false", 241 testFixturePath("plan"), 242 } 243 if code := c.Run(args); code != 0 { 244 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 245 } 246 247 if p.RefreshCalled { 248 t.Fatal("refresh should not be called") 249 } 250 } 251 252 func TestPlan_state(t *testing.T) { 253 // Write out some prior state 254 tf, err := ioutil.TempFile("", "tf") 255 if err != nil { 256 t.Fatalf("err: %s", err) 257 } 258 statePath := tf.Name() 259 defer os.Remove(tf.Name()) 260 261 originalState := testState() 262 err = terraform.WriteState(originalState, tf) 263 tf.Close() 264 if err != nil { 265 t.Fatalf("err: %s", err) 266 } 267 268 p := testProvider() 269 ui := new(cli.MockUi) 270 c := &PlanCommand{ 271 Meta: Meta{ 272 ContextOpts: testCtxConfig(p), 273 Ui: ui, 274 }, 275 } 276 277 args := []string{ 278 "-state", statePath, 279 testFixturePath("plan"), 280 } 281 if code := c.Run(args); code != 0 { 282 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 283 } 284 285 // Verify that the provider was called with the existing state 286 actual := strings.TrimSpace(p.DiffState.String()) 287 expected := strings.TrimSpace(testPlanStateStr) 288 if actual != expected { 289 t.Fatalf("bad:\n\n%s", actual) 290 } 291 } 292 293 func TestPlan_stateDefault(t *testing.T) { 294 originalState := testState() 295 296 // Write the state file in a temporary directory with the 297 // default filename. 298 td, err := ioutil.TempDir("", "tf") 299 if err != nil { 300 t.Fatalf("err: %s", err) 301 } 302 statePath := filepath.Join(td, DefaultStateFilename) 303 304 f, err := os.Create(statePath) 305 if err != nil { 306 t.Fatalf("err: %s", err) 307 } 308 err = terraform.WriteState(originalState, f) 309 f.Close() 310 if err != nil { 311 t.Fatalf("err: %s", err) 312 } 313 314 // Change to that directory 315 cwd, err := os.Getwd() 316 if err != nil { 317 t.Fatalf("err: %s", err) 318 } 319 if err := os.Chdir(filepath.Dir(statePath)); err != nil { 320 t.Fatalf("err: %s", err) 321 } 322 defer os.Chdir(cwd) 323 324 p := testProvider() 325 ui := new(cli.MockUi) 326 c := &PlanCommand{ 327 Meta: Meta{ 328 ContextOpts: testCtxConfig(p), 329 Ui: ui, 330 }, 331 } 332 333 args := []string{ 334 testFixturePath("plan"), 335 } 336 if code := c.Run(args); code != 0 { 337 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 338 } 339 340 // Verify that the provider was called with the existing state 341 actual := strings.TrimSpace(p.DiffState.String()) 342 expected := strings.TrimSpace(testPlanStateDefaultStr) 343 if actual != expected { 344 t.Fatalf("bad:\n\n%s", actual) 345 } 346 } 347 348 func TestPlan_vars(t *testing.T) { 349 p := testProvider() 350 ui := new(cli.MockUi) 351 c := &PlanCommand{ 352 Meta: Meta{ 353 ContextOpts: testCtxConfig(p), 354 Ui: ui, 355 }, 356 } 357 358 actual := "" 359 p.DiffFn = func( 360 info *terraform.InstanceInfo, 361 s *terraform.InstanceState, 362 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 363 if v, ok := c.Config["value"]; ok { 364 actual = v.(string) 365 } 366 367 return nil, nil 368 } 369 370 args := []string{ 371 "-var", "foo=bar", 372 testFixturePath("plan-vars"), 373 } 374 if code := c.Run(args); code != 0 { 375 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 376 } 377 378 if actual != "bar" { 379 t.Fatal("didn't work") 380 } 381 } 382 383 func TestPlan_varsUnset(t *testing.T) { 384 // Disable test mode so input would be asked 385 test = false 386 defer func() { test = true }() 387 388 defaultInputReader = bytes.NewBufferString("bar\n") 389 390 p := testProvider() 391 ui := new(cli.MockUi) 392 c := &PlanCommand{ 393 Meta: Meta{ 394 ContextOpts: testCtxConfig(p), 395 Ui: ui, 396 }, 397 } 398 399 args := []string{ 400 testFixturePath("plan-vars"), 401 } 402 if code := c.Run(args); code != 0 { 403 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 404 } 405 } 406 407 func TestPlan_varFile(t *testing.T) { 408 varFilePath := testTempFile(t) 409 if err := ioutil.WriteFile(varFilePath, []byte(planVarFile), 0644); err != nil { 410 t.Fatalf("err: %s", err) 411 } 412 413 p := testProvider() 414 ui := new(cli.MockUi) 415 c := &PlanCommand{ 416 Meta: Meta{ 417 ContextOpts: testCtxConfig(p), 418 Ui: ui, 419 }, 420 } 421 422 actual := "" 423 p.DiffFn = func( 424 info *terraform.InstanceInfo, 425 s *terraform.InstanceState, 426 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 427 if v, ok := c.Config["value"]; ok { 428 actual = v.(string) 429 } 430 431 return nil, nil 432 } 433 434 args := []string{ 435 "-var-file", varFilePath, 436 testFixturePath("plan-vars"), 437 } 438 if code := c.Run(args); code != 0 { 439 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 440 } 441 442 if actual != "bar" { 443 t.Fatal("didn't work") 444 } 445 } 446 447 func TestPlan_varFileDefault(t *testing.T) { 448 varFileDir := testTempDir(t) 449 varFilePath := filepath.Join(varFileDir, "terraform.tfvars") 450 if err := ioutil.WriteFile(varFilePath, []byte(planVarFile), 0644); err != nil { 451 t.Fatalf("err: %s", err) 452 } 453 454 cwd, err := os.Getwd() 455 if err != nil { 456 t.Fatalf("err: %s", err) 457 } 458 if err := os.Chdir(varFileDir); err != nil { 459 t.Fatalf("err: %s", err) 460 } 461 defer os.Chdir(cwd) 462 463 p := testProvider() 464 ui := new(cli.MockUi) 465 c := &PlanCommand{ 466 Meta: Meta{ 467 ContextOpts: testCtxConfig(p), 468 Ui: ui, 469 }, 470 } 471 472 actual := "" 473 p.DiffFn = func( 474 info *terraform.InstanceInfo, 475 s *terraform.InstanceState, 476 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { 477 if v, ok := c.Config["value"]; ok { 478 actual = v.(string) 479 } 480 481 return nil, nil 482 } 483 484 args := []string{ 485 testFixturePath("plan-vars"), 486 } 487 if code := c.Run(args); code != 0 { 488 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 489 } 490 491 if actual != "bar" { 492 t.Fatal("didn't work") 493 } 494 } 495 496 func TestPlan_backup(t *testing.T) { 497 // Write out some prior state 498 tf, err := ioutil.TempFile("", "tf") 499 if err != nil { 500 t.Fatalf("err: %s", err) 501 } 502 statePath := tf.Name() 503 defer os.Remove(tf.Name()) 504 505 // Write out some prior state 506 backupf, err := ioutil.TempFile("", "tf") 507 if err != nil { 508 t.Fatalf("err: %s", err) 509 } 510 backupPath := backupf.Name() 511 backupf.Close() 512 os.Remove(backupPath) 513 defer os.Remove(backupPath) 514 515 originalState := &terraform.State{ 516 Modules: []*terraform.ModuleState{ 517 &terraform.ModuleState{ 518 Path: []string{"root"}, 519 Resources: map[string]*terraform.ResourceState{ 520 "test_instance.foo": &terraform.ResourceState{ 521 Type: "test_instance", 522 Primary: &terraform.InstanceState{ 523 ID: "bar", 524 }, 525 }, 526 }, 527 }, 528 }, 529 } 530 531 err = terraform.WriteState(originalState, tf) 532 tf.Close() 533 if err != nil { 534 t.Fatalf("err: %s", err) 535 } 536 537 p := testProvider() 538 ui := new(cli.MockUi) 539 c := &PlanCommand{ 540 Meta: Meta{ 541 ContextOpts: testCtxConfig(p), 542 Ui: ui, 543 }, 544 } 545 546 args := []string{ 547 "-state", statePath, 548 "-backup", backupPath, 549 testFixturePath("plan"), 550 } 551 if code := c.Run(args); code != 0 { 552 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 553 } 554 555 // Verify that the provider was called with the existing state 556 actual := strings.TrimSpace(p.DiffState.String()) 557 expected := strings.TrimSpace(testPlanBackupStr) 558 if actual != expected { 559 t.Fatalf("bad:\n\n%s", actual) 560 } 561 562 // Verify the backup exist 563 f, err := os.Open(backupPath) 564 if err != nil { 565 t.Fatalf("err: %s", err) 566 } 567 568 backupState, err := terraform.ReadState(f) 569 f.Close() 570 if err != nil { 571 t.Fatalf("err: %s", err) 572 } 573 574 actualStr := strings.TrimSpace(backupState.String()) 575 expectedStr := strings.TrimSpace(originalState.String()) 576 if actualStr != expectedStr { 577 t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr) 578 } 579 } 580 581 func TestPlan_disableBackup(t *testing.T) { 582 // Write out some prior state 583 tf, err := ioutil.TempFile("", "tf") 584 if err != nil { 585 t.Fatalf("err: %s", err) 586 } 587 statePath := tf.Name() 588 defer os.Remove(tf.Name()) 589 590 originalState := &terraform.State{ 591 Modules: []*terraform.ModuleState{ 592 &terraform.ModuleState{ 593 Path: []string{"root"}, 594 Resources: map[string]*terraform.ResourceState{ 595 "test_instance.foo": &terraform.ResourceState{ 596 Type: "test_instance", 597 Primary: &terraform.InstanceState{ 598 ID: "bar", 599 }, 600 }, 601 }, 602 }, 603 }, 604 } 605 606 err = terraform.WriteState(originalState, tf) 607 tf.Close() 608 if err != nil { 609 t.Fatalf("err: %s", err) 610 } 611 612 p := testProvider() 613 ui := new(cli.MockUi) 614 c := &PlanCommand{ 615 Meta: Meta{ 616 ContextOpts: testCtxConfig(p), 617 Ui: ui, 618 }, 619 } 620 621 args := []string{ 622 "-state", statePath, 623 "-backup", "-", 624 testFixturePath("plan"), 625 } 626 if code := c.Run(args); code != 0 { 627 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 628 } 629 630 // Verify that the provider was called with the existing state 631 actual := strings.TrimSpace(p.DiffState.String()) 632 expected := strings.TrimSpace(testPlanDisableBackupStr) 633 if actual != expected { 634 t.Fatalf("bad:\n\n%s", actual) 635 } 636 637 // Ensure there is no backup 638 _, err = os.Stat(statePath + DefaultBackupExtension) 639 if err == nil || !os.IsNotExist(err) { 640 t.Fatalf("backup should not exist") 641 } 642 } 643 644 func TestPlan_detailedExitcode(t *testing.T) { 645 cwd, err := os.Getwd() 646 if err != nil { 647 t.Fatalf("err: %s", err) 648 } 649 if err := os.Chdir(testFixturePath("plan")); err != nil { 650 t.Fatalf("err: %s", err) 651 } 652 defer os.Chdir(cwd) 653 654 p := testProvider() 655 ui := new(cli.MockUi) 656 c := &PlanCommand{ 657 Meta: Meta{ 658 ContextOpts: testCtxConfig(p), 659 Ui: ui, 660 }, 661 } 662 663 args := []string{"-detailed-exitcode"} 664 if code := c.Run(args); code != 2 { 665 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 666 } 667 } 668 669 func TestPlan_detailedExitcode_emptyDiff(t *testing.T) { 670 cwd, err := os.Getwd() 671 if err != nil { 672 t.Fatalf("err: %s", err) 673 } 674 if err := os.Chdir(testFixturePath("plan-emptydiff")); err != nil { 675 t.Fatalf("err: %s", err) 676 } 677 defer os.Chdir(cwd) 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{"-detailed-exitcode"} 689 if code := c.Run(args); code != 0 { 690 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 691 } 692 } 693 694 const planVarFile = ` 695 foo = "bar" 696 ` 697 698 const testPlanBackupStr = ` 699 ID = bar 700 ` 701 702 const testPlanDisableBackupStr = ` 703 ID = bar 704 ` 705 706 const testPlanNoStateStr = ` 707 <not created> 708 ` 709 710 const testPlanStateStr = ` 711 ID = bar 712 ` 713 714 const testPlanStateDefaultStr = ` 715 ID = bar 716 `