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"
}
}