From 789045fc805b94cfdff56f9e2ce451719243d808 Mon Sep 17 00:00:00 2001 From: Ryan Johnson Date: Thu, 18 Jul 2024 22:38:53 -0400 Subject: [PATCH] refactor: consolidate workstation driver - Consolidates `Workstation9Driver` and `Workstation10Driver` to `WorkstationrDriver` within `driver_workstation.go`. - Addresses the deprecation of `syscall.StringToUTF16Ptr` with `windows.UTF16PtrFromString` in `readRegString`. Signed-off-by: Ryan Johnson --- builder/vmware/common/driver.go | 3 +- ..._workstation9.go => driver_workstation.go} | 54 ++++-- builder/vmware/common/driver_workstation10.go | 58 ------ .../common/driver_workstation10_windows.go | 38 ---- .../vmware/common/driver_workstation_unix.go | 8 +- .../common/driver_workstation_unix_test.go | 18 -- ...ndows.go => driver_workstation_windows.go} | 180 +++++++++++------- 7 files changed, 149 insertions(+), 210 deletions(-) rename builder/vmware/common/{driver_workstation9.go => driver_workstation.go} (85%) delete mode 100644 builder/vmware/common/driver_workstation10.go delete mode 100644 builder/vmware/common/driver_workstation10_windows.go delete mode 100644 builder/vmware/common/driver_workstation_unix_test.go rename builder/vmware/common/{driver_workstation9_windows.go => driver_workstation_windows.go} (73%) diff --git a/builder/vmware/common/driver.go b/builder/vmware/common/driver.go index c0a8a5d4..181bc9eb 100644 --- a/builder/vmware/common/driver.go +++ b/builder/vmware/common/driver.go @@ -158,8 +158,7 @@ func NewDriver(dconfig *DriverConfig, config *SSHConfig, vmName string) (Driver, fallthrough case "windows": drivers = []Driver{ - NewWorkstation10Driver(config), - NewWorkstation9Driver(config), + NewWorkstationDriver(config), NewPlayer6Driver(config), NewPlayer5Driver(config), } diff --git a/builder/vmware/common/driver_workstation9.go b/builder/vmware/common/driver_workstation.go similarity index 85% rename from builder/vmware/common/driver_workstation9.go rename to builder/vmware/common/driver_workstation.go index 6f2de250..0397fd73 100644 --- a/builder/vmware/common/driver_workstation9.go +++ b/builder/vmware/common/driver_workstation.go @@ -52,29 +52,45 @@ type NetmapConfig struct { Device string } -// Workstation9Driver is a driver that can run VMware Workstation 9 -type Workstation9Driver struct { +// WorkstationDriver is a driver for VMware Workstation. +type WorkstationDriver struct { VmwareDriver AppPath string VdiskManagerPath string VmrunPath string - // SSHConfig are the SSH settings for the Fusion VM SSHConfig *SSHConfig } -func NewWorkstation9Driver(config *SSHConfig) Driver { - return &Workstation9Driver{ +func NewWorkstationDriver(config *SSHConfig) Driver { + return &WorkstationDriver{ SSHConfig: config, } } -func (d *Workstation9Driver) Clone(dst, src string, linked bool, snapshot string) error { - return errors.New("linked clones are not supported on this version") +func (d *WorkstationDriver) Clone(dst, src string, linked bool, snapshot string) error { + + var cloneType string + if linked { + cloneType = "linked" + } else { + cloneType = "full" + } + + args := []string{"-T", "ws", "clone", src, dst, cloneType} + if snapshot != "" { + args = append(args, "-snapshot", snapshot) + } + cmd := exec.Command(d.VmrunPath, args...) + if _, _, err := runAndLog(cmd); err != nil { + return err + } + + return nil } -func (d *Workstation9Driver) CompactDisk(diskPath string) error { +func (d *WorkstationDriver) CompactDisk(diskPath string) error { defragCmd := exec.Command(d.VdiskManagerPath, "-d", diskPath) if _, _, err := runAndLog(defragCmd); err != nil { return err @@ -88,7 +104,7 @@ func (d *Workstation9Driver) CompactDisk(diskPath string) error { return nil } -func (d *Workstation9Driver) CreateDisk(output string, size string, adapter_type string, type_id string) error { +func (d *WorkstationDriver) CreateDisk(output string, size string, adapter_type string, type_id string) error { cmd := exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", adapter_type, "-t", type_id, output) if _, _, err := runAndLog(cmd); err != nil { return err @@ -97,13 +113,13 @@ func (d *Workstation9Driver) CreateDisk(output string, size string, adapter_type return nil } -func (d *Workstation9Driver) CreateSnapshot(vmxPath string, snapshotName string) error { +func (d *WorkstationDriver) CreateSnapshot(vmxPath string, snapshotName string) error { cmd := exec.Command(d.VmrunPath, "-T", "ws", "snapshot", vmxPath, snapshotName) _, _, err := runAndLog(cmd) return err } -func (d *Workstation9Driver) IsRunning(vmxPath string) (bool, error) { +func (d *WorkstationDriver) IsRunning(vmxPath string) (bool, error) { vmxPath, err := filepath.Abs(vmxPath) if err != nil { return false, err @@ -124,11 +140,11 @@ func (d *Workstation9Driver) IsRunning(vmxPath string) (bool, error) { return false, nil } -func (d *Workstation9Driver) CommHost(state multistep.StateBag) (string, error) { +func (d *WorkstationDriver) CommHost(state multistep.StateBag) (string, error) { return CommHost(d.SSHConfig)(state) } -func (d *Workstation9Driver) Start(vmxPath string, headless bool) error { +func (d *WorkstationDriver) Start(vmxPath string, headless bool) error { guiArgument := "gui" if headless { guiArgument = "nogui" @@ -142,7 +158,7 @@ func (d *Workstation9Driver) Start(vmxPath string, headless bool) error { return nil } -func (d *Workstation9Driver) Stop(vmxPath string) error { +func (d *WorkstationDriver) Stop(vmxPath string) error { cmd := exec.Command(d.VmrunPath, "-T", "ws", "stop", vmxPath, "hard") if _, _, err := runAndLog(cmd); err != nil { return err @@ -151,11 +167,11 @@ func (d *Workstation9Driver) Stop(vmxPath string) error { return nil } -func (d *Workstation9Driver) SuppressMessages(vmxPath string) error { +func (d *WorkstationDriver) SuppressMessages(vmxPath string) error { return nil } -func (d *Workstation9Driver) Verify() error { +func (d *WorkstationDriver) Verify() error { var err error if d.AppPath == "" { if d.AppPath, err = workstationFindVMware(); err != nil { @@ -242,15 +258,15 @@ func (d *Workstation9Driver) Verify() error { return nil } -func (d *Workstation9Driver) ToolsIsoPath(flavor string) string { +func (d *WorkstationDriver) ToolsIsoPath(flavor string) string { return workstationToolsIsoPath(flavor) } -func (d *Workstation9Driver) ToolsInstall() error { +func (d *WorkstationDriver) ToolsInstall() error { return nil } -func (d *Workstation9Driver) GetVmwareDriver() VmwareDriver { +func (d *WorkstationDriver) GetVmwareDriver() VmwareDriver { return d.VmwareDriver } diff --git a/builder/vmware/common/driver_workstation10.go b/builder/vmware/common/driver_workstation10.go deleted file mode 100644 index f55e329f..00000000 --- a/builder/vmware/common/driver_workstation10.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "os/exec" -) - -const VMWARE_WS_VERSION = "10" - -// Workstation10Driver is a driver that can run VMware Workstation 10 -// installations. - -type Workstation10Driver struct { - Workstation9Driver -} - -func NewWorkstation10Driver(config *SSHConfig) Driver { - return &Workstation10Driver{ - Workstation9Driver: Workstation9Driver{ - SSHConfig: config, - }, - } -} - -func (d *Workstation10Driver) Clone(dst, src string, linked bool, snapshot string) error { - - var cloneType string - if linked { - cloneType = "linked" - } else { - cloneType = "full" - } - - args := []string{"-T", "ws", "clone", src, dst, cloneType} - if snapshot != "" { - args = append(args, "-snapshot", snapshot) - } - cmd := exec.Command(d.Workstation9Driver.VmrunPath, args...) - if _, _, err := runAndLog(cmd); err != nil { - return err - } - - return nil -} - -func (d *Workstation10Driver) Verify() error { - if err := d.Workstation9Driver.Verify(); err != nil { - return err - } - - return workstationVerifyVersion(VMWARE_WS_VERSION) -} - -func (d *Workstation10Driver) GetVmwareDriver() VmwareDriver { - return d.Workstation9Driver.VmwareDriver -} diff --git a/builder/vmware/common/driver_workstation10_windows.go b/builder/vmware/common/driver_workstation10_windows.go deleted file mode 100644 index 704fcf74..00000000 --- a/builder/vmware/common/driver_workstation10_windows.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -//go:build windows - -package common - -import ( - "fmt" - "log" - "regexp" - "syscall" -) - -func workstationVerifyVersion(version string) error { - key := `SOFTWARE\Wow6432Node\VMware, Inc.\VMware Workstation` - subkey := "ProductVersion" - productVersion, err := readRegString(syscall.HKEY_LOCAL_MACHINE, key, subkey) - if err != nil { - log.Printf(`Unable to read registry key %s\%s`, key, subkey) - key = `SOFTWARE\VMware, Inc.\VMware Workstation` - productVersion, err = readRegString(syscall.HKEY_LOCAL_MACHINE, key, subkey) - if err != nil { - log.Printf(`Unable to read registry key %s\%s`, key, subkey) - return err - } - } - - versionRe := regexp.MustCompile(`^(\d+)\.`) - matches := versionRe.FindStringSubmatch(productVersion) - if matches == nil { - return fmt.Errorf( - `Could not find a VMware Workstation version in registry key %s\%s: '%s'`, key, subkey, productVersion) - } - log.Printf("Detected VMware Workstation version: %s", matches[1]) - - return compareVersions(matches[1], version, "Workstation") -} diff --git a/builder/vmware/common/driver_workstation_unix.go b/builder/vmware/common/driver_workstation_unix.go index 7481b540..0800862d 100644 --- a/builder/vmware/common/driver_workstation_unix.go +++ b/builder/vmware/common/driver_workstation_unix.go @@ -3,7 +3,8 @@ //go:build !windows -// These functions are compatible with WS 9 and 10 on *NIX +// VMware Workstation on Linux + package common import ( @@ -127,7 +128,6 @@ func workstationVerifyVersion(version string) error { return fmt.Errorf("driver is only supported on Linux or Windows, not %s", runtime.GOOS) } - //TODO(pmyjavec) there is a better way to find this, how? //the default will suffice for now. vmxpath := "/usr/lib/vmware/bin/vmware-vmx" @@ -144,9 +144,9 @@ func workstationTestVersion(wanted, versionOutput string) error { versionRe := regexp.MustCompile(`(?i)VMware Workstation (\d+)\.`) matches := versionRe.FindStringSubmatch(versionOutput) if matches == nil { - return fmt.Errorf("error parsing version output: %s", wanted) + return fmt.Errorf("error parsing version from output: %s", wanted) } - log.Printf("Detected VMware Workstation version: %s", matches[1]) + log.Printf("VMware Workstation: %s", matches[1]) return compareVersions(matches[1], wanted, "Workstation") } diff --git a/builder/vmware/common/driver_workstation_unix_test.go b/builder/vmware/common/driver_workstation_unix_test.go deleted file mode 100644 index ff730baa..00000000 --- a/builder/vmware/common/driver_workstation_unix_test.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -//go:build !windows - -package common - -import ( - "testing" -) - -func TestWorkstationVersion_ws14(t *testing.T) { - input := `VMware Workstation Information: -VMware Workstation 14.1.1 build-7528167 Release` - if err := workstationTestVersion("10", input); err != nil { - t.Fatal(err) - } -} diff --git a/builder/vmware/common/driver_workstation9_windows.go b/builder/vmware/common/driver_workstation_windows.go similarity index 73% rename from builder/vmware/common/driver_workstation9_windows.go rename to builder/vmware/common/driver_workstation_windows.go index 3d81a2e9..314c15a0 100644 --- a/builder/vmware/common/driver_workstation9_windows.go +++ b/builder/vmware/common/driver_workstation_windows.go @@ -6,17 +6,21 @@ package common import ( + "fmt" "log" "os" "os/exec" "path/filepath" + "regexp" "strings" "syscall" "unsafe" + + "golang.org/x/sys/windows" ) func workstationCheckLicense() error { - // Not implemented on Windows + // Not used on Windows. return nil } @@ -51,6 +55,7 @@ func workstationToolsIsoPath(flavor string) string { return findFile(flavor+".iso", workstationProgramFilePaths()) } +// Read the DHCP leases path from the registry. func workstationDhcpLeasesPath(device string) string { path, err := workstationDhcpLeasesPathRegistry() if err != nil { @@ -62,13 +67,14 @@ func workstationDhcpLeasesPath(device string) string { return findFile("vmnetdhcp.leases", workstationDataFilePaths()) } +// Read the DHCP configuration path from the registry. func workstationDhcpConfPath(device string) string { - // device isn't used on a windows host + // Not used on Windows. return findFile("vmnetdhcp.conf", workstationDataFilePaths()) } func workstationVmnetnatConfPath(device string) string { - // device isn't used on a windows host + // Not used on Windows. return findFile("vmnetnat.conf", workstationDataFilePaths()) } @@ -76,46 +82,7 @@ func workstationNetmapConfPath() string { return findFile("netmap.conf", workstationDataFilePaths()) } -// See http://blog.natefinch.com/2012/11/go-win-stuff.html -// -// This is used by workstationVMwareRoot in order to read some registry data. -func readRegString(hive syscall.Handle, subKeyPath, valueName string) (value string, err error) { - var h syscall.Handle - err = syscall.RegOpenKeyEx(hive, syscall.StringToUTF16Ptr(subKeyPath), 0, syscall.KEY_READ, &h) - if err != nil { - return - } - defer syscall.RegCloseKey(h) - - var typ uint32 - var bufSize uint32 - err = syscall.RegQueryValueEx( - h, - syscall.StringToUTF16Ptr(valueName), - nil, - &typ, - nil, - &bufSize) - if err != nil { - return - } - - data := make([]uint16, bufSize/2+1) - err = syscall.RegQueryValueEx( - h, - syscall.StringToUTF16Ptr(valueName), - nil, - &typ, - (*byte)(unsafe.Pointer(&data[0])), - &bufSize) - if err != nil { - return - } - - return syscall.UTF16ToString(data), nil -} - -// This reads the VMware installation path from the Windows registry. +// Read the installation path from the registry. func workstationVMwareRoot() (s string, err error) { key := `SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\vmware.exe` subkey := "Path" @@ -128,7 +95,7 @@ func workstationVMwareRoot() (s string, err error) { return normalizePath(s), nil } -// This reads the VMware DHCP leases path from the Windows registry. +// Read the DHCP leases path from the registry. func workstationDhcpLeasesPathRegistry() (s string, err error) { key := "SYSTEM\\CurrentControlSet\\services\\VMnetDHCP\\Parameters" subkey := "LeaseFile" @@ -141,35 +108,12 @@ func workstationDhcpLeasesPathRegistry() (s string, err error) { return normalizePath(s), nil } -func normalizePath(path string) string { - path = strings.Replace(path, "\\", "/", -1) - path = strings.Replace(path, "//", "/", -1) - path = strings.TrimRight(path, "/") - return path -} - -func findFile(file string, paths []string) string { - for _, path := range paths { - path = filepath.Join(path, file) - path = normalizePath(path) - log.Printf("Searching for file '%s'", path) - - if _, err := os.Stat(path); err == nil { - log.Printf("Found file '%s'", path) - return path - } - } - - log.Printf("File not found: '%s'", file) - return "" -} - // workstationProgramFilesPaths returns a list of paths that are eligible -// to contain program files we may want just as vmware.exe. +// to contain the VMware Workstation binaries. func workstationProgramFilePaths() []string { path, err := workstationVMwareRoot() if err != nil { - log.Printf("Error finding VMware root: %s", err) + log.Printf("Error finding the configuration root path: %s", err) } paths := make([]string, 0, 5) @@ -195,11 +139,11 @@ func workstationProgramFilePaths() []string { } // workstationDataFilePaths returns a list of paths that are eligible -// to contain data files we may want such as vmnet NAT configuration files. +// to contain configuration files. func workstationDataFilePaths() []string { leasesPath, err := workstationDhcpLeasesPathRegistry() if err != nil { - log.Printf("Error getting DHCP leases path: %s", err) + log.Printf("Error retrieving DHCP leases path from registry: %s", err) } if leasesPath != "" { @@ -227,3 +171,97 @@ func workstationDataFilePaths() []string { return paths } + +func workstationVerifyVersion(version string) error { + key := `SOFTWARE\Wow6432Node\VMware, Inc.\VMware Workstation` + subkey := "ProductVersion" + productVersion, err := readRegString(syscall.HKEY_LOCAL_MACHINE, key, subkey) + if err != nil { + log.Printf(`Unable to read registry key %s\%s`, key, subkey) + key = `SOFTWARE\VMware, Inc.\VMware Workstation` + productVersion, err = readRegString(syscall.HKEY_LOCAL_MACHINE, key, subkey) + if err != nil { + log.Printf(`Unable to read registry key %s\%s`, key, subkey) + return err + } + } + + versionRe := regexp.MustCompile(`^(\d+)\.`) + matches := versionRe.FindStringSubmatch(productVersion) + if matches == nil { + return fmt.Errorf("error retrieving the version from registry key %s\\%s: '%s'", key, subkey, productVersion) + } + log.Printf("VMware Workstation: %s", matches[1]) + + return compareVersions(matches[1], version, "Workstation") +} + +// Read Windows registry data. +func readRegString(hive syscall.Handle, subKeyPath, valueName string) (value string, err error) { + var h syscall.Handle + subKeyPathPtr, err := windows.UTF16PtrFromString(subKeyPath) + if err != nil { + return "", err + } + + err = syscall.RegOpenKeyEx(hive, subKeyPathPtr, 0, syscall.KEY_READ, &h) + if err != nil { + return + } + defer syscall.RegCloseKey(h) + + var typ uint32 + var bufSize uint32 + valueNamePtr, err := windows.UTF16PtrFromString(valueName) + if err != nil { + return "", err + } + + err = syscall.RegQueryValueEx( + h, + valueNamePtr, + nil, + &typ, + nil, + &bufSize) + if err != nil { + return + } + + data := make([]uint16, bufSize/2+1) + err = syscall.RegQueryValueEx( + h, + valueNamePtr, + nil, + &typ, + (*byte)(unsafe.Pointer(&data[0])), + &bufSize) + if err != nil { + return + } + + return syscall.UTF16ToString(data), nil +} + +func normalizePath(path string) string { + path = strings.Replace(path, "\\", "/", -1) + path = strings.Replace(path, "//", "/", -1) + path = strings.TrimRight(path, "/") + return path +} + +func findFile(file string, paths []string) string { + for _, path := range paths { + path = filepath.Join(path, file) + path = normalizePath(path) + log.Printf("Searching for file '%s'", path) + + if _, err := os.Stat(path); err == nil { + log.Printf("Found file '%s'", path) + return path + } + } + + log.Printf("File not found: '%s'", file) + return "" +}