github.com/mitchellh/packer@v1.3.2/builder/vmware/iso/driver_esx5.go (about)

     1  package iso
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"encoding/csv"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"log"
    11  	"net"
    12  	"os"
    13  	"path/filepath"
    14  	"strconv"
    15  	"strings"
    16  	"time"
    17  
    18  	vmwcommon "github.com/hashicorp/packer/builder/vmware/common"
    19  	"github.com/hashicorp/packer/communicator/ssh"
    20  	"github.com/hashicorp/packer/helper/multistep"
    21  	helperssh "github.com/hashicorp/packer/helper/ssh"
    22  	"github.com/hashicorp/packer/packer"
    23  	gossh "golang.org/x/crypto/ssh"
    24  )
    25  
    26  // ESX5 driver talks to an ESXi5 hypervisor remotely over SSH to build
    27  // virtual machines. This driver can only manage one machine at a time.
    28  type ESX5Driver struct {
    29  	base vmwcommon.VmwareDriver
    30  
    31  	Host           string
    32  	Port           uint
    33  	Username       string
    34  	Password       string
    35  	PrivateKeyFile string
    36  	Datastore      string
    37  	CacheDatastore string
    38  	CacheDirectory string
    39  
    40  	comm      packer.Communicator
    41  	outputDir string
    42  	vmId      string
    43  }
    44  
    45  func (d *ESX5Driver) Clone(dst, src string, linked bool) error {
    46  	return errors.New("Cloning is not supported with the ESX driver.")
    47  }
    48  
    49  func (d *ESX5Driver) CompactDisk(diskPathLocal string) error {
    50  	diskPath := d.datastorePath(diskPathLocal)
    51  	return d.sh("vmkfstools", "--punchzero", strconv.Quote(diskPath))
    52  }
    53  
    54  func (d *ESX5Driver) CreateDisk(diskPathLocal string, size string, adapter_type string, typeId string) error {
    55  	diskPath := strconv.Quote(d.datastorePath(diskPathLocal))
    56  	return d.sh("vmkfstools", "-c", size, "-d", typeId, "-a", adapter_type, diskPath)
    57  }
    58  
    59  func (d *ESX5Driver) IsRunning(string) (bool, error) {
    60  	state, err := d.run(nil, "vim-cmd", "vmsvc/power.getstate", d.vmId)
    61  	if err != nil {
    62  		return false, err
    63  	}
    64  	return strings.Contains(state, "Powered on"), nil
    65  }
    66  
    67  func (d *ESX5Driver) ReloadVM() error {
    68  	return d.sh("vim-cmd", "vmsvc/reload", d.vmId)
    69  }
    70  
    71  func (d *ESX5Driver) Start(vmxPathLocal string, headless bool) error {
    72  	for i := 0; i < 20; i++ {
    73  		//intentionally not checking for error since poweron may fail specially after initial VM registration
    74  		d.sh("vim-cmd", "vmsvc/power.on", d.vmId)
    75  		time.Sleep((time.Duration(i) * time.Second) + 1)
    76  		running, err := d.IsRunning(vmxPathLocal)
    77  		if err != nil {
    78  			return err
    79  		}
    80  		if running {
    81  			return nil
    82  		}
    83  	}
    84  	return errors.New("Retry limit exceeded")
    85  }
    86  
    87  func (d *ESX5Driver) Stop(vmxPathLocal string) error {
    88  	return d.sh("vim-cmd", "vmsvc/power.off", d.vmId)
    89  }
    90  
    91  func (d *ESX5Driver) Register(vmxPathLocal string) error {
    92  	vmxPath := filepath.ToSlash(filepath.Join(d.outputDir, filepath.Base(vmxPathLocal)))
    93  	if err := d.upload(vmxPath, vmxPathLocal); err != nil {
    94  		return err
    95  	}
    96  	r, err := d.run(nil, "vim-cmd", "solo/registervm", strconv.Quote(vmxPath))
    97  	if err != nil {
    98  		return err
    99  	}
   100  	d.vmId = strings.TrimRight(r, "\n")
   101  	return nil
   102  }
   103  
   104  func (d *ESX5Driver) SuppressMessages(vmxPath string) error {
   105  	return nil
   106  }
   107  
   108  func (d *ESX5Driver) Unregister(vmxPathLocal string) error {
   109  	return d.sh("vim-cmd", "vmsvc/unregister", d.vmId)
   110  }
   111  
   112  func (d *ESX5Driver) Destroy() error {
   113  	return d.sh("vim-cmd", "vmsvc/destroy", d.vmId)
   114  }
   115  
   116  func (d *ESX5Driver) IsDestroyed() (bool, error) {
   117  	err := d.sh("test", "!", "-e", strconv.Quote(d.outputDir))
   118  	if err != nil {
   119  		return false, err
   120  	}
   121  	return true, err
   122  }
   123  
   124  func (d *ESX5Driver) UploadISO(localPath string, checksum string, checksumType string) (string, error) {
   125  	finalPath := d.cachePath(localPath)
   126  	if err := d.mkdir(filepath.ToSlash(filepath.Dir(finalPath))); err != nil {
   127  		return "", err
   128  	}
   129  
   130  	log.Printf("Verifying checksum of %s", finalPath)
   131  	if d.verifyChecksum(checksumType, checksum, finalPath) {
   132  		log.Println("Initial checksum matched, no upload needed.")
   133  		return finalPath, nil
   134  	}
   135  
   136  	if err := d.upload(finalPath, localPath); err != nil {
   137  		return "", err
   138  	}
   139  
   140  	return finalPath, nil
   141  }
   142  
   143  func (d *ESX5Driver) RemoveCache(localPath string) error {
   144  	finalPath := d.cachePath(localPath)
   145  	log.Printf("Removing remote cache path %s (local %s)", finalPath, localPath)
   146  	return d.sh("rm", "-f", strconv.Quote(finalPath))
   147  }
   148  
   149  func (d *ESX5Driver) ToolsIsoPath(string) string {
   150  	return ""
   151  }
   152  
   153  func (d *ESX5Driver) ToolsInstall() error {
   154  	return d.sh("vim-cmd", "vmsvc/tools.install", d.vmId)
   155  }
   156  
   157  func (d *ESX5Driver) Verify() error {
   158  	// Ensure that NetworkMapper is nil, since the mapping of device<->network
   159  	// is handled by ESX and thus can't be performed by packer unless we
   160  	// query things.
   161  
   162  	// FIXME: If we want to expose the network devices to the user, then we can
   163  	// probably use esxcli to enumerate the portgroup and switchId
   164  	d.base.NetworkMapper = nil
   165  
   166  	// Be safe/friendly and overwrite the rest of the utility functions with
   167  	// log functions despite the fact that these shouldn't be called anyways.
   168  	d.base.DhcpLeasesPath = func(device string) string {
   169  		log.Printf("Unexpected error, ESX5 driver attempted to call DhcpLeasesPath(%#v)\n", device)
   170  		return ""
   171  	}
   172  	d.base.DhcpConfPath = func(device string) string {
   173  		log.Printf("Unexpected error, ESX5 driver attempted to call DhcpConfPath(%#v)\n", device)
   174  		return ""
   175  	}
   176  	d.base.VmnetnatConfPath = func(device string) string {
   177  		log.Printf("Unexpected error, ESX5 driver attempted to call VmnetnatConfPath(%#v)\n", device)
   178  		return ""
   179  	}
   180  
   181  	checks := []func() error{
   182  		d.connect,
   183  		d.checkSystemVersion,
   184  		d.checkGuestIPHackEnabled,
   185  	}
   186  
   187  	for _, check := range checks {
   188  		if err := check(); err != nil {
   189  			return err
   190  		}
   191  	}
   192  	return nil
   193  }
   194  
   195  func (d *ESX5Driver) HostIP(multistep.StateBag) (string, error) {
   196  	conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", d.Host, d.Port))
   197  	if err != nil {
   198  		return "", err
   199  	}
   200  	defer conn.Close()
   201  
   202  	host, _, err := net.SplitHostPort(conn.LocalAddr().String())
   203  	return host, err
   204  }
   205  
   206  func (d *ESX5Driver) GuestIP(multistep.StateBag) (string, error) {
   207  	// GuestIP is defined by the user as d.Host..but let's validate it just to be sure
   208  	conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", d.Host, d.Port))
   209  	defer conn.Close()
   210  	if err != nil {
   211  		return "", err
   212  	}
   213  
   214  	host, _, err := net.SplitHostPort(conn.RemoteAddr().String())
   215  	return host, err
   216  }
   217  
   218  func (d *ESX5Driver) HostAddress(multistep.StateBag) (string, error) {
   219  	// make a connection
   220  	conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", d.Host, d.Port))
   221  	defer conn.Close()
   222  	if err != nil {
   223  		return "", err
   224  	}
   225  
   226  	// get the local address (the host)
   227  	host, _, err := net.SplitHostPort(conn.LocalAddr().String())
   228  	if err != nil {
   229  		return "", fmt.Errorf("Unable to determine host address for ESXi: %v", err)
   230  	}
   231  
   232  	// iterate through all the interfaces..
   233  	interfaces, err := net.Interfaces()
   234  	if err != nil {
   235  		return "", fmt.Errorf("Unable to enumerate host interfaces : %v", err)
   236  	}
   237  
   238  	for _, intf := range interfaces {
   239  		addrs, err := intf.Addrs()
   240  		if err != nil {
   241  			continue
   242  		}
   243  
   244  		// ..checking to see if any if it's addrs match the host address
   245  		for _, addr := range addrs {
   246  			if addr.String() == host { // FIXME: Is this the proper way to compare two HardwareAddrs?
   247  				return intf.HardwareAddr.String(), nil
   248  			}
   249  		}
   250  	}
   251  
   252  	// ..unfortunately nothing was found
   253  	return "", fmt.Errorf("Unable to locate interface matching host address in ESXi: %v", host)
   254  }
   255  
   256  func (d *ESX5Driver) GuestAddress(multistep.StateBag) (string, error) {
   257  	// list all the interfaces on the esx host
   258  	r, err := d.esxcli("network", "ip", "interface", "list")
   259  	if err != nil {
   260  		return "", fmt.Errorf("Could not retrieve network interfaces for ESXi: %v", err)
   261  	}
   262  
   263  	// rip out the interface name and the MAC address from the csv output
   264  	addrs := make(map[string]string)
   265  	for record, err := r.read(); record != nil && err == nil; record, err = r.read() {
   266  		if strings.ToUpper(record["Enabled"]) != "TRUE" {
   267  			continue
   268  		}
   269  		addrs[record["Name"]] = record["MAC Address"]
   270  	}
   271  
   272  	// list all the addresses on the esx host
   273  	r, err = d.esxcli("network", "ip", "interface", "ipv4", "get")
   274  	if err != nil {
   275  		return "", fmt.Errorf("Could not retrieve network addresses for ESXi: %v", err)
   276  	}
   277  
   278  	// figure out the interface name that matches the specified d.Host address
   279  	var intf string
   280  	intf = ""
   281  	for record, err := r.read(); record != nil && err == nil; record, err = r.read() {
   282  		if record["IPv4 Address"] == d.Host && record["Name"] != "" {
   283  			intf = record["Name"]
   284  			break
   285  		}
   286  	}
   287  	if intf == "" {
   288  		return "", fmt.Errorf("Unable to find matching address for ESXi guest")
   289  	}
   290  
   291  	// find the MAC address according to the interface name
   292  	result, ok := addrs[intf]
   293  	if !ok {
   294  		return "", fmt.Errorf("Unable to find address for ESXi guest interface")
   295  	}
   296  
   297  	// ..and we're good
   298  	return result, nil
   299  }
   300  
   301  func (d *ESX5Driver) VNCAddress(_ string, portMin, portMax uint) (string, uint, error) {
   302  	var vncPort uint
   303  
   304  	//Process ports ESXi is listening on to determine which are available
   305  	//This process does best effort to detect ports that are unavailable,
   306  	//it will ignore any ports listened to by only localhost
   307  	r, err := d.esxcli("network", "ip", "connection", "list")
   308  	if err != nil {
   309  		err = fmt.Errorf("Could not retrieve network information for ESXi: %v", err)
   310  		return "", 0, err
   311  	}
   312  
   313  	listenPorts := make(map[string]bool)
   314  	for record, err := r.read(); record != nil && err == nil; record, err = r.read() {
   315  		if record["State"] == "LISTEN" {
   316  			splitAddress := strings.Split(record["LocalAddress"], ":")
   317  			if splitAddress[0] != "127.0.0.1" {
   318  				port := splitAddress[len(splitAddress)-1]
   319  				log.Printf("ESXi listening on address %s, port %s unavailable for VNC", record["LocalAddress"], port)
   320  				listenPorts[port] = true
   321  			}
   322  		}
   323  	}
   324  
   325  	vncTimeout := time.Duration(15 * time.Second)
   326  	envTimeout := os.Getenv("PACKER_ESXI_VNC_PROBE_TIMEOUT")
   327  	if envTimeout != "" {
   328  		if parsedTimeout, err := time.ParseDuration(envTimeout); err != nil {
   329  			log.Printf("Error parsing PACKER_ESXI_VNC_PROBE_TIMEOUT. Falling back to default (15s). %s", err)
   330  		} else {
   331  			vncTimeout = parsedTimeout
   332  		}
   333  	}
   334  
   335  	for port := portMin; port <= portMax; port++ {
   336  		if _, ok := listenPorts[fmt.Sprintf("%d", port)]; ok {
   337  			log.Printf("Port %d in use", port)
   338  			continue
   339  		}
   340  		address := fmt.Sprintf("%s:%d", d.Host, port)
   341  		log.Printf("Trying address: %s...", address)
   342  		l, err := net.DialTimeout("tcp", address, vncTimeout)
   343  
   344  		if err != nil {
   345  			if e, ok := err.(*net.OpError); ok {
   346  				if e.Timeout() {
   347  					log.Printf("Timeout connecting to: %s (check firewall rules)", address)
   348  				} else {
   349  					vncPort = port
   350  					break
   351  				}
   352  			}
   353  		} else {
   354  			defer l.Close()
   355  		}
   356  	}
   357  
   358  	if vncPort == 0 {
   359  		err := fmt.Errorf("Unable to find available VNC port between %d and %d",
   360  			portMin, portMax)
   361  		return d.Host, vncPort, err
   362  	}
   363  
   364  	return d.Host, vncPort, nil
   365  }
   366  
   367  // UpdateVMX, adds the VNC port to the VMX data.
   368  func (ESX5Driver) UpdateVMX(_, password string, port uint, data map[string]string) {
   369  	// Do not set remotedisplay.vnc.ip - this breaks ESXi.
   370  	data["remotedisplay.vnc.enabled"] = "TRUE"
   371  	data["remotedisplay.vnc.port"] = fmt.Sprintf("%d", port)
   372  	if len(password) > 0 {
   373  		data["remotedisplay.vnc.password"] = password
   374  	}
   375  }
   376  
   377  func (d *ESX5Driver) CommHost(state multistep.StateBag) (string, error) {
   378  	config := state.Get("config").(*Config)
   379  	sshc := config.SSHConfig.Comm
   380  	port := sshc.SSHPort
   381  	if sshc.Type == "winrm" {
   382  		port = sshc.WinRMPort
   383  	}
   384  
   385  	if address := config.CommConfig.Host(); address != "" {
   386  		return address, nil
   387  	}
   388  
   389  	r, err := d.esxcli("network", "vm", "list")
   390  	if err != nil {
   391  		return "", err
   392  	}
   393  
   394  	// The value in the Name field returned by 'esxcli network vm list'
   395  	// corresponds directly to the value of displayName set in the VMX file
   396  	var displayName string
   397  	if v, ok := state.GetOk("display_name"); ok {
   398  		displayName = v.(string)
   399  	}
   400  	record, err := r.find("Name", displayName)
   401  	if err != nil {
   402  		return "", err
   403  	}
   404  	wid := record["WorldID"]
   405  	if wid == "" {
   406  		return "", errors.New("VM WorldID not found")
   407  	}
   408  
   409  	r, err = d.esxcli("network", "vm", "port", "list", "-w", wid)
   410  	if err != nil {
   411  		return "", err
   412  	}
   413  
   414  	// Loop through interfaces
   415  	for {
   416  		record, err = r.read()
   417  		if err == io.EOF {
   418  			break
   419  		}
   420  		if err != nil {
   421  			return "", err
   422  		}
   423  
   424  		if record["IPAddress"] == "0.0.0.0" {
   425  			continue
   426  		}
   427  		// When multiple NICs are connected to the same network, choose
   428  		// one that has a route back. This Dial should ensure that.
   429  		conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", record["IPAddress"], port), 2*time.Second)
   430  		if err != nil {
   431  			if e, ok := err.(*net.OpError); ok {
   432  				if e.Timeout() {
   433  					log.Printf("Timeout connecting to %s", record["IPAddress"])
   434  					continue
   435  				} else if strings.Contains(e.Error(), "connection refused") {
   436  					log.Printf("Connection refused when connecting to: %s", record["IPAddress"])
   437  					continue
   438  				}
   439  			}
   440  		} else {
   441  			defer conn.Close()
   442  			address := record["IPAddress"]
   443  			return address, nil
   444  		}
   445  	}
   446  	return "", errors.New("No interface on the VM has an IP address ready")
   447  }
   448  
   449  //-------------------------------------------------------------------
   450  // OutputDir implementation
   451  //-------------------------------------------------------------------
   452  
   453  func (d *ESX5Driver) DirExists() (bool, error) {
   454  	err := d.sh("test", "-e", strconv.Quote(d.outputDir))
   455  	return err == nil, nil
   456  }
   457  
   458  func (d *ESX5Driver) ListFiles() ([]string, error) {
   459  	stdout, err := d.ssh("ls -1p "+d.outputDir, nil)
   460  	if err != nil {
   461  		return nil, err
   462  	}
   463  
   464  	files := make([]string, 0, 10)
   465  	reader := bufio.NewReader(stdout)
   466  	for {
   467  		line, _, err := reader.ReadLine()
   468  		if err == io.EOF {
   469  			break
   470  		}
   471  		if line[len(line)-1] == '/' {
   472  			continue
   473  		}
   474  
   475  		files = append(files, filepath.ToSlash(filepath.Join(d.outputDir, string(line))))
   476  	}
   477  
   478  	return files, nil
   479  }
   480  
   481  func (d *ESX5Driver) MkdirAll() error {
   482  	return d.mkdir(d.outputDir)
   483  }
   484  
   485  func (d *ESX5Driver) Remove(path string) error {
   486  	return d.sh("rm", strconv.Quote(path))
   487  }
   488  
   489  func (d *ESX5Driver) RemoveAll() error {
   490  	return d.sh("rm", "-rf", strconv.Quote(d.outputDir))
   491  }
   492  
   493  func (d *ESX5Driver) SetOutputDir(path string) {
   494  	d.outputDir = d.datastorePath(path)
   495  }
   496  
   497  func (d *ESX5Driver) String() string {
   498  	return d.outputDir
   499  }
   500  
   501  func (d *ESX5Driver) datastorePath(path string) string {
   502  	dirPath := filepath.Dir(path)
   503  	return filepath.ToSlash(filepath.Join("/vmfs/volumes", d.Datastore, dirPath, filepath.Base(path)))
   504  }
   505  
   506  func (d *ESX5Driver) cachePath(path string) string {
   507  	return filepath.ToSlash(filepath.Join("/vmfs/volumes", d.CacheDatastore, d.CacheDirectory, filepath.Base(path)))
   508  }
   509  
   510  func (d *ESX5Driver) connect() error {
   511  	address := fmt.Sprintf("%s:%d", d.Host, d.Port)
   512  
   513  	auth := []gossh.AuthMethod{
   514  		gossh.Password(d.Password),
   515  		gossh.KeyboardInteractive(
   516  			ssh.PasswordKeyboardInteractive(d.Password)),
   517  	}
   518  
   519  	if d.PrivateKeyFile != "" {
   520  		signer, err := helperssh.FileSigner(d.PrivateKeyFile)
   521  		if err != nil {
   522  			return err
   523  		}
   524  
   525  		auth = append(auth, gossh.PublicKeys(signer))
   526  	}
   527  
   528  	sshConfig := &ssh.Config{
   529  		Connection: ssh.ConnectFunc("tcp", address),
   530  		SSHConfig: &gossh.ClientConfig{
   531  			User:            d.Username,
   532  			Auth:            auth,
   533  			HostKeyCallback: gossh.InsecureIgnoreHostKey(),
   534  		},
   535  	}
   536  
   537  	comm, err := ssh.New(address, sshConfig)
   538  	if err != nil {
   539  		return err
   540  	}
   541  
   542  	d.comm = comm
   543  	return nil
   544  }
   545  
   546  func (d *ESX5Driver) checkSystemVersion() error {
   547  	r, err := d.esxcli("system", "version", "get")
   548  	if err != nil {
   549  		return err
   550  	}
   551  
   552  	record, err := r.read()
   553  	if err != nil {
   554  		return err
   555  	}
   556  
   557  	log.Printf("Connected to %s %s %s", record["Product"],
   558  		record["Version"], record["Build"])
   559  	return nil
   560  }
   561  
   562  func (d *ESX5Driver) checkGuestIPHackEnabled() error {
   563  	r, err := d.esxcli("system", "settings", "advanced", "list", "-o", "/Net/GuestIPHack")
   564  	if err != nil {
   565  		return err
   566  	}
   567  
   568  	record, err := r.read()
   569  	if err != nil {
   570  		return err
   571  	}
   572  
   573  	if record["IntValue"] != "1" {
   574  		return errors.New(
   575  			"GuestIPHack is required, enable by running this on the ESX machine:\n" +
   576  				"esxcli system settings advanced set -o /Net/GuestIPHack -i 1")
   577  	}
   578  
   579  	return nil
   580  }
   581  
   582  func (d *ESX5Driver) mkdir(path string) error {
   583  	return d.sh("mkdir", "-p", strconv.Quote(path))
   584  }
   585  
   586  func (d *ESX5Driver) upload(dst, src string) error {
   587  	f, err := os.Open(src)
   588  	if err != nil {
   589  		return err
   590  	}
   591  	defer f.Close()
   592  	return d.comm.Upload(dst, f, nil)
   593  }
   594  
   595  func (d *ESX5Driver) verifyChecksum(ctype string, hash string, file string) bool {
   596  	if ctype == "none" {
   597  		if err := d.sh("stat", strconv.Quote(file)); err != nil {
   598  			return false
   599  		}
   600  	} else {
   601  		stdin := bytes.NewBufferString(fmt.Sprintf("%s  %s", hash, file))
   602  		_, err := d.run(stdin, fmt.Sprintf("%ssum", ctype), "-c")
   603  		if err != nil {
   604  			return false
   605  		}
   606  	}
   607  
   608  	return true
   609  }
   610  
   611  func (d *ESX5Driver) ssh(command string, stdin io.Reader) (*bytes.Buffer, error) {
   612  	var stdout, stderr bytes.Buffer
   613  
   614  	cmd := &packer.RemoteCmd{
   615  		Command: command,
   616  		Stdout:  &stdout,
   617  		Stderr:  &stderr,
   618  		Stdin:   stdin,
   619  	}
   620  
   621  	err := d.comm.Start(cmd)
   622  	if err != nil {
   623  		return nil, err
   624  	}
   625  
   626  	cmd.Wait()
   627  
   628  	if cmd.ExitStatus != 0 {
   629  		err = fmt.Errorf("'%s'\n\nStdout: %s\n\nStderr: %s",
   630  			cmd.Command, stdout.String(), stderr.String())
   631  		return nil, err
   632  	}
   633  
   634  	return &stdout, nil
   635  }
   636  
   637  func (d *ESX5Driver) run(stdin io.Reader, args ...string) (string, error) {
   638  	stdout, err := d.ssh(strings.Join(args, " "), stdin)
   639  	if err != nil {
   640  		return "", err
   641  	}
   642  	return stdout.String(), nil
   643  }
   644  
   645  func (d *ESX5Driver) sh(args ...string) error {
   646  	_, err := d.run(nil, args...)
   647  	return err
   648  }
   649  
   650  func (d *ESX5Driver) esxcli(args ...string) (*esxcliReader, error) {
   651  	stdout, err := d.ssh("esxcli --formatter csv "+strings.Join(args, " "), nil)
   652  	if err != nil {
   653  		return nil, err
   654  	}
   655  	r := csv.NewReader(bytes.NewReader(stdout.Bytes()))
   656  	r.TrailingComma = true
   657  	header, err := r.Read()
   658  	if err != nil {
   659  		return nil, err
   660  	}
   661  	return &esxcliReader{r, header}, nil
   662  }
   663  
   664  func (d *ESX5Driver) GetVmwareDriver() vmwcommon.VmwareDriver {
   665  	return d.base
   666  }
   667  
   668  type esxcliReader struct {
   669  	cr     *csv.Reader
   670  	header []string
   671  }
   672  
   673  func (r *esxcliReader) read() (map[string]string, error) {
   674  	fields, err := r.cr.Read()
   675  
   676  	if err != nil {
   677  		return nil, err
   678  	}
   679  
   680  	record := map[string]string{}
   681  	for i, v := range fields {
   682  		record[r.header[i]] = v
   683  	}
   684  
   685  	return record, nil
   686  }
   687  
   688  func (r *esxcliReader) find(key, val string) (map[string]string, error) {
   689  	for {
   690  		record, err := r.read()
   691  		if err != nil {
   692  			return nil, err
   693  		}
   694  		if record[key] == val {
   695  			return record, nil
   696  		}
   697  	}
   698  }