PageRenderTime 50ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 1ms

/builder/parallels/common/driver_9.go

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