github.com/andresvia/terraform@v0.6.15-0.20160412045437-d51c75946785/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 "stream_arn": &schema.Schema{ 179 Type: schema.TypeString, 180 Computed: true, 181 }, 182 }, 183 } 184 } 185 186 func resourceAwsDynamoDbTableCreate(d *schema.ResourceData, meta interface{}) error { 187 dynamodbconn := meta.(*AWSClient).dynamodbconn 188 189 name := d.Get("name").(string) 190 191 log.Printf("[DEBUG] DynamoDB table create: %s", name) 192 193 throughput := &dynamodb.ProvisionedThroughput{ 194 ReadCapacityUnits: aws.Int64(int64(d.Get("read_capacity").(int))), 195 WriteCapacityUnits: aws.Int64(int64(d.Get("write_capacity").(int))), 196 } 197 198 hash_key_name := d.Get("hash_key").(string) 199 keyschema := []*dynamodb.KeySchemaElement{ 200 &dynamodb.KeySchemaElement{ 201 AttributeName: aws.String(hash_key_name), 202 KeyType: aws.String("HASH"), 203 }, 204 } 205 206 if range_key, ok := d.GetOk("range_key"); ok { 207 range_schema_element := &dynamodb.KeySchemaElement{ 208 AttributeName: aws.String(range_key.(string)), 209 KeyType: aws.String("RANGE"), 210 } 211 keyschema = append(keyschema, range_schema_element) 212 } 213 214 req := &dynamodb.CreateTableInput{ 215 TableName: aws.String(name), 216 ProvisionedThroughput: throughput, 217 KeySchema: keyschema, 218 } 219 220 if attributedata, ok := d.GetOk("attribute"); ok { 221 attributes := []*dynamodb.AttributeDefinition{} 222 attributeSet := attributedata.(*schema.Set) 223 for _, attribute := range attributeSet.List() { 224 attr := attribute.(map[string]interface{}) 225 attributes = append(attributes, &dynamodb.AttributeDefinition{ 226 AttributeName: aws.String(attr["name"].(string)), 227 AttributeType: aws.String(attr["type"].(string)), 228 }) 229 } 230 231 req.AttributeDefinitions = attributes 232 } 233 234 if lsidata, ok := d.GetOk("local_secondary_index"); ok { 235 fmt.Printf("[DEBUG] Adding LSI data to the table") 236 237 lsiSet := lsidata.(*schema.Set) 238 localSecondaryIndexes := []*dynamodb.LocalSecondaryIndex{} 239 for _, lsiObject := range lsiSet.List() { 240 lsi := lsiObject.(map[string]interface{}) 241 242 projection := &dynamodb.Projection{ 243 ProjectionType: aws.String(lsi["projection_type"].(string)), 244 } 245 246 if lsi["projection_type"] == "INCLUDE" { 247 non_key_attributes := []*string{} 248 for _, attr := range lsi["non_key_attributes"].([]interface{}) { 249 non_key_attributes = append(non_key_attributes, aws.String(attr.(string))) 250 } 251 projection.NonKeyAttributes = non_key_attributes 252 } 253 254 localSecondaryIndexes = append(localSecondaryIndexes, &dynamodb.LocalSecondaryIndex{ 255 IndexName: aws.String(lsi["name"].(string)), 256 KeySchema: []*dynamodb.KeySchemaElement{ 257 &dynamodb.KeySchemaElement{ 258 AttributeName: aws.String(hash_key_name), 259 KeyType: aws.String("HASH"), 260 }, 261 &dynamodb.KeySchemaElement{ 262 AttributeName: aws.String(lsi["range_key"].(string)), 263 KeyType: aws.String("RANGE"), 264 }, 265 }, 266 Projection: projection, 267 }) 268 } 269 270 req.LocalSecondaryIndexes = localSecondaryIndexes 271 272 fmt.Printf("[DEBUG] Added %d LSI definitions", len(localSecondaryIndexes)) 273 } 274 275 if gsidata, ok := d.GetOk("global_secondary_index"); ok { 276 globalSecondaryIndexes := []*dynamodb.GlobalSecondaryIndex{} 277 278 gsiSet := gsidata.(*schema.Set) 279 for _, gsiObject := range gsiSet.List() { 280 gsi := gsiObject.(map[string]interface{}) 281 gsiObject := createGSIFromData(&gsi) 282 globalSecondaryIndexes = append(globalSecondaryIndexes, &gsiObject) 283 } 284 req.GlobalSecondaryIndexes = globalSecondaryIndexes 285 } 286 287 if _, ok := d.GetOk("stream_enabled"); ok { 288 289 req.StreamSpecification = &dynamodb.StreamSpecification{ 290 StreamEnabled: aws.Bool(d.Get("stream_enabled").(bool)), 291 StreamViewType: aws.String(d.Get("stream_view_type").(string)), 292 } 293 294 fmt.Printf("[DEBUG] Adding StreamSpecifications to the table") 295 } 296 297 attemptCount := 1 298 for attemptCount <= DYNAMODB_MAX_THROTTLE_RETRIES { 299 output, err := dynamodbconn.CreateTable(req) 300 if err != nil { 301 if awsErr, ok := err.(awserr.Error); ok { 302 if awsErr.Code() == "ThrottlingException" { 303 log.Printf("[DEBUG] Attempt %d/%d: Sleeping for a bit to throttle back create request", attemptCount, DYNAMODB_MAX_THROTTLE_RETRIES) 304 time.Sleep(DYNAMODB_THROTTLE_SLEEP) 305 attemptCount += 1 306 } else if awsErr.Code() == "LimitExceededException" { 307 log.Printf("[DEBUG] Limit on concurrent table creations hit, sleeping for a bit") 308 time.Sleep(DYNAMODB_LIMIT_EXCEEDED_SLEEP) 309 attemptCount += 1 310 } else { 311 // Some other non-retryable exception occurred 312 return fmt.Errorf("AWS Error creating DynamoDB table: %s", err) 313 } 314 } else { 315 // Non-AWS exception occurred, give up 316 return fmt.Errorf("Error creating DynamoDB table: %s", err) 317 } 318 } else { 319 // No error, set ID and return 320 d.SetId(*output.TableDescription.TableName) 321 if err := d.Set("arn", *output.TableDescription.TableArn); err != nil { 322 return err 323 } 324 325 return resourceAwsDynamoDbTableRead(d, meta) 326 } 327 } 328 329 // Too many throttling events occurred, give up 330 return fmt.Errorf("Unable to create DynamoDB table '%s' after %d attempts", name, attemptCount) 331 } 332 333 func resourceAwsDynamoDbTableUpdate(d *schema.ResourceData, meta interface{}) error { 334 335 log.Printf("[DEBUG] Updating DynamoDB table %s", d.Id()) 336 dynamodbconn := meta.(*AWSClient).dynamodbconn 337 338 // Ensure table is active before trying to update 339 waitForTableToBeActive(d.Id(), meta) 340 341 // LSI can only be done at create-time, abort if it's been changed 342 if d.HasChange("local_secondary_index") { 343 return fmt.Errorf("Local secondary indexes can only be built at creation, you cannot update them!") 344 } 345 346 if d.HasChange("hash_key") { 347 return fmt.Errorf("Hash key can only be specified at creation, you cannot modify it.") 348 } 349 350 if d.HasChange("range_key") { 351 return fmt.Errorf("Range key can only be specified at creation, you cannot modify it.") 352 } 353 354 if d.HasChange("read_capacity") || d.HasChange("write_capacity") { 355 req := &dynamodb.UpdateTableInput{ 356 TableName: aws.String(d.Id()), 357 } 358 359 throughput := &dynamodb.ProvisionedThroughput{ 360 ReadCapacityUnits: aws.Int64(int64(d.Get("read_capacity").(int))), 361 WriteCapacityUnits: aws.Int64(int64(d.Get("write_capacity").(int))), 362 } 363 req.ProvisionedThroughput = throughput 364 365 _, err := dynamodbconn.UpdateTable(req) 366 367 if err != nil { 368 return err 369 } 370 371 waitForTableToBeActive(d.Id(), meta) 372 } 373 374 if d.HasChange("stream_enabled") || d.HasChange("stream_view_type") { 375 req := &dynamodb.UpdateTableInput{ 376 TableName: aws.String(d.Id()), 377 } 378 379 req.StreamSpecification = &dynamodb.StreamSpecification{ 380 StreamEnabled: aws.Bool(d.Get("stream_enabled").(bool)), 381 StreamViewType: aws.String(d.Get("stream_view_type").(string)), 382 } 383 384 _, err := dynamodbconn.UpdateTable(req) 385 386 if err != nil { 387 return err 388 } 389 390 waitForTableToBeActive(d.Id(), meta) 391 } 392 393 if d.HasChange("global_secondary_index") { 394 log.Printf("[DEBUG] Changed GSI data") 395 req := &dynamodb.UpdateTableInput{ 396 TableName: aws.String(d.Id()), 397 } 398 399 o, n := d.GetChange("global_secondary_index") 400 401 oldSet := o.(*schema.Set) 402 newSet := n.(*schema.Set) 403 404 // Track old names so we can know which ones we need to just update based on 405 // capacity changes, terraform appears to only diff on the set hash, not the 406 // contents so we need to make sure we don't delete any indexes that we 407 // just want to update the capacity for 408 oldGsiNameSet := make(map[string]bool) 409 newGsiNameSet := make(map[string]bool) 410 411 for _, gsidata := range oldSet.List() { 412 gsiName := gsidata.(map[string]interface{})["name"].(string) 413 oldGsiNameSet[gsiName] = true 414 } 415 416 for _, gsidata := range newSet.List() { 417 gsiName := gsidata.(map[string]interface{})["name"].(string) 418 newGsiNameSet[gsiName] = true 419 } 420 421 // First determine what's new 422 for _, newgsidata := range newSet.List() { 423 updates := []*dynamodb.GlobalSecondaryIndexUpdate{} 424 newGsiName := newgsidata.(map[string]interface{})["name"].(string) 425 if _, exists := oldGsiNameSet[newGsiName]; !exists { 426 attributes := []*dynamodb.AttributeDefinition{} 427 gsidata := newgsidata.(map[string]interface{}) 428 gsi := createGSIFromData(&gsidata) 429 log.Printf("[DEBUG] Adding GSI %s", *gsi.IndexName) 430 update := &dynamodb.GlobalSecondaryIndexUpdate{ 431 Create: &dynamodb.CreateGlobalSecondaryIndexAction{ 432 IndexName: gsi.IndexName, 433 KeySchema: gsi.KeySchema, 434 ProvisionedThroughput: gsi.ProvisionedThroughput, 435 Projection: gsi.Projection, 436 }, 437 } 438 updates = append(updates, update) 439 440 // Hash key is required, range key isn't 441 hashkey_type, err := getAttributeType(d, *gsi.KeySchema[0].AttributeName) 442 if err != nil { 443 return err 444 } 445 446 attributes = append(attributes, &dynamodb.AttributeDefinition{ 447 AttributeName: gsi.KeySchema[0].AttributeName, 448 AttributeType: aws.String(hashkey_type), 449 }) 450 451 // If there's a range key, there will be 2 elements in KeySchema 452 if len(gsi.KeySchema) == 2 { 453 rangekey_type, err := getAttributeType(d, *gsi.KeySchema[1].AttributeName) 454 if err != nil { 455 return err 456 } 457 458 attributes = append(attributes, &dynamodb.AttributeDefinition{ 459 AttributeName: gsi.KeySchema[1].AttributeName, 460 AttributeType: aws.String(rangekey_type), 461 }) 462 } 463 464 req.AttributeDefinitions = attributes 465 req.GlobalSecondaryIndexUpdates = updates 466 _, err = dynamodbconn.UpdateTable(req) 467 468 if err != nil { 469 return err 470 } 471 472 waitForTableToBeActive(d.Id(), meta) 473 waitForGSIToBeActive(d.Id(), *gsi.IndexName, meta) 474 475 } 476 } 477 478 for _, oldgsidata := range oldSet.List() { 479 updates := []*dynamodb.GlobalSecondaryIndexUpdate{} 480 oldGsiName := oldgsidata.(map[string]interface{})["name"].(string) 481 if _, exists := newGsiNameSet[oldGsiName]; !exists { 482 gsidata := oldgsidata.(map[string]interface{}) 483 log.Printf("[DEBUG] Deleting GSI %s", gsidata["name"].(string)) 484 update := &dynamodb.GlobalSecondaryIndexUpdate{ 485 Delete: &dynamodb.DeleteGlobalSecondaryIndexAction{ 486 IndexName: aws.String(gsidata["name"].(string)), 487 }, 488 } 489 updates = append(updates, update) 490 491 req.GlobalSecondaryIndexUpdates = updates 492 _, err := dynamodbconn.UpdateTable(req) 493 494 if err != nil { 495 return err 496 } 497 498 waitForTableToBeActive(d.Id(), meta) 499 } 500 } 501 } 502 503 // Update any out-of-date read / write capacity 504 if gsiObjects, ok := d.GetOk("global_secondary_index"); ok { 505 gsiSet := gsiObjects.(*schema.Set) 506 if len(gsiSet.List()) > 0 { 507 log.Printf("Updating capacity as needed!") 508 509 // We can only change throughput, but we need to make sure it's actually changed 510 tableDescription, err := dynamodbconn.DescribeTable(&dynamodb.DescribeTableInput{ 511 TableName: aws.String(d.Id()), 512 }) 513 514 if err != nil { 515 return err 516 } 517 518 table := tableDescription.Table 519 520 updates := []*dynamodb.GlobalSecondaryIndexUpdate{} 521 522 for _, updatedgsidata := range gsiSet.List() { 523 gsidata := updatedgsidata.(map[string]interface{}) 524 gsiName := gsidata["name"].(string) 525 gsiWriteCapacity := gsidata["write_capacity"].(int) 526 gsiReadCapacity := gsidata["read_capacity"].(int) 527 528 log.Printf("[DEBUG] Updating GSI %s", gsiName) 529 gsi, err := getGlobalSecondaryIndex(gsiName, table.GlobalSecondaryIndexes) 530 531 if err != nil { 532 return err 533 } 534 535 capacityUpdated := false 536 537 if int64(gsiReadCapacity) != *gsi.ProvisionedThroughput.ReadCapacityUnits || 538 int64(gsiWriteCapacity) != *gsi.ProvisionedThroughput.WriteCapacityUnits { 539 capacityUpdated = true 540 } 541 542 if capacityUpdated { 543 update := &dynamodb.GlobalSecondaryIndexUpdate{ 544 Update: &dynamodb.UpdateGlobalSecondaryIndexAction{ 545 IndexName: aws.String(gsidata["name"].(string)), 546 ProvisionedThroughput: &dynamodb.ProvisionedThroughput{ 547 WriteCapacityUnits: aws.Int64(int64(gsiWriteCapacity)), 548 ReadCapacityUnits: aws.Int64(int64(gsiReadCapacity)), 549 }, 550 }, 551 } 552 updates = append(updates, update) 553 554 } 555 556 if len(updates) > 0 { 557 558 req := &dynamodb.UpdateTableInput{ 559 TableName: aws.String(d.Id()), 560 } 561 562 req.GlobalSecondaryIndexUpdates = updates 563 564 log.Printf("[DEBUG] Updating GSI read / write capacity on %s", d.Id()) 565 _, err := dynamodbconn.UpdateTable(req) 566 567 if err != nil { 568 log.Printf("[DEBUG] Error updating table: %s", err) 569 return err 570 } 571 } 572 } 573 } 574 575 } 576 577 return resourceAwsDynamoDbTableRead(d, meta) 578 } 579 580 func resourceAwsDynamoDbTableRead(d *schema.ResourceData, meta interface{}) error { 581 dynamodbconn := meta.(*AWSClient).dynamodbconn 582 log.Printf("[DEBUG] Loading data for DynamoDB table '%s'", d.Id()) 583 req := &dynamodb.DescribeTableInput{ 584 TableName: aws.String(d.Id()), 585 } 586 587 result, err := dynamodbconn.DescribeTable(req) 588 589 if err != nil { 590 if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "ResourceNotFoundException" { 591 log.Printf("[WARN] Dynamodb Table (%s) not found, error code (404)", d.Id()) 592 d.SetId("") 593 return nil 594 } 595 return err 596 } 597 598 table := result.Table 599 600 d.Set("write_capacity", table.ProvisionedThroughput.WriteCapacityUnits) 601 d.Set("read_capacity", table.ProvisionedThroughput.ReadCapacityUnits) 602 603 attributes := []interface{}{} 604 for _, attrdef := range table.AttributeDefinitions { 605 attribute := map[string]string{ 606 "name": *attrdef.AttributeName, 607 "type": *attrdef.AttributeType, 608 } 609 attributes = append(attributes, attribute) 610 log.Printf("[DEBUG] Added Attribute: %s", attribute["name"]) 611 } 612 613 d.Set("attribute", attributes) 614 615 gsiList := make([]map[string]interface{}, 0, len(table.GlobalSecondaryIndexes)) 616 for _, gsiObject := range table.GlobalSecondaryIndexes { 617 gsi := map[string]interface{}{ 618 "write_capacity": *gsiObject.ProvisionedThroughput.WriteCapacityUnits, 619 "read_capacity": *gsiObject.ProvisionedThroughput.ReadCapacityUnits, 620 "name": *gsiObject.IndexName, 621 } 622 623 for _, attribute := range gsiObject.KeySchema { 624 if *attribute.KeyType == "HASH" { 625 gsi["hash_key"] = *attribute.AttributeName 626 } 627 628 if *attribute.KeyType == "RANGE" { 629 gsi["range_key"] = *attribute.AttributeName 630 } 631 } 632 633 gsi["projection_type"] = *(gsiObject.Projection.ProjectionType) 634 635 nonKeyAttrs := make([]string, 0, len(gsiObject.Projection.NonKeyAttributes)) 636 for _, nonKeyAttr := range gsiObject.Projection.NonKeyAttributes { 637 nonKeyAttrs = append(nonKeyAttrs, *nonKeyAttr) 638 } 639 gsi["non_key_attributes"] = nonKeyAttrs 640 641 gsiList = append(gsiList, gsi) 642 log.Printf("[DEBUG] Added GSI: %s - Read: %d / Write: %d", gsi["name"], gsi["read_capacity"], gsi["write_capacity"]) 643 } 644 645 if table.StreamSpecification != nil { 646 d.Set("stream_view_type", table.StreamSpecification.StreamViewType) 647 d.Set("stream_enabled", table.StreamSpecification.StreamEnabled) 648 d.Set("stream_arn", table.LatestStreamArn) 649 } 650 651 err = d.Set("global_secondary_index", gsiList) 652 if err != nil { 653 return err 654 } 655 656 d.Set("arn", table.TableArn) 657 658 return nil 659 } 660 661 func resourceAwsDynamoDbTableDelete(d *schema.ResourceData, meta interface{}) error { 662 dynamodbconn := meta.(*AWSClient).dynamodbconn 663 664 waitForTableToBeActive(d.Id(), meta) 665 666 log.Printf("[DEBUG] DynamoDB delete table: %s", d.Id()) 667 668 _, err := dynamodbconn.DeleteTable(&dynamodb.DeleteTableInput{ 669 TableName: aws.String(d.Id()), 670 }) 671 if err != nil { 672 return err 673 } 674 675 params := &dynamodb.DescribeTableInput{ 676 TableName: aws.String(d.Id()), 677 } 678 679 err = resource.Retry(10*time.Minute, func() *resource.RetryError { 680 t, err := dynamodbconn.DescribeTable(params) 681 if err != nil { 682 if awserr, ok := err.(awserr.Error); ok && awserr.Code() == "ResourceNotFoundException" { 683 return nil 684 } 685 // Didn't recognize the error, so shouldn't retry. 686 return resource.NonRetryableError(err) 687 } 688 689 if t != nil { 690 if t.Table.TableStatus != nil && strings.ToLower(*t.Table.TableStatus) == "deleting" { 691 log.Printf("[DEBUG] AWS Dynamo DB table (%s) is still deleting", d.Id()) 692 return resource.RetryableError(fmt.Errorf("still deleting")) 693 } 694 } 695 696 // we should be not found or deleting, so error here 697 return resource.NonRetryableError(err) 698 }) 699 700 // check error from retry 701 if err != nil { 702 return err 703 } 704 705 return nil 706 } 707 708 func createGSIFromData(data *map[string]interface{}) dynamodb.GlobalSecondaryIndex { 709 710 projection := &dynamodb.Projection{ 711 ProjectionType: aws.String((*data)["projection_type"].(string)), 712 } 713 714 if (*data)["projection_type"] == "INCLUDE" { 715 non_key_attributes := []*string{} 716 for _, attr := range (*data)["non_key_attributes"].([]interface{}) { 717 non_key_attributes = append(non_key_attributes, aws.String(attr.(string))) 718 } 719 projection.NonKeyAttributes = non_key_attributes 720 } 721 722 writeCapacity := (*data)["write_capacity"].(int) 723 readCapacity := (*data)["read_capacity"].(int) 724 725 key_schema := []*dynamodb.KeySchemaElement{ 726 &dynamodb.KeySchemaElement{ 727 AttributeName: aws.String((*data)["hash_key"].(string)), 728 KeyType: aws.String("HASH"), 729 }, 730 } 731 732 range_key_name := (*data)["range_key"] 733 if range_key_name != "" { 734 range_key_element := &dynamodb.KeySchemaElement{ 735 AttributeName: aws.String(range_key_name.(string)), 736 KeyType: aws.String("RANGE"), 737 } 738 739 key_schema = append(key_schema, range_key_element) 740 } 741 742 return dynamodb.GlobalSecondaryIndex{ 743 IndexName: aws.String((*data)["name"].(string)), 744 KeySchema: key_schema, 745 Projection: projection, 746 ProvisionedThroughput: &dynamodb.ProvisionedThroughput{ 747 WriteCapacityUnits: aws.Int64(int64(writeCapacity)), 748 ReadCapacityUnits: aws.Int64(int64(readCapacity)), 749 }, 750 } 751 } 752 753 func getGlobalSecondaryIndex(indexName string, indexList []*dynamodb.GlobalSecondaryIndexDescription) (*dynamodb.GlobalSecondaryIndexDescription, error) { 754 for _, gsi := range indexList { 755 if *gsi.IndexName == indexName { 756 return gsi, nil 757 } 758 } 759 760 return &dynamodb.GlobalSecondaryIndexDescription{}, fmt.Errorf("Can't find a GSI by that name...") 761 } 762 763 func getAttributeType(d *schema.ResourceData, attributeName string) (string, error) { 764 if attributedata, ok := d.GetOk("attribute"); ok { 765 attributeSet := attributedata.(*schema.Set) 766 for _, attribute := range attributeSet.List() { 767 attr := attribute.(map[string]interface{}) 768 if attr["name"] == attributeName { 769 return attr["type"].(string), nil 770 } 771 } 772 } 773 774 return "", fmt.Errorf("Unable to find an attribute named %s", attributeName) 775 } 776 777 func waitForGSIToBeActive(tableName string, gsiName string, meta interface{}) error { 778 dynamodbconn := meta.(*AWSClient).dynamodbconn 779 req := &dynamodb.DescribeTableInput{ 780 TableName: aws.String(tableName), 781 } 782 783 activeIndex := false 784 785 for activeIndex == false { 786 787 result, err := dynamodbconn.DescribeTable(req) 788 789 if err != nil { 790 return err 791 } 792 793 table := result.Table 794 var targetGSI *dynamodb.GlobalSecondaryIndexDescription = nil 795 796 for _, gsi := range table.GlobalSecondaryIndexes { 797 if *gsi.IndexName == gsiName { 798 targetGSI = gsi 799 } 800 } 801 802 if targetGSI != nil { 803 activeIndex = *targetGSI.IndexStatus == "ACTIVE" 804 805 if !activeIndex { 806 log.Printf("[DEBUG] Sleeping for 5 seconds for %s GSI to become active", gsiName) 807 time.Sleep(5 * time.Second) 808 } 809 } else { 810 log.Printf("[DEBUG] GSI %s did not exist, giving up", gsiName) 811 break 812 } 813 } 814 815 return nil 816 817 } 818 819 func waitForTableToBeActive(tableName string, meta interface{}) error { 820 dynamodbconn := meta.(*AWSClient).dynamodbconn 821 req := &dynamodb.DescribeTableInput{ 822 TableName: aws.String(tableName), 823 } 824 825 activeState := false 826 827 for activeState == false { 828 result, err := dynamodbconn.DescribeTable(req) 829 830 if err != nil { 831 return err 832 } 833 834 activeState = *result.Table.TableStatus == "ACTIVE" 835 836 // Wait for a few seconds 837 if !activeState { 838 log.Printf("[DEBUG] Sleeping for 5 seconds for table to become active") 839 time.Sleep(5 * time.Second) 840 } 841 } 842 843 return nil 844 845 }