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”.
╵