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