2013-12-24 00:58:41 -05:00
|
|
|
package iso
|
2013-09-19 20:07:04 -04:00
|
|
|
|
|
|
|
import (
|
2013-11-12 15:49:57 -05:00
|
|
|
"bufio"
|
2013-09-19 20:07:04 -04:00
|
|
|
"bytes"
|
|
|
|
"encoding/csv"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"log"
|
|
|
|
"net"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
"time"
|
2015-05-27 17:16:28 -04:00
|
|
|
|
2017-04-13 20:10:40 -04:00
|
|
|
vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
|
2017-04-04 16:39:01 -04:00
|
|
|
commonssh "github.com/hashicorp/packer/common/ssh"
|
|
|
|
"github.com/hashicorp/packer/communicator/ssh"
|
2018-01-19 19:18:44 -05:00
|
|
|
"github.com/hashicorp/packer/helper/multistep"
|
2017-04-04 16:39:01 -04:00
|
|
|
"github.com/hashicorp/packer/packer"
|
2015-04-04 01:30:13 -04:00
|
|
|
gossh "golang.org/x/crypto/ssh"
|
2013-09-19 20:07:04 -04:00
|
|
|
)
|
|
|
|
|
2013-11-08 00:18:25 -05:00
|
|
|
// ESX5 driver talks to an ESXi5 hypervisor remotely over SSH to build
|
|
|
|
// virtual machines. This driver can only manage one machine at a time.
|
2013-09-19 20:07:04 -04:00
|
|
|
type ESX5Driver struct {
|
2017-04-13 18:39:55 -04:00
|
|
|
base vmwcommon.VmwareDriver
|
2015-11-11 07:39:06 -05:00
|
|
|
|
2014-10-28 11:35:21 -04:00
|
|
|
Host string
|
|
|
|
Port uint
|
|
|
|
Username string
|
|
|
|
Password string
|
2015-11-03 22:08:35 -05:00
|
|
|
PrivateKey string
|
2014-10-28 11:35:21 -04:00
|
|
|
Datastore string
|
2014-09-10 01:10:06 -04:00
|
|
|
CacheDatastore string
|
|
|
|
CacheDirectory string
|
2013-11-07 15:00:27 -05:00
|
|
|
|
2013-11-08 00:18:25 -05:00
|
|
|
comm packer.Communicator
|
|
|
|
outputDir string
|
2014-04-21 11:25:32 -04:00
|
|
|
vmId string
|
2013-09-19 20:07:04 -04:00
|
|
|
}
|
|
|
|
|
2013-12-26 17:03:12 -05:00
|
|
|
func (d *ESX5Driver) Clone(dst, src string) error {
|
|
|
|
return errors.New("Cloning is not supported with the ESX driver.")
|
|
|
|
}
|
|
|
|
|
2013-09-19 20:07:04 -04:00
|
|
|
func (d *ESX5Driver) CompactDisk(diskPathLocal string) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-12-25 15:08:08 -05:00
|
|
|
func (d *ESX5Driver) CreateDisk(diskPathLocal string, size string, adapter_type string, typeId string) error {
|
2013-09-19 20:07:04 -04:00
|
|
|
diskPath := d.datastorePath(diskPathLocal)
|
2017-12-25 15:08:08 -05:00
|
|
|
return d.sh("vmkfstools", "-c", size, "-d", typeId, "-a", adapter_type, diskPath)
|
2013-09-19 20:07:04 -04:00
|
|
|
}
|
|
|
|
|
2014-04-21 11:25:32 -04:00
|
|
|
func (d *ESX5Driver) IsRunning(string) (bool, error) {
|
|
|
|
state, err := d.run(nil, "vim-cmd", "vmsvc/power.getstate", d.vmId)
|
2013-09-19 20:07:04 -04:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
return strings.Contains(state, "Powered on"), nil
|
|
|
|
}
|
|
|
|
|
2014-11-16 13:31:08 -05:00
|
|
|
func (d *ESX5Driver) ReloadVM() error {
|
|
|
|
return d.sh("vim-cmd", "vmsvc/reload", d.vmId)
|
|
|
|
}
|
|
|
|
|
2013-09-19 20:07:04 -04:00
|
|
|
func (d *ESX5Driver) Start(vmxPathLocal string, headless bool) error {
|
2014-08-11 03:18:12 -04:00
|
|
|
for i := 0; i < 20; i++ {
|
2016-08-19 07:21:50 -04:00
|
|
|
//intentionally not checking for error since poweron may fail specially after initial VM registration
|
|
|
|
d.sh("vim-cmd", "vmsvc/power.on", d.vmId)
|
2014-08-11 03:18:12 -04:00
|
|
|
time.Sleep((time.Duration(i) * time.Second) + 1)
|
|
|
|
running, err := d.IsRunning(vmxPathLocal)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if running {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return errors.New("Retry limit exceeded")
|
2013-09-19 20:07:04 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (d *ESX5Driver) Stop(vmxPathLocal string) error {
|
2014-04-21 11:25:32 -04:00
|
|
|
return d.sh("vim-cmd", "vmsvc/power.off", d.vmId)
|
2013-09-19 20:07:04 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (d *ESX5Driver) Register(vmxPathLocal string) error {
|
2014-07-02 08:55:47 -04:00
|
|
|
vmxPath := filepath.ToSlash(filepath.Join(d.outputDir, filepath.Base(vmxPathLocal)))
|
2013-11-07 23:47:15 -05:00
|
|
|
if err := d.upload(vmxPath, vmxPathLocal); err != nil {
|
2013-09-19 20:07:04 -04:00
|
|
|
return err
|
|
|
|
}
|
2014-04-21 11:25:32 -04:00
|
|
|
r, err := d.run(nil, "vim-cmd", "solo/registervm", vmxPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
d.vmId = strings.TrimRight(r, "\n")
|
|
|
|
return nil
|
2013-09-19 20:07:04 -04:00
|
|
|
}
|
|
|
|
|
2013-11-08 15:19:09 -05:00
|
|
|
func (d *ESX5Driver) SuppressMessages(vmxPath string) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-09-19 20:07:04 -04:00
|
|
|
func (d *ESX5Driver) Unregister(vmxPathLocal string) error {
|
2014-04-21 11:25:32 -04:00
|
|
|
return d.sh("vim-cmd", "vmsvc/unregister", d.vmId)
|
2013-09-19 20:07:04 -04:00
|
|
|
}
|
|
|
|
|
2015-02-13 05:13:58 -05:00
|
|
|
func (d *ESX5Driver) Destroy() error {
|
|
|
|
return d.sh("vim-cmd", "vmsvc/destroy", d.vmId)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *ESX5Driver) IsDestroyed() (bool, error) {
|
|
|
|
err := d.sh("test", "!", "-e", d.outputDir)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
return true, err
|
|
|
|
}
|
|
|
|
|
2014-06-12 01:33:34 -04:00
|
|
|
func (d *ESX5Driver) UploadISO(localPath string, checksum string, checksumType string) (string, error) {
|
2014-09-10 01:10:06 -04:00
|
|
|
finalPath := d.cachePath(localPath)
|
2014-07-02 08:55:47 -04:00
|
|
|
if err := d.mkdir(filepath.ToSlash(filepath.Dir(finalPath))); err != nil {
|
2013-11-07 15:28:41 -05:00
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2014-06-12 01:33:34 -04:00
|
|
|
log.Printf("Verifying checksum of %s", finalPath)
|
|
|
|
if d.verifyChecksum(checksumType, checksum, finalPath) {
|
|
|
|
log.Println("Initial checksum matched, no upload needed.")
|
|
|
|
return finalPath, nil
|
|
|
|
}
|
|
|
|
|
2013-11-07 15:28:41 -05:00
|
|
|
if err := d.upload(finalPath, localPath); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return finalPath, nil
|
|
|
|
}
|
|
|
|
|
2018-04-26 13:56:48 -04:00
|
|
|
func (d *ESX5Driver) RemoveCache(localPath string) error {
|
2018-04-26 15:00:35 -04:00
|
|
|
finalPath := d.cachePath(localPath)
|
2018-04-26 13:56:48 -04:00
|
|
|
log.Printf("Removing remote cache path %s (local %s)", finalPath, localPath)
|
2018-04-26 15:38:28 -04:00
|
|
|
return d.sh("rm", "-f", finalPath)
|
2018-04-26 13:56:48 -04:00
|
|
|
}
|
|
|
|
|
2013-09-19 20:07:04 -04:00
|
|
|
func (d *ESX5Driver) ToolsIsoPath(string) string {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2014-04-21 11:25:32 -04:00
|
|
|
func (d *ESX5Driver) ToolsInstall() error {
|
|
|
|
return d.sh("vim-cmd", "vmsvc/tools.install", d.vmId)
|
|
|
|
}
|
|
|
|
|
2013-09-19 20:07:04 -04:00
|
|
|
func (d *ESX5Driver) Verify() error {
|
2018-02-20 20:42:45 -05:00
|
|
|
// Ensure that NetworkMapper is nil, since the mapping of device<->network
|
|
|
|
// is handled by ESX and thus can't be performed by packer unless we
|
|
|
|
// query things.
|
|
|
|
|
|
|
|
// FIXME: If we want to expose the network devices to the user, then we can
|
|
|
|
// probably use esxcli to enumerate the portgroup and switchId
|
|
|
|
d.base.NetworkMapper = nil
|
|
|
|
|
|
|
|
// Be safe/friendly and overwrite the rest of the utility functions with
|
|
|
|
// log functions despite the fact that these shouldn't be called anyways.
|
|
|
|
d.base.DhcpLeasesPath = func(device string) string {
|
|
|
|
log.Printf("Unexpected error, ESX5 driver attempted to call DhcpLeasesPath(%#v)\n", device)
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
d.base.DhcpConfPath = func(device string) string {
|
|
|
|
log.Printf("Unexpected error, ESX5 driver attempted to call DhcpConfPath(%#v)\n", device)
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
d.base.VmnetnatConfPath = func(device string) string {
|
|
|
|
log.Printf("Unexpected error, ESX5 driver attempted to call VmnetnatConfPath(%#v)\n", device)
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2013-09-19 20:07:04 -04:00
|
|
|
checks := []func() error{
|
|
|
|
d.connect,
|
|
|
|
d.checkSystemVersion,
|
|
|
|
d.checkGuestIPHackEnabled,
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, check := range checks {
|
2013-11-07 15:00:27 -05:00
|
|
|
if err := check(); err != nil {
|
2013-09-19 20:07:04 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-11-11 07:39:06 -05:00
|
|
|
func (d *ESX5Driver) HostIP(multistep.StateBag) (string, error) {
|
2014-02-13 05:53:47 -05:00
|
|
|
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", d.Host, d.Port))
|
2013-09-19 20:07:04 -04:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2017-10-05 19:34:13 -04:00
|
|
|
defer conn.Close()
|
2013-09-19 20:07:04 -04:00
|
|
|
|
2014-02-13 05:53:47 -05:00
|
|
|
host, _, err := net.SplitHostPort(conn.LocalAddr().String())
|
|
|
|
return host, err
|
2013-09-19 20:07:04 -04:00
|
|
|
}
|
|
|
|
|
2017-04-13 18:39:55 -04:00
|
|
|
func (d *ESX5Driver) GuestIP(multistep.StateBag) (string, error) {
|
|
|
|
// GuestIP is defined by the user as d.Host..but let's validate it just to be sure
|
|
|
|
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", d.Host, d.Port))
|
|
|
|
defer conn.Close()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
host, _, err := net.SplitHostPort(conn.RemoteAddr().String())
|
|
|
|
return host, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *ESX5Driver) HostAddress(multistep.StateBag) (string, error) {
|
|
|
|
// make a connection
|
|
|
|
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", d.Host, d.Port))
|
|
|
|
defer conn.Close()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
// get the local address (the host)
|
|
|
|
host, _, err := net.SplitHostPort(conn.LocalAddr().String())
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("Unable to determine host address for ESXi: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// iterate through all the interfaces..
|
|
|
|
interfaces, err := net.Interfaces()
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("Unable to enumerate host interfaces : %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, intf := range interfaces {
|
|
|
|
addrs, err := intf.Addrs()
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// ..checking to see if any if it's addrs match the host address
|
|
|
|
for _, addr := range addrs {
|
|
|
|
if addr.String() == host { // FIXME: Is this the proper way to compare two HardwareAddrs?
|
|
|
|
return intf.HardwareAddr.String(), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ..unfortunately nothing was found
|
|
|
|
return "", fmt.Errorf("Unable to locate interface matching host address in ESXi: %v", host)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *ESX5Driver) GuestAddress(multistep.StateBag) (string, error) {
|
|
|
|
// list all the interfaces on the esx host
|
|
|
|
r, err := d.esxcli("network", "ip", "interface", "list")
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("Could not retrieve network interfaces for ESXi: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// rip out the interface name and the MAC address from the csv output
|
|
|
|
addrs := make(map[string]string)
|
|
|
|
for record, err := r.read(); record != nil && err == nil; record, err = r.read() {
|
|
|
|
if strings.ToUpper(record["Enabled"]) != "TRUE" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
addrs[record["Name"]] = record["MAC Address"]
|
|
|
|
}
|
|
|
|
|
|
|
|
// list all the addresses on the esx host
|
|
|
|
r, err = d.esxcli("network", "ip", "interface", "ipv4", "get")
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("Could not retrieve network addresses for ESXi: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// figure out the interface name that matches the specified d.Host address
|
|
|
|
var intf string
|
|
|
|
intf = ""
|
|
|
|
for record, err := r.read(); record != nil && err == nil; record, err = r.read() {
|
|
|
|
if record["IPv4 Address"] == d.Host && record["Name"] != "" {
|
|
|
|
intf = record["Name"]
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if intf == "" {
|
|
|
|
return "", fmt.Errorf("Unable to find matching address for ESXi guest")
|
|
|
|
}
|
|
|
|
|
|
|
|
// find the MAC address according to the interface name
|
|
|
|
result, ok := addrs[intf]
|
|
|
|
if !ok {
|
|
|
|
return "", fmt.Errorf("Unable to find address for ESXi guest interface")
|
|
|
|
}
|
|
|
|
|
|
|
|
// ..and we're good
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2016-07-21 12:14:02 -04:00
|
|
|
func (d *ESX5Driver) VNCAddress(_ string, portMin, portMax uint) (string, uint, error) {
|
2013-09-19 20:07:04 -04:00
|
|
|
var vncPort uint
|
2014-08-18 22:50:48 -04:00
|
|
|
|
|
|
|
//Process ports ESXi is listening on to determine which are available
|
2014-09-16 23:00:52 -04:00
|
|
|
//This process does best effort to detect ports that are unavailable,
|
|
|
|
//it will ignore any ports listened to by only localhost
|
2014-08-18 22:50:48 -04:00
|
|
|
r, err := d.esxcli("network", "ip", "connection", "list")
|
|
|
|
if err != nil {
|
|
|
|
err = fmt.Errorf("Could not retrieve network information for ESXi: %v", err)
|
|
|
|
return "", 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
listenPorts := make(map[string]bool)
|
|
|
|
for record, err := r.read(); record != nil && err == nil; record, err = r.read() {
|
|
|
|
if record["State"] == "LISTEN" {
|
|
|
|
splitAddress := strings.Split(record["LocalAddress"], ":")
|
2014-09-16 23:00:52 -04:00
|
|
|
if splitAddress[0] != "127.0.0.1" {
|
|
|
|
port := splitAddress[len(splitAddress)-1]
|
|
|
|
log.Printf("ESXi listening on address %s, port %s unavailable for VNC", record["LocalAddress"], port)
|
|
|
|
listenPorts[port] = true
|
|
|
|
}
|
2014-08-18 22:50:48 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-22 20:58:18 -04:00
|
|
|
vncTimeout := time.Duration(15 * time.Second)
|
2017-05-23 17:50:49 -04:00
|
|
|
envTimeout := os.Getenv("PACKER_ESXI_VNC_PROBE_TIMEOUT")
|
|
|
|
if envTimeout != "" {
|
|
|
|
if parsedTimeout, err := time.ParseDuration(envTimeout); err != nil {
|
|
|
|
log.Printf("Error parsing PACKER_ESXI_VNC_PROBE_TIMEOUT. Falling back to default (15s). %s", err)
|
|
|
|
} else {
|
|
|
|
vncTimeout = parsedTimeout
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-19 20:07:04 -04:00
|
|
|
for port := portMin; port <= portMax; port++ {
|
2014-08-20 21:42:05 -04:00
|
|
|
if _, ok := listenPorts[fmt.Sprintf("%d", port)]; ok {
|
2014-08-18 22:50:48 -04:00
|
|
|
log.Printf("Port %d in use", port)
|
|
|
|
continue
|
|
|
|
}
|
2013-11-07 15:00:27 -05:00
|
|
|
address := fmt.Sprintf("%s:%d", d.Host, port)
|
2013-09-19 20:07:04 -04:00
|
|
|
log.Printf("Trying address: %s...", address)
|
2017-05-23 17:50:49 -04:00
|
|
|
l, err := net.DialTimeout("tcp", address, vncTimeout)
|
2014-08-18 22:50:48 -04:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
if e, ok := err.(*net.OpError); ok {
|
|
|
|
if e.Timeout() {
|
|
|
|
log.Printf("Timeout connecting to: %s (check firewall rules)", address)
|
|
|
|
} else {
|
|
|
|
vncPort = port
|
|
|
|
break
|
|
|
|
}
|
2013-09-19 20:07:04 -04:00
|
|
|
}
|
2014-08-18 22:50:48 -04:00
|
|
|
} else {
|
|
|
|
defer l.Close()
|
2013-09-19 20:07:04 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-18 21:28:25 -04:00
|
|
|
if vncPort == 0 {
|
|
|
|
err := fmt.Errorf("Unable to find available VNC port between %d and %d",
|
|
|
|
portMin, portMax)
|
|
|
|
return d.Host, vncPort, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return d.Host, vncPort, nil
|
2013-09-19 20:07:04 -04:00
|
|
|
}
|
|
|
|
|
2016-07-21 12:14:02 -04:00
|
|
|
// UpdateVMX, adds the VNC port to the VMX data.
|
2015-06-26 04:29:33 -04:00
|
|
|
func (ESX5Driver) UpdateVMX(_, password string, port uint, data map[string]string) {
|
2016-07-21 12:14:02 -04:00
|
|
|
// Do not set remotedisplay.vnc.ip - this breaks ESXi.
|
|
|
|
data["remotedisplay.vnc.enabled"] = "TRUE"
|
|
|
|
data["remotedisplay.vnc.port"] = fmt.Sprintf("%d", port)
|
2016-08-19 06:49:23 -04:00
|
|
|
if len(password) > 0 {
|
|
|
|
data["remotedisplay.vnc.password"] = password
|
|
|
|
}
|
2016-07-21 12:14:02 -04:00
|
|
|
}
|
|
|
|
|
2015-06-13 19:23:33 -04:00
|
|
|
func (d *ESX5Driver) CommHost(state multistep.StateBag) (string, error) {
|
2015-05-27 17:16:28 -04:00
|
|
|
config := state.Get("config").(*Config)
|
2016-08-26 16:58:04 -04:00
|
|
|
sshc := config.SSHConfig.Comm
|
|
|
|
port := sshc.SSHPort
|
|
|
|
if sshc.Type == "winrm" {
|
|
|
|
port = sshc.WinRMPort
|
|
|
|
}
|
2013-11-07 15:00:27 -05:00
|
|
|
|
2016-08-19 07:02:55 -04:00
|
|
|
if address := config.CommConfig.Host(); address != "" {
|
|
|
|
return address, nil
|
|
|
|
}
|
|
|
|
|
2014-05-02 11:25:58 -04:00
|
|
|
r, err := d.esxcli("network", "vm", "list")
|
2013-11-07 15:00:27 -05:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2014-05-02 11:25:58 -04:00
|
|
|
record, err := r.find("Name", config.VMName)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
wid := record["WorldID"]
|
|
|
|
if wid == "" {
|
|
|
|
return "", errors.New("VM WorldID not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
r, err = d.esxcli("network", "vm", "port", "list", "-w", wid)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2016-08-19 07:23:43 -04:00
|
|
|
// Loop through interfaces
|
|
|
|
for {
|
|
|
|
record, err = r.read()
|
|
|
|
if err == io.EOF {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2013-11-07 15:00:27 -05:00
|
|
|
|
2016-08-19 07:23:43 -04:00
|
|
|
if record["IPAddress"] == "0.0.0.0" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// When multiple NICs are connected to the same network, choose
|
|
|
|
// one that has a route back. This Dial should ensure that.
|
2016-08-26 16:58:04 -04:00
|
|
|
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", record["IPAddress"], port), 2*time.Second)
|
2016-08-19 07:23:43 -04:00
|
|
|
if err != nil {
|
|
|
|
if e, ok := err.(*net.OpError); ok {
|
|
|
|
if e.Timeout() {
|
|
|
|
log.Printf("Timeout connecting to %s", record["IPAddress"])
|
|
|
|
continue
|
2017-10-13 19:00:48 -04:00
|
|
|
} else if strings.Contains(e.Error(), "connection refused") {
|
|
|
|
log.Printf("Connection refused when connecting to: %s", record["IPAddress"])
|
|
|
|
continue
|
2016-08-19 07:23:43 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
defer conn.Close()
|
|
|
|
address := record["IPAddress"]
|
|
|
|
return address, nil
|
|
|
|
}
|
2013-11-07 15:00:27 -05:00
|
|
|
}
|
2016-08-19 07:23:43 -04:00
|
|
|
return "", errors.New("No interface on the VM has an IP address ready")
|
2013-09-19 20:07:04 -04:00
|
|
|
}
|
|
|
|
|
2014-01-01 23:55:08 -05:00
|
|
|
//-------------------------------------------------------------------
|
|
|
|
// OutputDir implementation
|
|
|
|
//-------------------------------------------------------------------
|
|
|
|
|
2013-11-08 00:18:25 -05:00
|
|
|
func (d *ESX5Driver) DirExists() (bool, error) {
|
|
|
|
err := d.sh("test", "-e", d.outputDir)
|
2013-11-07 23:46:34 -05:00
|
|
|
return err == nil, nil
|
2013-09-19 20:07:04 -04:00
|
|
|
}
|
|
|
|
|
2013-11-12 15:49:57 -05:00
|
|
|
func (d *ESX5Driver) ListFiles() ([]string, error) {
|
|
|
|
stdout, err := d.ssh("ls -1p "+d.outputDir, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
files := make([]string, 0, 10)
|
|
|
|
reader := bufio.NewReader(stdout)
|
|
|
|
for {
|
|
|
|
line, _, err := reader.ReadLine()
|
|
|
|
if err == io.EOF {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if line[len(line)-1] == '/' {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2014-07-02 08:55:47 -04:00
|
|
|
files = append(files, filepath.ToSlash(filepath.Join(d.outputDir, string(line))))
|
2013-11-12 15:49:57 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return files, nil
|
|
|
|
}
|
|
|
|
|
2013-11-08 00:18:25 -05:00
|
|
|
func (d *ESX5Driver) MkdirAll() error {
|
|
|
|
return d.mkdir(d.outputDir)
|
|
|
|
}
|
|
|
|
|
2013-11-12 15:49:57 -05:00
|
|
|
func (d *ESX5Driver) Remove(path string) error {
|
|
|
|
return d.sh("rm", path)
|
|
|
|
}
|
|
|
|
|
2013-11-08 00:18:25 -05:00
|
|
|
func (d *ESX5Driver) RemoveAll() error {
|
|
|
|
return d.sh("rm", "-rf", d.outputDir)
|
2013-09-19 20:07:04 -04:00
|
|
|
}
|
|
|
|
|
2013-11-08 00:18:25 -05:00
|
|
|
func (d *ESX5Driver) SetOutputDir(path string) {
|
|
|
|
d.outputDir = d.datastorePath(path)
|
2013-09-19 20:07:04 -04:00
|
|
|
}
|
|
|
|
|
2014-01-01 23:55:08 -05:00
|
|
|
func (d *ESX5Driver) String() string {
|
|
|
|
return d.outputDir
|
|
|
|
}
|
|
|
|
|
2013-09-19 20:07:04 -04:00
|
|
|
func (d *ESX5Driver) datastorePath(path string) string {
|
2015-06-04 05:43:16 -04:00
|
|
|
dirPath := filepath.Dir(path)
|
|
|
|
return filepath.ToSlash(filepath.Join("/vmfs/volumes", d.Datastore, dirPath, filepath.Base(path)))
|
2013-09-19 20:07:04 -04:00
|
|
|
}
|
|
|
|
|
2014-09-10 01:10:06 -04:00
|
|
|
func (d *ESX5Driver) cachePath(path string) string {
|
|
|
|
return filepath.ToSlash(filepath.Join("/vmfs/volumes", d.CacheDatastore, d.CacheDirectory, filepath.Base(path)))
|
|
|
|
}
|
|
|
|
|
2013-09-19 20:07:04 -04:00
|
|
|
func (d *ESX5Driver) connect() error {
|
2013-11-07 15:00:27 -05:00
|
|
|
address := fmt.Sprintf("%s:%d", d.Host, d.Port)
|
|
|
|
|
2014-04-10 04:48:55 -04:00
|
|
|
auth := []gossh.AuthMethod{
|
|
|
|
gossh.Password(d.Password),
|
|
|
|
gossh.KeyboardInteractive(
|
2013-11-07 15:00:27 -05:00
|
|
|
ssh.PasswordKeyboardInteractive(d.Password)),
|
2013-09-19 20:07:04 -04:00
|
|
|
}
|
2013-11-07 15:00:27 -05:00
|
|
|
|
2015-11-03 22:08:35 -05:00
|
|
|
if d.PrivateKey != "" {
|
|
|
|
signer, err := commonssh.FileSigner(d.PrivateKey)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
auth = append(auth, gossh.PublicKeys(signer))
|
|
|
|
}
|
|
|
|
|
2013-09-19 20:07:04 -04:00
|
|
|
sshConfig := &ssh.Config{
|
|
|
|
Connection: ssh.ConnectFunc("tcp", address),
|
|
|
|
SSHConfig: &gossh.ClientConfig{
|
2017-06-21 13:09:11 -04:00
|
|
|
User: d.Username,
|
|
|
|
Auth: auth,
|
|
|
|
HostKeyCallback: gossh.InsecureIgnoreHostKey(),
|
2013-09-19 20:07:04 -04:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2014-04-10 04:48:55 -04:00
|
|
|
comm, err := ssh.New(address, sshConfig)
|
2013-09-19 20:07:04 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
d.comm = comm
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *ESX5Driver) checkSystemVersion() error {
|
|
|
|
r, err := d.esxcli("system", "version", "get")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
record, err := r.read()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2013-11-07 15:00:27 -05:00
|
|
|
log.Printf("Connected to %s %s %s", record["Product"],
|
|
|
|
record["Version"], record["Build"])
|
2013-09-19 20:07:04 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *ESX5Driver) checkGuestIPHackEnabled() error {
|
|
|
|
r, err := d.esxcli("system", "settings", "advanced", "list", "-o", "/Net/GuestIPHack")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
record, err := r.read()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if record["IntValue"] != "1" {
|
2013-12-07 13:31:57 -05:00
|
|
|
return errors.New(
|
|
|
|
"GuestIPHack is required, enable by running this on the ESX machine:\n" +
|
2013-12-11 14:19:36 -05:00
|
|
|
"esxcli system settings advanced set -o /Net/GuestIPHack -i 1")
|
2013-09-19 20:07:04 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-11-08 00:18:25 -05:00
|
|
|
func (d *ESX5Driver) mkdir(path string) error {
|
|
|
|
return d.sh("mkdir", "-p", path)
|
|
|
|
}
|
|
|
|
|
2013-11-07 23:47:15 -05:00
|
|
|
func (d *ESX5Driver) upload(dst, src string) error {
|
2013-09-19 20:07:04 -04:00
|
|
|
f, err := os.Open(src)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer f.Close()
|
2014-05-10 00:03:35 -04:00
|
|
|
return d.comm.Upload(dst, f, nil)
|
2013-09-19 20:07:04 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (d *ESX5Driver) verifyChecksum(ctype string, hash string, file string) bool {
|
2015-06-29 12:33:20 -04:00
|
|
|
if ctype == "none" {
|
|
|
|
if err := d.sh("stat", file); err != nil {
|
2015-06-25 21:03:00 -04:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
stdin := bytes.NewBufferString(fmt.Sprintf("%s %s", hash, file))
|
|
|
|
_, err := d.run(stdin, fmt.Sprintf("%ssum", ctype), "-c")
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
2013-09-19 20:07:04 -04:00
|
|
|
}
|
2015-06-29 12:33:20 -04:00
|
|
|
|
2013-09-19 20:07:04 -04:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *ESX5Driver) ssh(command string, stdin io.Reader) (*bytes.Buffer, error) {
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
|
|
|
|
|
|
cmd := &packer.RemoteCmd{
|
|
|
|
Command: command,
|
|
|
|
Stdout: &stdout,
|
|
|
|
Stderr: &stderr,
|
|
|
|
Stdin: stdin,
|
|
|
|
}
|
|
|
|
|
|
|
|
err := d.comm.Start(cmd)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd.Wait()
|
|
|
|
|
|
|
|
if cmd.ExitStatus != 0 {
|
|
|
|
err = fmt.Errorf("'%s'\n\nStdout: %s\n\nStderr: %s",
|
|
|
|
cmd.Command, stdout.String(), stderr.String())
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &stdout, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *ESX5Driver) run(stdin io.Reader, args ...string) (string, error) {
|
|
|
|
stdout, err := d.ssh(strings.Join(args, " "), stdin)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return stdout.String(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *ESX5Driver) sh(args ...string) error {
|
|
|
|
_, err := d.run(nil, args...)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *ESX5Driver) esxcli(args ...string) (*esxcliReader, error) {
|
|
|
|
stdout, err := d.ssh("esxcli --formatter csv "+strings.Join(args, " "), nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
r := csv.NewReader(bytes.NewReader(stdout.Bytes()))
|
|
|
|
r.TrailingComma = true
|
|
|
|
header, err := r.Read()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &esxcliReader{r, header}, nil
|
|
|
|
}
|
|
|
|
|
2017-04-13 18:39:55 -04:00
|
|
|
func (d *ESX5Driver) GetVmwareDriver() vmwcommon.VmwareDriver {
|
|
|
|
return d.base
|
|
|
|
}
|
|
|
|
|
2013-09-19 20:07:04 -04:00
|
|
|
type esxcliReader struct {
|
|
|
|
cr *csv.Reader
|
|
|
|
header []string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *esxcliReader) read() (map[string]string, error) {
|
|
|
|
fields, err := r.cr.Read()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
record := map[string]string{}
|
|
|
|
for i, v := range fields {
|
|
|
|
record[r.header[i]] = v
|
|
|
|
}
|
|
|
|
|
|
|
|
return record, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *esxcliReader) find(key, val string) (map[string]string, error) {
|
|
|
|
for {
|
|
|
|
record, err := r.read()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if record[key] == val {
|
|
|
|
return record, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|