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