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