diff --git a/aws/resource_aws_dynamodb_table.go b/aws/resource_aws_dynamodb_table.go index 2fcc37d58c3..74bdd5b0a8f 100644 --- a/aws/resource_aws_dynamodb_table.go +++ b/aws/resource_aws_dynamodb_table.go @@ -83,13 +83,22 @@ func resourceAwsDynamoDbTable() *schema.Resource { Optional: true, ForceNew: true, }, + "billing_mode": { + Type: schema.TypeString, + Optional: true, + Default: dynamodb.BillingModeProvisioned, + ValidateFunc: validation.StringInSlice([]string{ + dynamodb.BillingModePayPerRequest, + dynamodb.BillingModeProvisioned, + }, false), + }, "write_capacity": { Type: schema.TypeInt, - Required: true, + Optional: true, }, "read_capacity": { Type: schema.TypeInt, - Required: true, + Optional: true, }, "attribute": { Type: schema.TypeSet, @@ -279,12 +288,22 @@ 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{}{ - "read_capacity": d.Get("read_capacity"), - "write_capacity": d.Get("write_capacity"), - }), - KeySchema: expandDynamoDbKeySchema(keySchemaMap), + 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 { + 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": v_read, + "write_capacity": v_write, + }) } if v, ok := d.GetOk("attribute"); ok { @@ -360,10 +379,35 @@ 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 { + + 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": v_read, + "write_capacity": v_write, + }) + } + _, err := conn.UpdateTable(req) + if err != nil { + 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) + } + } // 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{}{ diff --git a/aws/resource_aws_dynamodb_table_test.go b/aws/resource_aws_dynamodb_table_test.go index af533962902..fa9ae42dac8 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 != 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 { return fmt.Errorf("Provisioned write capacity was %d, not 2!", table.ProvisionedThroughput.WriteCapacityUnits) } @@ -1066,6 +1097,39 @@ 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 == 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 + } +} + func testAccCheckDynamoDbTableWasUpdated(n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -1186,6 +1250,21 @@ 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" + } +} +`, rName) +} + func testAccAWSDynamoDbConfigInitialState(rName string) string { return fmt.Sprintf(` resource "aws_dynamodb_table" "basic-dynamodb-table" { diff --git a/aws/structure.go b/aws/structure.go index e322158966e..ec99a849d2c 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) diff --git a/website/docs/r/dynamodb_table.html.markdown b/website/docs/r/dynamodb_table.html.markdown index 713aa3fe7ee..61278e0e063 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