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