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