Files
napalm-vmware/napalm_vmware/provision/ovf.py
T
Christian Manivong 06b446934f fix: what bandit and the remaining lint rules found
- zip() in provisioning is strict (B905): the OVF asks for one adapter
  per requested NIC, so the two lists match. A VM that came back with
  fewer adapters used to get a seed that left a NIC unconfigured; it now
  fails, and create_vm_from_cloud_init discards it as on any other error
  after the import. A test covers it.
- A failed abort of the import lease is logged at debug level instead of
  passed over (B110); the original error is still the one raised.
- nosec for false positives, each with its reason: the 0.0.0.0 entries in
  the parsers are addresses vSphere reports, not binds (B104); qemu-img
  runs with a fixed argv and no shell (B404, B603); xml.sax.saxutils only
  escapes the OVF descriptor the driver writes, nothing is parsed (B406),
  so defusedxml would not apply.
2026-10-23 08:38:41 +02:00

122 lines
4.5 KiB
Python

"""A minimal OVF 1.0 descriptor for importing one converted cloud image.
vSphere builds the VM from this (``OvfManager.CreateImportSpec``) and then
takes the disk contents over NFC. The descriptor only has to describe what
the image does not: CPU, memory, one disk and the NICs.
Devices are the ones VMware recommends for a 64-bit Linux guest -- PVSCSI and
VMXNET3. Both drivers are in the mainline kernel; whether every distribution's
*cloud* kernel carries them is one of the things #305 checks on real hardware.
"""
from __future__ import annotations
# Escaping for the descriptor this module writes; no XML is parsed here.
from xml.sax.saxutils import escape, quoteattr # nosec B406
_HW_VERSION = "vmx-13" # ESXi 6.5 and later
_GUEST_OS = "otherLinux64Guest"
_STREAM_OPTIMIZED = "http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized"
def _item(**elements: str) -> str:
# CIM requires the rasd elements in alphabetical order.
body = "".join(f"<rasd:{k}>{escape(v)}</rasd:{k}>" for k, v in sorted(elements.items()))
return f"<Item>{body}</Item>"
def _nic_items(nic_count: int, first_instance: int) -> str:
return "".join(
_item(
AutomaticAllocation="true",
Connection=f"net{i}",
ElementName=f"Network adapter {i + 1}",
InstanceID=str(first_instance + i),
ResourceSubType="VmxNet3",
ResourceType="10",
)
for i in range(nic_count)
)
def ovf_descriptor(
name: str,
*,
cpu: int,
memory_mb: int,
capacity_bytes: int,
nic_count: int,
firmware: str = "bios",
) -> str:
networks = "".join(
f'<Network ovf:name="net{i}"><Description>net{i}</Description></Network>'
for i in range(nic_count)
)
hardware = "".join(
[
_item(
AllocationUnits="hertz * 10^6",
ElementName=f"{cpu} virtual CPU(s)",
InstanceID="1",
ResourceType="3",
VirtualQuantity=str(cpu),
),
_item(
AllocationUnits="byte * 2^20",
ElementName=f"{memory_mb} MB of memory",
InstanceID="2",
ResourceType="4",
VirtualQuantity=str(memory_mb),
),
_item(
Address="0",
ElementName="SCSI controller 0",
InstanceID="3",
ResourceSubType="VirtualSCSI",
ResourceType="6",
),
_item(
AddressOnParent="0",
ElementName="Hard disk 1",
HostResource="ovf:/disk/vmdisk1",
InstanceID="4",
Parent="3",
ResourceType="17",
),
_nic_items(nic_count, first_instance=5),
]
)
firmware_config = (
'<vmw:Config ovf:required="false" vmw:key="firmware" vmw:value="efi"/>'
if firmware == "efi"
else ""
)
return (
'<?xml version="1.0" encoding="UTF-8"?>'
'<Envelope xmlns="http://schemas.dmtf.org/ovf/envelope/1"'
' xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1"'
' xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData"'
' xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData"'
' xmlns:vmw="http://www.vmware.com/schema/ovf">'
'<References><File ovf:href="disk.vmdk" ovf:id="file1"/></References>'
"<DiskSection><Info>Virtual disks</Info>"
f'<Disk ovf:capacity="{capacity_bytes}" ovf:capacityAllocationUnits="byte"'
f' ovf:diskId="vmdisk1" ovf:fileRef="file1" ovf:format="{_STREAM_OPTIMIZED}"/>'
"</DiskSection>"
f"<NetworkSection><Info>Networks</Info>{networks}</NetworkSection>"
f"<VirtualSystem ovf:id={quoteattr(name)}>"
"<Info>A virtual machine</Info>"
f"<Name>{escape(name)}</Name>"
f'<OperatingSystemSection ovf:id="101" vmw:osType="{_GUEST_OS}"><Info>Guest OS</Info>'
"</OperatingSystemSection>"
"<VirtualHardwareSection><Info>Virtual hardware</Info>"
"<System><vssd:ElementName>Virtual Hardware Family</vssd:ElementName>"
"<vssd:InstanceID>0</vssd:InstanceID>"
f"<vssd:VirtualSystemIdentifier>{escape(name)}</vssd:VirtualSystemIdentifier>"
f"<vssd:VirtualSystemType>{_HW_VERSION}</vssd:VirtualSystemType></System>"
f"{hardware}{firmware_config}"
"</VirtualHardwareSection>"
"</VirtualSystem>"
"</Envelope>"
)