github.com/mitchellh/packer@v1.3.2/builder/vmware/common/driver.go (about)

     1  package common
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"log"
     9  	"net"
    10  	"os"
    11  	"os/exec"
    12  	"regexp"
    13  	"runtime"
    14  	"strconv"
    15  	"strings"
    16  	"time"
    17  
    18  	"github.com/hashicorp/packer/helper/multistep"
    19  )
    20  
    21  // A driver is able to talk to VMware, control virtual machines, etc.
    22  type Driver interface {
    23  	// Clone clones the VMX and the disk to the destination path. The
    24  	// destination is a path to the VMX file. The disk will be copied
    25  	// to that same directory.
    26  	Clone(dst string, src string, cloneType bool) error
    27  
    28  	// CompactDisk compacts a virtual disk.
    29  	CompactDisk(string) error
    30  
    31  	// CreateDisk creates a virtual disk with the given size.
    32  	CreateDisk(string, string, string, string) error
    33  
    34  	// Checks if the VMX file at the given path is running.
    35  	IsRunning(string) (bool, error)
    36  
    37  	// Start starts a VM specified by the path to the VMX given.
    38  	Start(string, bool) error
    39  
    40  	// Stop stops a VM specified by the path to the VMX given.
    41  	Stop(string) error
    42  
    43  	// SuppressMessages modifies the VMX or surrounding directory so that
    44  	// VMware doesn't show any annoying messages.
    45  	SuppressMessages(string) error
    46  
    47  	// Get the path to the VMware ISO for the given flavor.
    48  	ToolsIsoPath(string) string
    49  
    50  	// Attach the VMware tools ISO
    51  	ToolsInstall() error
    52  
    53  	// Verify checks to make sure that this driver should function
    54  	// properly. This should check that all the files it will use
    55  	// appear to exist and so on. If everything is okay, this doesn't
    56  	// return an error. Otherwise, this returns an error. Each vmware
    57  	// driver should assign the VmwareMachine callback functions for locating
    58  	// paths within this function.
    59  	Verify() error
    60  
    61  	/// This is to establish a connection to the guest
    62  	CommHost(multistep.StateBag) (string, error)
    63  
    64  	/// These methods are generally implemented by the VmwareDriver
    65  	/// structure within this file. A driver implementation can
    66  	/// reimplement these, though, if it wants.
    67  	GetVmwareDriver() VmwareDriver
    68  
    69  	// Get the guest hw address for the vm
    70  	GuestAddress(multistep.StateBag) (string, error)
    71  
    72  	// Get the guest ip address for the vm
    73  	GuestIP(multistep.StateBag) (string, error)
    74  
    75  	// Get the host hw address for the vm
    76  	HostAddress(multistep.StateBag) (string, error)
    77  
    78  	// Get the host ip address for the vm
    79  	HostIP(multistep.StateBag) (string, error)
    80  }
    81  
    82  // NewDriver returns a new driver implementation for this operating
    83  // system, or an error if the driver couldn't be initialized.
    84  func NewDriver(dconfig *DriverConfig, config *SSHConfig) (Driver, error) {
    85  	drivers := []Driver{}
    86  
    87  	switch runtime.GOOS {
    88  	case "darwin":
    89  		drivers = []Driver{
    90  			&Fusion6Driver{
    91  				Fusion5Driver: Fusion5Driver{
    92  					AppPath:   dconfig.FusionAppPath,
    93  					SSHConfig: config,
    94  				},
    95  			},
    96  			&Fusion5Driver{
    97  				AppPath:   dconfig.FusionAppPath,
    98  				SSHConfig: config,
    99  			},
   100  		}
   101  	case "linux":
   102  		fallthrough
   103  	case "windows":
   104  		drivers = []Driver{
   105  			&Workstation10Driver{
   106  				Workstation9Driver: Workstation9Driver{
   107  					SSHConfig: config,
   108  				},
   109  			},
   110  			&Workstation9Driver{
   111  				SSHConfig: config,
   112  			},
   113  			&Player6Driver{
   114  				Player5Driver: Player5Driver{
   115  					SSHConfig: config,
   116  				},
   117  			},
   118  			&Player5Driver{
   119  				SSHConfig: config,
   120  			},
   121  		}
   122  	default:
   123  		return nil, fmt.Errorf("can't find driver for OS: %s", runtime.GOOS)
   124  	}
   125  
   126  	errs := ""
   127  	for _, driver := range drivers {
   128  		err := driver.Verify()
   129  		log.Printf("Testing vmware driver %T. Success: %t",
   130  			driver, err == nil)
   131  
   132  		if err == nil {
   133  			return driver, nil
   134  		}
   135  		errs += "* " + err.Error() + "\n"
   136  	}
   137  
   138  	return nil, fmt.Errorf(
   139  		"Unable to initialize any driver for this platform. The errors\n"+
   140  			"from each driver are shown below. Please fix at least one driver\n"+
   141  			"to continue:\n%s", errs)
   142  }
   143  
   144  func runAndLog(cmd *exec.Cmd) (string, string, error) {
   145  	var stdout, stderr bytes.Buffer
   146  
   147  	log.Printf("Executing: %s %s", cmd.Path, strings.Join(cmd.Args[1:], " "))
   148  	cmd.Stdout = &stdout
   149  	cmd.Stderr = &stderr
   150  	err := cmd.Run()
   151  
   152  	stdoutString := strings.TrimSpace(stdout.String())
   153  	stderrString := strings.TrimSpace(stderr.String())
   154  
   155  	if _, ok := err.(*exec.ExitError); ok {
   156  		message := stderrString
   157  		if message == "" {
   158  			message = stdoutString
   159  		}
   160  
   161  		err = fmt.Errorf("VMware error: %s", message)
   162  
   163  		// If "unknown error" is in there, add some additional notes
   164  		re := regexp.MustCompile(`(?i)unknown error`)
   165  		if re.MatchString(message) {
   166  			err = fmt.Errorf(
   167  				"%s\n\n%s", err,
   168  				"Packer detected a VMware 'Unknown Error'. Unfortunately VMware\n"+
   169  					"often has extremely vague error messages such as this and Packer\n"+
   170  					"itself can't do much about that. Please check the vmware.log files\n"+
   171  					"created by VMware when a VM is started (in the directory of the\n"+
   172  					"vmx file), which often contains more detailed error information.")
   173  		}
   174  	}
   175  
   176  	log.Printf("stdout: %s", stdoutString)
   177  	log.Printf("stderr: %s", stderrString)
   178  
   179  	// Replace these for Windows, we only want to deal with Unix
   180  	// style line endings.
   181  	returnStdout := strings.Replace(stdout.String(), "\r\n", "\n", -1)
   182  	returnStderr := strings.Replace(stderr.String(), "\r\n", "\n", -1)
   183  
   184  	return returnStdout, returnStderr, err
   185  }
   186  
   187  func normalizeVersion(version string) (string, error) {
   188  	i, err := strconv.Atoi(version)
   189  	if err != nil {
   190  		return "", fmt.Errorf(
   191  			"VMware version '%s' is not numeric", version)
   192  	}
   193  
   194  	return fmt.Sprintf("%02d", i), nil
   195  }
   196  
   197  func compareVersions(versionFound string, versionWanted string, product string) error {
   198  	found, err := normalizeVersion(versionFound)
   199  	if err != nil {
   200  		return err
   201  	}
   202  
   203  	wanted, err := normalizeVersion(versionWanted)
   204  	if err != nil {
   205  		return err
   206  	}
   207  
   208  	if found < wanted {
   209  		return fmt.Errorf(
   210  			"VMware %s version %s, or greater, is required. Found version: %s", product, versionWanted, versionFound)
   211  	}
   212  
   213  	return nil
   214  }
   215  
   216  /// helper functions that read configuration information from a file
   217  // read the network<->device configuration out of the specified path
   218  func ReadNetmapConfig(path string) (NetworkMap, error) {
   219  	fd, err := os.Open(path)
   220  	if err != nil {
   221  		return nil, err
   222  	}
   223  	defer fd.Close()
   224  	return ReadNetworkMap(fd)
   225  }
   226  
   227  // read the dhcp configuration out of the specified path
   228  func ReadDhcpConfig(path string) (DhcpConfiguration, error) {
   229  	fd, err := os.Open(path)
   230  	if err != nil {
   231  		return nil, err
   232  	}
   233  	defer fd.Close()
   234  	return ReadDhcpConfiguration(fd)
   235  }
   236  
   237  // read the VMX configuration from the specified path
   238  func readVMXConfig(path string) (map[string]string, error) {
   239  	f, err := os.Open(path)
   240  	if err != nil {
   241  		return map[string]string{}, err
   242  	}
   243  	defer f.Close()
   244  
   245  	vmxBytes, err := ioutil.ReadAll(f)
   246  	if err != nil {
   247  		return map[string]string{}, err
   248  	}
   249  	return ParseVMX(string(vmxBytes)), nil
   250  }
   251  
   252  // read the connection type out of a vmx configuration
   253  func readCustomDeviceName(vmxData map[string]string) (string, error) {
   254  
   255  	connectionType, ok := vmxData["ethernet0.connectiontype"]
   256  	if !ok || connectionType != "custom" {
   257  		return "", fmt.Errorf("Unable to determine the device name for the connection type : %s", connectionType)
   258  	}
   259  
   260  	device, ok := vmxData["ethernet0.vnet"]
   261  	if !ok || device == "" {
   262  		return "", fmt.Errorf("Unable to determine the device name for the connection type \"%s\" : %s", connectionType, device)
   263  	}
   264  	return device, nil
   265  }
   266  
   267  // This VmwareDriver is a base class that contains default methods
   268  // that a Driver can use or implement themselves.
   269  type VmwareDriver struct {
   270  	/// These methods define paths that are utilized by the driver
   271  	/// A driver must overload these in order to point to the correct
   272  	/// files so that the address detection (ip and ethernet) machinery
   273  	/// works.
   274  	DhcpLeasesPath   func(string) string
   275  	DhcpConfPath     func(string) string
   276  	VmnetnatConfPath func(string) string
   277  
   278  	/// This method returns an object with the NetworkNameMapper interface
   279  	/// that maps network to device and vice-versa.
   280  	NetworkMapper func() (NetworkNameMapper, error)
   281  }
   282  
   283  func (d *VmwareDriver) GuestAddress(state multistep.StateBag) (string, error) {
   284  	vmxPath := state.Get("vmx_path").(string)
   285  
   286  	log.Println("Lookup up IP information...")
   287  	vmxData, err := readVMXConfig(vmxPath)
   288  	if err != nil {
   289  		return "", err
   290  	}
   291  
   292  	var ok bool
   293  	macAddress := ""
   294  	if macAddress, ok = vmxData["ethernet0.address"]; !ok || macAddress == "" {
   295  		if macAddress, ok = vmxData["ethernet0.generatedaddress"]; !ok || macAddress == "" {
   296  			return "", errors.New("couldn't find MAC address in VMX")
   297  		}
   298  	}
   299  	log.Printf("GuestAddress found MAC address in VMX: %s", macAddress)
   300  
   301  	res, err := net.ParseMAC(macAddress)
   302  	if err != nil {
   303  		return "", err
   304  	}
   305  
   306  	return res.String(), nil
   307  }
   308  
   309  func (d *VmwareDriver) GuestIP(state multistep.StateBag) (string, error) {
   310  
   311  	// grab network mapper
   312  	netmap, err := d.NetworkMapper()
   313  	if err != nil {
   314  		return "", err
   315  	}
   316  
   317  	// convert the stashed network to a device
   318  	network := state.Get("vmnetwork").(string)
   319  	devices, err := netmap.NameIntoDevices(network)
   320  
   321  	// log them to see what was detected
   322  	for _, device := range devices {
   323  		log.Printf("GuestIP discovered device matching %s: %s", network, device)
   324  	}
   325  
   326  	// we were unable to find the device, maybe it's a custom one...
   327  	// so, check to see if it's in the .vmx configuration
   328  	if err != nil || network == "custom" {
   329  		vmxPath := state.Get("vmx_path").(string)
   330  		vmxData, err := readVMXConfig(vmxPath)
   331  		if err != nil {
   332  			return "", err
   333  		}
   334  
   335  		var device string
   336  		device, err = readCustomDeviceName(vmxData)
   337  		devices = append(devices, device)
   338  		if err != nil {
   339  			return "", err
   340  		}
   341  		log.Printf("GuestIP discovered custom device matching %s: %s", network, device)
   342  	}
   343  
   344  	// figure out our MAC address for looking up the guest address
   345  	MACAddress, err := d.GuestAddress(state)
   346  	if err != nil {
   347  		return "", err
   348  	}
   349  
   350  	for _, device := range devices {
   351  		// figure out the correct dhcp leases
   352  		dhcpLeasesPath := d.DhcpLeasesPath(device)
   353  		log.Printf("Trying DHCP leases path: %s", dhcpLeasesPath)
   354  		if dhcpLeasesPath == "" {
   355  			return "", fmt.Errorf("no DHCP leases path found for device %s", device)
   356  		}
   357  
   358  		// open up the lease and read its contents
   359  		fh, err := os.Open(dhcpLeasesPath)
   360  		if err != nil {
   361  			log.Printf("Error while reading DHCP lease path file %s: %s", dhcpLeasesPath, err.Error())
   362  			continue
   363  		}
   364  		defer fh.Close()
   365  
   366  		dhcpBytes, err := ioutil.ReadAll(fh)
   367  		if err != nil {
   368  			return "", err
   369  		}
   370  
   371  		// start grepping through the file looking for fields that we care about
   372  		var lastIp string
   373  		var lastLeaseEnd time.Time
   374  
   375  		var curIp string
   376  		var curLeaseEnd time.Time
   377  
   378  		ipLineRe := regexp.MustCompile(`^lease (.+?) {$`)
   379  		endTimeLineRe := regexp.MustCompile(`^\s*ends \d (.+?);$`)
   380  		macLineRe := regexp.MustCompile(`^\s*hardware ethernet (.+?);$`)
   381  
   382  		for _, line := range strings.Split(string(dhcpBytes), "\n") {
   383  			// Need to trim off CR character when running in windows
   384  			line = strings.TrimRight(line, "\r")
   385  
   386  			matches := ipLineRe.FindStringSubmatch(line)
   387  			if matches != nil {
   388  				lastIp = matches[1]
   389  				continue
   390  			}
   391  
   392  			matches = endTimeLineRe.FindStringSubmatch(line)
   393  			if matches != nil {
   394  				lastLeaseEnd, _ = time.Parse("2006/01/02 15:04:05", matches[1])
   395  				continue
   396  			}
   397  
   398  			// If the mac address matches and this lease ends farther in the
   399  			// future than the last match we might have, then choose it.
   400  			matches = macLineRe.FindStringSubmatch(line)
   401  			if matches != nil && strings.EqualFold(matches[1], MACAddress) && curLeaseEnd.Before(lastLeaseEnd) {
   402  				curIp = lastIp
   403  				curLeaseEnd = lastLeaseEnd
   404  			}
   405  		}
   406  		if curIp != "" {
   407  			return curIp, nil
   408  		}
   409  	}
   410  
   411  	return "", fmt.Errorf("None of the found device(s) %v has a DHCP lease for MAC %s", devices, MACAddress)
   412  }
   413  
   414  func (d *VmwareDriver) HostAddress(state multistep.StateBag) (string, error) {
   415  
   416  	// grab mapper for converting network<->device
   417  	netmap, err := d.NetworkMapper()
   418  	if err != nil {
   419  		return "", err
   420  	}
   421  
   422  	// convert network to name
   423  	network := state.Get("vmnetwork").(string)
   424  	devices, err := netmap.NameIntoDevices(network)
   425  
   426  	// log them to see what was detected
   427  	for _, device := range devices {
   428  		log.Printf("HostAddress discovered device matching %s: %s", network, device)
   429  	}
   430  
   431  	// we were unable to find the device, maybe it's a custom one...
   432  	// so, check to see if it's in the .vmx configuration
   433  	if err != nil || network == "custom" {
   434  		vmxPath := state.Get("vmx_path").(string)
   435  		vmxData, err := readVMXConfig(vmxPath)
   436  		if err != nil {
   437  			return "", err
   438  		}
   439  
   440  		var device string
   441  		device, err = readCustomDeviceName(vmxData)
   442  		devices = append(devices, device)
   443  		if err != nil {
   444  			return "", err
   445  		}
   446  		log.Printf("HostAddress discovered custom device matching %s: %s", network, device)
   447  	}
   448  
   449  	var lastError error
   450  	for _, device := range devices {
   451  		// parse dhcpd configuration
   452  		pathDhcpConfig := d.DhcpConfPath(device)
   453  		if _, err := os.Stat(pathDhcpConfig); err != nil {
   454  			return "", fmt.Errorf("Could not find vmnetdhcp conf file: %s", pathDhcpConfig)
   455  		}
   456  
   457  		config, err := ReadDhcpConfig(pathDhcpConfig)
   458  		if err != nil {
   459  			lastError = err
   460  			continue
   461  		}
   462  
   463  		// find the entry configured in the dhcpd
   464  		interfaceConfig, err := config.HostByName(device)
   465  		if err != nil {
   466  			lastError = err
   467  			continue
   468  		}
   469  
   470  		// finally grab the hardware address
   471  		address, err := interfaceConfig.Hardware()
   472  		if err == nil {
   473  			return address.String(), nil
   474  		}
   475  
   476  		// we didn't find it, so search through our interfaces for the device name
   477  		interfaceList, err := net.Interfaces()
   478  		if err == nil {
   479  			return "", err
   480  		}
   481  
   482  		names := make([]string, 0)
   483  		for _, intf := range interfaceList {
   484  			if strings.HasSuffix(strings.ToLower(intf.Name), device) {
   485  				return intf.HardwareAddr.String(), nil
   486  			}
   487  			names = append(names, intf.Name)
   488  		}
   489  	}
   490  	return "", fmt.Errorf("Unable to find host address from devices %v, last error: %s", devices, lastError)
   491  }
   492  
   493  func (d *VmwareDriver) HostIP(state multistep.StateBag) (string, error) {
   494  
   495  	// grab mapper for converting network<->device
   496  	netmap, err := d.NetworkMapper()
   497  	if err != nil {
   498  		return "", err
   499  	}
   500  
   501  	// convert network to name
   502  	network := state.Get("vmnetwork").(string)
   503  	devices, err := netmap.NameIntoDevices(network)
   504  
   505  	// log them to see what was detected
   506  	for _, device := range devices {
   507  		log.Printf("HostIP discovered device matching %s: %s", network, device)
   508  	}
   509  
   510  	// we were unable to find the device, maybe it's a custom one...
   511  	// so, check to see if it's in the .vmx configuration
   512  	if err != nil || network == "custom" {
   513  		vmxPath := state.Get("vmx_path").(string)
   514  		vmxData, err := readVMXConfig(vmxPath)
   515  		if err != nil {
   516  			return "", err
   517  		}
   518  
   519  		var device string
   520  		device, err = readCustomDeviceName(vmxData)
   521  		devices = append(devices, device)
   522  		if err != nil {
   523  			return "", err
   524  		}
   525  		log.Printf("HostIP discovered custom device matching %s: %s", network, device)
   526  	}
   527  
   528  	var lastError error
   529  	for _, device := range devices {
   530  		// parse dhcpd configuration
   531  		pathDhcpConfig := d.DhcpConfPath(device)
   532  		if _, err := os.Stat(pathDhcpConfig); err != nil {
   533  			return "", fmt.Errorf("Could not find vmnetdhcp conf file: %s", pathDhcpConfig)
   534  		}
   535  		config, err := ReadDhcpConfig(pathDhcpConfig)
   536  		if err != nil {
   537  			lastError = err
   538  			continue
   539  		}
   540  
   541  		// find the entry configured in the dhcpd
   542  		interfaceConfig, err := config.HostByName(device)
   543  		if err != nil {
   544  			lastError = err
   545  			continue
   546  		}
   547  
   548  		address, err := interfaceConfig.IP4()
   549  		if err != nil {
   550  			lastError = err
   551  			continue
   552  		}
   553  
   554  		return address.String(), nil
   555  	}
   556  	return "", fmt.Errorf("Unable to find host IP from devices %v, last error: %s", devices, lastError)
   557  }