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