github.com/leeprovoost/terraform@v0.6.10-0.20160119085442-96f3f76118e7/builtin/providers/aws/resource_aws_dynamodb_table.go (about) 1 package aws 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "strings" 8 "time" 9 10 "github.com/hashicorp/terraform/helper/resource" 11 "github.com/hashicorp/terraform/helper/schema" 12 13 "github.com/aws/aws-sdk-go/aws" 14 "github.com/aws/aws-sdk-go/aws/awserr" 15 "github.com/aws/aws-sdk-go/service/dynamodb" 16 "github.com/hashicorp/terraform/helper/hashcode" 17 ) 18 19 // Number of times to retry if a throttling-related exception occurs 20 const DYNAMODB_MAX_THROTTLE_RETRIES = 5 21 22 // How long to sleep when a throttle-event happens 23 const DYNAMODB_THROTTLE_SLEEP = 5 * time.Second 24 25 // How long to sleep if a limit-exceeded event happens 26 const DYNAMODB_LIMIT_EXCEEDED_SLEEP = 10 * time.Second 27 28 // A number of these are marked as computed because if you don't 29 // provide a value, DynamoDB will provide you with defaults (which are the 30 // default values specified below) 31 func resourceAwsDynamoDbTable() *schema.Resource { 32 return &schema.Resource{ 33 Create: resourceAwsDynamoDbTableCreate, 34 Read: resourceAwsDynamoDbTableRead, 35 Update: resourceAwsDynamoDbTableUpdate, 36 Delete: resourceAwsDynamoDbTableDelete, 37 38 Schema: map[string]*schema.Schema{ 39 "arn": &schema.Schema{ 40 Type: schema.TypeString, 41 Computed: true, 42 }, 43 "name": &schema.Schema{ 44 Type: schema.TypeString, 45 Required: true, 46 ForceNew: true, 47 }, 48 "hash_key": &schema.Schema{ 49 Type: schema.TypeString, 50 Required: true, 51 }, 52 "range_key": &schema.Schema{ 53 Type: schema.TypeString, 54 Optional: true, 55 }, 56 "write_capacity": &schema.Schema{ 57 Type: schema.TypeInt, 58 Required: true, 59 }, 60 "read_capacity": &schema.Schema{ 61 Type: schema.TypeInt, 62 Required: true, 63 }, 64 "attribute": &schema.Schema{ 65 Type: schema.TypeSet, 66 Required: true, 67 Elem: &schema.Resource{ 68 Schema: map[string]*schema.Schema{ 69 "name": &schema.Schema{ 70 Type: schema.TypeString, 71 Required: true, 72 }, 73 "type": &schema.Schema{ 74 Type: schema.TypeString, 75 Required: true, 76 }, 77 }, 78 }, 79 Set: func(v interface{}) int { 80 var buf bytes.Buffer 81 m := v.(map[string]interface{}) 82 buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) 83 return hashcode.String(buf.String()) 84 }, 85 }, 86 "local_secondary_index": &schema.Schema{ 87 Type: schema.TypeSet, 88 Optional: true, 89 Elem: &schema.Resource{ 90 Schema: map[string]*schema.Schema{ 91 "name": &schema.Schema{ 92 Type: schema.TypeString, 93 Required: true, 94 }, 95 "range_key": &schema.Schema{ 96 Type: schema.TypeString, 97 Required: true, 98 }, 99 "projection_type": &schema.Schema{ 100 Type: schema.TypeString, 101 Required: true, 102 }, 103 "non_key_attributes": &schema.Schema{ 104 Type: schema.TypeList, 105 Optional: true, 106 Elem: &schema.Schema{Type: schema.TypeString}, 107 }, 108 }, 109 }, 110 Set: func(v interface{}) int { 111 var buf bytes.Buffer 112 m := v.(map[string]interface{}) 113 buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) 114 return hashcode.String(buf.String()) 115 }, 116 }, 117 "global_secondary_index": &schema.Schema{ 118 Type: schema.TypeSet, 119 Optional: true, 120 Elem: &schema.Resource{ 121 Schema: map[string]*schema.Schema{ 122 "name": &schema.Schema{ 123 Type: schema.TypeString, 124 Required: true, 125 }, 126 "write_capacity": &schema.Schema{ 127 Type: schema.TypeInt, 128 Required: true, 129 }, 130 "read_capacity": &schema.Schema{ 131 Type: schema.TypeInt, 132 Required: true, 133 }, 134 "hash_key": &schema.Schema{ 135 Type: schema.TypeString, 136 Required: true, 137 }, 138 "range_key": &schema.Schema{ 139 Type: schema.TypeString, 140 Optional: true, 141 }, 142 "projection_type": &schema.Schema{ 143 Type: schema.TypeString, 144 Required: true, 145 }, 146 "non_key_attributes": &schema.Schema{ 147 Type: schema.TypeList, 148 Optional: true, 149 Elem: &schema.Schema{Type: schema.TypeString}, 150 }, 151 }, 152 }, 153 // GSI names are the uniqueness constraint 154 Set: func(v interface{}) int { 155 var buf bytes.Buffer 156 m := v.(map[string]interface{}) 157 buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) 158 buf.WriteString(fmt.Sprintf("%d-", m["write_capacity"].(int))) 159 buf.WriteString(fmt.Sprintf("%d-", m["read_capacity"].(int))) 160 return hashcode.String(buf.String()) 161 }, 162 }, 163 "stream_enabled": &schema.Schema{ 164 Type: schema.TypeBool, 165 Optional: true, 166 Computed: true, 167 }, 168 "stream_view_type": &schema.Schema{ 169 Type: schema.TypeString, 170 Optional: true, 171 Computed: true, 172 StateFunc: func(v interface{}) string { 173 value := v.(string) 174 return strings.ToUpper(value) 175 }, 176 ValidateFunc: validateStreamViewType, 177 }, 178 }, 179 } 180 } 181 182 func resourceAwsDynamoDbTableCreate(d *schema.ResourceData, meta interface{}) error { 183 dynamodbconn := meta.(*AWSClient).dynamodbconn 184 185 name := d.Get("name").(string) 186 187 log.Printf("[DEBUG] DynamoDB table create: %s", name) 188 189 throughput := &dynamodb.ProvisionedThroughput{ 190 ReadCapacityUnits: aws.Int64(int64(d.Get("read_capacity").(int))), 191 WriteCapacityUnits: aws.Int64(int64(d.Get("write_capacity").(int))), 192 } 193 194 hash_key_name := d.Get("hash_key").(string) 195 keyschema := []*dynamodb.KeySchemaElement{ 196 &dynamodb.KeySchemaElement{ 197 AttributeName: aws.String(hash_key_name), 198 KeyType: aws.String("HASH"), 199 }, 200 } 201 202 if range_key, ok := d.GetOk("range_key"); ok { 203 range_schema_element := &dynamodb.KeySchemaElement{ 204 AttributeName: aws.String(range_key.(string)), 205 KeyType: aws.String("RANGE"), 206 } 207 keyschema = append(keyschema, range_schema_element) 208 } 209 210 req := &dynamodb.CreateTableInput{ 211 TableName: aws.String(name), 212 ProvisionedThroughput: throughput, 213 KeySchema: keyschema, 214 } 215 216 if attributedata, ok := d.GetOk("attribute"); ok { 217 attributes := []*dynamodb.AttributeDefinition{} 218 attributeSet := attributedata.(*schema.Set) 219 for _, attribute := range attributeSet.List() { 220 attr := attribute.(map[string]interface{}) 221 attributes = append(attributes, &dynamodb.AttributeDefinition{ 222 AttributeName: aws.String(attr["name"].(string)), 223 AttributeType: aws.String(attr["type"].(string)), 224 }) 225 } 226 227 req.AttributeDefinitions = attributes 228 } 229 230 if lsidata, ok := d.GetOk("local_secondary_index"); ok { 231 fmt.Printf("[DEBUG] Adding LSI data to the table") 232 233 lsiSet := lsidata.(*schema.Set) 234 localSecondaryIndexes := []*dynamodb.LocalSecondaryIndex{} 235 for _, lsiObject := range lsiSet.List() { 236 lsi := lsiObject.(map[string]interface{}) 237 238 projection := &dynamodb.Projection{ 239 ProjectionType: aws.String(lsi["projection_type"].(string)), 240 } 241 242 if lsi["projection_type"] == "INCLUDE" { 243 non_key_attributes := []*string{} 244 for _, attr := range lsi["non_key_attributes"].([]interface{}) { 245 non_key_attributes = append(non_key_attributes, aws.String(attr.(string))) 246 } 247 projection.NonKeyAttributes = non_key_attributes 248 } 249 250 localSecondaryIndexes = append(localSecondaryIndexes, &dynamodb.LocalSecondaryIndex{ 251 IndexName: aws.String(lsi["name"].(string)), 252 KeySchema: []*dynamodb.KeySchemaElement{ 253 &dynamodb.KeySchemaElement{ 254 AttributeName: aws.String(hash_key_name), 255 KeyType: aws.String("HASH"), 256 }, 257 &dynamodb.KeySchemaElement{ 258 AttributeName: aws.String(lsi["range_key"].(string)), 259 KeyType: aws.String("RANGE"), 260 }, 261 }, 262 Projection: projection, 263 }) 264 } 265 266 req.LocalSecondaryIndexes = localSecondaryIndexes 267 268 fmt.Printf("[DEBUG] Added %d LSI definitions", len(localSecondaryIndexes)) 269 } 270 271 if gsidata, ok := d.GetOk("global_secondary_index"); ok { 272 globalSecondaryIndexes := []*dynamodb.GlobalSecondaryIndex{} 273 274 gsiSet := gsidata.(*schema.Set) 275 for _, gsiObject := range gsiSet.List() { 276 gsi := gsiObject.(map[string]interface{}) 277 gsiObject := createGSIFromData(&gsi) 278 globalSecondaryIndexes = append(globalSecondaryIndexes, &gsiObject) 279 } 280 req.GlobalSecondaryIndexes = globalSecondaryIndexes 281 } 282 283 if _, ok := d.GetOk("stream_enabled"); ok { 284 285 req.StreamSpecification = &dynamodb.StreamSpecification{ 286 StreamEnabled: aws.Bool(d.Get("stream_enabled").(bool)), 287 StreamViewType: aws.String(d.Get("stream_view_type").(string)), 288 } 289 290 fmt.Printf("[DEBUG] Adding StreamSpecifications to the table") 291 } 292 293 attemptCount := 1 294 for attemptCount <= DYNAMODB_MAX_THROTTLE_RETRIES { 295 output, err := dynamodbconn.CreateTable(req) 296 if err != nil { 297 if awsErr, ok := err.(awserr.Error); ok { 298 if awsErr.Code() == "ThrottlingException" { 299 log.Printf("[DEBUG] Attempt %d/%d: Sleeping for a bit to throttle back create request", attemptCount, DYNAMODB_MAX_THROTTLE_RETRIES) 300 time.Sleep(DYNAMODB_THROTTLE_SLEEP) 301 attemptCount += 1 302 } else if awsErr.Code() == "LimitExceededException" { 303 log.Printf("[DEBUG] Limit on concurrent table creations hit, sleeping for a bit") 304 time.Sleep(DYNAMODB_LIMIT_EXCEEDED_SLEEP) 305 attemptCount += 1 306 } else { 307 // Some other non-retryable exception occurred 308 return fmt.Errorf("AWS Error creating DynamoDB table: %s", err) 309 } 310 } else { 311 // Non-AWS exception occurred, give up 312 return fmt.Errorf("Error creating DynamoDB table: %s", err) 313 } 314 } else { 315 // No error, set ID and return 316 d.SetId(*output.TableDescription.TableName) 317 if err := d.Set("arn", *output.TableDescription.TableArn); err != nil { 318 return err 319 } 320 321 return resourceAwsDynamoDbTableRead(d, meta) 322 } 323 } 324 325 // Too many throttling events occurred, give up 326 return fmt.Errorf("Unable to create DynamoDB table '%s' after %d attempts", name, attemptCount) 327 } 328 329 func resourceAwsDynamoDbTableUpdate(d *schema.ResourceData, meta interface{}) error { 330 331 log.Printf("[DEBUG] Updating DynamoDB table %s", d.Id()) 332 dynamodbconn := meta.(*AWSClient).dynamodbconn 333 334 // Ensure table is active before trying to update 335 waitForTableToBeActive(d.Id(), meta) 336 337 // LSI can only be done at create-time, abort if it's been changed 338 if d.HasChange("local_secondary_index") { 339 return fmt.Errorf("Local secondary indexes can only be built at creation, you cannot update them!") 340 } 341 342 if d.HasChange("hash_key") { 343 return fmt.Errorf("Hash key can only be specified at creation, you cannot modify it.") 344 } 345 346 if d.HasChange("range_key") { 347 return fmt.Errorf("Range key can only be specified at creation, you cannot modify it.") 348 } 349 350 if d.HasChange("read_capacity") || d.HasChange("write_capacity") { 351 req := &dynamodb.UpdateTableInput{ 352 TableName: aws.String(d.Id()), 353 } 354 355 throughput := &dynamodb.ProvisionedThroughput{ 356 ReadCapacityUnits: aws.Int64(int64(d.Get("read_capacity").(int))), 357 WriteCapacityUnits: aws.Int64(int64(d.Get("write_capacity").(int))), 358 } 359 req.ProvisionedThroughput = throughput 360 361 _, err := dynamodbconn.UpdateTable(req) 362 363 if err != nil { 364 return err 365 } 366 367 waitForTableToBeActive(d.Id(), meta) 368 } 369 370 if d.HasChange("stream_enabled") || d.HasChange("stream_view_type") { 371 req := &dynamodb.UpdateTableInput{ 372 TableName: aws.String(d.Id()), 373 } 374 375 req.StreamSpecification = &dynamodb.StreamSpecification{ 376 StreamEnabled: aws.Bool(d.Get("stream_enabled").(bool)), 377 StreamViewType: aws.String(d.Get("stream_view_type").(string)), 378 } 379 380 _, err := dynamodbconn.UpdateTable(req) 381 382 if err != nil { 383 return err 384 } 385 386 waitForTableToBeActive(d.Id(), meta) 387 } 388 389 if d.HasChange("global_secondary_index") { 390 log.Printf("[DEBUG] Changed GSI data") 391 req := &dynamodb.UpdateTableInput{ 392 TableName: aws.String(d.Id()), 393 } 394 395 o, n := d.GetChange("global_secondary_index") 396 397 oldSet := o.(*schema.Set) 398 newSet := n.(*schema.Set) 399 400 // Track old names so we can know which ones we need to just update based on 401 // capacity changes, terraform appears to only diff on the set hash, not the 402 // contents so we need to make sure we don't delete any indexes that we 403 // just want to update the capacity for 404 oldGsiNameSet := make(map[string]bool) 405 newGsiNameSet := make(map[string]bool) 406 407 for _, gsidata := range oldSet.List() { 408 gsiName := gsidata.(map[string]interface{})["name"].(string) 409 oldGsiNameSet[gsiName] = true 410 } 411 412 for _, gsidata := range newSet.List() { 413 gsiName := gsidata.(map[string]interface{})["name"].(string) 414 newGsiNameSet[gsiName] = true 415 } 416 417 // First determine what's new 418 for _, newgsidata := range newSet.List() { 419 updates := []*dynamodb.GlobalSecondaryIndexUpdate{} 420 newGsiName := newgsidata.(map[string]interface{})["name"].(string) 421 if _, exists := oldGsiNameSet[newGsiName]; !exists { 422 attributes := []*dynamodb.AttributeDefinition{} 423 gsidata := newgsidata.(map[string]interface{}) 424 gsi := createGSIFromData(&gsidata) 425 log.Printf("[DEBUG] Adding GSI %s", *gsi.IndexName) 426 update := &dynamodb.GlobalSecondaryIndexUpdate{ 427 Create: &dynamodb.CreateGlobalSecondaryIndexAction{ 428 IndexName: gsi.IndexName, 429 KeySchema: gsi.KeySchema, 430 ProvisionedThroughput: gsi.ProvisionedThroughput, 431 Projection: gsi.Projection, 432 }, 433 } 434 updates = append(updates, update) 435 436 // Hash key is required, range key isn't 437 hashkey_type, err := getAttributeType(d, *gsi.KeySchema[0].AttributeName) 438 if err != nil { 439 return err 440 } 441 442 attributes = append(attributes, &dynamodb.AttributeDefinition{ 443 AttributeName: gsi.KeySchema[0].AttributeName, 444 AttributeType: aws.String(hashkey_type), 445 }) 446 447 // If there's a range key, there will be 2 elements in KeySchema 448 if len(gsi.KeySchema) == 2 { 449 rangekey_type, err := getAttributeType(d, *gsi.KeySchema[1].AttributeName) 450 if err != nil { 451 return err 452 } 453 454 attributes = append(attributes, &dynamodb.AttributeDefinition{ 455 AttributeName: gsi.KeySchema[1].AttributeName, 456 AttributeType: aws.String(rangekey_type), 457 }) 458 } 459 460 req.AttributeDefinitions = attributes 461 req.GlobalSecondaryIndexUpdates = updates 462 _, err = dynamodbconn.UpdateTable(req) 463 464 if err != nil { 465 return err 466 } 467 468 waitForTableToBeActive(d.Id(), meta) 469 waitForGSIToBeActive(d.Id(), *gsi.IndexName, meta) 470 471 } 472 } 473 474 for _, oldgsidata := range oldSet.List() { 475 updates := []*dynamodb.GlobalSecondaryIndexUpdate{} 476 oldGsiName := oldgsidata.(map[string]interface{})["name"].(string) 477 if _, exists := newGsiNameSet[oldGsiName]; !exists { 478 gsidata := oldgsidata.(map[string]interface{}) 479 log.Printf("[DEBUG] Deleting GSI %s", gsidata["name"].(string)) 480 update := &dynamodb.GlobalSecondaryIndexUpdate{ 481 Delete: &dynamodb.DeleteGlobalSecondaryIndexAction{ 482 IndexName: aws.String(gsidata["name"].(string)), 483 }, 484 } 485 updates = append(updates, update) 486 487 req.GlobalSecondaryIndexUpdates = updates 488 _, err := dynamodbconn.UpdateTable(req) 489 490 if err != nil { 491 return err 492 } 493 494 waitForTableToBeActive(d.Id(), meta) 495 } 496 } 497 } 498 499 // Update any out-of-date read / write capacity 500 if gsiObjects, ok := d.GetOk("global_secondary_index"); ok { 501 gsiSet := gsiObjects.(*schema.Set) 502 if len(gsiSet.List()) > 0 { 503 log.Printf("Updating capacity as needed!") 504 505 // We can only change throughput, but we need to make sure it's actually changed 506 tableDescription, err := dynamodbconn.DescribeTable(&dynamodb.DescribeTableInput{ 507 TableName: aws.String(d.Id()), 508 }) 509 510 if err != nil { 511 return err 512 } 513 514 table := tableDescription.Table 515 516 updates := []*dynamodb.GlobalSecondaryIndexUpdate{} 517 518 for _, updatedgsidata := range gsiSet.List() { 519 gsidata := updatedgsidata.(map[string]interface{}) 520 gsiName := gsidata["name"].(string) 521 gsiWriteCapacity := gsidata["write_capacity"].(int) 522 gsiReadCapacity := gsidata["read_capacity"].(int) 523 524 log.Printf("[DEBUG] Updating GSI %s", gsiName) 525 gsi, err := getGlobalSecondaryIndex(gsiName, table.GlobalSecondaryIndexes) 526 527 if err != nil { 528 return err 529 } 530 531 capacityUpdated := false 532 533 if int64(gsiReadCapacity) != *gsi.ProvisionedThroughput.ReadCapacityUnits || 534 int64(gsiWriteCapacity) != *gsi.ProvisionedThroughput.WriteCapacityUnits { 535 capacityUpdated = true 536 } 537 538 if capacityUpdated { 539 update := &dynamodb.GlobalSecondaryIndexUpdate{ 540 Update: &dynamodb.UpdateGlobalSecondaryIndexAction{ 541 IndexName: aws.String(gsidata["name"].(string)), 542 ProvisionedThroughput: &dynamodb.ProvisionedThroughput{ 543 WriteCapacityUnits: aws.Int64(int64(gsiWriteCapacity)), 544 ReadCapacityUnits: aws.Int64(int64(gsiReadCapacity)), 545 }, 546 }, 547 } 548 updates = append(updates, update) 549 550 } 551 552 if len(updates) > 0 { 553 554 req := &dynamodb.UpdateTableInput{ 555 TableName: aws.String(d.Id()), 556 } 557 558 req.GlobalSecondaryIndexUpdates = updates 559 560 log.Printf("[DEBUG] Updating GSI read / write capacity on %s", d.Id()) 561 _, err := dynamodbconn.UpdateTable(req) 562 563 if err != nil { 564 log.Printf("[DEBUG] Error updating table: %s", err) 565 return err 566 } 567 } 568 } 569 } 570 571 } 572 573 return resourceAwsDynamoDbTableRead(d, meta) 574 } 575 576 func resourceAwsDynamoDbTableRead(d *schema.ResourceData, meta interface{}) error { 577 dynamodbconn := meta.(*AWSClient).dynamodbconn 578 log.Printf("[DEBUG] Loading data for DynamoDB table '%s'", d.Id()) 579 req := &dynamodb.DescribeTableInput{ 580 TableName: aws.String(d.Id()), 581 } 582 583 result, err := dynamodbconn.DescribeTable(req) 584 585 if err != nil { 586 return err 587 } 588 589 table := result.Table 590 591 d.Set("write_capacity", table.ProvisionedThroughput.WriteCapacityUnits) 592 d.Set("read_capacity", table.ProvisionedThroughput.ReadCapacityUnits) 593 594 attributes := []interface{}{} 595 for _, attrdef := range table.AttributeDefinitions { 596 attribute := map[string]string{ 597 "name": *attrdef.AttributeName, 598 "type": *attrdef.AttributeType, 599 } 600 attributes = append(attributes, attribute) 601 log.Printf("[DEBUG] Added Attribute: %s", attribute["name"]) 602 } 603 604 d.Set("attribute", attributes) 605 606 gsiList := make([]map[string]interface{}, 0, len(table.GlobalSecondaryIndexes)) 607 for _, gsiObject := range table.GlobalSecondaryIndexes { 608 gsi := map[string]interface{}{ 609 "write_capacity": *gsiObject.ProvisionedThroughput.WriteCapacityUnits, 610 "read_capacity": *gsiObject.ProvisionedThroughput.ReadCapacityUnits, 611 "name": *gsiObject.IndexName, 612 } 613 614 for _, attribute := range gsiObject.KeySchema { 615 if *attribute.KeyType == "HASH" { 616 gsi["hash_key"] = *attribute.AttributeName 617 } 618 619 if *attribute.KeyType == "RANGE" { 620 gsi["range_key"] = *attribute.AttributeName 621 } 622 } 623 624 gsi["projection_type"] = *(gsiObject.Projection.ProjectionType) 625 626 nonKeyAttrs := make([]string, 0, len(gsiObject.Projection.NonKeyAttributes)) 627 for _, nonKeyAttr := range gsiObject.Projection.NonKeyAttributes { 628 nonKeyAttrs = append(nonKeyAttrs, *nonKeyAttr) 629 } 630 gsi["non_key_attributes"] = nonKeyAttrs 631 632 gsiList = append(gsiList, gsi) 633 log.Printf("[DEBUG] Added GSI: %s - Read: %d / Write: %d", gsi["name"], gsi["read_capacity"], gsi["write_capacity"]) 634 } 635 636 if table.StreamSpecification != nil { 637 d.Set("stream_view_type", table.StreamSpecification.StreamViewType) 638 d.Set("stream_enabled", table.StreamSpecification.StreamEnabled) 639 } 640 641 err = d.Set("global_secondary_index", gsiList) 642 if err != nil { 643 return err 644 } 645 646 d.Set("arn", table.TableArn) 647 648 return nil 649 } 650 651 func resourceAwsDynamoDbTableDelete(d *schema.ResourceData, meta interface{}) error { 652 dynamodbconn := meta.(*AWSClient).dynamodbconn 653 654 waitForTableToBeActive(d.Id(), meta) 655 656 log.Printf("[DEBUG] DynamoDB delete table: %s", d.Id()) 657 658 _, err := dynamodbconn.DeleteTable(&dynamodb.DeleteTableInput{ 659 TableName: aws.String(d.Id()), 660 }) 661 if err != nil { 662 return err 663 } 664 665 params := &dynamodb.DescribeTableInput{ 666 TableName: aws.String(d.Id()), 667 } 668 669 err = resource.Retry(10*time.Minute, func() error { 670 t, err := dynamodbconn.DescribeTable(params) 671 if err != nil { 672 if awserr, ok := err.(awserr.Error); ok && awserr.Code() == "ResourceNotFoundException" { 673 return nil 674 } 675 // Didn't recognize the error, so shouldn't retry. 676 return resource.RetryError{Err: err} 677 } 678 679 if t != nil { 680 if t.Table.TableStatus != nil && strings.ToLower(*t.Table.TableStatus) == "deleting" { 681 log.Printf("[DEBUG] AWS Dynamo DB table (%s) is still deleting", d.Id()) 682 return fmt.Errorf("still deleting") 683 } 684 } 685 686 // we should be not found or deleting, so error here 687 return resource.RetryError{Err: fmt.Errorf("[ERR] Error deleting Dynamo DB table, unexpected state: %s", t)} 688 }) 689 690 // check error from retry 691 if err != nil { 692 return err 693 } 694 695 return nil 696 } 697 698 func createGSIFromData(data *map[string]interface{}) dynamodb.GlobalSecondaryIndex { 699 700 projection := &dynamodb.Projection{ 701 ProjectionType: aws.String((*data)["projection_type"].(string)), 702 } 703 704 if (*data)["projection_type"] == "INCLUDE" { 705 non_key_attributes := []*string{} 706 for _, attr := range (*data)["non_key_attributes"].([]interface{}) { 707 non_key_attributes = append(non_key_attributes, aws.String(attr.(string))) 708 } 709 projection.NonKeyAttributes = non_key_attributes 710 } 711 712 writeCapacity := (*data)["write_capacity"].(int) 713 readCapacity := (*data)["read_capacity"].(int) 714 715 key_schema := []*dynamodb.KeySchemaElement{ 716 &dynamodb.KeySchemaElement{ 717 AttributeName: aws.String((*data)["hash_key"].(string)), 718 KeyType: aws.String("HASH"), 719 }, 720 } 721 722 range_key_name := (*data)["range_key"] 723 if range_key_name != "" { 724 range_key_element := &dynamodb.KeySchemaElement{ 725 AttributeName: aws.String(range_key_name.(string)), 726 KeyType: aws.String("RANGE"), 727 } 728 729 key_schema = append(key_schema, range_key_element) 730 } 731 732 return dynamodb.GlobalSecondaryIndex{ 733 IndexName: aws.String((*data)["name"].(string)), 734 KeySchema: key_schema, 735 Projection: projection, 736 ProvisionedThroughput: &dynamodb.ProvisionedThroughput{ 737 WriteCapacityUnits: aws.Int64(int64(writeCapacity)), 738 ReadCapacityUnits: aws.Int64(int64(readCapacity)), 739 }, 740 } 741 } 742 743 func getGlobalSecondaryIndex(indexName string, indexList []*dynamodb.GlobalSecondaryIndexDescription) (*dynamodb.GlobalSecondaryIndexDescription, error) { 744 for _, gsi := range indexList { 745 if *gsi.IndexName == indexName { 746 return gsi, nil 747 } 748 } 749 750 return &dynamodb.GlobalSecondaryIndexDescription{}, fmt.Errorf("Can't find a GSI by that name...") 751 } 752 753 func getAttributeType(d *schema.ResourceData, attributeName string) (string, error) { 754 if attributedata, ok := d.GetOk("attribute"); ok { 755 attributeSet := attributedata.(*schema.Set) 756 for _, attribute := range attributeSet.List() { 757 attr := attribute.(map[string]interface{}) 758 if attr["name"] == attributeName { 759 return attr["type"].(string), nil 760 } 761 } 762 } 763 764 return "", fmt.Errorf("Unable to find an attribute named %s", attributeName) 765 } 766 767 func waitForGSIToBeActive(tableName string, gsiName string, meta interface{}) error { 768 dynamodbconn := meta.(*AWSClient).dynamodbconn 769 req := &dynamodb.DescribeTableInput{ 770 TableName: aws.String(tableName), 771 } 772 773 activeIndex := false 774 775 for activeIndex == false { 776 777 result, err := dynamodbconn.DescribeTable(req) 778 779 if err != nil { 780 return err 781 } 782 783 table := result.Table 784 var targetGSI *dynamodb.GlobalSecondaryIndexDescription = nil 785 786 for _, gsi := range table.GlobalSecondaryIndexes { 787 if *gsi.IndexName == gsiName { 788 targetGSI = gsi 789 } 790 } 791 792 if targetGSI != nil { 793 activeIndex = *targetGSI.IndexStatus == "ACTIVE" 794 795 if !activeIndex { 796 log.Printf("[DEBUG] Sleeping for 5 seconds for %s GSI to become active", gsiName) 797 time.Sleep(5 * time.Second) 798 } 799 } else { 800 log.Printf("[DEBUG] GSI %s did not exist, giving up", gsiName) 801 break 802 } 803 } 804 805 return nil 806 807 } 808 809 func waitForTableToBeActive(tableName string, meta interface{}) error { 810 dynamodbconn := meta.(*AWSClient).dynamodbconn 811 req := &dynamodb.DescribeTableInput{ 812 TableName: aws.String(tableName), 813 } 814 815 activeState := false 816 817 for activeState == false { 818 result, err := dynamodbconn.DescribeTable(req) 819 820 if err != nil { 821 return err 822 } 823 824 activeState = *result.Table.TableStatus == "ACTIVE" 825 826 // Wait for a few seconds 827 if !activeState { 828 log.Printf("[DEBUG] Sleeping for 5 seconds for table to become active") 829 time.Sleep(5 * time.Second) 830 } 831 } 832 833 return nil 834 835 }