From 1933466e3ac2a0df8a098a97582c9a2dba0fa138 Mon Sep 17 00:00:00 2001 From: Ryan Johnson Date: Fri, 5 Jul 2024 09:22:54 -0400 Subject: [PATCH] fix: generate default `netmap.conf`, if required (#198) * fix: generate default `netmap.conf`, if required Generates the `netmap.conf` in the default location if the file does not exist in the default path or the alternate path. There have been reported issues (GH, Discuss, blogs) of fresh installations of Workstation 17 (and 16) not generating the default file which will cause a build to fail on a newly configured system. Ref: #65 * Update builder/vmware/common/driver_workstation9.go --------- Signed-off-by: Ryan Johnson Co-authored-by: Wilken Rivera --- builder/vmware/common/driver_workstation9.go | 178 +++++++++++++++++-- 1 file changed, 160 insertions(+), 18 deletions(-) diff --git a/builder/vmware/common/driver_workstation9.go b/builder/vmware/common/driver_workstation9.go index 7a75bdb8..6f2de250 100644 --- a/builder/vmware/common/driver_workstation9.go +++ b/builder/vmware/common/driver_workstation9.go @@ -6,15 +6,52 @@ package common import ( "errors" "fmt" + "html/template" "log" "os" "os/exec" "path/filepath" + "runtime" + "sort" "strings" "github.com/hashicorp/packer-plugin-sdk/multistep" ) +// Template for the network mapper configuration file, 'netmap.conf'. +// This file is used to map network devices to their respective network names. +// This template is used to generate the file if if the default file does not +// exist on the system. +const netmapTemplate = ` +# This file is automatically generated. +# Hand-editing this file is not recommended. +network0.name = "Bridged" +network0.device = "vmnet0" +network1.name = "HostOnly" +network1.device = "vmnet1" +{{- range . }} +{{- if gt .Index 1 }} +{{- if lt .Index 8 }} +network{{ .Index }}.name = "VMNet{{ .Index }}" +network{{ .Index }}.device = "vmnet{{ .Index }}" +{{- else if eq .Index 8 }} +network8.name = "NAT" +network8.device = "vmnet8" +{{- else }} +network{{ .Index }}.name = "VMNet{{ .Index }}" +network{{ .Index }}.device = "vmnet{{ .Index }}" +{{- end }} +{{- end }} +{{- end }} +` + +// NetmapConfig is a struct that represents a network mapper configuration. +type NetmapConfig struct { + Index int + Name string + Device string +} + // Workstation9Driver is a driver that can run VMware Workstation 9 type Workstation9Driver struct { VmwareDriver @@ -173,33 +210,34 @@ func (d *Workstation9Driver) Verify() error { } d.VmwareDriver.NetworkMapper = func() (NetworkNameMapper, error) { - pathNetmap := workstationNetmapConfPath() + // Check if the network mapper configuration file exists. + mapper, err := checkNetmapConfExists() + + if err == nil { + return mapper, nil + } - // Check that the file for the networkmapper configuration exists. If there's no - // error, then the file exists and we can proceed to read the configuration out of it. - if _, err := os.Stat(pathNetmap); err == nil { - log.Printf("Located networkmapper configuration file: %s", pathNetmap) - return ReadNetmapConfig(pathNetmap) + // For non-Windows, if neither the default nor alternate configuration + // paths exist, return an error. + if err != nil && runtime.GOOS != "windows" { + return nil, err } - // 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, _ := workstationVMwareRoot() - 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) + if !errors.Is(err, os.ErrNotExist) { + return nil, err } - // 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) + // For Windows, safely generate the network mapper configuration file + // in the default path. + pathNetmap, err := generateNetmapConfig() if err != nil { + log.Fatalf("error generating the absent network configuration file: %v", err) return nil, err } - defer fd.Close() - // Then we pass the handle to the networking configuration parser. - return ReadNetworkingConfig(fd) + // If the file exists, attempt to read the newly generated network + // mapper configuration file. + return ReadNetmapConfig(pathNetmap) } return nil } @@ -215,3 +253,107 @@ func (d *Workstation9Driver) ToolsInstall() error { func (d *Workstation9Driver) GetVmwareDriver() VmwareDriver { return d.VmwareDriver } + +// checkNetmapConfExists checks if the network mapper configuration file exists. +// If not, if attempts to generate the file using generateNetmapConfig. +func checkNetmapConfExists() (NetworkNameMapper, error) { + pathNetmap := workstationNetmapConfPath() + + // Check if the default network mapper configuration file exists. + _, err := os.Stat(pathNetmap) + + if err == nil { + // If the default network mapper configuration file exists, read the configuration. + log.Printf("Located the network mapper configuration file: %s", pathNetmap) + return ReadNetmapConfig(pathNetmap) + } + + if !os.IsNotExist(err) { + return nil, fmt.Errorf("error determining network mappings from files %w", err) + } + + log.Printf("A network mapper configuration file does not exist in the default path: %s", pathNetmap) + + // The file does not exist, check the alternate configuration path. + libpath, _ := workstationVMwareRoot() + pathNetworking := filepath.Join(libpath, "networking") + log.Printf("Checking alternate path for network mapper configuration file: %s", pathNetworking) + _, err = os.Stat(pathNetworking) + + // If there is an error, wrap return it. + // The caller can check if it is a ErrNotExist and act accordingly. + if err != nil { + return nil, fmt.Errorf("error determining network mappings from files %w", err) + } + + // Alternate networking configuration path exists. Using this path. + log.Printf("Located the network mapper configuration file: %s", pathNetworking) + fd, err := os.Open(pathNetworking) + if err != nil { + return nil, err + } + + defer fd.Close() + + // Pass the handle to the networking configuration parser. + return ReadNetworkingConfig(fd) +} + +// generateNetmapConfig creates the network mapper configuration file in the +// default path using the netmapTemplate and returns the path of the created +// file. +func generateNetmapConfig() (string, error) { + var paths []string + if os.Getenv("ProgramData") != "" { + paths = append(paths, filepath.Join(os.Getenv("ProgramData"), "VMware")) + } + + networks := []NetmapConfig{ + {0, "Bridged", "vmnet0"}, + {1, "HostOnly", "vmnet1"}, + {8, "NAT", "vmnet8"}, + } + for i := 2; i <= 19; i++ { + if i != 8 { + networks = append(networks, NetmapConfig{i, fmt.Sprintf("VMNet%d", i), fmt.Sprintf("vmnet%d", i)}) + } + } + + sort.Slice(networks, func(i, j int) bool { + return networks[i].Index < networks[j].Index + }) + + t, err := template.New("netmap").Parse(netmapTemplate) + if err != nil { + return "", err + } + + var sb strings.Builder + if err := t.Execute(&sb, networks); err != nil { + return "", err + } + netmapContent := []byte(sb.String()) + + var pathNetmap string + for _, basePath := range paths { + path := filepath.Join(basePath, "netmap.conf") + if err := os.MkdirAll(basePath, 0755); err != nil { + continue // Skip to the next path on error. + } + if err := os.WriteFile(path, netmapContent, 0644); err != nil { + continue // Skip to the next path on error. + } + + // If the file exists, set pathNetmap and break the loop. + if _, err := os.Stat(path); err == nil { + pathNetmap = path + break // Exit the loop since the file was successfully created. + } + } + + if pathNetmap != "" { + return pathNetmap, nil + } else { + return "", fmt.Errorf("no valid path found for generating the network mapper configuration file") + } +}