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