github.com/sneal/packer@v0.5.2/builder/vmware/iso/driver_esx5.go (about)

     1  package iso
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	gossh "code.google.com/p/go.crypto/ssh"
     7  	"encoding/csv"
     8  	"errors"
     9  	"fmt"
    10  	"github.com/mitchellh/multistep"
    11  	"github.com/mitchellh/packer/communicator/ssh"
    12  	"github.com/mitchellh/packer/packer"
    13  	"io"
    14  	"log"
    15  	"net"
    16  	"os"
    17  	"path/filepath"
    18  	"strings"
    19  	"syscall"
    20  	"time"
    21  )
    22  
    23  // ESX5 driver talks to an ESXi5 hypervisor remotely over SSH to build
    24  // virtual machines. This driver can only manage one machine at a time.
    25  type ESX5Driver struct {
    26  	Host      string
    27  	Port      uint
    28  	Username  string
    29  	Password  string
    30  	Datastore string
    31  
    32  	comm      packer.Communicator
    33  	outputDir string
    34  }
    35  
    36  func (d *ESX5Driver) Clone(dst, src string) error {
    37  	return errors.New("Cloning is not supported with the ESX driver.")
    38  }
    39  
    40  func (d *ESX5Driver) CompactDisk(diskPathLocal string) error {
    41  	return nil
    42  }
    43  
    44  func (d *ESX5Driver) CreateDisk(diskPathLocal string, size string, typeId string) error {
    45  	diskPath := d.datastorePath(diskPathLocal)
    46  	return d.sh("vmkfstools", "-c", size, "-d", typeId, "-a", "lsilogic", diskPath)
    47  }
    48  
    49  func (d *ESX5Driver) IsRunning(vmxPathLocal string) (bool, error) {
    50  	vmxPath := filepath.Join(d.outputDir, filepath.Base(vmxPathLocal))
    51  	state, err := d.run(nil, "vim-cmd", "vmsvc/power.getstate", vmxPath)
    52  	if err != nil {
    53  		return false, err
    54  	}
    55  	return strings.Contains(state, "Powered on"), nil
    56  }
    57  
    58  func (d *ESX5Driver) Start(vmxPathLocal string, headless bool) error {
    59  	vmxPath := filepath.Join(d.outputDir, filepath.Base(vmxPathLocal))
    60  	return d.sh("vim-cmd", "vmsvc/power.on", vmxPath)
    61  }
    62  
    63  func (d *ESX5Driver) Stop(vmxPathLocal string) error {
    64  	vmxPath := filepath.Join(d.outputDir, filepath.Base(vmxPathLocal))
    65  	return d.sh("vim-cmd", "vmsvc/power.off", vmxPath)
    66  }
    67  
    68  func (d *ESX5Driver) Register(vmxPathLocal string) error {
    69  	vmxPath := filepath.Join(d.outputDir, filepath.Base(vmxPathLocal))
    70  	if err := d.upload(vmxPath, vmxPathLocal); err != nil {
    71  		return err
    72  	}
    73  	return d.sh("vim-cmd", "solo/registervm", vmxPath)
    74  }
    75  
    76  func (d *ESX5Driver) SuppressMessages(vmxPath string) error {
    77  	return nil
    78  }
    79  
    80  func (d *ESX5Driver) Unregister(vmxPathLocal string) error {
    81  	vmxPath := filepath.Join(d.outputDir, filepath.Base(vmxPathLocal))
    82  	return d.sh("vim-cmd", "vmsvc/unregister", vmxPath)
    83  }
    84  
    85  func (d *ESX5Driver) UploadISO(localPath string) (string, error) {
    86  	cacheRoot, _ := filepath.Abs(".")
    87  	targetFile, err := filepath.Rel(cacheRoot, localPath)
    88  	if err != nil {
    89  		return "", err
    90  	}
    91  
    92  	finalPath := d.datastorePath(targetFile)
    93  	if err := d.mkdir(filepath.Dir(finalPath)); err != nil {
    94  		return "", err
    95  	}
    96  
    97  	if err := d.upload(finalPath, localPath); err != nil {
    98  		return "", err
    99  	}
   100  
   101  	return finalPath, nil
   102  }
   103  
   104  func (d *ESX5Driver) ToolsIsoPath(string) string {
   105  	return ""
   106  }
   107  
   108  func (d *ESX5Driver) DhcpLeasesPath(string) string {
   109  	return ""
   110  }
   111  
   112  func (d *ESX5Driver) Verify() error {
   113  	checks := []func() error{
   114  		d.connect,
   115  		d.checkSystemVersion,
   116  		d.checkGuestIPHackEnabled,
   117  	}
   118  
   119  	for _, check := range checks {
   120  		if err := check(); err != nil {
   121  			return err
   122  		}
   123  	}
   124  
   125  	return nil
   126  }
   127  
   128  func (d *ESX5Driver) HostIP() (string, error) {
   129  	conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", d.Host, d.Port))
   130  	defer conn.Close()
   131  	if err != nil {
   132  		return "", err
   133  	}
   134  
   135  	host, _, err := net.SplitHostPort(conn.LocalAddr().String())
   136  	return host, err
   137  }
   138  
   139  func (d *ESX5Driver) VNCAddress(portMin, portMax uint) (string, uint) {
   140  	var vncPort uint
   141  	// TODO(dougm) use esxcli network ip connection list
   142  	for port := portMin; port <= portMax; port++ {
   143  		address := fmt.Sprintf("%s:%d", d.Host, port)
   144  		log.Printf("Trying address: %s...", address)
   145  		l, err := net.DialTimeout("tcp", address, 1*time.Second)
   146  
   147  		if err == nil {
   148  			log.Printf("%s in use", address)
   149  			l.Close()
   150  		} else if e, ok := err.(*net.OpError); ok {
   151  			if e.Err == syscall.ECONNREFUSED {
   152  				// then port should be available for listening
   153  				vncPort = port
   154  				break
   155  			} else if e.Timeout() {
   156  				log.Printf("Timeout connecting to: %s (check firewall rules)", address)
   157  			}
   158  		}
   159  	}
   160  
   161  	return d.Host, vncPort
   162  }
   163  
   164  func (d *ESX5Driver) SSHAddress(state multistep.StateBag) (string, error) {
   165  	config := state.Get("config").(*config)
   166  
   167  	if address, ok := state.GetOk("vm_address"); ok {
   168  		return address.(string), nil
   169  	}
   170  
   171  	r, err := d.esxcli("network", "vm", "list")
   172  	if err != nil {
   173  		return "", err
   174  	}
   175  
   176  	record, err := r.find("Name", config.VMName)
   177  	if err != nil {
   178  		return "", err
   179  	}
   180  	wid := record["WorldID"]
   181  	if wid == "" {
   182  		return "", errors.New("VM WorldID not found")
   183  	}
   184  
   185  	r, err = d.esxcli("network", "vm", "port", "list", "-w", wid)
   186  	if err != nil {
   187  		return "", err
   188  	}
   189  
   190  	record, err = r.read()
   191  	if err != nil {
   192  		return "", err
   193  	}
   194  
   195  	if record["IPAddress"] == "0.0.0.0" {
   196  		return "", errors.New("VM network port found, but no IP address")
   197  	}
   198  
   199  	address := fmt.Sprintf("%s:%d", record["IPAddress"], config.SSHPort)
   200  	state.Put("vm_address", address)
   201  	return address, nil
   202  }
   203  
   204  //-------------------------------------------------------------------
   205  // OutputDir implementation
   206  //-------------------------------------------------------------------
   207  
   208  func (d *ESX5Driver) DirExists() (bool, error) {
   209  	err := d.sh("test", "-e", d.outputDir)
   210  	return err == nil, nil
   211  }
   212  
   213  func (d *ESX5Driver) ListFiles() ([]string, error) {
   214  	stdout, err := d.ssh("ls -1p "+d.outputDir, nil)
   215  	if err != nil {
   216  		return nil, err
   217  	}
   218  
   219  	files := make([]string, 0, 10)
   220  	reader := bufio.NewReader(stdout)
   221  	for {
   222  		line, _, err := reader.ReadLine()
   223  		if err == io.EOF {
   224  			break
   225  		}
   226  		if line[len(line)-1] == '/' {
   227  			continue
   228  		}
   229  
   230  		files = append(files, filepath.Join(d.outputDir, string(line)))
   231  	}
   232  
   233  	return files, nil
   234  }
   235  
   236  func (d *ESX5Driver) MkdirAll() error {
   237  	return d.mkdir(d.outputDir)
   238  }
   239  
   240  func (d *ESX5Driver) Remove(path string) error {
   241  	return d.sh("rm", path)
   242  }
   243  
   244  func (d *ESX5Driver) RemoveAll() error {
   245  	return d.sh("rm", "-rf", d.outputDir)
   246  }
   247  
   248  func (d *ESX5Driver) SetOutputDir(path string) {
   249  	d.outputDir = d.datastorePath(path)
   250  }
   251  
   252  func (d *ESX5Driver) String() string {
   253  	return d.outputDir
   254  }
   255  
   256  func (d *ESX5Driver) datastorePath(path string) string {
   257  	return filepath.Join("/vmfs/volumes", d.Datastore, path)
   258  }
   259  
   260  func (d *ESX5Driver) connect() error {
   261  	address := fmt.Sprintf("%s:%d", d.Host, d.Port)
   262  
   263  	auth := []gossh.ClientAuth{
   264  		gossh.ClientAuthPassword(ssh.Password(d.Password)),
   265  		gossh.ClientAuthKeyboardInteractive(
   266  			ssh.PasswordKeyboardInteractive(d.Password)),
   267  	}
   268  
   269  	// TODO(dougm) KeyPath support
   270  	sshConfig := &ssh.Config{
   271  		Connection: ssh.ConnectFunc("tcp", address),
   272  		SSHConfig: &gossh.ClientConfig{
   273  			User: d.Username,
   274  			Auth: auth,
   275  		},
   276  		NoPty: true,
   277  	}
   278  
   279  	comm, err := ssh.New(sshConfig)
   280  	if err != nil {
   281  		return err
   282  	}
   283  
   284  	d.comm = comm
   285  	return nil
   286  }
   287  
   288  func (d *ESX5Driver) checkSystemVersion() error {
   289  	r, err := d.esxcli("system", "version", "get")
   290  	if err != nil {
   291  		return err
   292  	}
   293  
   294  	record, err := r.read()
   295  	if err != nil {
   296  		return err
   297  	}
   298  
   299  	log.Printf("Connected to %s %s %s", record["Product"],
   300  		record["Version"], record["Build"])
   301  	return nil
   302  }
   303  
   304  func (d *ESX5Driver) checkGuestIPHackEnabled() error {
   305  	r, err := d.esxcli("system", "settings", "advanced", "list", "-o", "/Net/GuestIPHack")
   306  	if err != nil {
   307  		return err
   308  	}
   309  
   310  	record, err := r.read()
   311  	if err != nil {
   312  		return err
   313  	}
   314  
   315  	if record["IntValue"] != "1" {
   316  		return errors.New(
   317  			"GuestIPHack is required, enable by running this on the ESX machine:\n" +
   318  				"esxcli system settings advanced set -o /Net/GuestIPHack -i 1")
   319  	}
   320  
   321  	return nil
   322  }
   323  
   324  func (d *ESX5Driver) mkdir(path string) error {
   325  	return d.sh("mkdir", "-p", path)
   326  }
   327  
   328  func (d *ESX5Driver) upload(dst, src string) error {
   329  	f, err := os.Open(src)
   330  	if err != nil {
   331  		return err
   332  	}
   333  	defer f.Close()
   334  	return d.comm.Upload(dst, f)
   335  }
   336  
   337  func (d *ESX5Driver) verifyChecksum(ctype string, hash string, file string) bool {
   338  	stdin := bytes.NewBufferString(fmt.Sprintf("%s  %s", hash, file))
   339  	_, err := d.run(stdin, fmt.Sprintf("%ssum", ctype), "-c")
   340  	if err != nil {
   341  		return false
   342  	}
   343  	return true
   344  }
   345  
   346  func (d *ESX5Driver) ssh(command string, stdin io.Reader) (*bytes.Buffer, error) {
   347  	var stdout, stderr bytes.Buffer
   348  
   349  	cmd := &packer.RemoteCmd{
   350  		Command: command,
   351  		Stdout:  &stdout,
   352  		Stderr:  &stderr,
   353  		Stdin:   stdin,
   354  	}
   355  
   356  	err := d.comm.Start(cmd)
   357  	if err != nil {
   358  		return nil, err
   359  	}
   360  
   361  	cmd.Wait()
   362  
   363  	if cmd.ExitStatus != 0 {
   364  		err = fmt.Errorf("'%s'\n\nStdout: %s\n\nStderr: %s",
   365  			cmd.Command, stdout.String(), stderr.String())
   366  		return nil, err
   367  	}
   368  
   369  	return &stdout, nil
   370  }
   371  
   372  func (d *ESX5Driver) run(stdin io.Reader, args ...string) (string, error) {
   373  	stdout, err := d.ssh(strings.Join(args, " "), stdin)
   374  	if err != nil {
   375  		return "", err
   376  	}
   377  	return stdout.String(), nil
   378  }
   379  
   380  func (d *ESX5Driver) sh(args ...string) error {
   381  	_, err := d.run(nil, args...)
   382  	return err
   383  }
   384  
   385  func (d *ESX5Driver) esxcli(args ...string) (*esxcliReader, error) {
   386  	stdout, err := d.ssh("esxcli --formatter csv "+strings.Join(args, " "), nil)
   387  	if err != nil {
   388  		return nil, err
   389  	}
   390  	r := csv.NewReader(bytes.NewReader(stdout.Bytes()))
   391  	r.TrailingComma = true
   392  	header, err := r.Read()
   393  	if err != nil {
   394  		return nil, err
   395  	}
   396  	return &esxcliReader{r, header}, nil
   397  }
   398  
   399  type esxcliReader struct {
   400  	cr     *csv.Reader
   401  	header []string
   402  }
   403  
   404  func (r *esxcliReader) read() (map[string]string, error) {
   405  	fields, err := r.cr.Read()
   406  
   407  	if err != nil {
   408  		return nil, err
   409  	}
   410  
   411  	record := map[string]string{}
   412  	for i, v := range fields {
   413  		record[r.header[i]] = v
   414  	}
   415  
   416  	return record, nil
   417  }
   418  
   419  func (r *esxcliReader) find(key, val string) (map[string]string, error) {
   420  	for {
   421  		record, err := r.read()
   422  		if err != nil {
   423  			return nil, err
   424  		}
   425  		if record[key] == val {
   426  			return record, nil
   427  		}
   428  	}
   429  }