Search code examples
terraformvsphereterraform-provider-vsphere

Access an object inside an object in Terraform data block


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'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 my network data block is like this but I can't access the vm_nics as a whole.

Pipeline\VMWare\main.tf

data "vsphere_virtual_machine" "network" {
  for_each = flatten([
    for vm_key, vm_value in var.tfvars.vm : [
      for nic_key, nic_value in vm_value.vm_nics : {
        name         = nic_value.vsphere_vm_network
        datacenter_id = data.vsphere_datacenter.datacenter.id
      }
    ]
  ])

  name          = each.value.name
  datacenter_id = each.value.datacenter_id
}

This has brought me as close as I can get to the variable but I get this error

│ The given "for_each" argument value is unsuitable: the "for_each" argument
│ must be a map, or set of strings, and you have provided a value of type
│ tuple.

Solution

  • I'd advice you to create a child module to handle the creation of one VM and just loop that module instead.

    module "my_module" {
      source = "./modules/my_module"
      # Deploy this module for each configured VM
      for_each = var.tfvars.vm
      # Pass VM confiuration (properties inside the VM object)
      vm_property = each.value.vm_property
      # Pass shared properties (properties not in the VM object)
      shared_property = var.tfvars.shared_property
      # Pass the NIC configuration for this VM only
      vm_nics = each.value.vm_nics
    }
    

    Then in your module you could do this:

    variable "vm_nics" {
      # Can use map(any) but I prefer explicit declaration
      type = map(object({
        # Declare the expected properties in here
      })
    }
    
    data "vsphere_virtual_machine" "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
    }
    
    # Outputs a list of all IDs
    output "ids" {
      value = data.vsphere_virtual_machine.network.*.id
    }
    

    Though I'm a bit confused about this setup, perhaps the vsphere_virtual_machine should be a vsphere_network?

    Side note, if you want to you could limit the module to only getting the NIC IDs for each VM. In that case you don't need to declare the VM property variables, just give it the list of NICs for each VM and then you can access them like:

    # In the main module, for example when creating the VMs
    resource "some_resource" "this" {
      for_each = var.tfvars.vm
      network_interfaces = module.my_module[each.key].ids
    }