Skip to content

Commit

Permalink
feat:r/vsphere_virtual_machine add usb controller
Browse files Browse the repository at this point in the history
Add the ability to add usb controller on build and modification of virtual machine.

Signed-off-by: Jared Burns <[email protected]>
  • Loading branch information
burnsjared0415 committed Oct 30, 2024
1 parent adf6714 commit 86290f9
Show file tree
Hide file tree
Showing 5 changed files with 268 additions and 0 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# <!-- markdownlint-disable first-line-h1 no-inline-html -->

## 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:
Expand Down
34 changes: 34 additions & 0 deletions vsphere/data_source_vsphere_virtual_machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,20 @@ func dataSourceVSphereVirtualMachine() *schema.Resource {
Computed: true,
Description: "Instance UUID of this virtual machine.",
},
"usb_controller": {
Type: schema.TypeList,
Computed: true,
Description: "List of virtual USB controllers present on the virtual machine, including their versions.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"version": {
Type: schema.TypeString,
Computed: true,
Description: "The version of the USB controller.",
},
},
},
},
}

// Merge the VirtualMachineConfig structure so that we can include the number of
Expand Down Expand Up @@ -283,6 +297,26 @@ func dataSourceVSphereVirtualMachineRead(d *schema.ResourceData, meta interface{
return fmt.Errorf("error setting guest IP addresses: %s", err)
}
}

var usbControllers []map[string]interface{}

for _, dev := range props.Config.Hardware.Device {
switch dev.(type) {
case *types.VirtualUSBController:
usbControllers = append(usbControllers, map[string]interface{}{
"version": "2.x",
})
case *types.VirtualUSBXHCIController:
usbControllers = append(usbControllers, map[string]interface{}{
"version": "3.x",
})
}
}

if err := d.Set("usb_controller", usbControllers); err != nil {
return fmt.Errorf("error setting usb_controller: %s", err)
}

log.Printf("[DEBUG] VM search for %q completed successfully (UUID %q)", name, props.Config.Uuid)
return nil
}
205 changes: 205 additions & 0 deletions vsphere/resource_vsphere_virtual_machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ https://www.terraform.io/docs/commands/taint.html

const questionCheckIntervalSecs = 5

var usbControllerVersions = []string{"2.0", "3.1", "3.2"}

func resourceVSphereVirtualMachine() *schema.Resource {
s := map[string]*schema.Schema{
"resource_pool_id": {
Expand Down Expand Up @@ -283,6 +285,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(usbControllerVersions, false),
},
},
},
},

vSphereTagAttributeKey: tagsSchema(),
customattribute.ConfigKey: customattribute.ConfigSchema(),
}
Expand Down Expand Up @@ -594,6 +613,24 @@ func resourceVSphereVirtualMachineRead(d *schema.ResourceData, meta interface{})
d.Set("power_state", "suspended")
}

usbControllers := d.Get("usb_controller").([]interface{})
var desiredUSBVersions []string
for _, usbController := range usbControllers {
controller := usbController.(map[string]interface{})
desiredUSBVersions = append(desiredUSBVersions, controller["usb_version"].(string))
}

// Check for existing USB controllers
usbControllersState, err := readUSBControllers(vprops.Config, desiredUSBVersions)
if err != nil {
return fmt.Errorf("error reading USB controllers: %s", err)
}

// Set the presence of USB controllers in the resource data
if err := d.Set("usb_controller", usbControllersState); err != nil {
return fmt.Errorf("error setting usb_controller: %s", err)
}

