I have a Terraform module to create Linux VMs, and it works fine when I only need it to have the main OS Disk.
But I now need to be able to deploy Linux VM(s) with additional managed disk data disk(s), and I'm failing on how to loop to attach the disks that are created.
My module code looks like this:
# VM depends on NIC
# Create network interface first
resource "azurerm_network_interface" "nic" {
for_each = var.virtual_machines
name = each.value.nic_name
location = var.location
resource_group_name = var.resource_group_name
ip_configuration {
name = each.value.ip_configuration.name
subnet_id = each.value.ip_configuration.subnet_id
private_ip_address_allocation = each.value.ip_configuration.private_ip_address_allocation
private_ip_address = each.value.ip_configuration.private_ip_address
}
}
resource "azurerm_linux_virtual_machine" "vm" {
for_each = var.virtual_machines
name = each.value.name
computer_name = each.value.computer_name
location = var.location
resource_group_name = var.resource_group_name
size = var.vm_size
network_interface_ids = [azurerm_network_interface.nic[each.key].id]
availability_set_id = var.availability_set_name != "" ? azurerm_availability_set.avset[0].id : null
admin_username = var.admin_username
admin_ssh_key {
username = var.admin_username
public_key = file("~/.ssh/id_rsa.pub")
}
dynamic "os_disk" {
for_each = {
for index, os_disk in each.value.os_disk : os_disk.name => os_disk
}
content {
name = os_disk.value.name
caching = os_disk.value.caching
storage_account_type = os_disk.value.storage_account_type
disk_size_gb = os_disk.value.disk_size_gb
}
}
dynamic "source_image_reference" {
for_each = {
for index, source_image_reference in each.value.source_image_reference : source_image_reference.publisher => source_image_reference
}
content {
publisher = source_image_reference.value.publisher
offer = source_image_reference.value.offer
sku = source_image_reference.value.sku
version = source_image_reference.value.version
}
}
}
# Optional availability set
resource "azurerm_availability_set" "avset" {
count = var.availability_set_name != "" ? 1 : 0
name = var.availability_set_name
location = var.location
resource_group_name = var.resource_group_name
managed = true
platform_fault_domain_count = 2 # For managed disks this can only be in the range of 1-2
}
# Optional data disk(s)
resource "azurerm_managed_disk" "disk" {
for_each = {
for index, data_disk in var.data_disks : data_disk.name => data_disk
}
name = each.value.name
location = var.location
resource_group_name = var.resource_group_name
create_option = "Empty"
storage_account_type = each.value.storage_account_type
disk_size_gb = each.value.disk_size_gb
}
# Optional data disk attachment to VM
resource "azurerm_virtual_machine_data_disk_attachment" "data_disk_attach" {
for_each = {
for index, data_disk in var.data_disks : data_disk.name => data_disk
}
managed_disk_id = azurerm_managed_disk.disk[each.key].id
virtual_machine_id = azurerm_linux_virtual_machine.vm[each.key].id
lun = each.value.lun
caching = each.value.caching
}
I'm trying to create basing the logic on: "if any data disks have been supplied create them and attach them to the VM you are creating".
The data_disks are supplied as a list of objects:
variable "data_disks" {
type = list(object({
name = string
caching = string
storage_account_type = string
disk_size_gb = number
lun = number
}))
description = "(Optional) Values for any additional data disks"
}
I am testing this using this:
locals {
primary_location = "UK South"
environment = "dev"
rg_name = "rg-temp"
}
module "linux_vm" {
source = "../"
location = local.primary_location
resource_group_name = local.rg_name
vm_size = "Standard_B2ms"
admin_username = "admin " # Default user to create?
admin_password = "RandomPassword000."
availability_set_name = ""
tags = {}
virtual_machines = {
"linux-001" = {
name = "linux-001"
computer_name = "linux-001"
os_disk = [
{
name = "disk-001"
caching = "ReadWrite"
storage_account_type = "StandardSSD_LRS"
disk_size_gb = 128
}
]
source_image_reference = [
{
publisher = "Canonical"
offer = "UbuntuServer"
sku = "20.04-LTS"
version = "latest"
}
]
nic_name = "nic-dev-linux-001"
ip_configuration = {
name = "linux-001"
subnet_id = "/subscriptions/e286703f-8ba4-4a0d-xxxx-xxxxxxxxxxxx/resourceGroups/shared-networks/providers/Microsoft.Network/virtualNetworks/shared-vnet-10/subnets/1-24"
private_ip_address_allocation = "Static"
private_ip_address = "10.10.10.20"
}
}
}
data_disks = [
{
name = "data-disk-001"
caching = "ReadWrite"
storage_account_type = "StandardSSD_LRS"
disk_size_gb = 128
lun = 5
}
]
}
But no matter how I try to get the id for the linux vm it creates to use with the disk attach block I get an error.
Trying [each.key]
virtual_machine_id = azurerm_linux_virtual_machine.vm[each.key].id
Gets:
│ Error: Invalid index
│
│ on ../main.tf line 90, in resource "azurerm_virtual_machine_data_disk_attachment" "data_disk_attach":
│ 90: virtual_machine_id = azurerm_linux_virtual_machine.vm[each.key].id
│ ├────────────────
│ │ azurerm_linux_virtual_machine.vm is object with 1 attribute "linux-001"
│ │ each.key is "data-disk-001"
│
│ The given key does not identify an element in this collection value.
Trying [0]
virtual_machine_id = azurerm_linux_virtual_machine.vm[*].id
Gets:
│ Error: Invalid index
│
│ on ../main.tf line 90, in resource "azurerm_virtual_machine_data_disk_attachment" "data_disk_attach":
│ 90: virtual_machine_id = azurerm_linux_virtual_machine.vm[0].id
│ ├────────────────
│ │ azurerm_linux_virtual_machine.vm is object with 1 attribute "linux-001"
│
│ The given key does not identify an element in this collection value. An object only supports looking up attributes by name, not by numeric index.
╵
And trying [*]
virtual_machine_id = azurerm_linux_virtual_machine.vm[*].id
Gets:
│ Error: Incorrect attribute value type
│
│ on ../main.tf line 90, in resource "azurerm_virtual_machine_data_disk_attachment" "data_disk_attach":
│ 90: virtual_machine_id = azurerm_linux_virtual_machine.vm[*].id
│ ├────────────────
│ │ azurerm_linux_virtual_machine.vm is object with 1 attribute "linux-001"
│
│ Inappropriate value for attribute "virtual_machine_id": string required.
╵
╷
│ Error: Unsupported attribute
│
│ on ../main.tf line 90, in resource "azurerm_virtual_machine_data_disk_attachment" "data_disk_attach":
│ 90: virtual_machine_id = azurerm_linux_virtual_machine.vm[*].id
│
│ This object does not have an attribute named "id".
╵
None of these are finding an 'id' attribute for the Linux VM being created, and some are suggesting the object only has one attribute "linux-001" (which is just the name of the vm being created in this test case).
Am I going at this in completely the wrong way?
Deploying Linux VMs with Managed Disks using terraform
Issue seems to be with the way youre trying to fetch the VM id here you trying to attach a disk to the wrong virtual machine because you're using the disk's name instead of the virtual machine's name by refering it wrongly.
I tried a updated configuration by changing referencing by using value
function.
virtual_machine_id = values(azurerm_linux_virtual_machine.vm)[0].id
which works as per the requirement by refering the correct VM for the data disk attachment.
Configuration:
resource "azurerm_virtual_network" "vnet" {
name = var.vnet_name
location = var.location
resource_group_name = var.resource_group_name
address_space = [var.address_space]
}
resource "azurerm_subnet" "subnet" {
name = var.subnet_name
resource_group_name = var.resource_group_name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = [var.subnet_address_prefix]
}
resource "azurerm_availability_set" "avset" {
count = var.availability_set_name != "" ? 1 : 0
name = var.availability_set_name
location = var.location
resource_group_name = var.resource_group_name
managed = true
platform_fault_domain_count = 2
}
resource "azurerm_network_interface" "nic" {
for_each = var.virtual_machines
name = each.value.nic_name
location = var.location
resource_group_name = var.resource_group_name
ip_configuration {
name = each.value.ip_configuration.name
subnet_id = azurerm_subnet.subnet.id
private_ip_address_allocation = each.value.ip_configuration.private_ip_address_allocation
private_ip_address = each.value.ip_configuration.private_ip_address
}
}
resource "azurerm_linux_virtual_machine" "vm" {
for_each = var.virtual_machines
name = each.value.name
computer_name = each.value.computer_name
location = var.location
resource_group_name = var.resource_group_name
size = var.vm_size
network_interface_ids = [azurerm_network_interface.nic[each.key].id]
availability_set_id = var.availability_set_name != "" ? azurerm_availability_set.avset[0].id : null
admin_username = var.admin_username
admin_password = var.admin_password
disable_password_authentication = false
dynamic "os_disk" {
for_each = {
for index, os_disk in each.value.os_disk : os_disk.name => os_disk
}
content {
name = os_disk.value.name
caching = os_disk.value.caching
storage_account_type = os_disk.value.storage_account_type
disk_size_gb = os_disk.value.disk_size_gb
}
}
dynamic "source_image_reference" {
for_each = {
for index, source_image_reference in each.value.source_image_reference : source_image_reference.publisher => source_image_reference
}
content {
publisher = source_image_reference.value.publisher
offer = source_image_reference.value.offer
sku = source_image_reference.value.sku
version = source_image_reference.value.version
}
}
}
resource "azurerm_managed_disk" "disk" {
for_each = {
for index, data_disk in var.data_disks : data_disk.name => data_disk
}
name = each.value.name
location = var.location
resource_group_name = var.resource_group_name
create_option = "Empty"
storage_account_type = each.value.storage_account_type
disk_size_gb = each.value.disk_size_gb
}
resource "azurerm_virtual_machine_data_disk_attachment" "data_disk_attach" {
for_each = {
for index, data_disk in var.data_disks : data_disk.name => data_disk
}
managed_disk_id = azurerm_managed_disk.disk[each.key].id
virtual_machine_id = values(azurerm_linux_virtual_machine.vm)[0].id
lun = each.value.lun
caching = each.value.caching
}
Deployment:
Refer:
https://developer.hashicorp.com/terraform/language/functions/values