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