diff --git a/builder/vmware/common/driver.go b/builder/vmware/common/driver.go index c0a8a5d4..1985885a 100644 --- a/builder/vmware/common/driver.go +++ b/builder/vmware/common/driver.go @@ -33,6 +33,7 @@ const ( // Operating systems. osWindows = "windows" + osLinux = "linux" // Clone types. cloneTypeLinked = "linked" @@ -43,16 +44,41 @@ const ( guiArgumentGUI = "gui" // Application binary names. + appOvfTool = "ovftool" + appPlayer = "vmplayer" appVdiskManager = "vmware-vdiskmanager" appVmrun = "vmrun" appVmx = "vmware-vmx" + appQemuImg = "qemu-img" - // Version Regular Expressions. + // Version regular expressions. productVersionRegex = `(?i)VMware [a-z0-9-]+ (\d+\.\d+\.\d+)` technicalPreviewRegex = `(?i)VMware [a-z0-9-]+ e\.x\.p ` ovfToolVersionRegex = `\d+\.\d+\.\d+` + + // File names. + dhcpVmnetConfFile = "vmnetdhcp.conf" + dhcpVmnetLeasesFile = "vmnetdhcp.leases" + natVmnetConfFile = "vmnetnat.conf" + netmapConfFile = "netmap.conf" ) +// The possible paths to the DHCP leases file. +var dhcpLeasesPaths = []string{ + "dhcp/dhcp.leases", + "dhcp/dhcpd.leases", + "dhcpd/dhcp.leases", + "dhcpd/dhcpd.leases", +} + +// The possible paths to the DHCP configuration file. +var dhcpConfPaths = []string{ + "dhcp/dhcp.conf", + "dhcp/dhcpd.conf", + "dhcpd/dhcp.conf", + "dhcpd/dhcpd.conf", +} + // The product version. var productVersion = regexp.MustCompile(productVersionRegex) @@ -160,8 +186,7 @@ func NewDriver(dconfig *DriverConfig, config *SSHConfig, vmName string) (Driver, drivers = []Driver{ NewWorkstation10Driver(config), NewWorkstation9Driver(config), - NewPlayer6Driver(config), - NewPlayer5Driver(config), + NewPlayerDriver(config), } default: return nil, fmt.Errorf("error finding a driver for %s", runtime.GOOS) @@ -683,9 +708,19 @@ func (d *VmwareDriver) HostIP(state multistep.StateBag) (string, error) { return "", fmt.Errorf("unable to find host IP from devices %v, last error: %s", devices, lastError) } +// GetDhcpLeasesPaths returns a copy of the DHCP leases paths. +func GetDhcpLeasesPaths() []string { + return append([]string(nil), dhcpLeasesPaths...) +} + +// GetDhcpConfPaths returns a copy of the DHCP configuration paths. +func GetDhcpConfPaths() []string { + return append([]string(nil), dhcpConfPaths...) +} + func GetOvfTool() string { - ovftool := "ovftool" - if runtime.GOOS == "windows" { + ovftool := appOvfTool + if runtime.GOOS == osWindows { ovftool += ".exe" } diff --git a/builder/vmware/common/driver_player.go b/builder/vmware/common/driver_player.go new file mode 100644 index 00000000..85b4f070 --- /dev/null +++ b/builder/vmware/common/driver_player.go @@ -0,0 +1,298 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package common + +import ( + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/hashicorp/packer-plugin-sdk/multistep" +) + +const ( + // VMware Workstation Player application name. + playerProductName = "VMware Workstation Player" + + // VMware Workstation Player versions. + // TODO: Update to best effort comply with the Broadcom Product Lifecycle. + minimumPlayerVersion = "6.0.0" +) + +// PlayerDriver is a driver for VMware Workstation Player. +type PlayerDriver struct { + VmwareDriver + + // The path to VMware Workstation Player. + AppPath string + + VdiskManagerPath string + QemuImgPath string + VmrunPath string + + SSHConfig *SSHConfig +} + +func NewPlayerDriver(config *SSHConfig) Driver { + return &PlayerDriver{ + SSHConfig: config, + } +} + +func (d *PlayerDriver) CompactDisk(diskPath string) error { + if d.QemuImgPath != "" { + return d.qemuCompactDisk(diskPath) + } + + defragCmd := exec.Command(d.VdiskManagerPath, "-d", diskPath) + if _, _, err := runAndLog(defragCmd); err != nil { + return err + } + + shrinkCmd := exec.Command(d.VdiskManagerPath, "-k", diskPath) + if _, _, err := runAndLog(shrinkCmd); err != nil { + return err + } + + return nil +} + +func (d *PlayerDriver) qemuCompactDisk(diskPath string) error { + cmd := exec.Command(d.QemuImgPath, "convert", "-f", "vmdk", "-O", "vmdk", "-o", "compat6", diskPath, diskPath+".new") + if _, _, err := runAndLog(cmd); err != nil { + return err + } + + if err := os.Remove(diskPath); err != nil { + return err + } + + if err := os.Rename(diskPath+".new", diskPath); err != nil { + return err + } + + return nil +} + +func (d *PlayerDriver) CreateDisk(output string, size string, adapter_type string, type_id string) error { + var cmd *exec.Cmd + if d.QemuImgPath != "" { + cmd = exec.Command(d.QemuImgPath, "create", "-f", "vmdk", "-o", "compat6", output, size) + } else { + cmd = exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", adapter_type, "-t", type_id, output) + } + if _, _, err := runAndLog(cmd); err != nil { + return err + } + + return nil +} + +func (d *PlayerDriver) CreateSnapshot(vmxPath string, snapshotName string) error { + cmd := exec.Command(d.VmrunPath, "-T", "player", "snapshot", vmxPath, snapshotName) + _, _, err := runAndLog(cmd) + return err +} + +func (d *PlayerDriver) IsRunning(vmxPath string) (bool, error) { + vmxPath, err := filepath.Abs(vmxPath) + if err != nil { + return false, err + } + + cmd := exec.Command(d.VmrunPath, "-T", "player", "list") + stdout, _, err := runAndLog(cmd) + if err != nil { + return false, err + } + + for _, line := range strings.Split(stdout, "\n") { + if line == vmxPath { + return true, nil + } + } + + return false, nil +} + +func (d *PlayerDriver) CommHost(state multistep.StateBag) (string, error) { + return CommHost(d.SSHConfig)(state) +} + +func (d *PlayerDriver) Start(vmxPath string, headless bool) error { + guiArgument := guiArgumentNoGUI + if !headless { + guiArgument = guiArgumentGUI + } + + cmd := exec.Command(d.VmrunPath, "-T", "player", "start", vmxPath, guiArgument) + if _, _, err := runAndLog(cmd); err != nil { + return err + } + + return nil +} + +func (d *PlayerDriver) Stop(vmxPath string) error { + cmd := exec.Command(d.VmrunPath, "-T", "player", "stop", vmxPath, "hard") + if _, _, err := runAndLog(cmd); err != nil { + // Check if the virtual machine is running. If not, it is stopped. + running, runningErr := d.IsRunning(vmxPath) + if runningErr == nil && !running { + return nil + } + + return err + } + + return nil +} + +func (d *PlayerDriver) SuppressMessages(vmxPath string) error { + return nil +} + +func (d *PlayerDriver) Verify() error { + var err error + + log.Printf("[INFO] Checking %s paths...", playerProductName) + + if d.AppPath == "" { + if d.AppPath, err = playerFindVmplayer(); err != nil { + return fmt.Errorf("%s not found: %s", playerProductName, err) + } + } + log.Printf("[INFO] - %s app path: %s", playerProductName, d.AppPath) + + if d.VmrunPath == "" { + if d.VmrunPath, err = playerFindVmrun(); err != nil { + return fmt.Errorf("%s not found: %s", appVmrun, err) + } + } + log.Printf("[INFO] - %s found at: %s", appVmrun, d.VmrunPath) + + if d.VdiskManagerPath == "" { + d.VdiskManagerPath, err = playerFindVdiskManager() + } + + if d.VdiskManagerPath == "" && d.QemuImgPath == "" { + d.QemuImgPath, err = playerFindQemuImg() + } + + if err != nil { + return fmt.Errorf("error finding either %s or %s: %s", appVdiskManager, appQemuImg, err) + } + + log.Printf("[INFO] - %s found at: %s", appVdiskManager, d.VdiskManagerPath) + log.Printf("[INFO] - %s found at: %s", appQemuImg, d.QemuImgPath) + + if _, err := os.Stat(d.AppPath); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("%s not found at: %s", playerProductName, d.AppPath) + } + return err + } + log.Printf("[INFO] - %s found at: %s", playerProductName, d.AppPath) + + if _, err := os.Stat(d.VmrunPath); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("%s not found at: %s", appVmrun, d.VmrunPath) + } + return err + } + log.Printf("[INFO] - %s found at: %s", appVmrun, d.VmrunPath) + + if d.VdiskManagerPath != "" { + if _, err := os.Stat(d.VdiskManagerPath); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("%s not found at: %s", appVdiskManager, d.VdiskManagerPath) + } + return err + } + log.Printf("[INFO] - %s found at: %s", appVdiskManager, d.VdiskManagerPath) + } else { + if _, err := os.Stat(d.QemuImgPath); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("%s not found at: %s", appQemuImg, d.QemuImgPath) + } + return err + } + log.Printf("[INFO] - %s found at: %s", appQemuImg, d.QemuImgPath) + } + + // Assigning the path callbacks to VmwareDriver + d.VmwareDriver.DhcpLeasesPath = func(device string) string { + return playerDhcpLeasesPath(device) + } + + d.VmwareDriver.DhcpConfPath = func(device string) string { + return playerVmDhcpConfPath(device) + } + + d.VmwareDriver.VmnetnatConfPath = func(device string) string { + return playerVmnetnatConfPath(device) + } + + d.VmwareDriver.NetworkMapper = func() (NetworkNameMapper, error) { + pathNetmap := playerNetmapConfPath() + + if _, err := os.Stat(pathNetmap); err == nil { + log.Printf("[INFO] Found: %s", pathNetmap) + return ReadNetmapConfig(pathNetmap) + } + + libpath, _ := playerInstallationPath() + pathNetworking := filepath.Join(libpath, "networking") + if _, err := os.Stat(pathNetworking); err != nil { + return nil, fmt.Errorf("not found: %s", pathNetworking) + } + + log.Printf("[INFO] Found: %s", pathNetworking) + fd, err := os.Open(pathNetworking) + if err != nil { + return nil, err + } + defer fd.Close() + + return ReadNetworkingConfig(fd) + } + + return playerVerifyVersion(minimumPlayerVersion) +} + +func (d *PlayerDriver) ToolsIsoPath(flavor string) string { + return playerToolsIsoPath(flavor) +} + +func (d *PlayerDriver) ToolsInstall() error { + return nil +} + +func (d *PlayerDriver) Clone(dst, src string, linked bool, snapshot string) error { + var cloneType string + + if linked { + cloneType = cloneTypeLinked + } else { + cloneType = cloneTypeFull + } + + 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 *PlayerDriver) GetVmwareDriver() VmwareDriver { + return d.VmwareDriver +} diff --git a/builder/vmware/common/driver_player5.go b/builder/vmware/common/driver_player5.go deleted file mode 100644 index f0ec8cd0..00000000 --- a/builder/vmware/common/driver_player5.go +++ /dev/null @@ -1,252 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "errors" - "fmt" - "log" - "os" - "os/exec" - "path/filepath" - "strings" - - "github.com/hashicorp/packer-plugin-sdk/multistep" -) - -// Player5Driver is a driver that can run VMware Player 5 on Linux. -type Player5Driver struct { - VmwareDriver - - AppPath string - VdiskManagerPath string - QemuImgPath string - VmrunPath string - - // SSHConfig are the SSH settings for the Fusion VM - SSHConfig *SSHConfig -} - -func NewPlayer5Driver(config *SSHConfig) Driver { - return &Player5Driver{ - SSHConfig: config, - } -} - -func (d *Player5Driver) Clone(dst, src string, linked bool, snapshot string) error { - return errors.New("linked clones are not supported on this version of VMware Player, please upgrade") -} - -func (d *Player5Driver) CompactDisk(diskPath string) error { - if d.QemuImgPath != "" { - return d.qemuCompactDisk(diskPath) - } - - defragCmd := exec.Command(d.VdiskManagerPath, "-d", diskPath) - if _, _, err := runAndLog(defragCmd); err != nil { - return err - } - - shrinkCmd := exec.Command(d.VdiskManagerPath, "-k", diskPath) - if _, _, err := runAndLog(shrinkCmd); err != nil { - return err - } - - return nil -} - -func (d *Player5Driver) qemuCompactDisk(diskPath string) error { - cmd := exec.Command(d.QemuImgPath, "convert", "-f", "vmdk", "-O", "vmdk", "-o", "compat6", diskPath, diskPath+".new") - if _, _, err := runAndLog(cmd); err != nil { - return err - } - - if err := os.Remove(diskPath); err != nil { - return err - } - - if err := os.Rename(diskPath+".new", diskPath); err != nil { - return err - } - - return nil -} - -func (d *Player5Driver) CreateDisk(output string, size string, adapter_type string, type_id string) error { - var cmd *exec.Cmd - if d.QemuImgPath != "" { - cmd = exec.Command(d.QemuImgPath, "create", "-f", "vmdk", "-o", "compat6", output, size) - } else { - cmd = exec.Command(d.VdiskManagerPath, "-c", "-s", size, "-a", adapter_type, "-t", type_id, output) - } - if _, _, err := runAndLog(cmd); err != nil { - return err - } - - return nil -} - -func (d *Player5Driver) CreateSnapshot(vmxPath string, snapshotName string) error { - cmd := exec.Command(d.VmrunPath, "-T", "player", "snapshot", vmxPath, snapshotName) - _, _, err := runAndLog(cmd) - return err -} - -func (d *Player5Driver) IsRunning(vmxPath string) (bool, error) { - vmxPath, err := filepath.Abs(vmxPath) - if err != nil { - return false, err - } - - cmd := exec.Command(d.VmrunPath, "-T", "player", "list") - stdout, _, err := runAndLog(cmd) - if err != nil { - return false, err - } - - for _, line := range strings.Split(stdout, "\n") { - if line == vmxPath { - return true, nil - } - } - - return false, nil -} - -func (d *Player5Driver) CommHost(state multistep.StateBag) (string, error) { - return CommHost(d.SSHConfig)(state) -} - -func (d *Player5Driver) Start(vmxPath string, headless bool) error { - guiArgument := "gui" - if headless { - guiArgument = "nogui" - } - - cmd := exec.Command(d.VmrunPath, "-T", "player", "start", vmxPath, guiArgument) - if _, _, err := runAndLog(cmd); err != nil { - return err - } - - return nil -} - -func (d *Player5Driver) Stop(vmxPath string) error { - cmd := exec.Command(d.VmrunPath, "-T", "player", "stop", vmxPath, "hard") - if _, _, err := runAndLog(cmd); err != nil { - return err - } - - return nil -} - -func (d *Player5Driver) SuppressMessages(vmxPath string) error { - return nil -} - -func (d *Player5Driver) Verify() error { - var err error - if d.AppPath == "" { - if d.AppPath, err = playerFindVMware(); err != nil { - return err - } - } - - if d.VmrunPath == "" { - if d.VmrunPath, err = playerFindVmrun(); err != nil { - return err - } - } - - if d.VdiskManagerPath == "" { - d.VdiskManagerPath, err = playerFindVdiskManager() - } - - if d.VdiskManagerPath == "" && d.QemuImgPath == "" { - d.QemuImgPath, err = playerFindQemuImg() - } - - if err != nil { - return fmt.Errorf("error finding either 'vmware-vdiskmanager' or 'qemu-img' in path") - } - - log.Printf("VMware app path: %s", d.AppPath) - log.Printf("vmrun path: %s", d.VmrunPath) - log.Printf("vdisk-manager path: %s", d.VdiskManagerPath) - log.Printf("qemu-img path: %s", d.QemuImgPath) - - if _, err := os.Stat(d.AppPath); err != nil { - return fmt.Errorf("player not found in path: %s", d.AppPath) - } - - if _, err := os.Stat(d.VmrunPath); err != nil { - return fmt.Errorf("'vmrun' not found in path: %s", d.VmrunPath) - } - - if d.VdiskManagerPath != "" { - _, err = os.Stat(d.VdiskManagerPath) - } else { - _, err = os.Stat(d.QemuImgPath) - } - - if err != nil { - return fmt.Errorf("error finding either 'vmware-vdiskmanager' or 'qemu-img' in path: %s", d.VdiskManagerPath) - } - - // Assigning the path callbacks to VmwareDriver - d.VmwareDriver.DhcpLeasesPath = func(device string) string { - return playerDhcpLeasesPath(device) - } - - d.VmwareDriver.DhcpConfPath = func(device string) string { - return playerVmDhcpConfPath(device) - } - - d.VmwareDriver.VmnetnatConfPath = func(device string) string { - return playerVmnetnatConfPath(device) - } - - d.VmwareDriver.NetworkMapper = func() (NetworkNameMapper, error) { - pathNetmap := playerNetmapConfPath() - - // If we were able to find the file (no error), then we can proceed with reading - // the networkmapper configuration. - if _, err := os.Stat(pathNetmap); err == nil { - log.Printf("Located networkmapper configuration file: %s", pathNetmap) - return ReadNetmapConfig(pathNetmap) - } - - // If we weren't able to find the networkmapper configuration file, then fall back - // to the networking file which might also be in the configuration directory. - libpath, _ := playerVMwareRoot() - pathNetworking := filepath.Join(libpath, "networking") - if _, err := os.Stat(pathNetworking); err != nil { - return nil, fmt.Errorf("error determining network mappings from files in path: %s", libpath) - } - - // We were able to successfully stat the file.. So, now we can open a handle to it. - log.Printf("Located networking configuration file: %s", pathNetworking) - fd, err := os.Open(pathNetworking) - if err != nil { - return nil, err - } - defer fd.Close() - - // Then we pass the handle to the networking configuration parser. - return ReadNetworkingConfig(fd) - } - return nil -} - -func (d *Player5Driver) ToolsIsoPath(flavor string) string { - return playerToolsIsoPath(flavor) -} - -func (d *Player5Driver) ToolsInstall() error { - return nil -} - -func (d *Player5Driver) GetVmwareDriver() VmwareDriver { - return d.VmwareDriver -} diff --git a/builder/vmware/common/driver_player5_windows.go b/builder/vmware/common/driver_player5_windows.go deleted file mode 100644 index b3dd3d55..00000000 --- a/builder/vmware/common/driver_player5_windows.go +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -//go:build windows - -package common - -import ( - "log" - "os" - "os/exec" - "path/filepath" - "syscall" -) - -func playerFindVdiskManager() (string, error) { - path, err := exec.LookPath("vmware-vdiskmanager.exe") - if err == nil { - return path, nil - } - - return findFile("vmware-vdiskmanager.exe", playerProgramFilePaths()), nil -} - -func playerFindQemuImg() (string, error) { - path, err := exec.LookPath("qemu-img.exe") - if err == nil { - return path, nil - } - - return findFile("qemu-img.exe", playerProgramFilePaths()), nil -} - -func playerFindVMware() (string, error) { - path, err := exec.LookPath("vmplayer.exe") - if err == nil { - return path, nil - } - - return findFile("vmplayer.exe", playerProgramFilePaths()), nil -} - -func playerFindVmrun() (string, error) { - path, err := exec.LookPath("vmrun.exe") - if err == nil { - return path, nil - } - - return findFile("vmrun.exe", playerProgramFilePaths()), nil -} - -func playerToolsIsoPath(flavor string) string { - return findFile(flavor+".iso", playerProgramFilePaths()) -} - -func playerDhcpLeasesPath(device string) string { - path, err := playerDhcpLeasesPathRegistry() - if err != nil { - log.Printf("Error finding leases in registry: %s", err) - } else if _, err := os.Stat(path); err == nil { - return path - } - return findFile("vmnetdhcp.leases", playerDataFilePaths()) -} - -func playerVmDhcpConfPath(device string) string { - // the device isn't actually used on windows hosts - path, err := playerDhcpConfigPathRegistry() - if err != nil { - log.Printf("Error finding configuration in registry: %s", err) - } else if _, err := os.Stat(path); err == nil { - return path - } - return findFile("vmnetdhcp.conf", playerDataFilePaths()) -} - -func playerVmnetnatConfPath(device string) string { - // the device isn't actually used on windows hosts - return findFile("vmnetnat.conf", playerDataFilePaths()) -} - -func playerNetmapConfPath() string { - return findFile("netmap.conf", playerDataFilePaths()) -} - -// This reads the VMware installation path from the Windows registry. -func playerVMwareRoot() (s string, err error) { - key := `SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\vmplayer.exe` - subkey := "Path" - s, err = readRegString(syscall.HKEY_LOCAL_MACHINE, key, subkey) - if err != nil { - log.Printf(`Unable to read registry key %s\%s`, key, subkey) - return - } - - return normalizePath(s), nil -} - -// This reads the VMware DHCP leases path from the Windows registry. -func playerDhcpLeasesPathRegistry() (s string, err error) { - key := "SYSTEM\\CurrentControlSet\\services\\VMnetDHCP\\Parameters" - subkey := "LeaseFile" - s, err = readRegString(syscall.HKEY_LOCAL_MACHINE, key, subkey) - if err != nil { - log.Printf(`Unable to read registry key %s\%s`, key, subkey) - return - } - return normalizePath(s), nil -} - -// This reads the VMware DHCP configuration path from the Windows registry. -func playerDhcpConfigPathRegistry() (s string, err error) { - key := "SYSTEM\\CurrentControlSet\\services\\VMnetDHCP\\Parameters" - subkey := "ConfFile" - s, err = readRegString(syscall.HKEY_LOCAL_MACHINE, key, subkey) - if err != nil { - log.Printf(`Unable to read registry key %s\%s`, key, subkey) - return - } - return normalizePath(s), nil -} - -// playerProgramFilesPaths returns a list of paths that are eligible -// to contain program files we may want just as vmware.exe. -func playerProgramFilePaths() []string { - path, err := playerVMwareRoot() - if err != nil { - log.Printf("Error finding VMware root: %s", err) - } - - paths := make([]string, 0, 5) - if os.Getenv("VMWARE_HOME") != "" { - paths = append(paths, os.Getenv("VMWARE_HOME")) - } - - if path != "" { - paths = append(paths, path) - } - - if os.Getenv("ProgramFiles(x86)") != "" { - paths = append(paths, - filepath.Join(os.Getenv("ProgramFiles(x86)"), "/VMware/VMware Player")) - } - - if os.Getenv("ProgramFiles") != "" { - paths = append(paths, - filepath.Join(os.Getenv("ProgramFiles"), "/VMware/VMware Player")) - } - - if os.Getenv("QEMU_HOME") != "" { - paths = append(paths, os.Getenv("QEMU_HOME")) - } - - if os.Getenv("ProgramFiles(x86)") != "" { - paths = append(paths, - filepath.Join(os.Getenv("ProgramFiles(x86)"), "/QEMU")) - } - - if os.Getenv("ProgramFiles") != "" { - paths = append(paths, - filepath.Join(os.Getenv("ProgramFiles"), "/QEMU")) - } - - if os.Getenv("SystemDrive") != "" { - paths = append(paths, - filepath.Join(os.Getenv("SystemDrive"), "/QEMU")) - } - - return paths -} - -// playerDataFilePaths returns a list of paths that are eligible -// to contain data files we may want such as vmnet NAT configuration files. -func playerDataFilePaths() []string { - leasesPath, err := playerDhcpLeasesPathRegistry() - if err != nil { - log.Printf("Error getting DHCP leases path: %s", err) - } - - if leasesPath != "" { - leasesPath = filepath.Dir(leasesPath) - } - - paths := make([]string, 0, 5) - if os.Getenv("VMWARE_DATA") != "" { - paths = append(paths, os.Getenv("VMWARE_DATA")) - } - - if leasesPath != "" { - paths = append(paths, leasesPath) - } - - if os.Getenv("ProgramData") != "" { - paths = append(paths, - filepath.Join(os.Getenv("ProgramData"), "/VMware")) - } - - if os.Getenv("ALLUSERSPROFILE") != "" { - paths = append(paths, - filepath.Join(os.Getenv("ALLUSERSPROFILE"), "/Application Data/VMware")) - } - - return paths -} diff --git a/builder/vmware/common/driver_player6.go b/builder/vmware/common/driver_player6.go deleted file mode 100644 index 4d2af0f9..00000000 --- a/builder/vmware/common/driver_player6.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "os/exec" -) - -const VMWARE_PLAYER_VERSION = "6" - -// Player6Driver is a driver that can run VMware Player 6 -// installations. - -type Player6Driver struct { - Player5Driver -} - -func NewPlayer6Driver(config *SSHConfig) Driver { - return &Player6Driver{ - Player5Driver: Player5Driver{ - SSHConfig: config, - }, - } -} - -func (d *Player6Driver) Clone(dst, src string, linked bool, snapshot string) error { - // TODO(rasa) check if running player+, not just player - - 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.Player5Driver.VmrunPath, args...) - if _, _, err := runAndLog(cmd); err != nil { - return err - } - - return nil -} - -func (d *Player6Driver) Verify() error { - if err := d.Player5Driver.Verify(); err != nil { - return err - } - - return playerVerifyVersion(VMWARE_PLAYER_VERSION) -} - -func (d *Player6Driver) GetVmwareDriver() VmwareDriver { - return d.Player5Driver.VmwareDriver -} diff --git a/builder/vmware/common/driver_player6_windows.go b/builder/vmware/common/driver_player6_windows.go deleted file mode 100644 index bfacb2df..00000000 --- a/builder/vmware/common/driver_player6_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 playerVerifyVersion(version string) error { - key := `SOFTWARE\Wow6432Node\VMware, Inc.\VMware Player` - 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 Player` - 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 Player version in registry key %s\%s: '%s'`, key, subkey, productVersion) - } - log.Printf("Detected VMware Player version: %s", matches[1]) - - return compareVersions(matches[1], version, "Player") -} diff --git a/builder/vmware/common/driver_player_unix.go b/builder/vmware/common/driver_player_unix.go index f8bba40c..f1f235ed 100644 --- a/builder/vmware/common/driver_player_unix.go +++ b/builder/vmware/common/driver_player_unix.go @@ -3,7 +3,6 @@ //go:build !windows -// These functions are compatible with WS 9 and 10 on *NIX package common import ( @@ -17,46 +16,42 @@ import ( "runtime" ) -func playerFindVdiskManager() (string, error) { - return exec.LookPath("vmware-vdiskmanager") +// VMware Workstation Player for Linux. + +func playerFindVmplayer() (string, error) { + return exec.LookPath(appPlayer) } -func playerFindQemuImg() (string, error) { - return exec.LookPath("qemu-img") +func playerFindVmrun() (string, error) { + return exec.LookPath(appVmrun) } -func playerFindVMware() (string, error) { - return exec.LookPath("vmplayer") +func playerFindVdiskManager() (string, error) { + return exec.LookPath(appVdiskManager) } -func playerFindVmrun() (string, error) { - return exec.LookPath("vmrun") +func playerFindQemuImg() (string, error) { + return exec.LookPath(appQemuImg) } func playerToolsIsoPath(flavor string) string { return "/usr/lib/vmware/isoimages/" + flavor + ".iso" } -// return the base path to vmware's config on the host -func playerVMwareRoot() (s string, err error) { +// Return the base path to configuration files. +func playerInstallationPath() (s string, err error) { return "/etc/vmware", nil } -func playerDhcpLeasesPath(device string) string { - base, err := playerVMwareRoot() +// Helper function to find configuration paths +func playerFindConfigPath(device string, paths []string) string { + base, err := playerInstallationPath() if err != nil { - log.Printf("Error finding VMware root: %s", err) + log.Printf("Error finding configuration root path: %s", err) return "" } - // Build the base path to VMware configuration for specified device: `/etc/vmware/${device}` devicebase := filepath.Join(base, device) - - // Walk through a list of paths searching for the correct permutation... - // ...as it appears that in >= WS14 and < WS14, the leases file may be labelled differently. - - // Docs say we should expect: dhcpd/dhcpd.leases - paths := []string{"dhcpd/dhcpd.leases", "dhcpd/dhcp.leases", "dhcp/dhcpd.leases", "dhcp/dhcp.leases"} for _, p := range paths { fp := filepath.Join(devicebase, p) if _, err := os.Stat(fp); !os.IsNotExist(err) { @@ -64,62 +59,43 @@ func playerDhcpLeasesPath(device string) string { } } - log.Printf("Error finding VMware DHCP Server Leases (dhcpd.leases) under device path: %s", devicebase) + log.Printf("Error finding configuration file in device path: %s", devicebase) return "" } -func playerVmDhcpConfPath(device string) string { - base, err := playerVMwareRoot() - if err != nil { - log.Printf("Error finding VMware root: %s", err) - return "" - } - - // Build the base path to VMware configuration for specified device: `/etc/vmware/${device}` - devicebase := filepath.Join(base, device) - - // Walk through a list of paths searching for the correct permutation... - // ...as it appears that in >= WS14 and < WS14, the dhcp config may be labelled differently. - - // Docs say we should expect: dhcp/dhcp.conf - paths := []string{"dhcp/dhcp.conf", "dhcp/dhcpd.conf", "dhcpd/dhcp.conf", "dhcpd/dhcpd.conf"} - for _, p := range paths { - fp := filepath.Join(devicebase, p) - if _, err := os.Stat(fp); !os.IsNotExist(err) { - return fp - } - } +func playerDhcpLeasesPath(device string) string { + return playerFindConfigPath(device, GetDhcpLeasesPaths()) +} - log.Printf("Error finding VMware DHCP Server Configuration (dhcp.conf) under device path: %s", devicebase) - return "" +func playerVmDhcpConfPath(device string) string { + return playerFindConfigPath(device, GetDhcpConfPaths()) } func playerVmnetnatConfPath(device string) string { - base, err := playerVMwareRoot() + base, err := playerInstallationPath() if err != nil { - log.Printf("Error finding VMware root: %s", err) + log.Printf("Error finding the configuration root path: %s", err) return "" } return filepath.Join(base, device, "nat/nat.conf") } func playerNetmapConfPath() string { - base, err := playerVMwareRoot() + base, err := playerInstallationPath() if err != nil { - log.Printf("Error finding VMware root: %s", err) + log.Printf("Error finding the configuration root path: %s", err) return "" } - return filepath.Join(base, "netmap.conf") + return filepath.Join(base, netmapConfFile) } func playerVerifyVersion(version string) error { - if runtime.GOOS != "linux" { - return fmt.Errorf("driver is only supported on Linux and Windows, not %s", runtime.GOOS) + if runtime.GOOS != osLinux { + return fmt.Errorf("driver is only supported on linux and windows, not %s", runtime.GOOS) } - //TODO: Is there is a better way to find this? // Using the default. - vmxpath := "/usr/lib/vmware/bin/vmware-vmx" + vmxpath := "/usr/lib/vmware/bin/" + appVmx var stderr bytes.Buffer cmd := exec.Command(vmxpath, "-v") @@ -131,9 +107,9 @@ func playerVerifyVersion(version string) error { versionRe := regexp.MustCompile(`(?i)VMware Player (\d+)\.`) matches := versionRe.FindStringSubmatch(stderr.String()) if matches == nil { - return fmt.Errorf("error parsing version output: %s", stderr.String()) + return fmt.Errorf("error parsing version from output: %s", stderr.String()) } - log.Printf("Detected VMware Player version: %s", matches[1]) + log.Printf("[INFO] VMware Workstation Player: %s", matches[1]) return compareVersions(matches[1], version, "Player") } diff --git a/builder/vmware/common/driver_player_windows.go b/builder/vmware/common/driver_player_windows.go new file mode 100644 index 00000000..e1ea33b2 --- /dev/null +++ b/builder/vmware/common/driver_player_windows.go @@ -0,0 +1,189 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:build windows + +package common + +import ( + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "regexp" + "syscall" +) + +// VMware Workstation Player for Windows. + +const ( + playerInstallationPathKey = `SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\vmplayer.exe` + playerRegistryKey = "SYSTEM\\CurrentControlSet\\services\\VMnetDHCP\\Parameters" +) + +func playerExecutable(executable string) (string, error) { + path, err := exec.LookPath(executable + ".exe") + if err == nil { + return path, nil + } + return findFile(executable+".exe", playerProgramFilePaths()), nil +} + +func playerFindVmplayer() (string, error) { + return playerExecutable(appPlayer) +} + +func playerFindVmrun() (string, error) { + return playerExecutable(appVmrun) +} + +func playerFindVdiskManager() (string, error) { + return playerExecutable(appVdiskManager) +} + +func playerFindQemuImg() (string, error) { + return playerExecutable(appQemuImg) +} + +func playerToolsIsoPath(flavor string) string { + return findFile(flavor+".iso", playerProgramFilePaths()) +} + +func playerDhcpLeasesPath(device string) string { + // Not used on Windows. + path, err := playerDhcpLeasesPathRegistry() + if err != nil { + log.Printf("[WARN] Unable to retrieve DHCP leases path from registry: %s", err) + } else if _, err := os.Stat(path); err == nil { + return path + } + return findFile(dhcpVmnetLeasesFile, playerDataFilePaths()) +} + +func playerVmDhcpConfPath(device string) string { + // Not used on Windows. + path, err := playerDhcpConfigPathRegistry() + if err != nil { + log.Printf("[WARN] Unable to retrieve DHCP configuration path from registry: %s", err) + } else if _, err := os.Stat(path); err == nil { + return path + } + return findFile(dhcpVmnetConfFile, playerDataFilePaths()) +} + +func playerVmnetnatConfPath(device string) string { + // Not used on Windows. + return findFile(natVmnetConfFile, playerDataFilePaths()) +} + +func playerNetmapConfPath() string { + return findFile(netmapConfFile, playerDataFilePaths()) +} + +func playerReadRegistryPath(key, subkey string) (string, error) { + s, err := readRegString(syscall.HKEY_LOCAL_MACHINE, key, subkey) + if err != nil { + return "", fmt.Errorf("unable to read registry key %s\\%s: %w", key, subkey, err) + } + return normalizePath(s), nil +} + +// Read the installation path from the registry. +func playerInstallationPath() (string, error) { + return playerReadRegistryPath(playerInstallationPathKey, "Path") +} + +// Read the DHCP leases path from the registry. +func playerDhcpLeasesPathRegistry() (string, error) { + return playerReadRegistryPath(playerRegistryKey, "LeaseFile") +} + +// Read the DHCP configuration path from the registry. +func playerDhcpConfigPathRegistry() (string, error) { + return playerReadRegistryPath(playerRegistryKey, "ConfFile") +} + +// Append path if the environment variable exists. +func appendPlayerPath(paths []string, envVar, suffix string) []string { + if value := os.Getenv(envVar); value != "" { + paths = append(paths, filepath.Join(value, suffix)) + } + return paths +} + +func playerProgramFilePaths() []string { + path, err := playerInstallationPath() + if err != nil { + log.Printf("[WARN] Unable to retrieve installation path from registry: %s", err) + } + + paths := make([]string, 0, 5) + + if homePath := os.Getenv("VMWARE_HOME"); homePath != "" { + paths = append(paths, homePath) + } + + if path != "" { + paths = append(paths, path) + } + + paths = appendPlayerPath(paths, "ProgramFiles(x86)", "VMware/VMware Player") + paths = appendPlayerPath(paths, "ProgramFiles", "VMware/VMware Player") + paths = appendPlayerPath(paths, "QEMU_HOME", "") + paths = appendPlayerPath(paths, "ProgramFiles(x86)", "QEMU") + paths = appendPlayerPath(paths, "ProgramFiles", "QEMU") + paths = appendPlayerPath(paths, "SystemDrive", "QEMU") + + return paths +} + +// Read a list of possible data paths. +func playerDataFilePaths() []string { + leasesPath, err := playerDhcpLeasesPathRegistry() + if err != nil { + log.Printf("[WARN] Unable to retrieve DHCP leases path from registry: %s", err) + } + + if leasesPath != "" { + leasesPath = filepath.Dir(leasesPath) + } + + paths := make([]string, 0, 5) + + if dataPath := os.Getenv("VMWARE_DATA"); dataPath != "" { + paths = append(paths, dataPath) + } + + if leasesPath != "" { + paths = append(paths, leasesPath) + } + + paths = appendPlayerPath(paths, "ProgramData", "VMware") + paths = appendPlayerPath(paths, "ALLUSERSPROFILE", "Application Data/VMware") + + return paths +} + +func playerVerifyVersion(version string) error { + key := `SOFTWARE\Wow6432Node\VMware, Inc.\VMware Player` + subkey := "ProductVersion" + productVersion, err := readRegString(syscall.HKEY_LOCAL_MACHINE, key, subkey) + if err != nil { + log.Printf(`[WARN] Unable to read registry key %s\%s`, key, subkey) + key = `SOFTWARE\VMware, Inc.\VMware Player` + productVersion, err = readRegString(syscall.HKEY_LOCAL_MACHINE, key, subkey) + if err != nil { + return fmt.Errorf("unable to read registry key %s\\%s: %w", key, subkey, 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("[INFO] VMware Workstation Player: %s", matches[1]) + + return compareVersions(matches[1], version, "Player") +}