From 7888494efbf4a75620dce1608537801a18da61f7 Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Mon, 8 Jun 2026 04:08:15 -0600 Subject: [PATCH] create_vm: add CD/DVD drive with optional ISO boot Previously created VMs had no CD/DVD drive, so attach_iso failed with 'No CD/DVD drive found' and there was no way to install a guest from an ISO. create_vm now always adds a CD/DVD drive on the default IDE controller. New optional args iso_path/iso_datastore back the drive with an ISO and boot_from_iso puts the CD/DVD first in the boot order, giving MCP clients a complete create-and-install-from-ISO path. --- src/mcvsphere/mixins/vm_lifecycle.py | 58 +++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/src/mcvsphere/mixins/vm_lifecycle.py b/src/mcvsphere/mixins/vm_lifecycle.py index bd78155..b0bfa1a 100644 --- a/src/mcvsphere/mixins/vm_lifecycle.py +++ b/src/mcvsphere/mixins/vm_lifecycle.py @@ -99,7 +99,7 @@ class VMLifecycleMixin(MCPMixin): @mcp_tool( name="create_vm", - description="Create a new virtual machine with specified resources", + description="Create a new virtual machine with specified resources and a CD/DVD drive (optionally booting from an ISO)", annotations=ToolAnnotations(destructiveHint=False, idempotentHint=False), ) def create_vm( @@ -111,8 +111,28 @@ class VMLifecycleMixin(MCPMixin): datastore: str | None = None, network: str | None = None, guest_id: str = "otherGuest64", + iso_path: str | None = None, + iso_datastore: str | None = None, + boot_from_iso: bool = True, ) -> str: - """Create a new virtual machine with specified configuration.""" + """Create a new virtual machine with specified configuration. + + The VM always gets a CD/DVD drive. If ``iso_path`` is given, the drive + is backed by that ISO and connected at power-on; otherwise it is an + empty client device (so ``attach_iso`` can mount media later). + + Args: + name: VM name + cpu: vCPU count + memory_mb: Memory in MB + disk_gb: Primary disk size in GB + datastore: Datastore for the VM (default: largest available) + network: Port group for the NIC (default: configured network) + guest_id: vSphere guest OS identifier + iso_path: ISO path on a datastore (e.g. 'iso/ubuntu.iso') to mount in the CD/DVD drive + iso_datastore: Datastore holding the ISO (default: the VM's datastore) + boot_from_iso: When an ISO is given, put the CD/DVD first in the boot order + """ # Resolve datastore datastore_obj = self.conn.datastore if datastore: @@ -195,6 +215,40 @@ class VMLifecycleMixin(MCPMixin): ) device_specs.append(nic_spec) + # Add a CD/DVD drive on the default IDE controller (vSphere auto-creates + # IDE controller key 200). Backed by an ISO if one was requested, + # otherwise an empty client device so media can be mounted later. + cdrom_spec = vim.vm.device.VirtualDeviceSpec() + cdrom_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add + cdrom_spec.device = vim.vm.device.VirtualCdrom() + cdrom_spec.device.controllerKey = 200 + cdrom_spec.device.unitNumber = 0 + cdrom_spec.device.key = -2 + connectable = vim.vm.device.VirtualDevice.ConnectInfo() + connectable.allowGuestControl = True + connectable.connected = False + + if iso_path: + iso_ds = iso_datastore or datastore_obj.name + backing = vim.vm.device.VirtualCdrom.IsoBackingInfo() + backing.fileName = f"[{iso_ds}] {iso_path}" + connectable.startConnected = True + else: + backing = vim.vm.device.VirtualCdrom.RemotePassthroughBackingInfo() + backing.deviceName = "" + backing.exclusive = False + connectable.startConnected = False + + cdrom_spec.device.backing = backing + cdrom_spec.device.connectable = connectable + device_specs.append(cdrom_spec) + + # Boot from the CD/DVD first when installing from an ISO + if iso_path and boot_from_iso: + vm_spec.bootOptions = vim.vm.BootOptions( + bootOrder=[vim.vm.BootOptions.BootableCdromDevice()] + ) + vm_spec.deviceChange = device_specs # Create VM