diff --git a/builder/vmware/common/driver.go b/builder/vmware/common/driver.go index 8fa185efd..47dc6e8b6 100644 --- a/builder/vmware/common/driver.go +++ b/builder/vmware/common/driver.go @@ -315,7 +315,7 @@ func (d *VmwareDriver) GuestIP(state multistep.StateBag) (string, error) { // convert the stashed network to a device network := state.Get("vmnetwork").(string) - device, err := netmap.NameIntoDevice(network) + devices, err := netmap.NameIntoDevices(network) // we were unable to find the device, maybe it's a custom one... // so, check to see if it's in the .vmx configuration @@ -326,7 +326,9 @@ func (d *VmwareDriver) GuestIP(state multistep.StateBag) (string, error) { return "", err } + var device string device, err = readCustomDeviceName(vmxData) + devices = append(devices, device) if err != nil { return "", err } @@ -338,64 +340,67 @@ func (d *VmwareDriver) GuestIP(state multistep.StateBag) (string, error) { return "", err } - // figure out the correct dhcp leases - dhcpLeasesPath := d.DhcpLeasesPath(device) - log.Printf("DHCP leases path: %s", dhcpLeasesPath) - if dhcpLeasesPath == "" { - return "", errors.New("no DHCP leases path found.") - } - - // open up the lease and read its contents - fh, err := os.Open(dhcpLeasesPath) - if err != nil { - return "", err - } - defer fh.Close() - - dhcpBytes, err := ioutil.ReadAll(fh) - if err != nil { - return "", err - } - - // start grepping through the file looking for fields that we care about - var lastIp string - var lastLeaseEnd time.Time - - var curIp string - var curLeaseEnd time.Time - - ipLineRe := regexp.MustCompile(`^lease (.+?) {$`) - endTimeLineRe := regexp.MustCompile(`^\s*ends \d (.+?);$`) - macLineRe := regexp.MustCompile(`^\s*hardware ethernet (.+?);$`) - - for _, line := range strings.Split(string(dhcpBytes), "\n") { - // Need to trim off CR character when running in windows - line = strings.TrimRight(line, "\r") - - matches := ipLineRe.FindStringSubmatch(line) - if matches != nil { - lastIp = matches[1] - continue + for _, device := range devices { + // figure out the correct dhcp leases + dhcpLeasesPath := d.DhcpLeasesPath(device) + log.Printf("Trying DHCP leases path: %s", dhcpLeasesPath) + if dhcpLeasesPath == "" { + return "", fmt.Errorf("no DHCP leases path found for device %s", device) } - matches = endTimeLineRe.FindStringSubmatch(line) - if matches != nil { - lastLeaseEnd, _ = time.Parse("2006/01/02 15:04:05", matches[1]) - continue + // open up the lease and read its contents + fh, err := os.Open(dhcpLeasesPath) + if err != nil { + return "", err + } + defer fh.Close() + + dhcpBytes, err := ioutil.ReadAll(fh) + if err != nil { + return "", err } - // If the mac address matches and this lease ends farther in the - // future than the last match we might have, then choose it. - matches = macLineRe.FindStringSubmatch(line) - if matches != nil && strings.EqualFold(matches[1], MACAddress) && curLeaseEnd.Before(lastLeaseEnd) { - curIp = lastIp - curLeaseEnd = lastLeaseEnd + // start grepping through the file looking for fields that we care about + var lastIp string + var lastLeaseEnd time.Time + + var curIp string + var curLeaseEnd time.Time + + ipLineRe := regexp.MustCompile(`^lease (.+?) {$`) + endTimeLineRe := regexp.MustCompile(`^\s*ends \d (.+?);$`) + macLineRe := regexp.MustCompile(`^\s*hardware ethernet (.+?);$`) + + for _, line := range strings.Split(string(dhcpBytes), "\n") { + // Need to trim off CR character when running in windows + line = strings.TrimRight(line, "\r") + + matches := ipLineRe.FindStringSubmatch(line) + if matches != nil { + lastIp = matches[1] + continue + } + + matches = endTimeLineRe.FindStringSubmatch(line) + if matches != nil { + lastLeaseEnd, _ = time.Parse("2006/01/02 15:04:05", matches[1]) + continue + } + + // If the mac address matches and this lease ends farther in the + // future than the last match we might have, then choose it. + matches = macLineRe.FindStringSubmatch(line) + if matches != nil && strings.EqualFold(matches[1], MACAddress) && curLeaseEnd.Before(lastLeaseEnd) { + curIp = lastIp + curLeaseEnd = lastLeaseEnd + } + } + if curIp != "" { + return curIp, nil } } - if curIp == "" { - return "", fmt.Errorf("IP not found for MAC %s in DHCP leases at %s", MACAddress, dhcpLeasesPath) - } - return curIp, nil + + return "", fmt.Errorf("None of the found device(s) %v has a DHCP lease for MAC %s", devices, MACAddress) } func (d *VmwareDriver) HostAddress(state multistep.StateBag) (string, error) { @@ -408,7 +413,7 @@ func (d *VmwareDriver) HostAddress(state multistep.StateBag) (string, error) { // convert network to name network := state.Get("vmnetwork").(string) - device, err := netmap.NameIntoDevice(network) + devices, err := netmap.NameIntoDevices(network) // we were unable to find the device, maybe it's a custom one... // so, check to see if it's in the .vmx configuration @@ -419,49 +424,56 @@ func (d *VmwareDriver) HostAddress(state multistep.StateBag) (string, error) { return "", err } + var device string device, err = readCustomDeviceName(vmxData) + devices = append(devices, device) if err != nil { return "", err } } - // parse dhcpd configuration - pathDhcpConfig := d.DhcpConfPath(device) - if _, err := os.Stat(pathDhcpConfig); err != nil { - return "", fmt.Errorf("Could not find vmnetdhcp conf file: %s", pathDhcpConfig) - } - - config, err := ReadDhcpConfig(pathDhcpConfig) - if err != nil { - return "", err - } - - // find the entry configured in the dhcpd - interfaceConfig, err := config.HostByName(device) - if err != nil { - return "", err - } - - // finally grab the hardware address - address, err := interfaceConfig.Hardware() - if err == nil { - return address.String(), nil - } - - // we didn't find it, so search through our interfaces for the device name - interfaceList, err := net.Interfaces() - if err == nil { - return "", err - } - - names := make([]string, 0) - for _, intf := range interfaceList { - if strings.HasSuffix(strings.ToLower(intf.Name), device) { - return intf.HardwareAddr.String(), nil + var lastError error + for _, device := range devices { + // parse dhcpd configuration + pathDhcpConfig := d.DhcpConfPath(device) + if _, err := os.Stat(pathDhcpConfig); err != nil { + return "", fmt.Errorf("Could not find vmnetdhcp conf file: %s", pathDhcpConfig) + } + + config, err := ReadDhcpConfig(pathDhcpConfig) + if err != nil { + lastError = err + continue + } + + // find the entry configured in the dhcpd + interfaceConfig, err := config.HostByName(device) + if err != nil { + lastError = err + continue + } + + // finally grab the hardware address + address, err := interfaceConfig.Hardware() + if err == nil { + return address.String(), nil + } + + // we didn't find it, so search through our interfaces for the device name + interfaceList, err := net.Interfaces() + if err == nil { + return "", err + } + + names := make([]string, 0) + for _, intf := range interfaceList { + if strings.HasSuffix(strings.ToLower(intf.Name), device) { + return intf.HardwareAddr.String(), nil + } + names = append(names, intf.Name) } - names = append(names, intf.Name) } - return "", fmt.Errorf("Unable to find device %s : %v", device, names) + return "", fmt.Errorf("Unable to find host address from devices %v, last error: %s", devices, lastError) } func (d *VmwareDriver) HostIP(state multistep.StateBag) (string, error) { @@ -474,7 +486,7 @@ func (d *VmwareDriver) HostIP(state multistep.StateBag) (string, error) { // convert network to name network := state.Get("vmnetwork").(string) - device, err := netmap.NameIntoDevice(network) + devices, err := netmap.NameIntoDevices(network) // we were unable to find the device, maybe it's a custom one... // so, check to see if it's in the .vmx configuration @@ -485,32 +497,41 @@ func (d *VmwareDriver) HostIP(state multistep.StateBag) (string, error) { return "", err } + var device string device, err = readCustomDeviceName(vmxData) + devices = append(devices, device) if err != nil { return "", err } } - // parse dhcpd configuration - pathDhcpConfig := d.DhcpConfPath(device) - if _, err := os.Stat(pathDhcpConfig); err != nil { - return "", fmt.Errorf("Could not find vmnetdhcp conf file: %s", pathDhcpConfig) - } - config, err := ReadDhcpConfig(pathDhcpConfig) - if err != nil { - return "", err - } + var lastError error + for _, device := range devices { + // parse dhcpd configuration + pathDhcpConfig := d.DhcpConfPath(device) + if _, err := os.Stat(pathDhcpConfig); err != nil { + return "", fmt.Errorf("Could not find vmnetdhcp conf file: %s", pathDhcpConfig) + } + config, err := ReadDhcpConfig(pathDhcpConfig) + if err != nil { + lastError = err + continue + } - // find the entry configured in the dhcpd - interfaceConfig, err := config.HostByName(device) - if err != nil { - return "", err - } + // find the entry configured in the dhcpd + interfaceConfig, err := config.HostByName(device) + if err != nil { + lastError = err + continue + } - address, err := interfaceConfig.IP4() - if err != nil { - return "", err - } + address, err := interfaceConfig.IP4() + if err != nil { + lastError = err + continue + } - return address.String(), nil + return address.String(), nil + } + return "", fmt.Errorf("Unable to find host IP from devices %v, last error: %s", devices, lastError) } diff --git a/builder/vmware/common/driver_mock.go b/builder/vmware/common/driver_mock.go index a0955db88..8c540913e 100644 --- a/builder/vmware/common/driver_mock.go +++ b/builder/vmware/common/driver_mock.go @@ -98,9 +98,9 @@ type NetworkMapperMock struct { DeviceIntoNameCalled int } -func (m NetworkMapperMock) NameIntoDevice(name string) (string, error) { +func (m NetworkMapperMock) NameIntoDevices(name string) ([]string, error) { m.NameIntoDeviceCalled += 1 - return "", nil + return make([]string, 0), nil } func (m NetworkMapperMock) DeviceIntoName(device string) (string, error) { m.DeviceIntoNameCalled += 1 diff --git a/builder/vmware/common/driver_parser.go b/builder/vmware/common/driver_parser.go index 24ecaf0df..fc54b8221 100644 --- a/builder/vmware/common/driver_parser.go +++ b/builder/vmware/common/driver_parser.go @@ -1065,7 +1065,7 @@ func (e *DhcpConfiguration) HostByName(host string) (configDeclaration, error) { type NetworkMap []map[string]string type NetworkNameMapper interface { - NameIntoDevice(string) (string, error) + NameIntoDevices(string) ([]string, error) DeviceIntoName(string) (string, error) } @@ -1082,13 +1082,17 @@ func ReadNetworkMap(fd *os.File) (NetworkMap, error) { return result, nil } -func (e NetworkMap) NameIntoDevice(name string) (string, error) { +func (e NetworkMap) NameIntoDevices(name string) ([]string, error) { + var devices []string for _, val := range e { if strings.ToLower(val["name"]) == strings.ToLower(name) { - return val["device"], nil + devices = append(devices, val["device"]) } } - return "", fmt.Errorf("Network name not found : %v", name) + if len(devices) > 0 { + return devices, nil + } + return make([]string, 0), fmt.Errorf("Network name not found : %v", name) } func (e NetworkMap) DeviceIntoName(device string) (string, error) { for _, val := range e { @@ -1905,21 +1909,26 @@ func networkingConfig_NamesToVmnet(config NetworkingConfig) map[NetworkingType][ const NetworkingInterfacePrefix = "vmnet" -func (e NetworkingConfig) NameIntoDevice(name string) (string, error) { +func (e NetworkingConfig) NameIntoDevices(name string) ([]string, error) { netmapper := networkingConfig_NamesToVmnet(e) name = strings.ToLower(name) - var vmnet int + var vmnets []string + var networkingType NetworkingType if name == "hostonly" && len(netmapper[NetworkingType_HOSTONLY]) > 0 { - vmnet = netmapper[NetworkingType_HOSTONLY][0] + networkingType = NetworkingType_HOSTONLY } else if name == "nat" && len(netmapper[NetworkingType_NAT]) > 0 { - vmnet = netmapper[NetworkingType_NAT][0] + networkingType = NetworkingType_NAT } else if name == "bridged" && len(netmapper[NetworkingType_BRIDGED]) > 0 { - vmnet = netmapper[NetworkingType_BRIDGED][0] + networkingType = NetworkingType_BRIDGED } else { - return "", fmt.Errorf("Network name not found : %v", name) + return make([]string, 0), fmt.Errorf("Network name not found: %v", name) } - return fmt.Sprintf("%s%d", NetworkingInterfacePrefix, vmnet), nil + + for i := 0; i < len(netmapper[networkingType]); i++ { + vmnets = append(vmnets, fmt.Sprintf("%s%d", NetworkingInterfacePrefix, netmapper[networkingType][i])) + } + return vmnets, nil } func (e NetworkingConfig) DeviceIntoName(device string) (string, error) { diff --git a/builder/vmware/iso/step_create_vmx.go b/builder/vmware/iso/step_create_vmx.go index ffeb724ec..7dbb01b79 100644 --- a/builder/vmware/iso/step_create_vmx.go +++ b/builder/vmware/iso/step_create_vmx.go @@ -475,14 +475,19 @@ func (s *stepCreateVMX) Run(_ context.Context, state multistep.StateBag) multist } // try and convert the specified network to a device. - device, err := netmap.NameIntoDevice(network) + devices, err := netmap.NameIntoDevices(network) - if err == nil { - // success. so we know that it's an actual network type inside netmap.conf + if err == nil && len(devices) > 0 { + // If multiple devices exist, for example for network "nat", VMware chooses + // the actual device. Only type "custom" allows the exact choice of a + // specific virtual network (see below). We allow VMware to choose the device + // and for device-specific operations like GuestIP, try to go over all + // devices that match a name (e.g. "nat"). + // https://pubs.vmware.com/workstation-9/index.jsp?topic=%2Fcom.vmware.ws.using.doc%2FGUID-3B504F2F-7A0B-415F-AE01-62363A95D052.html templateData.Network_Type = network - templateData.Network_Device = device + templateData.Network_Device = "" } else { - // otherwise, we were unable to find the type, so assume its a custom device. + // otherwise, we were unable to find the type, so assume it's a custom device templateData.Network_Type = "custom" templateData.Network_Device = network }