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