github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/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(showFixtureProvider()), 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(showFixtureProvider()), 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(showFixtureProvider()), 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 root := originalState.RootModule() 451 root.SetOutputValue("test", cty.ObjectVal(map[string]cty.Value{ 452 "attr": cty.NullVal(cty.DynamicPseudoType), 453 "null": cty.NullVal(cty.String), 454 "list": cty.ListVal([]cty.Value{cty.NullVal(cty.Number)}), 455 }), false) 456 457 statePath := testStateFile(t, originalState) 458 defer os.RemoveAll(filepath.Dir(statePath)) 459 460 view, done := testView(t) 461 c := &ShowCommand{ 462 Meta: Meta{ 463 testingOverrides: metaOverridesForProvider(showFixtureProvider()), 464 View: view, 465 }, 466 } 467 468 args := []string{ 469 statePath, 470 "-no-color", 471 } 472 code := c.Run(args) 473 output := done(t) 474 475 if code != 0 { 476 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, output.Stderr()) 477 } 478 } 479 480 func TestShow_json_output(t *testing.T) { 481 fixtureDir := "testdata/show-json" 482 testDirs, err := ioutil.ReadDir(fixtureDir) 483 if err != nil { 484 t.Fatal(err) 485 } 486 487 for _, entry := range testDirs { 488 if !entry.IsDir() { 489 continue 490 } 491 492 t.Run(entry.Name(), func(t *testing.T) { 493 td := t.TempDir() 494 inputDir := filepath.Join(fixtureDir, entry.Name()) 495 testCopyDir(t, inputDir, td) 496 defer testChdir(t, td)() 497 498 expectError := strings.Contains(entry.Name(), "error") 499 500 providerSource, close := newMockProviderSource(t, map[string][]string{ 501 "test": {"1.2.3"}, 502 "hashicorp2/test": {"1.2.3"}, 503 }) 504 defer close() 505 506 p := showFixtureProvider() 507 508 // init 509 ui := new(cli.MockUi) 510 ic := &InitCommand{ 511 Meta: Meta{ 512 testingOverrides: metaOverridesForProvider(p), 513 Ui: ui, 514 ProviderSource: providerSource, 515 }, 516 } 517 if code := ic.Run([]string{}); code != 0 { 518 if expectError { 519 // this should error, but not panic. 520 return 521 } 522 t.Fatalf("init failed\n%s", ui.ErrorWriter) 523 } 524 525 // plan 526 planView, planDone := testView(t) 527 pc := &PlanCommand{ 528 Meta: Meta{ 529 testingOverrides: metaOverridesForProvider(p), 530 View: planView, 531 ProviderSource: providerSource, 532 }, 533 } 534 535 args := []string{ 536 "-out=terraform.plan", 537 } 538 539 code := pc.Run(args) 540 planOutput := planDone(t) 541 542 if code != 0 { 543 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, planOutput.Stderr()) 544 } 545 546 // show 547 showView, showDone := testView(t) 548 sc := &ShowCommand{ 549 Meta: Meta{ 550 testingOverrides: metaOverridesForProvider(p), 551 View: showView, 552 ProviderSource: providerSource, 553 }, 554 } 555 556 args = []string{ 557 "-json", 558 "terraform.plan", 559 } 560 defer os.Remove("terraform.plan") 561 code = sc.Run(args) 562 showOutput := showDone(t) 563 564 if code != 0 { 565 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, showOutput.Stderr()) 566 } 567 568 // compare view output to wanted output 569 var got, want plan 570 571 gotString := showOutput.Stdout() 572 json.Unmarshal([]byte(gotString), &got) 573 574 wantFile, err := os.Open("output.json") 575 if err != nil { 576 t.Fatalf("unexpected err: %s", err) 577 } 578 defer wantFile.Close() 579 byteValue, err := ioutil.ReadAll(wantFile) 580 if err != nil { 581 t.Fatalf("unexpected err: %s", err) 582 } 583 json.Unmarshal([]byte(byteValue), &want) 584 585 // Disregard format version to reduce needless test fixture churn 586 want.FormatVersion = got.FormatVersion 587 588 if !cmp.Equal(got, want) { 589 t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want)) 590 } 591 }) 592 } 593 } 594 595 func TestShow_json_output_sensitive(t *testing.T) { 596 td := t.TempDir() 597 inputDir := "testdata/show-json-sensitive" 598 testCopyDir(t, inputDir, td) 599 defer testChdir(t, td)() 600 601 providerSource, close := newMockProviderSource(t, map[string][]string{"test": {"1.2.3"}}) 602 defer close() 603 604 p := showFixtureSensitiveProvider() 605 606 // init 607 ui := new(cli.MockUi) 608 ic := &InitCommand{ 609 Meta: Meta{ 610 testingOverrides: metaOverridesForProvider(p), 611 Ui: ui, 612 ProviderSource: providerSource, 613 }, 614 } 615 if code := ic.Run([]string{}); code != 0 { 616 t.Fatalf("init failed\n%s", ui.ErrorWriter) 617 } 618 619 // plan 620 planView, planDone := testView(t) 621 pc := &PlanCommand{ 622 Meta: Meta{ 623 testingOverrides: metaOverridesForProvider(p), 624 View: planView, 625 ProviderSource: providerSource, 626 }, 627 } 628 629 args := []string{ 630 "-out=terraform.plan", 631 } 632 code := pc.Run(args) 633 planOutput := planDone(t) 634 635 if code != 0 { 636 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, planOutput.Stderr()) 637 } 638 639 // show 640 showView, showDone := testView(t) 641 sc := &ShowCommand{ 642 Meta: Meta{ 643 testingOverrides: metaOverridesForProvider(p), 644 View: showView, 645 ProviderSource: providerSource, 646 }, 647 } 648 649 args = []string{ 650 "-json", 651 "terraform.plan", 652 } 653 defer os.Remove("terraform.plan") 654 code = sc.Run(args) 655 showOutput := showDone(t) 656 657 if code != 0 { 658 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, showOutput.Stderr()) 659 } 660 661 // compare ui output to wanted output 662 var got, want plan 663 664 gotString := showOutput.Stdout() 665 json.Unmarshal([]byte(gotString), &got) 666 667 wantFile, err := os.Open("output.json") 668 if err != nil { 669 t.Fatalf("unexpected err: %s", err) 670 } 671 defer wantFile.Close() 672 byteValue, err := ioutil.ReadAll(wantFile) 673 if err != nil { 674 t.Fatalf("unexpected err: %s", err) 675 } 676 json.Unmarshal([]byte(byteValue), &want) 677 678 // Disregard format version to reduce needless test fixture churn 679 want.FormatVersion = got.FormatVersion 680 681 if !cmp.Equal(got, want) { 682 t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want)) 683 } 684 } 685 686 // Failing conditions are only present in JSON output for refresh-only plans, 687 // so we test that separately here. 688 func TestShow_json_output_conditions_refresh_only(t *testing.T) { 689 td := t.TempDir() 690 inputDir := "testdata/show-json/conditions" 691 testCopyDir(t, inputDir, td) 692 defer testChdir(t, td)() 693 694 providerSource, close := newMockProviderSource(t, map[string][]string{"test": {"1.2.3"}}) 695 defer close() 696 697 p := showFixtureSensitiveProvider() 698 699 // init 700 ui := new(cli.MockUi) 701 ic := &InitCommand{ 702 Meta: Meta{ 703 testingOverrides: metaOverridesForProvider(p), 704 Ui: ui, 705 ProviderSource: providerSource, 706 }, 707 } 708 if code := ic.Run([]string{}); code != 0 { 709 t.Fatalf("init failed\n%s", ui.ErrorWriter) 710 } 711 712 // plan 713 planView, planDone := testView(t) 714 pc := &PlanCommand{ 715 Meta: Meta{ 716 testingOverrides: metaOverridesForProvider(p), 717 View: planView, 718 ProviderSource: providerSource, 719 }, 720 } 721 722 args := []string{ 723 "-refresh-only", 724 "-out=terraform.plan", 725 "-var=ami=bad-ami", 726 "-state=for-refresh.tfstate", 727 } 728 code := pc.Run(args) 729 planOutput := planDone(t) 730 731 if code != 0 { 732 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, planOutput.Stderr()) 733 } 734 735 // show 736 showView, showDone := testView(t) 737 sc := &ShowCommand{ 738 Meta: Meta{ 739 testingOverrides: metaOverridesForProvider(p), 740 View: showView, 741 ProviderSource: providerSource, 742 }, 743 } 744 745 args = []string{ 746 "-json", 747 "terraform.plan", 748 } 749 defer os.Remove("terraform.plan") 750 code = sc.Run(args) 751 showOutput := showDone(t) 752 753 if code != 0 { 754 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, showOutput.Stderr()) 755 } 756 757 // compare JSON output to wanted output 758 var got, want plan 759 760 gotString := showOutput.Stdout() 761 json.Unmarshal([]byte(gotString), &got) 762 763 wantFile, err := os.Open("output-refresh-only.json") 764 if err != nil { 765 t.Fatalf("unexpected err: %s", err) 766 } 767 defer wantFile.Close() 768 byteValue, err := ioutil.ReadAll(wantFile) 769 if err != nil { 770 t.Fatalf("unexpected err: %s", err) 771 } 772 json.Unmarshal([]byte(byteValue), &want) 773 774 // Disregard format version to reduce needless test fixture churn 775 want.FormatVersion = got.FormatVersion 776 777 if !cmp.Equal(got, want) { 778 t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want)) 779 } 780 } 781 782 // similar test as above, without the plan 783 func TestShow_json_output_state(t *testing.T) { 784 fixtureDir := "testdata/show-json-state" 785 testDirs, err := ioutil.ReadDir(fixtureDir) 786 if err != nil { 787 t.Fatal(err) 788 } 789 790 for _, entry := range testDirs { 791 if !entry.IsDir() { 792 continue 793 } 794 795 t.Run(entry.Name(), func(t *testing.T) { 796 td := t.TempDir() 797 inputDir := filepath.Join(fixtureDir, entry.Name()) 798 testCopyDir(t, inputDir, td) 799 defer testChdir(t, td)() 800 801 providerSource, close := newMockProviderSource(t, map[string][]string{ 802 "test": {"1.2.3"}, 803 }) 804 defer close() 805 806 p := showFixtureProvider() 807 808 // init 809 ui := new(cli.MockUi) 810 ic := &InitCommand{ 811 Meta: Meta{ 812 testingOverrides: metaOverridesForProvider(p), 813 Ui: ui, 814 ProviderSource: providerSource, 815 }, 816 } 817 if code := ic.Run([]string{}); code != 0 { 818 t.Fatalf("init failed\n%s", ui.ErrorWriter) 819 } 820 821 // show 822 showView, showDone := testView(t) 823 sc := &ShowCommand{ 824 Meta: Meta{ 825 testingOverrides: metaOverridesForProvider(p), 826 View: showView, 827 ProviderSource: providerSource, 828 }, 829 } 830 831 code := sc.Run([]string{"-json"}) 832 showOutput := showDone(t) 833 834 if code != 0 { 835 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, showOutput.Stderr()) 836 } 837 838 // compare ui output to wanted output 839 type state struct { 840 FormatVersion string `json:"format_version,omitempty"` 841 TerraformVersion string `json:"terraform_version"` 842 Values map[string]interface{} `json:"values,omitempty"` 843 SensitiveValues map[string]bool `json:"sensitive_values,omitempty"` 844 } 845 var got, want state 846 847 gotString := showOutput.Stdout() 848 json.Unmarshal([]byte(gotString), &got) 849 850 wantFile, err := os.Open("output.json") 851 if err != nil { 852 t.Fatalf("unexpected error: %s", err) 853 } 854 defer wantFile.Close() 855 byteValue, err := ioutil.ReadAll(wantFile) 856 if err != nil { 857 t.Fatalf("unexpected err: %s", err) 858 } 859 json.Unmarshal([]byte(byteValue), &want) 860 861 if !cmp.Equal(got, want) { 862 t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want)) 863 } 864 }) 865 } 866 } 867 868 func TestShow_planWithNonDefaultStateLineage(t *testing.T) { 869 // Create a temporary working directory that is empty 870 td := t.TempDir() 871 testCopyDir(t, testFixturePath("show"), td) 872 defer testChdir(t, td)() 873 874 // Write default state file with a testing lineage ("fake-for-testing") 875 testStateFileDefault(t, testState()) 876 877 // Create a plan with a different lineage, which we should still be able 878 // to show 879 _, snap := testModuleWithSnapshot(t, "show") 880 state := testState() 881 plan := testPlan(t) 882 stateMeta := statemgr.SnapshotMeta{ 883 Lineage: "fake-for-plan", 884 Serial: 1, 885 TerraformVersion: version.SemVer, 886 } 887 planPath := testPlanFileMatchState(t, snap, state, plan, stateMeta) 888 889 view, done := testView(t) 890 c := &ShowCommand{ 891 Meta: Meta{ 892 testingOverrides: metaOverridesForProvider(testProvider()), 893 View: view, 894 }, 895 } 896 897 args := []string{ 898 planPath, 899 "-no-color", 900 } 901 code := c.Run(args) 902 output := done(t) 903 904 if code != 0 { 905 t.Fatalf("unexpected exit status %d; want 0\ngot: %s", code, output.Stderr()) 906 } 907 908 got := output.Stdout() 909 want := `No changes. Your infrastructure matches the configuration.` 910 if !strings.Contains(got, want) { 911 t.Fatalf("unexpected output\ngot: %s\nwant: %s", got, want) 912 } 913 } 914 915 func TestShow_corruptStatefile(t *testing.T) { 916 td := t.TempDir() 917 inputDir := "testdata/show-corrupt-statefile" 918 testCopyDir(t, inputDir, td) 919 defer testChdir(t, td)() 920 921 view, done := testView(t) 922 c := &ShowCommand{ 923 Meta: Meta{ 924 testingOverrides: metaOverridesForProvider(testProvider()), 925 View: view, 926 }, 927 } 928 929 code := c.Run([]string{}) 930 output := done(t) 931 932 if code != 1 { 933 t.Fatalf("unexpected exit status %d; want 1\ngot: %s", code, output.Stdout()) 934 } 935 936 got := output.Stderr() 937 want := `Unsupported state file format` 938 if !strings.Contains(got, want) { 939 t.Errorf("unexpected output\ngot: %s\nwant:\n%s", got, want) 940 } 941 } 942 943 // showFixtureSchema returns a schema suitable for processing the configuration 944 // in testdata/show. This schema should be assigned to a mock provider 945 // named "test". 946 func showFixtureSchema() *providers.GetProviderSchemaResponse { 947 return &providers.GetProviderSchemaResponse{ 948 Provider: providers.Schema{ 949 Block: &configschema.Block{ 950 Attributes: map[string]*configschema.Attribute{ 951 "region": {Type: cty.String, Optional: true}, 952 }, 953 }, 954 }, 955 ResourceTypes: map[string]providers.Schema{ 956 "test_instance": { 957 Block: &configschema.Block{ 958 Attributes: map[string]*configschema.Attribute{ 959 "id": {Type: cty.String, Optional: true, Computed: true}, 960 "ami": {Type: cty.String, Optional: true}, 961 }, 962 }, 963 }, 964 }, 965 } 966 } 967 968 // showFixtureSensitiveSchema returns a schema suitable for processing the configuration 969 // in testdata/show. This schema should be assigned to a mock provider 970 // named "test". It includes a sensitive attribute. 971 func showFixtureSensitiveSchema() *providers.GetProviderSchemaResponse { 972 return &providers.GetProviderSchemaResponse{ 973 Provider: providers.Schema{ 974 Block: &configschema.Block{ 975 Attributes: map[string]*configschema.Attribute{ 976 "region": {Type: cty.String, Optional: true}, 977 }, 978 }, 979 }, 980 ResourceTypes: map[string]providers.Schema{ 981 "test_instance": { 982 Block: &configschema.Block{ 983 Attributes: map[string]*configschema.Attribute{ 984 "id": {Type: cty.String, Optional: true, Computed: true}, 985 "ami": {Type: cty.String, Optional: true}, 986 "password": {Type: cty.String, Optional: true, Sensitive: true}, 987 }, 988 }, 989 }, 990 }, 991 } 992 } 993 994 // showFixtureProvider returns a mock provider that is configured for basic 995 // operation with the configuration in testdata/show. This mock has 996 // GetSchemaResponse, PlanResourceChangeFn, and ApplyResourceChangeFn populated, 997 // with the plan/apply steps just passing through the data determined by 998 // Terraform Core. 999 func showFixtureProvider() *terraform.MockProvider { 1000 p := testProvider() 1001 p.GetProviderSchemaResponse = showFixtureSchema() 1002 p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse { 1003 idVal := req.PriorState.GetAttr("id") 1004 amiVal := req.PriorState.GetAttr("ami") 1005 if amiVal.RawEquals(cty.StringVal("refresh-me")) { 1006 amiVal = cty.StringVal("refreshed") 1007 } 1008 return providers.ReadResourceResponse{ 1009 NewState: cty.ObjectVal(map[string]cty.Value{ 1010 "id": idVal, 1011 "ami": amiVal, 1012 }), 1013 Private: req.Private, 1014 } 1015 } 1016 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 1017 // this is a destroy plan, 1018 if req.ProposedNewState.IsNull() { 1019 resp.PlannedState = req.ProposedNewState 1020 resp.PlannedPrivate = req.PriorPrivate 1021 return resp 1022 } 1023 1024 idVal := req.ProposedNewState.GetAttr("id") 1025 amiVal := req.ProposedNewState.GetAttr("ami") 1026 if idVal.IsNull() { 1027 idVal = cty.UnknownVal(cty.String) 1028 } 1029 var reqRep []cty.Path 1030 if amiVal.RawEquals(cty.StringVal("force-replace")) { 1031 reqRep = append(reqRep, cty.GetAttrPath("ami")) 1032 } 1033 return providers.PlanResourceChangeResponse{ 1034 PlannedState: cty.ObjectVal(map[string]cty.Value{ 1035 "id": idVal, 1036 "ami": amiVal, 1037 }), 1038 RequiresReplace: reqRep, 1039 } 1040 } 1041 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 1042 idVal := req.PlannedState.GetAttr("id") 1043 amiVal := req.PlannedState.GetAttr("ami") 1044 if !idVal.IsKnown() { 1045 idVal = cty.StringVal("placeholder") 1046 } 1047 return providers.ApplyResourceChangeResponse{ 1048 NewState: cty.ObjectVal(map[string]cty.Value{ 1049 "id": idVal, 1050 "ami": amiVal, 1051 }), 1052 } 1053 } 1054 return p 1055 } 1056 1057 // showFixtureSensitiveProvider returns a mock provider that is configured for basic 1058 // operation with the configuration in testdata/show. This mock has 1059 // GetSchemaResponse, PlanResourceChangeFn, and ApplyResourceChangeFn populated, 1060 // with the plan/apply steps just passing through the data determined by 1061 // Terraform Core. It also has a sensitive attribute in the provider schema. 1062 func showFixtureSensitiveProvider() *terraform.MockProvider { 1063 p := testProvider() 1064 p.GetProviderSchemaResponse = showFixtureSensitiveSchema() 1065 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 1066 idVal := req.ProposedNewState.GetAttr("id") 1067 if idVal.IsNull() { 1068 idVal = cty.UnknownVal(cty.String) 1069 } 1070 return providers.PlanResourceChangeResponse{ 1071 PlannedState: cty.ObjectVal(map[string]cty.Value{ 1072 "id": idVal, 1073 "ami": req.ProposedNewState.GetAttr("ami"), 1074 "password": req.ProposedNewState.GetAttr("password"), 1075 }), 1076 } 1077 } 1078 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 1079 idVal := req.PlannedState.GetAttr("id") 1080 if !idVal.IsKnown() { 1081 idVal = cty.StringVal("placeholder") 1082 } 1083 return providers.ApplyResourceChangeResponse{ 1084 NewState: cty.ObjectVal(map[string]cty.Value{ 1085 "id": idVal, 1086 "ami": req.PlannedState.GetAttr("ami"), 1087 "password": req.PlannedState.GetAttr("password"), 1088 }), 1089 } 1090 } 1091 return p 1092 } 1093 1094 // showFixturePlanFile creates a plan file at a temporary location containing a 1095 // single change to create or update the test_instance.foo that is included in the "show" 1096 // test fixture, returning the location of that plan file. 1097 // `action` is the planned change you would like to elicit 1098 func showFixturePlanFile(t *testing.T, action plans.Action) string { 1099 _, snap := testModuleWithSnapshot(t, "show") 1100 plannedVal := cty.ObjectVal(map[string]cty.Value{ 1101 "id": cty.UnknownVal(cty.String), 1102 "ami": cty.StringVal("bar"), 1103 }) 1104 priorValRaw, err := plans.NewDynamicValue(cty.NullVal(plannedVal.Type()), plannedVal.Type()) 1105 if err != nil { 1106 t.Fatal(err) 1107 } 1108 plannedValRaw, err := plans.NewDynamicValue(plannedVal, plannedVal.Type()) 1109 if err != nil { 1110 t.Fatal(err) 1111 } 1112 plan := testPlan(t) 1113 plan.Changes.SyncWrapper().AppendResourceInstanceChange(&plans.ResourceInstanceChangeSrc{ 1114 Addr: addrs.Resource{ 1115 Mode: addrs.ManagedResourceMode, 1116 Type: "test_instance", 1117 Name: "foo", 1118 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 1119 ProviderAddr: addrs.AbsProviderConfig{ 1120 Provider: addrs.NewDefaultProvider("test"), 1121 Module: addrs.RootModule, 1122 }, 1123 ChangeSrc: plans.ChangeSrc{ 1124 Action: action, 1125 Before: priorValRaw, 1126 After: plannedValRaw, 1127 }, 1128 }) 1129 return testPlanFile( 1130 t, 1131 snap, 1132 states.NewState(), 1133 plan, 1134 ) 1135 } 1136 1137 // this simplified plan struct allows us to preserve field order when marshaling 1138 // the command output. NOTE: we are leaving "terraform_version" out of this test 1139 // to avoid needing to constantly update the expected output; as a potential 1140 // TODO we could write a jsonplan compare function. 1141 type plan struct { 1142 FormatVersion string `json:"format_version,omitempty"` 1143 Variables map[string]interface{} `json:"variables,omitempty"` 1144 PlannedValues map[string]interface{} `json:"planned_values,omitempty"` 1145 ResourceDrift []interface{} `json:"resource_drift,omitempty"` 1146 ResourceChanges []interface{} `json:"resource_changes,omitempty"` 1147 OutputChanges map[string]interface{} `json:"output_changes,omitempty"` 1148 PriorState priorState `json:"prior_state,omitempty"` 1149 Config map[string]interface{} `json:"configuration,omitempty"` 1150 } 1151 1152 type priorState struct { 1153 FormatVersion string `json:"format_version,omitempty"` 1154 Values map[string]interface{} `json:"values,omitempty"` 1155 SensitiveValues map[string]bool `json:"sensitive_values,omitempty"` 1156 }