azurelistfor-loopterraform

Azure Terraform Deploying Linux VMs with Managed Disks


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?


Solution

  • 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:

    enter image description here

    enter image description here

    Refer:

    https://developer.hashicorp.com/terraform/language/functions/values

    azurerm_virtual_machine_data_disk_attachment | Resources | hashicorp/azurerm | Terraform | Terraform Registry