github.com/pulumi/terraform@v1.4.0/pkg/backend/testing.go (about)

     1  package backend
     2  
     3  import (
     4  	"reflect"
     5  	"sort"
     6  	"testing"
     7  
     8  	uuid "github.com/hashicorp/go-uuid"
     9  	"github.com/hashicorp/hcl/v2"
    10  	"github.com/hashicorp/hcl/v2/hcldec"
    11  
    12  	"github.com/pulumi/terraform/pkg/addrs"
    13  	"github.com/pulumi/terraform/pkg/configs"
    14  	"github.com/pulumi/terraform/pkg/configs/hcl2shim"
    15  	"github.com/pulumi/terraform/pkg/states"
    16  	"github.com/pulumi/terraform/pkg/states/statemgr"
    17  	"github.com/pulumi/terraform/pkg/tfdiags"
    18  )
    19  
    20  // TestBackendConfig validates and configures the backend with the
    21  // given configuration.
    22  func TestBackendConfig(t *testing.T, b Backend, c hcl.Body) Backend {
    23  	t.Helper()
    24  
    25  	t.Logf("TestBackendConfig on %T with %#v", b, c)
    26  
    27  	var diags tfdiags.Diagnostics
    28  
    29  	// To make things easier for test authors, we'll allow a nil body here
    30  	// (even though that's not normally valid) and just treat it as an empty
    31  	// body.
    32  	if c == nil {
    33  		c = hcl.EmptyBody()
    34  	}
    35  
    36  	schema := b.ConfigSchema()
    37  	spec := schema.DecoderSpec()
    38  	obj, decDiags := hcldec.Decode(c, spec, nil)
    39  	diags = diags.Append(decDiags)
    40  
    41  	newObj, valDiags := b.PrepareConfig(obj)
    42  	diags = diags.Append(valDiags.InConfigBody(c, ""))
    43  
    44  	// it's valid for a Backend to have warnings (e.g. a Deprecation) as such we should only raise on errors
    45  	if diags.HasErrors() {
    46  		t.Fatal(diags.ErrWithWarnings())
    47  	}
    48  
    49  	obj = newObj
    50  
    51  	confDiags := b.Configure(obj)
    52  	if len(confDiags) != 0 {
    53  		confDiags = confDiags.InConfigBody(c, "")
    54  		t.Fatal(confDiags.ErrWithWarnings())
    55  	}
    56  
    57  	return b
    58  }
    59  
    60  // TestWrapConfig takes a raw data structure and converts it into a
    61  // synthetic hcl.Body to use for testing.
    62  //
    63  // The given structure should only include values that can be accepted by
    64  // hcl2shim.HCL2ValueFromConfigValue. If incompatible values are given,
    65  // this function will panic.
    66  func TestWrapConfig(raw map[string]interface{}) hcl.Body {
    67  	obj := hcl2shim.HCL2ValueFromConfigValue(raw)
    68  	return configs.SynthBody("<TestWrapConfig>", obj.AsValueMap())
    69  }
    70  
    71  // TestBackend will test the functionality of a Backend. The backend is
    72  // assumed to already be configured. This will test state functionality.
    73  // If the backend reports it doesn't support multi-state by returning the
    74  // error ErrWorkspacesNotSupported, then it will not test that.
    75  func TestBackendStates(t *testing.T, b Backend) {
    76  	t.Helper()
    77  
    78  	noDefault := false
    79  	if _, err := b.StateMgr(DefaultStateName); err != nil {
    80  		if err == ErrDefaultWorkspaceNotSupported {
    81  			noDefault = true
    82  		} else {
    83  			t.Fatalf("error: %v", err)
    84  		}
    85  	}
    86  
    87  	workspaces, err := b.Workspaces()
    88  	if err != nil {
    89  		if err == ErrWorkspacesNotSupported {
    90  			t.Logf("TestBackend: workspaces not supported in %T, skipping", b)
    91  			return
    92  		}
    93  		t.Fatalf("error: %v", err)
    94  	}
    95  
    96  	// Test it starts with only the default
    97  	if !noDefault && (len(workspaces) != 1 || workspaces[0] != DefaultStateName) {
    98  		t.Fatalf("should only have the default workspace to start: %#v", workspaces)
    99  	}
   100  
   101  	// Create a couple states
   102  	foo, err := b.StateMgr("foo")
   103  	if err != nil {
   104  		t.Fatalf("error: %s", err)
   105  	}
   106  	if err := foo.RefreshState(); err != nil {
   107  		t.Fatalf("bad: %s", err)
   108  	}
   109  	if v := foo.State(); v.HasManagedResourceInstanceObjects() {
   110  		t.Fatalf("should be empty: %s", v)
   111  	}
   112  
   113  	bar, err := b.StateMgr("bar")
   114  	if err != nil {
   115  		t.Fatalf("error: %s", err)
   116  	}
   117  	if err := bar.RefreshState(); err != nil {
   118  		t.Fatalf("bad: %s", err)
   119  	}
   120  	if v := bar.State(); v.HasManagedResourceInstanceObjects() {
   121  		t.Fatalf("should be empty: %s", v)
   122  	}
   123  
   124  	// Verify they are distinct states that can be read back from storage
   125  	{
   126  		// We'll use two distinct states here and verify that changing one
   127  		// does not also change the other.
   128  		fooState := states.NewState()
   129  		barState := states.NewState()
   130  
   131  		// write a known state to foo
   132  		if err := foo.WriteState(fooState); err != nil {
   133  			t.Fatal("error writing foo state:", err)
   134  		}
   135  		if err := foo.PersistState(nil); err != nil {
   136  			t.Fatal("error persisting foo state:", err)
   137  		}
   138  
   139  		// We'll make "bar" different by adding a fake resource state to it.
   140  		barState.SyncWrapper().SetResourceInstanceCurrent(
   141  			addrs.ResourceInstance{
   142  				Resource: addrs.Resource{
   143  					Mode: addrs.ManagedResourceMode,
   144  					Type: "test_thing",
   145  					Name: "foo",
   146  				},
   147  			}.Absolute(addrs.RootModuleInstance),
   148  			&states.ResourceInstanceObjectSrc{
   149  				AttrsJSON:     []byte("{}"),
   150  				Status:        states.ObjectReady,
   151  				SchemaVersion: 0,
   152  			},
   153  			addrs.AbsProviderConfig{
   154  				Provider: addrs.NewDefaultProvider("test"),
   155  				Module:   addrs.RootModule,
   156  			},
   157  		)
   158  
   159  		// write a distinct known state to bar
   160  		if err := bar.WriteState(barState); err != nil {
   161  			t.Fatalf("bad: %s", err)
   162  		}
   163  		if err := bar.PersistState(nil); err != nil {
   164  			t.Fatalf("bad: %s", err)
   165  		}
   166  
   167  		// verify that foo is unchanged with the existing state manager
   168  		if err := foo.RefreshState(); err != nil {
   169  			t.Fatal("error refreshing foo:", err)
   170  		}
   171  		fooState = foo.State()
   172  		if fooState.HasManagedResourceInstanceObjects() {
   173  			t.Fatal("after writing a resource to bar, foo now has resources too")
   174  		}
   175  
   176  		// fetch foo again from the backend
   177  		foo, err = b.StateMgr("foo")
   178  		if err != nil {
   179  			t.Fatal("error re-fetching state:", err)
   180  		}
   181  		if err := foo.RefreshState(); err != nil {
   182  			t.Fatal("error refreshing foo:", err)
   183  		}
   184  		fooState = foo.State()
   185  		if fooState.HasManagedResourceInstanceObjects() {
   186  			t.Fatal("after writing a resource to bar and re-reading foo, foo now has resources too")
   187  		}
   188  
   189  		// fetch the bar again from the backend
   190  		bar, err = b.StateMgr("bar")
   191  		if err != nil {
   192  			t.Fatal("error re-fetching state:", err)
   193  		}
   194  		if err := bar.RefreshState(); err != nil {
   195  			t.Fatal("error refreshing bar:", err)
   196  		}
   197  		barState = bar.State()
   198  		if !barState.HasManagedResourceInstanceObjects() {
   199  			t.Fatal("after writing a resource instance object to bar and re-reading it, the object has vanished")
   200  		}
   201  	}
   202  
   203  	// Verify we can now list them
   204  	{
   205  		// we determined that named stated are supported earlier
   206  		workspaces, err := b.Workspaces()
   207  		if err != nil {
   208  			t.Fatalf("err: %s", err)
   209  		}
   210  
   211  		sort.Strings(workspaces)
   212  		expected := []string{"bar", "default", "foo"}
   213  		if noDefault {
   214  			expected = []string{"bar", "foo"}
   215  		}
   216  		if !reflect.DeepEqual(workspaces, expected) {
   217  			t.Fatalf("wrong workspaces list\ngot:  %#v\nwant: %#v", workspaces, expected)
   218  		}
   219  	}
   220  
   221  	// Delete some workspaces
   222  	if err := b.DeleteWorkspace("foo", true); err != nil {
   223  		t.Fatalf("err: %s", err)
   224  	}
   225  
   226  	// Verify the default state can't be deleted
   227  	if err := b.DeleteWorkspace(DefaultStateName, true); err == nil {
   228  		t.Fatal("expected error")
   229  	}
   230  
   231  	// Create and delete the foo workspace again.
   232  	// Make sure that there are no leftover artifacts from a deleted state
   233  	// preventing re-creation.
   234  	foo, err = b.StateMgr("foo")
   235  	if err != nil {
   236  		t.Fatalf("error: %s", err)
   237  	}
   238  	if err := foo.RefreshState(); err != nil {
   239  		t.Fatalf("bad: %s", err)
   240  	}
   241  	if v := foo.State(); v.HasManagedResourceInstanceObjects() {
   242  		t.Fatalf("should be empty: %s", v)
   243  	}
   244  	// and delete it again
   245  	if err := b.DeleteWorkspace("foo", true); err != nil {
   246  		t.Fatalf("err: %s", err)
   247  	}
   248  
   249  	// Verify deletion
   250  	{
   251  		workspaces, err := b.Workspaces()
   252  		if err != nil {
   253  			t.Fatalf("err: %s", err)
   254  		}
   255  
   256  		sort.Strings(workspaces)
   257  		expected := []string{"bar", "default"}
   258  		if noDefault {
   259  			expected = []string{"bar"}
   260  		}
   261  		if !reflect.DeepEqual(workspaces, expected) {
   262  			t.Fatalf("wrong workspaces list\ngot:  %#v\nwant: %#v", workspaces, expected)
   263  		}
   264  	}
   265  }
   266  
   267  // TestBackendStateLocks will test the locking functionality of the remote
   268  // state backend.
   269  func TestBackendStateLocks(t *testing.T, b1, b2 Backend) {
   270  	t.Helper()
   271  	testLocks(t, b1, b2, false)
   272  }
   273  
   274  // TestBackendStateForceUnlock verifies that the lock error is the expected
   275  // type, and the lock can be unlocked using the ID reported in the error.
   276  // Remote state backends that support -force-unlock should call this in at
   277  // least one of the acceptance tests.
   278  func TestBackendStateForceUnlock(t *testing.T, b1, b2 Backend) {
   279  	t.Helper()
   280  	testLocks(t, b1, b2, true)
   281  }
   282  
   283  // TestBackendStateLocksInWS will test the locking functionality of the remote
   284  // state backend.
   285  func TestBackendStateLocksInWS(t *testing.T, b1, b2 Backend, ws string) {
   286  	t.Helper()
   287  	testLocksInWorkspace(t, b1, b2, false, ws)
   288  }
   289  
   290  // TestBackendStateForceUnlockInWS verifies that the lock error is the expected
   291  // type, and the lock can be unlocked using the ID reported in the error.
   292  // Remote state backends that support -force-unlock should call this in at
   293  // least one of the acceptance tests.
   294  func TestBackendStateForceUnlockInWS(t *testing.T, b1, b2 Backend, ws string) {
   295  	t.Helper()
   296  	testLocksInWorkspace(t, b1, b2, true, ws)
   297  }
   298  
   299  func testLocks(t *testing.T, b1, b2 Backend, testForceUnlock bool) {
   300  	testLocksInWorkspace(t, b1, b2, testForceUnlock, DefaultStateName)
   301  }
   302  
   303  func testLocksInWorkspace(t *testing.T, b1, b2 Backend, testForceUnlock bool, workspace string) {
   304  	t.Helper()
   305  
   306  	// Get the default state for each
   307  	b1StateMgr, err := b1.StateMgr(DefaultStateName)
   308  	if err != nil {
   309  		t.Fatalf("error: %s", err)
   310  	}
   311  	if err := b1StateMgr.RefreshState(); err != nil {
   312  		t.Fatalf("bad: %s", err)
   313  	}
   314  
   315  	// Fast exit if this doesn't support locking at all
   316  	if _, ok := b1StateMgr.(statemgr.Locker); !ok {
   317  		t.Logf("TestBackend: backend %T doesn't support state locking, not testing", b1)
   318  		return
   319  	}
   320  
   321  	t.Logf("TestBackend: testing state locking for %T", b1)
   322  
   323  	b2StateMgr, err := b2.StateMgr(DefaultStateName)
   324  	if err != nil {
   325  		t.Fatalf("error: %s", err)
   326  	}
   327  	if err := b2StateMgr.RefreshState(); err != nil {
   328  		t.Fatalf("bad: %s", err)
   329  	}
   330  
   331  	// Reassign so its obvious whats happening
   332  	lockerA := b1StateMgr.(statemgr.Locker)
   333  	lockerB := b2StateMgr.(statemgr.Locker)
   334  
   335  	infoA := statemgr.NewLockInfo()
   336  	infoA.Operation = "test"
   337  	infoA.Who = "clientA"
   338  
   339  	infoB := statemgr.NewLockInfo()
   340  	infoB.Operation = "test"
   341  	infoB.Who = "clientB"
   342  
   343  	lockIDA, err := lockerA.Lock(infoA)
   344  	if err != nil {
   345  		t.Fatal("unable to get initial lock:", err)
   346  	}
   347  
   348  	// Make sure we can still get the statemgr.Full from another instance even
   349  	// when locked.  This should only happen when a state is loaded via the
   350  	// backend, and as a remote state.
   351  	_, err = b2.StateMgr(DefaultStateName)
   352  	if err != nil {
   353  		t.Errorf("failed to read locked state from another backend instance: %s", err)
   354  	}
   355  
   356  	// If the lock ID is blank, assume locking is disabled
   357  	if lockIDA == "" {
   358  		t.Logf("TestBackend: %T: empty string returned for lock, assuming disabled", b1)
   359  		return
   360  	}
   361  
   362  	_, err = lockerB.Lock(infoB)
   363  	if err == nil {
   364  		lockerA.Unlock(lockIDA)
   365  		t.Fatal("client B obtained lock while held by client A")
   366  	}
   367  
   368  	if err := lockerA.Unlock(lockIDA); err != nil {
   369  		t.Fatal("error unlocking client A", err)
   370  	}
   371  
   372  	lockIDB, err := lockerB.Lock(infoB)
   373  	if err != nil {
   374  		t.Fatal("unable to obtain lock from client B")
   375  	}
   376  
   377  	if lockIDB == lockIDA {
   378  		t.Errorf("duplicate lock IDs: %q", lockIDB)
   379  	}
   380  
   381  	if err = lockerB.Unlock(lockIDB); err != nil {
   382  		t.Fatal("error unlocking client B:", err)
   383  	}
   384  
   385  	// test the equivalent of -force-unlock, by using the id from the error
   386  	// output.
   387  	if !testForceUnlock {
   388  		return
   389  	}
   390  
   391  	// get a new ID
   392  	infoA.ID, err = uuid.GenerateUUID()
   393  	if err != nil {
   394  		panic(err)
   395  	}
   396  
   397  	lockIDA, err = lockerA.Lock(infoA)
   398  	if err != nil {
   399  		t.Fatal("unable to get re lock A:", err)
   400  	}
   401  	unlock := func() {
   402  		err := lockerA.Unlock(lockIDA)
   403  		if err != nil {
   404  			t.Fatal(err)
   405  		}
   406  	}
   407  
   408  	_, err = lockerB.Lock(infoB)
   409  	if err == nil {
   410  		unlock()
   411  		t.Fatal("client B obtained lock while held by client A")
   412  	}
   413  
   414  	infoErr, ok := err.(*statemgr.LockError)
   415  	if !ok {
   416  		unlock()
   417  		t.Fatalf("expected type *statemgr.LockError, got : %#v", err)
   418  	}
   419  
   420  	// try to unlock with the second unlocker, using the ID from the error
   421  	if err := lockerB.Unlock(infoErr.Info.ID); err != nil {
   422  		unlock()
   423  		t.Fatalf("could not unlock with the reported ID %q: %s", infoErr.Info.ID, err)
   424  	}
   425  }