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