From 8bd3ca4470c454353591d0c0c79ef1783602c17a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Dec 2013 11:00:51 -0700 Subject: [PATCH] builder/vmware/common: shuffling stuff around --- builder/vmware/common/driver.go | 43 ++++++++++++ builder/vmware/common/guest_ip.go | 89 +++++++++++++++++++++++++ builder/vmware/common/ssh.go | 77 ++++++++++++++++++++++ builder/vmware/iso/ssh.go | 105 ------------------------------ 4 files changed, 209 insertions(+), 105 deletions(-) create mode 100644 builder/vmware/common/driver.go create mode 100644 builder/vmware/common/guest_ip.go delete mode 100644 builder/vmware/iso/ssh.go diff --git a/builder/vmware/common/driver.go b/builder/vmware/common/driver.go new file mode 100644 index 000000000..55de3ff14 --- /dev/null +++ b/builder/vmware/common/driver.go @@ -0,0 +1,43 @@ +package common + +import ( + "github.com/mitchellh/multistep" +) + +// A driver is able to talk to VMware, control virtual machines, etc. +type Driver interface { + // CompactDisk compacts a virtual disk. + CompactDisk(string) error + + // CreateDisk creates a virtual disk with the given size. + CreateDisk(string, string, string) error + + // Checks if the VMX file at the given path is running. + IsRunning(string) (bool, error) + + // SSHAddress returns the SSH address for the VM that is being + // managed by this driver. + SSHAddress(multistep.StateBag) (string, error) + + // Start starts a VM specified by the path to the VMX given. + Start(string, bool) error + + // Stop stops a VM specified by the path to the VMX given. + Stop(string) error + + // SuppressMessages modifies the VMX or surrounding directory so that + // VMware doesn't show any annoying messages. + SuppressMessages(string) error + + // Get the path to the VMware ISO for the given flavor. + ToolsIsoPath(string) string + + // Get the path to the DHCP leases file for the given device. + DhcpLeasesPath(string) string + + // Verify checks to make sure that this driver should function + // properly. This should check that all the files it will use + // appear to exist and so on. If everything is okay, this doesn't + // return an error. Otherwise, this returns an error. + Verify() error +} diff --git a/builder/vmware/common/guest_ip.go b/builder/vmware/common/guest_ip.go new file mode 100644 index 000000000..ad345d435 --- /dev/null +++ b/builder/vmware/common/guest_ip.go @@ -0,0 +1,89 @@ +package common + +import ( + "errors" + "io/ioutil" + "log" + "os" + "regexp" + "strings" + "time" +) + +// Interface to help find the IP address of a running virtual machine. +type GuestIPFinder interface { + GuestIP() (string, error) +} + +// DHCPLeaseGuestLookup looks up the IP address of a guest using DHCP +// lease information from the VMware network devices. +type DHCPLeaseGuestLookup struct { + // Driver that is being used (to find leases path) + Driver Driver + + // Device that the guest is connected to. + Device string + + // MAC address of the guest. + MACAddress string +} + +func (f *DHCPLeaseGuestLookup) GuestIP() (string, error) { + dhcpLeasesPath := f.Driver.DhcpLeasesPath(f.Device) + log.Printf("DHCP leases path: %s", dhcpLeasesPath) + if dhcpLeasesPath == "" { + return "", errors.New("no DHCP leases path found.") + } + + fh, err := os.Open(dhcpLeasesPath) + if err != nil { + return "", err + } + defer fh.Close() + + dhcpBytes, err := ioutil.ReadAll(fh) + if err != nil { + return "", err + } + + var lastIp string + var lastLeaseEnd time.Time + + var curIp string + var curLeaseEnd time.Time + + ipLineRe := regexp.MustCompile(`^lease (.+?) {$`) + endTimeLineRe := regexp.MustCompile(`^\s*ends \d (.+?);$`) + macLineRe := regexp.MustCompile(`^\s*hardware ethernet (.+?);$`) + + for _, line := range strings.Split(string(dhcpBytes), "\n") { + // Need to trim off CR character when running in windows + line = strings.TrimRight(line, "\r") + + matches := ipLineRe.FindStringSubmatch(line) + if matches != nil { + lastIp = matches[1] + continue + } + + matches = endTimeLineRe.FindStringSubmatch(line) + if matches != nil { + lastLeaseEnd, _ = time.Parse("2006/01/02 15:04:05", matches[1]) + continue + } + + // If the mac address matches and this lease ends farther in the + // future than the last match we might have, then choose it. + matches = macLineRe.FindStringSubmatch(line) + if matches != nil && matches[1] == f.MACAddress && curLeaseEnd.Before(lastLeaseEnd) { + curIp = lastIp + curLeaseEnd = lastLeaseEnd + } + } + + if curIp == "" { + return "", errors.New("IP not found for MAC in DHCP leases") + } + + return curIp, nil +} diff --git a/builder/vmware/common/ssh.go b/builder/vmware/common/ssh.go index 985f2eb76..7923dc376 100644 --- a/builder/vmware/common/ssh.go +++ b/builder/vmware/common/ssh.go @@ -2,12 +2,89 @@ package common import ( gossh "code.google.com/p/go.crypto/ssh" + "errors" + "fmt" "io/ioutil" + "log" "os" + "github.com/mitchellh/multistep" "github.com/mitchellh/packer/communicator/ssh" ) +func sshAddress(config *SSHConfig) func(multistep.StateBag) (string, error) { + return func(state multistep.StateBag) (string, error) { + driver := state.Get("driver").(Driver) + vmxPath := state.Get("vmx_path").(string) + + log.Println("Lookup up IP information...") + f, err := os.Open(vmxPath) + if err != nil { + return "", err + } + defer f.Close() + + vmxBytes, err := ioutil.ReadAll(f) + if err != nil { + return "", err + } + + vmxData := ParseVMX(string(vmxBytes)) + + var ok bool + macAddress := "" + if macAddress, ok = vmxData["ethernet0.address"]; !ok || macAddress == "" { + if macAddress, ok = vmxData["ethernet0.generatedaddress"]; !ok || macAddress == "" { + return "", errors.New("couldn't find MAC address in VMX") + } + } + + ipLookup := &DHCPLeaseGuestLookup{ + Driver: driver, + Device: "vmnet8", + MACAddress: macAddress, + } + + ipAddress, err := ipLookup.GuestIP() + if err != nil { + log.Printf("IP lookup failed: %s", err) + return "", fmt.Errorf("IP lookup failed: %s", err) + } + + if ipAddress == "" { + log.Println("IP is blank, no IP yet.") + return "", errors.New("IP is blank") + } + + log.Printf("Detected IP: %s", ipAddress) + return fmt.Sprintf("%s:%d", ipAddress, config.SSHPort), nil + } +} + +func sshConfig(config *SSHConfig) func(multistep.StateBag) (*gossh.ClientConfig, error) { + return func(state multistep.StateBag) (*gossh.ClientConfig, error) { + auth := []gossh.ClientAuth{ + gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)), + gossh.ClientAuthKeyboardInteractive( + ssh.PasswordKeyboardInteractive(config.SSHPassword)), + } + + if config.SSHKeyPath != "" { + keyring, err := sshKeyToKeyring(config.SSHKeyPath) + if err != nil { + return nil, err + } + + auth = append(auth, gossh.ClientAuthKeyring(keyring)) + } + + return &gossh.ClientConfig{ + User: config.SSHUser, + Auth: auth, + }, nil + } +} + func sshKeyToKeyring(path string) (gossh.ClientKeyring, error) { f, err := os.Open(path) if err != nil { diff --git a/builder/vmware/iso/ssh.go b/builder/vmware/iso/ssh.go deleted file mode 100644 index 32c7cb962..000000000 --- a/builder/vmware/iso/ssh.go +++ /dev/null @@ -1,105 +0,0 @@ -package iso - -import ( - gossh "code.google.com/p/go.crypto/ssh" - "errors" - "fmt" - "github.com/mitchellh/multistep" - vmwcommon "github.com/mitchellh/packer/builder/vmware/common" - "github.com/mitchellh/packer/communicator/ssh" - "io/ioutil" - "log" - "os" -) - -func sshAddress(state multistep.StateBag) (string, error) { - config := state.Get("config").(*config) - driver := state.Get("driver").(Driver) - vmxPath := state.Get("vmx_path").(string) - - log.Println("Lookup up IP information...") - f, err := os.Open(vmxPath) - if err != nil { - return "", err - } - defer f.Close() - - vmxBytes, err := ioutil.ReadAll(f) - if err != nil { - return "", err - } - - vmxData := vmwcommon.ParseVMX(string(vmxBytes)) - - var ok bool - macAddress := "" - if macAddress, ok = vmxData["ethernet0.address"]; !ok || macAddress == "" { - if macAddress, ok = vmxData["ethernet0.generatedaddress"]; !ok || macAddress == "" { - return "", errors.New("couldn't find MAC address in VMX") - } - } - - ipLookup := &DHCPLeaseGuestLookup{ - Driver: driver, - Device: "vmnet8", - MACAddress: macAddress, - } - - ipAddress, err := ipLookup.GuestIP() - if err != nil { - log.Printf("IP lookup failed: %s", err) - return "", fmt.Errorf("IP lookup failed: %s", err) - } - - if ipAddress == "" { - log.Println("IP is blank, no IP yet.") - return "", errors.New("IP is blank") - } - - log.Printf("Detected IP: %s", ipAddress) - return fmt.Sprintf("%s:%d", ipAddress, config.SSHPort), nil -} - -func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) { - config := state.Get("config").(*config) - - auth := []gossh.ClientAuth{ - gossh.ClientAuthPassword(ssh.Password(config.SSHPassword)), - gossh.ClientAuthKeyboardInteractive( - ssh.PasswordKeyboardInteractive(config.SSHPassword)), - } - - if config.SSHKeyPath != "" { - keyring, err := sshKeyToKeyring(config.SSHKeyPath) - if err != nil { - return nil, err - } - - auth = append(auth, gossh.ClientAuthKeyring(keyring)) - } - - return &gossh.ClientConfig{ - User: config.SSHUser, - Auth: auth, - }, nil -} - -func sshKeyToKeyring(path string) (gossh.ClientKeyring, error) { - f, err := os.Open(path) - if err != nil { - return nil, err - } - defer f.Close() - - keyBytes, err := ioutil.ReadAll(f) - if err != nil { - return nil, err - } - - keyring := new(ssh.SimpleKeychain) - if err := keyring.AddPEMKey(string(keyBytes)); err != nil { - return nil, err - } - - return keyring, nil -}