diff --git a/builder/vmware/common/driver_workstation9.go b/builder/vmware/common/driver_workstation9.go index 7a75bdb8..973f0a5a 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,113 @@ 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. + // 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) + + // If the default network mapper configuration file exists, read the + // configuration. + log.Printf("Located the network mapper configuration file: %s", pathNetmap) + return ReadNetmapConfig(pathNetmap) +} + +// 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") + } +}