Search code examples
terraformvsphereterraform-provider-vsphere

Terraform vSphere foreach iterate problem


I need a help again.

I need to generate network interfaces dynamically. For this purpose i declare interfaces in the vm config like this:

`virtual_machines = {
  "gw" = {
    template = "cent7_template"
    host_name = "gw"
        domain_name = "contoso.com"
    dns_server_list = ["8.8.8.8", "8.8.4.4"]

    num_cpus = 2
    memory = 2048
    interfaces = [
          {
            ipv4_address = "192.168.5.240"
        ipv4_netmask = "24"
            network = "VM Network"
      },
          {
            ipv4_address = "10.10.0.1"
        ipv4_netmask = "24"
            network = "Contoso Lan"
      }
    ]

    gateway = "192.168.5.251"
  }


  "lab" = {
    template = "cent7_template"
    host_name = "lab"
        domain_name = "contoso.com"
    dns_server_list = ["8.8.8.8", "8.8.4.4"]

    num_cpus = 2
    memory = 2048
    interfaces = [
          {
            ipv4_address = "10.10.0.2"
        ipv4_netmask = "24"
            network = "Contoso Lan"
      }
    ]

    gateway = "10.10.0.1"
  }

}`

To iterate through interfaces and generate it as data source i created local:

locals {
  networks = flatten([
    for vm_key, objects in var.virtual_machines : [
      for interface in objects.interfaces : {
          network           = interface.network
          ipv4_address = interface.ipv4_address
          ipv4_netmask  = interface.ipv4_netmask
      }
    ]
  ])
}

And dynamic data source:

data "vsphere_network" "interfaces" {
  for_each      = {for idx, val in local.networks: idx => val}
  name          = each.value.network
  datacenter_id = data.vsphere_datacenter.dc.id
}

But this resource code generates all of the interfaces that are declared in all vm's. For example, my first vm have 2 interfaces(VM Network and Contoso Lan), my second interface have 1 interface Contoso Lan and my code want to create 3 interfaces in summary but i need to create interfaces that are declared for VM.

  dynamic "network_interface" {
        for_each        = {for idx, val in local.networks: idx => val}
        content{
          network_id    = data.vsphere_network.interfaces[network_interface.key].id
        }
  }

Data sources are also generated 3 times.

I need to create only interfaces that are declared for this vm and not for all vms. How can i achieve this?

Thank you for answers!

Resource definition:

resource "vsphere_virtual_machine" "vm" {

  for_each         = var.virtual_machines

  name             = "terra-${each.key}.${each.value.domain_name}"
  resource_pool_id = data.vsphere_host.host.resource_pool_id
  datastore_id     = data.vsphere_datastore.datastore.id

  num_cpus         = each.value.num_cpus
  memory           = each.value.memory
  guest_id         = data.vsphere_virtual_machine.template[each.key].guest_id

  disk {
        label            = "disk0"
        size             = data.vsphere_virtual_machine.template[each.key].disks[0].size
        thin_provisioned = data.vsphere_virtual_machine.template[each.key].disks[0].thin_provisioned
  }

  dynamic "network_interface" {
        for_each        = {for idx, val in local.networks: idx => val}
        content{
          network_id    = data.vsphere_network.interfaces[network_interface.key].id
        }
  }

  clone {
        template_uuid = data.vsphere_virtual_machine.template[each.key].id

        customize {
          linux_options {
                host_name = "${each.key}.${each.value.domain_name}"
                domain    = each.value.domain_name
          }

                  dynamic "network_interface" {
                          for_each        = {for idx, val in local.networks: idx => val}

                          content {
                                ipv4_address = network_interface.value.ipv4_address
                                ipv4_netmask = network_interface.value.ipv4_netmask
                        }
                  }

          ipv4_gateway    = each.value.gateway
        }
  }
}

Locals:

locals {
  networks = flatten([
    for vm_key, objects in var.virtual_machines : [
      for interface in objects.interfaces : {
          network           = interface.network
          ipv4_address = interface.ipv4_address
          ipv4_netmask  = interface.ipv4_netmask
      }
    ]
  ])
}

Solution

  • Its better to flatten your networks this way, as this will give you map, not list, with keys vmname-ipaddress:

     
    locals {
      networks = merge([
        for vm_key, objects in var.virtual_machines : {
          for interface in objects.interfaces : 
            "${vm_key}-${interface.ipv4_address}"  => {   
              network           = interface.network
              ipv4_address = interface.ipv4_address
              ipv4_netmask  = interface.ipv4_netmask
          }
      }]...)
    }
    

    then

    data "vsphere_network" "interfaces" {
      for_each      = local.networks
      name          = each.value.network
      datacenter_id = data.vsphere_datacenter.dc.id
    }
    

    and finally you get correct NI using the key vmname-ipaddress in the loop:

      dynamic "network_interface" {
            for_each        = each.value.interfaces
            content{
              network_id    = data.vsphere_network.interfaces["${each.key}-${network_interface.value.ipv4_address}"].id
            }
      }