Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ need to move the old `aws_dynamodb_table` resource that is being `destroyed` to
terraform state mv module.dynamodb_table.aws_dynamodb_table.autoscaled module.dynamodb_table.aws_dynamodb_table.autoscaled_ignore_gsi
```

**Warning: AWS-managed warm throughput drift**

When using PAY_PER_REQUEST billing mode, AWS automatically adjusts warm throughput values for tables and GSIs based on usage patterns at no additional cost. This causes Terraform to detect drift even though no intentional changes were made. To ignore these AWS-managed adjustments while still detecting intentional configuration changes, you can enable the `ignore_warm_throughput_changes` setting.

**NOTE**: Setting `ignore_warm_throughput_changes` after the table is already created causes your table to be recreated. In this case, you will need to move the old `aws_dynamodb_table` resource that is being `destroyed` to the new resource that is being `created`. For example:

```
terraform state mv module.dynamodb_table.aws_dynamodb_table.this module.dynamodb_table.aws_dynamodb_table.warm_throughput_ignore
```

## Module wrappers

Users of this Terraform module can create multiple similar resources by using [`for_each` meta-argument within `module` block](https://www.terraform.io/language/meta-arguments/for_each) which became available in Terraform 0.13.
Expand Down
200 changes: 198 additions & 2 deletions main.tf
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
locals {
dynamodb_table_arn = try(aws_dynamodb_table.this[0].arn, aws_dynamodb_table.autoscaled[0].arn, aws_dynamodb_table.autoscaled_gsi_ignore[0].arn, "")
dynamodb_table_arn = try(aws_dynamodb_table.this[0].arn, aws_dynamodb_table.autoscaled[0].arn, aws_dynamodb_table.autoscaled_gsi_ignore[0].arn, aws_dynamodb_table.warm_throughput_ignore[0].arn, "")
}

resource "aws_dynamodb_table" "this" {
count = var.create_table && !var.autoscaling_enabled ? 1 : 0
count = var.create_table && !var.autoscaling_enabled && !var.ignore_warm_throughput_changes ? 1 : 0

name = var.name
billing_mode = var.billing_mode
Expand Down Expand Up @@ -435,6 +435,202 @@ resource "aws_dynamodb_table" "autoscaled_gsi_ignore" {
}
}

resource "aws_dynamodb_table" "warm_throughput_ignore" {
count = var.create_table && !var.autoscaling_enabled && var.ignore_warm_throughput_changes ? 1 : 0

name = var.name
billing_mode = var.billing_mode
hash_key = var.hash_key
range_key = var.range_key
read_capacity = var.read_capacity
write_capacity = var.write_capacity
stream_enabled = var.stream_enabled
stream_view_type = var.stream_view_type
table_class = var.table_class
deletion_protection_enabled = var.deletion_protection_enabled
region = var.region
restore_date_time = var.restore_date_time
restore_source_name = var.restore_source_name
restore_source_table_arn = var.restore_source_table_arn
restore_to_latest_time = var.restore_to_latest_time

ttl {
enabled = var.ttl_enabled
attribute_name = var.ttl_attribute_name
}

point_in_time_recovery {
enabled = var.point_in_time_recovery_enabled
recovery_period_in_days = var.point_in_time_recovery_period_in_days
}

dynamic "attribute" {
for_each = var.attributes

content {
name = attribute.value.name
type = attribute.value.type
}
}

dynamic "local_secondary_index" {
for_each = var.local_secondary_indexes

content {
name = local_secondary_index.value.name
range_key = local_secondary_index.value.range_key
projection_type = local_secondary_index.value.projection_type
non_key_attributes = lookup(local_secondary_index.value, "non_key_attributes", null)
}
}

dynamic "global_secondary_index" {
for_each = var.global_secondary_indexes

content {
name = global_secondary_index.value.name
hash_key = global_secondary_index.value.hash_key
projection_type = global_secondary_index.value.projection_type
range_key = lookup(global_secondary_index.value, "range_key", null)
read_capacity = lookup(global_secondary_index.value, "read_capacity", null)
write_capacity = lookup(global_secondary_index.value, "write_capacity", null)
non_key_attributes = lookup(global_secondary_index.value, "non_key_attributes", null)

dynamic "on_demand_throughput" {
for_each = try([global_secondary_index.value.on_demand_throughput], [])

content {
max_read_request_units = try(on_demand_throughput.value.max_read_request_units, null)
max_write_request_units = try(on_demand_throughput.value.max_write_request_units, null)
}
}

dynamic "warm_throughput" {
for_each = try([global_secondary_index.value.warm_throughput], [])

content {
read_units_per_second = try(warm_throughput.value.read_units_per_second, null)
write_units_per_second = try(warm_throughput.value.write_units_per_second, null)
}
}
}
}

dynamic "replica" {
for_each = var.replica_regions

content {
region_name = replica.value.region_name
kms_key_arn = lookup(replica.value, "kms_key_arn", null)
propagate_tags = lookup(replica.value, "propagate_tags", null)
point_in_time_recovery = lookup(replica.value, "point_in_time_recovery", null)
deletion_protection_enabled = lookup(replica.value, "deletion_protection_enabled", null)
consistency_mode = try(replica.value.consistency_mode, null)
}
}

server_side_encryption {
enabled = var.server_side_encryption_enabled
kms_key_arn = var.server_side_encryption_kms_key_arn
}

dynamic "import_table" {
for_each = length(var.import_table) > 0 ? [var.import_table] : []

content {
input_format = import_table.value.input_format
input_compression_type = try(import_table.value.input_compression_type, null)

dynamic "input_format_options" {
for_each = try([import_table.value.input_format_options], [])

content {

dynamic "csv" {
for_each = try([input_format_options.value.csv], [])

content {
delimiter = try(csv.value.delimiter, null)
header_list = try(csv.value.header_list, null)
}
}
}
}

s3_bucket_source {
bucket = import_table.value.bucket
bucket_owner = try(import_table.value.bucket_owner, null)
key_prefix = try(import_table.value.key_prefix, null)
}
}
}

dynamic "on_demand_throughput" {
for_each = length(var.on_demand_throughput) > 0 ? [var.on_demand_throughput] : []

content {
max_read_request_units = try(on_demand_throughput.value.max_read_request_units, null)
max_write_request_units = try(on_demand_throughput.value.max_write_request_units, null)
}
}

dynamic "warm_throughput" {
for_each = length(var.warm_throughput) > 0 ? [var.warm_throughput] : []

content {
read_units_per_second = try(warm_throughput.value.read_units_per_second, null)
write_units_per_second = try(warm_throughput.value.write_units_per_second, null)
}
}

dynamic "global_table_witness" {
for_each = var.global_table_witness != null ? [var.global_table_witness] : []

content {
region_name = global_table_witness.value.region_name
}
}

tags = merge(
var.tags,
{
"Name" = format("%s", var.name)
},
)

timeouts {
create = lookup(var.timeouts, "create", null)
delete = lookup(var.timeouts, "delete", null)
update = lookup(var.timeouts, "update", null)
}

lifecycle {
ignore_changes = [
global_secondary_index[0].warm_throughput,
global_secondary_index[1].warm_throughput,
global_secondary_index[2].warm_throughput,
global_secondary_index[3].warm_throughput,
global_secondary_index[4].warm_throughput,
global_secondary_index[5].warm_throughput,
global_secondary_index[6].warm_throughput,
global_secondary_index[7].warm_throughput,
global_secondary_index[8].warm_throughput,
global_secondary_index[9].warm_throughput,
global_secondary_index[10].warm_throughput,
global_secondary_index[11].warm_throughput,
global_secondary_index[12].warm_throughput,
global_secondary_index[13].warm_throughput,
global_secondary_index[14].warm_throughput,
global_secondary_index[15].warm_throughput,
global_secondary_index[16].warm_throughput,
global_secondary_index[17].warm_throughput,
global_secondary_index[18].warm_throughput,
global_secondary_index[19].warm_throughput,
warm_throughput
]
}
}

resource "aws_dynamodb_resource_policy" "this" {
count = var.create_table && var.resource_policy != null ? 1 : 0

Expand Down
6 changes: 6 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,12 @@ variable "ignore_changes_global_secondary_index" {
default = false
}

variable "ignore_warm_throughput_changes" {
description = "Whether to ignore changes to warm_throughput in global secondary indices and table-level warm_throughput. Useful when AWS automatically adjusts these values based on usage patterns in PAY_PER_REQUEST mode"
type = bool
default = false
}

variable "on_demand_throughput" {
description = "Sets the maximum number of read and write units for the specified on-demand table"
type = any
Expand Down
Loading