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:
commit
3a01d5f5e1
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue