From 24110664aee12b43fa96f4aae296661dfece8bc1 Mon Sep 17 00:00:00 2001 From: Ryan Johnson Date: Wed, 26 Jun 2024 15:12:22 -0400 Subject: [PATCH] refactor: consolidate fusion driver Consolidates `Fusion5Driver` and `Fusion6Driver` to `FusionDriver` withing `driver_fusion.go`. Note: Minimum version is set to VMware Fusion 13. Signed-off-by: Ryan Johnson --- builder/vmware/common/driver_fusion.go | 294 ++++++++++++++++++++++++ builder/vmware/common/driver_fusion5.go | 214 ----------------- builder/vmware/common/driver_fusion6.go | 152 ------------ 3 files changed, 294 insertions(+), 366 deletions(-) create mode 100644 builder/vmware/common/driver_fusion.go delete mode 100644 builder/vmware/common/driver_fusion5.go delete mode 100644 builder/vmware/common/driver_fusion6.go diff --git a/builder/vmware/common/driver_fusion.go b/builder/vmware/common/driver_fusion.go new file mode 100644 index 00000000..2907ddb3 --- /dev/null +++ b/builder/vmware/common/driver_fusion.go @@ -0,0 +1,294 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package common + +import ( + "bytes" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "regexp" + "runtime" + "strings" + + "github.com/hashicorp/packer-plugin-sdk/multistep" +) + +const minimumFusionVersion = "13" + +const fusionSuppressPlist = ` + + + + disallowUpgrade + + +` + +// FusionDriver is a driver that can run VMware Fusion 6. +type FusionDriver struct { + VmwareDriver + + // This is the path to the "VMware Fusion.app" + AppPath string + + // SSHConfig are the SSH settings for the Fusion VM + SSHConfig *SSHConfig +} + +func NewFusionDriver(dconfig *DriverConfig, config *SSHConfig) Driver { + return &FusionDriver{ + AppPath: dconfig.FusionAppPath, + SSHConfig: config, + } +} + +func (d *FusionDriver) CompactDisk(diskPath string) error { + 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 *FusionDriver) 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 + } + + return nil +} + +func (d *FusionDriver) CreateSnapshot(vmxPath string, snapshotName string) error { + cmd := exec.Command(d.vmrunPath(), "-T", "fusion", "snapshot", vmxPath, snapshotName) + _, _, err := runAndLog(cmd) + return err +} + +func (d *FusionDriver) IsRunning(vmxPath string) (bool, error) { + vmxPath, err := filepath.Abs(vmxPath) + if err != nil { + return false, err + } + + cmd := exec.Command(d.vmrunPath(), "-T", "fusion", "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 *FusionDriver) CommHost(state multistep.StateBag) (string, error) { + return CommHost(d.SSHConfig)(state) +} + +func (d *FusionDriver) Start(vmxPath string, headless bool) error { + guiArgument := "gui" + if headless { + guiArgument = "nogui" + } + + cmd := exec.Command(d.vmrunPath(), "-T", "fusion", "start", vmxPath, guiArgument) + if _, _, err := runAndLog(cmd); err != nil { + return err + } + + return nil +} + +func (d *FusionDriver) Stop(vmxPath string) error { + cmd := exec.Command(d.vmrunPath(), "-T", "fusion", "stop", vmxPath, "hard") + if _, _, err := runAndLog(cmd); err != nil { + // Check if the VM is running. If its not, it was already stopped + running, rerr := d.IsRunning(vmxPath) + if rerr == nil && !running { + return nil + } + + return err + } + + return nil +} + +func (d *FusionDriver) SuppressMessages(vmxPath string) error { + dir := filepath.Dir(vmxPath) + base := filepath.Base(vmxPath) + base = strings.Replace(base, ".vmx", "", -1) + + plistPath := filepath.Join(dir, base+".plist") + return os.WriteFile(plistPath, []byte(fusionSuppressPlist), 0644) +} + +func (d *FusionDriver) vdiskManagerPath() string { + return filepath.Join(d.AppPath, "Contents", "Library", "vmware-vdiskmanager") +} + +func (d *FusionDriver) vmrunPath() string { + return filepath.Join(d.AppPath, "Contents", "Library", "vmrun") +} + +func (d *FusionDriver) ToolsInstall() error { + return nil +} + +func (d *FusionDriver) Clone(dst, src string, linked bool, snapshot string) error { + + var cloneType string + if linked { + cloneType = "linked" + } else { + cloneType = "full" + } + + args := []string{"-T", "fusion", "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 *FusionDriver) Verify() error { + + if _, err := os.Stat(d.AppPath); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("Fusion application not found at path: %s", d.AppPath) + } + + return err + } + + vmxpath := filepath.Join(d.AppPath, "Contents", "Library", "vmware-vmx") + if _, err := os.Stat(vmxpath); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("vmware-vmx could not be found at path: %s", + vmxpath) + } + + return err + } + + if _, err := os.Stat(d.vmrunPath()); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf( + "Critical application 'vmrun' not found at path: %s", d.vmrunPath()) + + } + + return err + } + + if _, err := os.Stat(d.vdiskManagerPath()); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf( + "Critical application vdisk manager not found at path: %s", + d.vdiskManagerPath()) + } + + return err + } + + var stderr bytes.Buffer + cmd := exec.Command(vmxpath, "-v") + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + return err + } + + techPreviewRe := regexp.MustCompile(`(?i)VMware [a-z0-9-]+ e\.x\.p `) + matches := techPreviewRe.FindStringSubmatch(stderr.String()) + if matches != nil { + log.Printf("Detected VMware version: e.x.p (Tech Preview)") + return nil + } + + // Example: VMware Fusion 7.1.3 build-3204469 Release + versionRe := regexp.MustCompile(`(?i)VMware [a-z0-9-]+ (\d+)\.`) + matches = versionRe.FindStringSubmatch(stderr.String()) + if matches == nil { + return fmt.Errorf( + "Couldn't find VMware version in output: %s", stderr.String()) + } + log.Printf("Detected VMware version: %s", matches[1]) + + libpath := filepath.Join("/", "Library", "Preferences", "VMware Fusion") + + d.VmwareDriver.DhcpLeasesPath = func(device string) string { + return "/var/db/vmware/vmnet-dhcpd-" + device + ".leases" + } + d.VmwareDriver.DhcpConfPath = func(device string) string { + return filepath.Join(libpath, device, "dhcpd.conf") + } + + d.VmwareDriver.VmnetnatConfPath = func(device string) string { + return filepath.Join(libpath, device, "nat.conf") + } + d.VmwareDriver.NetworkMapper = func() (NetworkNameMapper, error) { + pathNetworking := filepath.Join(libpath, "networking") + if _, err := os.Stat(pathNetworking); err != nil { + return nil, fmt.Errorf("Could not find networking conf file: %s", pathNetworking) + } + log.Printf("Located networkmapper configuration file using Fusion6: %s", pathNetworking) + + fd, err := os.Open(pathNetworking) + if err != nil { + return nil, err + } + defer fd.Close() + + return ReadNetworkingConfig(fd) + } + + return compareVersions(matches[1], minimumFusionVersion, "Fusion Professional") +} + +func (d *FusionDriver) ToolsIsoPath(k string) string { + // Fusion 13.x.x changes tools iso location + vmxpath := filepath.Join(d.AppPath, "Contents", "Library", "vmware-vmx") + var stderr bytes.Buffer + cmd := exec.Command(vmxpath, "-v") + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + log.Printf("[DEBUG] failed to execute vmware-vmx command to get version %v", err) + log.Printf("[DEBUG] continuing with default iso path for fusion6+.") + return filepath.Join(d.AppPath, "Contents", "Library", "isoimages", "x86_x64", k+".iso") + } + versionRe := regexp.MustCompile(`(?i)VMware [a-z0-9-]+ (\d+)\.`) + matches := versionRe.FindStringSubmatch(stderr.String()) + if len(matches) > 0 && (matches[1] < "13") { + return filepath.Join(d.AppPath, "Contents", "Library", "isoimages", k+".iso") + } + if k == "windows" && runtime.GOARCH == "arm64" { + return filepath.Join(d.AppPath, "Contents", "Library", "isoimages", "arm64", k+".iso") + } + + return filepath.Join(d.AppPath, "Contents", "Library", "isoimages", "x86_x64", k+".iso") +} + +func (d *FusionDriver) GetVmwareDriver() VmwareDriver { + return d.VmwareDriver +} diff --git a/builder/vmware/common/driver_fusion5.go b/builder/vmware/common/driver_fusion5.go deleted file mode 100644 index adc67abe..00000000 --- a/builder/vmware/common/driver_fusion5.go +++ /dev/null @@ -1,214 +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" -) - -// Fusion5Driver is a driver that can run VMware Fusion 5. -type Fusion5Driver struct { - VmwareDriver - - // This is the path to the "VMware Fusion.app" - AppPath string - - // SSHConfig are the SSH settings for the Fusion VM - SSHConfig *SSHConfig -} - -func NewFusion5Driver(dconfig *DriverConfig, config *SSHConfig) Driver { - return &Fusion5Driver{ - AppPath: dconfig.FusionAppPath, - SSHConfig: config, - } -} - -func (d *Fusion5Driver) Clone(dst, src string, linked bool, snapshot string) error { - return errors.New("linked clones are not supported on this version VMware Fusion, please upgrade") -} - -func (d *Fusion5Driver) CompactDisk(diskPath string) error { - 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 *Fusion5Driver) 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 - } - - return nil -} - -func (d *Fusion5Driver) CreateSnapshot(vmxPath string, snapshotName string) error { - cmd := exec.Command(d.vmrunPath(), "-T", "fusion", "snapshot", vmxPath, snapshotName) - _, _, err := runAndLog(cmd) - return err -} - -func (d *Fusion5Driver) IsRunning(vmxPath string) (bool, error) { - vmxPath, err := filepath.Abs(vmxPath) - if err != nil { - return false, err - } - - cmd := exec.Command(d.vmrunPath(), "-T", "fusion", "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 *Fusion5Driver) CommHost(state multistep.StateBag) (string, error) { - return CommHost(d.SSHConfig)(state) -} - -func (d *Fusion5Driver) Start(vmxPath string, headless bool) error { - guiArgument := "gui" - if headless { - guiArgument = "nogui" - } - - cmd := exec.Command(d.vmrunPath(), "-T", "fusion", "start", vmxPath, guiArgument) - if _, _, err := runAndLog(cmd); err != nil { - return err - } - - return nil -} - -func (d *Fusion5Driver) Stop(vmxPath string) error { - cmd := exec.Command(d.vmrunPath(), "-T", "fusion", "stop", vmxPath, "hard") - if _, _, err := runAndLog(cmd); err != nil { - // Check if the VM is running. If its not, it was already stopped - running, rerr := d.IsRunning(vmxPath) - if rerr == nil && !running { - return nil - } - - return err - } - - return nil -} - -func (d *Fusion5Driver) SuppressMessages(vmxPath string) error { - dir := filepath.Dir(vmxPath) - base := filepath.Base(vmxPath) - base = strings.Replace(base, ".vmx", "", -1) - - plistPath := filepath.Join(dir, base+".plist") - return os.WriteFile(plistPath, []byte(fusionSuppressPlist), 0644) -} - -func (d *Fusion5Driver) Verify() error { - if _, err := os.Stat(d.AppPath); err != nil { - if os.IsNotExist(err) { - return fmt.Errorf("fusion not found in path: %s", d.AppPath) - } - - return err - } - - if _, err := os.Stat(d.vmrunPath()); err != nil { - if os.IsNotExist(err) { - return fmt.Errorf("'vmrun' not found in path: %s", d.vmrunPath()) - } - - return err - } - - if _, err := os.Stat(d.vdiskManagerPath()); err != nil { - if os.IsNotExist(err) { - return fmt.Errorf("error finding either 'vmware-vdiskmanager' or 'qemu-img' in path: %s", d.vdiskManagerPath()) - } - - return err - } - - libpath := filepath.Join("/", "Library", "Preferences", "VMware Fusion") - - d.VmwareDriver.DhcpLeasesPath = func(device string) string { - return "/var/db/vmware/vmnet-dhcpd-" + device + ".leases" - } - d.VmwareDriver.DhcpConfPath = func(device string) string { - return filepath.Join(libpath, device, "dhcpd.conf") - } - d.VmwareDriver.VmnetnatConfPath = func(device string) string { - return filepath.Join(libpath, device, "nat.conf") - } - d.VmwareDriver.NetworkMapper = func() (NetworkNameMapper, error) { - pathNetworking := filepath.Join(libpath, "networking") - if _, err := os.Stat(pathNetworking); err != nil { - return nil, fmt.Errorf("error finding networking configuration file %q: %s", pathNetworking, err) - } - log.Printf("[INFO] Located networkmapper configuration file: %s", pathNetworking) - - fd, err := os.Open(pathNetworking) - if err != nil { - return nil, err - } - defer fd.Close() - - return ReadNetworkingConfig(fd) - } - - return nil -} - -func (d *Fusion5Driver) vdiskManagerPath() string { - return filepath.Join(d.AppPath, "Contents", "Library", "vmware-vdiskmanager") -} - -func (d *Fusion5Driver) vmrunPath() string { - return filepath.Join(d.AppPath, "Contents", "Library", "vmrun") -} - -func (d *Fusion5Driver) ToolsIsoPath(k string) string { - return filepath.Join(d.AppPath, "Contents", "Library", "isoimages", k+".iso") -} - -func (d *Fusion5Driver) ToolsInstall() error { - return nil -} - -const fusionSuppressPlist = ` - - - - disallowUpgrade - - -` - -func (d *Fusion5Driver) GetVmwareDriver() VmwareDriver { - return d.VmwareDriver -} diff --git a/builder/vmware/common/driver_fusion6.go b/builder/vmware/common/driver_fusion6.go deleted file mode 100644 index 411073dd..00000000 --- a/builder/vmware/common/driver_fusion6.go +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "bytes" - "fmt" - "log" - "os" - "os/exec" - "path/filepath" - "regexp" - "runtime" - "strings" -) - -const VMWARE_FUSION_VERSION = "6" - -// Fusion6Driver is a driver that can run VMware Fusion 6. -type Fusion6Driver struct { - Fusion5Driver -} - -func NewFusion6Driver(dconfig *DriverConfig, config *SSHConfig) Driver { - return &Fusion6Driver{ - Fusion5Driver: Fusion5Driver{ - AppPath: dconfig.FusionAppPath, - SSHConfig: config, - }, - } -} - -func (d *Fusion6Driver) Clone(dst, src string, linked bool, snapshot string) error { - - var cloneType string - if linked { - cloneType = "linked" - } else { - cloneType = "full" - } - - args := []string{"-T", "fusion", "clone", src, dst, cloneType} - if snapshot != "" { - args = append(args, "-snapshot", snapshot) - } - cmd := exec.Command(d.vmrunPath(), args...) - if _, _, err := runAndLog(cmd); err != nil { - if strings.Contains(err.Error(), "parameters was invalid") { - return fmt.Errorf("linked clones are not supported on this version") - } - - return err - } - - return nil -} - -func (d *Fusion6Driver) Verify() error { - if err := d.Fusion5Driver.Verify(); err != nil { - return err - } - - vmxpath := filepath.Join(d.AppPath, "Contents", "Library", "vmware-vmx") - if _, err := os.Stat(vmxpath); err != nil { - if os.IsNotExist(err) { - return fmt.Errorf("'vmware-vmx' not found in path: %s", vmxpath) - } - - return err - } - - var stderr bytes.Buffer - cmd := exec.Command(vmxpath, "-v") - cmd.Stderr = &stderr - if err := cmd.Run(); err != nil { - return err - } - - // Example: VMware Fusion e.x.p build-6048684 Release - techPreviewRe := regexp.MustCompile(`(?i)VMware [a-z0-9-]+ e\.x\.p `) - matches := techPreviewRe.FindStringSubmatch(stderr.String()) - if matches != nil { - log.Printf("Detected VMware version: e.x.p (Tech Preview)") - return nil - } - - // Example: VMware Fusion 7.1.3 build-3204469 Release - versionRe := regexp.MustCompile(`(?i)VMware [a-z0-9-]+ (\d+)\.`) - matches = versionRe.FindStringSubmatch(stderr.String()) - if matches == nil { - return fmt.Errorf("error parsing version output: %s", stderr.String()) - } - log.Printf("Detected VMware version: %s", matches[1]) - - libpath := filepath.Join("/", "Library", "Preferences", "VMware Fusion") - - d.VmwareDriver.DhcpLeasesPath = func(device string) string { - return "/var/db/vmware/vmnet-dhcpd-" + device + ".leases" - } - d.VmwareDriver.DhcpConfPath = func(device string) string { - return filepath.Join(libpath, device, "dhcpd.conf") - } - - d.VmwareDriver.VmnetnatConfPath = func(device string) string { - return filepath.Join(libpath, device, "nat.conf") - } - d.VmwareDriver.NetworkMapper = func() (NetworkNameMapper, error) { - pathNetworking := filepath.Join(libpath, "networking") - if _, err := os.Stat(pathNetworking); err != nil { - return nil, fmt.Errorf("error finding networking configuration file: %s", pathNetworking) - } - log.Printf("Located networkmapper configuration file: %s", pathNetworking) - - fd, err := os.Open(pathNetworking) - if err != nil { - return nil, err - } - defer fd.Close() - - return ReadNetworkingConfig(fd) - } - - return compareVersions(matches[1], VMWARE_FUSION_VERSION, "Fusion Professional") -} - -func (d *Fusion6Driver) ToolsIsoPath(k string) string { - // Fusion 13.x.x changes tools iso location - vmxpath := filepath.Join(d.AppPath, "Contents", "Library", "vmware-vmx") - var stderr bytes.Buffer - cmd := exec.Command(vmxpath, "-v") - cmd.Stderr = &stderr - if err := cmd.Run(); err != nil { - log.Printf("[DEBUG] failed to execute vmware-vmx command to get version %v", err) - log.Printf("[DEBUG] continuing with default iso path for fusion6+.") - return filepath.Join(d.AppPath, "Contents", "Library", "isoimages", "x86_x64", k+".iso") - } - versionRe := regexp.MustCompile(`(?i)VMware [a-z0-9-]+ (\d+)\.`) - matches := versionRe.FindStringSubmatch(stderr.String()) - if len(matches) > 0 && (matches[1] < "13") { - return filepath.Join(d.AppPath, "Contents", "Library", "isoimages", k+".iso") - } - if k == "windows" && runtime.GOARCH == "arm64" { - return filepath.Join(d.AppPath, "Contents", "Library", "isoimages", "arm64", k+".iso") - } - - return filepath.Join(d.AppPath, "Contents", "Library", "isoimages", "x86_x64", k+".iso") -} - -func (d *Fusion6Driver) GetVmwareDriver() VmwareDriver { - return d.Fusion5Driver.VmwareDriver -}