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 }