github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/backend/remote-state/s3/client_test.go (about) 1 package s3 2 3 import ( 4 "bytes" 5 "crypto/md5" 6 "fmt" 7 "strings" 8 "testing" 9 "time" 10 11 "github.com/hashicorp/terraform/backend" 12 "github.com/hashicorp/terraform/state" 13 "github.com/hashicorp/terraform/state/remote" 14 "github.com/hashicorp/terraform/states/statefile" 15 ) 16 17 func TestRemoteClient_impl(t *testing.T) { 18 var _ remote.Client = new(RemoteClient) 19 var _ remote.ClientLocker = new(RemoteClient) 20 } 21 22 func TestRemoteClient(t *testing.T) { 23 testACC(t) 24 bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix()) 25 keyName := "testState" 26 27 b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 28 "bucket": bucketName, 29 "key": keyName, 30 "encrypt": true, 31 })).(*Backend) 32 33 createS3Bucket(t, b.s3Client, bucketName) 34 defer deleteS3Bucket(t, b.s3Client, bucketName) 35 36 state, err := b.StateMgr(backend.DefaultStateName) 37 if err != nil { 38 t.Fatal(err) 39 } 40 41 remote.TestClient(t, state.(*remote.State).Client) 42 } 43 44 func TestRemoteClientLocks(t *testing.T) { 45 testACC(t) 46 bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix()) 47 keyName := "testState" 48 49 b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 50 "bucket": bucketName, 51 "key": keyName, 52 "encrypt": true, 53 "dynamodb_table": bucketName, 54 })).(*Backend) 55 56 b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 57 "bucket": bucketName, 58 "key": keyName, 59 "encrypt": true, 60 "dynamodb_table": bucketName, 61 })).(*Backend) 62 63 createS3Bucket(t, b1.s3Client, bucketName) 64 defer deleteS3Bucket(t, b1.s3Client, bucketName) 65 createDynamoDBTable(t, b1.dynClient, bucketName) 66 defer deleteDynamoDBTable(t, b1.dynClient, bucketName) 67 68 s1, err := b1.StateMgr(backend.DefaultStateName) 69 if err != nil { 70 t.Fatal(err) 71 } 72 73 s2, err := b2.StateMgr(backend.DefaultStateName) 74 if err != nil { 75 t.Fatal(err) 76 } 77 78 remote.TestRemoteLocks(t, s1.(*remote.State).Client, s2.(*remote.State).Client) 79 } 80 81 // verify that we can unlock a state with an existing lock 82 func TestForceUnlock(t *testing.T) { 83 testACC(t) 84 bucketName := fmt.Sprintf("terraform-remote-s3-test-force-%x", time.Now().Unix()) 85 keyName := "testState" 86 87 b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 88 "bucket": bucketName, 89 "key": keyName, 90 "encrypt": true, 91 "dynamodb_table": bucketName, 92 })).(*Backend) 93 94 b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 95 "bucket": bucketName, 96 "key": keyName, 97 "encrypt": true, 98 "dynamodb_table": bucketName, 99 })).(*Backend) 100 101 createS3Bucket(t, b1.s3Client, bucketName) 102 defer deleteS3Bucket(t, b1.s3Client, bucketName) 103 createDynamoDBTable(t, b1.dynClient, bucketName) 104 defer deleteDynamoDBTable(t, b1.dynClient, bucketName) 105 106 // first test with default 107 s1, err := b1.StateMgr(backend.DefaultStateName) 108 if err != nil { 109 t.Fatal(err) 110 } 111 112 info := state.NewLockInfo() 113 info.Operation = "test" 114 info.Who = "clientA" 115 116 lockID, err := s1.Lock(info) 117 if err != nil { 118 t.Fatal("unable to get initial lock:", err) 119 } 120 121 // s1 is now locked, get the same state through s2 and unlock it 122 s2, err := b2.StateMgr(backend.DefaultStateName) 123 if err != nil { 124 t.Fatal("failed to get default state to force unlock:", err) 125 } 126 127 if err := s2.Unlock(lockID); err != nil { 128 t.Fatal("failed to force-unlock default state") 129 } 130 131 // now try the same thing with a named state 132 // first test with default 133 s1, err = b1.StateMgr("test") 134 if err != nil { 135 t.Fatal(err) 136 } 137 138 info = state.NewLockInfo() 139 info.Operation = "test" 140 info.Who = "clientA" 141 142 lockID, err = s1.Lock(info) 143 if err != nil { 144 t.Fatal("unable to get initial lock:", err) 145 } 146 147 // s1 is now locked, get the same state through s2 and unlock it 148 s2, err = b2.StateMgr("test") 149 if err != nil { 150 t.Fatal("failed to get named state to force unlock:", err) 151 } 152 153 if err = s2.Unlock(lockID); err != nil { 154 t.Fatal("failed to force-unlock named state") 155 } 156 } 157 158 func TestRemoteClient_clientMD5(t *testing.T) { 159 testACC(t) 160 161 bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix()) 162 keyName := "testState" 163 164 b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 165 "bucket": bucketName, 166 "key": keyName, 167 "dynamodb_table": bucketName, 168 })).(*Backend) 169 170 createS3Bucket(t, b.s3Client, bucketName) 171 defer deleteS3Bucket(t, b.s3Client, bucketName) 172 createDynamoDBTable(t, b.dynClient, bucketName) 173 defer deleteDynamoDBTable(t, b.dynClient, bucketName) 174 175 s, err := b.StateMgr(backend.DefaultStateName) 176 if err != nil { 177 t.Fatal(err) 178 } 179 client := s.(*remote.State).Client.(*RemoteClient) 180 181 sum := md5.Sum([]byte("test")) 182 183 if err := client.putMD5(sum[:]); err != nil { 184 t.Fatal(err) 185 } 186 187 getSum, err := client.getMD5() 188 if err != nil { 189 t.Fatal(err) 190 } 191 192 if !bytes.Equal(getSum, sum[:]) { 193 t.Fatalf("getMD5 returned the wrong checksum: expected %x, got %x", sum[:], getSum) 194 } 195 196 if err := client.deleteMD5(); err != nil { 197 t.Fatal(err) 198 } 199 200 if getSum, err := client.getMD5(); err == nil { 201 t.Fatalf("expected getMD5 error, got none. checksum: %x", getSum) 202 } 203 } 204 205 // verify that a client won't return a state with an incorrect checksum. 206 func TestRemoteClient_stateChecksum(t *testing.T) { 207 testACC(t) 208 209 bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix()) 210 keyName := "testState" 211 212 b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 213 "bucket": bucketName, 214 "key": keyName, 215 "dynamodb_table": bucketName, 216 })).(*Backend) 217 218 createS3Bucket(t, b1.s3Client, bucketName) 219 defer deleteS3Bucket(t, b1.s3Client, bucketName) 220 createDynamoDBTable(t, b1.dynClient, bucketName) 221 defer deleteDynamoDBTable(t, b1.dynClient, bucketName) 222 223 s1, err := b1.StateMgr(backend.DefaultStateName) 224 if err != nil { 225 t.Fatal(err) 226 } 227 client1 := s1.(*remote.State).Client 228 229 // create an old and new state version to persist 230 s := state.TestStateInitial() 231 sf := &statefile.File{State: s} 232 var oldState bytes.Buffer 233 if err := statefile.Write(sf, &oldState); err != nil { 234 t.Fatal(err) 235 } 236 sf.Serial++ 237 var newState bytes.Buffer 238 if err := statefile.Write(sf, &newState); err != nil { 239 t.Fatal(err) 240 } 241 242 // Use b2 without a dynamodb_table to bypass the lock table to write the state directly. 243 // client2 will write the "incorrect" state, simulating s3 eventually consistency delays 244 b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 245 "bucket": bucketName, 246 "key": keyName, 247 })).(*Backend) 248 s2, err := b2.StateMgr(backend.DefaultStateName) 249 if err != nil { 250 t.Fatal(err) 251 } 252 client2 := s2.(*remote.State).Client 253 254 // write the new state through client2 so that there is no checksum yet 255 if err := client2.Put(newState.Bytes()); err != nil { 256 t.Fatal(err) 257 } 258 259 // verify that we can pull a state without a checksum 260 if _, err := client1.Get(); err != nil { 261 t.Fatal(err) 262 } 263 264 // write the new state back with its checksum 265 if err := client1.Put(newState.Bytes()); err != nil { 266 t.Fatal(err) 267 } 268 269 // put an empty state in place to check for panics during get 270 if err := client2.Put([]byte{}); err != nil { 271 t.Fatal(err) 272 } 273 274 // remove the timeouts so we can fail immediately 275 origTimeout := consistencyRetryTimeout 276 origInterval := consistencyRetryPollInterval 277 defer func() { 278 consistencyRetryTimeout = origTimeout 279 consistencyRetryPollInterval = origInterval 280 }() 281 consistencyRetryTimeout = 0 282 consistencyRetryPollInterval = 0 283 284 // fetching an empty state through client1 should now error out due to a 285 // mismatched checksum. 286 if _, err := client1.Get(); !strings.HasPrefix(err.Error(), errBadChecksumFmt[:80]) { 287 t.Fatalf("expected state checksum error: got %s", err) 288 } 289 290 // put the old state in place of the new, without updating the checksum 291 if err := client2.Put(oldState.Bytes()); err != nil { 292 t.Fatal(err) 293 } 294 295 // fetching the wrong state through client1 should now error out due to a 296 // mismatched checksum. 297 if _, err := client1.Get(); !strings.HasPrefix(err.Error(), errBadChecksumFmt[:80]) { 298 t.Fatalf("expected state checksum error: got %s", err) 299 } 300 301 // update the state with the correct one after we Get again 302 testChecksumHook = func() { 303 if err := client2.Put(newState.Bytes()); err != nil { 304 t.Fatal(err) 305 } 306 testChecksumHook = nil 307 } 308 309 consistencyRetryTimeout = origTimeout 310 311 // this final Get will fail to fail the checksum verification, the above 312 // callback will update the state with the correct version, and Get should 313 // retry automatically. 314 if _, err := client1.Get(); err != nil { 315 t.Fatal(err) 316 } 317 }