github.com/daniellockard/packer@v0.7.6-0.20141210173435-5a9390934716/builder/parallels/common/driver_9.go (about)

     1  package common
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"log"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"regexp"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/going/toolkit/xmlpath"
    16  )
    17  
    18  type Parallels9Driver struct {
    19  	// This is the path to the "prlctl" application.
    20  	PrlctlPath string
    21  }
    22  
    23  func (d *Parallels9Driver) Import(name, srcPath, dstDir string, reassignMac bool) error {
    24  
    25  	err := d.Prlctl("register", srcPath, "--preserve-uuid")
    26  	if err != nil {
    27  		return err
    28  	}
    29  
    30  	srcId, err := getVmId(srcPath)
    31  	if err != nil {
    32  		return err
    33  	}
    34  
    35  	srcMac := "auto"
    36  	if !reassignMac {
    37  		srcMac, err = getFirtsMacAddress(srcPath)
    38  		if err != nil {
    39  			return err
    40  		}
    41  	}
    42  
    43  	err = d.Prlctl("clone", srcId, "--name", name, "--dst", dstDir)
    44  	if err != nil {
    45  		return err
    46  	}
    47  
    48  	err = d.Prlctl("unregister", srcId)
    49  	if err != nil {
    50  		return err
    51  	}
    52  
    53  	err = d.Prlctl("set", name, "--device-set", "net0", "--mac", srcMac)
    54  	return nil
    55  }
    56  
    57  func getVmId(path string) (string, error) {
    58  	return getConfigValueFromXpath(path, "/ParallelsVirtualMachine/Identification/VmUuid")
    59  }
    60  
    61  func getFirtsMacAddress(path string) (string, error) {
    62  	return getConfigValueFromXpath(path, "/ParallelsVirtualMachine/Hardware/NetworkAdapter[@id='0']/MAC")
    63  }
    64  
    65  func getConfigValueFromXpath(path, xpath string) (string, error) {
    66  	file, err := os.Open(path + "/config.pvs")
    67  	if err != nil {
    68  		return "", err
    69  	}
    70  	xpathComp := xmlpath.MustCompile(xpath)
    71  	root, err := xmlpath.Parse(file)
    72  	if err != nil {
    73  		return "", err
    74  	}
    75  	value, _ := xpathComp.String(root)
    76  	return value, nil
    77  }
    78  
    79  // Finds an application bundle by identifier (for "darwin" platform only)
    80  func getAppPath(bundleId string) (string, error) {
    81  	var stdout bytes.Buffer
    82  
    83  	cmd := exec.Command("mdfind", "kMDItemCFBundleIdentifier ==", bundleId)
    84  	cmd.Stdout = &stdout
    85  	if err := cmd.Run(); err != nil {
    86  		return "", err
    87  	}
    88  
    89  	pathOutput := strings.TrimSpace(stdout.String())
    90  	if pathOutput == "" {
    91  		return "", fmt.Errorf(
    92  			"Could not detect Parallels Desktop! Make sure it is properly installed.")
    93  	}
    94  
    95  	return pathOutput, nil
    96  }
    97  
    98  func (d *Parallels9Driver) DeviceAddCdRom(name string, image string) (string, error) {
    99  	command := []string{
   100  		"set", name,
   101  		"--device-add", "cdrom",
   102  		"--image", image,
   103  	}
   104  
   105  	out, err := exec.Command(d.PrlctlPath, command...).Output()
   106  	if err != nil {
   107  		return "", err
   108  	}
   109  
   110  	deviceRe := regexp.MustCompile(`\s+(cdrom\d+)\s+`)
   111  	matches := deviceRe.FindStringSubmatch(string(out))
   112  	if matches == nil {
   113  		return "", fmt.Errorf(
   114  			"Could not determine cdrom device name in the output:\n%s", string(out))
   115  	}
   116  
   117  	device_name := matches[1]
   118  	return device_name, nil
   119  }
   120  
   121  func (d *Parallels9Driver) IsRunning(name string) (bool, error) {
   122  	var stdout bytes.Buffer
   123  
   124  	cmd := exec.Command(d.PrlctlPath, "list", name, "--no-header", "--output", "status")
   125  	cmd.Stdout = &stdout
   126  	if err := cmd.Run(); err != nil {
   127  		return false, err
   128  	}
   129  
   130  	log.Printf("Checking VM state: %s\n", strings.TrimSpace(stdout.String()))
   131  
   132  	for _, line := range strings.Split(stdout.String(), "\n") {
   133  		if line == "running" {
   134  			return true, nil
   135  		}
   136  
   137  		if line == "suspended" {
   138  			return true, nil
   139  		}
   140  		if line == "paused" {
   141  			return true, nil
   142  		}
   143  		if line == "stopping" {
   144  			return true, nil
   145  		}
   146  	}
   147  
   148  	return false, nil
   149  }
   150  
   151  func (d *Parallels9Driver) Stop(name string) error {
   152  	if err := d.Prlctl("stop", name); err != nil {
   153  		return err
   154  	}
   155  
   156  	// We sleep here for a little bit to let the session "unlock"
   157  	time.Sleep(2 * time.Second)
   158  
   159  	return nil
   160  }
   161  
   162  func (d *Parallels9Driver) Prlctl(args ...string) error {
   163  	var stdout, stderr bytes.Buffer
   164  
   165  	log.Printf("Executing prlctl: %#v", args)
   166  	cmd := exec.Command(d.PrlctlPath, args...)
   167  	cmd.Stdout = &stdout
   168  	cmd.Stderr = &stderr
   169  	err := cmd.Run()
   170  
   171  	stdoutString := strings.TrimSpace(stdout.String())
   172  	stderrString := strings.TrimSpace(stderr.String())
   173  
   174  	if _, ok := err.(*exec.ExitError); ok {
   175  		err = fmt.Errorf("prlctl error: %s", stderrString)
   176  	}
   177  
   178  	log.Printf("stdout: %s", stdoutString)
   179  	log.Printf("stderr: %s", stderrString)
   180  
   181  	return err
   182  }
   183  
   184  func (d *Parallels9Driver) Verify() error {
   185  	return nil
   186  }
   187  
   188  func (d *Parallels9Driver) Version() (string, error) {
   189  	out, err := exec.Command(d.PrlctlPath, "--version").Output()
   190  	if err != nil {
   191  		return "", err
   192  	}
   193  
   194  	versionRe := regexp.MustCompile(`prlctl version (\d+\.\d+.\d+)`)
   195  	matches := versionRe.FindStringSubmatch(string(out))
   196  	if matches == nil {
   197  		return "", fmt.Errorf(
   198  			"Could not find Parallels Desktop version in output:\n%s", string(out))
   199  	}
   200  
   201  	version := matches[1]
   202  	log.Printf("Parallels Desktop version: %s", version)
   203  	return version, nil
   204  }
   205  
   206  func (d *Parallels9Driver) SendKeyScanCodes(vmName string, codes ...string) error {
   207  	var stdout, stderr bytes.Buffer
   208  
   209  	if codes == nil || len(codes) == 0 {
   210  		log.Printf("No scan codes to send")
   211  		return nil
   212  	}
   213  
   214  	f, err := ioutil.TempFile("", "prltype")
   215  	if err != nil {
   216  		return err
   217  	}
   218  	defer os.Remove(f.Name())
   219  
   220  	script := []byte(Prltype)
   221  	_, err = f.Write(script)
   222  	if err != nil {
   223  		return err
   224  	}
   225  
   226  	args := prepend(vmName, codes)
   227  	args = prepend(f.Name(), args)
   228  	cmd := exec.Command("/usr/bin/python", args...)
   229  	cmd.Stdout = &stdout
   230  	cmd.Stderr = &stderr
   231  	err = cmd.Run()
   232  
   233  	stdoutString := strings.TrimSpace(stdout.String())
   234  	stderrString := strings.TrimSpace(stderr.String())
   235  
   236  	if _, ok := err.(*exec.ExitError); ok {
   237  		err = fmt.Errorf("prltype error: %s", stderrString)
   238  	}
   239  
   240  	log.Printf("stdout: %s", stdoutString)
   241  	log.Printf("stderr: %s", stderrString)
   242  
   243  	return err
   244  }
   245  
   246  func prepend(head string, tail []string) []string {
   247  	tmp := make([]string, len(tail)+1)
   248  	for i := 0; i < len(tail); i++ {
   249  		tmp[i+1] = tail[i]
   250  	}
   251  	tmp[0] = head
   252  	return tmp
   253  }
   254  
   255  func (d *Parallels9Driver) Mac(vmName string) (string, error) {
   256  	var stdout bytes.Buffer
   257  
   258  	cmd := exec.Command(d.PrlctlPath, "list", "-i", vmName)
   259  	cmd.Stdout = &stdout
   260  	if err := cmd.Run(); err != nil {
   261  		log.Printf("MAC address for NIC: nic0 on Virtual Machine: %s not found!\n", vmName)
   262  		return "", err
   263  	}
   264  
   265  	stdoutString := strings.TrimSpace(stdout.String())
   266  	re := regexp.MustCompile("net0.* mac=([0-9A-F]{12}) card=.*")
   267  	macMatch := re.FindAllStringSubmatch(stdoutString, 1)
   268  
   269  	if len(macMatch) != 1 {
   270  		return "", fmt.Errorf("MAC address for NIC: nic0 on Virtual Machine: %s not found!\n", vmName)
   271  	}
   272  
   273  	mac := macMatch[0][1]
   274  	log.Printf("Found MAC address for NIC: net0 - %s\n", mac)
   275  	return mac, nil
   276  }
   277  
   278  // Finds the IP address of a VM connected that uses DHCP by its MAC address
   279  func (d *Parallels9Driver) IpAddress(mac string) (string, error) {
   280  	var stdout bytes.Buffer
   281  	dhcp_lease_file := "/Library/Preferences/Parallels/parallels_dhcp_leases"
   282  
   283  	if len(mac) != 12 {
   284  		return "", fmt.Errorf("Not a valid MAC address: %s. It should be exactly 12 digits.", mac)
   285  	}
   286  
   287  	cmd := exec.Command("grep", "-i", mac, dhcp_lease_file)
   288  	cmd.Stdout = &stdout
   289  	if err := cmd.Run(); err != nil {
   290  		return "", err
   291  	}
   292  
   293  	stdoutString := strings.TrimSpace(stdout.String())
   294  	re := regexp.MustCompile("(.*)=.*")
   295  	ipMatch := re.FindAllStringSubmatch(stdoutString, 1)
   296  
   297  	if len(ipMatch) != 1 {
   298  		return "", fmt.Errorf("IP lease not found for MAC address %s in: %s\n", mac, dhcp_lease_file)
   299  	}
   300  
   301  	ip := ipMatch[0][1]
   302  	log.Printf("Found IP lease: %s for MAC address %s\n", ip, mac)
   303  	return ip, nil
   304  }
   305  
   306  func (d *Parallels9Driver) ToolsIsoPath(k string) (string, error) {
   307  	appPath, err := getAppPath("com.parallels.desktop.console")
   308  	if err != nil {
   309  		return "", err
   310  	}
   311  
   312  	toolsPath := filepath.Join(appPath, "Contents", "Resources", "Tools", "prl-tools-"+k+".iso")
   313  	log.Printf("Parallels Tools path: '%s'", toolsPath)
   314  	return toolsPath, nil
   315  }