Home > Software engineering >  Loop through map of lists to create DNS records for multiple domains in one resource block
Loop through map of lists to create DNS records for multiple domains in one resource block

Time:11-12

I have several domains and I want to create subdomains with as much DRY as possible. This is the original structure:

variable "domain1" {
  type = list(string)
  default = ["www", "www2"]
}

variable "domain2" {
  type = list(string)
  default = ["www3", "www1"]
}

resource "aws_route53_record" "domain1" {
  for_each = toset(var.domain1)
  
  type = "A"
  name = "${each.key}.domain1.com"
  zone_id = ""
}

resource "aws_route53_record" "domain2" {
  for_each = toset(var.domain2)
  
  type = "A"
  name = "${each.key}.domain2.com"
  zone_id = ""
}

that I want to combine to one variable and one resource block:

variable "subdomains" {
  type = map(list(string))
  default = {
    "domain1.com" = ["www", "www2"]
    "domain2.com" = ["www3", "www1"]
  }
}

resource "aws_route53_record" "domain1" {
  for_each = var.subdomains // make magic happen here...
  
  type = "A"
  name = "${each.subdomain_part}.${each.domain_part}" // ...and here
  zone_id = ""
}

Is there a way to achieve this?

CodePudding user response:

You can flatten your var.subdomains as follows:

locals {
    subdomains_flat = flatten([for domain, subdomains in var.subdomains:
                        [ for subdomain in subdomains:
                            {
                                domain_part = domain
                                subdomain_part = subdomain
                            }
                        ]
                      ])
}

then:

resource "aws_route53_record" "domain1" {
  for_each = {for idx, val in local.subdomains_flat: idx => val }
  
  type = "A"
  name = "${each.value.subdomain_part}.${each.value.domain_part}" 
  zone_id = ""
}

CodePudding user response:

Following up on the comment about a messy state, I would not say messy, but certainly there are some downsides, the index in that answer is numeric, a plan show that the resource ends up:

  # aws_route53_record.domain1["0"] will be created
    resource "aws_route53_record" "domain1" {

  # aws_route53_record.domain1["1"] will be created
    resource "aws_route53_record" "domain1" {

That can create problems when we add or remove subdomains to the list, the order can change and that will cause the resources to be destroyed and recreated, not ideal on route53 records...


Here is another approach that will create a different index in the resource name.
We still use flatten to extract the subdomains but on this case I'm concatenating right away, that local variable is ready for the aws_route53_record resource to consume it.

provider "aws" {
  region = "us-east-2"
}

variable "subdomains" {
  type = map(list(string))
  default = {
    "domain1.com" = ["www", "www2"]
    "domain2.com" = ["www3", "www1"]
  }
}

locals {
  records = flatten([for d, subs in var.subdomains: [for s in subs: "${s}.${d}"]])
}

resource "aws_route53_record" "domain1" {
  for_each = toset(local.records)

  type    = "A"
  name    = each.value
  zone_id = "us-east-1"
}

A terraform plan of that looks like:

Terraform will perform the following actions:

  # aws_route53_record.domain1["www.domain1.com"] will be created
    resource "aws_route53_record" "domain1" {
        allow_overwrite = (known after apply)
        fqdn            = (known after apply)
        id              = (known after apply)
        name            = "www.domain1.com"
        type            = "A"
        zone_id         = "us-east-1"
    }

  # aws_route53_record.domain1["www1.domain2.com"] will be created
    resource "aws_route53_record" "domain1" {
        allow_overwrite = (known after apply)
        fqdn            = (known after apply)
        id              = (known after apply)
        name            = "www1.domain2.com"
        type            = "A"
        zone_id         = "us-east-1"
    }
    ...

  • Related