Merge pull request #14104 from rlweb/feature/aws_dynamodb_table-add-timetolive

provider/aws: aws_dynamodb_table Add support for TimeToLive
This commit is contained in:
Jake Champlin 2017-05-01 15:37:34 -04:00 committed by GitHub
commit 3a01d5f5e1
3 changed files with 254 additions and 0 deletions

View File

@ -92,6 +92,23 @@ func resourceAwsDynamoDbTable() *schema.Resource {
return hashcode.String(buf.String())
},
},
"ttl": {
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"attribute_name": {
Type: schema.TypeString,
Required: true,
},
"enabled": {
Type: schema.TypeBool,
Required: true,
},
},
},
},
"local_secondary_index": {
Type: schema.TypeSet,
Optional: true,
@ -296,6 +313,7 @@ func resourceAwsDynamoDbTableCreate(d *schema.ResourceData, meta interface{}) er
log.Printf("[DEBUG] Adding StreamSpecifications to the table")
}
_, timeToLiveOk := d.GetOk("ttl")
_, tagsOk := d.GetOk("tags")
attemptCount := 1
@ -326,12 +344,28 @@ func resourceAwsDynamoDbTableCreate(d *schema.ResourceData, meta interface{}) er
if err := d.Set("arn", tableArn); err != nil {
return err
}
// Wait, till table is active before imitating any TimeToLive changes
if err := waitForTableToBeActive(d.Id(), meta); err != nil {
log.Printf("[DEBUG] Error waiting for table to be active: %s", err)
return err
}
log.Printf("[DEBUG] Setting DynamoDB TimeToLive on arn: %s", tableArn)
if timeToLiveOk {
if err := updateTimeToLive(d, meta); err != nil {
log.Printf("[DEBUG] Error updating table TimeToLive: %s", err)
return err
}
}
if tagsOk {
log.Printf("[DEBUG] Setting DynamoDB Tags on arn: %s", tableArn)
if err := createTableTags(d, meta); err != nil {
return err
}
}
return resourceAwsDynamoDbTableRead(d, meta)
}
}
@ -587,6 +621,13 @@ func resourceAwsDynamoDbTableUpdate(d *schema.ResourceData, meta interface{}) er
}
if d.HasChange("ttl") {
if err := updateTimeToLive(d, meta); err != nil {
log.Printf("[DEBUG] Error updating table TimeToLive: %s", err)
return err
}
}
// Update tags
if err := setTagsDynamoDb(dynamodbconn, d); err != nil {
return err
@ -595,6 +636,46 @@ func resourceAwsDynamoDbTableUpdate(d *schema.ResourceData, meta interface{}) er
return resourceAwsDynamoDbTableRead(d, meta)
}
func updateTimeToLive(d *schema.ResourceData, meta interface{}) error {
dynamodbconn := meta.(*AWSClient).dynamodbconn
if ttl, ok := d.GetOk("ttl"); ok {
timeToLiveSet := ttl.(*schema.Set)
spec := &dynamodb.TimeToLiveSpecification{}
timeToLive := timeToLiveSet.List()[0].(map[string]interface{})
spec.AttributeName = aws.String(timeToLive["attribute_name"].(string))
spec.Enabled = aws.Bool(timeToLive["enabled"].(bool))
req := &dynamodb.UpdateTimeToLiveInput{
TableName: aws.String(d.Id()),
TimeToLiveSpecification: spec,
}
_, err := dynamodbconn.UpdateTimeToLive(req)
if err != nil {
// If ttl was not set within the .tf file before and has now been added we still run this command to update
// But there has been no change so lets continue
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "ValidationException" && awsErr.Message() == "TimeToLive is already disabled" {
return nil
}
log.Printf("[DEBUG] Error updating TimeToLive on table: %s", err)
return err
}
log.Printf("[DEBUG] Updated TimeToLive on table")
if err := waitForTimeToLiveUpdateToBeCompleted(d.Id(), timeToLive["enabled"].(bool), meta); err != nil {
return errwrap.Wrapf("Error waiting for Dynamo DB TimeToLive to be updated: {{err}}", err)
}
}
return nil
}
func resourceAwsDynamoDbTableRead(d *schema.ResourceData, meta interface{}) error {
dynamodbconn := meta.(*AWSClient).dynamodbconn
log.Printf("[DEBUG] Loading data for DynamoDB table '%s'", d.Id())
@ -711,6 +792,23 @@ func resourceAwsDynamoDbTableRead(d *schema.ResourceData, meta interface{}) erro
d.Set("arn", table.TableArn)
timeToLiveReq := &dynamodb.DescribeTimeToLiveInput{
TableName: aws.String(d.Id()),
}
timeToLiveOutput, err := dynamodbconn.DescribeTimeToLive(timeToLiveReq)
if err != nil {
return err
}
timeToLive := []interface{}{}
attribute := map[string]*string{
"name": timeToLiveOutput.TimeToLiveDescription.AttributeName,
"type": timeToLiveOutput.TimeToLiveDescription.TimeToLiveStatus,
}
timeToLive = append(timeToLive, attribute)
d.Set("timeToLive", timeToLive)
log.Printf("[DEBUG] Loaded TimeToLive data for DynamoDB table '%s'", d.Id())
tags, err := readTableTags(d, meta)
if err != nil {
return err
@ -910,6 +1008,39 @@ func waitForTableToBeActive(tableName string, meta interface{}) error {
}
func waitForTimeToLiveUpdateToBeCompleted(tableName string, enabled bool, meta interface{}) error {
dynamodbconn := meta.(*AWSClient).dynamodbconn
req := &dynamodb.DescribeTimeToLiveInput{
TableName: aws.String(tableName),
}
stateMatched := false
for stateMatched == false {
result, err := dynamodbconn.DescribeTimeToLive(req)
if err != nil {
return err
}
if enabled {
stateMatched = *result.TimeToLiveDescription.TimeToLiveStatus == dynamodb.TimeToLiveStatusEnabled
} else {
stateMatched = *result.TimeToLiveDescription.TimeToLiveStatus == dynamodb.TimeToLiveStatusDisabled
}
// Wait for a few seconds, this may take a long time...
if !stateMatched {
log.Printf("[DEBUG] Sleeping for 5 seconds before checking TimeToLive state again")
time.Sleep(5 * time.Second)
}
}
log.Printf("[DEBUG] TimeToLive update complete")
return nil
}
func createTableTags(d *schema.ResourceData, meta interface{}) error {
// DynamoDB Table has to be in the ACTIVE state in order to tag the resource
if err := waitForTableToBeActive(d.Id(), meta); err != nil {

View File

@ -110,6 +110,71 @@ func TestAccAWSDynamoDbTable_gsiUpdate(t *testing.T) {
})
}
func TestAccAWSDynamoDbTable_ttl(t *testing.T) {
var conf dynamodb.DescribeTableOutput
rName := acctest.RandomWithPrefix("TerraformTestTable-")
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSDynamoDbTableDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSDynamoDbConfigInitialState(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckInitialAWSDynamoDbTableExists("aws_dynamodb_table.basic-dynamodb-table", &conf),
),
},
{
Config: testAccAWSDynamoDbConfigAddTimeToLive(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckDynamoDbTableTimeToLiveWasUpdated("aws_dynamodb_table.basic-dynamodb-table"),
),
},
},
})
}
func testAccCheckDynamoDbTableTimeToLiveWasUpdated(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
log.Printf("[DEBUG] Trying to create initial table state!")
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No DynamoDB table name specified!")
}
conn := testAccProvider.Meta().(*AWSClient).dynamodbconn
params := &dynamodb.DescribeTimeToLiveInput{
TableName: aws.String(rs.Primary.ID),
}
resp, err := conn.DescribeTimeToLive(params)
if err != nil {
return fmt.Errorf("[ERROR] Problem describing time to live for table '%s': %s", rs.Primary.ID, err)
}
ttlDescription := resp.TimeToLiveDescription
log.Printf("[DEBUG] Checking on table %s", rs.Primary.ID)
if *ttlDescription.TimeToLiveStatus != dynamodb.TimeToLiveStatusEnabled {
return fmt.Errorf("TimeToLiveStatus %s, not ENABLED!", *ttlDescription.TimeToLiveStatus)
}
if *ttlDescription.AttributeName != "TestTTL" {
return fmt.Errorf("AttributeName was %s, not TestTTL!", *ttlDescription.AttributeName)
}
return nil
}
}
func TestResourceAWSDynamoDbTableStreamViewType_validation(t *testing.T) {
cases := []struct {
Value string
@ -678,3 +743,55 @@ resource "aws_dynamodb_table" "test" {
}
`, name)
}
func testAccAWSDynamoDbConfigAddTimeToLive(rName string) string {
return fmt.Sprintf(`
resource "aws_dynamodb_table" "basic-dynamodb-table" {
name = "%s"
read_capacity = 10
write_capacity = 20
hash_key = "TestTableHashKey"
range_key = "TestTableRangeKey"
attribute {
name = "TestTableHashKey"
type = "S"
}
attribute {
name = "TestTableRangeKey"
type = "S"
}
attribute {
name = "TestLSIRangeKey"
type = "N"
}
attribute {
name = "TestGSIRangeKey"
type = "S"
}
local_secondary_index {
name = "TestTableLSI"
range_key = "TestLSIRangeKey"
projection_type = "ALL"
}
ttl {
attribute_name = "TestTTL"
enabled = true
}
global_secondary_index {
name = "InitialTestTableGSI"
hash_key = "TestTableHashKey"
range_key = "TestGSIRangeKey"
write_capacity = 10
read_capacity = 10
projection_type = "KEYS_ONLY"
}
}
`, rName)
}

View File

@ -38,6 +38,11 @@ resource "aws_dynamodb_table" "basic-dynamodb-table" {
type = "N"
}
ttl {
attribute_name = "TimeToExist"
enabled = false
}
global_secondary_index {
name = "GameTitleIndex"
hash_key = "GameTitle"
@ -72,6 +77,7 @@ The following arguments are supported:
* `type` - One of: S, N, or B for (S)tring, (N)umber or (B)inary data
* `stream_enabled` - (Optional) Indicates whether Streams are to be enabled (true) or disabled (false).
* `stream_view_type` - (Optional) When an item in the table is modified, StreamViewType determines what information is written to the table's stream. Valid values are KEYS_ONLY, NEW_IMAGE, OLD_IMAGE, NEW_AND_OLD_IMAGES.
* `ttl` - (Optional) Indicates whether time to live is enabled (true) or disabled (false) and the `attribute_name` to be used.
* `local_secondary_index` - (Optional, Forces new resource) Describe an LSI on the table;
these can only be allocated *at creation* so you cannot change this
definition after you have created the resource.