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