log.Printf("[DEBUG] %s: Read complete", resourceVSphereVirtualMachineIDString(d))
return nil
}
Expand Down Expand Up @@ -708,6 +745,69 @@ func resourceVSphereVirtualMachineUpdate(d *schema.ResourceData, meta interface{
if spec.DeviceChange, err = applyVirtualDevices(d, client, devices); err != nil {
return err
}

// Check for changes and add new USB controllers if allowed
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

// Initialize controller presence flags
usb2ControllerPresent := false
usb3ControllerPresent := false

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":
if usb2ControllerPresent {
return fmt.Errorf("only one USB 2.0 controller can be specified")
}
usb2ControllerPresent = true
enabled := true
ehciEnabled = &enabled
device = &types.VirtualUSBController{
VirtualController: types.VirtualController{
VirtualDevice: types.VirtualDevice{
Key: int32(keyCounter),
},
},
EhciEnabled: ehciEnabled,
}
case "3.1", "3.2":
if usb3ControllerPresent {
return fmt.Errorf("only one USB 3.x controller (3.1 or 3.2) can be specified")
}
usb3ControllerPresent = true
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,
})
log.Printf("[DEBUG] Added USB 3.x controller")
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)
Expand Down Expand Up @@ -1366,6 +1466,56 @@ func resourceVSphereVirtualMachineCreateBareStandard(
VmPathName: fmt.Sprintf("[%s]", ds.Name()),
}

// Add USB controller
if usb, ok := d.GetOk("usb_controller"); ok && len(usb.([]interface{})) > 0 {
var usb2ControllerSpecified bool
var usb3xControllerSpecified bool
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":
if usb2ControllerSpecified {
return nil, fmt.Errorf("only one USB 2.0 controller can be specified")
}
usb2ControllerSpecified = true
enabled := true
ehciEnabled = &enabled
device = &types.VirtualUSBController{
VirtualController: types.VirtualController{
VirtualDevice: types.VirtualDevice{
Key: -1,
},
},
EhciEnabled: ehciEnabled,
}
case "3.1", "3.2":
if usb3xControllerSpecified {
return nil, fmt.Errorf("only one USB 3.x controller (3.1 or 3.2) can be specified")
}
usb3xControllerSpecified = true
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 {
Expand Down Expand Up @@ -2024,3 +2174,58 @@ func NewOvfHelperParamsFromVMResource(d *schema.ResourceData) *ovfdeploy.OvfHelp
}
return ovfParams
}

func readUSBControllers(vprops *types.VirtualMachineConfigInfo, desiredUSBVersions []string) ([]map[string]interface{}, error) {
var usbControllers []map[string]interface{}
var usb2ControllerPresent bool
var usb3ControllerPresent bool

for _, dev := range vprops.Hardware.Device {
switch dev.(type) {
case *types.VirtualUSBController:
if usb2ControllerPresent {
return nil, fmt.Errorf("more than one USB 2.0 controller found")
}
usb2ControllerPresent = true
for _, version := range desiredUSBVersions {
if version == "2.0" {
usbControllers = append(usbControllers, map[string]interface{}{
"usb_version": version,
})
break
}
}
case *types.VirtualUSBXHCIController:
if usb3ControllerPresent {
return nil, fmt.Errorf("more than one USB 3.x controller found")
}
usb3ControllerPresent = true
// Use the specific version from desiredUSBVersions
for _, version := range desiredUSBVersions {
if version == "3.1" || version == "3.2" {
usbControllers = append(usbControllers, map[string]interface{}{
"usb_version": version,
})
break
}
}
default:
log.Printf("[DEBUG] Found other device type: %T", dev)
}
}

// Use the desired USB versions specified in the main.tf
for _, version := range desiredUSBVersions {
if version == "2.0" && !usb2ControllerPresent {
usbControllers = append(usbControllers, map[string]interface{}{
"usb_version": version,
})
} else if (version == "3.1" || version == "3.2") && !usb3ControllerPresent {
usbControllers = append(usbControllers, map[string]interface{}{
"usb_version": version,
})
}
}

return usbControllers, nil
}
1 change: 1 addition & 0 deletions website/docs/d/virtual_machine.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions website/docs/r/virtual_machine.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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 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.1. This setting is only available on new builds and reconfiguration to add a USB controller; 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.
Expand Down

0 comments on commit 86290f9

Please sign in to comment.