Looping through a map, multiple keys?

Fairly new to terraform, but loving it!

I need to create mulitple healthcheck with different configuration each.

for instance:

say i have 2 servers that need to have HC:

ip_address = "1.2.3.4"
hc_name = "server1"
hc_port = "80"

ip_address = "4.3.2.1"
hc_name = "server2"
hc_port = "88"

I want to use only one tf, meaning i would like to loop through the servers, something like:

variable "servers" {
  type = list
  default = [ "server1", "server2" ]
}

variable "server1" {
  type        = map(string)
  default     = {
    ip_address      = "1.2.3.4"
    hc_port = "80"
  }

variable "server2" {
  type        = map(string)
  default     = {
    ip_address      = "4.3.2.1"
    hc_port = "88"
  }

and do something like:

resource "aws_route53_health_check" "prod-hc" {
	for s in servers:
	
    ip_address              = lookup(s,"ip_address")
    type                    = "HTTP"
    port                    = lookup(s,"port")

}

Is this possible at all?

Many thanks!

Hi @davidcsi,

This is the sort of thing we can do with resource for_each, if you’re able to change slightly the shape of your input variables:

variable "servers" {
  type = map(object({
    ip_address = string
    hc_port    = number
  }})
}

resource "aws_route53_health_check" "prod" {
  for_each = var.servers

  ip_address = each.value.ip_address
  type       = "HTTP"
  port       = each.value.hc_port
}

An important aspect of resource for_each is that the keys in the given map become part of the unique address of each instance of that resource. For example, if the caller of the module were to set the value like this:

  servers = {
    server1 = {
      ip_address = "1.2.3.4"
      hc_port    = 80
    }
    server2 = {
      ip_address = "4.3.2.1"
      port       = 88
    }
  }

…then Terraform would see that it should be creating two instances for this resource, with the following addresses:

  • aws_route53_health_check.prod["server1"]
  • aws_route53_health_check.prod["server2"]

Because the key is part of the identifying address, if you change one of the IP addresses or port numbers in future without changing the map keys then Terraform will understand that as requesting an update to one of the existing instances, but if you change the map keys or add new elements with different keys then Terraform will understand that as requesting instances to be added and/or removed.

Ok for the moment this is what’s working for me:

variable "servers" {
    type = list(map(string))
    default = [
        {
            "ip_address" = "1.2.3.4", 
            "hc_port" = "80"
        },
        {
            "ip_address" = "4.3.2.1", 
            "hc_port" = "80"
        }
    ]
}

resource "random_pet" "this" {
    count = 1
}
output "subnet" {
    value = [
        for ip, ip_value in var.servers :
            { ip_address = ip_value }
    ]
}

Many thanks for your help!

Ok that seems to work with outputs… but i’m trying to use that map in a resource like this:

this is how it works on my test:

output "subnets" {
    value = [
        for key, value in var.servers :
            { 
                ip_address = value.ip_address
                healthcheck_name = value.healthcheck_name
            }
    ]
}

variable "servers" {
    type = list(map(string))
    default = [
        {
            ip_address = "1.2.3.4"
            healthcheck_name = "prod-fs-1-failure"
            resource_path = "/alive.html"
            resource_port = "80"
        },
        {
            ip_address = "4.3.2.1"
            healthcheck_name = "prod-fs-2-failure"
            resource_path = "/alive.html"
            resource_port = "80"
        },
        {
            ip_address = "9.8.7.6"
            healthcheck_name = "prod-kam-failure"
            resource_path = "/alive.html"
            resource_port = "8888"            
        }
    ]
}

This outputs:

subnet = [
  {
    "healthcheck_name" = "prod-fs-1-failure"
    "ip_address" = "1.2.3.4"
  },
  {
    "healthcheck_name" = "prod-fs-2-failure"
    "ip_address" = "4.3.2.1"
  },
  {
    "healthcheck_name" = "prod-kam-failure"
    "ip_address" = "9.8.7.6"
  },
]

Which is perfect.

But if i want to do the same on a resource like this:

(same variable map)

resource "aws_route53_health_check" "prod-hc" {

    for key, value in var.servers :
    {
        provider                = aws.use1
        ip_address              = value.ip_address
        type                    = "HTTP"
        resource_path           = value.resource_path
        port                    = value.resource_port
        failure_threshold       = "2"
        request_interval        = "30"
        regions                 = var.hc_regions

        tags = {
            Product	        = "Servers Monitoring"
            Owner           = "mp-devops"
            TagsComponent   = "HeathCheck"
            Env	            = "prod"
            Contact	        = "mp-david"
            Name            = value.healthcheck_name
        }
    }
}

I’m getting:

Error: Invalid block definition

  on main.tf line 32, in resource "aws_route53_health_check" "prod-hc":
  32:     for key, value in var.servers :

Either a quoted string block label or an opening brace ("{") is expected here.

Any ideas why?

Thanks

I finally figured it all out, it’s all on https://github.com/davidcsi/terraform
if anyone is interested.

Thanks!