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 }