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