From 714fc11e90036005cc8bb35d31e26e1af2d807e8 Mon Sep 17 00:00:00 2001 From: Sunil Kumar Mohanty Date: Wed, 28 Nov 2018 23:16:58 +0200 Subject: [PATCH 1/7] add support for billing mode in dynamodb --- aws/resource_aws_dynamodb_table.go | 46 ++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/aws/resource_aws_dynamodb_table.go b/aws/resource_aws_dynamodb_table.go index 2fcc37d58c37..56a6b1cd14bf 100644 --- a/aws/resource_aws_dynamodb_table.go +++ b/aws/resource_aws_dynamodb_table.go @@ -83,13 +83,21 @@ func resourceAwsDynamoDbTable() *schema.Resource { Optional: true, ForceNew: true, }, + "billing_mode": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + dynamodb.BillingModePayPerRequest, + dynamodb.BillingModeProvisioned, + }, false), + }, "write_capacity": { Type: schema.TypeInt, - Required: true, + Required: false, }, "read_capacity": { Type: schema.TypeInt, - Required: true, + Required: false, }, "attribute": { Type: schema.TypeSet, @@ -279,12 +287,16 @@ func resourceAwsDynamoDbTableCreate(d *schema.ResourceData, meta interface{}) er log.Printf("[DEBUG] Creating DynamoDB table with key schema: %#v", keySchemaMap) req := &dynamodb.CreateTableInput{ - TableName: aws.String(d.Get("name").(string)), - ProvisionedThroughput: expandDynamoDbProvisionedThroughput(map[string]interface{}{ + TableName: aws.String(d.Get("name").(string)), + BillingMode: aws.String(d.Get("billing_mode").(string)), + KeySchema: expandDynamoDbKeySchema(keySchemaMap), + } + + if d.Get("billing_mode").(string) == dynamodb.BillingModeProvisioned { + req.ProvisionedThroughput = expandDynamoDbProvisionedThroughput(map[string]interface{}{ "read_capacity": d.Get("read_capacity"), "write_capacity": d.Get("write_capacity"), - }), - KeySchema: expandDynamoDbKeySchema(keySchemaMap), + }) } if v, ok := d.GetOk("attribute"); ok { @@ -360,10 +372,28 @@ func resourceAwsDynamoDbTableCreate(d *schema.ResourceData, meta interface{}) er func resourceAwsDynamoDbTableUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).dynamodbconn - + if d.HasChange("billing_mode") && !d.IsNewResource() { + req := &dynamodb.UpdateTableInput{ + TableName: aws.String(d.Id()), + BillingMode: aws.String(d.Get("billing_mode").(string)), + } + if d.Get("billing_mode").(string) == dynamodb.BillingModeProvisioned { + req.ProvisionedThroughput = expandDynamoDbProvisionedThroughput(map[string]interface{}{ + "read_capacity": d.Get("read_capacity"), + "write_capacity": d.Get("write_capacity"), + }) + } + _, err := conn.UpdateTable(req) + if err != nil { + return err + } + if err := waitForDynamoDbTableToBeActive(d.Id(), d.Timeout(schema.TimeoutUpdate), conn); err != nil { + return fmt.Errorf("Error waiting for DynamoDB Table update: %s", err) + } + } // Cannot create or delete index while updating table IOPS // so we update IOPS separately - if (d.HasChange("read_capacity") || d.HasChange("write_capacity")) && !d.IsNewResource() { + if !d.HasChange("billing_mode") && d.Get("billing_mode").(string) == dynamodb.BillingModeProvisioned && (d.HasChange("read_capacity") || d.HasChange("write_capacity")) && !d.IsNewResource() { _, err := conn.UpdateTable(&dynamodb.UpdateTableInput{ TableName: aws.String(d.Id()), ProvisionedThroughput: expandDynamoDbProvisionedThroughput(map[string]interface{}{ From a8c052bf8e2949e5a3df42e282ec28e8e318374d Mon Sep 17 00:00:00 2001 From: Sunil Kumar Mohanty Date: Thu, 29 Nov 2018 06:15:25 +0200 Subject: [PATCH 2/7] add default value for dynamodb billing mode --- aws/resource_aws_dynamodb_table.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aws/resource_aws_dynamodb_table.go b/aws/resource_aws_dynamodb_table.go index 56a6b1cd14bf..e5902b366f7d 100644 --- a/aws/resource_aws_dynamodb_table.go +++ b/aws/resource_aws_dynamodb_table.go @@ -85,7 +85,8 @@ func resourceAwsDynamoDbTable() *schema.Resource { }, "billing_mode": { Type: schema.TypeString, - Required: true, + Optional: true, + Default: dynamodb.BillingModeProvisioned, ValidateFunc: validation.StringInSlice([]string{ dynamodb.BillingModePayPerRequest, dynamodb.BillingModeProvisioned, From 3058345e6681332c5a878e8f6cf3d1217befcf5b Mon Sep 17 00:00:00 2001 From: Sunil Kumar Mohanty Date: Thu, 29 Nov 2018 21:45:32 +0200 Subject: [PATCH 3/7] add test cases for billing mode pay_per_request --- aws/resource_aws_dynamodb_table_test.go | 79 +++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/aws/resource_aws_dynamodb_table_test.go b/aws/resource_aws_dynamodb_table_test.go index af533962902b..0016b31f29a6 100644 --- a/aws/resource_aws_dynamodb_table_test.go +++ b/aws/resource_aws_dynamodb_table_test.go @@ -477,6 +477,33 @@ func TestAccAWSDynamoDbTable_enablePitr(t *testing.T) { }) } +func TestAccAWSDynamoDbTable_BillingMode(t *testing.T) { + var conf dynamodb.DescribeTableOutput + + rName := acctest.RandomWithPrefix("TerraformTestTable-") + + resource.ParallelTest(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), + testAccCheckInitialAWSDynamoDbTableConf("aws_dynamodb_table.basic-dynamodb-table"), + ), + }, + { + Config: testAccAWSDynamoDbBilling_PayPerRequest(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckDynamoDbTableHasBilling_PayPerRequest("aws_dynamodb_table.basic-dynamodb-table"), + ), + }, + }, + }) +} + func TestAccAWSDynamoDbTable_streamSpecification(t *testing.T) { var conf dynamodb.DescribeTableOutput @@ -989,6 +1016,10 @@ func testAccCheckInitialAWSDynamoDbTableConf(n string) resource.TestCheckFunc { log.Printf("[DEBUG] Checking on table %s", rs.Primary.ID) + if *table.BillingModeSummary.BillingMode != dynamodb.BillingModeProvisioned { + return fmt.Errorf("Billing Mode was %s, not %s!", *table.BillingModeSummary.BillingMode, dynamodb.BillingModeProvisioned) + } + if *table.ProvisionedThroughput.WriteCapacityUnits != 2 { return fmt.Errorf("Provisioned write capacity was %d, not 2!", table.ProvisionedThroughput.WriteCapacityUnits) } @@ -1066,6 +1097,36 @@ func testAccCheckDynamoDbTableHasPointInTimeRecoveryEnabled(n string) resource.T } } +func testAccCheckDynamoDbTableHasBilling_PayPerRequest(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + 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.DescribeTableInput{ + TableName: aws.String(rs.Primary.ID), + } + resp, err := conn.DescribeTable(params) + + if err != nil { + return err + } + table := resp.Table + + if *table.BillingModeSummary.BillingMode != dynamodb.BillingModePayPerRequest { + return fmt.Errorf("Billing Mode was %s, not %s!", *table.BillingModeSummary.BillingMode, dynamodb.BillingModePayPerRequest) + } + + return nil + } +} + func testAccCheckDynamoDbTableWasUpdated(n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -1186,6 +1247,24 @@ resource "aws_dynamodb_table" "basic-dynamodb-table" { `, rName) } +func testAccAWSDynamoDbBilling_PayPerRequest(rName string) string { + return fmt.Sprintf(` +resource "aws_dynamodb_table" "basic-dynamodb-table" { + name = "%s" + billing_mode = "PAY_PER_REQUEST" + hash_key = "TestTableHashKey" + + attribute { + name = "TestTableHashKey" + type = "S" + } + point_in_time_recovery { + enabled = true + } +} +`, rName) +} + func testAccAWSDynamoDbConfigInitialState(rName string) string { return fmt.Sprintf(` resource "aws_dynamodb_table" "basic-dynamodb-table" { From 9fd288ec74903661cf573835745eb325c980af8e Mon Sep 17 00:00:00 2001 From: Sunil Kumar Mohanty Date: Thu, 29 Nov 2018 21:59:18 +0200 Subject: [PATCH 4/7] update documentation --- website/docs/r/dynamodb_table.html.markdown | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/website/docs/r/dynamodb_table.html.markdown b/website/docs/r/dynamodb_table.html.markdown index 713aa3fe7eec..61278e0e0634 100644 --- a/website/docs/r/dynamodb_table.html.markdown +++ b/website/docs/r/dynamodb_table.html.markdown @@ -20,6 +20,7 @@ in the [AWS SDK example documentation](https://docs.aws.amazon.com/amazondynamod ```hcl resource "aws_dynamodb_table" "basic-dynamodb-table" { name = "GameScores" + billing_mode = "PROVISIONED" read_capacity = 20 write_capacity = 20 hash_key = "UserId" @@ -83,10 +84,11 @@ The following arguments are supported: * `name` - (Required) The name of the table, this needs to be unique within a region. +* `billing_mode` - (Optional) Controls how you are charged for read and write throughput and how you manage capacity. The valid values are `PROVISIONED` and `PAY_PER_REQUEST`. Defaults to `PROVISIONED`. * `hash_key` - (Required, Forces new resource) The attribute to use as the hash (partition) key. Must also be defined as an `attribute`, see below. * `range_key` - (Optional, Forces new resource) The attribute to use as the range (sort) key. Must also be defined as an `attribute`, see below. -* `write_capacity` - (Required) The number of write units for this table -* `read_capacity` - (Required) The number of read units for this table +* `write_capacity` - (Optional) The number of write units for this table. If the `billing_mode` is `PROVISIONED`, this field is required. +* `read_capacity` - (Optional) The number of read units for this table. If the `billing_mode` is `PROVISIONED`, this field is required. * `attribute` - (Required) List of nested attribute definitions. Only required for `hash_key` and `range_key` attributes. Each attribute has two properties: * `name` - (Required) The name of the attribute * `type` - (Required) Attribute type, which must be a scalar type: `S`, `N`, or `B` for (S)tring, (N)umber or (B)inary data From f2d56e23d735b9df35bc9e16238fff518a290996 Mon Sep 17 00:00:00 2001 From: Sunil Kumar Mohanty Date: Thu, 29 Nov 2018 22:04:04 +0200 Subject: [PATCH 5/7] format code by running make fmt --- aws/resource_aws_dynamodb_table_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_dynamodb_table_test.go b/aws/resource_aws_dynamodb_table_test.go index 0016b31f29a6..964f7c479ce8 100644 --- a/aws/resource_aws_dynamodb_table_test.go +++ b/aws/resource_aws_dynamodb_table_test.go @@ -497,7 +497,7 @@ func TestAccAWSDynamoDbTable_BillingMode(t *testing.T) { { Config: testAccAWSDynamoDbBilling_PayPerRequest(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckDynamoDbTableHasBilling_PayPerRequest("aws_dynamodb_table.basic-dynamodb-table"), + testAccCheckDynamoDbTableHasBilling_PayPerRequest("aws_dynamodb_table.basic-dynamodb-table"), ), }, }, From a4bb35447f1bea15f11546d9cb0df07c7ddb4fa1 Mon Sep 17 00:00:00 2001 From: Sunil Kumar Mohanty Date: Fri, 30 Nov 2018 07:20:50 +0200 Subject: [PATCH 6/7] returns error when read/write capacity not set when billing mode is PROVISIONED and other small fixes --- aws/resource_aws_dynamodb_table.go | 27 ++++++++++++++++++------- aws/resource_aws_dynamodb_table_test.go | 3 --- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/aws/resource_aws_dynamodb_table.go b/aws/resource_aws_dynamodb_table.go index e5902b366f7d..74bdd5b0a8f1 100644 --- a/aws/resource_aws_dynamodb_table.go +++ b/aws/resource_aws_dynamodb_table.go @@ -94,11 +94,11 @@ func resourceAwsDynamoDbTable() *schema.Resource { }, "write_capacity": { Type: schema.TypeInt, - Required: false, + Optional: true, }, "read_capacity": { Type: schema.TypeInt, - Required: false, + Optional: true, }, "attribute": { Type: schema.TypeSet, @@ -294,9 +294,15 @@ func resourceAwsDynamoDbTableCreate(d *schema.ResourceData, meta interface{}) er } if d.Get("billing_mode").(string) == dynamodb.BillingModeProvisioned { + v_read, ok_read := d.GetOk("read_capacity") + v_write, ok_write := d.GetOk("write_capacity") + if !ok_read || !ok_write { + return fmt.Errorf("Read and Write capacity should be set when billing mode is PROVISIONED") + } + req.ProvisionedThroughput = expandDynamoDbProvisionedThroughput(map[string]interface{}{ - "read_capacity": d.Get("read_capacity"), - "write_capacity": d.Get("write_capacity"), + "read_capacity": v_read, + "write_capacity": v_write, }) } @@ -379,14 +385,21 @@ func resourceAwsDynamoDbTableUpdate(d *schema.ResourceData, meta interface{}) er BillingMode: aws.String(d.Get("billing_mode").(string)), } if d.Get("billing_mode").(string) == dynamodb.BillingModeProvisioned { + + v_read, ok_read := d.GetOk("read_capacity") + v_write, ok_write := d.GetOk("write_capacity") + if !ok_read || !ok_write { + return fmt.Errorf("Read and Write capacity should be set when billing mode is PROVISIONED") + } + req.ProvisionedThroughput = expandDynamoDbProvisionedThroughput(map[string]interface{}{ - "read_capacity": d.Get("read_capacity"), - "write_capacity": d.Get("write_capacity"), + "read_capacity": v_read, + "write_capacity": v_write, }) } _, err := conn.UpdateTable(req) if err != nil { - return err + return fmt.Errorf("Error updating DynamoDB Table (%s) billing mode: %s", d.Id(), err) } if err := waitForDynamoDbTableToBeActive(d.Id(), d.Timeout(schema.TimeoutUpdate), conn); err != nil { return fmt.Errorf("Error waiting for DynamoDB Table update: %s", err) diff --git a/aws/resource_aws_dynamodb_table_test.go b/aws/resource_aws_dynamodb_table_test.go index 964f7c479ce8..ad284b30d30a 100644 --- a/aws/resource_aws_dynamodb_table_test.go +++ b/aws/resource_aws_dynamodb_table_test.go @@ -1258,9 +1258,6 @@ resource "aws_dynamodb_table" "basic-dynamodb-table" { name = "TestTableHashKey" type = "S" } - point_in_time_recovery { - enabled = true - } } `, rName) } From 0f34622939c0707da890a7f42963044a81d55a37 Mon Sep 17 00:00:00 2001 From: Sunil Kumar Mohanty Date: Fri, 30 Nov 2018 22:15:02 +0200 Subject: [PATCH 7/7] FIX: Multiple PR feedbacks --- aws/resource_aws_dynamodb_table_test.go | 11 +++++++---- aws/structure.go | 5 +++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/aws/resource_aws_dynamodb_table_test.go b/aws/resource_aws_dynamodb_table_test.go index ad284b30d30a..fa9ae42dac8d 100644 --- a/aws/resource_aws_dynamodb_table_test.go +++ b/aws/resource_aws_dynamodb_table_test.go @@ -1016,8 +1016,8 @@ func testAccCheckInitialAWSDynamoDbTableConf(n string) resource.TestCheckFunc { log.Printf("[DEBUG] Checking on table %s", rs.Primary.ID) - if *table.BillingModeSummary.BillingMode != dynamodb.BillingModeProvisioned { - return fmt.Errorf("Billing Mode was %s, not %s!", *table.BillingModeSummary.BillingMode, dynamodb.BillingModeProvisioned) + if table.BillingModeSummary != nil && aws.StringValue(table.BillingModeSummary.BillingMode) != dynamodb.BillingModeProvisioned { + return fmt.Errorf("Billing Mode was %s, not %s!", aws.StringValue(table.BillingModeSummary.BillingMode), dynamodb.BillingModeProvisioned) } if *table.ProvisionedThroughput.WriteCapacityUnits != 2 { @@ -1119,8 +1119,11 @@ func testAccCheckDynamoDbTableHasBilling_PayPerRequest(n string) resource.TestCh } table := resp.Table - if *table.BillingModeSummary.BillingMode != dynamodb.BillingModePayPerRequest { - return fmt.Errorf("Billing Mode was %s, not %s!", *table.BillingModeSummary.BillingMode, dynamodb.BillingModePayPerRequest) + if table.BillingModeSummary == nil { + return fmt.Errorf("Billing Mode summary was empty, expected summary to exist and contain billing mode %s", dynamodb.BillingModePayPerRequest) + } else if aws.StringValue(table.BillingModeSummary.BillingMode) != dynamodb.BillingModePayPerRequest { + return fmt.Errorf("Billing Mode was %s, not %s!", aws.StringValue(table.BillingModeSummary.BillingMode), dynamodb.BillingModePayPerRequest) + } return nil diff --git a/aws/structure.go b/aws/structure.go index e322158966e3..ec99a849d2cc 100644 --- a/aws/structure.go +++ b/aws/structure.go @@ -4154,6 +4154,11 @@ func flattenDynamoDbPitr(pitrDesc *dynamodb.DescribeContinuousBackupsOutput) []i } func flattenAwsDynamoDbTableResource(d *schema.ResourceData, table *dynamodb.TableDescription) error { + d.Set("billing_mode", dynamodb.BillingModeProvisioned) + if table.BillingModeSummary != nil { + d.Set("billing_mode", table.BillingModeSummary.BillingMode) + } + d.Set("write_capacity", table.ProvisionedThroughput.WriteCapacityUnits) d.Set("read_capacity", table.ProvisionedThroughput.ReadCapacityUnits)