diff --git a/napalm_vmware/parse/hosts.py b/napalm_vmware/parse/hosts.py index b14123b..a971a86 100644 --- a/napalm_vmware/parse/hosts.py +++ b/napalm_vmware/parse/hosts.py @@ -11,7 +11,8 @@ from typing import Any from napalm_device_types.models import HypervisorHostDict _CONNECTED = "connected" -_UNMANAGED = {None, "", "0.0.0.0"} +# Addresses vSphere reports for "no managing vCenter"; data, not a bind. +_UNMANAGED = {None, "", "0.0.0.0"} # nosec B104 def _host(host: dict[str, Any], standalone: bool) -> HypervisorHostDict: diff --git a/napalm_vmware/parse/interfaces.py b/napalm_vmware/parse/interfaces.py index fc02514..8721361 100644 --- a/napalm_vmware/parse/interfaces.py +++ b/napalm_vmware/parse/interfaces.py @@ -12,7 +12,8 @@ import ipaddress from typing import Any _DEFAULT_MTU = 1500 -_UNASSIGNED = {"", "0.0.0.0"} +# Addresses vSphere reports for an interface without one; data, not a bind. +_UNASSIGNED = {"", "0.0.0.0"} # nosec B104 def _uplink_mtus(host: dict[str, Any]) -> dict[str, int]: diff --git a/napalm_vmware/provision/image.py b/napalm_vmware/provision/image.py index b9e8bc2..311f8a4 100644 --- a/napalm_vmware/provision/image.py +++ b/napalm_vmware/provision/image.py @@ -12,7 +12,9 @@ import hashlib import json import os import shutil -import subprocess + +# qemu-img is run with a fixed argv and no shell (see below). +import subprocess # nosec B404 import tempfile from collections.abc import Callable from pathlib import Path @@ -46,7 +48,8 @@ def fetch(url: str, dest: Path, timeout: float) -> None: # pragma: no cover - n def convert_to_vmdk(src: Path, dst: Path) -> None: - subprocess.run( + # Fixed argv, no shell: qemu-img from PATH and the cache's own file paths. + subprocess.run( # nosec B603 [ _qemu_img(), "convert", @@ -64,7 +67,8 @@ def convert_to_vmdk(src: Path, dst: Path) -> None: def virtual_size(path: Path) -> int: """The disk size the image describes, in bytes (not the file size).""" - out = subprocess.run( + # Fixed argv, no shell: qemu-img from PATH and the cache's own file path. + out = subprocess.run( # nosec B603 [_qemu_img(), "info", "--output", "json", str(path)], check=True, capture_output=True, diff --git a/napalm_vmware/provision/ovf.py b/napalm_vmware/provision/ovf.py index 971d853..49dd123 100644 --- a/napalm_vmware/provision/ovf.py +++ b/napalm_vmware/provision/ovf.py @@ -11,7 +11,8 @@ VMXNET3. Both drivers are in the mainline kernel; whether every distribution's from __future__ import annotations -from xml.sax.saxutils import escape, quoteattr +# 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" diff --git a/napalm_vmware/provisioning.py b/napalm_vmware/provisioning.py index c9cd65a..b173569 100644 --- a/napalm_vmware/provisioning.py +++ b/napalm_vmware/provisioning.py @@ -252,7 +252,7 @@ class VmwareProvisioningMixin: try: lease.HttpNfcLeaseAbort() except Exception: # noqa: BLE001 - the original error is the one to report - pass + logger.debug("Aborting the import lease failed: ignored", exc_info=True) raise return info["info"]["entity"] @@ -279,13 +279,16 @@ class VmwareProvisioningMixin: changes = _mac_changes(adapters, nics) + _disk_growth(devices, disk_gb, image_size) macs = [ (nic.get("mac") or adapter.macAddress or "").lower() - for adapter, nic in zip(adapters, nics) + for adapter, nic in zip(adapters, nics, strict=True) ] seed = nocloud_iso( user, meta_data(name, f"iid-{uuid.uuid4()}"), network_config( - [(mac, nic.get("dhcp", i == 0)) for i, (mac, nic) in enumerate(zip(macs, nics))] + [ + (mac, nic.get("dhcp", i == 0)) + for i, (mac, nic) in enumerate(zip(macs, nics, strict=True)) + ] ), ) vm_dir = posixpath.dirname(str(props["config.files.vmPathName"]).split("] ", 1)[1]) @@ -365,7 +368,7 @@ class VmwareProvisioningMixin: def _mac_changes(adapters: list[Any], nics: list[NICConfigDict]) -> list[Any]: """Device edits pinning the MACs the caller asked for.""" changes = [] - for adapter, nic in zip(adapters, nics): + for adapter, nic in zip(adapters, nics, strict=True): if not nic.get("mac"): continue adapter.addressType = "manual" diff --git a/tests/test_provisioning.py b/tests/test_provisioning.py index 8d447d4..1bd87da 100644 --- a/tests/test_provisioning.py +++ b/tests/test_provisioning.py @@ -258,6 +258,13 @@ class TestFinishVm: _, spec = self._finish(driver, esxi_data, [{"bridge": "VM Network"}], disk_gb=1) assert not any(type(c.device).__name__.endswith("VirtualDisk") for c in spec.deviceChange) + def test_a_vm_without_an_adapter_per_requested_nic_is_refused(self, driver, esxi_data): + # The OVF asks for one adapter per NIC; a VM that came back with fewer would + # get a seed that leaves a NIC unconfigured. create_vm_from_cloud_init + # discards it on any error from here. + with pytest.raises(ValueError): + self._finish(driver, esxi_data, [{"bridge": "VM Network"}, {"bridge": "VM Network"}]) + class TestDestroyAndStatus: def test_destroy_powers_off_first(self, driver, esxi_data):