Terraform: How to iterate through a nested list

While trying to refactor a terraform module to be re-usable, I learned about the flatten function. I will share in this article how I use it to iterate through a nested list and make my Terraform module re-usable.

How my Terraform module looks like before refactoring.

Module:

resource "aws_appmesh_gateway_route" "gateway_route" {
for_each = var.routes

name = trimprefix(each.key, "/")
mesh_name = var.mesh_name
virtual_gateway_name = var.virtual_gateway_name
spec {
http_route {
action {
target {
virtual_service {
virtual_service_name = each.value
}
}
}
match {
prefix = each.key
}
}
}
}

Variables:

virtual_gateway_name = "backend"mesh_name = "development"routes = {
"/north" = "north.dev.svc.cluster.local"
"/south" = "south.dev.svc.cluster.local"
"/east" = "east.dev.svc.cluster.local"
"/west" = "west.dev.svc.cluster.local"
}

I will exclude virtual gateway to simplify my explanation.

Based on the module above, I will have a problem creating another set of virtual gateway routes. The module only iterates through a list to get the different virtual gateway routes. To make the module accept other groups of virtual gateway routes, I will need to use a nested list and refactor the module.

How my Terraform module looks like after refactoring.

Module:

locals {
nestedlist = flatten([
for k, v in var.route : [
for n, s in v : [
{
key = k,
name = n,
svc_url = s
}
]
]
])
}
resource "aws_appmesh_gateway_route" "gateway_route" {
for_each = { for o in local.nestedlist : o.name => o }

name = trimprefix(each.value.name, "/")
mesh_name = var.mesh_name
virtual_gateway_name = var.virtual_gateway_name
spec {
http_route {
action {
target {
virtual_service {
virtual_service_name = each.value.svc_url
}
}
}
match {
prefix = each.value.name
}
}
}
}

Variables:

virtual_gateway_name = "backend"mesh_name = "development"route = {
"region" = {
"/north" = "north.dev.svc.cluster.local"
"/south" = "south.dev.svc.cluster.local"
"/east" = "east.dev.svc.cluster.local"
"/west" = "west.dev.svc.cluster.local"
}
"direction" = {
"/up" = "up.dev.svc.cluster.local"
"/down" = "down.dev.svc.cluster.local"
"/right" = "right.dev.svc.cluster.local"
"/left" = "left.dev.svc.cluster.local"
}
}

The example uses flatten function to flatten the lists and nested lists recursively to get the prefix name and service URL. This way, the terraform module is now refactored to create multiple sets of virtual gateway routes. I will be able to add more virtual gateway routes in the future by adding them to the route list.

As shown from the outputs, there will be eight resources created.

Outputs:

