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 }