github.com/bradfeehan/terraform@v0.7.0-rc3.0.20170529055808-34b45c5ad841/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/errwrap"
    11  	"github.com/hashicorp/terraform/helper/resource"
    12  	"github.com/hashicorp/terraform/helper/schema"
    13  
    14  	"github.com/aws/aws-sdk-go/aws"
    15  	"github.com/aws/aws-sdk-go/aws/awserr"
    16  	"github.com/aws/aws-sdk-go/service/dynamodb"
    17  	"github.com/hashicorp/terraform/helper/hashcode"
    18  )
    19  
    20  // Number of times to retry if a throttling-related exception occurs
    21  const DYNAMODB_MAX_THROTTLE_RETRIES = 5
    22  
    23  // How long to sleep when a throttle-event happens
    24  const DYNAMODB_THROTTLE_SLEEP = 5 * time.Second
    25  
    26  // How long to sleep if a limit-exceeded event happens
    27  const DYNAMODB_LIMIT_EXCEEDED_SLEEP = 10 * time.Second
    28  
    29  // A number of these are marked as computed because if you don't
    30  // provide a value, DynamoDB will provide you with defaults (which are the
    31  // default values specified below)
    32  func resourceAwsDynamoDbTable() *schema.Resource {
    33  	return &schema.Resource{
    34  		Create: resourceAwsDynamoDbTableCreate,
    35  		Read:   resourceAwsDynamoDbTableRead,
    36  		Update: resourceAwsDynamoDbTableUpdate,
    37  		Delete: resourceAwsDynamoDbTableDelete,
    38  		Importer: &schema.ResourceImporter{
    39  			State: schema.ImportStatePassthrough,
    40  		},
    41  
    42  		SchemaVersion: 1,
    43  		MigrateState:  resourceAwsDynamoDbTableMigrateState,
    44  
    45  		Schema: map[string]*schema.Schema{
    46  			"arn": {
    47  				Type:     schema.TypeString,
    48  				Computed: true,
    49  			},
    50  			"name": {
    51  				Type:     schema.TypeString,
    52  				Required: true,
    53  				ForceNew: true,
    54  			},
    55  			"hash_key": {
    56  				Type:     schema.TypeString,
    57  				Required: true,
    58  				ForceNew: true,
    59  			},
    60  			"range_key": {
    61  				Type:     schema.TypeString,
    62  				Optional: true,
    63  				ForceNew: true,
    64  			},
    65  			"write_capacity": {
    66  				Type:     schema.TypeInt,
    67  				Required: true,
    68  			},
    69  			"read_capacity": {
    70  				Type:     schema.TypeInt,
    71  				Required: true,
    72  			},
    73  			"attribute": {
    74  				Type:     schema.TypeSet,
    75  				Required: true,
    76  				Elem: &schema.Resource{
    77  					Schema: map[string]*schema.Schema{
    78  						"name": {
    79  							Type:     schema.TypeString,
    80  							Required: true,
    81  						},
    82  						"type": {
    83  							Type:     schema.TypeString,
    84  							Required: true,
    85  						},
    86  					},
    87  				},
    88  				Set: func(v interface{}) int {
    89  					var buf bytes.Buffer
    90  					m := v.(map[string]interface{})
    91  					buf.WriteString(fmt.Sprintf("%s-", m["name"].(string)))
    92  					return hashcode.String(buf.String())
    93  				},
    94  			},
    95  			"ttl": {
    96  				Type:     schema.TypeSet,
    97  				Optional: true,
    98  				MaxItems: 1,
    99  				Elem: &schema.Resource{
   100  					Schema: map[string]*schema.Schema{
   101  						"attribute_name": {
   102  							Type:     schema.TypeString,
   103  							Required: true,
   104  						},
   105  						"enabled": {
   106  							Type:     schema.TypeBool,
   107  							Required: true,
   108  						},
   109  					},
   110  				},
   111  			},
   112  			"local_secondary_index": {
   113  				Type:     schema.TypeSet,
   114  				Optional: true,
   115  				ForceNew: true,
   116  				Elem: &schema.Resource{
   117  					Schema: map[string]*schema.Schema{
   118  						"name": {
   119  							Type:     schema.TypeString,
   120  							Required: true,
   121  						},
   122  						"range_key": {
   123  							Type:     schema.TypeString,
   124  							Required: true,
   125  						},
   126  						"projection_type": {
   127  							Type:     schema.TypeString,
   128  							Required: true,
   129  						},
   130  						"non_key_attributes": {
   131  							Type:     schema.TypeList,
   132  							Optional: true,
   133  							Elem:     &schema.Schema{Type: schema.TypeString},
   134  						},
   135  					},
   136  				},
   137  				Set: func(v interface{}) int {
   138  					var buf bytes.Buffer
   139  					m := v.(map[string]interface{})
   140  					buf.WriteString(fmt.Sprintf("%s-", m["name"].(string)))
   141  					return hashcode.String(buf.String())
   142  				},
   143  			},
   144  			"global_secondary_index": {
   145  				Type:     schema.TypeSet,
   146  				Optional: true,
   147  				Elem: &schema.Resource{
   148  					Schema: map[string]*schema.Schema{
   149  						"name": {
   150  							Type:     schema.TypeString,
   151  							Required: true,
   152  						},
   153  						"write_capacity": {
   154  							Type:     schema.TypeInt,
   155  							Required: true,
   156  						},
   157  						"read_capacity": {
   158  							Type:     schema.TypeInt,
   159  							Required: true,
   160  						},
   161  						"hash_key": {
   162  							Type:     schema.TypeString,
   163  							Required: true,
   164  						},
   165  						"range_key": {
   166  							Type:     schema.TypeString,
   167  							Optional: true,
   168  						},
   169  						"projection_type": {
   170  							Type:     schema.TypeString,
   171  							Required: true,
   172  						},
   173  						"non_key_attributes": {
   174  							Type:     schema.TypeList,
   175  							Optional: true,
   176  							Elem:     &schema.Schema{Type: schema.TypeString},
   177  						},
   178  					},
   179  				},
   180  			},
   181  			"stream_enabled": {
   182  				Type:     schema.TypeBool,
   183  				Optional: true,
   184  				Computed: true,
   185  			},
   186  			"stream_view_type": {
   187  				Type:     schema.TypeString,
   188  				Optional: true,
   189  				Computed: true,
   190  				StateFunc: func(v interface{}) string {
   191  					value := v.(string)
   192  					return strings.ToUpper(value)
   193  				},
   194  				ValidateFunc: validateStreamViewType,
   195  			},
   196  			"stream_arn": {
   197  				Type:     schema.TypeString,
   198  				Computed: true,
   199  			},
   200  			"tags": tagsSchema(),
   201  		},
   202  	}
   203  }
   204  
   205  func resourceAwsDynamoDbTableCreate(d *schema.ResourceData, meta interface{}) error {
   206  	dynamodbconn := meta.(*AWSClient).dynamodbconn
   207  
   208  	name := d.Get("name").(string)
   209  
   210  	log.Printf("[DEBUG] DynamoDB table create: %s", name)
   211  
   212  	throughput := &dynamodb.ProvisionedThroughput{
   213  		ReadCapacityUnits:  aws.Int64(int64(d.Get("read_capacity").(int))),
   214  		WriteCapacityUnits: aws.Int64(int64(d.Get("write_capacity").(int))),
   215  	}
   216  
   217  	hash_key_name := d.Get("hash_key").(string)
   218  	keyschema := []*dynamodb.KeySchemaElement{
   219  		{
   220  			AttributeName: aws.String(hash_key_name),
   221  			KeyType:       aws.String("HASH"),
   222  		},
   223  	}
   224  
   225  	if range_key, ok := d.GetOk("range_key"); ok {
   226  		range_schema_element := &dynamodb.KeySchemaElement{
   227  			AttributeName: aws.String(range_key.(string)),
   228  			KeyType:       aws.String("RANGE"),
   229  		}
   230  		keyschema = append(keyschema, range_schema_element)
   231  	}
   232  
   233  	req := &dynamodb.CreateTableInput{
   234  		TableName:             aws.String(name),
   235  		ProvisionedThroughput: throughput,
   236  		KeySchema:             keyschema,
   237  	}
   238  
   239  	if attributedata, ok := d.GetOk("attribute"); ok {
   240  		attributes := []*dynamodb.AttributeDefinition{}
   241  		attributeSet := attributedata.(*schema.Set)
   242  		for _, attribute := range attributeSet.List() {
   243  			attr := attribute.(map[string]interface{})
   244  			attributes = append(attributes, &dynamodb.AttributeDefinition{
   245  				AttributeName: aws.String(attr["name"].(string)),
   246  				AttributeType: aws.String(attr["type"].(string)),
   247  			})
   248  		}
   249  
   250  		req.AttributeDefinitions = attributes
   251  	}
   252  
   253  	if lsidata, ok := d.GetOk("local_secondary_index"); ok {
   254  		log.Printf("[DEBUG] Adding LSI data to the table")
   255  
   256  		lsiSet := lsidata.(*schema.Set)
   257  		localSecondaryIndexes := []*dynamodb.LocalSecondaryIndex{}
   258  		for _, lsiObject := range lsiSet.List() {
   259  			lsi := lsiObject.(map[string]interface{})
   260  
   261  			projection := &dynamodb.Projection{
   262  				ProjectionType: aws.String(lsi["projection_type"].(string)),
   263  			}
   264  
   265  			if lsi["projection_type"] == "INCLUDE" {
   266  				non_key_attributes := []*string{}
   267  				for _, attr := range lsi["non_key_attributes"].([]interface{}) {
   268  					non_key_attributes = append(non_key_attributes, aws.String(attr.(string)))
   269  				}
   270  				projection.NonKeyAttributes = non_key_attributes
   271  			}
   272  
   273  			localSecondaryIndexes = append(localSecondaryIndexes, &dynamodb.LocalSecondaryIndex{
   274  				IndexName: aws.String(lsi["name"].(string)),
   275  				KeySchema: []*dynamodb.KeySchemaElement{
   276  					{
   277  						AttributeName: aws.String(hash_key_name),
   278  						KeyType:       aws.String("HASH"),
   279  					},
   280  					{
   281  						AttributeName: aws.String(lsi["range_key"].(string)),
   282  						KeyType:       aws.String("RANGE"),
   283  					},
   284  				},
   285  				Projection: projection,
   286  			})
   287  		}
   288  
   289  		req.LocalSecondaryIndexes = localSecondaryIndexes
   290  
   291  		log.Printf("[DEBUG] Added %d LSI definitions", len(localSecondaryIndexes))
   292  	}
   293  
   294  	if gsidata, ok := d.GetOk("global_secondary_index"); ok {
   295  		globalSecondaryIndexes := []*dynamodb.GlobalSecondaryIndex{}
   296  
   297  		gsiSet := gsidata.(*schema.Set)
   298  		for _, gsiObject := range gsiSet.List() {
   299  			gsi := gsiObject.(map[string]interface{})
   300  			gsiObject := createGSIFromData(&gsi)
   301  			globalSecondaryIndexes = append(globalSecondaryIndexes, &gsiObject)
   302  		}
   303  		req.GlobalSecondaryIndexes = globalSecondaryIndexes
   304  	}
   305  
   306  	if _, ok := d.GetOk("stream_enabled"); ok {
   307  
   308  		req.StreamSpecification = &dynamodb.StreamSpecification{
   309  			StreamEnabled:  aws.Bool(d.Get("stream_enabled").(bool)),
   310  			StreamViewType: aws.String(d.Get("stream_view_type").(string)),
   311  		}
   312  
   313  		log.Printf("[DEBUG] Adding StreamSpecifications to the table")
   314  	}
   315  
   316  	_, timeToLiveOk := d.GetOk("ttl")
   317  	_, tagsOk := d.GetOk("tags")
   318  
   319  	attemptCount := 1
   320  	for attemptCount <= DYNAMODB_MAX_THROTTLE_RETRIES {
   321  		output, err := dynamodbconn.CreateTable(req)
   322  		if err != nil {
   323  			if awsErr, ok := err.(awserr.Error); ok {
   324  				switch code := awsErr.Code(); code {
   325  				case "ThrottlingException":
   326  					log.Printf("[DEBUG] Attempt %d/%d: Sleeping for a bit to throttle back create request", attemptCount, DYNAMODB_MAX_THROTTLE_RETRIES)
   327  					time.Sleep(DYNAMODB_THROTTLE_SLEEP)
   328  					attemptCount += 1
   329  				case "LimitExceededException":
   330  					// If we're at resource capacity, error out without retry
   331  					if strings.Contains(awsErr.Message(), "Subscriber limit exceeded:") {
   332  						return fmt.Errorf("AWS Error creating DynamoDB table: %s", err)
   333  					}
   334  					log.Printf("[DEBUG] Limit on concurrent table creations hit, sleeping for a bit")
   335  					time.Sleep(DYNAMODB_LIMIT_EXCEEDED_SLEEP)
   336  					attemptCount += 1
   337  				default:
   338  					// Some other non-retryable exception occurred
   339  					return fmt.Errorf("AWS Error creating DynamoDB table: %s", err)
   340  				}
   341  			} else {
   342  				// Non-AWS exception occurred, give up
   343  				return fmt.Errorf("Error creating DynamoDB table: %s", err)
   344  			}
   345  		} else {
   346  			// No error, set ID and return
   347  			d.SetId(*output.TableDescription.TableName)
   348  			tableArn := *output.TableDescription.TableArn
   349  			if err := d.Set("arn", tableArn); err != nil {
   350  				return err
   351  			}
   352  
   353  			// Wait, till table is active before imitating any TimeToLive changes
   354  			if err := waitForTableToBeActive(d.Id(), meta); err != nil {
   355  				log.Printf("[DEBUG] Error waiting for table to be active: %s", err)
   356  				return err
   357  			}
   358  
   359  			log.Printf("[DEBUG] Setting DynamoDB TimeToLive on arn: %s", tableArn)
   360  			if timeToLiveOk {
   361  				if err := updateTimeToLive(d, meta); err != nil {
   362  					log.Printf("[DEBUG] Error updating table TimeToLive: %s", err)
   363  					return err
   364  				}
   365  			}
   366  
   367  			if tagsOk {
   368  				log.Printf("[DEBUG] Setting DynamoDB Tags on arn: %s", tableArn)
   369  				if err := createTableTags(d, meta); err != nil {
   370  					return err
   371  				}
   372  			}
   373  
   374  			return resourceAwsDynamoDbTableRead(d, meta)
   375  		}
   376  	}
   377  
   378  	// Too many throttling events occurred, give up
   379  	return fmt.Errorf("Unable to create DynamoDB table '%s' after %d attempts", name, attemptCount)
   380  }
   381  
   382  func resourceAwsDynamoDbTableUpdate(d *schema.ResourceData, meta interface{}) error {
   383  
   384  	log.Printf("[DEBUG] Updating DynamoDB table %s", d.Id())
   385  	dynamodbconn := meta.(*AWSClient).dynamodbconn
   386  
   387  	// Ensure table is active before trying to update
   388  	if err := waitForTableToBeActive(d.Id(), meta); err != nil {
   389  		return errwrap.Wrapf("Error waiting for Dynamo DB Table update: {{err}}", err)
   390  	}
   391  
   392  	if d.HasChange("read_capacity") || d.HasChange("write_capacity") {
   393  		req := &dynamodb.UpdateTableInput{
   394  			TableName: aws.String(d.Id()),
   395  		}
   396  
   397  		throughput := &dynamodb.ProvisionedThroughput{
   398  			ReadCapacityUnits:  aws.Int64(int64(d.Get("read_capacity").(int))),
   399  			WriteCapacityUnits: aws.Int64(int64(d.Get("write_capacity").(int))),
   400  		}
   401  		req.ProvisionedThroughput = throughput
   402  
   403  		_, err := dynamodbconn.UpdateTable(req)
   404  
   405  		if err != nil {
   406  			return err
   407  		}
   408  
   409  		if err := waitForTableToBeActive(d.Id(), meta); err != nil {
   410  			return errwrap.Wrapf("Error waiting for Dynamo DB Table update: {{err}}", err)
   411  		}
   412  	}
   413  
   414  	if d.HasChange("stream_enabled") || d.HasChange("stream_view_type") {
   415  		req := &dynamodb.UpdateTableInput{
   416  			TableName: aws.String(d.Id()),
   417  		}
   418  
   419  		req.StreamSpecification = &dynamodb.StreamSpecification{
   420  			StreamEnabled:  aws.Bool(d.Get("stream_enabled").(bool)),
   421  			StreamViewType: aws.String(d.Get("stream_view_type").(string)),
   422  		}
   423  
   424  		_, err := dynamodbconn.UpdateTable(req)
   425  
   426  		if err != nil {
   427  			return err
   428  		}
   429  
   430  		if err := waitForTableToBeActive(d.Id(), meta); err != nil {
   431  			return errwrap.Wrapf("Error waiting for Dynamo DB Table update: {{err}}", err)
   432  		}
   433  	}
   434  
   435  	if d.HasChange("global_secondary_index") {
   436  		log.Printf("[DEBUG] Changed GSI data")
   437  		req := &dynamodb.UpdateTableInput{
   438  			TableName: aws.String(d.Id()),
   439  		}
   440  
   441  		o, n := d.GetChange("global_secondary_index")
   442  
   443  		oldSet := o.(*schema.Set)
   444  		newSet := n.(*schema.Set)
   445  
   446  		// Track old names so we can know which ones we need to just update based on
   447  		// capacity changes, terraform appears to only diff on the set hash, not the
   448  		// contents so we need to make sure we don't delete any indexes that we
   449  		// just want to update the capacity for
   450  		oldGsiNameSet := make(map[string]bool)
   451  		newGsiNameSet := make(map[string]bool)
   452  
   453  		for _, gsidata := range oldSet.List() {
   454  			gsiName := gsidata.(map[string]interface{})["name"].(string)
   455  			oldGsiNameSet[gsiName] = true
   456  		}
   457  
   458  		for _, gsidata := range newSet.List() {
   459  			gsiName := gsidata.(map[string]interface{})["name"].(string)
   460  			newGsiNameSet[gsiName] = true
   461  		}
   462  
   463  		// First determine what's new
   464  		for _, newgsidata := range newSet.List() {
   465  			updates := []*dynamodb.GlobalSecondaryIndexUpdate{}
   466  			newGsiName := newgsidata.(map[string]interface{})["name"].(string)
   467  			if _, exists := oldGsiNameSet[newGsiName]; !exists {
   468  				attributes := []*dynamodb.AttributeDefinition{}
   469  				gsidata := newgsidata.(map[string]interface{})
   470  				gsi := createGSIFromData(&gsidata)
   471  				log.Printf("[DEBUG] Adding GSI %s", *gsi.IndexName)
   472  				update := &dynamodb.GlobalSecondaryIndexUpdate{
   473  					Create: &dynamodb.CreateGlobalSecondaryIndexAction{
   474  						IndexName:             gsi.IndexName,
   475  						KeySchema:             gsi.KeySchema,
   476  						ProvisionedThroughput: gsi.ProvisionedThroughput,
   477  						Projection:            gsi.Projection,
   478  					},
   479  				}
   480  				updates = append(updates, update)
   481  
   482  				// Hash key is required, range key isn't
   483  				hashkey_type, err := getAttributeType(d, *gsi.KeySchema[0].AttributeName)
   484  				if err != nil {
   485  					return err
   486  				}
   487  
   488  				attributes = append(attributes, &dynamodb.AttributeDefinition{
   489  					AttributeName: gsi.KeySchema[0].AttributeName,
   490  					AttributeType: aws.String(hashkey_type),
   491  				})
   492  
   493  				// If there's a range key, there will be 2 elements in KeySchema
   494  				if len(gsi.KeySchema) == 2 {
   495  					rangekey_type, err := getAttributeType(d, *gsi.KeySchema[1].AttributeName)
   496  					if err != nil {
   497  						return err
   498  					}
   499  
   500  					attributes = append(attributes, &dynamodb.AttributeDefinition{
   501  						AttributeName: gsi.KeySchema[1].AttributeName,
   502  						AttributeType: aws.String(rangekey_type),
   503  					})
   504  				}
   505  
   506  				req.AttributeDefinitions = attributes
   507  				req.GlobalSecondaryIndexUpdates = updates
   508  				_, err = dynamodbconn.UpdateTable(req)
   509  
   510  				if err != nil {
   511  					return err
   512  				}
   513  
   514  				if err := waitForTableToBeActive(d.Id(), meta); err != nil {
   515  					return errwrap.Wrapf("Error waiting for Dynamo DB Table update: {{err}}", err)
   516  				}
   517  
   518  				if err := waitForGSIToBeActive(d.Id(), *gsi.IndexName, meta); err != nil {
   519  					return errwrap.Wrapf("Error waiting for Dynamo DB GSIT to be active: {{err}}", err)
   520  				}
   521  
   522  			}
   523  		}
   524  
   525  		for _, oldgsidata := range oldSet.List() {
   526  			updates := []*dynamodb.GlobalSecondaryIndexUpdate{}
   527  			oldGsiName := oldgsidata.(map[string]interface{})["name"].(string)
   528  			if _, exists := newGsiNameSet[oldGsiName]; !exists {
   529  				gsidata := oldgsidata.(map[string]interface{})
   530  				log.Printf("[DEBUG] Deleting GSI %s", gsidata["name"].(string))
   531  				update := &dynamodb.GlobalSecondaryIndexUpdate{
   532  					Delete: &dynamodb.DeleteGlobalSecondaryIndexAction{
   533  						IndexName: aws.String(gsidata["name"].(string)),
   534  					},
   535  				}
   536  				updates = append(updates, update)
   537  
   538  				req.GlobalSecondaryIndexUpdates = updates
   539  				_, err := dynamodbconn.UpdateTable(req)
   540  
   541  				if err != nil {
   542  					return err
   543  				}
   544  
   545  				if err := waitForTableToBeActive(d.Id(), meta); err != nil {
   546  					return errwrap.Wrapf("Error waiting for Dynamo DB Table update: {{err}}", err)
   547  				}
   548  			}
   549  		}
   550  	}
   551  
   552  	// Update any out-of-date read / write capacity
   553  	if gsiObjects, ok := d.GetOk("global_secondary_index"); ok {
   554  		gsiSet := gsiObjects.(*schema.Set)
   555  		if len(gsiSet.List()) > 0 {
   556  			log.Printf("Updating capacity as needed!")
   557  
   558  			// We can only change throughput, but we need to make sure it's actually changed
   559  			tableDescription, err := dynamodbconn.DescribeTable(&dynamodb.DescribeTableInput{
   560  				TableName: aws.String(d.Id()),
   561  			})
   562  
   563  			if err != nil {
   564  				return err
   565  			}
   566  
   567  			table := tableDescription.Table
   568  
   569  			for _, updatedgsidata := range gsiSet.List() {
   570  				updates := []*dynamodb.GlobalSecondaryIndexUpdate{}
   571  				gsidata := updatedgsidata.(map[string]interface{})
   572  				gsiName := gsidata["name"].(string)
   573  				gsiWriteCapacity := gsidata["write_capacity"].(int)
   574  				gsiReadCapacity := gsidata["read_capacity"].(int)
   575  
   576  				log.Printf("[DEBUG] Updating GSI %s", gsiName)
   577  				gsi, err := getGlobalSecondaryIndex(gsiName, table.GlobalSecondaryIndexes)
   578  
   579  				if err != nil {
   580  					return err
   581  				}
   582  
   583  				capacityUpdated := false
   584  
   585  				if int64(gsiReadCapacity) != *gsi.ProvisionedThroughput.ReadCapacityUnits ||
   586  					int64(gsiWriteCapacity) != *gsi.ProvisionedThroughput.WriteCapacityUnits {
   587  					capacityUpdated = true
   588  				}
   589  
   590  				if capacityUpdated {
   591  					update := &dynamodb.GlobalSecondaryIndexUpdate{
   592  						Update: &dynamodb.UpdateGlobalSecondaryIndexAction{
   593  							IndexName: aws.String(gsidata["name"].(string)),
   594  							ProvisionedThroughput: &dynamodb.ProvisionedThroughput{
   595  								WriteCapacityUnits: aws.Int64(int64(gsiWriteCapacity)),
   596  								ReadCapacityUnits:  aws.Int64(int64(gsiReadCapacity)),
   597  							},
   598  						},
   599  					}
   600  					updates = append(updates, update)
   601  
   602  				}
   603  
   604  				if len(updates) > 0 {
   605  
   606  					req := &dynamodb.UpdateTableInput{
   607  						TableName: aws.String(d.Id()),
   608  					}
   609  
   610  					req.GlobalSecondaryIndexUpdates = updates
   611  
   612  					log.Printf("[DEBUG] Updating GSI read / write capacity on %s", d.Id())
   613  					_, err := dynamodbconn.UpdateTable(req)
   614  
   615  					if err != nil {
   616  						log.Printf("[DEBUG] Error updating table: %s", err)
   617  						return err
   618  					}
   619  
   620  					if err := waitForGSIToBeActive(d.Id(), gsiName, meta); err != nil {
   621  						return errwrap.Wrapf("Error waiting for Dynamo DB GSI to be active: {{err}}", err)
   622  					}
   623  				}
   624  			}
   625  		}
   626  
   627  	}
   628  
   629  	if d.HasChange("ttl") {
   630  		if err := updateTimeToLive(d, meta); err != nil {
   631  			log.Printf("[DEBUG] Error updating table TimeToLive: %s", err)
   632  			return err
   633  		}
   634  	}
   635  
   636  	// Update tags
   637  	if err := setTagsDynamoDb(dynamodbconn, d); err != nil {
   638  		return err
   639  	}
   640  
   641  	return resourceAwsDynamoDbTableRead(d, meta)
   642  }
   643  
   644  func updateTimeToLive(d *schema.ResourceData, meta interface{}) error {
   645  	dynamodbconn := meta.(*AWSClient).dynamodbconn
   646  
   647  	if ttl, ok := d.GetOk("ttl"); ok {
   648  
   649  		timeToLiveSet := ttl.(*schema.Set)
   650  
   651  		spec := &dynamodb.TimeToLiveSpecification{}
   652  
   653  		timeToLive := timeToLiveSet.List()[0].(map[string]interface{})
   654  		spec.AttributeName = aws.String(timeToLive["attribute_name"].(string))
   655  		spec.Enabled = aws.Bool(timeToLive["enabled"].(bool))
   656  
   657  		req := &dynamodb.UpdateTimeToLiveInput{
   658  			TableName:               aws.String(d.Id()),
   659  			TimeToLiveSpecification: spec,
   660  		}
   661  
   662  		_, err := dynamodbconn.UpdateTimeToLive(req)
   663  
   664  		if err != nil {
   665  			// If ttl was not set within the .tf file before and has now been added we still run this command to update
   666  			// But there has been no change so lets continue
   667  			if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "ValidationException" && awsErr.Message() == "TimeToLive is already disabled" {
   668  				return nil
   669  			}
   670  			log.Printf("[DEBUG] Error updating TimeToLive on table: %s", err)
   671  			return err
   672  		}
   673  
   674  		log.Printf("[DEBUG] Updated TimeToLive on table")
   675  
   676  		if err := waitForTimeToLiveUpdateToBeCompleted(d.Id(), timeToLive["enabled"].(bool), meta); err != nil {
   677  			return errwrap.Wrapf("Error waiting for Dynamo DB TimeToLive to be updated: {{err}}", err)
   678  		}
   679  	}
   680  
   681  	return nil
   682  }
   683  
   684  func resourceAwsDynamoDbTableRead(d *schema.ResourceData, meta interface{}) error {
   685  	dynamodbconn := meta.(*AWSClient).dynamodbconn
   686  	log.Printf("[DEBUG] Loading data for DynamoDB table '%s'", d.Id())
   687  	req := &dynamodb.DescribeTableInput{
   688  		TableName: aws.String(d.Id()),
   689  	}
   690  
   691  	result, err := dynamodbconn.DescribeTable(req)
   692  
   693  	if err != nil {
   694  		if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "ResourceNotFoundException" {
   695  			log.Printf("[WARN] Dynamodb Table (%s) not found, error code (404)", d.Id())
   696  			d.SetId("")
   697  			return nil
   698  		}
   699  		return err
   700  	}
   701  
   702  	table := result.Table
   703  
   704  	d.Set("write_capacity", table.ProvisionedThroughput.WriteCapacityUnits)
   705  	d.Set("read_capacity", table.ProvisionedThroughput.ReadCapacityUnits)
   706  
   707  	attributes := []interface{}{}
   708  	for _, attrdef := range table.AttributeDefinitions {
   709  		attribute := map[string]string{
   710  			"name": *attrdef.AttributeName,
   711  			"type": *attrdef.AttributeType,
   712  		}
   713  		attributes = append(attributes, attribute)
   714  		log.Printf("[DEBUG] Added Attribute: %s", attribute["name"])
   715  	}
   716  
   717  	d.Set("attribute", attributes)
   718  	d.Set("name", table.TableName)
   719  
   720  	for _, attribute := range table.KeySchema {
   721  		if *attribute.KeyType == "HASH" {
   722  			d.Set("hash_key", attribute.AttributeName)
   723  		}
   724  
   725  		if *attribute.KeyType == "RANGE" {
   726  			d.Set("range_key", attribute.AttributeName)
   727  		}
   728  	}
   729  
   730  	lsiList := make([]map[string]interface{}, 0, len(table.LocalSecondaryIndexes))
   731  	for _, lsiObject := range table.LocalSecondaryIndexes {
   732  		lsi := map[string]interface{}{
   733  			"name":            *lsiObject.IndexName,
   734  			"projection_type": *lsiObject.Projection.ProjectionType,
   735  		}
   736  
   737  		for _, attribute := range lsiObject.KeySchema {
   738  
   739  			if *attribute.KeyType == "RANGE" {
   740  				lsi["range_key"] = *attribute.AttributeName
   741  			}
   742  		}
   743  		nkaList := make([]string, len(lsiObject.Projection.NonKeyAttributes))
   744  		for _, nka := range lsiObject.Projection.NonKeyAttributes {
   745  			nkaList = append(nkaList, *nka)
   746  		}
   747  		lsi["non_key_attributes"] = nkaList
   748  
   749  		lsiList = append(lsiList, lsi)
   750  	}
   751  
   752  	err = d.Set("local_secondary_index", lsiList)
   753  	if err != nil {
   754  		return err
   755  	}
   756  
   757  	gsiList := make([]map[string]interface{}, 0, len(table.GlobalSecondaryIndexes))
   758  	for _, gsiObject := range table.GlobalSecondaryIndexes {
   759  		gsi := map[string]interface{}{
   760  			"write_capacity": *gsiObject.ProvisionedThroughput.WriteCapacityUnits,
   761  			"read_capacity":  *gsiObject.ProvisionedThroughput.ReadCapacityUnits,
   762  			"name":           *gsiObject.IndexName,
   763  		}
   764  
   765  		for _, attribute := range gsiObject.KeySchema {
   766  			if *attribute.KeyType == "HASH" {
   767  				gsi["hash_key"] = *attribute.AttributeName
   768  			}
   769  
   770  			if *attribute.KeyType == "RANGE" {
   771  				gsi["range_key"] = *attribute.AttributeName
   772  			}
   773  		}
   774  
   775  		gsi["projection_type"] = *(gsiObject.Projection.ProjectionType)
   776  
   777  		nonKeyAttrs := make([]string, 0, len(gsiObject.Projection.NonKeyAttributes))
   778  		for _, nonKeyAttr := range gsiObject.Projection.NonKeyAttributes {
   779  			nonKeyAttrs = append(nonKeyAttrs, *nonKeyAttr)
   780  		}
   781  		gsi["non_key_attributes"] = nonKeyAttrs
   782  
   783  		gsiList = append(gsiList, gsi)
   784  		log.Printf("[DEBUG] Added GSI: %s - Read: %d / Write: %d", gsi["name"], gsi["read_capacity"], gsi["write_capacity"])
   785  	}
   786  
   787  	if table.StreamSpecification != nil {
   788  		d.Set("stream_view_type", table.StreamSpecification.StreamViewType)
   789  		d.Set("stream_enabled", table.StreamSpecification.StreamEnabled)
   790  		d.Set("stream_arn", table.LatestStreamArn)
   791  	}
   792  
   793  	err = d.Set("global_secondary_index", gsiList)
   794  	if err != nil {
   795  		return err
   796  	}
   797  
   798  	d.Set("arn", table.TableArn)
   799  
   800  	timeToLiveReq := &dynamodb.DescribeTimeToLiveInput{
   801  		TableName: aws.String(d.Id()),
   802  	}
   803  	timeToLiveOutput, err := dynamodbconn.DescribeTimeToLive(timeToLiveReq)
   804  	if err != nil {
   805  		return err
   806  	}
   807  	timeToLive := []interface{}{}
   808  	attribute := map[string]*string{
   809  		"name": timeToLiveOutput.TimeToLiveDescription.AttributeName,
   810  		"type": timeToLiveOutput.TimeToLiveDescription.TimeToLiveStatus,
   811  	}
   812  	timeToLive = append(timeToLive, attribute)
   813  	d.Set("timeToLive", timeToLive)
   814  
   815  	log.Printf("[DEBUG] Loaded TimeToLive data for DynamoDB table '%s'", d.Id())
   816  
   817  	tags, err := readTableTags(d, meta)
   818  	if err != nil {
   819  		return err
   820  	}
   821  	if len(tags) != 0 {
   822  		d.Set("tags", tags)
   823  	}
   824  
   825  	return nil
   826  }
   827  
   828  func resourceAwsDynamoDbTableDelete(d *schema.ResourceData, meta interface{}) error {
   829  	dynamodbconn := meta.(*AWSClient).dynamodbconn
   830  
   831  	if err := waitForTableToBeActive(d.Id(), meta); err != nil {
   832  		return errwrap.Wrapf("Error waiting for Dynamo DB Table update: {{err}}", err)
   833  	}
   834  
   835  	log.Printf("[DEBUG] DynamoDB delete table: %s", d.Id())
   836  
   837  	_, err := dynamodbconn.DeleteTable(&dynamodb.DeleteTableInput{
   838  		TableName: aws.String(d.Id()),
   839  	})
   840  	if err != nil {
   841  		return err
   842  	}
   843  
   844  	params := &dynamodb.DescribeTableInput{
   845  		TableName: aws.String(d.Id()),
   846  	}
   847  
   848  	err = resource.Retry(10*time.Minute, func() *resource.RetryError {
   849  		t, err := dynamodbconn.DescribeTable(params)
   850  		if err != nil {
   851  			if awserr, ok := err.(awserr.Error); ok && awserr.Code() == "ResourceNotFoundException" {
   852  				return nil
   853  			}
   854  			// Didn't recognize the error, so shouldn't retry.
   855  			return resource.NonRetryableError(err)
   856  		}
   857  
   858  		if t != nil {
   859  			if t.Table.TableStatus != nil && strings.ToLower(*t.Table.TableStatus) == "deleting" {
   860  				log.Printf("[DEBUG] AWS Dynamo DB table (%s) is still deleting", d.Id())
   861  				return resource.RetryableError(fmt.Errorf("still deleting"))
   862  			}
   863  		}
   864  
   865  		// we should be not found or deleting, so error here
   866  		return resource.NonRetryableError(err)
   867  	})
   868  
   869  	// check error from retry
   870  	if err != nil {
   871  		return err
   872  	}
   873  
   874  	return nil
   875  }
   876  
   877  func createGSIFromData(data *map[string]interface{}) dynamodb.GlobalSecondaryIndex {
   878  
   879  	projection := &dynamodb.Projection{
   880  		ProjectionType: aws.String((*data)["projection_type"].(string)),
   881  	}
   882  
   883  	if (*data)["projection_type"] == "INCLUDE" {
   884  		non_key_attributes := []*string{}
   885  		for _, attr := range (*data)["non_key_attributes"].([]interface{}) {
   886  			non_key_attributes = append(non_key_attributes, aws.String(attr.(string)))
   887  		}
   888  		projection.NonKeyAttributes = non_key_attributes
   889  	}
   890  
   891  	writeCapacity := (*data)["write_capacity"].(int)
   892  	readCapacity := (*data)["read_capacity"].(int)
   893  
   894  	key_schema := []*dynamodb.KeySchemaElement{
   895  		{
   896  			AttributeName: aws.String((*data)["hash_key"].(string)),
   897  			KeyType:       aws.String("HASH"),
   898  		},
   899  	}
   900  
   901  	range_key_name := (*data)["range_key"]
   902  	if range_key_name != "" {
   903  		range_key_element := &dynamodb.KeySchemaElement{
   904  			AttributeName: aws.String(range_key_name.(string)),
   905  			KeyType:       aws.String("RANGE"),
   906  		}
   907  
   908  		key_schema = append(key_schema, range_key_element)
   909  	}
   910  
   911  	return dynamodb.GlobalSecondaryIndex{
   912  		IndexName:  aws.String((*data)["name"].(string)),
   913  		KeySchema:  key_schema,
   914  		Projection: projection,
   915  		ProvisionedThroughput: &dynamodb.ProvisionedThroughput{
   916  			WriteCapacityUnits: aws.Int64(int64(writeCapacity)),
   917  			ReadCapacityUnits:  aws.Int64(int64(readCapacity)),
   918  		},
   919  	}
   920  }
   921  
   922  func getGlobalSecondaryIndex(indexName string, indexList []*dynamodb.GlobalSecondaryIndexDescription) (*dynamodb.GlobalSecondaryIndexDescription, error) {
   923  	for _, gsi := range indexList {
   924  		if *gsi.IndexName == indexName {
   925  			return gsi, nil
   926  		}
   927  	}
   928  
   929  	return &dynamodb.GlobalSecondaryIndexDescription{}, fmt.Errorf("Can't find a GSI by that name...")
   930  }
   931  
   932  func getAttributeType(d *schema.ResourceData, attributeName string) (string, error) {
   933  	if attributedata, ok := d.GetOk("attribute"); ok {
   934  		attributeSet := attributedata.(*schema.Set)
   935  		for _, attribute := range attributeSet.List() {
   936  			attr := attribute.(map[string]interface{})
   937  			if attr["name"] == attributeName {
   938  				return attr["type"].(string), nil
   939  			}
   940  		}
   941  	}
   942  
   943  	return "", fmt.Errorf("Unable to find an attribute named %s", attributeName)
   944  }
   945  
   946  func waitForGSIToBeActive(tableName string, gsiName string, meta interface{}) error {
   947  	dynamodbconn := meta.(*AWSClient).dynamodbconn
   948  	req := &dynamodb.DescribeTableInput{
   949  		TableName: aws.String(tableName),
   950  	}
   951  
   952  	activeIndex := false
   953  
   954  	for activeIndex == false {
   955  
   956  		result, err := dynamodbconn.DescribeTable(req)
   957  
   958  		if err != nil {
   959  			return err
   960  		}
   961  
   962  		table := result.Table
   963  		var targetGSI *dynamodb.GlobalSecondaryIndexDescription = nil
   964  
   965  		for _, gsi := range table.GlobalSecondaryIndexes {
   966  			if *gsi.IndexName == gsiName {
   967  				targetGSI = gsi
   968  			}
   969  		}
   970  
   971  		if targetGSI != nil {
   972  			activeIndex = *targetGSI.IndexStatus == "ACTIVE"
   973  
   974  			if !activeIndex {
   975  				log.Printf("[DEBUG] Sleeping for 5 seconds for %s GSI to become active", gsiName)
   976  				time.Sleep(5 * time.Second)
   977  			}
   978  		} else {
   979  			log.Printf("[DEBUG] GSI %s did not exist, giving up", gsiName)
   980  			break
   981  		}
   982  	}
   983  
   984  	return nil
   985  
   986  }
   987  
   988  func waitForTableToBeActive(tableName string, meta interface{}) error {
   989  	dynamodbconn := meta.(*AWSClient).dynamodbconn
   990  	req := &dynamodb.DescribeTableInput{
   991  		TableName: aws.String(tableName),
   992  	}
   993  
   994  	activeState := false
   995  
   996  	for activeState == false {
   997  		result, err := dynamodbconn.DescribeTable(req)
   998  
   999  		if err != nil {
  1000  			return err
  1001  		}
  1002  
  1003  		activeState = *result.Table.TableStatus == "ACTIVE"
  1004  
  1005  		// Wait for a few seconds
  1006  		if !activeState {
  1007  			log.Printf("[DEBUG] Sleeping for 5 seconds for table to become active")
  1008  			time.Sleep(5 * time.Second)
  1009  		}
  1010  	}
  1011  
  1012  	return nil
  1013  
  1014  }
  1015  
  1016  func waitForTimeToLiveUpdateToBeCompleted(tableName string, enabled bool, meta interface{}) error {
  1017  	dynamodbconn := meta.(*AWSClient).dynamodbconn
  1018  	req := &dynamodb.DescribeTimeToLiveInput{
  1019  		TableName: aws.String(tableName),
  1020  	}
  1021  
  1022  	stateMatched := false
  1023  	for stateMatched == false {
  1024  		result, err := dynamodbconn.DescribeTimeToLive(req)
  1025  
  1026  		if err != nil {
  1027  			return err
  1028  		}
  1029  
  1030  		if enabled {
  1031  			stateMatched = *result.TimeToLiveDescription.TimeToLiveStatus == dynamodb.TimeToLiveStatusEnabled
  1032  		} else {
  1033  			stateMatched = *result.TimeToLiveDescription.TimeToLiveStatus == dynamodb.TimeToLiveStatusDisabled
  1034  		}
  1035  
  1036  		// Wait for a few seconds, this may take a long time...
  1037  		if !stateMatched {
  1038  			log.Printf("[DEBUG] Sleeping for 5 seconds before checking TimeToLive state again")
  1039  			time.Sleep(5 * time.Second)
  1040  		}
  1041  	}
  1042  
  1043  	log.Printf("[DEBUG] TimeToLive update complete")
  1044  
  1045  	return nil
  1046  
  1047  }
  1048  
  1049  func createTableTags(d *schema.ResourceData, meta interface{}) error {
  1050  	// DynamoDB Table has to be in the ACTIVE state in order to tag the resource
  1051  	if err := waitForTableToBeActive(d.Id(), meta); err != nil {
  1052  		return err
  1053  	}
  1054  	tags := d.Get("tags").(map[string]interface{})
  1055  	arn := d.Get("arn").(string)
  1056  	dynamodbconn := meta.(*AWSClient).dynamodbconn
  1057  	req := &dynamodb.TagResourceInput{
  1058  		ResourceArn: aws.String(arn),
  1059  		Tags:        tagsFromMapDynamoDb(tags),
  1060  	}
  1061  	_, err := dynamodbconn.TagResource(req)
  1062  	if err != nil {
  1063  		return fmt.Errorf("Error tagging dynamodb resource: %s", err)
  1064  	}
  1065  	return nil
  1066  }
  1067  
  1068  func readTableTags(d *schema.ResourceData, meta interface{}) (map[string]string, error) {
  1069  	if err := waitForTableToBeActive(d.Id(), meta); err != nil {
  1070  		return nil, err
  1071  	}
  1072  	arn := d.Get("arn").(string)
  1073  	//result := make(map[string]string)
  1074  
  1075  	dynamodbconn := meta.(*AWSClient).dynamodbconn
  1076  	req := &dynamodb.ListTagsOfResourceInput{
  1077  		ResourceArn: aws.String(arn),
  1078  	}
  1079  
  1080  	output, err := dynamodbconn.ListTagsOfResource(req)
  1081  	if err != nil {
  1082  		return nil, fmt.Errorf("Error reading tags from dynamodb resource: %s", err)
  1083  	}
  1084  	result := tagsToMapDynamoDb(output.Tags)
  1085  	// TODO Read NextToken if avail
  1086  	return result, nil
  1087  }