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  }