github.com/askholme/packer@v0.7.2-0.20140924152349-70d9566a6852/builder/parallels/common/driver_9.go (about)

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