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 }