github.com/opentofu/opentofu@v1.7.1/internal/command/refresh_test.go (about)

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