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