github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/command/show_test.go (about)

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