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