Add multiple network adapters to vsphere_virtual_machine clone

I’m hoping you’d be able to assist me.

I’ve got a Terraform pipeline set up with the hasicorp/vsphere provider and I’m trying to create a virtual machine with 2 NICs from a tfvars file. I’m trying to make a module out of the vsphere_virtual_machine resource and all I’m getting are errors. I’ll try to provide as much information as I can. I’ve been working on this all week (including the weekend) and it’s making a mockery of me

I’ve got my tfvars file formatted as follows

Terraform\VMWare\NewBuildCluster\vmware_server_build.tfvars

tfvars = {

  # Core vSphere Vars
  vsphere_server            = "vcentre.example.com" # Address of vCentre
  vsphere_datacenter        = "vsphere_datacenter" # Location to build new servers
  vsphere_datastore         = "datastore0001" # Data store to save VM files
  vsphere_cluster           = "NewBuildCluster" # Cluster to build on

  #VM Related Vars
  vm = {
    TestVM01 = {
      vsphere_vm_template             = "WindowsTemplate2022" # Name of template
      vsphere_vm_name                 = "TestVM01" # Name of VM
      vsphere_vm_cpu_cores            = 4 # Total cores
      vsphere_vm_num_cores_per_socket = 2 # Cores per socket
      vsphere_vm_memory               = 8 # RAM assignment (Math conducted in module for MB conversion)

      #VM Customisation Variables
      vm_nics = {
        eth0 = {
          vsphere_vm_network        = "VLAN10" # Name of Virtual Network (defined in vSphere)
          vsphere_vm_domain         = "ad.example.com" # Domain to join server to once built
          vsphere_vm_ip             = "10.200.10.240" # Static assigned IP address
          vsphere_vm_ip_gateway     = "10.200.10.1" # Gateway IP Address
          vsphere_vm_ip_dnslist     = ["10.200.10.10", "10.200.10.30"]  # List of DNS addresses
        },
        eth1 = {
          vsphere_vm_network        = "VLAN10" # Name of Virtual Network (defined in vSphere)
          vsphere_vm_domain         = "ad.example.com" # Domain to join server to once built
          vsphere_vm_ip             = "10.200.10.241" # Static assigned IP address
          vsphere_vm_ip_gateway     = "10.200.10.1" # Gateway IP Address
          vsphere_vm_ip_dnslist     = ["10.200.10.10", "10.200.10.30"]  # List of DNS addresses
        }
      }

      data_disk = {
        disk1 = {
          size_gb = 50 # Data disk size
        },
        disk2 = {
          size_gb = 10 # Data disk size
        }
      }
      
    }
}

And I’ve modulised the vm block using the following main.tf

Pipeline\VMWare\main.tf

terraform {
  backend "azurerm" {}
  required_providers {
    vsphere = {
      source = "hashicorp/vsphere"
    }
  }
}

provider "vsphere" {
  user                 = var.vsphere_user
  password             = var.vsphere_password
  vsphere_server       = var.tfvars.vsphere_server
  allow_unverified_ssl = true
}

data "vsphere_datacenter" "datacenter" {
  name = var.tfvars.vsphere_datacenter
}

data "vsphere_datastore" "datastore" {
  name          = var.tfvars.vsphere_datastore
  datacenter_id = data.vsphere_datacenter.datacenter.id
}

data "vsphere_compute_cluster" "cluster" {
  name          = var.tfvars.vsphere_cluster
  datacenter_id = data.vsphere_datacenter.datacenter.id
}

data "vsphere_virtual_machine" "template" {

  for_each = var.tfvars.vm

  name          = var.tfvars.vm[each.key].vsphere_vm_template
  datacenter_id = data.vsphere_datacenter.datacenter.id
}

data "vsphere_network" "network" {
  for_each = toset(flatten([
    for vm_key, vm_value in var.tfvars.vm : [
      for nic_key, nic_value in vm_value.vm_nics : nic_value.vsphere_vm_network
    ]
  ])) 
  name          = each.value.vsphere_vm_network
}

module "vm" {
  source   = "./../../resource_modules/vsphere_vm"
  for_each = var.tfvars.vm

  resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id
  datastore_id = data.vsphere_datastore.datastore.id
  guest_id = data.vsphere_virtual_machine.template[each.key].guest_id
  scsi_type = data.vsphere_virtual_machine.template[each.key].scsi_type
  network_id = data.vsphere_network.network[each.key].id
  disk_size = "${data.vsphere_virtual_machine.template[each.key].disks.0.size}"
  thin_provisioned = "${data.vsphere_virtual_machine.template[each.key].disks.0.thin_provisioned}"
  template_uuid = data.vsphere_virtual_machine.template[each.key].id
  vm_nics = each.value.vm_nics
}

This is the module I’m working with

resource_modules\vsphere_vm\main.tf
#vm

data "vsphere_datacenter" "datacenter" {
  name = var.datacenter_id
}

data "vsphere_network" "network" {
  # Gives you each NIC object for the VM
  for_each = var.vm_nics

  name          = each.value.vsphere_vm_network
  datacenter_id = data.vsphere_datacenter.datacenter.id
}

resource "vsphere_virtual_machine" "vm" {

  for_each = var.tfvars.vm

  name                  = var.tfvars.vm.vsphere_vm_name
  resource_pool_id      = var.resource_pool_id
  datastore_id          = var.datastore_id
  guest_id              = var.guest_id
  scsi_type             = var.scsi_type

  num_cpus              = var.tfvars.vm[each.key].vsphere_vm_cpu_cores
  num_cores_per_socket  = var.tfvars.vm[each.key].vsphere_vm_num_cores_per_socket
  memory                = var.tfvars.vm[each.key].vsphere_vm_memory * 1024
  firmware              = "efi"

  dynamic network_interface {
    for_each = var.vm_nics
    iterator = template_network
    content {
      network_id   = data.vsphere_network.network.id
     # adapter_type = data.vsphere_virtual_machine.template.network_interface_types[0]
    }
    
  }

  dynamic disk {
    for_each = var.tfvars.vm[each.key].data_disk
    iterator = template_disks
    content {
      label             = length(var.tfvars.vm[each.key].data_disk) > 1 ? "disk-${var.vm.vsphere_vm_name}-0${template_disks.key+1}" : "disk-${var.vm.vsphere_vm_name}-01"
      size              = var.tfvars.vm[each.key].data_disk[template_disks.key].size_gb
      thin_provisioned  = true #data.vsphere_virtual_machine.template[0].disks[template_disks.key].thin_provisioned
    }
  }
 # Additional disks
  dynamic "disk" {
    for_each = var.data_disk
    iterator = terraform_disks
    content {
      label = terraform_disks.key
      size  = lookup(terraform_disks.value, "size_gb", null)
      thin_provisioned  = true
    }
  }
  disk {
    label            = "disk-${var.tfvars.vm[each.key].vsphere_vm_name}-01"
    size             = var.disk_size
    thin_provisioned = var.thin_provisioned
    }
  
  clone {
    template_uuid = var.template_uuid
    customize {
      windows_options {
        computer_name = var.tfvars.vm[each.key].vsphere_vm_name
        join_domain    = var.tfvars.vm[each.key].vsphere_vm_domain
        domain_admin_user = "svc_joindomain@ad.example.com"
        domain_admin_password = "DOM@1N.9455w07d!"
        auto_logon_count = 1
      }
      dynamic "network_interface" {
        for_each  = {for idx, val in var.vm_nics: idx => val}

          content {
                ipv4_address = val.vsphere_vm_ip
                ipv4_netmask = 24 #val.vsphere_vm_ip_netmask
                dns_domain = val.vsphere_vm_domain
                dns_server_list = val.vsphere_vm_ip_dnslist
          }
      }
      ipv4_gateway = var.tfvars.vm[each.key].vsphere_vm_ip_gateway
    }
  }
}

With the above config I’m getting a few error messages which refer to the data “vsphere_network” “network” block on the pipeline\vmware\main.tf file

The output as follows identifies it’s got the correct ID’s for each of the things it needs in

module.vm[“TestVM01”].data.vsphere_datacenter.datacenter: Reading…

data.vsphere_datacenter.datacenter: Reading…

data.vsphere_datacenter.datacenter: Read complete after 0s [id=datacenter-1006]

module.vm[“TestVM01”].data.vsphere_datacenter.datacenter: Read complete after 0s [id=datacenter-1006]

module.vm[“TestVM01”].data.vsphere_network.network[“eth0”]: Reading…

data.vsphere_compute_cluster.cluster: Reading…

module.vm[“TestVM01”].data.vsphere_network.network[“eth1”]: Reading…

data.vsphere_datastore.datastore: Reading…

data.vsphere_virtual_machine.template[“TestVM01”]: Reading…

module.vm[“TestVM01”].data.vsphere_network.network[“eth1”]: Read complete after 0s [id=network-1077]

module.vm[“TestVM01”].data.vsphere_network.network[“eth0”]: Read complete after 0s [id=network-1077]

data.vsphere_datastore.datastore: Read complete after 0s [id=datastore-21006]

data.vsphere_compute_cluster.cluster: Read complete after 0s [id=domain-c6796]

data.vsphere_virtual_machine.template[“TestVM01”]: Read complete after 0s [id=42307c66-93ae-1b78-0653-19b744f169b6]

│ 47: name = each.value.vsphere_vm_network
│ ├────────────────
│ │ each.value is “VLAN10”

│ Can’t access attributes on a primitive-typed value (string).


│ Error: Missing map element

│ on …/…/resource_modules/vsphere_vm/main.tf line 17, in resource “vsphere_virtual_machine” “vm”:
│ 17: for_each = var.tfvars.vm
│ ├────────────────
│ │ var.tfvars is empty map of dynamic

│ This map does not have an element with the key “vm”.

##[error]Terraform command ‘plan’ failed with exit code ‘1’.
##[error]╷
│ Error: Unsupported attribute

│ on main.tf line 47, in data “vsphere_network” “network”:
│ 47: name = each.value.vsphere_vm_network
│ ├────────────────
│ │ each.value is “VLAN10”

│ Can’t access attributes on a primitive-typed value (string).


│ Error: Missing map element

│ on …/…/resource_modules/vsphere_vm/main.tf line 17, in resource “vsphere_virtual_machine” “vm”:
│ 17: for_each = var.tfvars.vm
│ ├────────────────
│ │ var.tfvars is empty map of dynamic

│ This map does not have an element with the key “vm”.