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  }