Using variable value as part of resources name to get value

I am trying to use values set in a variable to obtain values from modules, locals, etc so I can iterate using a module. For example, here is some interesting bits of code I am working on:

variables.tfvars:

windows_VMs = {

  bernarddevvm = {

    serverType                    = "SWJ"

    userDefinedString             = "maltdev"

    postfix                       = "01"

    resource_group                = "Management"

    subnet                        = "MAZ"

    private_ip_address_allocation = "Static"

    admin_username                = "myadmin"

    admin_password                = "gvdsft45wt4w5"

    os_managed_disk_type          = "StandardSSD_LRS"

    vm_size                       = "Standard_D4s_v3"

    priority                      = "Spot"

  }
}

terraform code:

locals {
  bernarddevvm  = cidrhost(local.subnets.MAZ.address_prefix, 4)
}

 module "windows_servers" {
  for_each = var.windows_VMs

  source            = "github.com/canada-ca-terraform-modules/terraform-azurerm-caf-windows_virtual_machine?ref=v1.1.0"
  env               = var.env
  serverType        = each.value.serverType
  userDefinedString = each.value.userDefinedString
  postfix           = each.value.postfix
  resource_group    = local.resource_groups_L1."${each.value.resource_group}"
  location          = local.resource_groups_L1."${each.value.resource_group.location}"
  subnet            = module.Project-snet.object."${each.value.subnet}"
  nic_ip_configuration = {
private_ip_address            = [local."${each.key}"]
private_ip_address_allocation = ["${each.value.private_ip_address_allocation}"]
  }
  priority        = lookup(each.value, priority, "Regular")
  admin_username  = lookup(each.value, admin_username, "azureadmin")
  admin_password  = each.value.admin_password
  vm_size         = each.value.vm_size
  license_type    = "Windows_Server"
  dependancyAgent = lookup(each.value, dependancyAgent, false)
  tags            = var.tags
}

But I get a bunch of errors like:

Error: Invalid attribute name

  on 60-SRV-Windows.tf line 9, in module "windows_servers":
   9:   resource_group    = local.resource_groups_L1."${each.value.resource_group}"

An attribute name is required after a dot.

Is this possible in terraform? I use v0.13.

Hi @bernardmaltais,

The top-level Terraform objects are all separate declarations that are represented separately in Terraform’s dependency graph, so it isn’t possible to select them dynamically: that would prevent Terraform from constructing the dependency graph, because it wouldn’t be able to determine what object you’re actually accessing.

However, you can define a single local value that is itself a map, and then look up elements of that map dynamically. For example:

locals {
  vm_ip_addresses = {
    bernarddevvm = cidrhost(local.subnets.MAZ.address_prefix, 4)
  }
}

module "windows_servers" {
  for_each = var.windows_VMs

  # ...
  nic_ip_configuration = {
    private_ip_address = [local.vm_ip_addresses[each.key]]
    # ...
  }
  # ...
}

Note that an important consequence of this is that all of the keys in var.windows_VMs must also be present in local.vm_ip_addresses, or you’ll get an element lookup error. Perhaps that’s not a problem for your use-case, but I wanted to point it out just in case.

Elsewhere in your example you’ve used similar syntax to try to access attributes of objects, like in local.resource_groups_L1."${each.value.resource_group}". This is possible, because it refers statically to local.resource_groups_L1 like in the local.vm_ip_addresses example I showed above, but the correct way to write it is with square brackets like I showed above:

  resource_group    = local.resource_groups_L1[each.value.resource_group]

Notice that we’re not simply pasting a block of text into the configuration by interplation, using ${ .. }, as you might be familiar with in a shell script or a template language. The ${ ... } syntax in Terraform is only for constructing string values from string variables, and isn’t valid in any other context.

Thank you @apparentlymart. I will give this a try. My goal is to declare all VM parameters in a variable and use the new for_each module feature to create all required VM using a single module resource. Should reduce the amount of TF coding and move things to variable. I want to do this such that if the dev env requires fewer VMs than, say, prod, I just need to modify the variable file for that environment. That way I use the exact same terraform code for all env but can end up with different VMs, or other resources for that matter.

Here is my final code for reference. I removed the need for the local IP to make it a pure variable driven deployment:

module "windows_VMs" {

  source   = "github.com/canada-ca-terraform-modules/terraform-azurerm-caf-windows_virtual_machine?ref=v1.1.0"

  for_each = var.windows_VMs

  env               = var.env

  serverType        = each.value.serverType

  userDefinedString = each.value.userDefinedString

  postfix           = each.value.postfix

  resource_group    = local.resource_groups_L1[each.value.resource_group]

  location          = local.resource_groups_L1[each.value.resource_group].location

  subnet            = local.subnets[each.value.subnet]

  nic_ip_configuration = {

    private_ip_address            = [lookup(each.value, "private_ip_address_host", "Dynamic") == "Dynamic" ? null : cidrhost(local.subnets[each.value.subnet].address_prefix, each.value.private_ip_address_host)] # Test if private ip host is provided. If yes assign value. If no set to null

    private_ip_address_allocation = [lookup(each.value, "private_ip_address_host", "Dynamic") == "Dynamic" ? "Dynamic" : "Static"] # Test if private ip host is provided. If yes assign Static. If no set to Dynamic

  }

  public_ip       = lookup(each.value, "public_ip", false)

  priority        = lookup(each.value, "priority", "Regular")

  admin_username  = lookup(each.value, "admin_username", "azureadmin")

  admin_password  = each.value.admin_password

  vm_size         = each.value.vm_size

  license_type    = lookup(each.value, "license_type", "Windows_Server")

  dependancyAgent = lookup(each.value, "dependancyAgent", false)

  shutdownConfig  = lookup(each.value, "shutdownConfig", null)

  tags            = lookup(each.value, "tags", null) == null ? var.tags : merge(var.tags, each.value.tags)

}

And the var file:

SWJ-maltdev01 = {

    serverType           = "SWJ"

    userDefinedString    = "maltdev"

    postfix              = "01"

    resource_group       = "Management"

    subnet               = "MAZ"

    public_ip            = true

    private_ip_address_host   = 5

    admin_username       = "myadmin"

    admin_password       = "345dfg54"

    os_managed_disk_type = "StandardSSD_LRS"

    vm_size              = "Standard_D4s_v3"

    priority             = "Spot"

    shutdownConfig = {

      autoShutdownStatus             = "Enabled"

      autoShutdownTime               = "17:00"

      autoShutdownTimeZone           = "Eastern Standard Time"

      autoShutdownNotificationStatus = "Disabled"

    }

  }