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