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