Source code for saltext.vcf.clients.vim_ovf
"""Export OVF descriptors via ``OvfManager`` + ``HttpNfcLease``.
Import is intentionally not wrapped here — content library OVF deploy
(`vcenter_content_library.ovf_deploy`) is the modern path for that workflow.
"""
from pathlib import Path
import requests
from pyVmomi import vim
from saltext.vcf.utils import vcenter as vc_rest
from saltext.vcf.utils import vim as soap
def _find_vm(opts, vm_id_or_name, profile=None):
content = soap.content(opts, profile=profile)
container = content.viewManager.CreateContainerView(
content.rootFolder, [vim.VirtualMachine], True
)
try:
for vm in container.view:
if vm_id_or_name in (vm._moId, vm.name): # noqa: SLF001
return vm
finally:
container.Destroy()
raise LookupError(f"VM {vm_id_or_name!r} not found")
[docs]
def descriptor(opts, vm_id_or_name, *, ovf_name=None, description="", profile=None):
"""Generate and return the OVF descriptor XML for *vm_id_or_name*.
Does NOT export VMDKs — use :func:`export` for the full bundle.
"""
vm = _find_vm(opts, vm_id_or_name, profile=profile)
ovf_mgr = soap.content(opts, profile=profile).ovfManager
spec = vim.OvfManager.CreateDescriptorParams()
spec.name = ovf_name or vm.name
spec.description = description
result = ovf_mgr.CreateDescriptor(obj=vm, cdp=spec)
return {"ovf": result.ovfDescriptor, "warnings": list(result.warning or [])}
[docs]
def export(opts, vm_id_or_name, output_dir, *, ovf_name=None, profile=None):
"""Export OVF descriptor + all VMDKs to *output_dir*.
Returns ``{descriptor_path, vmdk_paths}``. The lease must be released
(``Complete()``) when the pull finishes, which this function does for you.
"""
vm = _find_vm(opts, vm_id_or_name, profile=profile)
out = Path(output_dir)
out.mkdir(parents=True, exist_ok=True)
cfg = vc_rest.get_config(opts, profile=profile)
cookie = soap.session_cookie(opts, profile=profile)
headers = {"Cookie": cookie}
lease = vm.ExportVm()
# Wait for lease to become ready.
while lease.state == vim.HttpNfcLease.State.initializing:
pass
if lease.state == vim.HttpNfcLease.State.error:
raise RuntimeError(lease.error.msg if lease.error else "lease error")
try:
vmdk_paths = []
for device in lease.info.deviceUrl or []:
if not device.disk:
continue
target = out / f"{vm.name}-{device.targetId}.vmdk"
with requests.get(
device.url,
headers=headers,
stream=True,
verify=cfg["verify_ssl"],
timeout=3600,
) as resp:
resp.raise_for_status()
with open(target, "wb") as fp:
for chunk in resp.iter_content(chunk_size=1024 * 1024):
if chunk:
fp.write(chunk)
vmdk_paths.append(str(target))
ovf_mgr = soap.content(opts, profile=profile).ovfManager
spec = vim.OvfManager.CreateDescriptorParams()
spec.name = ovf_name or vm.name
spec.ovfFiles = [
vim.OvfManager.OvfFile(
deviceId=device.key,
path=Path(vmdk_paths[i]).name,
size=Path(vmdk_paths[i]).stat().st_size,
)
for i, device in enumerate(d for d in lease.info.deviceUrl or [] if d.disk)
]
descriptor_obj = ovf_mgr.CreateDescriptor(obj=vm, cdp=spec)
descriptor_path = out / f"{vm.name}.ovf"
descriptor_path.write_text(descriptor_obj.ovfDescriptor)
finally:
lease.HttpNfcLeaseComplete()
return {
"descriptor_path": str(descriptor_path),
"vmdk_paths": vmdk_paths,
}