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 }