Typically, when a DevOps engineer provisions a VM in the vSphere IaaS control plane environment, an OVF template includes hard coded details such as basic network configuration. However, you might not know and often cannot assign certain values to the VM's OVF properties, such as such as IPAM provided network data, until after the VM CR is created. With template strings support, you don’t need to know network information in advance. You can use Golang based templating to populate the OVF property values and configure the VM’s network stack.

Procedure

  1. Make sure that for all properties to be configured, your OVF file includes the ovf:userConfigurable="true" entry.
    This entry enables the system to substitute networking value placeholders, such as nameservers and management IP, with real data after the data gets collected.
    Use the following example.
    <Property ovf:key="hostname" ovf:type="string" ovf:userConfigurable="true" ovf:value="ubuntuguest">
          <Description>Specifies the hostname for the appliance</Description>
    </Property>
    <Property ovf:key="nameservers" ovf:type="string" ovf:userConfigurable="true" ovf:value="1.1.1.1, 1.0.0.1">
          <Label>2.2. DNS</Label>
          <Description>A comma-separated list of IP addresses for up to three DNS servers</Description>
    </Property>
    <Property ovf:key="management_ip" ovf:type="string" ovf:userConfigurable="true">
          <Label>2.3. Management IP</Label>
          <Description>The static IP address for the appliance on the Management Port Group in CIDR format (Eg. ip/subnet mask bits). This cannot be DHCP.</Description>
    </Property>
  2. Create the VM YAML file with template strings.
    The template strings for bootstrap resources will collect the data necessary to populate the OVF property values.
    You can use one of the following methods to construct template strings.
    • Use vm-operator-api.

      For more information, see the following page on GitHub: https://github.com/vmware-tanzu/vm-operator/blob/main/api/v1alpha2/virtualmachinetempl_types.go.

      The following is a sample YAML file:
      apiVersion: vmoperator.vmware.com/v1alpha2
      kind: VirtualMachine
      metadata:
        name: template-vm
        namespace: test-ns
        annotations:
          vmoperator.vmware.com/image-supported-check: disable
      spec:
        className: best-effort-xsmall
        imageName: vmi-xxxx0000
        powerState: poweredOn
        storageClass: wcpglobal-storage-profile
        vmMetadata:
          configMapName: template-vm-1
          transport: vAppConfig
        
      ---
      apiVersion: v1
      kind: ConfigMap
      metadata:
        name: template-vm-1
        namespace: test-ns
      data:
        nameservers: "{{ (index .v1alpha2.Net.Nameservers 0) }}"
        hostname: "{{ .v1alpha2.VM.Name }} "                  
        management_ip: "{{ (index (index .v1alpha2.Net.Devices 0).IPAddresses 0) }}"
        management_gateway: "{{ (index .v1alpha2.Net.Devices 0).Gateway4 }}"
    • Use the following functions.
      Function name Signature Description
      V1alpha2_FirstIP func () string Get the first non-loopback IP from first NIC.
      V1alpha2_FirstIPFromNIC func (index int) string Get non-loopback IP address from the ith NIC. If index is out of bound, template string will not be parsed.
      V1alpha2_FormatIP func (IP string, netmask string) string Format an IP address with network length. A netmask can be either the length, for example, /24, or the decimal notation, for example, 255.255.255.0.

      If input netmask is not valid or different from the default mask, it will not be parsed.

      V1alpha2_FormatNameservers func (count int, delimiter string) string Format the first occurred count of nameservers with specific delimiter. A negative number for count means all nameservers.
      V1alpha2_IP func(IP string) string Format a static IP address with default netmask CIDR.

      If IP is not valid, template string will not be parsed.

      V1alpha2_IPsFromNIC func (index int) []string List all IPs from the ith NIC.

      If index is out of bound, template string will not be parsed.

      If you use the functions, the YAML file looks like the following:
      apiVersion: vmoperator.vmware.com/v1alpha2
      kind: VirtualMachine
      metadata:
        name: template-vm
        namespace: test-ns
      spec:
        className: best-effort-xsmall
        imageName: vmi-xxxx0000
        powerState: poweredOn
        storageClass: wcpglobal-storage-profile
        vmMetadata:
          configMapName: template-vm-2
          transport: vAppConfig
        
      ---
      apiVersion: v1
      kind: ConfigMap
      metadata:
        name: template-vm-2
        namespace: test-ns
      data:
        nameservers: "{{ V1alpha2_FormatNameservers 2 \",\" }}"
        hostname: "{{ .v1alpha2.VM.Name }} "                  
        management_ip: "{{ V1alpha2_FormatIP \"192.168.1.10\" \"255.255.255.0\" }}"
        management_gateway: "{{ (index .v1alpha2.Net.Devices 0).Gateway4 }}"
  3. Deploy the VM.
    kubectl apply -f file_name.yaml

What to do next

If the customization fails and the VM does not get an IP address, inspect the VM using the vSphere VM web console. See Troubleshoot VMs Using the vSphere VM Web Console.