Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions .web-docs/components/builder/iso/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,17 @@ JSON Example:

- `sound` (bool) - Enable virtual sound card device. Defaults to `false`.

- `usb` (bool) - Enable USB 2.0 controllers for the virtual machine.
- `usb` (bool) - Enable USB controller for the virtual machine.
Defaults to `false`.

~> **Note:** To enable USB 3.0 controllers, set a `usb_xhci.present`
key to `true` in the `vmx_data` option.
~> **Note:** Automatically enabled on Apple Silicon-based systems to
ensure plugin functionality.

- `usb_version` (string) - USB version to use when USB is enabled. Defaults to "2.0".
Allowed values are "2.0" and "3.1".

~> **Note:** Automatically set on Apple Silicon-based systems to ensure
plugin functionality.

- `serial` (string) - Add a serial port to the virtual machine. Use a format of
`Type:option1,option2,...`. Allowed values for the field `Type` include:
Expand Down
10 changes: 10 additions & 0 deletions builder/vmware/common/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ const (
cdromAdapterSata = "sata"
cdromAdapterScsi = "scsi"

// USB version types.
UsbVersion20 = "2.0"
UsbVersion31 = "3.1"

// Shutdown operation timings.
shutdownPollInterval = 150 * time.Millisecond
shutdownLockTimeout = 120 * time.Second
Expand Down Expand Up @@ -202,6 +206,12 @@ var AllowedCdromAdapterTypes = []string{
cdromAdapterScsi,
}

// AllowedUsbVersions defines the allowed USB versions for a virtual machine.
var AllowedUsbVersions = []string{
UsbVersion20,
UsbVersion31,
}

// The allowed values for the `ToolsUploadFlavor`.
var allowedToolsFlavorValues = []string{
toolsFlavorMacOS,
Expand Down
34 changes: 28 additions & 6 deletions builder/vmware/common/hw_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package common

import (
"fmt"
"log"
"path/filepath"
"runtime"
"slices"
Expand Down Expand Up @@ -45,12 +46,18 @@ type HWConfig struct {
NetworkAdapterType string `mapstructure:"network_adapter_type" required:"false"`
// Enable virtual sound card device. Defaults to `false`.
Sound bool `mapstructure:"sound" required:"false"`
// Enable USB 2.0 controllers for the virtual machine.
// Enable USB controller for the virtual machine.
// Defaults to `false`.
//
// ~> **Note:** To enable USB 3.0 controllers, set a `usb_xhci.present`
// key to `true` in the `vmx_data` option.
// ~> **Note:** Automatically enabled on Apple Silicon-based systems to
// ensure plugin functionality.
USB bool `mapstructure:"usb" required:"false"`
// USB version to use when USB is enabled. Defaults to "2.0".
// Allowed values are "2.0" and "3.1".
//
// ~> **Note:** Automatically set on Apple Silicon-based systems to ensure
// plugin functionality.
USBVersion string `mapstructure:"usb_version" required:"false"`
// Add a serial port to the virtual machine. Use a format of
// `Type:option1,option2,...`. Allowed values for the field `Type` include:
// `FILE`, `DEVICE`, `PIPE`, `AUTO`, or `NONE`.
Expand Down Expand Up @@ -138,13 +145,28 @@ func (c *HWConfig) Prepare(ctx *interpolate.Context) []error {
errs = append(errs, fmt.Errorf("invalid 'network_adapter_type' type specified: %s; must be one of %s", c.NetworkAdapterType, strings.Join(allowedNetworkAdapterTypes, ", ")))
}

// Peripherals
if !c.Sound {
c.Sound = false
}

if !c.USB {
c.USB = false
if c.USB {
if c.USBVersion == "" {
c.USBVersion = UsbVersion20
}

if !slices.Contains(AllowedUsbVersions, c.USBVersion) {
errs = append(errs, fmt.Errorf("invalid 'usb_version' specified: %s; must be one of %s", c.USBVersion, strings.Join(AllowedUsbVersions, ", ")))
}
} else if c.USBVersion != "" {
errs = append(errs, fmt.Errorf("'usb_version' can only be set when 'usb' is 'true'"))
}

// VMware Fusion on Apple Silicon requires USB controllers for the plugin
// to work properly. Auto-enable if not explicitly configured.
if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" && !c.USB && c.USBVersion == "" {
log.Printf("[INFO] Auto-enabling USB 2.0 on Apple Silicon for plugin functionality")
c.USB = true
c.USBVersion = UsbVersion20
}

if c.Parallel == "" {
Expand Down
151 changes: 149 additions & 2 deletions builder/vmware/common/hw_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package common

import (
"runtime"
"strings"
"testing"

Expand Down Expand Up @@ -35,8 +36,20 @@ func TestHWConfigPrepare(t *testing.T) {
t.Errorf("peripheral choice (sound) should be conservative: %t", c.Sound)
}

if c.USB {
t.Errorf("peripheral choice (usb) should be conservative: %t", c.USB)
if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
if !c.USB {
t.Errorf("USB should be automatically enabled on Apple Silicon: %t", c.USB)
}
if c.USBVersion != UsbVersion20 {
t.Errorf("USB version should be automatically set to 2.0 on Apple Silicon: %s", c.USBVersion)
}
} else {
if c.USB {
t.Errorf("peripheral choice (usb) should be conservative: %t", c.USB)
}
if c.USBVersion != "" {
t.Errorf("USB version should not be set when USB is disabled: %s", c.USBVersion)
}
}

if strings.ToUpper(c.Parallel) != "NONE" {
Expand Down Expand Up @@ -327,3 +340,137 @@ func TestHWConfigSerial_None(t *testing.T) {
t.Errorf("serial port shouldn't exist")
}
}

func TestHWConfigUSBValidation_USB2Only(t *testing.T) {
c := new(HWConfig)
c.NetworkAdapterType = "vmxnet3"
c.USB = true
c.USBVersion = UsbVersion20

errs := c.Prepare(interpolate.NewContext())

// USB 2.0 should work on all platforms now, including Apple Silicon
if len(errs) > 0 {
t.Fatalf("err: %#v", errs)
}

if !c.USB {
t.Errorf("USB should be enabled: %t", c.USB)
}

if c.USBVersion != UsbVersion20 {
t.Errorf("USB version should be 2.0: %s", c.USBVersion)
}
}

func TestHWConfigUSBValidation_USB3Only(t *testing.T) {
c := new(HWConfig)
c.NetworkAdapterType = "vmxnet3"
c.USB = true
c.USBVersion = UsbVersion31

if errs := c.Prepare(interpolate.NewContext()); len(errs) > 0 {
t.Fatalf("err: %#v", errs)
}

if !c.USB {
t.Errorf("USB should be enabled: %t", c.USB)
}

if c.USBVersion != UsbVersion31 {
t.Errorf("USB version should be 3.1: %s", c.USBVersion)
}
}

func TestHWConfigUSBValidation_USBVersionDefault(t *testing.T) {
c := new(HWConfig)
c.NetworkAdapterType = "vmxnet3"
c.USB = true
// Don't set USBVersion, should default to 2.0

errs := c.Prepare(interpolate.NewContext())
if len(errs) > 0 {
t.Fatalf("err: %#v", errs)
}

if !c.USB {
t.Errorf("USB should be enabled: %t", c.USB)
}

if c.USBVersion != UsbVersion20 {
t.Errorf("USB version should default to 2.0: %s", c.USBVersion)
}
}

func TestHWConfigUSBValidation_USBDisabled(t *testing.T) {
c := new(HWConfig)
c.NetworkAdapterType = "vmxnet3"

if errs := c.Prepare(interpolate.NewContext()); len(errs) > 0 {
t.Fatalf("err: %#v", errs)
}

if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
if !c.USB {
t.Errorf("USB should be automatically enabled on Apple Silicon: %t", c.USB)
}
if c.USBVersion != UsbVersion20 {
t.Errorf("USB version should be automatically set to 2.0 on Apple Silicon: %s", c.USBVersion)
}
} else {
if c.USB {
t.Errorf("USB should be disabled by default: %t", c.USB)
}
if c.USBVersion != "" {
t.Errorf("USB version should not be set when USB is disabled: %s", c.USBVersion)
}
}
}

func TestHWConfigUSBValidation_InvalidVersion(t *testing.T) {
c := new(HWConfig)
c.NetworkAdapterType = "vmxnet3"
c.USB = true
c.USBVersion = "1.1" // Invalid version.

errs := c.Prepare(interpolate.NewContext())
if len(errs) == 0 {
t.Fatal("expected validation error for invalid USB version")
}

expectedError := "invalid 'usb_version' specified: 1.1; must be one of 2.0, 3.1"
found := false
for _, err := range errs {
if err.Error() == expectedError {
found = true
break
}
}
if !found {
t.Errorf("expected error message not found. Got errors: %v", errs)
}
}

func TestHWConfigUSBValidation_VersionWithoutUSB(t *testing.T) {
c := new(HWConfig)
c.NetworkAdapterType = "vmxnet3"
c.USB = false
c.USBVersion = UsbVersion31 // Set the version, but disabled.

errs := c.Prepare(interpolate.NewContext())
if len(errs) == 0 {
t.Fatal("expected validation error when USB version is set but USB is disabled")
}

expectedError := "'usb_version' can only be set when 'usb' is 'true'"
found := false
for _, err := range errs {
if err.Error() == expectedError {
found = true
break
}
}
if !found {
t.Errorf("expected error message not found. Got errors: %v", errs)
}
}
2 changes: 2 additions & 0 deletions builder/vmware/iso/config.hcl2spec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions builder/vmware/iso/step_create_vmx.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type vmxTemplateData struct {

SoundPresent string
UsbPresent string
UsbVersion string

SerialPresent string
SerialType string
Expand Down Expand Up @@ -175,6 +176,7 @@ func (s *stepCreateVMX) Run(ctx context.Context, state multistep.StateBag) multi

SoundPresent: map[bool]string{true: "TRUE", false: "FALSE"}[config.Sound],
UsbPresent: map[bool]string{true: "TRUE", false: "FALSE"}[config.USB],
UsbVersion: config.USBVersion,

SerialPresent: "FALSE",
ParallelPresent: "FALSE",
Expand Down Expand Up @@ -463,9 +465,6 @@ pciBridge7.pciSlotNumber = "24"
pciBridge7.present = "TRUE"
pciBridge7.virtualDev = "pcieRootPort"

ehci.present = "TRUE"
ehci.pciSlotNumber = "34"

vmci0.present = "TRUE"
vmci0.id = "1861462627"
vmci0.pciSlotNumber = "35"
Expand Down Expand Up @@ -505,9 +504,10 @@ sound.present = "{{ .SoundPresent }}"
sound.fileName = "-1"
sound.autodetect = "TRUE"

// USB
usb.pciSlotNumber = "32"
usb.present = "{{ .UsbPresent }}"
// USB Controllers
{{ if .UsbPresent }}usb.present = "{{ .UsbPresent }}"{{ end }}
{{ if .UsbPresent }}ehci.present = "{{ .UsbPresent }}"{{ end }}
{{ if and .UsbPresent (eq .UsbVersion "3.1") }}usb_xhci.present = "{{ .UsbPresent }}"{{ end }}

// Serial
serial0.present = "{{ .SerialPresent }}"
Expand Down
Loading
Loading