github.com/kevinklinger/open_terraform@v1.3.6/noninternal/backend/local/backend_refresh_test.go (about)

     1  package local
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/kevinklinger/open_terraform/noninternal/addrs"
    10  	"github.com/kevinklinger/open_terraform/noninternal/backend"
    11  	"github.com/kevinklinger/open_terraform/noninternal/command/arguments"
    12  	"github.com/kevinklinger/open_terraform/noninternal/command/clistate"
    13  	"github.com/kevinklinger/open_terraform/noninternal/command/views"
    14  	"github.com/kevinklinger/open_terraform/noninternal/configs/configschema"
    15  	"github.com/kevinklinger/open_terraform/noninternal/depsfile"
    16  	"github.com/kevinklinger/open_terraform/noninternal/initwd"
    17  	"github.com/kevinklinger/open_terraform/noninternal/providers"
    18  	"github.com/kevinklinger/open_terraform/noninternal/states"
    19  	"github.com/kevinklinger/open_terraform/noninternal/terminal"
    20  	"github.com/kevinklinger/open_terraform/noninternal/terraform"
    21  
    22  	"github.com/zclconf/go-cty/cty"
    23  )
    24  
    25  func TestLocal_refresh(t *testing.T) {
    26  	b := TestLocal(t)
    27  
    28  	p := TestLocalProvider(t, b, "test", refreshFixtureSchema())
    29  	testStateFile(t, b.StatePath, testRefreshState())
    30  
    31  	p.ReadResourceFn = nil
    32  	p.ReadResourceResponse = &providers.ReadResourceResponse{NewState: cty.ObjectVal(map[string]cty.Value{
    33  		"id": cty.StringVal("yes"),
    34  	})}
    35  
    36  	op, configCleanup, done := testOperationRefresh(t, "./testdata/refresh")
    37  	defer configCleanup()
    38  	defer done(t)
    39  
    40  	run, err := b.Operation(context.Background(), op)
    41  	if err != nil {
    42  		t.Fatalf("bad: %s", err)
    43  	}
    44  	<-run.Done()
    45  
    46  	if !p.ReadResourceCalled {
    47  		t.Fatal("ReadResource should be called")
    48  	}
    49  
    50  	checkState(t, b.StateOutPath, `
    51  test_instance.foo:
    52    ID = yes
    53    provider = provider["registry.terraform.io/hashicorp/test"]
    54  	`)
    55  
    56  	// the backend should be unlocked after a run
    57  	assertBackendStateUnlocked(t, b)
    58  }
    59  
    60  func TestLocal_refreshInput(t *testing.T) {
    61  	b := TestLocal(t)
    62  
    63  	schema := &terraform.ProviderSchema{
    64  		Provider: &configschema.Block{
    65  			Attributes: map[string]*configschema.Attribute{
    66  				"value": {Type: cty.String, Optional: true},
    67  			},
    68  		},
    69  		ResourceTypes: map[string]*configschema.Block{
    70  			"test_instance": {
    71  				Attributes: map[string]*configschema.Attribute{
    72  					"id":  {Type: cty.String, Computed: true},
    73  					"foo": {Type: cty.String, Optional: true},
    74  					"ami": {Type: cty.String, Optional: true},
    75  				},
    76  			},
    77  		},
    78  	}
    79  
    80  	p := TestLocalProvider(t, b, "test", schema)
    81  	testStateFile(t, b.StatePath, testRefreshState())
    82  
    83  	p.ReadResourceFn = nil
    84  	p.ReadResourceResponse = &providers.ReadResourceResponse{NewState: cty.ObjectVal(map[string]cty.Value{
    85  		"id": cty.StringVal("yes"),
    86  	})}
    87  	p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
    88  		val := req.Config.GetAttr("value")
    89  		if val.IsNull() || val.AsString() != "bar" {
    90  			resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("incorrect value %#v", val))
    91  		}
    92  
    93  		return
    94  	}
    95  
    96  	// Enable input asking since it is normally disabled by default
    97  	b.OpInput = true
    98  	b.ContextOpts.UIInput = &terraform.MockUIInput{InputReturnString: "bar"}
    99  
   100  	op, configCleanup, done := testOperationRefresh(t, "./testdata/refresh-var-unset")
   101  	defer configCleanup()
   102  	defer done(t)
   103  	op.UIIn = b.ContextOpts.UIInput
   104  
   105  	run, err := b.Operation(context.Background(), op)
   106  	if err != nil {
   107  		t.Fatalf("bad: %s", err)
   108  	}
   109  	<-run.Done()
   110  
   111  	if !p.ReadResourceCalled {
   112  		t.Fatal("ReadResource should be called")
   113  	}
   114  
   115  	checkState(t, b.StateOutPath, `
   116  test_instance.foo:
   117    ID = yes
   118    provider = provider["registry.terraform.io/hashicorp/test"]
   119  	`)
   120  }
   121  
   122  func TestLocal_refreshValidate(t *testing.T) {
   123  	b := TestLocal(t)
   124  	p := TestLocalProvider(t, b, "test", refreshFixtureSchema())
   125  	testStateFile(t, b.StatePath, testRefreshState())
   126  	p.ReadResourceFn = nil
   127  	p.ReadResourceResponse = &providers.ReadResourceResponse{NewState: cty.ObjectVal(map[string]cty.Value{
   128  		"id": cty.StringVal("yes"),
   129  	})}
   130  
   131  	// Enable validation
   132  	b.OpValidation = true
   133  
   134  	op, configCleanup, done := testOperationRefresh(t, "./testdata/refresh")
   135  	defer configCleanup()
   136  	defer done(t)
   137  
   138  	run, err := b.Operation(context.Background(), op)
   139  	if err != nil {
   140  		t.Fatalf("bad: %s", err)
   141  	}
   142  	<-run.Done()
   143  
   144  	checkState(t, b.StateOutPath, `
   145  test_instance.foo:
   146    ID = yes
   147    provider = provider["registry.terraform.io/hashicorp/test"]
   148  	`)
   149  }
   150  
   151  func TestLocal_refreshValidateProviderConfigured(t *testing.T) {
   152  	b := TestLocal(t)
   153  
   154  	schema := &terraform.ProviderSchema{
   155  		Provider: &configschema.Block{
   156  			Attributes: map[string]*configschema.Attribute{
   157  				"value": {Type: cty.String, Optional: true},
   158  			},
   159  		},
   160  		ResourceTypes: map[string]*configschema.Block{
   161  			"test_instance": {
   162  				Attributes: map[string]*configschema.Attribute{
   163  					"id":  {Type: cty.String, Computed: true},
   164  					"ami": {Type: cty.String, Optional: true},
   165  				},
   166  			},
   167  		},
   168  	}
   169  
   170  	p := TestLocalProvider(t, b, "test", schema)
   171  	testStateFile(t, b.StatePath, testRefreshState())
   172  	p.ReadResourceFn = nil
   173  	p.ReadResourceResponse = &providers.ReadResourceResponse{NewState: cty.ObjectVal(map[string]cty.Value{
   174  		"id": cty.StringVal("yes"),
   175  	})}
   176  
   177  	// Enable validation
   178  	b.OpValidation = true
   179  
   180  	op, configCleanup, done := testOperationRefresh(t, "./testdata/refresh-provider-config")
   181  	defer configCleanup()
   182  	defer done(t)
   183  
   184  	run, err := b.Operation(context.Background(), op)
   185  	if err != nil {
   186  		t.Fatalf("bad: %s", err)
   187  	}
   188  	<-run.Done()
   189  
   190  	if !p.ValidateProviderConfigCalled {
   191  		t.Fatal("Validate provider config should be called")
   192  	}
   193  
   194  	checkState(t, b.StateOutPath, `
   195  test_instance.foo:
   196    ID = yes
   197    provider = provider["registry.terraform.io/hashicorp/test"]
   198  	`)
   199  }
   200  
   201  // This test validates the state lacking behavior when the inner call to
   202  // Context() fails
   203  func TestLocal_refresh_context_error(t *testing.T) {
   204  	b := TestLocal(t)
   205  	testStateFile(t, b.StatePath, testRefreshState())
   206  	op, configCleanup, done := testOperationRefresh(t, "./testdata/apply")
   207  	defer configCleanup()
   208  	defer done(t)
   209  
   210  	// we coerce a failure in Context() by omitting the provider schema
   211  
   212  	run, err := b.Operation(context.Background(), op)
   213  	if err != nil {
   214  		t.Fatalf("bad: %s", err)
   215  	}
   216  	<-run.Done()
   217  	if run.Result == backend.OperationSuccess {
   218  		t.Fatal("operation succeeded; want failure")
   219  	}
   220  	assertBackendStateUnlocked(t, b)
   221  }
   222  
   223  func TestLocal_refreshEmptyState(t *testing.T) {
   224  	b := TestLocal(t)
   225  
   226  	p := TestLocalProvider(t, b, "test", refreshFixtureSchema())
   227  	testStateFile(t, b.StatePath, states.NewState())
   228  
   229  	p.ReadResourceFn = nil
   230  	p.ReadResourceResponse = &providers.ReadResourceResponse{NewState: cty.ObjectVal(map[string]cty.Value{
   231  		"id": cty.StringVal("yes"),
   232  	})}
   233  
   234  	op, configCleanup, done := testOperationRefresh(t, "./testdata/refresh")
   235  	defer configCleanup()
   236  
   237  	run, err := b.Operation(context.Background(), op)
   238  	if err != nil {
   239  		t.Fatalf("bad: %s", err)
   240  	}
   241  	<-run.Done()
   242  
   243  	output := done(t)
   244  
   245  	if stderr := output.Stderr(); stderr != "" {
   246  		t.Fatalf("expected only warning diags, got errors: %s", stderr)
   247  	}
   248  	if got, want := output.Stdout(), "Warning: Empty or non-existent state"; !strings.Contains(got, want) {
   249  		t.Errorf("wrong diags\n got: %s\nwant: %s", got, want)
   250  	}
   251  
   252  	// the backend should be unlocked after a run
   253  	assertBackendStateUnlocked(t, b)
   254  }
   255  
   256  func testOperationRefresh(t *testing.T, configDir string) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
   257  	t.Helper()
   258  
   259  	_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
   260  
   261  	streams, done := terminal.StreamsForTesting(t)
   262  	view := views.NewOperation(arguments.ViewHuman, false, views.NewView(streams))
   263  
   264  	// Many of our tests use an overridden "test" provider that's just in-memory
   265  	// inside the test process, not a separate plugin on disk.
   266  	depLocks := depsfile.NewLocks()
   267  	depLocks.SetProviderOverridden(addrs.MustParseProviderSourceString("registry.terraform.io/hashicorp/test"))
   268  
   269  	return &backend.Operation{
   270  		Type:            backend.OperationTypeRefresh,
   271  		ConfigDir:       configDir,
   272  		ConfigLoader:    configLoader,
   273  		StateLocker:     clistate.NewNoopLocker(),
   274  		View:            view,
   275  		DependencyLocks: depLocks,
   276  	}, configCleanup, done
   277  }
   278  
   279  // testRefreshState is just a common state that we use for testing refresh.
   280  func testRefreshState() *states.State {
   281  	state := states.NewState()
   282  	root := state.EnsureModule(addrs.RootModuleInstance)
   283  	root.SetResourceInstanceCurrent(
   284  		mustResourceInstanceAddr("test_instance.foo").Resource,
   285  		&states.ResourceInstanceObjectSrc{
   286  			Status:    states.ObjectReady,
   287  			AttrsJSON: []byte(`{"id":"bar"}`),
   288  		},
   289  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   290  	)
   291  	return state
   292  }
   293  
   294  // refreshFixtureSchema returns a schema suitable for processing the
   295  // configuration in testdata/refresh . This schema should be
   296  // assigned to a mock provider named "test".
   297  func refreshFixtureSchema() *terraform.ProviderSchema {
   298  	return &terraform.ProviderSchema{
   299  		ResourceTypes: map[string]*configschema.Block{
   300  			"test_instance": {
   301  				Attributes: map[string]*configschema.Attribute{
   302  					"ami": {Type: cty.String, Optional: true},
   303  					"id":  {Type: cty.String, Computed: true},
   304  				},
   305  			},
   306  		},
   307  	}
   308  }