On my previous post, we used count
parameter to deploy multiple resources of the same kind. This method may cause some unintended results. Let's take a look at the route table as an example. You can check out my previous post here:

Previous method using count
When we add the routes to the route table using the count
method it works okay at some level.
variable "public-subnets" {
type = list
default = ["20.20.20.0/24" , "30.30.30.0/24" , "40.40.40.0/24" , "50.50.50.0/24"]
}
resource "aws_route" "ner-subnets-public-rtable" {
count = length(var.public-subnets)
route_table_id = aws_route_table.prod-public-rtable.id
destination_cidr_block = element(var.public-subnets , count.index)
gateway_id = aws_internet_gateway.prod-igw.id
}
However, It gives us problems when we want to remove or change the order of the subnets, because, Terraform creates the resources, each with their own state key, which is using the count index number:
Let's change the order and see what happens.
variable "public-subnets" {
type = list
default = ["20.20.20.0/24" , "30.30.30.0/24" , "50.50.50.0/24" , "40.40.40.0/24"]
}
Terraform will perform the following actions:
# aws_route.ner-subnets-public-rtable[2] must be replaced
-/+ resource "aws_route" "ner-subnets-public-rtable" {
~ destination_cidr_block = "40.40.40.0/24" -> "50.50.50.0/24" # forces replacement
+ destination_prefix_list_id = (known after apply)
+ egress_only_gateway_id = (known after apply)
gateway_id = "igw-04c17769351e6f1ee"
~ id = "r-rtb-08349d005a1d578162197524267" -> (known after apply)
+ instance_id = (known after apply)
+ instance_owner_id = (known after apply)
+ local_gateway_id = (known after apply)
+ nat_gateway_id = (known after apply)
+ network_interface_id = (known after apply)
~ origin = "CreateRoute" -> (known after apply)
route_table_id = "rtb-08349d005a1d57816"
~ state = "active" -> (known after apply)
}
# aws_route.ner-subnets-public-rtable[3] must be replaced
-/+ resource "aws_route" "ner-subnets-public-rtable" {
~ destination_cidr_block = "50.50.50.0/24" -> "40.40.40.0/24" # forces replacement
+ destination_prefix_list_id = (known after apply)
+ egress_only_gateway_id = (known after apply)
gateway_id = "igw-04c17769351e6f1ee"
~ id = "r-rtb-08349d005a1d57816383268388" -> (known after apply)
+ instance_id = (known after apply)
+ instance_owner_id = (known after apply)
+ local_gateway_id = (known after apply)
+ nat_gateway_id = (known after apply)
+ network_interface_id = (known after apply)
~ origin = "CreateRoute" -> (known after apply)
route_table_id = "rtb-08349d005a1d57816"
~ state = "active" -> (known after apply)
}
Plan: 2 to add, 0 to change, 2 to destroy.
As you can see on figure-1, Terraform is trying to delete and recreate them with a new state key.
New approach using for_each loop
Let's do something a bit more interesting by using for_each
to dynamically create multiple resources.
To demonstrate this I updated the previous example with the for_each
function.
When we use for_each
in a resource, it expects either a set
or map
so, we can't pass a list
directly. We can however, pass a list
value to toset
to convert it to a set
, which will remove any duplicate elements and discard the ordering of the elements for_each = toset(var.public-subnets)
Terraform will loop over the variable called public-subnets
and set the value to destination_cidr_block
variable "public-subnets" {
type = list
default = ["20.20.20.0/24" , "30.30.30.0/24" , "40.40.40.0/24" , "50.50.50.0/24"]
}
resource "aws_route" "ner-subnets-public-rtable" {
route_table_id = aws_route_table.prod-public-rtable.id
gateway_id = aws_internet_gateway.prod-igw.id
for_each = toset(var.public-subnets)
destination_cidr_block = each.value
}
Let's re-order the subnets and see what happens.
variable "public-subnets" {
type = list
default = ["20.20.20.0/24" , "30.30.30.0/24" , "50.50.50.0/24" , "40.40.40.0/24"]
}
ubuntu@ubuntu:~/terraform_vpc$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
*** OUTPUT OMMITED ***
------------------------------------------------------------------------
No changes. Infrastructure is up-to-date.
This means that Terraform did not detect any differences between your
configuration and real physical resources that exist. As a result, no
actions need to be performed.
As you can see above, Terraform is not going to make any changes to the resources.
Thanks for reading
As always, your feedback and comments are more than welcome
Reference
https://binx.io/blog/2020/06/17/creating-multiple-resources-at-once-with-terraform-for-each/
