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