github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/command/show_test.go (about) 1 package command 2 3 import ( 4 "encoding/json" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 "strings" 9 "testing" 10 11 "github.com/google/go-cmp/cmp" 12 "github.com/hashicorp/terraform/internal/addrs" 13 "github.com/hashicorp/terraform/internal/configs/configschema" 14 "github.com/hashicorp/terraform/internal/plans" 15 "github.com/hashicorp/terraform/internal/providers" 16 "github.com/hashicorp/terraform/internal/states" 17 "github.com/hashicorp/terraform/internal/states/statemgr" 18 "github.com/hashicorp/terraform/internal/terraform" 19 "github.com/hashicorp/terraform/version" 20 "github.com/mitchellh/cli" 21 "github.com/zclconf/go-cty/cty" 22 ) 23 24 func TestShow_badArgs(t *testing.T) { 25 view, done := testView(t) 26 c := &ShowCommand{ 27 Meta: Meta{ 28 testingOverrides: metaOverridesForProvider(testProvider()), 29 View: view, 30 }, 31 } 32 33 args := []string{ 34 "bad", 35 "bad", 36 "-no-color", 37 } 38 39 code := c.Run(args) 40 output := done(t) 41 42 if code != 1 { 43 t.Fatalf("unexpected exit status %d; want 1\ngot: %s", code, output.Stdout()) 44 } 45 } 46 47 func TestShow_noArgsNoState(t *testing.T) { 48 view, done := testView(t) 49 c := &ShowCommand{ 50 Meta: Meta{ 51 testingOverrides: metaOverridesForProvider(testProvider()), 52 View: view, 53 }, 54 } 55 56 code := c.Run([]string{}) 57 output := done(t) 58 59 if code != 0 { 60 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, output.Stderr()) 61 } 62 63 got := output.Stdout() 64 want := `No state.` 65 if !strings.Contains(got, want) { 66 t.Fatalf("unexpected output\ngot: %s\nwant: %s", got, want) 67 } 68 } 69 70 func TestShow_noArgsWithState(t *testing.T) { 71 // Get a temp cwd 72 testCwd(t) 73 // Create the default state 74 testStateFileDefault(t, testState()) 75 76 view, done := testView(t) 77 c := &ShowCommand{ 78 Meta: Meta{ 79 testingOverrides: metaOverridesForProvider(testProvider()), 80 View: view, 81 }, 82 } 83 84 code := c.Run([]string{}) 85 output := done(t) 86 87 if code != 0 { 88 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, output.Stderr()) 89 } 90 91 got := output.Stdout() 92 want := `# test_instance.foo:` 93 if !strings.Contains(got, want) { 94 t.Fatalf("unexpected output\ngot: %s\nwant: %s", got, want) 95 } 96 } 97 98 func TestShow_argsWithState(t *testing.T) { 99 // Create the default state 100 statePath := testStateFile(t, testState()) 101 stateDir := filepath.Dir(statePath) 102 defer os.RemoveAll(stateDir) 103 defer testChdir(t, stateDir)() 104 105 view, done := testView(t) 106 c := &ShowCommand{ 107 Meta: Meta{ 108 testingOverrides: metaOverridesForProvider(testProvider()), 109 View: view, 110 }, 111 } 112 113 path := filepath.Base(statePath) 114 args := []string{ 115 path, 116 "-no-color", 117 } 118 code := c.Run(args) 119 output := done(t) 120 121 if code != 0 { 122 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, output.Stderr()) 123 } 124 } 125 126 // https://github.com/hashicorp/terraform/issues/21462 127 func TestShow_argsWithStateAliasedProvider(t *testing.T) { 128 // Create the default state with aliased resource 129 testState := states.BuildState(func(s *states.SyncState) { 130 s.SetResourceInstanceCurrent( 131 addrs.Resource{ 132 Mode: addrs.ManagedResourceMode, 133 Type: "test_instance", 134 Name: "foo", 135 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 136 &states.ResourceInstanceObjectSrc{ 137 // The weird whitespace here is reflective of how this would 138 // get written out in a real state file, due to the indentation 139 // of all of the containing wrapping objects and arrays. 140 AttrsJSON: []byte("{\n \"id\": \"bar\"\n }"), 141 Status: states.ObjectReady, 142 Dependencies: []addrs.ConfigResource{}, 143 }, 144 addrs.RootModuleInstance.ProviderConfigAliased(addrs.NewDefaultProvider("test"), "alias"), 145 ) 146 }) 147 148 statePath := testStateFile(t, testState) 149 stateDir := filepath.Dir(statePath) 150 defer os.RemoveAll(stateDir) 151 defer testChdir(t, stateDir)() 152 153 view, done := testView(t) 154 c := &ShowCommand{ 155 Meta: Meta{ 156 testingOverrides: metaOverridesForProvider(testProvider()), 157 View: view, 158 }, 159 } 160 161 path := filepath.Base(statePath) 162 args := []string{ 163 path, 164 "-no-color", 165 } 166 code := c.Run(args) 167 output := done(t) 168 169 if code != 0 { 170 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, output.Stderr()) 171 } 172 173 got := output.Stdout() 174 want := `# missing schema for provider \"test.alias\"` 175 if strings.Contains(got, want) { 176 t.Fatalf("unexpected output\ngot: %s", got) 177 } 178 } 179 180 func TestShow_argsPlanFileDoesNotExist(t *testing.T) { 181 view, done := testView(t) 182 c := &ShowCommand{ 183 Meta: Meta{ 184 testingOverrides: metaOverridesForProvider(testProvider()), 185 View: view, 186 }, 187 } 188 189 args := []string{ 190 "doesNotExist.tfplan", 191 "-no-color", 192 } 193 code := c.Run(args) 194 output := done(t) 195 196 if code != 1 { 197 t.Fatalf("unexpected exit status %d; want 1\ngot: %s", code, output.Stdout()) 198 } 199 200 got := output.Stderr() 201 want := `Plan read error: open doesNotExist.tfplan:` 202 if !strings.Contains(got, want) { 203 t.Errorf("unexpected output\ngot: %s\nwant:\n%s", got, want) 204 } 205 } 206 207 func TestShow_argsStatefileDoesNotExist(t *testing.T) { 208 view, done := testView(t) 209 c := &ShowCommand{ 210 Meta: Meta{ 211 testingOverrides: metaOverridesForProvider(testProvider()), 212 View: view, 213 }, 214 } 215 216 args := []string{ 217 "doesNotExist.tfstate", 218 "-no-color", 219 } 220 code := c.Run(args) 221 output := done(t) 222 223 if code != 1 { 224 t.Fatalf("unexpected exit status %d; want 1\ngot: %s", code, output.Stdout()) 225 } 226 227 got := output.Stderr() 228 want := `State read error: Error loading statefile:` 229 if !strings.Contains(got, want) { 230 t.Errorf("unexpected output\ngot: %s\nwant:\n%s", got, want) 231 } 232 } 233 234 func TestShow_json_argsPlanFileDoesNotExist(t *testing.T) { 235 view, done := testView(t) 236 c := &ShowCommand{ 237 Meta: Meta{ 238 testingOverrides: metaOverridesForProvider(testProvider()), 239 View: view, 240 }, 241 } 242 243 args := []string{ 244 "-json", 245 "doesNotExist.tfplan", 246 "-no-color", 247 } 248 code := c.Run(args) 249 output := done(t) 250 251 if code != 1 { 252 t.Fatalf("unexpected exit status %d; want 1\ngot: %s", code, output.Stdout()) 253 } 254 255 got := output.Stderr() 256 want := `Plan read error: open doesNotExist.tfplan:` 257 if !strings.Contains(got, want) { 258 t.Errorf("unexpected output\ngot: %s\nwant:\n%s", got, want) 259 } 260 } 261 262 func TestShow_json_argsStatefileDoesNotExist(t *testing.T) { 263 view, done := testView(t) 264 c := &ShowCommand{ 265 Meta: Meta{ 266 testingOverrides: metaOverridesForProvider(testProvider()), 267 View: view, 268 }, 269 } 270 271 args := []string{ 272 "-json", 273 "doesNotExist.tfstate", 274 "-no-color", 275 } 276 code := c.Run(args) 277 output := done(t) 278 279 if code != 1 { 280 t.Fatalf("unexpected exit status %d; want 1\ngot: %s", code, output.Stdout()) 281 } 282 283 got := output.Stderr() 284 want := `State read error: Error loading statefile:` 285 if !strings.Contains(got, want) { 286 t.Errorf("unexpected output\ngot: %s\nwant:\n%s", got, want) 287 } 288 } 289 290 func TestShow_planNoop(t *testing.T) { 291 planPath := testPlanFileNoop(t) 292 293 view, done := testView(t) 294 c := &ShowCommand{ 295 Meta: Meta{ 296 testingOverrides: metaOverridesForProvider(testProvider()), 297 View: view, 298 }, 299 } 300 301 args := []string{ 302 planPath, 303 "-no-color", 304 } 305 code := c.Run(args) 306 output := done(t) 307 308 if code != 0 { 309 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, output.Stderr()) 310 } 311 312 got := output.Stdout() 313 want := `No changes. Your infrastructure matches the configuration.` 314 if !strings.Contains(got, want) { 315 t.Errorf("unexpected output\ngot: %s\nwant:\n%s", got, want) 316 } 317 } 318 319 func TestShow_planWithChanges(t *testing.T) { 320 planPathWithChanges := showFixturePlanFile(t, plans.DeleteThenCreate) 321 322 view, done := testView(t) 323 c := &ShowCommand{ 324 Meta: Meta{ 325 testingOverrides: metaOverridesForProvider(showFixtureProvider()), 326 View: view, 327 }, 328 } 329 330 args := []string{ 331 planPathWithChanges, 332 "-no-color", 333 } 334 code := c.Run(args) 335 output := done(t) 336 337 if code != 0 { 338 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, output.Stderr()) 339 } 340 341 got := output.Stdout() 342 want := `test_instance.foo must be replaced` 343 if !strings.Contains(got, want) { 344 t.Fatalf("unexpected output\ngot: %s\nwant: %s", got, want) 345 } 346 } 347 348 func TestShow_planWithForceReplaceChange(t *testing.T) { 349 // The main goal of this test is to see that the "replace by request" 350 // resource instance action reason can round-trip through a plan file and 351 // be reflected correctly in the "terraform show" output, the same way 352 // as it would appear in "terraform plan" output. 353 354 _, snap := testModuleWithSnapshot(t, "show") 355 plannedVal := cty.ObjectVal(map[string]cty.Value{ 356 "id": cty.UnknownVal(cty.String), 357 "ami": cty.StringVal("bar"), 358 }) 359 priorValRaw, err := plans.NewDynamicValue(cty.NullVal(plannedVal.Type()), plannedVal.Type()) 360 if err != nil { 361 t.Fatal(err) 362 } 363 plannedValRaw, err := plans.NewDynamicValue(plannedVal, plannedVal.Type()) 364 if err != nil { 365 t.Fatal(err) 366 } 367 plan := testPlan(t) 368 plan.Changes.SyncWrapper().AppendResourceInstanceChange(&plans.ResourceInstanceChangeSrc{ 369 Addr: addrs.Resource{ 370 Mode: addrs.ManagedResourceMode, 371 Type: "test_instance", 372 Name: "foo", 373 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 374 ProviderAddr: addrs.AbsProviderConfig{ 375 Provider: addrs.NewDefaultProvider("test"), 376 Module: addrs.RootModule, 377 }, 378 ChangeSrc: plans.ChangeSrc{ 379 Action: plans.CreateThenDelete, 380 Before: priorValRaw, 381 After: plannedValRaw, 382 }, 383 ActionReason: plans.ResourceInstanceReplaceByRequest, 384 }) 385 planFilePath := testPlanFile( 386 t, 387 snap, 388 states.NewState(), 389 plan, 390 ) 391 392 view, done := testView(t) 393 c := &ShowCommand{ 394 Meta: Meta{ 395 testingOverrides: metaOverridesForProvider(showFixtureProvider()), 396 View: view, 397 }, 398 } 399 400 args := []string{ 401 planFilePath, 402 "-no-color", 403 } 404 code := c.Run(args) 405 output := done(t) 406 407 if code != 0 { 408 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, output.Stderr()) 409 } 410 411 got := output.Stdout() 412 want := `test_instance.foo will be replaced, as requested` 413 if !strings.Contains(got, want) { 414 t.Fatalf("unexpected output\ngot: %s\nwant: %s", got, want) 415 } 416 417 want = `Plan: 1 to add, 0 to change, 1 to destroy.` 418 if !strings.Contains(got, want) { 419 t.Fatalf("unexpected output\ngot: %s\nwant: %s", got, want) 420 } 421 422 } 423 424 func TestShow_plan_json(t *testing.T) { 425 planPath := showFixturePlanFile(t, plans.Create) 426 427 view, done := testView(t) 428 c := &ShowCommand{ 429 Meta: Meta{ 430 testingOverrides: metaOverridesForProvider(showFixtureProvider()), 431 View: view, 432 }, 433 } 434 435 args := []string{ 436 "-json", 437 planPath, 438 "-no-color", 439 } 440 code := c.Run(args) 441 output := done(t) 442 443 if code != 0 { 444 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, output.Stderr()) 445 } 446 } 447 448 func TestShow_state(t *testing.T) { 449 originalState := testState() 450 statePath := testStateFile(t, originalState) 451 defer os.RemoveAll(filepath.Dir(statePath)) 452 453 view, done := testView(t) 454 c := &ShowCommand{ 455 Meta: Meta{ 456 testingOverrides: metaOverridesForProvider(testProvider()), 457 View: view, 458 }, 459 } 460 461 args := []string{ 462 statePath, 463 "-no-color", 464 } 465 code := c.Run(args) 466 output := done(t) 467 468 if code != 0 { 469 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, output.Stderr()) 470 } 471 } 472 473 func TestShow_json_output(t *testing.T) { 474 fixtureDir := "testdata/show-json" 475 testDirs, err := ioutil.ReadDir(fixtureDir) 476 if err != nil { 477 t.Fatal(err) 478 } 479 480 for _, entry := range testDirs { 481 if !entry.IsDir() { 482 continue 483 } 484 485 t.Run(entry.Name(), func(t *testing.T) { 486 td := t.TempDir() 487 inputDir := filepath.Join(fixtureDir, entry.Name()) 488 testCopyDir(t, inputDir, td) 489 defer testChdir(t, td)() 490 491 expectError := strings.Contains(entry.Name(), "error") 492 493 providerSource, close := newMockProviderSource(t, map[string][]string{ 494 "test": {"1.2.3"}, 495 "hashicorp2/test": {"1.2.3"}, 496 }) 497 defer close() 498 499 p := showFixtureProvider() 500 501 // init 502 ui := new(cli.MockUi) 503 ic := &InitCommand{ 504 Meta: Meta{ 505 testingOverrides: metaOverridesForProvider(p), 506 Ui: ui, 507 ProviderSource: providerSource, 508 }, 509 } 510 if code := ic.Run([]string{}); code != 0 { 511 if expectError { 512 // this should error, but not panic. 513 return 514 } 515 t.Fatalf("init failed\n%s", ui.ErrorWriter) 516 } 517 518 // plan 519 planView, planDone := testView(t) 520 pc := &PlanCommand{ 521 Meta: Meta{ 522 testingOverrides: metaOverridesForProvider(p), 523 View: planView, 524 ProviderSource: providerSource, 525 }, 526 } 527 528 args := []string{ 529 "-out=terraform.plan", 530 } 531 532 code := pc.Run(args) 533 planOutput := planDone(t) 534 535 if code != 0 { 536 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, planOutput.Stderr()) 537 } 538 539 // show 540 showView, showDone := testView(t) 541 sc := &ShowCommand{ 542 Meta: Meta{ 543 testingOverrides: metaOverridesForProvider(p), 544 View: showView, 545 ProviderSource: providerSource, 546 }, 547 } 548 549 args = []string{ 550 "-json", 551 "terraform.plan", 552 } 553 defer os.Remove("terraform.plan") 554 code = sc.Run(args) 555 showOutput := showDone(t) 556 557 if code != 0 { 558 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, showOutput.Stderr()) 559 } 560 561 // compare view output to wanted output 562 var got, want plan 563 564 gotString := showOutput.Stdout() 565 json.Unmarshal([]byte(gotString), &got) 566 567 wantFile, err := os.Open("output.json") 568 if err != nil { 569 t.Fatalf("unexpected err: %s", err) 570 } 571 defer wantFile.Close() 572 byteValue, err := ioutil.ReadAll(wantFile) 573 if err != nil { 574 t.Fatalf("unexpected err: %s", err) 575 } 576 json.Unmarshal([]byte(byteValue), &want) 577 578 // Disregard format version to reduce needless test fixture churn 579 want.FormatVersion = got.FormatVersion 580 581 if !cmp.Equal(got, want) { 582 t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want)) 583 } 584 }) 585 } 586 } 587 588 func TestShow_json_output_sensitive(t *testing.T) { 589 td := t.TempDir() 590 inputDir := "testdata/show-json-sensitive" 591 testCopyDir(t, inputDir, td) 592 defer testChdir(t, td)() 593 594 providerSource, close := newMockProviderSource(t, map[string][]string{"test": {"1.2.3"}}) 595 defer close() 596 597 p := showFixtureSensitiveProvider() 598 599 // init 600 ui := new(cli.MockUi) 601 ic := &InitCommand{ 602 Meta: Meta{ 603 testingOverrides: metaOverridesForProvider(p), 604 Ui: ui, 605 ProviderSource: providerSource, 606 }, 607 } 608 if code := ic.Run([]string{}); code != 0 { 609 t.Fatalf("init failed\n%s", ui.ErrorWriter) 610 } 611 612 // plan 613 planView, planDone := testView(t) 614 pc := &PlanCommand{ 615 Meta: Meta{ 616 testingOverrides: metaOverridesForProvider(p), 617 View: planView, 618 ProviderSource: providerSource, 619 }, 620 } 621 622 args := []string{ 623 "-out=terraform.plan", 624 } 625 code := pc.Run(args) 626 planOutput := planDone(t) 627 628 if code != 0 { 629 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, planOutput.Stderr()) 630 } 631 632 // show 633 showView, showDone := testView(t) 634 sc := &ShowCommand{ 635 Meta: Meta{ 636 testingOverrides: metaOverridesForProvider(p), 637 View: showView, 638 ProviderSource: providerSource, 639 }, 640 } 641 642 args = []string{ 643 "-json", 644 "terraform.plan", 645 } 646 defer os.Remove("terraform.plan") 647 code = sc.Run(args) 648 showOutput := showDone(t) 649 650 if code != 0 { 651 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, showOutput.Stderr()) 652 } 653 654 // compare ui output to wanted output 655 var got, want plan 656 657 gotString := showOutput.Stdout() 658 json.Unmarshal([]byte(gotString), &got) 659 660 wantFile, err := os.Open("output.json") 661 if err != nil { 662 t.Fatalf("unexpected err: %s", err) 663 } 664 defer wantFile.Close() 665 byteValue, err := ioutil.ReadAll(wantFile) 666 if err != nil { 667 t.Fatalf("unexpected err: %s", err) 668 } 669 json.Unmarshal([]byte(byteValue), &want) 670 671 // Disregard format version to reduce needless test fixture churn 672 want.FormatVersion = got.FormatVersion 673 674 if !cmp.Equal(got, want) { 675 t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want)) 676 } 677 } 678 679 // Failing conditions are only present in JSON output for refresh-only plans, 680 // so we test that separately here. 681 func TestShow_json_output_conditions_refresh_only(t *testing.T) { 682 td := t.TempDir() 683 inputDir := "testdata/show-json/conditions" 684 testCopyDir(t, inputDir, td) 685 defer testChdir(t, td)() 686 687 providerSource, close := newMockProviderSource(t, map[string][]string{"test": {"1.2.3"}}) 688 defer close() 689 690 p := showFixtureSensitiveProvider() 691 692 // init 693 ui := new(cli.MockUi) 694 ic := &InitCommand{ 695 Meta: Meta{ 696 testingOverrides: metaOverridesForProvider(p), 697 Ui: ui, 698 ProviderSource: providerSource, 699 }, 700 } 701 if code := ic.Run([]string{}); code != 0 { 702 t.Fatalf("init failed\n%s", ui.ErrorWriter) 703 } 704 705 // plan 706 planView, planDone := testView(t) 707 pc := &PlanCommand{ 708 Meta: Meta{ 709 testingOverrides: metaOverridesForProvider(p), 710 View: planView, 711 ProviderSource: providerSource, 712 }, 713 } 714 715 args := []string{ 716 "-refresh-only", 717 "-out=terraform.plan", 718 "-var=ami=bad-ami", 719 "-state=for-refresh.tfstate", 720 } 721 code := pc.Run(args) 722 planOutput := planDone(t) 723 724 if code != 0 { 725 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, planOutput.Stderr()) 726 } 727 728 // show 729 showView, showDone := testView(t) 730 sc := &ShowCommand{ 731 Meta: Meta{ 732 testingOverrides: metaOverridesForProvider(p), 733 View: showView, 734 ProviderSource: providerSource, 735 }, 736 } 737 738 args = []string{ 739 "-json", 740 "terraform.plan", 741 } 742 defer os.Remove("terraform.plan") 743 code = sc.Run(args) 744 showOutput := showDone(t) 745 746 if code != 0 { 747 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, showOutput.Stderr()) 748 } 749 750 // compare JSON output to wanted output 751 var got, want plan 752 753 gotString := showOutput.Stdout() 754 json.Unmarshal([]byte(gotString), &got) 755 756 wantFile, err := os.Open("output-refresh-only.json") 757 if err != nil { 758 t.Fatalf("unexpected err: %s", err) 759 } 760 defer wantFile.Close() 761 byteValue, err := ioutil.ReadAll(wantFile) 762 if err != nil { 763 t.Fatalf("unexpected err: %s", err) 764 } 765 json.Unmarshal([]byte(byteValue), &want) 766 767 // Disregard format version to reduce needless test fixture churn 768 want.FormatVersion = got.FormatVersion 769 770 if !cmp.Equal(got, want) { 771 t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want)) 772 } 773 } 774 775 // similar test as above, without the plan 776 func TestShow_json_output_state(t *testing.T) { 777 fixtureDir := "testdata/show-json-state" 778 testDirs, err := ioutil.ReadDir(fixtureDir) 779 if err != nil { 780 t.Fatal(err) 781 } 782 783 for _, entry := range testDirs { 784 if !entry.IsDir() { 785 continue 786 } 787 788 t.Run(entry.Name(), func(t *testing.T) { 789 td := t.TempDir() 790 inputDir := filepath.Join(fixtureDir, entry.Name()) 791 testCopyDir(t, inputDir, td) 792 defer testChdir(t, td)() 793 794 providerSource, close := newMockProviderSource(t, map[string][]string{ 795 "test": {"1.2.3"}, 796 }) 797 defer close() 798 799 p := showFixtureProvider() 800 801 // init 802 ui := new(cli.MockUi) 803 ic := &InitCommand{ 804 Meta: Meta{ 805 testingOverrides: metaOverridesForProvider(p), 806 Ui: ui, 807 ProviderSource: providerSource, 808 }, 809 } 810 if code := ic.Run([]string{}); code != 0 { 811 t.Fatalf("init failed\n%s", ui.ErrorWriter) 812 } 813 814 // show 815 showView, showDone := testView(t) 816 sc := &ShowCommand{ 817 Meta: Meta{ 818 testingOverrides: metaOverridesForProvider(p), 819 View: showView, 820 ProviderSource: providerSource, 821 }, 822 } 823 824 code := sc.Run([]string{"-json"}) 825 showOutput := showDone(t) 826 827 if code != 0 { 828 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, showOutput.Stderr()) 829 } 830 831 // compare ui output to wanted output 832 type state struct { 833 FormatVersion string `json:"format_version,omitempty"` 834 TerraformVersion string `json:"terraform_version"` 835 Values map[string]interface{} `json:"values,omitempty"` 836 SensitiveValues map[string]bool `json:"sensitive_values,omitempty"` 837 } 838 var got, want state 839 840 gotString := showOutput.Stdout() 841 json.Unmarshal([]byte(gotString), &got) 842 843 wantFile, err := os.Open("output.json") 844 if err != nil { 845 t.Fatalf("unexpected error: %s", err) 846 } 847 defer wantFile.Close() 848 byteValue, err := ioutil.ReadAll(wantFile) 849 if err != nil { 850 t.Fatalf("unexpected err: %s", err) 851 } 852 json.Unmarshal([]byte(byteValue), &want) 853 854 if !cmp.Equal(got, want) { 855 t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want)) 856 } 857 }) 858 } 859 } 860 861 func TestShow_planWithNonDefaultStateLineage(t *testing.T) { 862 // Create a temporary working directory that is empty 863 td := t.TempDir() 864 testCopyDir(t, testFixturePath("show"), td) 865 defer testChdir(t, td)() 866 867 // Write default state file with a testing lineage ("fake-for-testing") 868 testStateFileDefault(t, testState()) 869 870 // Create a plan with a different lineage, which we should still be able 871 // to show 872 _, snap := testModuleWithSnapshot(t, "show") 873 state := testState() 874 plan := testPlan(t) 875 stateMeta := statemgr.SnapshotMeta{ 876 Lineage: "fake-for-plan", 877 Serial: 1, 878 TerraformVersion: version.SemVer, 879 } 880 planPath := testPlanFileMatchState(t, snap, state, plan, stateMeta) 881 882 view, done := testView(t) 883 c := &ShowCommand{ 884 Meta: Meta{ 885 testingOverrides: metaOverridesForProvider(testProvider()), 886 View: view, 887 }, 888 } 889 890 args := []string{ 891 planPath, 892 "-no-color", 893 } 894 code := c.Run(args) 895 output := done(t) 896 897 if code != 0 { 898 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, output.Stderr()) 899 } 900 901 got := output.Stdout() 902 want := `No changes. Your infrastructure matches the configuration.` 903 if !strings.Contains(got, want) { 904 t.Fatalf("unexpected output\ngot: %s\nwant: %s", got, want) 905 } 906 } 907 908 func TestShow_corruptStatefile(t *testing.T) { 909 td := t.TempDir() 910 inputDir := "testdata/show-corrupt-statefile" 911 testCopyDir(t, inputDir, td) 912 defer testChdir(t, td)() 913 914 view, done := testView(t) 915 c := &ShowCommand{ 916 Meta: Meta{ 917 testingOverrides: metaOverridesForProvider(testProvider()), 918 View: view, 919 }, 920 } 921 922 code := c.Run([]string{}) 923 output := done(t) 924 925 if code != 1 { 926 t.Fatalf("unexpected exit status %d; want 1\ngot: %s", code, output.Stdout()) 927 } 928 929 got := output.Stderr() 930 want := `Unsupported state file format` 931 if !strings.Contains(got, want) { 932 t.Errorf("unexpected output\ngot: %s\nwant:\n%s", got, want) 933 } 934 } 935 936 // showFixtureSchema returns a schema suitable for processing the configuration 937 // in testdata/show. This schema should be assigned to a mock provider 938 // named "test". 939 func showFixtureSchema() *providers.GetProviderSchemaResponse { 940 return &providers.GetProviderSchemaResponse{ 941 Provider: providers.Schema{ 942 Block: &configschema.Block{ 943 Attributes: map[string]*configschema.Attribute{ 944 "region": {Type: cty.String, Optional: true}, 945 }, 946 }, 947 }, 948 ResourceTypes: map[string]providers.Schema{ 949 "test_instance": { 950 Block: &configschema.Block{ 951 Attributes: map[string]*configschema.Attribute{ 952 "id": {Type: cty.String, Optional: true, Computed: true}, 953 "ami": {Type: cty.String, Optional: true}, 954 }, 955 }, 956 }, 957 }, 958 } 959 } 960 961 // showFixtureSensitiveSchema returns a schema suitable for processing the configuration 962 // in testdata/show. This schema should be assigned to a mock provider 963 // named "test". It includes a sensitive attribute. 964 func showFixtureSensitiveSchema() *providers.GetProviderSchemaResponse { 965 return &providers.GetProviderSchemaResponse{ 966 Provider: providers.Schema{ 967 Block: &configschema.Block{ 968 Attributes: map[string]*configschema.Attribute{ 969 "region": {Type: cty.String, Optional: true}, 970 }, 971 }, 972 }, 973 ResourceTypes: map[string]providers.Schema{ 974 "test_instance": { 975 Block: &configschema.Block{ 976 Attributes: map[string]*configschema.Attribute{ 977 "id": {Type: cty.String, Optional: true, Computed: true}, 978 "ami": {Type: cty.String, Optional: true}, 979 "password": {Type: cty.String, Optional: true, Sensitive: true}, 980 }, 981 }, 982 }, 983 }, 984 } 985 } 986 987 // showFixtureProvider returns a mock provider that is configured for basic 988 // operation with the configuration in testdata/show. This mock has 989 // GetSchemaResponse, PlanResourceChangeFn, and ApplyResourceChangeFn populated, 990 // with the plan/apply steps just passing through the data determined by 991 // Terraform Core. 992 func showFixtureProvider() *terraform.MockProvider { 993 p := testProvider() 994 p.GetProviderSchemaResponse = showFixtureSchema() 995 p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { 996 idVal := req.PriorState.GetAttr("id") 997 amiVal := req.PriorState.GetAttr("ami") 998 if amiVal.RawEquals(cty.StringVal("refresh-me")) { 999 amiVal = cty.StringVal("refreshed") 1000 } 1001 return providers.ReadResourceResponse{ 1002 NewState: cty.ObjectVal(map[string]cty.Value{ 1003 "id": idVal, 1004 "ami": amiVal, 1005 }), 1006 Private: req.Private, 1007 } 1008 } 1009 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 1010 // this is a destroy plan, 1011 if req.ProposedNewState.IsNull() { 1012 resp.PlannedState = req.ProposedNewState 1013 resp.PlannedPrivate = req.PriorPrivate 1014 return resp 1015 } 1016 1017 idVal := req.ProposedNewState.GetAttr("id") 1018 amiVal := req.ProposedNewState.GetAttr("ami") 1019 if idVal.IsNull() { 1020 idVal = cty.UnknownVal(cty.String) 1021 } 1022 var reqRep []cty.Path 1023 if amiVal.RawEquals(cty.StringVal("force-replace")) { 1024 reqRep = append(reqRep, cty.GetAttrPath("ami")) 1025 } 1026 return providers.PlanResourceChangeResponse{ 1027 PlannedState: cty.ObjectVal(map[string]cty.Value{ 1028 "id": idVal, 1029 "ami": amiVal, 1030 }), 1031 RequiresReplace: reqRep, 1032 } 1033 } 1034 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 1035 idVal := req.PlannedState.GetAttr("id") 1036 amiVal := req.PlannedState.GetAttr("ami") 1037 if !idVal.IsKnown() { 1038 idVal = cty.StringVal("placeholder") 1039 } 1040 return providers.ApplyResourceChangeResponse{ 1041 NewState: cty.ObjectVal(map[string]cty.Value{ 1042 "id": idVal, 1043 "ami": amiVal, 1044 }), 1045 } 1046 } 1047 return p 1048 } 1049 1050 // showFixtureSensitiveProvider returns a mock provider that is configured for basic 1051 // operation with the configuration in testdata/show. This mock has 1052 // GetSchemaResponse, PlanResourceChangeFn, and ApplyResourceChangeFn populated, 1053 // with the plan/apply steps just passing through the data determined by 1054 // Terraform Core. It also has a sensitive attribute in the provider schema. 1055 func showFixtureSensitiveProvider() *terraform.MockProvider { 1056 p := testProvider() 1057 p.GetProviderSchemaResponse = showFixtureSensitiveSchema() 1058 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 1059 idVal := req.ProposedNewState.GetAttr("id") 1060 if idVal.IsNull() { 1061 idVal = cty.UnknownVal(cty.String) 1062 } 1063 return providers.PlanResourceChangeResponse{ 1064 PlannedState: cty.ObjectVal(map[string]cty.Value{ 1065 "id": idVal, 1066 "ami": req.ProposedNewState.GetAttr("ami"), 1067 "password": req.ProposedNewState.GetAttr("password"), 1068 }), 1069 } 1070 } 1071 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 1072 idVal := req.PlannedState.GetAttr("id") 1073 if !idVal.IsKnown() { 1074 idVal = cty.StringVal("placeholder") 1075 } 1076 return providers.ApplyResourceChangeResponse{ 1077 NewState: cty.ObjectVal(map[string]cty.Value{ 1078 "id": idVal, 1079 "ami": req.PlannedState.GetAttr("ami"), 1080 "password": req.PlannedState.GetAttr("password"), 1081 }), 1082 } 1083 } 1084 return p 1085 } 1086 1087 // showFixturePlanFile creates a plan file at a temporary location containing a 1088 // single change to create or update the test_instance.foo that is included in the "show" 1089 // test fixture, returning the location of that plan file. 1090 // `action` is the planned change you would like to elicit 1091 func showFixturePlanFile(t *testing.T, action plans.Action) string { 1092 _, snap := testModuleWithSnapshot(t, "show") 1093 plannedVal := cty.ObjectVal(map[string]cty.Value{ 1094 "id": cty.UnknownVal(cty.String), 1095 "ami": cty.StringVal("bar"), 1096 }) 1097 priorValRaw, err := plans.NewDynamicValue(cty.NullVal(plannedVal.Type()), plannedVal.Type()) 1098 if err != nil { 1099 t.Fatal(err) 1100 } 1101 plannedValRaw, err := plans.NewDynamicValue(plannedVal, plannedVal.Type()) 1102 if err != nil { 1103 t.Fatal(err) 1104 } 1105 plan := testPlan(t) 1106 plan.Changes.SyncWrapper().AppendResourceInstanceChange(&plans.ResourceInstanceChangeSrc{ 1107 Addr: addrs.Resource{ 1108 Mode: addrs.ManagedResourceMode, 1109 Type: "test_instance", 1110 Name: "foo", 1111 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 1112 ProviderAddr: addrs.AbsProviderConfig{ 1113 Provider: addrs.NewDefaultProvider("test"), 1114 Module: addrs.RootModule, 1115 }, 1116 ChangeSrc: plans.ChangeSrc{ 1117 Action: action, 1118 Before: priorValRaw, 1119 After: plannedValRaw, 1120 }, 1121 }) 1122 return testPlanFile( 1123 t, 1124 snap, 1125 states.NewState(), 1126 plan, 1127 ) 1128 } 1129 1130 // this simplified plan struct allows us to preserve field order when marshaling 1131 // the command output. NOTE: we are leaving "terraform_version" out of this test 1132 // to avoid needing to constantly update the expected output; as a potential 1133 // TODO we could write a jsonplan compare function. 1134 type plan struct { 1135 FormatVersion string `json:"format_version,omitempty"` 1136 Variables map[string]interface{} `json:"variables,omitempty"` 1137 PlannedValues map[string]interface{} `json:"planned_values,omitempty"` 1138 ResourceDrift []interface{} `json:"resource_drift,omitempty"` 1139 ResourceChanges []interface{} `json:"resource_changes,omitempty"` 1140 OutputChanges map[string]interface{} `json:"output_changes,omitempty"` 1141 PriorState priorState `json:"prior_state,omitempty"` 1142 Config map[string]interface{} `json:"configuration,omitempty"` 1143 } 1144 1145 type priorState struct { 1146 FormatVersion string `json:"format_version,omitempty"` 1147 Values map[string]interface{} `json:"values,omitempty"` 1148 SensitiveValues map[string]bool `json:"sensitive_values,omitempty"` 1149 }