github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/backend/remote-state/s3/backend_test.go (about) 1 package s3 2 3 import ( 4 "fmt" 5 "net/url" 6 "os" 7 "reflect" 8 "testing" 9 "time" 10 11 "github.com/aws/aws-sdk-go/aws" 12 "github.com/aws/aws-sdk-go/service/dynamodb" 13 "github.com/aws/aws-sdk-go/service/s3" 14 awsbase "github.com/hashicorp/aws-sdk-go-base" 15 "github.com/hashicorp/terraform/internal/backend" 16 "github.com/hashicorp/terraform/internal/configs/hcl2shim" 17 "github.com/hashicorp/terraform/internal/states" 18 "github.com/hashicorp/terraform/internal/states/remote" 19 ) 20 21 var ( 22 mockStsGetCallerIdentityRequestBody = url.Values{ 23 "Action": []string{"GetCallerIdentity"}, 24 "Version": []string{"2011-06-15"}, 25 }.Encode() 26 ) 27 28 // verify that we are doing ACC tests or the S3 tests specifically 29 func testACC(t *testing.T) { 30 skip := os.Getenv("TF_ACC") == "" && os.Getenv("TF_S3_TEST") == "" 31 if skip { 32 t.Log("s3 backend tests require setting TF_ACC or TF_S3_TEST") 33 t.Skip() 34 } 35 if os.Getenv("AWS_DEFAULT_REGION") == "" { 36 os.Setenv("AWS_DEFAULT_REGION", "us-west-2") 37 } 38 } 39 40 func TestBackend_impl(t *testing.T) { 41 var _ backend.Backend = new(Backend) 42 } 43 44 func TestBackendConfig(t *testing.T) { 45 testACC(t) 46 config := map[string]interface{}{ 47 "region": "us-west-1", 48 "bucket": "tf-test", 49 "key": "state", 50 "encrypt": true, 51 "dynamodb_table": "dynamoTable", 52 } 53 54 b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(config)).(*Backend) 55 56 if *b.s3Client.Config.Region != "us-west-1" { 57 t.Fatalf("Incorrect region was populated") 58 } 59 if b.bucketName != "tf-test" { 60 t.Fatalf("Incorrect bucketName was populated") 61 } 62 if b.keyName != "state" { 63 t.Fatalf("Incorrect keyName was populated") 64 } 65 66 credentials, err := b.s3Client.Config.Credentials.Get() 67 if err != nil { 68 t.Fatalf("Error when requesting credentials") 69 } 70 if credentials.AccessKeyID == "" { 71 t.Fatalf("No Access Key Id was populated") 72 } 73 if credentials.SecretAccessKey == "" { 74 t.Fatalf("No Secret Access Key was populated") 75 } 76 } 77 78 func TestBackendConfig_AssumeRole(t *testing.T) { 79 testACC(t) 80 81 testCases := []struct { 82 Config map[string]interface{} 83 Description string 84 MockStsEndpoints []*awsbase.MockEndpoint 85 }{ 86 { 87 Config: map[string]interface{}{ 88 "bucket": "tf-test", 89 "key": "state", 90 "region": "us-west-1", 91 "role_arn": awsbase.MockStsAssumeRoleArn, 92 "session_name": awsbase.MockStsAssumeRoleSessionName, 93 }, 94 Description: "role_arn", 95 MockStsEndpoints: []*awsbase.MockEndpoint{ 96 { 97 Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: url.Values{ 98 "Action": []string{"AssumeRole"}, 99 "DurationSeconds": []string{"900"}, 100 "RoleArn": []string{awsbase.MockStsAssumeRoleArn}, 101 "RoleSessionName": []string{awsbase.MockStsAssumeRoleSessionName}, 102 "Version": []string{"2011-06-15"}, 103 }.Encode()}, 104 Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsAssumeRoleValidResponseBody, ContentType: "text/xml"}, 105 }, 106 { 107 Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: mockStsGetCallerIdentityRequestBody}, 108 Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsGetCallerIdentityValidResponseBody, ContentType: "text/xml"}, 109 }, 110 }, 111 }, 112 { 113 Config: map[string]interface{}{ 114 "assume_role_duration_seconds": 3600, 115 "bucket": "tf-test", 116 "key": "state", 117 "region": "us-west-1", 118 "role_arn": awsbase.MockStsAssumeRoleArn, 119 "session_name": awsbase.MockStsAssumeRoleSessionName, 120 }, 121 Description: "assume_role_duration_seconds", 122 MockStsEndpoints: []*awsbase.MockEndpoint{ 123 { 124 Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: url.Values{ 125 "Action": []string{"AssumeRole"}, 126 "DurationSeconds": []string{"3600"}, 127 "RoleArn": []string{awsbase.MockStsAssumeRoleArn}, 128 "RoleSessionName": []string{awsbase.MockStsAssumeRoleSessionName}, 129 "Version": []string{"2011-06-15"}, 130 }.Encode()}, 131 Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsAssumeRoleValidResponseBody, ContentType: "text/xml"}, 132 }, 133 { 134 Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: mockStsGetCallerIdentityRequestBody}, 135 Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsGetCallerIdentityValidResponseBody, ContentType: "text/xml"}, 136 }, 137 }, 138 }, 139 { 140 Config: map[string]interface{}{ 141 "bucket": "tf-test", 142 "external_id": awsbase.MockStsAssumeRoleExternalId, 143 "key": "state", 144 "region": "us-west-1", 145 "role_arn": awsbase.MockStsAssumeRoleArn, 146 "session_name": awsbase.MockStsAssumeRoleSessionName, 147 }, 148 Description: "external_id", 149 MockStsEndpoints: []*awsbase.MockEndpoint{ 150 { 151 Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: url.Values{ 152 "Action": []string{"AssumeRole"}, 153 "DurationSeconds": []string{"900"}, 154 "ExternalId": []string{awsbase.MockStsAssumeRoleExternalId}, 155 "RoleArn": []string{awsbase.MockStsAssumeRoleArn}, 156 "RoleSessionName": []string{awsbase.MockStsAssumeRoleSessionName}, 157 "Version": []string{"2011-06-15"}, 158 }.Encode()}, 159 Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsAssumeRoleValidResponseBody, ContentType: "text/xml"}, 160 }, 161 { 162 Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: mockStsGetCallerIdentityRequestBody}, 163 Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsGetCallerIdentityValidResponseBody, ContentType: "text/xml"}, 164 }, 165 }, 166 }, 167 { 168 Config: map[string]interface{}{ 169 "assume_role_policy": awsbase.MockStsAssumeRolePolicy, 170 "bucket": "tf-test", 171 "key": "state", 172 "region": "us-west-1", 173 "role_arn": awsbase.MockStsAssumeRoleArn, 174 "session_name": awsbase.MockStsAssumeRoleSessionName, 175 }, 176 Description: "assume_role_policy", 177 MockStsEndpoints: []*awsbase.MockEndpoint{ 178 { 179 Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: url.Values{ 180 "Action": []string{"AssumeRole"}, 181 "DurationSeconds": []string{"900"}, 182 "Policy": []string{awsbase.MockStsAssumeRolePolicy}, 183 "RoleArn": []string{awsbase.MockStsAssumeRoleArn}, 184 "RoleSessionName": []string{awsbase.MockStsAssumeRoleSessionName}, 185 "Version": []string{"2011-06-15"}, 186 }.Encode()}, 187 Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsAssumeRoleValidResponseBody, ContentType: "text/xml"}, 188 }, 189 { 190 Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: mockStsGetCallerIdentityRequestBody}, 191 Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsGetCallerIdentityValidResponseBody, ContentType: "text/xml"}, 192 }, 193 }, 194 }, 195 { 196 Config: map[string]interface{}{ 197 "assume_role_policy_arns": []interface{}{awsbase.MockStsAssumeRolePolicyArn}, 198 "bucket": "tf-test", 199 "key": "state", 200 "region": "us-west-1", 201 "role_arn": awsbase.MockStsAssumeRoleArn, 202 "session_name": awsbase.MockStsAssumeRoleSessionName, 203 }, 204 Description: "assume_role_policy_arns", 205 MockStsEndpoints: []*awsbase.MockEndpoint{ 206 { 207 Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: url.Values{ 208 "Action": []string{"AssumeRole"}, 209 "DurationSeconds": []string{"900"}, 210 "PolicyArns.member.1.arn": []string{awsbase.MockStsAssumeRolePolicyArn}, 211 "RoleArn": []string{awsbase.MockStsAssumeRoleArn}, 212 "RoleSessionName": []string{awsbase.MockStsAssumeRoleSessionName}, 213 "Version": []string{"2011-06-15"}, 214 }.Encode()}, 215 Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsAssumeRoleValidResponseBody, ContentType: "text/xml"}, 216 }, 217 { 218 Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: mockStsGetCallerIdentityRequestBody}, 219 Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsGetCallerIdentityValidResponseBody, ContentType: "text/xml"}, 220 }, 221 }, 222 }, 223 { 224 Config: map[string]interface{}{ 225 "assume_role_tags": map[string]interface{}{ 226 awsbase.MockStsAssumeRoleTagKey: awsbase.MockStsAssumeRoleTagValue, 227 }, 228 "bucket": "tf-test", 229 "key": "state", 230 "region": "us-west-1", 231 "role_arn": awsbase.MockStsAssumeRoleArn, 232 "session_name": awsbase.MockStsAssumeRoleSessionName, 233 }, 234 Description: "assume_role_tags", 235 MockStsEndpoints: []*awsbase.MockEndpoint{ 236 { 237 Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: url.Values{ 238 "Action": []string{"AssumeRole"}, 239 "DurationSeconds": []string{"900"}, 240 "RoleArn": []string{awsbase.MockStsAssumeRoleArn}, 241 "RoleSessionName": []string{awsbase.MockStsAssumeRoleSessionName}, 242 "Tags.member.1.Key": []string{awsbase.MockStsAssumeRoleTagKey}, 243 "Tags.member.1.Value": []string{awsbase.MockStsAssumeRoleTagValue}, 244 "Version": []string{"2011-06-15"}, 245 }.Encode()}, 246 Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsAssumeRoleValidResponseBody, ContentType: "text/xml"}, 247 }, 248 { 249 Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: mockStsGetCallerIdentityRequestBody}, 250 Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsGetCallerIdentityValidResponseBody, ContentType: "text/xml"}, 251 }, 252 }, 253 }, 254 { 255 Config: map[string]interface{}{ 256 "assume_role_tags": map[string]interface{}{ 257 awsbase.MockStsAssumeRoleTagKey: awsbase.MockStsAssumeRoleTagValue, 258 }, 259 "assume_role_transitive_tag_keys": []interface{}{awsbase.MockStsAssumeRoleTagKey}, 260 "bucket": "tf-test", 261 "key": "state", 262 "region": "us-west-1", 263 "role_arn": awsbase.MockStsAssumeRoleArn, 264 "session_name": awsbase.MockStsAssumeRoleSessionName, 265 }, 266 Description: "assume_role_transitive_tag_keys", 267 MockStsEndpoints: []*awsbase.MockEndpoint{ 268 { 269 Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: url.Values{ 270 "Action": []string{"AssumeRole"}, 271 "DurationSeconds": []string{"900"}, 272 "RoleArn": []string{awsbase.MockStsAssumeRoleArn}, 273 "RoleSessionName": []string{awsbase.MockStsAssumeRoleSessionName}, 274 "Tags.member.1.Key": []string{awsbase.MockStsAssumeRoleTagKey}, 275 "Tags.member.1.Value": []string{awsbase.MockStsAssumeRoleTagValue}, 276 "TransitiveTagKeys.member.1": []string{awsbase.MockStsAssumeRoleTagKey}, 277 "Version": []string{"2011-06-15"}, 278 }.Encode()}, 279 Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsAssumeRoleValidResponseBody, ContentType: "text/xml"}, 280 }, 281 { 282 Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: mockStsGetCallerIdentityRequestBody}, 283 Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsGetCallerIdentityValidResponseBody, ContentType: "text/xml"}, 284 }, 285 }, 286 }, 287 } 288 289 for _, testCase := range testCases { 290 testCase := testCase 291 292 t.Run(testCase.Description, func(t *testing.T) { 293 closeSts, mockStsSession, err := awsbase.GetMockedAwsApiSession("STS", testCase.MockStsEndpoints) 294 defer closeSts() 295 296 if err != nil { 297 t.Fatalf("unexpected error creating mock STS server: %s", err) 298 } 299 300 if mockStsSession != nil && mockStsSession.Config != nil { 301 testCase.Config["sts_endpoint"] = aws.StringValue(mockStsSession.Config.Endpoint) 302 } 303 304 diags := New().Configure(hcl2shim.HCL2ValueFromConfigValue(testCase.Config)) 305 306 if diags.HasErrors() { 307 for _, diag := range diags { 308 t.Errorf("unexpected error: %s", diag.Description().Summary) 309 } 310 } 311 }) 312 } 313 } 314 315 func TestBackendConfig_invalidKey(t *testing.T) { 316 testACC(t) 317 cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{ 318 "region": "us-west-1", 319 "bucket": "tf-test", 320 "key": "/leading-slash", 321 "encrypt": true, 322 "dynamodb_table": "dynamoTable", 323 }) 324 325 _, diags := New().PrepareConfig(cfg) 326 if !diags.HasErrors() { 327 t.Fatal("expected config validation error") 328 } 329 330 cfg = hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{ 331 "region": "us-west-1", 332 "bucket": "tf-test", 333 "key": "trailing-slash/", 334 "encrypt": true, 335 "dynamodb_table": "dynamoTable", 336 }) 337 338 _, diags = New().PrepareConfig(cfg) 339 if !diags.HasErrors() { 340 t.Fatal("expected config validation error") 341 } 342 } 343 344 func TestBackendConfig_invalidSSECustomerKeyLength(t *testing.T) { 345 testACC(t) 346 cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{ 347 "region": "us-west-1", 348 "bucket": "tf-test", 349 "encrypt": true, 350 "key": "state", 351 "dynamodb_table": "dynamoTable", 352 "sse_customer_key": "key", 353 }) 354 355 _, diags := New().PrepareConfig(cfg) 356 if !diags.HasErrors() { 357 t.Fatal("expected error for invalid sse_customer_key length") 358 } 359 } 360 361 func TestBackendConfig_invalidSSECustomerKeyEncoding(t *testing.T) { 362 testACC(t) 363 cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{ 364 "region": "us-west-1", 365 "bucket": "tf-test", 366 "encrypt": true, 367 "key": "state", 368 "dynamodb_table": "dynamoTable", 369 "sse_customer_key": "====CT70aTYB2JGff7AjQtwbiLkwH4npICay1PWtmdka", 370 }) 371 372 diags := New().Configure(cfg) 373 if !diags.HasErrors() { 374 t.Fatal("expected error for failing to decode sse_customer_key") 375 } 376 } 377 378 func TestBackendConfig_conflictingEncryptionSchema(t *testing.T) { 379 testACC(t) 380 cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{ 381 "region": "us-west-1", 382 "bucket": "tf-test", 383 "key": "state", 384 "encrypt": true, 385 "dynamodb_table": "dynamoTable", 386 "sse_customer_key": "1hwbcNPGWL+AwDiyGmRidTWAEVmCWMKbEHA+Es8w75o=", 387 "kms_key_id": "arn:aws:kms:us-west-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab", 388 }) 389 390 diags := New().Configure(cfg) 391 if !diags.HasErrors() { 392 t.Fatal("expected error for simultaneous usage of kms_key_id and sse_customer_key") 393 } 394 } 395 396 func TestBackend(t *testing.T) { 397 testACC(t) 398 399 bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix()) 400 keyName := "testState" 401 402 b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 403 "bucket": bucketName, 404 "key": keyName, 405 "encrypt": true, 406 })).(*Backend) 407 408 createS3Bucket(t, b.s3Client, bucketName) 409 defer deleteS3Bucket(t, b.s3Client, bucketName) 410 411 backend.TestBackendStates(t, b) 412 } 413 414 func TestBackendLocked(t *testing.T) { 415 testACC(t) 416 417 bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix()) 418 keyName := "test/state" 419 420 b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 421 "bucket": bucketName, 422 "key": keyName, 423 "encrypt": true, 424 "dynamodb_table": bucketName, 425 })).(*Backend) 426 427 b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 428 "bucket": bucketName, 429 "key": keyName, 430 "encrypt": true, 431 "dynamodb_table": bucketName, 432 })).(*Backend) 433 434 createS3Bucket(t, b1.s3Client, bucketName) 435 defer deleteS3Bucket(t, b1.s3Client, bucketName) 436 createDynamoDBTable(t, b1.dynClient, bucketName) 437 defer deleteDynamoDBTable(t, b1.dynClient, bucketName) 438 439 backend.TestBackendStateLocks(t, b1, b2) 440 backend.TestBackendStateForceUnlock(t, b1, b2) 441 } 442 443 func TestBackendSSECustomerKey(t *testing.T) { 444 testACC(t) 445 bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix()) 446 447 b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 448 "bucket": bucketName, 449 "encrypt": true, 450 "key": "test-SSE-C", 451 "sse_customer_key": "4Dm1n4rphuFgawxuzY/bEfvLf6rYK0gIjfaDSLlfXNk=", 452 })).(*Backend) 453 454 createS3Bucket(t, b.s3Client, bucketName) 455 defer deleteS3Bucket(t, b.s3Client, bucketName) 456 457 backend.TestBackendStates(t, b) 458 } 459 460 // add some extra junk in S3 to try and confuse the env listing. 461 func TestBackendExtraPaths(t *testing.T) { 462 testACC(t) 463 bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix()) 464 keyName := "test/state/tfstate" 465 466 b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 467 "bucket": bucketName, 468 "key": keyName, 469 "encrypt": true, 470 })).(*Backend) 471 472 createS3Bucket(t, b.s3Client, bucketName) 473 defer deleteS3Bucket(t, b.s3Client, bucketName) 474 475 // put multiple states in old env paths. 476 s1 := states.NewState() 477 s2 := states.NewState() 478 479 // RemoteClient to Put things in various paths 480 client := &RemoteClient{ 481 s3Client: b.s3Client, 482 dynClient: b.dynClient, 483 bucketName: b.bucketName, 484 path: b.path("s1"), 485 serverSideEncryption: b.serverSideEncryption, 486 acl: b.acl, 487 kmsKeyID: b.kmsKeyID, 488 ddbTable: b.ddbTable, 489 } 490 491 // Write the first state 492 stateMgr := &remote.State{Client: client} 493 stateMgr.WriteState(s1) 494 if err := stateMgr.PersistState(nil); err != nil { 495 t.Fatal(err) 496 } 497 498 // Write the second state 499 // Note a new state manager - otherwise, because these 500 // states are equal, the state will not Put to the remote 501 client.path = b.path("s2") 502 stateMgr2 := &remote.State{Client: client} 503 stateMgr2.WriteState(s2) 504 if err := stateMgr2.PersistState(nil); err != nil { 505 t.Fatal(err) 506 } 507 508 s2Lineage := stateMgr2.StateSnapshotMeta().Lineage 509 510 if err := checkStateList(b, []string{"default", "s1", "s2"}); err != nil { 511 t.Fatal(err) 512 } 513 514 // put a state in an env directory name 515 client.path = b.workspaceKeyPrefix + "/error" 516 stateMgr.WriteState(states.NewState()) 517 if err := stateMgr.PersistState(nil); err != nil { 518 t.Fatal(err) 519 } 520 if err := checkStateList(b, []string{"default", "s1", "s2"}); err != nil { 521 t.Fatal(err) 522 } 523 524 // add state with the wrong key for an existing env 525 client.path = b.workspaceKeyPrefix + "/s2/notTestState" 526 stateMgr.WriteState(states.NewState()) 527 if err := stateMgr.PersistState(nil); err != nil { 528 t.Fatal(err) 529 } 530 if err := checkStateList(b, []string{"default", "s1", "s2"}); err != nil { 531 t.Fatal(err) 532 } 533 534 // remove the state with extra subkey 535 if err := client.Delete(); err != nil { 536 t.Fatal(err) 537 } 538 539 // delete the real workspace 540 if err := b.DeleteWorkspace("s2", true); err != nil { 541 t.Fatal(err) 542 } 543 544 if err := checkStateList(b, []string{"default", "s1"}); err != nil { 545 t.Fatal(err) 546 } 547 548 // fetch that state again, which should produce a new lineage 549 s2Mgr, err := b.StateMgr("s2") 550 if err != nil { 551 t.Fatal(err) 552 } 553 if err := s2Mgr.RefreshState(); err != nil { 554 t.Fatal(err) 555 } 556 557 if s2Mgr.(*remote.State).StateSnapshotMeta().Lineage == s2Lineage { 558 t.Fatal("state s2 was not deleted") 559 } 560 s2 = s2Mgr.State() 561 s2Lineage = stateMgr.StateSnapshotMeta().Lineage 562 563 // add a state with a key that matches an existing environment dir name 564 client.path = b.workspaceKeyPrefix + "/s2/" 565 stateMgr.WriteState(states.NewState()) 566 if err := stateMgr.PersistState(nil); err != nil { 567 t.Fatal(err) 568 } 569 570 // make sure s2 is OK 571 s2Mgr, err = b.StateMgr("s2") 572 if err != nil { 573 t.Fatal(err) 574 } 575 if err := s2Mgr.RefreshState(); err != nil { 576 t.Fatal(err) 577 } 578 579 if stateMgr.StateSnapshotMeta().Lineage != s2Lineage { 580 t.Fatal("we got the wrong state for s2") 581 } 582 583 if err := checkStateList(b, []string{"default", "s1", "s2"}); err != nil { 584 t.Fatal(err) 585 } 586 } 587 588 // ensure we can separate the workspace prefix when it also matches the prefix 589 // of the workspace name itself. 590 func TestBackendPrefixInWorkspace(t *testing.T) { 591 testACC(t) 592 bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix()) 593 594 b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 595 "bucket": bucketName, 596 "key": "test-env.tfstate", 597 "workspace_key_prefix": "env", 598 })).(*Backend) 599 600 createS3Bucket(t, b.s3Client, bucketName) 601 defer deleteS3Bucket(t, b.s3Client, bucketName) 602 603 // get a state that contains the prefix as a substring 604 sMgr, err := b.StateMgr("env-1") 605 if err != nil { 606 t.Fatal(err) 607 } 608 if err := sMgr.RefreshState(); err != nil { 609 t.Fatal(err) 610 } 611 612 if err := checkStateList(b, []string{"default", "env-1"}); err != nil { 613 t.Fatal(err) 614 } 615 } 616 617 func TestKeyEnv(t *testing.T) { 618 testACC(t) 619 keyName := "some/paths/tfstate" 620 621 bucket0Name := fmt.Sprintf("terraform-remote-s3-test-%x-0", time.Now().Unix()) 622 b0 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 623 "bucket": bucket0Name, 624 "key": keyName, 625 "encrypt": true, 626 "workspace_key_prefix": "", 627 })).(*Backend) 628 629 createS3Bucket(t, b0.s3Client, bucket0Name) 630 defer deleteS3Bucket(t, b0.s3Client, bucket0Name) 631 632 bucket1Name := fmt.Sprintf("terraform-remote-s3-test-%x-1", time.Now().Unix()) 633 b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 634 "bucket": bucket1Name, 635 "key": keyName, 636 "encrypt": true, 637 "workspace_key_prefix": "project/env:", 638 })).(*Backend) 639 640 createS3Bucket(t, b1.s3Client, bucket1Name) 641 defer deleteS3Bucket(t, b1.s3Client, bucket1Name) 642 643 bucket2Name := fmt.Sprintf("terraform-remote-s3-test-%x-2", time.Now().Unix()) 644 b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 645 "bucket": bucket2Name, 646 "key": keyName, 647 "encrypt": true, 648 })).(*Backend) 649 650 createS3Bucket(t, b2.s3Client, bucket2Name) 651 defer deleteS3Bucket(t, b2.s3Client, bucket2Name) 652 653 if err := testGetWorkspaceForKey(b0, "some/paths/tfstate", ""); err != nil { 654 t.Fatal(err) 655 } 656 657 if err := testGetWorkspaceForKey(b0, "ws1/some/paths/tfstate", "ws1"); err != nil { 658 t.Fatal(err) 659 } 660 661 if err := testGetWorkspaceForKey(b1, "project/env:/ws1/some/paths/tfstate", "ws1"); err != nil { 662 t.Fatal(err) 663 } 664 665 if err := testGetWorkspaceForKey(b1, "project/env:/ws2/some/paths/tfstate", "ws2"); err != nil { 666 t.Fatal(err) 667 } 668 669 if err := testGetWorkspaceForKey(b2, "env:/ws3/some/paths/tfstate", "ws3"); err != nil { 670 t.Fatal(err) 671 } 672 673 backend.TestBackendStates(t, b0) 674 backend.TestBackendStates(t, b1) 675 backend.TestBackendStates(t, b2) 676 } 677 678 func testGetWorkspaceForKey(b *Backend, key string, expected string) error { 679 if actual := b.keyEnv(key); actual != expected { 680 return fmt.Errorf("incorrect workspace for key[%q]. Expected[%q]: Actual[%q]", key, expected, actual) 681 } 682 return nil 683 } 684 685 func checkStateList(b backend.Backend, expected []string) error { 686 states, err := b.Workspaces() 687 if err != nil { 688 return err 689 } 690 691 if !reflect.DeepEqual(states, expected) { 692 return fmt.Errorf("incorrect states listed: %q", states) 693 } 694 return nil 695 } 696 697 func createS3Bucket(t *testing.T, s3Client *s3.S3, bucketName string) { 698 createBucketReq := &s3.CreateBucketInput{ 699 Bucket: &bucketName, 700 } 701 702 // Be clear about what we're doing in case the user needs to clean 703 // this up later. 704 t.Logf("creating S3 bucket %s in %s", bucketName, *s3Client.Config.Region) 705 _, err := s3Client.CreateBucket(createBucketReq) 706 if err != nil { 707 t.Fatal("failed to create test S3 bucket:", err) 708 } 709 } 710 711 func deleteS3Bucket(t *testing.T, s3Client *s3.S3, bucketName string) { 712 warning := "WARNING: Failed to delete the test S3 bucket. It may have been left in your AWS account and may incur storage charges. (error was %s)" 713 714 // first we have to get rid of the env objects, or we can't delete the bucket 715 resp, err := s3Client.ListObjects(&s3.ListObjectsInput{Bucket: &bucketName}) 716 if err != nil { 717 t.Logf(warning, err) 718 return 719 } 720 for _, obj := range resp.Contents { 721 if _, err := s3Client.DeleteObject(&s3.DeleteObjectInput{Bucket: &bucketName, Key: obj.Key}); err != nil { 722 // this will need cleanup no matter what, so just warn and exit 723 t.Logf(warning, err) 724 return 725 } 726 } 727 728 if _, err := s3Client.DeleteBucket(&s3.DeleteBucketInput{Bucket: &bucketName}); err != nil { 729 t.Logf(warning, err) 730 } 731 } 732 733 // create the dynamoDB table, and wait until we can query it. 734 func createDynamoDBTable(t *testing.T, dynClient *dynamodb.DynamoDB, tableName string) { 735 createInput := &dynamodb.CreateTableInput{ 736 AttributeDefinitions: []*dynamodb.AttributeDefinition{ 737 { 738 AttributeName: aws.String("LockID"), 739 AttributeType: aws.String("S"), 740 }, 741 }, 742 KeySchema: []*dynamodb.KeySchemaElement{ 743 { 744 AttributeName: aws.String("LockID"), 745 KeyType: aws.String("HASH"), 746 }, 747 }, 748 ProvisionedThroughput: &dynamodb.ProvisionedThroughput{ 749 ReadCapacityUnits: aws.Int64(5), 750 WriteCapacityUnits: aws.Int64(5), 751 }, 752 TableName: aws.String(tableName), 753 } 754 755 _, err := dynClient.CreateTable(createInput) 756 if err != nil { 757 t.Fatal(err) 758 } 759 760 // now wait until it's ACTIVE 761 start := time.Now() 762 time.Sleep(time.Second) 763 764 describeInput := &dynamodb.DescribeTableInput{ 765 TableName: aws.String(tableName), 766 } 767 768 for { 769 resp, err := dynClient.DescribeTable(describeInput) 770 if err != nil { 771 t.Fatal(err) 772 } 773 774 if *resp.Table.TableStatus == "ACTIVE" { 775 return 776 } 777 778 if time.Since(start) > time.Minute { 779 t.Fatalf("timed out creating DynamoDB table %s", tableName) 780 } 781 782 time.Sleep(3 * time.Second) 783 } 784 785 } 786 787 func deleteDynamoDBTable(t *testing.T, dynClient *dynamodb.DynamoDB, tableName string) { 788 params := &dynamodb.DeleteTableInput{ 789 TableName: aws.String(tableName), 790 } 791 _, err := dynClient.DeleteTable(params) 792 if err != nil { 793 t.Logf("WARNING: Failed to delete the test DynamoDB table %q. It has been left in your AWS account and may incur charges. (error was %s)", tableName, err) 794 } 795 }