github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/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/hashicorp/terraform/addrs"
    13  	"github.com/hashicorp/terraform/configs"
    14  	"github.com/hashicorp/terraform/configs/hcl2shim"
    15  	"github.com/hashicorp/terraform/state"
    16  	"github.com/hashicorp/terraform/states"
    17  	"github.com/hashicorp/terraform/states/statemgr"
    18  	"github.com/hashicorp/terraform/tfdiags"
    19  )
    20  
    21  // TestBackendConfig validates and configures the backend with the
    22  // given configuration.
    23  func TestBackendConfig(t *testing.T, b Backend, c hcl.Body) Backend {
    24  	t.Helper()
    25  
    26  	t.Logf("TestBackendConfig on %T with %#v", b, c)
    27  
    28  	var diags tfdiags.Diagnostics
    29  
    30  	// To make things easier for test authors, we'll allow a nil body here
    31  	// (even though that's not normally valid) and just treat it as an empty
    32  	// body.
    33  	if c == nil {
    34  		c = hcl.EmptyBody()
    35  	}
    36  
    37  	schema := b.ConfigSchema()
    38  	spec := schema.DecoderSpec()
    39  	obj, decDiags := hcldec.Decode(c, spec, nil)
    40  	diags = diags.Append(decDiags)
    41  
    42  	newObj, valDiags := b.PrepareConfig(obj)
    43  	diags = diags.Append(valDiags.InConfigBody(c))
    44  
    45  	if len(diags) != 0 {
    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 default 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.HasResources() {
   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.HasResources() {
   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(); 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.ProviderConfig{
   154  				Type: addrs.NewLegacyProvider("test"),
   155  			}.Absolute(addrs.RootModuleInstance),
   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.HasResources() {
   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.HasResources() {
   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.HasResources() {
   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.HasResources() {
   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  func testLocks(t *testing.T, b1, b2 Backend, testForceUnlock bool) {
   283  	t.Helper()
   284  
   285  	// Get the default state for each
   286  	b1StateMgr, err := b1.StateMgr(DefaultStateName)
   287  	if err != nil {
   288  		t.Fatalf("error: %s", err)
   289  	}
   290  	if err := b1StateMgr.RefreshState(); err != nil {
   291  		t.Fatalf("bad: %s", err)
   292  	}
   293  
   294  	// Fast exit if this doesn't support locking at all
   295  	if _, ok := b1StateMgr.(state.Locker); !ok {
   296  		t.Logf("TestBackend: backend %T doesn't support state locking, not testing", b1)
   297  		return
   298  	}
   299  
   300  	t.Logf("TestBackend: testing state locking for %T", b1)
   301  
   302  	b2StateMgr, err := b2.StateMgr(DefaultStateName)
   303  	if err != nil {
   304  		t.Fatalf("error: %s", err)
   305  	}
   306  	if err := b2StateMgr.RefreshState(); err != nil {
   307  		t.Fatalf("bad: %s", err)
   308  	}
   309  
   310  	// Reassign so its obvious whats happening
   311  	lockerA := b1StateMgr.(state.Locker)
   312  	lockerB := b2StateMgr.(state.Locker)
   313  
   314  	infoA := state.NewLockInfo()
   315  	infoA.Operation = "test"
   316  	infoA.Who = "clientA"
   317  
   318  	infoB := state.NewLockInfo()
   319  	infoB.Operation = "test"
   320  	infoB.Who = "clientB"
   321  
   322  	lockIDA, err := lockerA.Lock(infoA)
   323  	if err != nil {
   324  		t.Fatal("unable to get initial lock:", err)
   325  	}
   326  
   327  	// Make sure we can still get the state.State from another instance even
   328  	// when locked.  This should only happen when a state is loaded via the
   329  	// backend, and as a remote state.
   330  	_, err = b2.StateMgr(DefaultStateName)
   331  	if err != nil {
   332  		t.Errorf("failed to read locked state from another backend instance: %s", err)
   333  	}
   334  
   335  	// If the lock ID is blank, assume locking is disabled
   336  	if lockIDA == "" {
   337  		t.Logf("TestBackend: %T: empty string returned for lock, assuming disabled", b1)
   338  		return
   339  	}
   340  
   341  	_, err = lockerB.Lock(infoB)
   342  	if err == nil {
   343  		lockerA.Unlock(lockIDA)
   344  		t.Fatal("client B obtained lock while held by client A")
   345  	}
   346  
   347  	if err := lockerA.Unlock(lockIDA); err != nil {
   348  		t.Fatal("error unlocking client A", err)
   349  	}
   350  
   351  	lockIDB, err := lockerB.Lock(infoB)
   352  	if err != nil {
   353  		t.Fatal("unable to obtain lock from client B")
   354  	}
   355  
   356  	if lockIDB == lockIDA {
   357  		t.Errorf("duplicate lock IDs: %q", lockIDB)
   358  	}
   359  
   360  	if err = lockerB.Unlock(lockIDB); err != nil {
   361  		t.Fatal("error unlocking client B:", err)
   362  	}
   363  
   364  	// test the equivalent of -force-unlock, by using the id from the error
   365  	// output.
   366  	if !testForceUnlock {
   367  		return
   368  	}
   369  
   370  	// get a new ID
   371  	infoA.ID, err = uuid.GenerateUUID()
   372  	if err != nil {
   373  		panic(err)
   374  	}
   375  
   376  	lockIDA, err = lockerA.Lock(infoA)
   377  	if err != nil {
   378  		t.Fatal("unable to get re lock A:", err)
   379  	}
   380  	unlock := func() {
   381  		err := lockerA.Unlock(lockIDA)
   382  		if err != nil {
   383  			t.Fatal(err)
   384  		}
   385  	}
   386  
   387  	_, err = lockerB.Lock(infoB)
   388  	if err == nil {
   389  		unlock()
   390  		t.Fatal("client B obtained lock while held by client A")
   391  	}
   392  
   393  	infoErr, ok := err.(*statemgr.LockError)
   394  	if !ok {
   395  		unlock()
   396  		t.Fatalf("expected type *statemgr.LockError, got : %#v", err)
   397  	}
   398  
   399  	// try to unlock with the second unlocker, using the ID from the error
   400  	if err := lockerB.Unlock(infoErr.Info.ID); err != nil {
   401  		unlock()
   402  		t.Fatalf("could not unlock with the reported ID %q: %s", infoErr.Info.ID, err)
   403  	}
   404  }