diff --git a/CHANGELOG.md b/CHANGELOG.md index 600308d50..826ae31b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # +## 2.10.0 (Not Released) + +FEATURES: + +- `resource/vsphere_virtual_machine`: Adds ability to add `usb_controller` to virtual machine on creation or clone. + [#2280](https://github.com/hashicorp/terraform-provider-vsphere/pull/2280) +- `data/vsphere_virtual_machine`: Adds ability read `usb_controller` on virtual machine; will return `true` or `false` based on the configuration. + [#2280](https://github.com/hashicorp/terraform-provider-vsphere/pull/2280) + ## 2.9.3 (October 8, 2024) BUG FIX: diff --git a/vsphere/data_source_vsphere_virtual_machine.go b/vsphere/data_source_vsphere_virtual_machine.go index 26db5b9b8..dbc04eaaa 100644 --- a/vsphere/data_source_vsphere_virtual_machine.go +++ b/vsphere/data_source_vsphere_virtual_machine.go @@ -165,6 +165,11 @@ func dataSourceVSphereVirtualMachine() *schema.Resource { Computed: true, Description: "Instance UUID of this virtual machine.", }, + "usb_controller": { + Type: schema.TypeBool, + Computed: true, + Description: "Indicates whether a virtual USB controller device is present on the virtual machine.", + }, } // Merge the VirtualMachineConfig structure so that we can include the number of @@ -283,6 +288,16 @@ func dataSourceVSphereVirtualMachineRead(d *schema.ResourceData, meta interface{ return fmt.Errorf("error setting guest IP addresses: %s", err) } } + + var isUSBPresent bool + for _, dev := range props.Config.Hardware.Device { + if _, ok := dev.(*types.VirtualUSBController); ok { + isUSBPresent = true + break + } + } + _ = d.Set("usb_controller", isUSBPresent) + log.Printf("[DEBUG] VM search for %q completed successfully (UUID %q)", name, props.Config.Uuid) return nil } diff --git a/vsphere/resource_vsphere_virtual_machine.go b/vsphere/resource_vsphere_virtual_machine.go index 2e59c5af7..a0ae5a892 100644 --- a/vsphere/resource_vsphere_virtual_machine.go +++ b/vsphere/resource_vsphere_virtual_machine.go @@ -283,6 +283,23 @@ func resourceVSphereVirtualMachine() *schema.Resource { Computed: true, Description: "The power state of the virtual machine.", }, + "usb_controller": { + Type: schema.TypeList, + Optional: true, + Description: "A specification for a USB controller on the virtual machine.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "usb_version": { + Type: schema.TypeString, + Optional: true, + Default: "2.0", + Description: "The version of the USB controller.", + ValidateFunc: validation.StringInSlice([]string{"2.0", "3.1"}, false), + }, + }, + }, + }, + vSphereTagAttributeKey: tagsSchema(), customattribute.ConfigKey: customattribute.ConfigSchema(), } @@ -594,6 +611,16 @@ func resourceVSphereVirtualMachineRead(d *schema.ResourceData, meta interface{}) d.Set("power_state", "suspended") } + // Get USB Controller information + var isUSBPresent bool + for _, dev := range vprops.Config.Hardware.Device { + if _, ok := dev.(*types.VirtualUSBController); ok { + isUSBPresent = true + break + } + } + _ = d.Set("usb_controller", isUSBPresent) + log.Printf("[DEBUG] %s: Read complete", resourceVSphereVirtualMachineIDString(d)) return nil } @@ -708,6 +735,57 @@ func resourceVSphereVirtualMachineUpdate(d *schema.ResourceData, meta interface{ if spec.DeviceChange, err = applyVirtualDevices(d, client, devices); err != nil { return err } + + // Add USB controller + if d.HasChange("usb_controller") { + usb := d.Get("usb_controller").([]interface{}) + if len(usb) == 0 { + return fmt.Errorf("usb_controller is empty") + } + + // Initialize a key counter + keyCounter := -100 + + for _, usbControllerInterface := range usb { + usbController := usbControllerInterface.(map[string]interface{}) + usbVersion := usbController["usb_version"].(string) + + var ehciEnabled *bool + var device types.BaseVirtualDevice + + switch usbVersion { + case "2.0": + enabled := true + ehciEnabled = &enabled + device = &types.VirtualUSBController{ + VirtualController: types.VirtualController{ + VirtualDevice: types.VirtualDevice{ + Key: int32(keyCounter), + }, + }, + EhciEnabled: ehciEnabled, + } + case "3.1": + device = &types.VirtualUSBXHCIController{ + VirtualController: types.VirtualController{ + VirtualDevice: types.VirtualDevice{ + Key: int32(keyCounter), + }, + }, + } + default: + return fmt.Errorf("unsupported USB version: %s", usbVersion) + } + + spec.DeviceChange = append(spec.DeviceChange, &types.VirtualDeviceConfigSpec{ + Operation: types.VirtualDeviceConfigSpecOperationAdd, + Device: device, + }) + + // Increment the key counter for the next device + keyCounter-- + } + } // Only carry out the reconfigure if we actually have a change to process. cv := virtualmachine.GetHardwareVersionNumber(vprops.Config.Version) tv := d.Get("hardware_version").(int) @@ -1366,6 +1444,46 @@ func resourceVSphereVirtualMachineCreateBareStandard( VmPathName: fmt.Sprintf("[%s]", ds.Name()), } + // Add USB controller + if usb, ok := d.GetOk("usb_controller"); ok && len(usb.([]interface{})) > 0 { + for _, usbControllerInterface := range usb.([]interface{}) { + usbController := usbControllerInterface.(map[string]interface{}) + usbVersion := usbController["usb_version"].(string) + + var ehciEnabled *bool + var device types.BaseVirtualDevice + + switch usbVersion { + case "2.0": + enabled := true + ehciEnabled = &enabled + device = &types.VirtualUSBController{ + VirtualController: types.VirtualController{ + VirtualDevice: types.VirtualDevice{ + Key: -1, + }, + }, + EhciEnabled: ehciEnabled, + } + case "3.1": + device = &types.VirtualUSBXHCIController{ + VirtualController: types.VirtualController{ + VirtualDevice: types.VirtualDevice{ + Key: -1, + }, + }, + } + default: + return nil, fmt.Errorf("unsupported USB version: %s", usbVersion) + } + + spec.DeviceChange = append(spec.DeviceChange, &types.VirtualDeviceConfigSpec{ + Operation: types.VirtualDeviceConfigSpecOperationAdd, + Device: device, + }) + } + } + timeout := meta.(*Client).timeout vm, err := virtualmachine.Create(client, fo, spec, pool, hs, timeout) if err != nil { diff --git a/website/docs/d/virtual_machine.html.markdown b/website/docs/d/virtual_machine.html.markdown index a7faf9fa1..438962b3b 100644 --- a/website/docs/d/virtual_machine.html.markdown +++ b/website/docs/d/virtual_machine.html.markdown @@ -159,6 +159,7 @@ The following attributes are exported: the VM is powered off, this value will be blank. * `guest_ip_addresses` - A list of IP addresses as reported by VMware Tools. * `instance_uuid` - The instance UUID of the virtual machine or template. +* `usb_controller` - Indicates whether a virtual USB controller device is present on the virtual machine. ~> **NOTE:** Keep in mind when using the results of `scsi_type` and `network_interface_types`, that the `vsphere_virtual_machine` resource only diff --git a/website/docs/r/virtual_machine.html.markdown b/website/docs/r/virtual_machine.html.markdown index 7aaaee32f..2634cfe47 100644 --- a/website/docs/r/virtual_machine.html.markdown +++ b/website/docs/r/virtual_machine.html.markdown @@ -1518,6 +1518,25 @@ When cloning from a template, there are additional requirements in both the reso You can use the [`vsphere_virtual_machine`][tf-vsphere-virtual-machine-ds] data source, which provides disk attributes, network interface types, SCSI bus types, and the guest ID of the source template, to return this information. See the section on [cloning and customization](#cloning-and-customization) for more information. + +## USB Controller + +When creating a virtual machine or cloning one from a template, you have the option to add a virtual usb controller Module device. + +**Example**: + +```hcl +resource "vsphere_virtual_machine" "vm" { + # ... other configuration ... + usb_controller { + usb_version = "3.1" + } + # ... other configuration ... +} +``` + +~> **NOTE:** Supported versions include 2.0 or 3.0. This setting is only available on new builds and reconfiguration to add a usb controller module; removal is not supported in the provider. + ## Virtual Machine Migration The `vsphere_virtual_machine` resource supports live migration both on the host and storage level. You can migrate the virtual machine to another host, cluster, resource pool, or datastore. You can also migrate or pin a virtual disk to a specific datastore.