github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/command/refresh_test.go (about)

     1  package command
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"reflect"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/davecgh/go-spew/spew"
    14  	"github.com/google/go-cmp/cmp"
    15  	"github.com/google/go-cmp/cmp/cmpopts"
    16  	"github.com/mitchellh/cli"
    17  	"github.com/zclconf/go-cty/cty"
    18  
    19  	"github.com/cycloidio/terraform/addrs"
    20  	"github.com/cycloidio/terraform/configs/configschema"
    21  	"github.com/cycloidio/terraform/providers"
    22  	"github.com/cycloidio/terraform/states"
    23  	"github.com/cycloidio/terraform/states/statefile"
    24  	"github.com/cycloidio/terraform/states/statemgr"
    25  	"github.com/cycloidio/terraform/tfdiags"
    26  )
    27  
    28  var equateEmpty = cmpopts.EquateEmpty()
    29  
    30  func TestRefresh(t *testing.T) {
    31  	// Create a temporary working directory that is empty
    32  	td := tempDir(t)
    33  	testCopyDir(t, testFixturePath("refresh"), td)
    34  	defer os.RemoveAll(td)
    35  	defer testChdir(t, td)()
    36  
    37  	state := testState()
    38  	statePath := testStateFile(t, state)
    39  
    40  	p := testProvider()
    41  	view, done := testView(t)
    42  	c := &RefreshCommand{
    43  		Meta: Meta{
    44  			testingOverrides: metaOverridesForProvider(p),
    45  			View:             view,
    46  		},
    47  	}
    48  
    49  	p.GetProviderSchemaResponse = refreshFixtureSchema()
    50  	p.ReadResourceFn = nil
    51  	p.ReadResourceResponse = &providers.ReadResourceResponse{
    52  		NewState: cty.ObjectVal(map[string]cty.Value{
    53  			"id": cty.StringVal("yes"),
    54  		}),
    55  	}
    56  
    57  	args := []string{
    58  		"-state", statePath,
    59  	}
    60  	code := c.Run(args)
    61  	output := done(t)
    62  	if code != 0 {
    63  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
    64  	}
    65  
    66  	if !p.ReadResourceCalled {
    67  		t.Fatal("ReadResource should have been called")
    68  	}
    69  
    70  	f, err := os.Open(statePath)
    71  	if err != nil {
    72  		t.Fatalf("err: %s", err)
    73  	}
    74  
    75  	newStateFile, err := statefile.Read(f)
    76  	f.Close()
    77  	if err != nil {
    78  		t.Fatalf("err: %s", err)
    79  	}
    80  
    81  	actual := strings.TrimSpace(newStateFile.State.String())
    82  	expected := strings.TrimSpace(testRefreshStr)
    83  	if actual != expected {
    84  		t.Fatalf("bad:\n\n%s", actual)
    85  	}
    86  }
    87  
    88  func TestRefresh_empty(t *testing.T) {
    89  	// Create a temporary working directory that is empty
    90  	td := tempDir(t)
    91  	testCopyDir(t, testFixturePath("refresh-empty"), td)
    92  	defer os.RemoveAll(td)
    93  	defer testChdir(t, td)()
    94  
    95  	p := testProvider()
    96  	view, done := testView(t)
    97  	c := &RefreshCommand{
    98  		Meta: Meta{
    99  			testingOverrides: metaOverridesForProvider(p),
   100  			View:             view,
   101  		},
   102  	}
   103  
   104  	p.ReadResourceFn = nil
   105  	p.ReadResourceResponse = &providers.ReadResourceResponse{
   106  		NewState: cty.ObjectVal(map[string]cty.Value{
   107  			"id": cty.StringVal("yes"),
   108  		}),
   109  	}
   110  
   111  	args := []string{}
   112  	code := c.Run(args)
   113  	output := done(t)
   114  	if code != 0 {
   115  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   116  	}
   117  
   118  	if p.ReadResourceCalled {
   119  		t.Fatal("ReadResource should not have been called")
   120  	}
   121  }
   122  
   123  func TestRefresh_lockedState(t *testing.T) {
   124  	// Create a temporary working directory that is empty
   125  	td := tempDir(t)
   126  	testCopyDir(t, testFixturePath("refresh"), td)
   127  	defer os.RemoveAll(td)
   128  	defer testChdir(t, td)()
   129  
   130  	state := testState()
   131  	statePath := testStateFile(t, state)
   132  
   133  	unlock, err := testLockState(testDataDir, statePath)
   134  	if err != nil {
   135  		t.Fatal(err)
   136  	}
   137  	defer unlock()
   138  
   139  	p := testProvider()
   140  	view, done := testView(t)
   141  	c := &RefreshCommand{
   142  		Meta: Meta{
   143  			testingOverrides: metaOverridesForProvider(p),
   144  			View:             view,
   145  		},
   146  	}
   147  
   148  	p.GetProviderSchemaResponse = refreshFixtureSchema()
   149  	p.ReadResourceFn = nil
   150  	p.ReadResourceResponse = &providers.ReadResourceResponse{
   151  		NewState: cty.ObjectVal(map[string]cty.Value{
   152  			"id": cty.StringVal("yes"),
   153  		}),
   154  	}
   155  
   156  	args := []string{
   157  		"-state", statePath,
   158  	}
   159  
   160  	code := c.Run(args)
   161  	output := done(t)
   162  	if code == 0 {
   163  		t.Fatal("expected error")
   164  	}
   165  
   166  	got := output.Stderr()
   167  	if !strings.Contains(got, "lock") {
   168  		t.Fatal("command output does not look like a lock error:", got)
   169  	}
   170  }
   171  
   172  func TestRefresh_cwd(t *testing.T) {
   173  	cwd, err := os.Getwd()
   174  	if err != nil {
   175  		t.Fatalf("err: %s", err)
   176  	}
   177  	if err := os.Chdir(testFixturePath("refresh")); err != nil {
   178  		t.Fatalf("err: %s", err)
   179  	}
   180  	defer os.Chdir(cwd)
   181  
   182  	state := testState()
   183  	statePath := testStateFile(t, state)
   184  
   185  	p := testProvider()
   186  	view, done := testView(t)
   187  	c := &RefreshCommand{
   188  		Meta: Meta{
   189  			testingOverrides: metaOverridesForProvider(p),
   190  			View:             view,
   191  		},
   192  	}
   193  
   194  	p.GetProviderSchemaResponse = refreshFixtureSchema()
   195  	p.ReadResourceFn = nil
   196  	p.ReadResourceResponse = &providers.ReadResourceResponse{
   197  		NewState: cty.ObjectVal(map[string]cty.Value{
   198  			"id": cty.StringVal("yes"),
   199  		}),
   200  	}
   201  
   202  	args := []string{
   203  		"-state", statePath,
   204  	}
   205  	code := c.Run(args)
   206  	output := done(t)
   207  	if code != 0 {
   208  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   209  	}
   210  
   211  	if !p.ReadResourceCalled {
   212  		t.Fatal("ReadResource should have been called")
   213  	}
   214  
   215  	f, err := os.Open(statePath)
   216  	if err != nil {
   217  		t.Fatalf("err: %s", err)
   218  	}
   219  
   220  	newStateFile, err := statefile.Read(f)
   221  	f.Close()
   222  	if err != nil {
   223  		t.Fatalf("err: %s", err)
   224  	}
   225  
   226  	actual := strings.TrimSpace(newStateFile.State.String())
   227  	expected := strings.TrimSpace(testRefreshCwdStr)
   228  	if actual != expected {
   229  		t.Fatalf("bad:\n\n%s", actual)
   230  	}
   231  }
   232  
   233  func TestRefresh_defaultState(t *testing.T) {
   234  	// Create a temporary working directory that is empty
   235  	td := tempDir(t)
   236  	testCopyDir(t, testFixturePath("refresh"), td)
   237  	defer os.RemoveAll(td)
   238  	defer testChdir(t, td)()
   239  
   240  	originalState := testState()
   241  
   242  	// Write the state file in a temporary directory with the
   243  	// default filename.
   244  	statePath := testStateFile(t, originalState)
   245  
   246  	localState := statemgr.NewFilesystem(statePath)
   247  	if err := localState.RefreshState(); err != nil {
   248  		t.Fatal(err)
   249  	}
   250  	s := localState.State()
   251  	if s == nil {
   252  		t.Fatal("empty test state")
   253  	}
   254  
   255  	// Change to that directory
   256  	cwd, err := os.Getwd()
   257  	if err != nil {
   258  		t.Fatalf("err: %s", err)
   259  	}
   260  	if err := os.Chdir(filepath.Dir(statePath)); err != nil {
   261  		t.Fatalf("err: %s", err)
   262  	}
   263  	defer os.Chdir(cwd)
   264  
   265  	p := testProvider()
   266  	view, done := testView(t)
   267  	c := &RefreshCommand{
   268  		Meta: Meta{
   269  			testingOverrides: metaOverridesForProvider(p),
   270  			View:             view,
   271  		},
   272  	}
   273  
   274  	p.GetProviderSchemaResponse = refreshFixtureSchema()
   275  	p.ReadResourceFn = nil
   276  	p.ReadResourceResponse = &providers.ReadResourceResponse{
   277  		NewState: cty.ObjectVal(map[string]cty.Value{
   278  			"id": cty.StringVal("yes"),
   279  		}),
   280  	}
   281  
   282  	args := []string{
   283  		"-state", statePath,
   284  	}
   285  	code := c.Run(args)
   286  	output := done(t)
   287  	if code != 0 {
   288  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   289  	}
   290  
   291  	if !p.ReadResourceCalled {
   292  		t.Fatal("ReadResource should have been called")
   293  	}
   294  
   295  	newState := testStateRead(t, statePath)
   296  
   297  	actual := newState.RootModule().Resources["test_instance.foo"].Instances[addrs.NoKey].Current
   298  	expected := &states.ResourceInstanceObjectSrc{
   299  		Status:       states.ObjectReady,
   300  		AttrsJSON:    []byte("{\n            \"ami\": null,\n            \"id\": \"yes\"\n          }"),
   301  		Dependencies: []addrs.ConfigResource{},
   302  	}
   303  	if !reflect.DeepEqual(actual, expected) {
   304  		t.Fatalf("wrong new object\ngot:  %swant: %s", spew.Sdump(actual), spew.Sdump(expected))
   305  	}
   306  
   307  	backupState := testStateRead(t, statePath+DefaultBackupExtension)
   308  
   309  	actual = backupState.RootModule().Resources["test_instance.foo"].Instances[addrs.NoKey].Current
   310  	expected = originalState.RootModule().Resources["test_instance.foo"].Instances[addrs.NoKey].Current
   311  	if !reflect.DeepEqual(actual, expected) {
   312  		t.Fatalf("wrong new object\ngot:  %swant: %s", spew.Sdump(actual), spew.Sdump(expected))
   313  	}
   314  }
   315  
   316  func TestRefresh_outPath(t *testing.T) {
   317  	// Create a temporary working directory that is empty
   318  	td := tempDir(t)
   319  	testCopyDir(t, testFixturePath("refresh"), td)
   320  	defer os.RemoveAll(td)
   321  	defer testChdir(t, td)()
   322  
   323  	state := testState()
   324  	statePath := testStateFile(t, state)
   325  
   326  	// Output path
   327  	outf, err := ioutil.TempFile(testingDir, "tf")
   328  	if err != nil {
   329  		t.Fatalf("err: %s", err)
   330  	}
   331  	outPath := outf.Name()
   332  	outf.Close()
   333  	os.Remove(outPath)
   334  
   335  	p := testProvider()
   336  	view, done := testView(t)
   337  	c := &RefreshCommand{
   338  		Meta: Meta{
   339  			testingOverrides: metaOverridesForProvider(p),
   340  			View:             view,
   341  		},
   342  	}
   343  
   344  	p.GetProviderSchemaResponse = refreshFixtureSchema()
   345  	p.ReadResourceFn = nil
   346  	p.ReadResourceResponse = &providers.ReadResourceResponse{
   347  		NewState: cty.ObjectVal(map[string]cty.Value{
   348  			"id": cty.StringVal("yes"),
   349  		}),
   350  	}
   351  
   352  	args := []string{
   353  		"-state", statePath,
   354  		"-state-out", outPath,
   355  	}
   356  	code := c.Run(args)
   357  	output := done(t)
   358  	if code != 0 {
   359  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   360  	}
   361  
   362  	newState := testStateRead(t, statePath)
   363  	if !reflect.DeepEqual(newState, state) {
   364  		t.Fatalf("bad: %#v", newState)
   365  	}
   366  
   367  	newState = testStateRead(t, outPath)
   368  	actual := newState.RootModule().Resources["test_instance.foo"].Instances[addrs.NoKey].Current
   369  	expected := &states.ResourceInstanceObjectSrc{
   370  		Status:       states.ObjectReady,
   371  		AttrsJSON:    []byte("{\n            \"ami\": null,\n            \"id\": \"yes\"\n          }"),
   372  		Dependencies: []addrs.ConfigResource{},
   373  	}
   374  	if !reflect.DeepEqual(actual, expected) {
   375  		t.Fatalf("wrong new object\ngot:  %swant: %s", spew.Sdump(actual), spew.Sdump(expected))
   376  	}
   377  
   378  	if _, err := os.Stat(outPath + DefaultBackupExtension); !os.IsNotExist(err) {
   379  		if err != nil {
   380  			t.Fatalf("failed to test for backup file: %s", err)
   381  		}
   382  		t.Fatalf("backup file exists, but it should not because output file did not initially exist")
   383  	}
   384  }
   385  
   386  func TestRefresh_var(t *testing.T) {
   387  	// Create a temporary working directory that is empty
   388  	td := tempDir(t)
   389  	testCopyDir(t, testFixturePath("refresh-var"), td)
   390  	defer os.RemoveAll(td)
   391  	defer testChdir(t, td)()
   392  
   393  	state := testState()
   394  	statePath := testStateFile(t, state)
   395  
   396  	p := testProvider()
   397  	view, done := testView(t)
   398  	c := &RefreshCommand{
   399  		Meta: Meta{
   400  			testingOverrides: metaOverridesForProvider(p),
   401  			View:             view,
   402  		},
   403  	}
   404  	p.GetProviderSchemaResponse = refreshVarFixtureSchema()
   405  
   406  	args := []string{
   407  		"-var", "foo=bar",
   408  		"-state", statePath,
   409  	}
   410  	code := c.Run(args)
   411  	output := done(t)
   412  	if code != 0 {
   413  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   414  	}
   415  
   416  	if !p.ConfigureProviderCalled {
   417  		t.Fatal("configure should be called")
   418  	}
   419  	if got, want := p.ConfigureProviderRequest.Config.GetAttr("value"), cty.StringVal("bar"); !want.RawEquals(got) {
   420  		t.Fatalf("wrong provider configuration\ngot:  %#v\nwant: %#v", got, want)
   421  	}
   422  }
   423  
   424  func TestRefresh_varFile(t *testing.T) {
   425  	// Create a temporary working directory that is empty
   426  	td := tempDir(t)
   427  	testCopyDir(t, testFixturePath("refresh-var"), td)
   428  	defer os.RemoveAll(td)
   429  	defer testChdir(t, td)()
   430  
   431  	state := testState()
   432  	statePath := testStateFile(t, state)
   433  
   434  	p := testProvider()
   435  	view, done := testView(t)
   436  	c := &RefreshCommand{
   437  		Meta: Meta{
   438  			testingOverrides: metaOverridesForProvider(p),
   439  			View:             view,
   440  		},
   441  	}
   442  	p.GetProviderSchemaResponse = refreshVarFixtureSchema()
   443  
   444  	varFilePath := testTempFile(t)
   445  	if err := ioutil.WriteFile(varFilePath, []byte(refreshVarFile), 0644); err != nil {
   446  		t.Fatalf("err: %s", err)
   447  	}
   448  
   449  	args := []string{
   450  		"-var-file", varFilePath,
   451  		"-state", statePath,
   452  	}
   453  	code := c.Run(args)
   454  	output := done(t)
   455  	if code != 0 {
   456  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   457  	}
   458  
   459  	if !p.ConfigureProviderCalled {
   460  		t.Fatal("configure should be called")
   461  	}
   462  	if got, want := p.ConfigureProviderRequest.Config.GetAttr("value"), cty.StringVal("bar"); !want.RawEquals(got) {
   463  		t.Fatalf("wrong provider configuration\ngot:  %#v\nwant: %#v", got, want)
   464  	}
   465  }
   466  
   467  func TestRefresh_varFileDefault(t *testing.T) {
   468  	// Create a temporary working directory that is empty
   469  	td := tempDir(t)
   470  	testCopyDir(t, testFixturePath("refresh-var"), td)
   471  	defer os.RemoveAll(td)
   472  	defer testChdir(t, td)()
   473  
   474  	state := testState()
   475  	statePath := testStateFile(t, state)
   476  
   477  	p := testProvider()
   478  	view, done := testView(t)
   479  	c := &RefreshCommand{
   480  		Meta: Meta{
   481  			testingOverrides: metaOverridesForProvider(p),
   482  			View:             view,
   483  		},
   484  	}
   485  	p.GetProviderSchemaResponse = refreshVarFixtureSchema()
   486  
   487  	varFilePath := filepath.Join(td, "terraform.tfvars")
   488  	if err := ioutil.WriteFile(varFilePath, []byte(refreshVarFile), 0644); err != nil {
   489  		t.Fatalf("err: %s", err)
   490  	}
   491  
   492  	args := []string{
   493  		"-state", statePath,
   494  	}
   495  	code := c.Run(args)
   496  	output := done(t)
   497  	if code != 0 {
   498  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   499  	}
   500  
   501  	if !p.ConfigureProviderCalled {
   502  		t.Fatal("configure should be called")
   503  	}
   504  	if got, want := p.ConfigureProviderRequest.Config.GetAttr("value"), cty.StringVal("bar"); !want.RawEquals(got) {
   505  		t.Fatalf("wrong provider configuration\ngot:  %#v\nwant: %#v", got, want)
   506  	}
   507  }
   508  
   509  func TestRefresh_varsUnset(t *testing.T) {
   510  	// Create a temporary working directory that is empty
   511  	td := tempDir(t)
   512  	testCopyDir(t, testFixturePath("refresh-unset-var"), td)
   513  	defer os.RemoveAll(td)
   514  	defer testChdir(t, td)()
   515  
   516  	// Disable test mode so input would be asked
   517  	test = false
   518  	defer func() { test = true }()
   519  
   520  	defaultInputReader = bytes.NewBufferString("bar\n")
   521  
   522  	state := testState()
   523  	statePath := testStateFile(t, state)
   524  
   525  	p := testProvider()
   526  	ui := new(cli.MockUi)
   527  	view, done := testView(t)
   528  	c := &RefreshCommand{
   529  		Meta: Meta{
   530  			testingOverrides: metaOverridesForProvider(p),
   531  			Ui:               ui,
   532  			View:             view,
   533  		},
   534  	}
   535  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
   536  		ResourceTypes: map[string]providers.Schema{
   537  			"test_instance": {
   538  				Block: &configschema.Block{
   539  					Attributes: map[string]*configschema.Attribute{
   540  						"id":  {Type: cty.String, Optional: true, Computed: true},
   541  						"ami": {Type: cty.String, Optional: true},
   542  					},
   543  				},
   544  			},
   545  		},
   546  	}
   547  
   548  	args := []string{
   549  		"-state", statePath,
   550  	}
   551  	code := c.Run(args)
   552  	output := done(t)
   553  	if code != 0 {
   554  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   555  	}
   556  }
   557  
   558  func TestRefresh_backup(t *testing.T) {
   559  	// Create a temporary working directory that is empty
   560  	td := tempDir(t)
   561  	testCopyDir(t, testFixturePath("refresh"), td)
   562  	defer os.RemoveAll(td)
   563  	defer testChdir(t, td)()
   564  
   565  	state := testState()
   566  	statePath := testStateFile(t, state)
   567  
   568  	// Output path
   569  	outf, err := ioutil.TempFile(testingDir, "tf")
   570  	if err != nil {
   571  		t.Fatalf("err: %s", err)
   572  	}
   573  	outPath := outf.Name()
   574  	defer outf.Close()
   575  
   576  	// Need to put some state content in the output file so that there's
   577  	// something to back up.
   578  	err = statefile.Write(statefile.New(state, "baz", 0), outf)
   579  	if err != nil {
   580  		t.Fatalf("error writing initial output state file %s", err)
   581  	}
   582  
   583  	// Backup path
   584  	backupf, err := ioutil.TempFile(testingDir, "tf")
   585  	if err != nil {
   586  		t.Fatalf("err: %s", err)
   587  	}
   588  	backupPath := backupf.Name()
   589  	backupf.Close()
   590  	os.Remove(backupPath)
   591  
   592  	p := testProvider()
   593  	view, done := testView(t)
   594  	c := &RefreshCommand{
   595  		Meta: Meta{
   596  			testingOverrides: metaOverridesForProvider(p),
   597  			View:             view,
   598  		},
   599  	}
   600  
   601  	p.GetProviderSchemaResponse = refreshFixtureSchema()
   602  	p.ReadResourceFn = nil
   603  	p.ReadResourceResponse = &providers.ReadResourceResponse{
   604  		NewState: cty.ObjectVal(map[string]cty.Value{
   605  			"id": cty.StringVal("changed"),
   606  		}),
   607  	}
   608  
   609  	args := []string{
   610  		"-state", statePath,
   611  		"-state-out", outPath,
   612  		"-backup", backupPath,
   613  	}
   614  	code := c.Run(args)
   615  	output := done(t)
   616  	if code != 0 {
   617  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   618  	}
   619  
   620  	newState := testStateRead(t, statePath)
   621  	if !cmp.Equal(newState, state, cmpopts.EquateEmpty()) {
   622  		t.Fatalf("got:\n%s\nexpected:\n%s\n", newState, state)
   623  	}
   624  
   625  	newState = testStateRead(t, outPath)
   626  	actual := newState.RootModule().Resources["test_instance.foo"].Instances[addrs.NoKey].Current
   627  	expected := &states.ResourceInstanceObjectSrc{
   628  		Status:       states.ObjectReady,
   629  		AttrsJSON:    []byte("{\n            \"ami\": null,\n            \"id\": \"changed\"\n          }"),
   630  		Dependencies: []addrs.ConfigResource{},
   631  	}
   632  	if !reflect.DeepEqual(actual, expected) {
   633  		t.Fatalf("wrong new object\ngot:  %swant: %s", spew.Sdump(actual), spew.Sdump(expected))
   634  	}
   635  
   636  	backupState := testStateRead(t, backupPath)
   637  	actualStr := strings.TrimSpace(backupState.String())
   638  	expectedStr := strings.TrimSpace(state.String())
   639  	if actualStr != expectedStr {
   640  		t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr)
   641  	}
   642  }
   643  
   644  func TestRefresh_disableBackup(t *testing.T) {
   645  	// Create a temporary working directory that is empty
   646  	td := tempDir(t)
   647  	testCopyDir(t, testFixturePath("refresh"), td)
   648  	defer os.RemoveAll(td)
   649  	defer testChdir(t, td)()
   650  
   651  	state := testState()
   652  	statePath := testStateFile(t, state)
   653  
   654  	// Output path
   655  	outf, err := ioutil.TempFile(testingDir, "tf")
   656  	if err != nil {
   657  		t.Fatalf("err: %s", err)
   658  	}
   659  	outPath := outf.Name()
   660  	outf.Close()
   661  	os.Remove(outPath)
   662  
   663  	p := testProvider()
   664  	view, done := testView(t)
   665  	c := &RefreshCommand{
   666  		Meta: Meta{
   667  			testingOverrides: metaOverridesForProvider(p),
   668  			View:             view,
   669  		},
   670  	}
   671  
   672  	p.GetProviderSchemaResponse = refreshFixtureSchema()
   673  	p.ReadResourceFn = nil
   674  	p.ReadResourceResponse = &providers.ReadResourceResponse{
   675  		NewState: cty.ObjectVal(map[string]cty.Value{
   676  			"id": cty.StringVal("yes"),
   677  		}),
   678  	}
   679  
   680  	args := []string{
   681  		"-state", statePath,
   682  		"-state-out", outPath,
   683  		"-backup", "-",
   684  	}
   685  	code := c.Run(args)
   686  	output := done(t)
   687  	if code != 0 {
   688  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   689  	}
   690  
   691  	newState := testStateRead(t, statePath)
   692  	if !cmp.Equal(state, newState, equateEmpty) {
   693  		spew.Config.DisableMethods = true
   694  		fmt.Println(cmp.Diff(state, newState, equateEmpty))
   695  		t.Fatalf("bad: %s", newState)
   696  	}
   697  
   698  	newState = testStateRead(t, outPath)
   699  	actual := newState.RootModule().Resources["test_instance.foo"].Instances[addrs.NoKey].Current
   700  	expected := &states.ResourceInstanceObjectSrc{
   701  		Status:       states.ObjectReady,
   702  		AttrsJSON:    []byte("{\n            \"ami\": null,\n            \"id\": \"yes\"\n          }"),
   703  		Dependencies: []addrs.ConfigResource{},
   704  	}
   705  	if !reflect.DeepEqual(actual, expected) {
   706  		t.Fatalf("wrong new object\ngot:  %swant: %s", spew.Sdump(actual), spew.Sdump(expected))
   707  	}
   708  
   709  	// Ensure there is no backup
   710  	_, err = os.Stat(outPath + DefaultBackupExtension)
   711  	if err == nil || !os.IsNotExist(err) {
   712  		t.Fatalf("backup should not exist")
   713  	}
   714  	_, err = os.Stat("-")
   715  	if err == nil || !os.IsNotExist(err) {
   716  		t.Fatalf("backup should not exist")
   717  	}
   718  }
   719  
   720  func TestRefresh_displaysOutputs(t *testing.T) {
   721  	// Create a temporary working directory that is empty
   722  	td := tempDir(t)
   723  	testCopyDir(t, testFixturePath("refresh-output"), td)
   724  	defer os.RemoveAll(td)
   725  	defer testChdir(t, td)()
   726  
   727  	state := testState()
   728  	statePath := testStateFile(t, state)
   729  
   730  	p := testProvider()
   731  	view, done := testView(t)
   732  	c := &RefreshCommand{
   733  		Meta: Meta{
   734  			testingOverrides: metaOverridesForProvider(p),
   735  			View:             view,
   736  		},
   737  	}
   738  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
   739  		ResourceTypes: map[string]providers.Schema{
   740  			"test_instance": {
   741  				Block: &configschema.Block{
   742  					Attributes: map[string]*configschema.Attribute{
   743  						"id":  {Type: cty.String, Optional: true, Computed: true},
   744  						"ami": {Type: cty.String, Optional: true},
   745  					},
   746  				},
   747  			},
   748  		},
   749  	}
   750  
   751  	args := []string{
   752  		"-state", statePath,
   753  	}
   754  	code := c.Run(args)
   755  	output := done(t)
   756  	if code != 0 {
   757  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   758  	}
   759  
   760  	// Test that outputs were displayed
   761  	outputValue := "foo.example.com"
   762  	actual := output.Stdout()
   763  	if !strings.Contains(actual, outputValue) {
   764  		t.Fatalf("Expected:\n%s\n\nTo include: %q", actual, outputValue)
   765  	}
   766  }
   767  
   768  // Config with multiple resources, targeting refresh of a subset
   769  func TestRefresh_targeted(t *testing.T) {
   770  	td := tempDir(t)
   771  	testCopyDir(t, testFixturePath("refresh-targeted"), td)
   772  	defer os.RemoveAll(td)
   773  	defer testChdir(t, td)()
   774  
   775  	state := testState()
   776  	statePath := testStateFile(t, state)
   777  
   778  	p := testProvider()
   779  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
   780  		ResourceTypes: map[string]providers.Schema{
   781  			"test_instance": {
   782  				Block: &configschema.Block{
   783  					Attributes: map[string]*configschema.Attribute{
   784  						"id": {Type: cty.String, Computed: true},
   785  					},
   786  				},
   787  			},
   788  		},
   789  	}
   790  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
   791  		return providers.PlanResourceChangeResponse{
   792  			PlannedState: req.ProposedNewState,
   793  		}
   794  	}
   795  
   796  	view, done := testView(t)
   797  	c := &RefreshCommand{
   798  		Meta: Meta{
   799  			testingOverrides: metaOverridesForProvider(p),
   800  			View:             view,
   801  		},
   802  	}
   803  
   804  	args := []string{
   805  		"-target", "test_instance.foo",
   806  		"-state", statePath,
   807  	}
   808  	code := c.Run(args)
   809  	output := done(t)
   810  	if code != 0 {
   811  		t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   812  	}
   813  
   814  	got := output.Stdout()
   815  	if want := "test_instance.foo: Refreshing"; !strings.Contains(got, want) {
   816  		t.Fatalf("expected output to contain %q, got:\n%s", want, got)
   817  	}
   818  	if doNotWant := "test_instance.bar: Refreshing"; strings.Contains(got, doNotWant) {
   819  		t.Fatalf("expected output not to contain %q, got:\n%s", doNotWant, got)
   820  	}
   821  }
   822  
   823  // Diagnostics for invalid -target flags
   824  func TestRefresh_targetFlagsDiags(t *testing.T) {
   825  	testCases := map[string]string{
   826  		"test_instance.": "Dot must be followed by attribute name.",
   827  		"test_instance":  "Resource specification must include a resource type and name.",
   828  	}
   829  
   830  	for target, wantDiag := range testCases {
   831  		t.Run(target, func(t *testing.T) {
   832  			td := testTempDir(t)
   833  			defer os.RemoveAll(td)
   834  			defer testChdir(t, td)()
   835  
   836  			view, done := testView(t)
   837  			c := &RefreshCommand{
   838  				Meta: Meta{
   839  					View: view,
   840  				},
   841  			}
   842  
   843  			args := []string{
   844  				"-target", target,
   845  			}
   846  			code := c.Run(args)
   847  			output := done(t)
   848  			if code != 1 {
   849  				t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   850  			}
   851  
   852  			got := output.Stderr()
   853  			if !strings.Contains(got, target) {
   854  				t.Fatalf("bad error output, want %q, got:\n%s", target, got)
   855  			}
   856  			if !strings.Contains(got, wantDiag) {
   857  				t.Fatalf("bad error output, want %q, got:\n%s", wantDiag, got)
   858  			}
   859  		})
   860  	}
   861  }
   862  
   863  func TestRefresh_warnings(t *testing.T) {
   864  	// Create a temporary working directory that is empty
   865  	td := tempDir(t)
   866  	testCopyDir(t, testFixturePath("apply"), td)
   867  	defer os.RemoveAll(td)
   868  	defer testChdir(t, td)()
   869  
   870  	p := testProvider()
   871  	p.GetProviderSchemaResponse = refreshFixtureSchema()
   872  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
   873  		return providers.PlanResourceChangeResponse{
   874  			PlannedState: req.ProposedNewState,
   875  			Diagnostics: tfdiags.Diagnostics{
   876  				tfdiags.SimpleWarning("warning 1"),
   877  				tfdiags.SimpleWarning("warning 2"),
   878  			},
   879  		}
   880  	}
   881  
   882  	t.Run("full warnings", func(t *testing.T) {
   883  		view, done := testView(t)
   884  		c := &RefreshCommand{
   885  			Meta: Meta{
   886  				testingOverrides: metaOverridesForProvider(p),
   887  				View:             view,
   888  			},
   889  		}
   890  
   891  		code := c.Run([]string{})
   892  		output := done(t)
   893  		if code != 0 {
   894  			t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   895  		}
   896  		wantWarnings := []string{
   897  			"warning 1",
   898  			"warning 2",
   899  		}
   900  		for _, want := range wantWarnings {
   901  			if !strings.Contains(output.Stdout(), want) {
   902  				t.Errorf("missing warning %s", want)
   903  			}
   904  		}
   905  	})
   906  
   907  	t.Run("compact warnings", func(t *testing.T) {
   908  		view, done := testView(t)
   909  		c := &RefreshCommand{
   910  			Meta: Meta{
   911  				testingOverrides: metaOverridesForProvider(p),
   912  				View:             view,
   913  			},
   914  		}
   915  
   916  		code := c.Run([]string{"-compact-warnings"})
   917  		output := done(t)
   918  		if code != 0 {
   919  			t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
   920  		}
   921  		// the output should contain 2 warnings and a message about -compact-warnings
   922  		wantWarnings := []string{
   923  			"warning 1",
   924  			"warning 2",
   925  			"To see the full warning notes, run Terraform without -compact-warnings.",
   926  		}
   927  		for _, want := range wantWarnings {
   928  			if !strings.Contains(output.Stdout(), want) {
   929  				t.Errorf("missing warning %s", want)
   930  			}
   931  		}
   932  	})
   933  }
   934  
   935  // configuration in testdata/refresh . This schema should be
   936  // assigned to a mock provider named "test".
   937  func refreshFixtureSchema() *providers.GetProviderSchemaResponse {
   938  	return &providers.GetProviderSchemaResponse{
   939  		ResourceTypes: map[string]providers.Schema{
   940  			"test_instance": {
   941  				Block: &configschema.Block{
   942  					Attributes: map[string]*configschema.Attribute{
   943  						"id":  {Type: cty.String, Optional: true, Computed: true},
   944  						"ami": {Type: cty.String, Optional: true},
   945  					},
   946  				},
   947  			},
   948  		},
   949  	}
   950  }
   951  
   952  // refreshVarFixtureSchema returns a schema suitable for processing the
   953  // configuration in testdata/refresh-var . This schema should be
   954  // assigned to a mock provider named "test".
   955  func refreshVarFixtureSchema() *providers.GetProviderSchemaResponse {
   956  	return &providers.GetProviderSchemaResponse{
   957  		Provider: providers.Schema{
   958  			Block: &configschema.Block{
   959  				Attributes: map[string]*configschema.Attribute{
   960  					"value": {Type: cty.String, Optional: true},
   961  				},
   962  			},
   963  		},
   964  		ResourceTypes: map[string]providers.Schema{
   965  			"test_instance": {
   966  				Block: &configschema.Block{
   967  					Attributes: map[string]*configschema.Attribute{
   968  						"id": {Type: cty.String, Optional: true, Computed: true},
   969  					},
   970  				},
   971  			},
   972  		},
   973  	}
   974  }
   975  
   976  const refreshVarFile = `
   977  foo = "bar"
   978  `
   979  
   980  const testRefreshStr = `
   981  test_instance.foo:
   982    ID = yes
   983    provider = provider["registry.terraform.io/hashicorp/test"]
   984  `
   985  const testRefreshCwdStr = `
   986  test_instance.foo:
   987    ID = yes
   988    provider = provider["registry.terraform.io/hashicorp/test"]
   989  `