Skip to content

Commit 226f463

Browse files
jurgenwebermilldrclaude
authored
use for_each over count for iteration (#96)
* issue #30, use for_each over count for iteration * fix ifs * fixes for the target * docs: add CHANGELOG.md for 2.0.0 breaking changes * fix: use index-based keys for for_each to support unknown values * docs: update CHANGELOG state migration examples with correct index-based keys * feat: use name attribute as for_each key for authorization_rules and additional_routes - authorization_rules: uses `name` if provided, otherwise falls back to list index - additional_routes: added optional `name` attribute, uses it as key if provided, otherwise falls back to list index - Updated CHANGELOG with improved state migration examples and documentation for the new name attribute 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: update README usage example with optional name attribute 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: milldr <miller0daniel@gmail.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 881500a commit 226f463

File tree

7 files changed

+68
-12
lines changed

7 files changed

+68
-12
lines changed

CHANGELOG.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Changelog
2+
3+
## 2.0.0
4+
5+
### BREAKING CHANGES
6+
7+
This release contains breaking changes that require action when upgrading.
8+
9+
#### Resource iteration changed from `count` to `for_each` (#96)
10+
11+
The following resources now use `for_each` instead of `count`:
12+
- `aws_ec2_client_vpn_network_association.default` - keyed by list index (`"0"`, `"1"`, etc.)
13+
- `aws_ec2_client_vpn_authorization_rule.default` - keyed by `name` attribute if provided, otherwise list index
14+
- `aws_ec2_client_vpn_route.default` - keyed by `name` attribute if provided, otherwise list index
15+
16+
**Impact:** Terraform will see existing resources as needing replacement. To avoid recreation, migrate state before applying:
17+
18+
```bash
19+
# Network associations use index-based keys
20+
terraform state mv 'aws_ec2_client_vpn_network_association.default[0]' 'aws_ec2_client_vpn_network_association.default["0"]'
21+
terraform state mv 'aws_ec2_client_vpn_network_association.default[1]' 'aws_ec2_client_vpn_network_association.default["1"]'
22+
23+
# Authorization rules use the "name" attribute as the key
24+
terraform state mv 'aws_ec2_client_vpn_authorization_rule.default[0]' 'aws_ec2_client_vpn_authorization_rule.default["<name>"]'
25+
26+
# Routes use "name" if provided, otherwise index
27+
terraform state mv 'aws_ec2_client_vpn_route.default[0]' 'aws_ec2_client_vpn_route.default["<name-or-index>"]'
28+
```
29+
30+
#### New optional `name` attribute for `additional_routes`
31+
32+
The `additional_routes` variable now supports an optional `name` attribute. When provided, it is used as the `for_each` key for the route resource. This enables more stable resource addressing.
33+
34+
```hcl
35+
additional_routes = [
36+
{
37+
name = "internet" # Optional: used as for_each key
38+
destination_cidr_block = "0.0.0.0/0"
39+
description = "Internet Route"
40+
target_vpc_subnet_id = "subnet-abc123"
41+
}
42+
]
43+
```
44+
45+
Thanks to @jurgenweber for the contribution!

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ module "ec2_client_vpn" {
105105
106106
additional_routes = [
107107
{
108+
name = "internet"
108109
destination_cidr_block = "0.0.0.0/0"
109110
description = "Internet Route"
110111
target_vpc_subnet_id = element(module.subnets.private_subnet_ids, 0)
@@ -175,7 +176,7 @@ Here is an example of using this module:
175176

176177
| Name | Description | Type | Default | Required |
177178
|------|-------------|------|---------|:--------:|
178-
| <a name="input_additional_routes"></a> [additional\_routes](#input\_additional\_routes) | A list of additional routes that should be attached to the Client VPN endpoint | <pre>list(object({<br/> destination_cidr_block = string<br/> description = string<br/> target_vpc_subnet_id = string<br/> }))</pre> | `[]` | no |
179+
| <a name="input_additional_routes"></a> [additional\_routes](#input\_additional\_routes) | A list of additional routes that should be attached to the Client VPN endpoint | <pre>list(object({<br/> destination_cidr_block = string<br/> description = string<br/> target_vpc_subnet_id = string<br/> name = optional(string)<br/> }))</pre> | `[]` | no |
179180
| <a name="input_additional_security_group_rules"></a> [additional\_security\_group\_rules](#input\_additional\_security\_group\_rules) | A list of Security Group rule objects to add to the created security group, in addition to the ones<br/>this module normally creates. (To suppress the module's rules, set `create_security_group` to false<br/>and supply your own security group via `associated_security_group_ids`.)<br/>The keys and values of the objects are fully compatible with the `aws_security_group_rule` resource, except<br/>for `security_group_id` which will be ignored, and the optional "key" which, if provided, must be unique and known at "plan" time.<br/>To get more info see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule . | `list(any)` | `[]` | no |
180181
| <a name="input_additional_security_groups"></a> [additional\_security\_groups](#input\_additional\_security\_groups) | DEPRECATED: Use `associated_security_group_ids` instead.<br/>List of security groups to attach to the client vpn network associations | `list(string)` | `[]` | no |
181182
| <a name="input_additional_tag_map"></a> [additional\_tag\_map](#input\_additional\_tag\_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.<br/>This is for some rare cases where resources want additional configuration of tags<br/>and therefore take a list of maps with tag key, value, and additional configuration. | `map(string)` | `{}` | no |

README.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ usage: |-
116116
117117
additional_routes = [
118118
{
119+
name = "internet"
119120
destination_cidr_block = "0.0.0.0/0"
120121
description = "Internet Route"
121122
target_vpc_subnet_id = element(module.subnets.private_subnet_ids, 0)

examples/complete/main.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ locals {
1111
destination_cidr_block = route.destination_cidr_block
1212
description = route.description
1313
target_vpc_subnet_id = element(module.subnets.private_subnet_ids, 0)
14+
name = route.name
1415
}]
1516
}
1617

examples/complete/variables.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ variable "additional_routes" {
6161
type = list(object({
6262
destination_cidr_block = string
6363
description = string
64+
name = optional(string)
6465
}))
6566
}
6667

main.tf

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -229,29 +229,35 @@ module "vpn_security_group" {
229229
}
230230

231231
resource "aws_ec2_client_vpn_network_association" "default" {
232-
count = local.enabled ? length(var.associated_subnets) : 0
232+
for_each = {
233+
for k, v in var.associated_subnets : tostring(k) => v if local.enabled
234+
}
233235

234236
client_vpn_endpoint_id = join("", aws_ec2_client_vpn_endpoint.default[*].id)
235-
subnet_id = var.associated_subnets[count.index]
237+
subnet_id = each.value
236238
}
237239

238240
resource "aws_ec2_client_vpn_authorization_rule" "default" {
239-
count = local.enabled ? length(var.authorization_rules) : 0
241+
for_each = {
242+
for k, v in var.authorization_rules : coalesce(lookup(v, "name", null), tostring(k)) => v if local.enabled
243+
}
240244

241-
access_group_id = lookup(var.authorization_rules[count.index], "access_group_id", null)
242-
authorize_all_groups = lookup(var.authorization_rules[count.index], "authorize_all_groups", null)
245+
access_group_id = lookup(each.value, "access_group_id", null)
246+
authorize_all_groups = lookup(each.value, "authorize_all_groups", null)
243247
client_vpn_endpoint_id = join("", aws_ec2_client_vpn_endpoint.default[*].id)
244-
description = var.authorization_rules[count.index].description
245-
target_network_cidr = var.authorization_rules[count.index].target_network_cidr
248+
description = each.value.description
249+
target_network_cidr = each.value.target_network_cidr
246250
}
247251

248252
resource "aws_ec2_client_vpn_route" "default" {
249-
count = local.enabled ? length(var.additional_routes) : 0
253+
for_each = {
254+
for k, v in var.additional_routes : coalesce(v.name, tostring(k)) => v if local.enabled
255+
}
250256

251-
description = try(var.additional_routes[count.index].description, null)
252-
destination_cidr_block = var.additional_routes[count.index].destination_cidr_block
257+
description = lookup(each.value, "description", null)
258+
destination_cidr_block = each.value.destination_cidr_block
253259
client_vpn_endpoint_id = join("", aws_ec2_client_vpn_endpoint.default[*].id)
254-
target_vpc_subnet_id = var.additional_routes[count.index].target_vpc_subnet_id
260+
target_vpc_subnet_id = each.value.target_vpc_subnet_id
255261

256262
depends_on = [
257263
aws_ec2_client_vpn_network_association.default

variables.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ variable "additional_routes" {
7070
destination_cidr_block = string
7171
description = string
7272
target_vpc_subnet_id = string
73+
name = optional(string)
7374
}))
7475
}
7576

0 commit comments

Comments
 (0)