github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/backend/remote-state/s3/backend_test.go (about) 1 package s3 2 3 import ( 4 "fmt" 5 "os" 6 "reflect" 7 "testing" 8 "time" 9 10 "github.com/aws/aws-sdk-go/aws" 11 "github.com/aws/aws-sdk-go/service/dynamodb" 12 "github.com/aws/aws-sdk-go/service/s3" 13 "github.com/hashicorp/terraform/backend" 14 "github.com/hashicorp/terraform/state/remote" 15 "github.com/hashicorp/terraform/terraform" 16 ) 17 18 // verify that we are doing ACC tests or the S3 tests specifically 19 func testACC(t *testing.T) { 20 skip := os.Getenv("TF_ACC") == "" && os.Getenv("TF_S3_TEST") == "" 21 if skip { 22 t.Log("s3 backend tests require setting TF_ACC or TF_S3_TEST") 23 t.Skip() 24 } 25 if os.Getenv("AWS_DEFAULT_REGION") == "" { 26 os.Setenv("AWS_DEFAULT_REGION", "us-west-2") 27 } 28 } 29 30 func TestBackend_impl(t *testing.T) { 31 var _ backend.Backend = new(Backend) 32 } 33 34 func TestBackendConfig(t *testing.T) { 35 testACC(t) 36 config := map[string]interface{}{ 37 "region": "us-west-1", 38 "bucket": "tf-test", 39 "key": "state", 40 "encrypt": true, 41 "lock_table": "dynamoTable", 42 } 43 44 b := backend.TestBackendConfig(t, New(), config).(*Backend) 45 46 if *b.s3Client.Config.Region != "us-west-1" { 47 t.Fatalf("Incorrect region was populated") 48 } 49 if b.bucketName != "tf-test" { 50 t.Fatalf("Incorrect bucketName was populated") 51 } 52 if b.keyName != "state" { 53 t.Fatalf("Incorrect keyName was populated") 54 } 55 56 credentials, err := b.s3Client.Config.Credentials.Get() 57 if err != nil { 58 t.Fatalf("Error when requesting credentials") 59 } 60 if credentials.AccessKeyID == "" { 61 t.Fatalf("No Access Key Id was populated") 62 } 63 if credentials.SecretAccessKey == "" { 64 t.Fatalf("No Secret Access Key was populated") 65 } 66 } 67 68 func TestBackend(t *testing.T) { 69 testACC(t) 70 71 bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix()) 72 keyName := "testState" 73 74 b := backend.TestBackendConfig(t, New(), map[string]interface{}{ 75 "bucket": bucketName, 76 "key": keyName, 77 "encrypt": true, 78 }).(*Backend) 79 80 createS3Bucket(t, b.s3Client, bucketName) 81 defer deleteS3Bucket(t, b.s3Client, bucketName) 82 83 backend.TestBackend(t, b, nil) 84 } 85 86 func TestBackendLocked(t *testing.T) { 87 testACC(t) 88 89 bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix()) 90 keyName := "test/state" 91 92 b1 := backend.TestBackendConfig(t, New(), map[string]interface{}{ 93 "bucket": bucketName, 94 "key": keyName, 95 "encrypt": true, 96 "lock_table": bucketName, 97 }).(*Backend) 98 99 b2 := backend.TestBackendConfig(t, New(), map[string]interface{}{ 100 "bucket": bucketName, 101 "key": keyName, 102 "encrypt": true, 103 "lock_table": bucketName, 104 }).(*Backend) 105 106 createS3Bucket(t, b1.s3Client, bucketName) 107 defer deleteS3Bucket(t, b1.s3Client, bucketName) 108 createDynamoDBTable(t, b1.dynClient, bucketName) 109 defer deleteDynamoDBTable(t, b1.dynClient, bucketName) 110 111 backend.TestBackend(t, b1, b2) 112 } 113 114 // add some extra junk in S3 to try and confuse the env listing. 115 func TestBackendExtraPaths(t *testing.T) { 116 testACC(t) 117 bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix()) 118 keyName := "test/state/tfstate" 119 120 b := backend.TestBackendConfig(t, New(), map[string]interface{}{ 121 "bucket": bucketName, 122 "key": keyName, 123 "encrypt": true, 124 }).(*Backend) 125 126 createS3Bucket(t, b.s3Client, bucketName) 127 defer deleteS3Bucket(t, b.s3Client, bucketName) 128 129 // put multiple states in old env paths. 130 s1 := terraform.NewState() 131 s2 := terraform.NewState() 132 133 // RemoteClient to Put things in various paths 134 client := &RemoteClient{ 135 s3Client: b.s3Client, 136 dynClient: b.dynClient, 137 bucketName: b.bucketName, 138 path: b.path("s1"), 139 serverSideEncryption: b.serverSideEncryption, 140 acl: b.acl, 141 kmsKeyID: b.kmsKeyID, 142 lockTable: b.lockTable, 143 } 144 145 stateMgr := &remote.State{Client: client} 146 stateMgr.WriteState(s1) 147 if err := stateMgr.PersistState(); err != nil { 148 t.Fatal(err) 149 } 150 151 client.path = b.path("s2") 152 stateMgr.WriteState(s2) 153 if err := stateMgr.PersistState(); err != nil { 154 t.Fatal(err) 155 } 156 157 if err := checkStateList(b, []string{"default", "s1", "s2"}); err != nil { 158 t.Fatal(err) 159 } 160 161 // put a state in an env directory name 162 client.path = keyEnvPrefix + "/error" 163 stateMgr.WriteState(terraform.NewState()) 164 if err := stateMgr.PersistState(); err != nil { 165 t.Fatal(err) 166 } 167 if err := checkStateList(b, []string{"default", "s1", "s2"}); err != nil { 168 t.Fatal(err) 169 } 170 171 // add state with the wrong key for an existing env 172 client.path = keyEnvPrefix + "/s2/notTestState" 173 stateMgr.WriteState(terraform.NewState()) 174 if err := stateMgr.PersistState(); err != nil { 175 t.Fatal(err) 176 } 177 if err := checkStateList(b, []string{"default", "s1", "s2"}); err != nil { 178 t.Fatal(err) 179 } 180 181 // remove the state with extra subkey 182 if err := b.DeleteState("s2"); err != nil { 183 t.Fatal(err) 184 } 185 186 if err := checkStateList(b, []string{"default", "s1"}); err != nil { 187 t.Fatal(err) 188 } 189 190 // fetch that state again, which should produce a new lineage 191 s2Mgr, err := b.State("s2") 192 if err != nil { 193 t.Fatal(err) 194 } 195 if err := s2Mgr.RefreshState(); err != nil { 196 t.Fatal(err) 197 } 198 199 if s2Mgr.State().Lineage == s2.Lineage { 200 t.Fatal("state s2 was not deleted") 201 } 202 s2 = s2Mgr.State() 203 204 // add a state with a key that matches an existing environment dir name 205 client.path = keyEnvPrefix + "/s2/" 206 stateMgr.WriteState(terraform.NewState()) 207 if err := stateMgr.PersistState(); err != nil { 208 t.Fatal(err) 209 } 210 211 // make sure s2 is OK 212 s2Mgr, err = b.State("s2") 213 if err != nil { 214 t.Fatal(err) 215 } 216 if err := s2Mgr.RefreshState(); err != nil { 217 t.Fatal(err) 218 } 219 220 if s2Mgr.State().Lineage != s2.Lineage { 221 t.Fatal("we got the wrong state for s2") 222 } 223 224 if err := checkStateList(b, []string{"default", "s1", "s2"}); err != nil { 225 t.Fatal(err) 226 } 227 } 228 229 func checkStateList(b backend.Backend, expected []string) error { 230 states, err := b.States() 231 if err != nil { 232 return err 233 } 234 235 if !reflect.DeepEqual(states, expected) { 236 return fmt.Errorf("incorrect states listed: %q", states) 237 } 238 return nil 239 } 240 241 func createS3Bucket(t *testing.T, s3Client *s3.S3, bucketName string) { 242 createBucketReq := &s3.CreateBucketInput{ 243 Bucket: &bucketName, 244 } 245 246 // Be clear about what we're doing in case the user needs to clean 247 // this up later. 248 t.Logf("creating S3 bucket %s in %s", bucketName, *s3Client.Config.Region) 249 _, err := s3Client.CreateBucket(createBucketReq) 250 if err != nil { 251 t.Fatal("failed to create test S3 bucket:", err) 252 } 253 } 254 255 func deleteS3Bucket(t *testing.T, s3Client *s3.S3, bucketName string) { 256 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)" 257 258 // first we have to get rid of the env objects, or we can't delete the bucket 259 resp, err := s3Client.ListObjects(&s3.ListObjectsInput{Bucket: &bucketName}) 260 if err != nil { 261 t.Logf(warning, err) 262 return 263 } 264 for _, obj := range resp.Contents { 265 if _, err := s3Client.DeleteObject(&s3.DeleteObjectInput{Bucket: &bucketName, Key: obj.Key}); err != nil { 266 // this will need cleanup no matter what, so just warn and exit 267 t.Logf(warning, err) 268 return 269 } 270 } 271 272 if _, err := s3Client.DeleteBucket(&s3.DeleteBucketInput{Bucket: &bucketName}); err != nil { 273 t.Logf(warning, err) 274 } 275 } 276 277 // create the dynamoDB table, and wait until we can query it. 278 func createDynamoDBTable(t *testing.T, dynClient *dynamodb.DynamoDB, tableName string) { 279 createInput := &dynamodb.CreateTableInput{ 280 AttributeDefinitions: []*dynamodb.AttributeDefinition{ 281 { 282 AttributeName: aws.String("LockID"), 283 AttributeType: aws.String("S"), 284 }, 285 }, 286 KeySchema: []*dynamodb.KeySchemaElement{ 287 { 288 AttributeName: aws.String("LockID"), 289 KeyType: aws.String("HASH"), 290 }, 291 }, 292 ProvisionedThroughput: &dynamodb.ProvisionedThroughput{ 293 ReadCapacityUnits: aws.Int64(5), 294 WriteCapacityUnits: aws.Int64(5), 295 }, 296 TableName: aws.String(tableName), 297 } 298 299 _, err := dynClient.CreateTable(createInput) 300 if err != nil { 301 t.Fatal(err) 302 } 303 304 // now wait until it's ACTIVE 305 start := time.Now() 306 time.Sleep(time.Second) 307 308 describeInput := &dynamodb.DescribeTableInput{ 309 TableName: aws.String(tableName), 310 } 311 312 for { 313 resp, err := dynClient.DescribeTable(describeInput) 314 if err != nil { 315 t.Fatal(err) 316 } 317 318 if *resp.Table.TableStatus == "ACTIVE" { 319 return 320 } 321 322 if time.Since(start) > time.Minute { 323 t.Fatalf("timed out creating DynamoDB table %s", tableName) 324 } 325 326 time.Sleep(3 * time.Second) 327 } 328 329 } 330 331 func deleteDynamoDBTable(t *testing.T, dynClient *dynamodb.DynamoDB, tableName string) { 332 params := &dynamodb.DeleteTableInput{ 333 TableName: aws.String(tableName), 334 } 335 _, err := dynClient.DeleteTable(params) 336 if err != nil { 337 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) 338 } 339 }