Translate locals + interpolation into variables form

Terraform does not support interpolation in variables, so I have been able to create dns records using reference to a module (e.g. module.eip…) using locals:

locals {

  route53_hz_records = {
    "inc.example.net" = [
      {
        "name"    = "www"
        "type"    = "A"
        "ttl"     = 3600
        "records" = [ module.eip.eip["pubip1"].public_ip ]
      },
    ]
  }

}

How can be refactored this code to be able to use variables (e.g to allow different records configuration base on different environment - specific tfvars file based on env) ?

ps: module "eip" {...} will be created inside same terrafom configuration.

Hi @Roxyrob,

I think I might not be understanding correctly what you want to achieve, but the typical answer to letting something be specified by the calling module is to declare an input variable for it like this:

variable "route53_hz_records" {
  type = map(object({
    name    = string
    type    = string
    tty     = number
    records = list(string)
  }))
}

Each of your per-environment configurations can then call this module using different values for that variable::

module "eip" {

}

module "example" {
  # ...

  route53_hz_records = {
    "inc.example.net" = [
      {
        "name"    = "www"
        "type"    = "A"
        "ttl"     = 3600
        "records" = [ module.eip.eip["pubip1"].public_ip ]
      },
    ]
  }
}

The differences in which records are created per environment would therefore come from the differences between the configurations for each of those environments.

Hi @apparentlymart,
the complete config sample below.

I use terraspace to complement terraform. Terraspace allow to define variables specific for environments (creating a target terraform dir with spec tfvars file for environment and running terraform inside) so I might have different route53_hz_records_persist (and so different number or type of Route53 records) defined per environment in a specific tfvars file (devel.tfvars, prod.tfvars, …) but cannot do that as terraform does not support interpolation in variables (in this case for late subtitution of module.eip…), so I use below configuration (that works), but locals impose same config for every environment.

Not sure if I can overcome this limitation and/or if this is the right way !?

locals {

  ###   ROUTE 53 HOSTED ZONES RECORDS
  route53_hz_records_persist = {
    "inc.example.net" = [
      {
        "name"    = "www"
        "type"    = "A"
        "ttl"     = 3600
        "records" = [
          module.eip.eip["pubip3"].public_ip,
        ]
      },
      {
        "name"    = "service"
        "type"    = "A"
        "ttl"     = 3600
        "records" = [
          module.eip.eip["pubip3"].public_ip,
        ]
      },
      {
        "name"    = "anpr"
        "type"    = "A"
        "ttl"     = 3600
        "records" = [
          module.eip.eip["pubip4"].public_ip,
        ]
      },
    ]
  }
}


module "eip" {
  source = "..."
  ...
}

module "route53_hz_root_records" {
  source = "..."

  for_each = local.route53_hz_records_persist

  zone_name = each.key
  records   = each.value
}

Hi @Roxyrob,

I don’t think there’s any general answer to this because if you want to use arbitrary Terraform language expressions then you need to write your value in the Terraform language, not externally as an input variable.

However, if you can put some more constraints on the problem then you could potentially define an indirect approach for describing the intended shape and then derive the real final data structure, including the appropriate dynamically-chosen values, from that higher-level input.

For example:

variable "dns_records" {
  type = map(set(object({
    name       = string
    ttl        = number
    target_key = string
  })))
}

locals {
  dns_records = {
    for domain, records in var.dns_records : domain => {
      for record in records : {
        name = record.name
        type = "A"
        ttl  = record.ttl
        records = [
          module.eip.eip[record.target_key].public_ip
        ]
      }
    }
  }
}

The above example assumes that all of your records will refer to exactly one of the available elastic IP addresses using its key. This is just an example constraint to show what I mean; even if that’s not an appropriate constraint for your case, hopefully this example serves to illustrate that you can use a static value as a definition for how to construct a dynamic one, and thus allow the overall structure to be defined statically as a variable while the final values will come dynamically from the main configuration.

Thank you @apparentlymart. It’s a good solution for many cases. I’ll try to adapt to my config.

Thank you @apparentlymart,
approach you shown me, with some small corrections

 domain => [

instead of

 domain => {


is ok for my case. I just use eip referenced by name creating from a list like this

pub_ips = ["pubip1", "pubip2", "pubip3", "pubip4", ...]


Please, if you can, explane me better what you mean with your initial sentence I cannot understand completely


... because if you want to use arbitrary Terraform language expressions
then you need to write your value in the Terraform  language, not externally
as an input variable.


And also what about contraint you pointed to (I cannot see as I can choose different pubip using target_key for each record), see code below:


(local variable)

local {
  route53_hz_records_local = {
    for domain, records in var.route53_hz_records : domain => [
      for record in records : {
        name    = record.name
        type    = record.type
        ttl     = record.ttl
        records = [
          module.eip.eip[record.target_key].public_ip
        ]
      }
    ]
  }
}


(variables declaration)

variable "pub_ips" {
  type    = list(string)
  default = []
}

variable "route53_hz_records" {
  type = map(set(object({
    name       = string
    type       = string
    ttl        = number
    target_key = string
  })))
  default = {}
}


(variables definition)

pub_ips = ["pubip1", "pubip2", "pubip3", "pubip4"]

route53_hz_records = {
  "domain1.example.com" = [
    {
      "name"       = "www"
      "type"       = "A"
      "ttl"        = 3600
      "target_key" = "pubip1"
    },
    {
      "name"       = "service1"
      "type"       = "A"
      "ttl"        = 3600
      "target_key" = "pubip3"
    },
    {
      "name"       = "service2"
      "type"       = "A"
      "ttl"        = 3600
      "target_key" = "pubip4"
    },
  ]
}


(eip)

module "eip" {
  source = "..."
  ...
}


(records)

module "route53_hz_root_records" {
  source = "..."

  for_each = {
    for domain, records in local.route53_hz_records_local :
    domain => records
  }

  zone_name = each.key
  records   = each.value
}