github.com/anuaimi/terraform@v0.6.4-0.20150904235404-2bf9aec61da8/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  			return nil
   291  		}
   292  	}
   293  
   294  	// Too many throttling events occurred, give up
   295  	return fmt.Errorf("Unable to create DynamoDB table '%s' after %d attempts", name, attemptCount)
   296  }
   297  
   298  func resourceAwsDynamoDbTableUpdate(d *schema.ResourceData, meta interface{}) error {
   299  
   300  	log.Printf("[DEBUG] Updating DynamoDB table %s", d.Id())
   301  	dynamodbconn := meta.(*AWSClient).dynamodbconn
   302  
   303  	// Ensure table is active before trying to update
   304  	waitForTableToBeActive(d.Id(), meta)
   305  
   306  	// LSI can only be done at create-time, abort if it's been changed
   307  	if d.HasChange("local_secondary_index") {
   308  		return fmt.Errorf("Local secondary indexes can only be built at creation, you cannot update them!")
   309  	}
   310  
   311  	if d.HasChange("hash_key") {
   312  		return fmt.Errorf("Hash key can only be specified at creation, you cannot modify it.")
   313  	}
   314  
   315  	if d.HasChange("range_key") {
   316  		return fmt.Errorf("Range key can only be specified at creation, you cannot modify it.")
   317  	}
   318  
   319  	if d.HasChange("read_capacity") || d.HasChange("write_capacity") {
   320  		req := &dynamodb.UpdateTableInput{
   321  			TableName: aws.String(d.Id()),
   322  		}
   323  
   324  		throughput := &dynamodb.ProvisionedThroughput{
   325  			ReadCapacityUnits:  aws.Int64(int64(d.Get("read_capacity").(int))),
   326  			WriteCapacityUnits: aws.Int64(int64(d.Get("write_capacity").(int))),
   327  		}
   328  		req.ProvisionedThroughput = throughput
   329  
   330  		_, err := dynamodbconn.UpdateTable(req)
   331  
   332  		if err != nil {
   333  			return err
   334  		}
   335  
   336  		waitForTableToBeActive(d.Id(), meta)
   337  	}
   338  
   339  	if d.HasChange("global_secondary_index") {
   340  		log.Printf("[DEBUG] Changed GSI data")
   341  		req := &dynamodb.UpdateTableInput{
   342  			TableName: aws.String(d.Id()),
   343  		}
   344  
   345  		o, n := d.GetChange("global_secondary_index")
   346  
   347  		oldSet := o.(*schema.Set)
   348  		newSet := n.(*schema.Set)
   349  
   350  		// Track old names so we can know which ones we need to just update based on
   351  		// capacity changes, terraform appears to only diff on the set hash, not the
   352  		// contents so we need to make sure we don't delete any indexes that we
   353  		// just want to update the capacity for
   354  		oldGsiNameSet := make(map[string]bool)
   355  		newGsiNameSet := make(map[string]bool)
   356  
   357  		for _, gsidata := range oldSet.List() {
   358  			gsiName := gsidata.(map[string]interface{})["name"].(string)
   359  			oldGsiNameSet[gsiName] = true
   360  		}
   361  
   362  		for _, gsidata := range newSet.List() {
   363  			gsiName := gsidata.(map[string]interface{})["name"].(string)
   364  			newGsiNameSet[gsiName] = true
   365  		}
   366  
   367  		// First determine what's new
   368  		for _, newgsidata := range newSet.List() {
   369  			updates := []*dynamodb.GlobalSecondaryIndexUpdate{}
   370  			newGsiName := newgsidata.(map[string]interface{})["name"].(string)
   371  			if _, exists := oldGsiNameSet[newGsiName]; !exists {
   372  				attributes := []*dynamodb.AttributeDefinition{}
   373  				gsidata := newgsidata.(map[string]interface{})
   374  				gsi := createGSIFromData(&gsidata)
   375  				log.Printf("[DEBUG] Adding GSI %s", *gsi.IndexName)
   376  				update := &dynamodb.GlobalSecondaryIndexUpdate{
   377  					Create: &dynamodb.CreateGlobalSecondaryIndexAction{
   378  						IndexName:             gsi.IndexName,
   379  						KeySchema:             gsi.KeySchema,
   380  						ProvisionedThroughput: gsi.ProvisionedThroughput,
   381  						Projection:            gsi.Projection,
   382  					},
   383  				}
   384  				updates = append(updates, update)
   385  
   386  				// Hash key is required, range key isn't
   387  				hashkey_type, err := getAttributeType(d, *(gsi.KeySchema[0].AttributeName))
   388  				if err != nil {
   389  					return err
   390  				}
   391  
   392  				attributes = append(attributes, &dynamodb.AttributeDefinition{
   393  					AttributeName: gsi.KeySchema[0].AttributeName,
   394  					AttributeType: aws.String(hashkey_type),
   395  				})
   396  
   397  				// If there's a range key, there will be 2 elements in KeySchema
   398  				if len(gsi.KeySchema) == 2 {
   399  					rangekey_type, err := getAttributeType(d, *(gsi.KeySchema[1].AttributeName))
   400  					if err != nil {
   401  						return err
   402  					}
   403  
   404  					attributes = append(attributes, &dynamodb.AttributeDefinition{
   405  						AttributeName: gsi.KeySchema[1].AttributeName,
   406  						AttributeType: aws.String(rangekey_type),
   407  					})
   408  				}
   409  
   410  				req.AttributeDefinitions = attributes
   411  				req.GlobalSecondaryIndexUpdates = updates
   412  				_, err = dynamodbconn.UpdateTable(req)
   413  
   414  				if err != nil {
   415  					return err
   416  				}
   417  
   418  				waitForTableToBeActive(d.Id(), meta)
   419  				waitForGSIToBeActive(d.Id(), *gsi.IndexName, meta)
   420  
   421  			}
   422  		}
   423  
   424  		for _, oldgsidata := range oldSet.List() {
   425  			updates := []*dynamodb.GlobalSecondaryIndexUpdate{}
   426  			oldGsiName := oldgsidata.(map[string]interface{})["name"].(string)
   427  			if _, exists := newGsiNameSet[oldGsiName]; !exists {
   428  				gsidata := oldgsidata.(map[string]interface{})
   429  				log.Printf("[DEBUG] Deleting GSI %s", gsidata["name"].(string))
   430  				update := &dynamodb.GlobalSecondaryIndexUpdate{
   431  					Delete: &dynamodb.DeleteGlobalSecondaryIndexAction{
   432  						IndexName: aws.String(gsidata["name"].(string)),
   433  					},
   434  				}
   435  				updates = append(updates, update)
   436  
   437  				req.GlobalSecondaryIndexUpdates = updates
   438  				_, err := dynamodbconn.UpdateTable(req)
   439  
   440  				if err != nil {
   441  					return err
   442  				}
   443  
   444  				waitForTableToBeActive(d.Id(), meta)
   445  			}
   446  		}
   447  	}
   448  
   449  	// Update any out-of-date read / write capacity
   450  	if gsiObjects, ok := d.GetOk("global_secondary_index"); ok {
   451  		gsiSet := gsiObjects.(*schema.Set)
   452  		if len(gsiSet.List()) > 0 {
   453  			log.Printf("Updating capacity as needed!")
   454  
   455  			// We can only change throughput, but we need to make sure it's actually changed
   456  			tableDescription, err := dynamodbconn.DescribeTable(&dynamodb.DescribeTableInput{
   457  				TableName: aws.String(d.Id()),
   458  			})
   459  
   460  			if err != nil {
   461  				return err
   462  			}
   463  
   464  			table := tableDescription.Table
   465  
   466  			updates := []*dynamodb.GlobalSecondaryIndexUpdate{}
   467  
   468  			for _, updatedgsidata := range gsiSet.List() {
   469  				gsidata := updatedgsidata.(map[string]interface{})
   470  				gsiName := gsidata["name"].(string)
   471  				gsiWriteCapacity := gsidata["write_capacity"].(int)
   472  				gsiReadCapacity := gsidata["read_capacity"].(int)
   473  
   474  				log.Printf("[DEBUG] Updating GSI %s", gsiName)
   475  				gsi, err := getGlobalSecondaryIndex(gsiName, table.GlobalSecondaryIndexes)
   476  
   477  				if err != nil {
   478  					return err
   479  				}
   480  
   481  				capacityUpdated := false
   482  
   483  				if int64(gsiReadCapacity) != *(gsi.ProvisionedThroughput.ReadCapacityUnits) ||
   484  					int64(gsiWriteCapacity) != *(gsi.ProvisionedThroughput.WriteCapacityUnits) {
   485  					capacityUpdated = true
   486  				}
   487  
   488  				if capacityUpdated {
   489  					update := &dynamodb.GlobalSecondaryIndexUpdate{
   490  						Update: &dynamodb.UpdateGlobalSecondaryIndexAction{
   491  							IndexName: aws.String(gsidata["name"].(string)),
   492  							ProvisionedThroughput: &dynamodb.ProvisionedThroughput{
   493  								WriteCapacityUnits: aws.Int64(int64(gsiWriteCapacity)),
   494  								ReadCapacityUnits:  aws.Int64(int64(gsiReadCapacity)),
   495  							},
   496  						},
   497  					}
   498  					updates = append(updates, update)
   499  
   500  				}
   501  
   502  				if len(updates) > 0 {
   503  
   504  					req := &dynamodb.UpdateTableInput{
   505  						TableName: aws.String(d.Id()),
   506  					}
   507  
   508  					req.GlobalSecondaryIndexUpdates = updates
   509  
   510  					log.Printf("[DEBUG] Updating GSI read / write capacity on %s", d.Id())
   511  					_, err := dynamodbconn.UpdateTable(req)
   512  
   513  					if err != nil {
   514  						log.Printf("[DEBUG] Error updating table: %s", err)
   515  						return err
   516  					}
   517  				}
   518  			}
   519  		}
   520  
   521  	}
   522  
   523  	return resourceAwsDynamoDbTableRead(d, meta)
   524  }
   525  
   526  func resourceAwsDynamoDbTableRead(d *schema.ResourceData, meta interface{}) error {
   527  	dynamodbconn := meta.(*AWSClient).dynamodbconn
   528  	log.Printf("[DEBUG] Loading data for DynamoDB table '%s'", d.Id())
   529  	req := &dynamodb.DescribeTableInput{
   530  		TableName: aws.String(d.Id()),
   531  	}
   532  
   533  	result, err := dynamodbconn.DescribeTable(req)
   534  
   535  	if err != nil {
   536  		return err
   537  	}
   538  
   539  	table := result.Table
   540  
   541  	d.Set("write_capacity", table.ProvisionedThroughput.WriteCapacityUnits)
   542  	d.Set("read_capacity", table.ProvisionedThroughput.ReadCapacityUnits)
   543  
   544  	attributes := []interface{}{}
   545  	for _, attrdef := range table.AttributeDefinitions {
   546  		attribute := map[string]string{
   547  			"name": *(attrdef.AttributeName),
   548  			"type": *(attrdef.AttributeType),
   549  		}
   550  		attributes = append(attributes, attribute)
   551  		log.Printf("[DEBUG] Added Attribute: %s", attribute["name"])
   552  	}
   553  
   554  	d.Set("attribute", attributes)
   555  
   556  	gsiList := make([]map[string]interface{}, 0, len(table.GlobalSecondaryIndexes))
   557  	for _, gsiObject := range table.GlobalSecondaryIndexes {
   558  		gsi := map[string]interface{}{
   559  			"write_capacity": *(gsiObject.ProvisionedThroughput.WriteCapacityUnits),
   560  			"read_capacity":  *(gsiObject.ProvisionedThroughput.ReadCapacityUnits),
   561  			"name":           *(gsiObject.IndexName),
   562  		}
   563  
   564  		for _, attribute := range gsiObject.KeySchema {
   565  			if *attribute.KeyType == "HASH" {
   566  				gsi["hash_key"] = *attribute.AttributeName
   567  			}
   568  
   569  			if *attribute.KeyType == "RANGE" {
   570  				gsi["range_key"] = *attribute.AttributeName
   571  			}
   572  		}
   573  
   574  		gsi["projection_type"] = *(gsiObject.Projection.ProjectionType)
   575  		gsi["non_key_attributes"] = gsiObject.Projection.NonKeyAttributes
   576  
   577  		gsiList = append(gsiList, gsi)
   578  		log.Printf("[DEBUG] Added GSI: %s - Read: %d / Write: %d", gsi["name"], gsi["read_capacity"], gsi["write_capacity"])
   579  	}
   580  
   581  	d.Set("global_secondary_index", gsiList)
   582  	d.Set("arn", table.TableArn)
   583  
   584  	return nil
   585  }
   586  
   587  func resourceAwsDynamoDbTableDelete(d *schema.ResourceData, meta interface{}) error {
   588  	dynamodbconn := meta.(*AWSClient).dynamodbconn
   589  
   590  	waitForTableToBeActive(d.Id(), meta)
   591  
   592  	log.Printf("[DEBUG] DynamoDB delete table: %s", d.Id())
   593  
   594  	_, err := dynamodbconn.DeleteTable(&dynamodb.DeleteTableInput{
   595  		TableName: aws.String(d.Id()),
   596  	})
   597  	if err != nil {
   598  		return err
   599  	}
   600  	return nil
   601  }
   602  
   603  func createGSIFromData(data *map[string]interface{}) dynamodb.GlobalSecondaryIndex {
   604  
   605  	projection := &dynamodb.Projection{
   606  		ProjectionType: aws.String((*data)["projection_type"].(string)),
   607  	}
   608  
   609  	if (*data)["projection_type"] == "INCLUDE" {
   610  		non_key_attributes := []*string{}
   611  		for _, attr := range (*data)["non_key_attributes"].([]interface{}) {
   612  			non_key_attributes = append(non_key_attributes, aws.String(attr.(string)))
   613  		}
   614  		projection.NonKeyAttributes = non_key_attributes
   615  	}
   616  
   617  	writeCapacity := (*data)["write_capacity"].(int)
   618  	readCapacity := (*data)["read_capacity"].(int)
   619  
   620  	key_schema := []*dynamodb.KeySchemaElement{
   621  		&dynamodb.KeySchemaElement{
   622  			AttributeName: aws.String((*data)["hash_key"].(string)),
   623  			KeyType:       aws.String("HASH"),
   624  		},
   625  	}
   626  
   627  	range_key_name := (*data)["range_key"]
   628  	if range_key_name != "" {
   629  		range_key_element := &dynamodb.KeySchemaElement{
   630  			AttributeName: aws.String(range_key_name.(string)),
   631  			KeyType:       aws.String("RANGE"),
   632  		}
   633  
   634  		key_schema = append(key_schema, range_key_element)
   635  	}
   636  
   637  	return dynamodb.GlobalSecondaryIndex{
   638  		IndexName:  aws.String((*data)["name"].(string)),
   639  		KeySchema:  key_schema,
   640  		Projection: projection,
   641  		ProvisionedThroughput: &dynamodb.ProvisionedThroughput{
   642  			WriteCapacityUnits: aws.Int64(int64(writeCapacity)),
   643  			ReadCapacityUnits:  aws.Int64(int64(readCapacity)),
   644  		},
   645  	}
   646  }
   647  
   648  func getGlobalSecondaryIndex(indexName string, indexList []*dynamodb.GlobalSecondaryIndexDescription) (*dynamodb.GlobalSecondaryIndexDescription, error) {
   649  	for _, gsi := range indexList {
   650  		if *(gsi.IndexName) == indexName {
   651  			return gsi, nil
   652  		}
   653  	}
   654  
   655  	return &dynamodb.GlobalSecondaryIndexDescription{}, fmt.Errorf("Can't find a GSI by that name...")
   656  }
   657  
   658  func getAttributeType(d *schema.ResourceData, attributeName string) (string, error) {
   659  	if attributedata, ok := d.GetOk("attribute"); ok {
   660  		attributeSet := attributedata.(*schema.Set)
   661  		for _, attribute := range attributeSet.List() {
   662  			attr := attribute.(map[string]interface{})
   663  			if attr["name"] == attributeName {
   664  				return attr["type"].(string), nil
   665  			}
   666  		}
   667  	}
   668  
   669  	return "", fmt.Errorf("Unable to find an attribute named %s", attributeName)
   670  }
   671  
   672  func waitForGSIToBeActive(tableName string, gsiName string, meta interface{}) error {
   673  	dynamodbconn := meta.(*AWSClient).dynamodbconn
   674  	req := &dynamodb.DescribeTableInput{
   675  		TableName: aws.String(tableName),
   676  	}
   677  
   678  	activeIndex := false
   679  
   680  	for activeIndex == false {
   681  
   682  		result, err := dynamodbconn.DescribeTable(req)
   683  
   684  		if err != nil {
   685  			return err
   686  		}
   687  
   688  		table := result.Table
   689  		var targetGSI *dynamodb.GlobalSecondaryIndexDescription = nil
   690  
   691  		for _, gsi := range table.GlobalSecondaryIndexes {
   692  			if *gsi.IndexName == gsiName {
   693  				targetGSI = gsi
   694  			}
   695  		}
   696  
   697  		if targetGSI != nil {
   698  			activeIndex = *targetGSI.IndexStatus == "ACTIVE"
   699  
   700  			if !activeIndex {
   701  				log.Printf("[DEBUG] Sleeping for 5 seconds for %s GSI to become active", gsiName)
   702  				time.Sleep(5 * time.Second)
   703  			}
   704  		} else {
   705  			log.Printf("[DEBUG] GSI %s did not exist, giving up", gsiName)
   706  			break
   707  		}
   708  	}
   709  
   710  	return nil
   711  
   712  }
   713  
   714  func waitForTableToBeActive(tableName string, meta interface{}) error {
   715  	dynamodbconn := meta.(*AWSClient).dynamodbconn
   716  	req := &dynamodb.DescribeTableInput{
   717  		TableName: aws.String(tableName),
   718  	}
   719  
   720  	activeState := false
   721  
   722  	for activeState == false {
   723  		result, err := dynamodbconn.DescribeTable(req)
   724  
   725  		if err != nil {
   726  			return err
   727  		}
   728  
   729  		activeState = *(result.Table.TableStatus) == "ACTIVE"
   730  
   731  		// Wait for a few seconds
   732  		if !activeState {
   733  			log.Printf("[DEBUG] Sleeping for 5 seconds for table to become active")
   734  			time.Sleep(5 * time.Second)
   735  		}
   736  	}
   737  
   738  	return nil
   739  
   740  }