github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/command/show_test.go (about)

     1  package command
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/google/go-cmp/cmp"
    13  	"github.com/muratcelep/terraform/not-internal/addrs"
    14  	"github.com/muratcelep/terraform/not-internal/configs/configschema"
    15  	"github.com/muratcelep/terraform/not-internal/plans"
    16  	"github.com/muratcelep/terraform/not-internal/providers"
    17  	"github.com/muratcelep/terraform/not-internal/states"
    18  	"github.com/muratcelep/terraform/not-internal/terraform"
    19  	"github.com/mitchellh/cli"
    20  	"github.com/zclconf/go-cty/cty"
    21  )
    22  
    23  func TestShow(t *testing.T) {
    24  	ui := new(cli.MockUi)
    25  	view, _ := testView(t)
    26  	c := &ShowCommand{
    27  		Meta: Meta{
    28  			testingOverrides: metaOverridesForProvider(testProvider()),
    29  			Ui:               ui,
    30  			View:             view,
    31  		},
    32  	}
    33  
    34  	args := []string{
    35  		"bad",
    36  		"bad",
    37  	}
    38  	if code := c.Run(args); code != 1 {
    39  		t.Fatalf("bad: \n%s", ui.OutputWriter.String())
    40  	}
    41  }
    42  
    43  func TestShow_noArgs(t *testing.T) {
    44  	// Get a temp cwd
    45  	tmp, cwd := testCwd(t)
    46  	defer testFixCwd(t, tmp, cwd)
    47  	// Create the default state
    48  	testStateFileDefault(t, testState())
    49  
    50  	ui := new(cli.MockUi)
    51  	view, _ := testView(t)
    52  	c := &ShowCommand{
    53  		Meta: Meta{
    54  			testingOverrides: metaOverridesForProvider(testProvider()),
    55  			Ui:               ui,
    56  			View:             view,
    57  		},
    58  	}
    59  
    60  	if code := c.Run([]string{}); code != 0 {
    61  		t.Fatalf("bad: \n%s", ui.OutputWriter.String())
    62  	}
    63  
    64  	if !strings.Contains(ui.OutputWriter.String(), "# test_instance.foo:") {
    65  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
    66  	}
    67  }
    68  
    69  // https://github.com/muratcelep/terraform/issues/21462
    70  func TestShow_aliasedProvider(t *testing.T) {
    71  	// Create the default state with aliased resource
    72  	testState := states.BuildState(func(s *states.SyncState) {
    73  		s.SetResourceInstanceCurrent(
    74  			addrs.Resource{
    75  				Mode: addrs.ManagedResourceMode,
    76  				Type: "test_instance",
    77  				Name: "foo",
    78  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
    79  			&states.ResourceInstanceObjectSrc{
    80  				// The weird whitespace here is reflective of how this would
    81  				// get written out in a real state file, due to the indentation
    82  				// of all of the containing wrapping objects and arrays.
    83  				AttrsJSON:    []byte("{\n            \"id\": \"bar\"\n          }"),
    84  				Status:       states.ObjectReady,
    85  				Dependencies: []addrs.ConfigResource{},
    86  			},
    87  			addrs.RootModuleInstance.ProviderConfigAliased(addrs.NewDefaultProvider("test"), "alias"),
    88  		)
    89  	})
    90  
    91  	statePath := testStateFile(t, testState)
    92  	stateDir := filepath.Dir(statePath)
    93  	defer os.RemoveAll(stateDir)
    94  	defer testChdir(t, stateDir)()
    95  
    96  	ui := new(cli.MockUi)
    97  	view, _ := testView(t)
    98  	c := &ShowCommand{
    99  		Meta: Meta{
   100  			testingOverrides: metaOverridesForProvider(testProvider()),
   101  			Ui:               ui,
   102  			View:             view,
   103  		},
   104  	}
   105  
   106  	// the statefile created by testStateFile is named state.tfstate
   107  	args := []string{"state.tfstate"}
   108  	if code := c.Run(args); code != 0 {
   109  		t.Fatalf("bad exit code: \n%s", ui.OutputWriter.String())
   110  	}
   111  
   112  	if strings.Contains(ui.OutputWriter.String(), "# missing schema for provider \"test.alias\"") {
   113  		t.Fatalf("bad output: \n%s", ui.OutputWriter.String())
   114  	}
   115  }
   116  
   117  func TestShow_noArgsNoState(t *testing.T) {
   118  	// Create the default state
   119  	statePath := testStateFile(t, testState())
   120  	stateDir := filepath.Dir(statePath)
   121  	defer os.RemoveAll(stateDir)
   122  	defer testChdir(t, stateDir)()
   123  
   124  	ui := new(cli.MockUi)
   125  	view, _ := testView(t)
   126  	c := &ShowCommand{
   127  		Meta: Meta{
   128  			testingOverrides: metaOverridesForProvider(testProvider()),
   129  			Ui:               ui,
   130  			View:             view,
   131  		},
   132  	}
   133  
   134  	// the statefile created by testStateFile is named state.tfstate
   135  	args := []string{"state.tfstate"}
   136  	if code := c.Run(args); code != 0 {
   137  		t.Fatalf("bad: \n%s", ui.OutputWriter.String())
   138  	}
   139  }
   140  
   141  func TestShow_planNoop(t *testing.T) {
   142  	planPath := testPlanFileNoop(t)
   143  
   144  	ui := cli.NewMockUi()
   145  	view, done := testView(t)
   146  	c := &ShowCommand{
   147  		Meta: Meta{
   148  			testingOverrides: metaOverridesForProvider(testProvider()),
   149  			Ui:               ui,
   150  			View:             view,
   151  		},
   152  	}
   153  
   154  	args := []string{
   155  		planPath,
   156  	}
   157  	if code := c.Run(args); code != 0 {
   158  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   159  	}
   160  
   161  	want := `No changes. Your infrastructure matches the configuration.`
   162  	got := done(t).Stdout()
   163  	if !strings.Contains(got, want) {
   164  		t.Errorf("missing expected output\nwant: %s\ngot:\n%s", want, got)
   165  	}
   166  }
   167  
   168  func TestShow_planWithChanges(t *testing.T) {
   169  	planPathWithChanges := showFixturePlanFile(t, plans.DeleteThenCreate)
   170  
   171  	ui := cli.NewMockUi()
   172  	view, done := testView(t)
   173  	c := &ShowCommand{
   174  		Meta: Meta{
   175  			testingOverrides: metaOverridesForProvider(showFixtureProvider()),
   176  			Ui:               ui,
   177  			View:             view,
   178  		},
   179  	}
   180  
   181  	args := []string{
   182  		planPathWithChanges,
   183  	}
   184  
   185  	if code := c.Run(args); code != 0 {
   186  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   187  	}
   188  
   189  	want := `test_instance.foo must be replaced`
   190  	got := done(t).Stdout()
   191  	if !strings.Contains(got, want) {
   192  		t.Errorf("missing expected output\nwant: %s\ngot:\n%s", want, got)
   193  	}
   194  }
   195  
   196  func TestShow_planWithForceReplaceChange(t *testing.T) {
   197  	// The main goal of this test is to see that the "replace by request"
   198  	// resource instance action reason can round-trip through a plan file and
   199  	// be reflected correctly in the "terraform show" output, the same way
   200  	// as it would appear in "terraform plan" output.
   201  
   202  	_, snap := testModuleWithSnapshot(t, "show")
   203  	plannedVal := cty.ObjectVal(map[string]cty.Value{
   204  		"id":  cty.UnknownVal(cty.String),
   205  		"ami": cty.StringVal("bar"),
   206  	})
   207  	priorValRaw, err := plans.NewDynamicValue(cty.NullVal(plannedVal.Type()), plannedVal.Type())
   208  	if err != nil {
   209  		t.Fatal(err)
   210  	}
   211  	plannedValRaw, err := plans.NewDynamicValue(plannedVal, plannedVal.Type())
   212  	if err != nil {
   213  		t.Fatal(err)
   214  	}
   215  	plan := testPlan(t)
   216  	plan.Changes.SyncWrapper().AppendResourceInstanceChange(&plans.ResourceInstanceChangeSrc{
   217  		Addr: addrs.Resource{
   218  			Mode: addrs.ManagedResourceMode,
   219  			Type: "test_instance",
   220  			Name: "foo",
   221  		}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   222  		ProviderAddr: addrs.AbsProviderConfig{
   223  			Provider: addrs.NewDefaultProvider("test"),
   224  			Module:   addrs.RootModule,
   225  		},
   226  		ChangeSrc: plans.ChangeSrc{
   227  			Action: plans.CreateThenDelete,
   228  			Before: priorValRaw,
   229  			After:  plannedValRaw,
   230  		},
   231  		ActionReason: plans.ResourceInstanceReplaceByRequest,
   232  	})
   233  	planFilePath := testPlanFile(
   234  		t,
   235  		snap,
   236  		states.NewState(),
   237  		plan,
   238  	)
   239  
   240  	ui := cli.NewMockUi()
   241  	view, done := testView(t)
   242  	c := &ShowCommand{
   243  		Meta: Meta{
   244  			testingOverrides: metaOverridesForProvider(showFixtureProvider()),
   245  			Ui:               ui,
   246  			View:             view,
   247  		},
   248  	}
   249  
   250  	args := []string{
   251  		planFilePath,
   252  	}
   253  
   254  	if code := c.Run(args); code != 0 {
   255  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   256  	}
   257  
   258  	got := done(t).Stdout()
   259  	if want := `test_instance.foo will be replaced, as requested`; !strings.Contains(got, want) {
   260  		t.Errorf("wrong output\ngot:\n%s\n\nwant substring: %s", got, want)
   261  	}
   262  	if want := `Plan: 1 to add, 0 to change, 1 to destroy.`; !strings.Contains(got, want) {
   263  		t.Errorf("wrong output\ngot:\n%s\n\nwant substring: %s", got, want)
   264  	}
   265  
   266  }
   267  
   268  func TestShow_plan_json(t *testing.T) {
   269  	planPath := showFixturePlanFile(t, plans.Create)
   270  
   271  	ui := new(cli.MockUi)
   272  	view, _ := testView(t)
   273  	c := &ShowCommand{
   274  		Meta: Meta{
   275  			testingOverrides: metaOverridesForProvider(showFixtureProvider()),
   276  			Ui:               ui,
   277  			View:             view,
   278  		},
   279  	}
   280  
   281  	args := []string{
   282  		"-json",
   283  		planPath,
   284  	}
   285  	if code := c.Run(args); code != 0 {
   286  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   287  	}
   288  }
   289  
   290  func TestShow_state(t *testing.T) {
   291  	originalState := testState()
   292  	statePath := testStateFile(t, originalState)
   293  	defer os.RemoveAll(filepath.Dir(statePath))
   294  
   295  	ui := new(cli.MockUi)
   296  	view, _ := testView(t)
   297  	c := &ShowCommand{
   298  		Meta: Meta{
   299  			testingOverrides: metaOverridesForProvider(testProvider()),
   300  			Ui:               ui,
   301  			View:             view,
   302  		},
   303  	}
   304  
   305  	args := []string{
   306  		statePath,
   307  	}
   308  	if code := c.Run(args); code != 0 {
   309  		t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
   310  	}
   311  }
   312  
   313  func TestShow_json_output(t *testing.T) {
   314  	fixtureDir := "testdata/show-json"
   315  	testDirs, err := ioutil.ReadDir(fixtureDir)
   316  	if err != nil {
   317  		t.Fatal(err)
   318  	}
   319  
   320  	for _, entry := range testDirs {
   321  		if !entry.IsDir() {
   322  			continue
   323  		}
   324  
   325  		t.Run(entry.Name(), func(t *testing.T) {
   326  			td := tempDir(t)
   327  			inputDir := filepath.Join(fixtureDir, entry.Name())
   328  			testCopyDir(t, inputDir, td)
   329  			defer os.RemoveAll(td)
   330  			defer testChdir(t, td)()
   331  
   332  			expectError := strings.Contains(entry.Name(), "error")
   333  
   334  			providerSource, close := newMockProviderSource(t, map[string][]string{
   335  				"test": {"1.2.3"},
   336  			})
   337  			defer close()
   338  
   339  			p := showFixtureProvider()
   340  			ui := new(cli.MockUi)
   341  			view, _ := testView(t)
   342  			m := Meta{
   343  				testingOverrides: metaOverridesForProvider(p),
   344  				Ui:               ui,
   345  				View:             view,
   346  				ProviderSource:   providerSource,
   347  			}
   348  
   349  			// init
   350  			ic := &InitCommand{
   351  				Meta: m,
   352  			}
   353  			if code := ic.Run([]string{}); code != 0 {
   354  				if expectError {
   355  					// this should error, but not panic.
   356  					return
   357  				}
   358  				t.Fatalf("init failed\n%s", ui.ErrorWriter)
   359  			}
   360  
   361  			pc := &PlanCommand{
   362  				Meta: m,
   363  			}
   364  
   365  			args := []string{
   366  				"-out=terraform.plan",
   367  			}
   368  
   369  			if code := pc.Run(args); code != 0 {
   370  				t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String())
   371  			}
   372  
   373  			// flush the plan output from the mock ui
   374  			ui.OutputWriter.Reset()
   375  			sc := &ShowCommand{
   376  				Meta: m,
   377  			}
   378  
   379  			args = []string{
   380  				"-json",
   381  				"terraform.plan",
   382  			}
   383  			defer os.Remove("terraform.plan")
   384  
   385  			if code := sc.Run(args); code != 0 {
   386  				t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String())
   387  			}
   388  
   389  			// compare ui output to wanted output
   390  			var got, want plan
   391  
   392  			gotString := ui.OutputWriter.String()
   393  			json.Unmarshal([]byte(gotString), &got)
   394  
   395  			wantFile, err := os.Open("output.json")
   396  			if err != nil {
   397  				t.Fatalf("err: %s", err)
   398  			}
   399  			defer wantFile.Close()
   400  			byteValue, err := ioutil.ReadAll(wantFile)
   401  			if err != nil {
   402  				t.Fatalf("err: %s", err)
   403  			}
   404  			json.Unmarshal([]byte(byteValue), &want)
   405  
   406  			if !cmp.Equal(got, want) {
   407  				t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want))
   408  			}
   409  		})
   410  	}
   411  }
   412  
   413  func TestShow_json_output_sensitive(t *testing.T) {
   414  	td := tempDir(t)
   415  	inputDir := "testdata/show-json-sensitive"
   416  	testCopyDir(t, inputDir, td)
   417  	defer os.RemoveAll(td)
   418  	defer testChdir(t, td)()
   419  
   420  	providerSource, close := newMockProviderSource(t, map[string][]string{"test": {"1.2.3"}})
   421  	defer close()
   422  
   423  	p := showFixtureSensitiveProvider()
   424  	ui := new(cli.MockUi)
   425  	view, _ := testView(t)
   426  	m := Meta{
   427  		testingOverrides: metaOverridesForProvider(p),
   428  		Ui:               ui,
   429  		View:             view,
   430  		ProviderSource:   providerSource,
   431  	}
   432  
   433  	// init
   434  	ic := &InitCommand{
   435  		Meta: m,
   436  	}
   437  	if code := ic.Run([]string{}); code != 0 {
   438  		t.Fatalf("init failed\n%s", ui.ErrorWriter)
   439  	}
   440  
   441  	// flush init output
   442  	ui.OutputWriter.Reset()
   443  
   444  	pc := &PlanCommand{
   445  		Meta: m,
   446  	}
   447  
   448  	args := []string{
   449  		"-out=terraform.plan",
   450  	}
   451  
   452  	if code := pc.Run(args); code != 0 {
   453  		fmt.Println(ui.OutputWriter.String())
   454  		t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String())
   455  	}
   456  
   457  	// flush the plan output from the mock ui
   458  	ui.OutputWriter.Reset()
   459  	sc := &ShowCommand{
   460  		Meta: m,
   461  	}
   462  
   463  	args = []string{
   464  		"-json",
   465  		"terraform.plan",
   466  	}
   467  	defer os.Remove("terraform.plan")
   468  
   469  	if code := sc.Run(args); code != 0 {
   470  		t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String())
   471  	}
   472  
   473  	// compare ui output to wanted output
   474  	var got, want plan
   475  
   476  	gotString := ui.OutputWriter.String()
   477  	json.Unmarshal([]byte(gotString), &got)
   478  
   479  	wantFile, err := os.Open("output.json")
   480  	if err != nil {
   481  		t.Fatalf("err: %s", err)
   482  	}
   483  	defer wantFile.Close()
   484  	byteValue, err := ioutil.ReadAll(wantFile)
   485  	if err != nil {
   486  		t.Fatalf("err: %s", err)
   487  	}
   488  	json.Unmarshal([]byte(byteValue), &want)
   489  
   490  	if !cmp.Equal(got, want) {
   491  		t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want))
   492  	}
   493  }
   494  
   495  // similar test as above, without the plan
   496  func TestShow_json_output_state(t *testing.T) {
   497  	fixtureDir := "testdata/show-json-state"
   498  	testDirs, err := ioutil.ReadDir(fixtureDir)
   499  	if err != nil {
   500  		t.Fatal(err)
   501  	}
   502  
   503  	for _, entry := range testDirs {
   504  		if !entry.IsDir() {
   505  			continue
   506  		}
   507  
   508  		t.Run(entry.Name(), func(t *testing.T) {
   509  			td := tempDir(t)
   510  			inputDir := filepath.Join(fixtureDir, entry.Name())
   511  			testCopyDir(t, inputDir, td)
   512  			defer os.RemoveAll(td)
   513  			defer testChdir(t, td)()
   514  
   515  			providerSource, close := newMockProviderSource(t, map[string][]string{
   516  				"test": {"1.2.3"},
   517  			})
   518  			defer close()
   519  
   520  			p := showFixtureProvider()
   521  			ui := new(cli.MockUi)
   522  			view, _ := testView(t)
   523  			m := Meta{
   524  				testingOverrides: metaOverridesForProvider(p),
   525  				Ui:               ui,
   526  				View:             view,
   527  				ProviderSource:   providerSource,
   528  			}
   529  
   530  			// init
   531  			ic := &InitCommand{
   532  				Meta: m,
   533  			}
   534  			if code := ic.Run([]string{}); code != 0 {
   535  				t.Fatalf("init failed\n%s", ui.ErrorWriter)
   536  			}
   537  
   538  			// flush the plan output from the mock ui
   539  			ui.OutputWriter.Reset()
   540  			sc := &ShowCommand{
   541  				Meta: m,
   542  			}
   543  
   544  			if code := sc.Run([]string{"-json"}); code != 0 {
   545  				t.Fatalf("wrong exit status %d; want 0\nstderr: %s", code, ui.ErrorWriter.String())
   546  			}
   547  
   548  			// compare ui output to wanted output
   549  			type state struct {
   550  				FormatVersion    string                 `json:"format_version,omitempty"`
   551  				TerraformVersion string                 `json:"terraform_version"`
   552  				Values           map[string]interface{} `json:"values,omitempty"`
   553  				SensitiveValues  map[string]bool        `json:"sensitive_values,omitempty"`
   554  			}
   555  			var got, want state
   556  
   557  			gotString := ui.OutputWriter.String()
   558  			json.Unmarshal([]byte(gotString), &got)
   559  
   560  			wantFile, err := os.Open("output.json")
   561  			if err != nil {
   562  				t.Fatalf("err: %s", err)
   563  			}
   564  			defer wantFile.Close()
   565  			byteValue, err := ioutil.ReadAll(wantFile)
   566  			if err != nil {
   567  				t.Fatalf("err: %s", err)
   568  			}
   569  			json.Unmarshal([]byte(byteValue), &want)
   570  
   571  			if !cmp.Equal(got, want) {
   572  				t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, want))
   573  			}
   574  		})
   575  	}
   576  }
   577  
   578  // showFixtureSchema returns a schema suitable for processing the configuration
   579  // in testdata/show. This schema should be assigned to a mock provider
   580  // named "test".
   581  func showFixtureSchema() *providers.GetProviderSchemaResponse {
   582  	return &providers.GetProviderSchemaResponse{
   583  		Provider: providers.Schema{
   584  			Block: &configschema.Block{
   585  				Attributes: map[string]*configschema.Attribute{
   586  					"region": {Type: cty.String, Optional: true},
   587  				},
   588  			},
   589  		},
   590  		ResourceTypes: map[string]providers.Schema{
   591  			"test_instance": {
   592  				Block: &configschema.Block{
   593  					Attributes: map[string]*configschema.Attribute{
   594  						"id":  {Type: cty.String, Optional: true, Computed: true},
   595  						"ami": {Type: cty.String, Optional: true},
   596  					},
   597  				},
   598  			},
   599  		},
   600  	}
   601  }
   602  
   603  // showFixtureSensitiveSchema returns a schema suitable for processing the configuration
   604  // in testdata/show. This schema should be assigned to a mock provider
   605  // named "test". It includes a sensitive attribute.
   606  func showFixtureSensitiveSchema() *providers.GetProviderSchemaResponse {
   607  	return &providers.GetProviderSchemaResponse{
   608  		Provider: providers.Schema{
   609  			Block: &configschema.Block{
   610  				Attributes: map[string]*configschema.Attribute{
   611  					"region": {Type: cty.String, Optional: true},
   612  				},
   613  			},
   614  		},
   615  		ResourceTypes: map[string]providers.Schema{
   616  			"test_instance": {
   617  				Block: &configschema.Block{
   618  					Attributes: map[string]*configschema.Attribute{
   619  						"id":       {Type: cty.String, Optional: true, Computed: true},
   620  						"ami":      {Type: cty.String, Optional: true},
   621  						"password": {Type: cty.String, Optional: true, Sensitive: true},
   622  					},
   623  				},
   624  			},
   625  		},
   626  	}
   627  }
   628  
   629  // showFixtureProvider returns a mock provider that is configured for basic
   630  // operation with the configuration in testdata/show. This mock has
   631  // GetSchemaResponse, PlanResourceChangeFn, and ApplyResourceChangeFn populated,
   632  // with the plan/apply steps just passing through the data determined by
   633  // Terraform Core.
   634  func showFixtureProvider() *terraform.MockProvider {
   635  	p := testProvider()
   636  	p.GetProviderSchemaResponse = showFixtureSchema()
   637  	p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
   638  		idVal := req.PriorState.GetAttr("id")
   639  		amiVal := req.PriorState.GetAttr("ami")
   640  		if amiVal.RawEquals(cty.StringVal("refresh-me")) {
   641  			amiVal = cty.StringVal("refreshed")
   642  		}
   643  		return providers.ReadResourceResponse{
   644  			NewState: cty.ObjectVal(map[string]cty.Value{
   645  				"id":  idVal,
   646  				"ami": amiVal,
   647  			}),
   648  			Private: req.Private,
   649  		}
   650  	}
   651  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
   652  		idVal := req.ProposedNewState.GetAttr("id")
   653  		amiVal := req.ProposedNewState.GetAttr("ami")
   654  		if idVal.IsNull() {
   655  			idVal = cty.UnknownVal(cty.String)
   656  		}
   657  		var reqRep []cty.Path
   658  		if amiVal.RawEquals(cty.StringVal("force-replace")) {
   659  			reqRep = append(reqRep, cty.GetAttrPath("ami"))
   660  		}
   661  		return providers.PlanResourceChangeResponse{
   662  			PlannedState: cty.ObjectVal(map[string]cty.Value{
   663  				"id":  idVal,
   664  				"ami": amiVal,
   665  			}),
   666  			RequiresReplace: reqRep,
   667  		}
   668  	}
   669  	p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
   670  		idVal := req.PlannedState.GetAttr("id")
   671  		amiVal := req.PlannedState.GetAttr("ami")
   672  		if !idVal.IsKnown() {
   673  			idVal = cty.StringVal("placeholder")
   674  		}
   675  		return providers.ApplyResourceChangeResponse{
   676  			NewState: cty.ObjectVal(map[string]cty.Value{
   677  				"id":  idVal,
   678  				"ami": amiVal,
   679  			}),
   680  		}
   681  	}
   682  	return p
   683  }
   684  
   685  // showFixtureSensitiveProvider returns a mock provider that is configured for basic
   686  // operation with the configuration in testdata/show. This mock has
   687  // GetSchemaResponse, PlanResourceChangeFn, and ApplyResourceChangeFn populated,
   688  // with the plan/apply steps just passing through the data determined by
   689  // Terraform Core. It also has a sensitive attribute in the provider schema.
   690  func showFixtureSensitiveProvider() *terraform.MockProvider {
   691  	p := testProvider()
   692  	p.GetProviderSchemaResponse = showFixtureSensitiveSchema()
   693  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
   694  		idVal := req.ProposedNewState.GetAttr("id")
   695  		if idVal.IsNull() {
   696  			idVal = cty.UnknownVal(cty.String)
   697  		}
   698  		return providers.PlanResourceChangeResponse{
   699  			PlannedState: cty.ObjectVal(map[string]cty.Value{
   700  				"id":       idVal,
   701  				"ami":      req.ProposedNewState.GetAttr("ami"),
   702  				"password": req.ProposedNewState.GetAttr("password"),
   703  			}),
   704  		}
   705  	}
   706  	p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
   707  		idVal := req.PlannedState.GetAttr("id")
   708  		if !idVal.IsKnown() {
   709  			idVal = cty.StringVal("placeholder")
   710  		}
   711  		return providers.ApplyResourceChangeResponse{
   712  			NewState: cty.ObjectVal(map[string]cty.Value{
   713  				"id":       idVal,
   714  				"ami":      req.PlannedState.GetAttr("ami"),
   715  				"password": req.PlannedState.GetAttr("password"),
   716  			}),
   717  		}
   718  	}
   719  	return p
   720  }
   721  
   722  // showFixturePlanFile creates a plan file at a temporary location containing a
   723  // single change to create or update the test_instance.foo that is included in the "show"
   724  // test fixture, returning the location of that plan file.
   725  // `action` is the planned change you would like to elicit
   726  func showFixturePlanFile(t *testing.T, action plans.Action) string {
   727  	_, snap := testModuleWithSnapshot(t, "show")
   728  	plannedVal := cty.ObjectVal(map[string]cty.Value{
   729  		"id":  cty.UnknownVal(cty.String),
   730  		"ami": cty.StringVal("bar"),
   731  	})
   732  	priorValRaw, err := plans.NewDynamicValue(cty.NullVal(plannedVal.Type()), plannedVal.Type())
   733  	if err != nil {
   734  		t.Fatal(err)
   735  	}
   736  	plannedValRaw, err := plans.NewDynamicValue(plannedVal, plannedVal.Type())
   737  	if err != nil {
   738  		t.Fatal(err)
   739  	}
   740  	plan := testPlan(t)
   741  	plan.Changes.SyncWrapper().AppendResourceInstanceChange(&plans.ResourceInstanceChangeSrc{
   742  		Addr: addrs.Resource{
   743  			Mode: addrs.ManagedResourceMode,
   744  			Type: "test_instance",
   745  			Name: "foo",
   746  		}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   747  		ProviderAddr: addrs.AbsProviderConfig{
   748  			Provider: addrs.NewDefaultProvider("test"),
   749  			Module:   addrs.RootModule,
   750  		},
   751  		ChangeSrc: plans.ChangeSrc{
   752  			Action: action,
   753  			Before: priorValRaw,
   754  			After:  plannedValRaw,
   755  		},
   756  	})
   757  	return testPlanFile(
   758  		t,
   759  		snap,
   760  		states.NewState(),
   761  		plan,
   762  	)
   763  }
   764  
   765  // this simplified plan struct allows us to preserve field order when marshaling
   766  // the command output. NOTE: we are leaving "terraform_version" out of this test
   767  // to avoid needing to constantly update the expected output; as a potential
   768  // TODO we could write a jsonplan compare function.
   769  type plan struct {
   770  	FormatVersion   string                 `json:"format_version,omitempty"`
   771  	Variables       map[string]interface{} `json:"variables,omitempty"`
   772  	PlannedValues   map[string]interface{} `json:"planned_values,omitempty"`
   773  	ResourceDrift   []interface{}          `json:"resource_drift,omitempty"`
   774  	ResourceChanges []interface{}          `json:"resource_changes,omitempty"`
   775  	OutputChanges   map[string]interface{} `json:"output_changes,omitempty"`
   776  	PriorState      priorState             `json:"prior_state,omitempty"`
   777  	Config          map[string]interface{} `json:"configuration,omitempty"`
   778  }
   779  
   780  type priorState struct {
   781  	FormatVersion   string                 `json:"format_version,omitempty"`
   782  	Values          map[string]interface{} `json:"values,omitempty"`
   783  	SensitiveValues map[string]bool        `json:"sensitive_values,omitempty"`
   784  }