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