Terraform will perform the following actions:# aws_appmesh_gateway_route.gateway_route["/down"] will be created
+ resource "aws_appmesh_gateway_route" "gateway_route" {
+ arn = (known after apply)
+ created_date = (known after apply)
+ id = (known after apply)
+ last_updated_date = (known after apply)
+ mesh_name = "development"
+ mesh_owner = (known after apply)
+ name = "down"
+ resource_owner = (known after apply)
+ tags_all = (known after apply)
+ virtual_gateway_name = "backend"
+ spec {+ http_route {
+ action {
+ target {
+ virtual_service {
+ virtual_service_name = "down.dev.svc.cluster.local"
}
}
}
+ match {
+ prefix = "/down"
}
}
}
}
# aws_appmesh_gateway_route.gateway_route["/east"] will be created
+ resource "aws_appmesh_gateway_route" "gateway_route" {
+ arn = (known after apply)
+ created_date = (known after apply)
+ id = (known after apply)
+ last_updated_date = (known after apply)
+ mesh_name = "development"
+ mesh_owner = (known after apply)
+ name = "east"
+ resource_owner = (known after apply)
+ tags_all = (known after apply)
+ virtual_gateway_name = "backend"
+ spec {+ http_route {
+ action {
+ target {
+ virtual_service {
+ virtual_service_name = "east.dev.svc.cluster.local"
}
}
}
+ match {
+ prefix = "/east"
}
}
}
}
# aws_appmesh_gateway_route.gateway_route["/left"] will be created
+ resource "aws_appmesh_gateway_route" "gateway_route" {
+ arn = (known after apply)
+ created_date = (known after apply)
+ id = (known after apply)
+ last_updated_date = (known after apply)
+ mesh_name = "development"
+ mesh_owner = (known after apply)
+ name = "left"
+ resource_owner = (known after apply)
+ tags_all = (known after apply)
+ virtual_gateway_name = "backend"
+ spec {+ http_route {
+ action {
+ target {
+ virtual_service {
+ virtual_service_name = "left.dev.svc.cluster.local"
}
}
}
+ match {
+ prefix = "/left"
}
}
}
}
# aws_appmesh_gateway_route.gateway_route["/north"] will be created
+ resource "aws_appmesh_gateway_route" "gateway_route" {
+ arn = (known after apply)
+ created_date = (known after apply)
+ id = (known after apply)
+ last_updated_date = (known after apply)
+ mesh_name = "development"
+ mesh_owner = (known after apply)
+ name = "north"
+ resource_owner = (known after apply)
+ tags_all = (known after apply)
+ virtual_gateway_name = "backend"
+ spec {+ http_route {
+ action {
+ target {
+ virtual_service {
+ virtual_service_name = "north.dev.svc.cluster.local"
}
}
}
+ match {
+ prefix = "/north"
}
}
}
}
# aws_appmesh_gateway_route.gateway_route["/right"] will be created
+ resource "aws_appmesh_gateway_route" "gateway_route" {
+ arn = (known after apply)
+ created_date = (known after apply)
+ id = (known after apply)
+ last_updated_date = (known after apply)
+ mesh_name = "development"
+ mesh_owner = (known after apply)
+ name = "right"
+ resource_owner = (known after apply)
+ tags_all = (known after apply)
+ virtual_gateway_name = "backend"
+ spec {+ http_route {
+ action {
+ target {
+ virtual_service {
+ virtual_service_name = "right.dev.svc.cluster.local"
}
}
}
+ match {
+ prefix = "/right"
}
}
}
}
# aws_appmesh_gateway_route.gateway_route["/south"] will be created
+ resource "aws_appmesh_gateway_route" "gateway_route" {
+ arn = (known after apply)
+ created_date = (known after apply)
+ id = (known after apply)
+ last_updated_date = (known after apply)
+ mesh_name = "development"
+ mesh_owner = (known after apply)
+ name = "south"
+ resource_owner = (known after apply)
+ tags_all = (known after apply)
+ virtual_gateway_name = "backend"
+ spec {+ http_route {
+ action {
+ target {
+ virtual_service {
+ virtual_service_name = "south.dev.svc.cluster.local"
}
}
}
+ match {
+ prefix = "/south"
}
}
}
}
# aws_appmesh_gateway_route.gateway_route["/up"] will be created
+ resource "aws_appmesh_gateway_route" "gateway_route" {
+ arn = (known after apply)
+ created_date = (known after apply)
+ id = (known after apply)
+ last_updated_date = (known after apply)
+ mesh_name = "development"
+ mesh_owner = (known after apply)
+ name = "up"
+ resource_owner = (known after apply)
+ tags_all = (known after apply)
+ virtual_gateway_name = "backend"
+ spec {+ http_route {
+ action {
+ target {
+ virtual_service {
+ virtual_service_name = "up.dev.svc.cluster.local"
}
}
}
+ match {
+ prefix = "/up"
}
}
}
}
# aws_appmesh_gateway_route.gateway_route["/west"] will be created
+ resource "aws_appmesh_gateway_route" "gateway_route" {
+ arn = (known after apply)
+ created_date = (known after apply)
+ id = (known after apply)
+ last_updated_date = (known after apply)
+ mesh_name = "development"
+ mesh_owner = (known after apply)
+ name = "west"
+ resource_owner = (known after apply)
+ tags_all = (known after apply)
+ virtual_gateway_name = "backend"
+ spec {+ http_route {
+ action {
+ target {
+ virtual_service {
+ virtual_service_name = "west.dev.svc.cluster.local"
}
}
}
+ match {
+ prefix = "/west"
}
}
}
}
Plan: 8 to add, 0 to change, 0 to destroy.

I hope this article will be helpful and will help you to refactor your Terraform module.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store