github.phpd.cn/hashicorp/packer@v1.3.2/builder/vmware/common/driver.go (about) 1 package common 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "log" 9 "net" 10 "os" 11 "os/exec" 12 "regexp" 13 "runtime" 14 "strconv" 15 "strings" 16 "time" 17 18 "github.com/hashicorp/packer/helper/multistep" 19 ) 20 21 // A driver is able to talk to VMware, control virtual machines, etc. 22 type Driver interface { 23 // Clone clones the VMX and the disk to the destination path. The 24 // destination is a path to the VMX file. The disk will be copied 25 // to that same directory. 26 Clone(dst string, src string, cloneType bool) error 27 28 // CompactDisk compacts a virtual disk. 29 CompactDisk(string) error 30 31 // CreateDisk creates a virtual disk with the given size. 32 CreateDisk(string, string, string, string) error 33 34 // Checks if the VMX file at the given path is running. 35 IsRunning(string) (bool, error) 36 37 // Start starts a VM specified by the path to the VMX given. 38 Start(string, bool) error 39 40 // Stop stops a VM specified by the path to the VMX given. 41 Stop(string) error 42 43 // SuppressMessages modifies the VMX or surrounding directory so that 44 // VMware doesn't show any annoying messages. 45 SuppressMessages(string) error 46 47 // Get the path to the VMware ISO for the given flavor. 48 ToolsIsoPath(string) string 49 50 // Attach the VMware tools ISO 51 ToolsInstall() error 52 53 // Verify checks to make sure that this driver should function 54 // properly. This should check that all the files it will use 55 // appear to exist and so on. If everything is okay, this doesn't 56 // return an error. Otherwise, this returns an error. Each vmware 57 // driver should assign the VmwareMachine callback functions for locating 58 // paths within this function. 59 Verify() error 60 61 /// This is to establish a connection to the guest 62 CommHost(multistep.StateBag) (string, error) 63 64 /// These methods are generally implemented by the VmwareDriver 65 /// structure within this file. A driver implementation can 66 /// reimplement these, though, if it wants. 67 GetVmwareDriver() VmwareDriver 68 69 // Get the guest hw address for the vm 70 GuestAddress(multistep.StateBag) (string, error) 71 72 // Get the guest ip address for the vm 73 GuestIP(multistep.StateBag) (string, error) 74 75 // Get the host hw address for the vm 76 HostAddress(multistep.StateBag) (string, error) 77 78 // Get the host ip address for the vm 79 HostIP(multistep.StateBag) (string, error) 80 } 81 82 // NewDriver returns a new driver implementation for this operating 83 // system, or an error if the driver couldn't be initialized. 84 func NewDriver(dconfig *DriverConfig, config *SSHConfig) (Driver, error) { 85 drivers := []Driver{} 86 87 switch runtime.GOOS { 88 case "darwin": 89 drivers = []Driver{ 90 &Fusion6Driver{ 91 Fusion5Driver: Fusion5Driver{ 92 AppPath: dconfig.FusionAppPath, 93 SSHConfig: config, 94 }, 95 }, 96 &Fusion5Driver{ 97 AppPath: dconfig.FusionAppPath, 98 SSHConfig: config, 99 }, 100 } 101 case "linux": 102 fallthrough 103 case "windows": 104 drivers = []Driver{ 105 &Workstation10Driver{ 106 Workstation9Driver: Workstation9Driver{ 107 SSHConfig: config, 108 }, 109 }, 110 &Workstation9Driver{ 111 SSHConfig: config, 112 }, 113 &Player6Driver{ 114 Player5Driver: Player5Driver{ 115 SSHConfig: config, 116 }, 117 }, 118 &Player5Driver{ 119 SSHConfig: config, 120 }, 121 } 122 default: 123 return nil, fmt.Errorf("can't find driver for OS: %s", runtime.GOOS) 124 } 125 126 errs := "" 127 for _, driver := range drivers { 128 err := driver.Verify() 129 log.Printf("Testing vmware driver %T. Success: %t", 130 driver, err == nil) 131 132 if err == nil { 133 return driver, nil 134 } 135 errs += "* " + err.Error() + "\n" 136 } 137 138 return nil, fmt.Errorf( 139 "Unable to initialize any driver for this platform. The errors\n"+ 140 "from each driver are shown below. Please fix at least one driver\n"+ 141 "to continue:\n%s", errs) 142 } 143 144 func runAndLog(cmd *exec.Cmd) (string, string, error) { 145 var stdout, stderr bytes.Buffer 146 147 log.Printf("Executing: %s %s", cmd.Path, strings.Join(cmd.Args[1:], " ")) 148 cmd.Stdout = &stdout 149 cmd.Stderr = &stderr 150 err := cmd.Run() 151 152 stdoutString := strings.TrimSpace(stdout.String()) 153 stderrString := strings.TrimSpace(stderr.String()) 154 155 if _, ok := err.(*exec.ExitError); ok { 156 message := stderrString 157 if message == "" { 158 message = stdoutString 159 } 160 161 err = fmt.Errorf("VMware error: %s", message) 162 163 // If "unknown error" is in there, add some additional notes 164 re := regexp.MustCompile(`(?i)unknown error`) 165 if re.MatchString(message) { 166 err = fmt.Errorf( 167 "%s\n\n%s", err, 168 "Packer detected a VMware 'Unknown Error'. Unfortunately VMware\n"+ 169 "often has extremely vague error messages such as this and Packer\n"+ 170 "itself can't do much about that. Please check the vmware.log files\n"+ 171 "created by VMware when a VM is started (in the directory of the\n"+ 172 "vmx file), which often contains more detailed error information.") 173 } 174 } 175 176 log.Printf("stdout: %s", stdoutString) 177 log.Printf("stderr: %s", stderrString) 178 179 // Replace these for Windows, we only want to deal with Unix 180 // style line endings. 181 returnStdout := strings.Replace(stdout.String(), "\r\n", "\n", -1) 182 returnStderr := strings.Replace(stderr.String(), "\r\n", "\n", -1) 183 184 return returnStdout, returnStderr, err 185 } 186 187 func normalizeVersion(version string) (string, error) { 188 i, err := strconv.Atoi(version) 189 if err != nil { 190 return "", fmt.Errorf( 191 "VMware version '%s' is not numeric", version) 192 } 193 194 return fmt.Sprintf("%02d", i), nil 195 } 196 197 func compareVersions(versionFound string, versionWanted string, product string) error { 198 found, err := normalizeVersion(versionFound) 199 if err != nil { 200 return err 201 } 202 203 wanted, err := normalizeVersion(versionWanted) 204 if err != nil { 205 return err 206 } 207 208 if found < wanted { 209 return fmt.Errorf( 210 "VMware %s version %s, or greater, is required. Found version: %s", product, versionWanted, versionFound) 211 } 212 213 return nil 214 } 215 216 /// helper functions that read configuration information from a file 217 // read the network<->device configuration out of the specified path 218 func ReadNetmapConfig(path string) (NetworkMap, error) { 219 fd, err := os.Open(path) 220 if err != nil { 221 return nil, err 222 } 223 defer fd.Close() 224 return ReadNetworkMap(fd) 225 } 226 227 // read the dhcp configuration out of the specified path 228 func ReadDhcpConfig(path string) (DhcpConfiguration, error) { 229 fd, err := os.Open(path) 230 if err != nil { 231 return nil, err 232 } 233 defer fd.Close() 234 return ReadDhcpConfiguration(fd) 235 } 236 237 // read the VMX configuration from the specified path 238 func readVMXConfig(path string) (map[string]string, error) { 239 f, err := os.Open(path) 240 if err != nil { 241 return map[string]string{}, err 242 } 243 defer f.Close() 244 245 vmxBytes, err := ioutil.ReadAll(f) 246 if err != nil { 247 return map[string]string{}, err 248 } 249 return ParseVMX(string(vmxBytes)), nil 250 } 251 252 // read the connection type out of a vmx configuration 253 func readCustomDeviceName(vmxData map[string]string) (string, error) { 254 255 connectionType, ok := vmxData["ethernet0.connectiontype"] 256 if !ok || connectionType != "custom" { 257 return "", fmt.Errorf("Unable to determine the device name for the connection type : %s", connectionType) 258 } 259 260 device, ok := vmxData["ethernet0.vnet"] 261 if !ok || device == "" { 262 return "", fmt.Errorf("Unable to determine the device name for the connection type \"%s\" : %s", connectionType, device) 263 } 264 return device, nil 265 } 266 267 // This VmwareDriver is a base class that contains default methods 268 // that a Driver can use or implement themselves. 269 type VmwareDriver struct { 270 /// These methods define paths that are utilized by the driver 271 /// A driver must overload these in order to point to the correct 272 /// files so that the address detection (ip and ethernet) machinery 273 /// works. 274 DhcpLeasesPath func(string) string 275 DhcpConfPath func(string) string 276 VmnetnatConfPath func(string) string 277 278 /// This method returns an object with the NetworkNameMapper interface 279 /// that maps network to device and vice-versa. 280 NetworkMapper func() (NetworkNameMapper, error) 281 } 282 283 func (d *VmwareDriver) GuestAddress(state multistep.StateBag) (string, error) { 284 vmxPath := state.Get("vmx_path").(string) 285 286 log.Println("Lookup up IP information...") 287 vmxData, err := readVMXConfig(vmxPath) 288 if err != nil { 289 return "", err 290 } 291 292 var ok bool 293 macAddress := "" 294 if macAddress, ok = vmxData["ethernet0.address"]; !ok || macAddress == "" { 295 if macAddress, ok = vmxData["ethernet0.generatedaddress"]; !ok || macAddress == "" { 296 return "", errors.New("couldn't find MAC address in VMX") 297 } 298 } 299 log.Printf("GuestAddress found MAC address in VMX: %s", macAddress) 300 301 res, err := net.ParseMAC(macAddress) 302 if err != nil { 303 return "", err 304 } 305 306 return res.String(), nil 307 } 308 309 func (d *VmwareDriver) GuestIP(state multistep.StateBag) (string, error) { 310 311 // grab network mapper 312 netmap, err := d.NetworkMapper() 313 if err != nil { 314 return "", err 315 } 316 317 // convert the stashed network to a device 318 network := state.Get("vmnetwork").(string) 319 devices, err := netmap.NameIntoDevices(network) 320 321 // log them to see what was detected 322 for _, device := range devices { 323 log.Printf("GuestIP discovered device matching %s: %s", network, device) 324 } 325 326 // we were unable to find the device, maybe it's a custom one... 327 // so, check to see if it's in the .vmx configuration 328 if err != nil || network == "custom" { 329 vmxPath := state.Get("vmx_path").(string) 330 vmxData, err := readVMXConfig(vmxPath) 331 if err != nil { 332 return "", err 333 } 334 335 var device string 336 device, err = readCustomDeviceName(vmxData) 337 devices = append(devices, device) 338 if err != nil { 339 return "", err 340 } 341 log.Printf("GuestIP discovered custom device matching %s: %s", network, device) 342 } 343 344 // figure out our MAC address for looking up the guest address 345 MACAddress, err := d.GuestAddress(state) 346 if err != nil { 347 return "", err 348 } 349 350 for _, device := range devices { 351 // figure out the correct dhcp leases 352 dhcpLeasesPath := d.DhcpLeasesPath(device) 353 log.Printf("Trying DHCP leases path: %s", dhcpLeasesPath) 354 if dhcpLeasesPath == "" { 355 return "", fmt.Errorf("no DHCP leases path found for device %s", device) 356 } 357 358 // open up the lease and read its contents 359 fh, err := os.Open(dhcpLeasesPath) 360 if err != nil { 361 log.Printf("Error while reading DHCP lease path file %s: %s", dhcpLeasesPath, err.Error()) 362 continue 363 } 364 defer fh.Close() 365 366 dhcpBytes, err := ioutil.ReadAll(fh) 367 if err != nil { 368 return "", err 369 } 370 371 // start grepping through the file looking for fields that we care about 372 var lastIp string 373 var lastLeaseEnd time.Time 374 375 var curIp string 376 var curLeaseEnd time.Time 377 378 ipLineRe := regexp.MustCompile(`^lease (.+?) {$`) 379 endTimeLineRe := regexp.MustCompile(`^\s*ends \d (.+?);$`) 380 macLineRe := regexp.MustCompile(`^\s*hardware ethernet (.+?);$`) 381 382 for _, line := range strings.Split(string(dhcpBytes), "\n") { 383 // Need to trim off CR character when running in windows 384 line = strings.TrimRight(line, "\r") 385 386 matches := ipLineRe.FindStringSubmatch(line) 387 if matches != nil { 388 lastIp = matches[1] 389 continue 390 } 391 392 matches = endTimeLineRe.FindStringSubmatch(line) 393 if matches != nil { 394 lastLeaseEnd, _ = time.Parse("2006/01/02 15:04:05", matches[1]) 395 continue 396 } 397 398 // If the mac address matches and this lease ends farther in the 399 // future than the last match we might have, then choose it. 400 matches = macLineRe.FindStringSubmatch(line) 401 if matches != nil && strings.EqualFold(matches[1], MACAddress) && curLeaseEnd.Before(lastLeaseEnd) { 402 curIp = lastIp 403 curLeaseEnd = lastLeaseEnd 404 } 405 } 406 if curIp != "" { 407 return curIp, nil 408 } 409 } 410 411 return "", fmt.Errorf("None of the found device(s) %v has a DHCP lease for MAC %s", devices, MACAddress) 412 } 413 414 func (d *VmwareDriver) HostAddress(state multistep.StateBag) (string, error) { 415 416 // grab mapper for converting network<->device 417 netmap, err := d.NetworkMapper() 418 if err != nil { 419 return "", err 420 } 421 422 // convert network to name 423 network := state.Get("vmnetwork").(string) 424 devices, err := netmap.NameIntoDevices(network) 425 426 // log them to see what was detected 427 for _, device := range devices { 428 log.Printf("HostAddress discovered device matching %s: %s", network, device) 429 } 430 431 // we were unable to find the device, maybe it's a custom one... 432 // so, check to see if it's in the .vmx configuration 433 if err != nil || network == "custom" { 434 vmxPath := state.Get("vmx_path").(string) 435 vmxData, err := readVMXConfig(vmxPath) 436 if err != nil { 437 return "", err 438 } 439 440 var device string 441 device, err = readCustomDeviceName(vmxData) 442 devices = append(devices, device) 443 if err != nil { 444 return "", err 445 } 446 log.Printf("HostAddress discovered custom device matching %s: %s", network, device) 447 } 448 449 var lastError error 450 for _, device := range devices { 451 // parse dhcpd configuration 452 pathDhcpConfig := d.DhcpConfPath(device) 453 if _, err := os.Stat(pathDhcpConfig); err != nil { 454 return "", fmt.Errorf("Could not find vmnetdhcp conf file: %s", pathDhcpConfig) 455 } 456 457 config, err := ReadDhcpConfig(pathDhcpConfig) 458 if err != nil { 459 lastError = err 460 continue 461 } 462 463 // find the entry configured in the dhcpd 464 interfaceConfig, err := config.HostByName(device) 465 if err != nil { 466 lastError = err 467 continue 468 } 469 470 // finally grab the hardware address 471 address, err := interfaceConfig.Hardware() 472 if err == nil { 473 return address.String(), nil 474 } 475 476 // we didn't find it, so search through our interfaces for the device name 477 interfaceList, err := net.Interfaces() 478 if err == nil { 479 return "", err 480 } 481 482 names := make([]string, 0) 483 for _, intf := range interfaceList { 484 if strings.HasSuffix(strings.ToLower(intf.Name), device) { 485 return intf.HardwareAddr.String(), nil 486 } 487 names = append(names, intf.Name) 488 } 489 } 490 return "", fmt.Errorf("Unable to find host address from devices %v, last error: %s", devices, lastError) 491 } 492 493 func (d *VmwareDriver) HostIP(state multistep.StateBag) (string, error) { 494 495 // grab mapper for converting network<->device 496 netmap, err := d.NetworkMapper() 497 if err != nil { 498 return "", err 499 } 500 501 // convert network to name 502 network := state.Get("vmnetwork").(string) 503 devices, err := netmap.NameIntoDevices(network) 504 505 // log them to see what was detected 506 for _, device := range devices { 507 log.Printf("HostIP discovered device matching %s: %s", network, device) 508 } 509 510 // we were unable to find the device, maybe it's a custom one... 511 // so, check to see if it's in the .vmx configuration 512 if err != nil || network == "custom" { 513 vmxPath := state.Get("vmx_path").(string) 514 vmxData, err := readVMXConfig(vmxPath) 515 if err != nil { 516 return "", err 517 } 518 519 var device string 520 device, err = readCustomDeviceName(vmxData) 521 devices = append(devices, device) 522 if err != nil { 523 return "", err 524 } 525 log.Printf("HostIP discovered custom device matching %s: %s", network, device) 526 } 527 528 var lastError error 529 for _, device := range devices { 530 // parse dhcpd configuration 531 pathDhcpConfig := d.DhcpConfPath(device) 532 if _, err := os.Stat(pathDhcpConfig); err != nil { 533 return "", fmt.Errorf("Could not find vmnetdhcp conf file: %s", pathDhcpConfig) 534 } 535 config, err := ReadDhcpConfig(pathDhcpConfig) 536 if err != nil { 537 lastError = err 538 continue 539 } 540 541 // find the entry configured in the dhcpd 542 interfaceConfig, err := config.HostByName(device) 543 if err != nil { 544 lastError = err 545 continue 546 } 547 548 address, err := interfaceConfig.IP4() 549 if err != nil { 550 lastError = err 551 continue 552 } 553 554 return address.String(), nil 555 } 556 return "", fmt.Errorf("Unable to find host IP from devices %v, last error: %s", devices, lastError) 557 }