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  }