github.com/gabrielperezs/terraform@v0.7.0-rc2.0.20160715084931-f7da2612946f/builtin/providers/aws/resource_aws_dynamodb_table.go (about)

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