Skip to content

Commit 44187e9

Browse files
authored
feat: Added option to ignore changes to GSIs (#72)
1 parent 9b66b76 commit 44187e9

File tree

7 files changed

+129
-21
lines changed

7 files changed

+129
-21
lines changed

README.md

+9
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ There are two separate Terraform resources used for the DynamoDB table: one is f
3535
terraform state mv module.dynamodb_table.aws_dynamodb_table.this module.dynamodb_table.aws_dynamodb_table.autoscaled
3636
```
3737

38+
**Warning: autoscaling with global secondary indexes**
39+
40+
When using an autoscaled provisioned table with GSIs you may find that applying TF changes whilst a GSI is scaled up will reset the capacity, there
41+
is an [open issue for this on the AWS Provider](https://github.com/hashicorp/terraform-provider-aws/issues/671). To get around this issue you can enable
42+
the `ignore_changes_global_secondary_index` setting however, using this setting means that any changes to GSIs will be ignored by Terraform and will
43+
hence have to be applied manually (or via some other automation).
44+
3845
## Module wrappers
3946

4047
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.
@@ -78,6 +85,7 @@ No modules.
7885
| [aws_appautoscaling_target.table_read](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_target) | resource |
7986
| [aws_appautoscaling_target.table_write](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_target) | resource |
8087
| [aws_dynamodb_table.autoscaled](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table) | resource |
88+
| [aws_dynamodb_table.autoscaled_gsi_ignore](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table) | resource |
8189
| [aws_dynamodb_table.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table) | resource |
8290

8391
## Inputs
@@ -95,6 +103,7 @@ No modules.
95103
| <a name="input_deletion_protection_enabled"></a> [deletion\_protection\_enabled](#input\_deletion\_protection\_enabled) | Enables deletion protection for table | `bool` | `null` | no |
96104
| <a name="input_global_secondary_indexes"></a> [global\_secondary\_indexes](#input\_global\_secondary\_indexes) | Describe a GSI for the table; subject to the normal limits on the number of GSIs, projected attributes, etc. | `any` | `[]` | no |
97105
| <a name="input_hash_key"></a> [hash\_key](#input\_hash\_key) | The attribute to use as the hash (partition) key. Must also be defined as an attribute | `string` | `null` | no |
106+
| <a name="input_ignore_changes_global_secondary_index"></a> [ignore\_changes\_global\_secondary\_index](#input\_ignore\_changes\_global\_secondary\_index) | Whether to ignore changes lifecycle to global secondary indices, useful for provisioned tables with scaling | `bool` | `false` | no |
98107
| <a name="input_local_secondary_indexes"></a> [local\_secondary\_indexes](#input\_local\_secondary\_indexes) | 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. | `any` | `[]` | no |
99108
| <a name="input_name"></a> [name](#input\_name) | Name of the DynamoDB table | `string` | `null` | no |
100109
| <a name="input_point_in_time_recovery_enabled"></a> [point\_in\_time\_recovery\_enabled](#input\_point\_in\_time\_recovery\_enabled) | Whether to enable point-in-time recovery | `bool` | `false` | no |

autoscaling.tf

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ resource "aws_appautoscaling_target" "table_read" {
33

44
max_capacity = var.autoscaling_read["max_capacity"]
55
min_capacity = var.read_capacity
6-
resource_id = "table/${aws_dynamodb_table.autoscaled[0].name}"
6+
resource_id = "table/${try(aws_dynamodb_table.autoscaled[0].name, aws_dynamodb_table.autoscaled_gsi_ignore[0].name)}"
77
scalable_dimension = "dynamodb:table:ReadCapacityUnits"
88
service_namespace = "dynamodb"
99
}
@@ -33,7 +33,7 @@ resource "aws_appautoscaling_target" "table_write" {
3333

3434
max_capacity = var.autoscaling_write["max_capacity"]
3535
min_capacity = var.write_capacity
36-
resource_id = "table/${aws_dynamodb_table.autoscaled[0].name}"
36+
resource_id = "table/${try(aws_dynamodb_table.autoscaled[0].name, aws_dynamodb_table.autoscaled_gsi_ignore[0].name)}"
3737
scalable_dimension = "dynamodb:table:WriteCapacityUnits"
3838
service_namespace = "dynamodb"
3939
}
@@ -63,7 +63,7 @@ resource "aws_appautoscaling_target" "index_read" {
6363

6464
max_capacity = each.value["read_max_capacity"]
6565
min_capacity = each.value["read_min_capacity"]
66-
resource_id = "table/${aws_dynamodb_table.autoscaled[0].name}/index/${each.key}"
66+
resource_id = "table/${try(aws_dynamodb_table.autoscaled[0].name, aws_dynamodb_table.autoscaled_gsi_ignore[0].name)}/index/${each.key}"
6767
scalable_dimension = "dynamodb:index:ReadCapacityUnits"
6868
service_namespace = "dynamodb"
6969
}
@@ -93,7 +93,7 @@ resource "aws_appautoscaling_target" "index_write" {
9393

9494
max_capacity = each.value["write_max_capacity"]
9595
min_capacity = each.value["write_min_capacity"]
96-
resource_id = "table/${aws_dynamodb_table.autoscaled[0].name}/index/${each.key}"
96+
resource_id = "table/${try(aws_dynamodb_table.autoscaled[0].name, aws_dynamodb_table.autoscaled_gsi_ignore[0].name)}/index/${each.key}"
9797
scalable_dimension = "dynamodb:index:WriteCapacityUnits"
9898
service_namespace = "dynamodb"
9999
}

examples/autoscaling/main.tf

+8-7
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ resource "random_pet" "this" {
99
module "dynamodb_table" {
1010
source = "../../"
1111

12-
name = "my-table-${random_pet.this.id}"
13-
hash_key = "id"
14-
range_key = "title"
15-
billing_mode = "PROVISIONED"
16-
read_capacity = 5
17-
write_capacity = 5
18-
autoscaling_enabled = true
12+
name = "my-table-${random_pet.this.id}"
13+
hash_key = "id"
14+
range_key = "title"
15+
billing_mode = "PROVISIONED"
16+
read_capacity = 5
17+
write_capacity = 5
18+
autoscaling_enabled = true
19+
ignore_changes_global_secondary_index = true
1920

2021
autoscaling_read = {
2122
scale_in_cooldown = 50

main.tf

+92-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ resource "aws_dynamodb_table" "this" {
8686
}
8787

8888
resource "aws_dynamodb_table" "autoscaled" {
89-
count = var.create_table && var.autoscaling_enabled ? 1 : 0
89+
count = var.create_table && var.autoscaling_enabled && !var.ignore_changes_global_secondary_index ? 1 : 0
9090

9191
name = var.name
9292
billing_mode = var.billing_mode
@@ -175,3 +175,94 @@ resource "aws_dynamodb_table" "autoscaled" {
175175
ignore_changes = [read_capacity, write_capacity]
176176
}
177177
}
178+
179+
resource "aws_dynamodb_table" "autoscaled_gsi_ignore" {
180+
count = var.create_table && var.autoscaling_enabled && var.ignore_changes_global_secondary_index ? 1 : 0
181+
182+
name = var.name
183+
billing_mode = var.billing_mode
184+
hash_key = var.hash_key
185+
range_key = var.range_key
186+
read_capacity = var.read_capacity
187+
write_capacity = var.write_capacity
188+
stream_enabled = var.stream_enabled
189+
stream_view_type = var.stream_view_type
190+
table_class = var.table_class
191+
deletion_protection_enabled = var.deletion_protection_enabled
192+
193+
ttl {
194+
enabled = var.ttl_enabled
195+
attribute_name = var.ttl_attribute_name
196+
}
197+
198+
point_in_time_recovery {
199+
enabled = var.point_in_time_recovery_enabled
200+
}
201+
202+
dynamic "attribute" {
203+
for_each = var.attributes
204+
205+
content {
206+
name = attribute.value.name
207+
type = attribute.value.type
208+
}
209+
}
210+
211+
dynamic "local_secondary_index" {
212+
for_each = var.local_secondary_indexes
213+
214+
content {
215+
name = local_secondary_index.value.name
216+
range_key = local_secondary_index.value.range_key
217+
projection_type = local_secondary_index.value.projection_type
218+
non_key_attributes = lookup(local_secondary_index.value, "non_key_attributes", null)
219+
}
220+
}
221+
222+
dynamic "global_secondary_index" {
223+
for_each = var.global_secondary_indexes
224+
225+
content {
226+
name = global_secondary_index.value.name
227+
hash_key = global_secondary_index.value.hash_key
228+
projection_type = global_secondary_index.value.projection_type
229+
range_key = lookup(global_secondary_index.value, "range_key", null)
230+
read_capacity = lookup(global_secondary_index.value, "read_capacity", null)
231+
write_capacity = lookup(global_secondary_index.value, "write_capacity", null)
232+
non_key_attributes = lookup(global_secondary_index.value, "non_key_attributes", null)
233+
}
234+
}
235+
236+
dynamic "replica" {
237+
for_each = var.replica_regions
238+
239+
content {
240+
region_name = replica.value.region_name
241+
kms_key_arn = lookup(replica.value, "kms_key_arn", null)
242+
propagate_tags = lookup(replica.value, "propagate_tags", null)
243+
point_in_time_recovery = lookup(replica.value, "point_in_time_recovery", null)
244+
}
245+
}
246+
247+
server_side_encryption {
248+
enabled = var.server_side_encryption_enabled
249+
kms_key_arn = var.server_side_encryption_kms_key_arn
250+
}
251+
252+
tags = merge(
253+
var.tags,
254+
{
255+
"Name" = format("%s", var.name)
256+
},
257+
)
258+
259+
timeouts {
260+
create = lookup(var.timeouts, "create", null)
261+
delete = lookup(var.timeouts, "delete", null)
262+
update = lookup(var.timeouts, "update", null)
263+
}
264+
265+
lifecycle {
266+
ignore_changes = [global_secondary_index, read_capacity, write_capacity]
267+
}
268+
}

outputs.tf

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
output "dynamodb_table_arn" {
22
description = "ARN of the DynamoDB table"
3-
value = try(aws_dynamodb_table.this[0].arn, aws_dynamodb_table.autoscaled[0].arn, "")
3+
value = try(aws_dynamodb_table.this[0].arn, aws_dynamodb_table.autoscaled[0].arn, aws_dynamodb_table.autoscaled_gsi_ignore[0].arn, "")
44
}
55

66
output "dynamodb_table_id" {
77
description = "ID of the DynamoDB table"
8-
value = try(aws_dynamodb_table.this[0].id, aws_dynamodb_table.autoscaled[0].id, "")
8+
value = try(aws_dynamodb_table.this[0].id, aws_dynamodb_table.autoscaled[0].id, aws_dynamodb_table.autoscaled_gsi_ignore[0].id, "")
99
}
1010

1111
output "dynamodb_table_stream_arn" {
1212
description = "The ARN of the Table Stream. Only available when var.stream_enabled is true"
13-
value = var.stream_enabled ? try(aws_dynamodb_table.this[0].stream_arn, aws_dynamodb_table.autoscaled[0].stream_arn, "") : null
13+
value = var.stream_enabled ? try(aws_dynamodb_table.this[0].stream_arn, aws_dynamodb_table.autoscaled[0].stream_arn, aws_dynamodb_table.autoscaled_gsi_ignore[0].stream_arn, "") : null
1414
}
1515

1616
output "dynamodb_table_stream_label" {
1717
description = "A timestamp, in ISO 8601 format of the Table Stream. Only available when var.stream_enabled is true"
18-
value = var.stream_enabled ? try(aws_dynamodb_table.this[0].stream_label, aws_dynamodb_table.autoscaled[0].stream_label, "") : null
18+
value = var.stream_enabled ? try(aws_dynamodb_table.this[0].stream_label, aws_dynamodb_table.autoscaled[0].stream_label, aws_dynamodb_table.autoscaled_gsi_ignore[0].stream_label, "") : null
1919
}

variables.tf

+6
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,9 @@ variable "deletion_protection_enabled" {
167167
type = bool
168168
default = null
169169
}
170+
171+
variable "ignore_changes_global_secondary_index" {
172+
description = "Whether to ignore changes lifecycle to global secondary indices, useful for provisioned tables with scaling"
173+
type = bool
174+
default = false
175+
}

wrappers/main.tf

+6-5
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ module "wrapper" {
3333
scale_out_cooldown = 0
3434
target_value = 70
3535
})
36-
autoscaling_read = try(each.value.autoscaling_read, var.defaults.autoscaling_read, {})
37-
autoscaling_write = try(each.value.autoscaling_write, var.defaults.autoscaling_write, {})
38-
autoscaling_indexes = try(each.value.autoscaling_indexes, var.defaults.autoscaling_indexes, {})
39-
table_class = try(each.value.table_class, var.defaults.table_class, null)
40-
deletion_protection_enabled = try(each.value.deletion_protection_enabled, var.defaults.deletion_protection_enabled, null)
36+
autoscaling_read = try(each.value.autoscaling_read, var.defaults.autoscaling_read, {})
37+
autoscaling_write = try(each.value.autoscaling_write, var.defaults.autoscaling_write, {})
38+
autoscaling_indexes = try(each.value.autoscaling_indexes, var.defaults.autoscaling_indexes, {})
39+
table_class = try(each.value.table_class, var.defaults.table_class, null)
40+
deletion_protection_enabled = try(each.value.deletion_protection_enabled, var.defaults.deletion_protection_enabled, null)
41+
ignore_changes_global_secondary_index = try(each.value.ignore_changes_global_secondary_index, var.defaults.ignore_changes_global_secondary_index, false)
4142
}

0 commit comments

Comments
 (0)