- 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.
122 lines
4.5 KiB
Python
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>"
|
|
)
|