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