github.com/alloyci/alloy-runner@v1.0.1-0.20180222164613-925503ccafd6/helpers/parallels/control.go (about) 1 package parallels 2 3 // Large part of this source is taken from 4 // https://github.com/mitchellh/packer/blob/master/builder/parallels/common 5 6 import ( 7 "bytes" 8 "errors" 9 "fmt" 10 "github.com/Sirupsen/logrus" 11 "io/ioutil" 12 "os/exec" 13 "regexp" 14 "runtime" 15 "strconv" 16 "strings" 17 "time" 18 ) 19 20 type StatusType string 21 22 const ( 23 NotFound StatusType = "notfound" 24 Invalid StatusType = "invalid" 25 Stopped StatusType = "stopped" 26 Suspended StatusType = "suspended" 27 Running StatusType = "running" 28 // TODO: more statuses 29 ) 30 31 const ( 32 prlctlPath = "prlctl" 33 dhcpLeases = "/Library/Preferences/Parallels/parallels_dhcp_leases" 34 ) 35 36 func PrlctlOutput(args ...string) (string, error) { 37 if runtime.GOOS != "darwin" { 38 return "", fmt.Errorf("Parallels works only on \"darwin\" platform") 39 } 40 41 var stdout, stderr bytes.Buffer 42 43 logrus.Debugf("Executing PrlctlOutput: %#v", args) 44 cmd := exec.Command(prlctlPath, args...) 45 cmd.Stdout = &stdout 46 cmd.Stderr = &stderr 47 err := cmd.Run() 48 49 stderrString := strings.TrimSpace(stderr.String()) 50 51 if _, ok := err.(*exec.ExitError); ok { 52 err = fmt.Errorf("PrlctlOutput error: %s", stderrString) 53 } 54 55 return stdout.String(), err 56 } 57 58 func Prlctl(args ...string) error { 59 _, err := PrlctlOutput(args...) 60 return err 61 } 62 63 func Exec(vmName string, args ...string) (string, error) { 64 args2 := append([]string{"exec", vmName}, args...) 65 return PrlctlOutput(args2...) 66 } 67 68 func Version() (string, error) { 69 out, err := PrlctlOutput("--version") 70 if err != nil { 71 return "", err 72 } 73 74 versionRe := regexp.MustCompile(`prlctl version (\d+\.\d+.\d+)`) 75 matches := versionRe.FindStringSubmatch(string(out)) 76 if matches == nil { 77 return "", fmt.Errorf( 78 "Could not find Parallels Desktop version in output:\n%s", string(out)) 79 } 80 81 version := matches[1] 82 logrus.Debugf("Parallels Desktop version: %s", version) 83 return version, nil 84 } 85 86 func Exist(name string) bool { 87 err := Prlctl("list", name, "--no-header", "--output", "status") 88 if err != nil { 89 return false 90 } 91 return true 92 } 93 94 func CreateTemplate(vmName, templateName string) error { 95 return Prlctl("clone", vmName, "--name", templateName, "--template", "--linked") 96 } 97 98 func CreateOsVM(vmName, templateName string) error { 99 return Prlctl("create", vmName, "--ostemplate", templateName) 100 } 101 102 func CreateSnapshot(vmName, snapshotName string) error { 103 return Prlctl("snapshot", vmName, "--name", snapshotName) 104 } 105 106 func GetDefaultSnapshot(vmName string) (string, error) { 107 output, err := PrlctlOutput("snapshot-list", vmName) 108 if err != nil { 109 return "", err 110 } 111 112 lines := strings.Split(output, "\n") 113 for _, line := range lines { 114 pos := strings.Index(line, " *") 115 if pos >= 0 { 116 snapshot := line[pos+2:] 117 snapshot = strings.TrimSpace(snapshot) 118 if len(snapshot) > 0 { // It uses UUID so it should be 38 119 return snapshot, nil 120 } 121 } 122 } 123 124 return "", errors.New("No snapshot") 125 } 126 127 func RevertToSnapshot(vmName, snapshotID string) error { 128 return Prlctl("snapshot-switch", vmName, "--id", snapshotID) 129 } 130 131 func Start(vmName string) error { 132 return Prlctl("start", vmName) 133 } 134 135 func Status(vmName string) (StatusType, error) { 136 output, err := PrlctlOutput("list", vmName, "--no-header", "--output", "status") 137 if err != nil { 138 return NotFound, err 139 } 140 return StatusType(strings.TrimSpace(output)), nil 141 } 142 143 func WaitForStatus(vmName string, vmStatus StatusType, seconds int) error { 144 var status StatusType 145 var err error 146 for i := 0; i < seconds; i++ { 147 status, err = Status(vmName) 148 if err != nil { 149 return err 150 } 151 if status == vmStatus { 152 return nil 153 } 154 time.Sleep(time.Second) 155 } 156 return errors.New("VM " + vmName + " is in " + string(status) + " where it should be in " + string(vmStatus)) 157 } 158 159 func TryExec(vmName string, seconds int, cmd ...string) error { 160 var err error 161 for i := 0; i < seconds; i++ { 162 _, err = Exec(vmName, cmd...) 163 if err == nil { 164 return nil 165 } 166 time.Sleep(time.Second) 167 } 168 return err 169 } 170 171 func Kill(vmName string) error { 172 return Prlctl("stop", vmName, "--kill") 173 } 174 175 func Delete(vmName string) error { 176 return Prlctl("delete", vmName) 177 } 178 179 func Unregister(vmName string) error { 180 return Prlctl("unregister", vmName) 181 } 182 183 func Mac(vmName string) (string, error) { 184 output, err := PrlctlOutput("list", "-i", vmName) 185 if err != nil { 186 return "", err 187 } 188 189 stdoutString := strings.TrimSpace(output) 190 re := regexp.MustCompile("net0.* mac=([0-9A-F]{12}) card=.*") 191 macMatch := re.FindAllStringSubmatch(stdoutString, 1) 192 193 if len(macMatch) != 1 { 194 return "", fmt.Errorf("MAC address for NIC: nic0 on Virtual Machine: %s not found", vmName) 195 } 196 197 mac := macMatch[0][1] 198 logrus.Debugf("Found MAC address for NIC: net0 - %s\n", mac) 199 return mac, nil 200 } 201 202 // IPAddress finds the IP address of a VM connected that uses DHCP by its MAC address 203 // 204 // Parses the file /Library/Preferences/Parallels/parallels_dhcp_leases 205 // file contain a list of DHCP leases given by Parallels Desktop 206 // Example line: 207 // 10.211.55.181="1418921112,1800,001c42f593fb,ff42f593fb000100011c25b9ff001c42f593fb" 208 // IP Address ="Lease expiry, Lease time, MAC, MAC or DUID" 209 func IPAddress(mac string) (string, error) { 210 if len(mac) != 12 { 211 return "", fmt.Errorf("Not a valid MAC address: %s. It should be exactly 12 digits", mac) 212 } 213 214 leases, err := ioutil.ReadFile(dhcpLeases) 215 if err != nil { 216 return "", err 217 } 218 219 re := regexp.MustCompile("(.*)=\"(.*),(.*)," + strings.ToLower(mac) + ",.*\"") 220 mostRecentIP := "" 221 mostRecentLease := uint64(0) 222 for _, l := range re.FindAllStringSubmatch(string(leases), -1) { 223 ip := l[1] 224 expiry, _ := strconv.ParseUint(l[2], 10, 64) 225 leaseTime, _ := strconv.ParseUint(l[3], 10, 32) 226 logrus.Debugf("Found lease: %s for MAC: %s, expiring at %d, leased for %d s.\n", ip, mac, expiry, leaseTime) 227 if mostRecentLease <= expiry-leaseTime { 228 mostRecentIP = ip 229 mostRecentLease = expiry - leaseTime 230 } 231 } 232 233 if len(mostRecentIP) == 0 { 234 return "", fmt.Errorf("IP lease not found for MAC address %s in: %s", mac, dhcpLeases) 235 } 236 237 logrus.Debugf("Found IP lease: %s for MAC address %s\n", mostRecentIP, mac) 238 return mostRecentIP, nil 239 }