From 62208228f88fd899ad134ec21637e8364ae7aecd Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Wed, 15 Oct 2014 09:59:25 -0600 Subject: [PATCH 01/20] Update default ISO path to prefer an ISO that's next to the boot2docker CLI binary This is especially important for places like the Windows installer, where we want people to be able to install boot2docker 1.3 from an installer, and it includes the right ISO, so when they boot their VM, it should also be boot2docker 1.3, without any extra work or extra downloading. --- config.go | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/config.go b/config.go index 94cbe9d..8ec7363 100644 --- a/config.go +++ b/config.go @@ -5,6 +5,7 @@ import ( "fmt" "net" "os" + "os/exec" "path/filepath" "regexp" "runtime" @@ -61,6 +62,36 @@ func cfgFilename(dir string) string { return filename } +func defaultIsoPath(dir string) string { + var err error + + iso := filepath.Join(dir, "boot2docker.iso") + + exe := os.Args[0] + if filepath.Base(exe) == exe { + // this logic borrowed from reexec/reexec.go in Docker itself :) + if lp, err := exec.LookPath(exe); err == nil { + exe = lp + } + } + + if exe, err = filepath.Abs(exe); err != nil { + return iso + } + + if exe, err = filepath.EvalSymlinks(exe); err != nil { + return iso + } + + // if there's a "boot2docker.iso" next to our boot2docker-cli executable, let's prefer that one by default + exeIso := filepath.Join(filepath.Dir(exe), "boot2docker.iso") + if _, err = os.Stat(exeIso); err == nil { + return exeIso + } + + return iso +} + // Write configuration set by the combination of profile and flags // Should result in a format that can be piped into a profile file func printConfig() string { @@ -92,7 +123,7 @@ func config() (*flag.FlagSet, error) { //flags.StringVarP(&B2D.Dir, "dir", "d", dir, "boot2docker config directory.") B2D.Dir = dir flags.StringVar(&B2D.ISOURL, "iso-url", "https://api.github.com/repos/boot2docker/boot2docker/releases", "source URL to provision the boot2docker ISO image.") - flags.StringVar(&B2D.ISO, "iso", filepath.Join(dir, "boot2docker.iso"), "path to boot2docker ISO image.") + flags.StringVar(&B2D.ISO, "iso", defaultIsoPath(dir), "path to boot2docker ISO image.") // Sven disabled this, as it is broken - if I user with a fresh computer downloads // just the boot2docker-cli, and then runs `boot2docker --init ip`, we create a vm From 71c52c6a3ff512760fb559625e51011a66885747 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Wed, 15 Oct 2014 23:51:09 -0400 Subject: [PATCH 02/20] Report the iso path used by the vm to help debug possible support issues --- cmds.go | 6 +++++- virtualbox/machine.go | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/cmds.go b/cmds.go index c13312b..3ea5875 100644 --- a/cmds.go +++ b/cmds.go @@ -329,9 +329,13 @@ func cmdInfo() error { if err != nil { return fmt.Errorf("Failed to get machine %q: %s", B2D.VM, err) } - if err := json.NewEncoder(os.Stdout).Encode(m); err != nil { + b, err := json.MarshalIndent(m, "", "\t") + if err != nil { return fmt.Errorf("Failed to encode machine %q info: %s", B2D.VM, err) } + + os.Stdout.Write(b) + return nil } diff --git a/virtualbox/machine.go b/virtualbox/machine.go index 2009d01..5b30295 100644 --- a/virtualbox/machine.go +++ b/virtualbox/machine.go @@ -161,6 +161,7 @@ func (f Flag) Get(o Flag) string { type Machine struct { Name string UUID string + Iso string State driver.MachineState CPUs uint Memory uint // main memory (in MB) @@ -355,6 +356,8 @@ func GetMachine(id string) (*Machine, error) { m.Name = val case "UUID": m.UUID = val + case "SATA-0-0": + m.Iso = val case "VMState": m.State = driver.MachineState(val) case "memory": From a8ca6ecf73d0dd309d30083d62b0b25c44b2ba76 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 16 Oct 2014 04:23:27 -0600 Subject: [PATCH 03/20] Revert "Update default ISO path to prefer an ISO that's next to the boot2docker CLI binary" --- config.go | 33 +-------------------------------- 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/config.go b/config.go index a06851d..48436bb 100644 --- a/config.go +++ b/config.go @@ -5,7 +5,6 @@ import ( "fmt" "net" "os" - "os/exec" "path/filepath" "regexp" "runtime" @@ -62,36 +61,6 @@ func cfgFilename(dir string) string { return filename } -func defaultIsoPath(dir string) string { - var err error - - iso := filepath.Join(dir, "boot2docker.iso") - - exe := os.Args[0] - if filepath.Base(exe) == exe { - // this logic borrowed from reexec/reexec.go in Docker itself :) - if lp, err := exec.LookPath(exe); err == nil { - exe = lp - } - } - - if exe, err = filepath.Abs(exe); err != nil { - return iso - } - - if exe, err = filepath.EvalSymlinks(exe); err != nil { - return iso - } - - // if there's a "boot2docker.iso" next to our boot2docker-cli executable, let's prefer that one by default - exeIso := filepath.Join(filepath.Dir(exe), "boot2docker.iso") - if _, err = os.Stat(exeIso); err == nil { - return exeIso - } - - return iso -} - // Write configuration set by the combination of profile and flags // Should result in a format that can be piped into a profile file func printConfig() string { @@ -123,7 +92,7 @@ func config() (*flag.FlagSet, error) { //flags.StringVarP(&B2D.Dir, "dir", "d", dir, "boot2docker config directory.") B2D.Dir = dir flags.StringVar(&B2D.ISOURL, "iso-url", "https://api.github.com/repos/boot2docker/boot2docker/releases", "source URL to provision the boot2docker ISO image.") - flags.StringVar(&B2D.ISO, "iso", defaultIsoPath(dir), "path to boot2docker ISO image.") + flags.StringVar(&B2D.ISO, "iso", filepath.Join(dir, "boot2docker.iso"), "path to boot2docker ISO image.") // Sven disabled this, as it is broken - if I user with a fresh computer downloads // just the boot2docker-cli, and then runs `boot2docker --init ip`, we create a vm From 4a1bd39cda5de28c93fc96cff8af5af1818759a5 Mon Sep 17 00:00:00 2001 From: lalyos Date: Thu, 16 Oct 2014 16:45:17 +0200 Subject: [PATCH 04/20] Fixing name collision of driver.ErrMachineNotExist and virtualbox.ErrMachineNotExist --- virtualbox/machine.go | 2 +- virtualbox/vbm.go | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/virtualbox/machine.go b/virtualbox/machine.go index 2009d01..f61646c 100644 --- a/virtualbox/machine.go +++ b/virtualbox/machine.go @@ -330,7 +330,7 @@ func GetMachine(id string) (*Machine, error) { stdout, stderr, err := vbmOutErr("showvminfo", id, "--machinereadable") if err != nil { if reMachineNotFound.FindString(stderr) != "" { - return nil, ErrMachineNotExist + return nil, driver.ErrMachineNotExist } return nil, err } diff --git a/virtualbox/vbm.go b/virtualbox/vbm.go index 641c071..bc36d91 100644 --- a/virtualbox/vbm.go +++ b/virtualbox/vbm.go @@ -25,9 +25,8 @@ var ( ) var ( - ErrMachineExist = errors.New("machine already exists") - ErrMachineNotExist = errors.New("machine does not exist") - ErrVBMNotFound = errors.New("VBoxManage not found") + ErrMachineExist = errors.New("machine already exists") + ErrVBMNotFound = errors.New("VBoxManage not found") ) func vbm(args ...string) error { From 78bc9cc7ba7a39d09ab6360d51f674331b84f220 Mon Sep 17 00:00:00 2001 From: lalyos Date: Thu, 16 Oct 2014 16:49:07 +0200 Subject: [PATCH 05/20] Moving ErrMachineExist to driver package "machine already exists" is not virtualbox specific --- driver/driver.go | 1 + virtualbox/machine.go | 2 +- virtualbox/vbm.go | 3 +-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/driver/driver.go b/driver/driver.go index 9b5524a..2fbb8ab 100644 --- a/driver/driver.go +++ b/driver/driver.go @@ -53,6 +53,7 @@ var ( ErrNotSupported = errors.New("machine not supported") ErrMachineNotExist = errors.New("machine not exist") + ErrMachineExist = errors.New("machine already exists") ErrPrerequisites = errors.New("prerequisites for machine not satisfied (hypervisor installed?)") ) diff --git a/virtualbox/machine.go b/virtualbox/machine.go index f61646c..54d3059 100644 --- a/virtualbox/machine.go +++ b/virtualbox/machine.go @@ -443,7 +443,7 @@ func CreateMachine(mc *driver.MachineConfig) (*Machine, error) { } for _, m := range machineNames { if m == mc.VM { - return nil, ErrMachineExist + return nil, driver.ErrMachineExist } } diff --git a/virtualbox/vbm.go b/virtualbox/vbm.go index bc36d91..a4f534f 100644 --- a/virtualbox/vbm.go +++ b/virtualbox/vbm.go @@ -25,8 +25,7 @@ var ( ) var ( - ErrMachineExist = errors.New("machine already exists") - ErrVBMNotFound = errors.New("VBoxManage not found") + ErrVBMNotFound = errors.New("VBoxManage not found") ) func vbm(args ...string) error { From 013d5ccf8b07ba2d4b634308206ac9173c324d55 Mon Sep 17 00:00:00 2001 From: lalyos Date: Thu, 16 Oct 2014 16:54:25 +0200 Subject: [PATCH 06/20] Fixing error message when no suitable driver found Right now The error message is misleading in case of a non existing driver: "boot2docker-vm": machine not supported Its not the machine which is not supported, but the driver $ boot2docker --driver=nosuchdriver info Boot2Docker-cli version: v1.3.0 Git commit: 1d0b71b error in run: Failed to get machine "boot2docker-vm": machine not supported --- driver/driver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver/driver.go b/driver/driver.go index 2fbb8ab..525ee9b 100644 --- a/driver/driver.go +++ b/driver/driver.go @@ -51,7 +51,7 @@ var ( // All registred machines machines map[string]InitFunc - ErrNotSupported = errors.New("machine not supported") + ErrNotSupported = errors.New("driver not supported") ErrMachineNotExist = errors.New("machine not exist") ErrMachineExist = errors.New("machine already exists") ErrPrerequisites = errors.New("prerequisites for machine not satisfied (hypervisor installed?)") From 5d389e875766fb760badf9a9c41f9e58cea697b4 Mon Sep 17 00:00:00 2001 From: lalyos Date: Thu, 16 Oct 2014 21:20:49 +0200 Subject: [PATCH 07/20] Starting from saved state skips setUpShares Fixes #292 --- virtualbox/machine.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/virtualbox/machine.go b/virtualbox/machine.go index 5b30295..c6c4ed9 100644 --- a/virtualbox/machine.go +++ b/virtualbox/machine.go @@ -195,11 +195,14 @@ func (m *Machine) Start() error { switch m.State { case driver.Paused: return vbm("controlvm", m.Name, "resume") - case driver.Poweroff, driver.Saved, driver.Aborted: + case driver.Poweroff, driver.Aborted: if err := m.setUpShares(); err != nil { return err } + fallthrough + case driver.Saved: return vbm("startvm", m.Name, "--type", "headless") + } if err := m.Refresh(); err == nil { if m.State != driver.Running { From 1a3d05e8e7c7354f5499a68e3e9a35d910888164 Mon Sep 17 00:00:00 2001 From: Douglas Camata Date: Fri, 17 Oct 2014 15:04:10 -0300 Subject: [PATCH 08/20] printExport support for fish-shell (based on boot2docker/boot2docker-cli#260) --- cmds.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/cmds.go b/cmds.go index 3ea5875..14c60c3 100644 --- a/cmds.go +++ b/cmds.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "os/exec" + "path/filepath" "regexp" "runtime" "strings" @@ -195,9 +196,17 @@ func printExport(socket, certPath string) { for name, value := range exports(socket, certPath) { if os.Getenv(name) != value { if value == "" { - fmt.Printf(" unset %s\n", name) + if filepath.Base(os.Getenv("SHELL")) == "fish" { + fmt.Printf(" set -e %s\n", name) + } else { + fmt.Printf(" unset %s\n", name) + } } else { - fmt.Printf(" export %s=%s\n", name, value) + if filepath.Base(os.Getenv("SHELL")) == "fish" { + fmt.Printf(" set x %s\n", name) + } else { + fmt.Printf(" export %s=%s\n", name, value) + } } } } From ce2bc314e2ad49e46e422fdf8ef66d5b4275f461 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 23 Oct 2014 10:16:05 -0600 Subject: [PATCH 09/20] Make "socket" an alias of "shellinit" and hide it from usage --- cmds.go | 23 ----------------------- config.go | 5 ++--- main.go | 4 +--- 3 files changed, 3 insertions(+), 29 deletions(-) diff --git a/cmds.go b/cmds.go index 3ea5875..03f9ed0 100644 --- a/cmds.go +++ b/cmds.go @@ -349,29 +349,6 @@ func cmdStatus() error { return nil } -// tell the User the Docker socket to connect to -func cmdSocket() error { - m, err := driver.GetMachine(&B2D) - if err != nil { - return fmt.Errorf("Failed to get machine %q: %s", B2D.VM, err) - } - - if m.GetState() != driver.Running { - return fmt.Errorf("VM %q is not running.", B2D.VM) - } - - socket, err := RequestSocketFromSSH(m) - if err != nil { - return fmt.Errorf("Error requesting socket: %s\n", err) - } - - fmt.Fprintf(os.Stderr, "\n\t export DOCKER_HOST=") - fmt.Printf("%s", socket) - fmt.Fprintf(os.Stderr, "\n\n") - - return nil -} - // Call the external SSH command to login into boot2docker VM. func cmdSSH() error { m, err := driver.GetMachine(&B2D) diff --git a/config.go b/config.go index 48436bb..b9b96a8 100644 --- a/config.go +++ b/config.go @@ -171,7 +171,7 @@ func config() (*flag.FlagSet, error) { } func usageShort() { - fmt.Fprintf(os.Stderr, "Usage: %s [] {help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|ip|socket|shellinit|delete|download|upgrade|version} []\n", os.Args[0]) + fmt.Fprintf(os.Stderr, "Usage: %s [] {help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|ip|shellinit|delete|download|upgrade|version} []\n", os.Args[0]) } func usageLong(flags *flag.FlagSet) { @@ -193,8 +193,7 @@ Commands: config|cfg Show selected profile file settings. info Display detailed information of VM. ip Display the IP address of the VM's Host-only network. - socket Display the DOCKER_HOST socket to connect to. - shellinit Display the shell command to set up the Docker client. + shellinit Display the shell commands to set up the Docker client. status Display current state of VM. download Download Boot2Docker ISO image. upgrade Upgrade the Boot2Docker ISO image (restart if running). diff --git a/main.go b/main.go index d062da2..a4f90dd 100644 --- a/main.go +++ b/main.go @@ -65,9 +65,7 @@ func run() error { return cmdDelete() case "info": return cmdInfo() - case "socket": - return cmdSocket() - case "shellinit": + case "shellinit", "socket": return cmdShellInit() case "status": return cmdStatus() From 66eb47103445eb26f99690f7d8b778c07172bbc5 Mon Sep 17 00:00:00 2001 From: Douglas Camata Date: Thu, 23 Oct 2014 14:32:35 -0200 Subject: [PATCH 10/20] printExport always showing the export/unset commands --- cmds.go | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/cmds.go b/cmds.go index 14c60c3..b1d4608 100644 --- a/cmds.go +++ b/cmds.go @@ -193,20 +193,21 @@ func checkEnvironment(socket, certPath string) bool { } func printExport(socket, certPath string) { + shell = filepath.Base(os.Getenv("SHELL")) for name, value := range exports(socket, certPath) { - if os.Getenv(name) != value { - if value == "" { - if filepath.Base(os.Getenv("SHELL")) == "fish" { - fmt.Printf(" set -e %s\n", name) - } else { - fmt.Printf(" unset %s\n", name) - } - } else { - if filepath.Base(os.Getenv("SHELL")) == "fish" { - fmt.Printf(" set x %s\n", name) - } else { - fmt.Printf(" export %s=%s\n", name, value) - } + if value = "" { // unsetting vars + switch shell { + case "fish": + fmt.Printf(" set -e %s\n", name) + case "bash": // default command to export variables POSIX shells, like bash, zsh, etc. + fmt.Printf(" unset %s\n", name) + } + } else { // setting vars + switch shell { + case "fish": + fmt.Printf(" set -x %s\n", name) + case "bash": // default command to export variables POSIX shells, like bash, zsh, etc. + fmt.Printf(" export %s=%s\n", name, value) } } } From 7784a0934f69ae221c9fc1e7d18dfdf233c9e364 Mon Sep 17 00:00:00 2001 From: Douglas Camata Date: Thu, 23 Oct 2014 14:33:40 -0200 Subject: [PATCH 11/20] bash's behavior is default in printExport --- cmds.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmds.go b/cmds.go index b1d4608..32f5fb1 100644 --- a/cmds.go +++ b/cmds.go @@ -199,14 +199,14 @@ func printExport(socket, certPath string) { switch shell { case "fish": fmt.Printf(" set -e %s\n", name) - case "bash": // default command to export variables POSIX shells, like bash, zsh, etc. + default: // default command to export variables POSIX shells, like bash, zsh, etc. fmt.Printf(" unset %s\n", name) } } else { // setting vars switch shell { case "fish": fmt.Printf(" set -x %s\n", name) - case "bash": // default command to export variables POSIX shells, like bash, zsh, etc. + default: // default command to export variables POSIX shells, like bash, zsh, etc. fmt.Printf(" export %s=%s\n", name, value) } } From 8287ae83fd7808ebf7ded9a0c67a510e02372c3e Mon Sep 17 00:00:00 2001 From: Douglas Camata Date: Thu, 23 Oct 2014 14:36:37 -0200 Subject: [PATCH 12/20] refactoring printExport again --- cmds.go | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/cmds.go b/cmds.go index 32f5fb1..df73e55 100644 --- a/cmds.go +++ b/cmds.go @@ -195,20 +195,19 @@ func checkEnvironment(socket, certPath string) bool { func printExport(socket, certPath string) { shell = filepath.Base(os.Getenv("SHELL")) for name, value := range exports(socket, certPath) { - if value = "" { // unsetting vars - switch shell { - case "fish": + switch shell { + case "fish": + if value = "" { fmt.Printf(" set -e %s\n", name) - default: // default command to export variables POSIX shells, like bash, zsh, etc. - fmt.Printf(" unset %s\n", name) - } - } else { // setting vars - switch shell { - case "fish": + } else { fmt.Printf(" set -x %s\n", name) - default: // default command to export variables POSIX shells, like bash, zsh, etc. - fmt.Printf(" export %s=%s\n", name, value) } + default: // default command to export variables POSIX shells, like bash, zsh, etc. + if value = "" { + fmt.Printf(" unset %s\n", name) + } else { + fmt.Printf(" export %s=%s\n", name, value) + } } } } From 5e8d8c9b87f0992aace0069718be707fd8d48c2f Mon Sep 17 00:00:00 2001 From: Douglas Camata Date: Thu, 23 Oct 2014 14:37:36 -0200 Subject: [PATCH 13/20] type fixed in printExport --- cmds.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmds.go b/cmds.go index df73e55..65f1915 100644 --- a/cmds.go +++ b/cmds.go @@ -197,13 +197,13 @@ func printExport(socket, certPath string) { for name, value := range exports(socket, certPath) { switch shell { case "fish": - if value = "" { + if value == "" { fmt.Printf(" set -e %s\n", name) } else { fmt.Printf(" set -x %s\n", name) } default: // default command to export variables POSIX shells, like bash, zsh, etc. - if value = "" { + if value == "" { fmt.Printf(" unset %s\n", name) } else { fmt.Printf(" export %s=%s\n", name, value) From c7e7beb16dd232800e74d1ccd98696c4ed71f799 Mon Sep 17 00:00:00 2001 From: Douglas Camata Date: Thu, 23 Oct 2014 14:38:24 -0200 Subject: [PATCH 14/20] getting rid of unnecessary variable attribution --- cmds.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmds.go b/cmds.go index 65f1915..c6b9dc4 100644 --- a/cmds.go +++ b/cmds.go @@ -193,9 +193,8 @@ func checkEnvironment(socket, certPath string) bool { } func printExport(socket, certPath string) { - shell = filepath.Base(os.Getenv("SHELL")) for name, value := range exports(socket, certPath) { - switch shell { + switch filepath.Base(os.Getenv("SHELL")) { case "fish": if value == "" { fmt.Printf(" set -e %s\n", name) From 8c9238e12b91dbfee6c5c5f45ffe068271727ffe Mon Sep 17 00:00:00 2001 From: Douglas Camata Date: Thu, 23 Oct 2014 14:57:55 -0200 Subject: [PATCH 15/20] forgot to add value in the fish variable export, sorry --- cmds.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmds.go b/cmds.go index c6b9dc4..ff508e5 100644 --- a/cmds.go +++ b/cmds.go @@ -199,7 +199,7 @@ func printExport(socket, certPath string) { if value == "" { fmt.Printf(" set -e %s\n", name) } else { - fmt.Printf(" set -x %s\n", name) + fmt.Printf(" set -x %s %s\n", name, value) } default: // default command to export variables POSIX shells, like bash, zsh, etc. if value == "" { From 1481459554fef1d2a7559b38822b9b908ca74304 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 23 Oct 2014 16:13:32 -0600 Subject: [PATCH 16/20] Fix some minor whitespace/gofmt issues --- cmds.go | 8 ++++---- virtualbox/machine.go | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cmds.go b/cmds.go index b6f6609..2ba13f9 100644 --- a/cmds.go +++ b/cmds.go @@ -203,10 +203,10 @@ func printExport(socket, certPath string) { } default: // default command to export variables POSIX shells, like bash, zsh, etc. if value == "" { - fmt.Printf(" unset %s\n", name) - } else { - fmt.Printf(" export %s=%s\n", name, value) - } + fmt.Printf(" unset %s\n", name) + } else { + fmt.Printf(" export %s=%s\n", name, value) + } } } } diff --git a/virtualbox/machine.go b/virtualbox/machine.go index 3e23f1f..e275abb 100644 --- a/virtualbox/machine.go +++ b/virtualbox/machine.go @@ -202,7 +202,6 @@ func (m *Machine) Start() error { fallthrough case driver.Saved: return vbm("startvm", m.Name, "--type", "headless") - } if err := m.Refresh(); err == nil { if m.State != driver.Running { From 570418c2c985ccaa8f8f7ff11dda867be65e8f4b Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 30 Oct 2014 21:23:06 -0600 Subject: [PATCH 17/20] Bump to v1.3.1 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 18fa8e7..7574079 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v1.3.0 +v1.3.1 From 22ee883e8c35c1eda369df07f218a7cebf8d2a4f Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Mon, 10 Nov 2014 14:37:30 +1000 Subject: [PATCH 18/20] remove the library path before we exec --- virtualbox/vbm.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/virtualbox/vbm.go b/virtualbox/vbm.go index a4f534f..bbb5630 100644 --- a/virtualbox/vbm.go +++ b/virtualbox/vbm.go @@ -9,12 +9,18 @@ import ( "os/exec" "path/filepath" "regexp" + "runtime" "strings" "github.com/boot2docker/boot2docker-cli/driver" ) func init() { + if runtime.GOOS == "darwin" { + // remove DYLD_LIBRARY_PATH and LD_LIBRARY_PATH as they break VBoxManage on OSX + os.Setenv("DYLD_LIBRARY_PATH", "") + os.Setenv("LD_LIBRARY_PATH", "") + } } var ( From e41a9aea94fe6fc803ca70bc0a2f9e9c9f256aed Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 24 Nov 2014 10:16:15 -0700 Subject: [PATCH 19/20] Bump to v1.3.2 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 7574079..968e750 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v1.3.1 +v1.3.2 From 2f4c56f26b46e35bc95cad4b48e03008e831e1c1 Mon Sep 17 00:00:00 2001 From: Yang Yang Date: Wed, 26 Nov 2014 16:28:30 +0800 Subject: [PATCH 20/20] add vsphere driver support --- cmds.go | 3 +- driver/driver.go | 1 + dummy/machine.go | 5 + util.go | 2 +- virtualbox/machine.go | 5 + vsphere/README.md | 51 ++++ vsphere/errors/config_error.go | 18 ++ vsphere/errors/datastore_error.go | 22 ++ vsphere/errors/errors.go | 18 ++ vsphere/errors/govc_error.go | 18 ++ vsphere/errors/guest_error.go | 22 ++ vsphere/errors/login_error.go | 13 + vsphere/errors/state_error.go | 18 ++ vsphere/errors/vm_error.go | 22 ++ vsphere/govc.go | 55 ++++ vsphere/machine.go | 462 ++++++++++++++++++++++++++++++ vsphere/vcenter.go | 311 ++++++++++++++++++++ 17 files changed, 1044 insertions(+), 2 deletions(-) create mode 100644 vsphere/README.md create mode 100644 vsphere/errors/config_error.go create mode 100644 vsphere/errors/datastore_error.go create mode 100644 vsphere/errors/errors.go create mode 100644 vsphere/errors/govc_error.go create mode 100644 vsphere/errors/guest_error.go create mode 100644 vsphere/errors/login_error.go create mode 100644 vsphere/errors/state_error.go create mode 100644 vsphere/errors/vm_error.go create mode 100644 vsphere/govc.go create mode 100644 vsphere/machine.go create mode 100644 vsphere/vcenter.go diff --git a/cmds.go b/cmds.go index 2ba13f9..d41e263 100644 --- a/cmds.go +++ b/cmds.go @@ -13,6 +13,7 @@ import ( _ "github.com/boot2docker/boot2docker-cli/dummy" _ "github.com/boot2docker/boot2docker-cli/virtualbox" + _ "github.com/boot2docker/boot2docker-cli/vsphere" "github.com/boot2docker/boot2docker-cli/driver" ) @@ -84,7 +85,7 @@ func cmdUp() error { fmt.Println("Waiting for VM and Docker daemon to start...") //give the VM a little time to start, so we don't kill the Serial Pipe/Socket time.Sleep(time.Duration(B2D.Waittime) * time.Millisecond) - natSSH := fmt.Sprintf("localhost:%d", m.GetSSHPort()) + natSSH := fmt.Sprintf("%s:%d", m.GetAddr(), m.GetSSHPort()) IP := "" for i := 1; i < B2D.Retries; i++ { print(".") diff --git a/driver/driver.go b/driver/driver.go index 525ee9b..56b38ce 100644 --- a/driver/driver.go +++ b/driver/driver.go @@ -45,6 +45,7 @@ type Machine interface { GetSerialFile() string GetDockerPort() uint GetSSHPort() uint + GetAddr() string } var ( diff --git a/dummy/machine.go b/dummy/machine.go index cfc3f1d..c591e31 100644 --- a/dummy/machine.go +++ b/dummy/machine.go @@ -122,6 +122,11 @@ func (m *Machine) GetName() string { return m.Name } +// Get machine address +func (m *Machine) GetAddr() string { + return "localhost" +} + // Get current state func (m *Machine) GetState() driver.MachineState { return m.State diff --git a/util.go b/util.go index fa072aa..aa5348c 100644 --- a/util.go +++ b/util.go @@ -176,7 +176,7 @@ func getSSHCommand(m driver.Machine, args ...string) *exec.Cmd { "-o", "LogLevel=quiet", // suppress "Warning: Permanently added '[localhost]:2022' (ECDSA) to the list of known hosts." "-p", fmt.Sprintf("%d", m.GetSSHPort()), "-i", B2D.SSHKey, - "docker@localhost", + fmt.Sprintf("docker@%s", m.GetAddr()), } sshArgs := append(DefaultSSHArgs, args...) diff --git a/virtualbox/machine.go b/virtualbox/machine.go index e275abb..ee0614b 100644 --- a/virtualbox/machine.go +++ b/virtualbox/machine.go @@ -308,6 +308,11 @@ func (m *Machine) GetName() string { return m.Name } +// Get machine address +func (m *Machine) GetAddr() string { + return "localhost" +} + // Get current state func (m *Machine) GetState() driver.MachineState { return m.State diff --git a/vsphere/README.md b/vsphere/README.md new file mode 100644 index 0000000..0886c1a --- /dev/null +++ b/vsphere/README.md @@ -0,0 +1,51 @@ +Boot2docker vSphere Driver +========================== + +The vSphere driver is to support running vSphere environment. + +vSphere Environment Requirement +--------------- + +The vSphere environment requires DHCP on the VM network the boot2docker VM is running on. + + +Configuration +--------------- + +The boot2docker reads the driver information from its profile, and a sample snippet configuration is provided below: + +```ini +# boot2docker profile filename: /Users/my/.boot2docker/profile +...... +Driver = "vsphere" +...... + +[DriverCfg.vsphere] +# path to the govc binary +Govc = "govc" + +# vCenter IP address +VcenterIp = "10.150.100.200" + +# vCenter Username (console should prompt for password) +VcenterUser = "root" + +# target datacenter to deploy the boot2docker virtual machine +VcenterDatacenter = "Datacenter" + +# target datastore to upload the boot2docker ISO and store the boot2docker virtual machine +VcenterDatastore = "datastore1" + +# target network to add the boot2docker virtual machine (requires DHCP) +VcenterNetwork = "VM Network" + +# (optional) required when user want to deploy to a specified host or multiple clusters/hosts exist in the environment +VcenterHostIp = "10.120.180.160" + +# (optional) required when user wants to deploy to a specified cluster or multiple clusters/hosts exist in this environment +VcenterPool = "cluster" + +# (optional) the default vm CPU number is 2 +VmCPU = 4 +``` + diff --git a/vsphere/errors/config_error.go b/vsphere/errors/config_error.go new file mode 100644 index 0000000..593fe1a --- /dev/null +++ b/vsphere/errors/config_error.go @@ -0,0 +1,18 @@ +package errors + +import "fmt" + +type IncompleteVcConfigError struct { + component string +} + +func NewIncompleteVcConfigError(component string) error { + err := IncompleteVcConfigError{ + component: component, + } + return &err +} + +func (err *IncompleteVcConfigError) Error() string { + return fmt.Sprintf("Incomplete vCenter information: missing %s", err.component) +} diff --git a/vsphere/errors/datastore_error.go b/vsphere/errors/datastore_error.go new file mode 100644 index 0000000..fcf9cc4 --- /dev/null +++ b/vsphere/errors/datastore_error.go @@ -0,0 +1,22 @@ +package errors + +import "fmt" + +type DatastoreError struct { + datastore string + operation string + reason string +} + +func NewDatastoreError(datastore, operation, reason string) error { + err := DatastoreError{ + datastore: datastore, + operation: operation, + reason: reason, + } + return &err +} + +func (err *DatastoreError) Error() string { + return fmt.Sprintf("Unable to %s on datastore %s due to %s", err.operation, err.datastore, err.reason) +} diff --git a/vsphere/errors/errors.go b/vsphere/errors/errors.go new file mode 100644 index 0000000..bb7843a --- /dev/null +++ b/vsphere/errors/errors.go @@ -0,0 +1,18 @@ +package errors + +import ( + original "errors" + "fmt" +) + +func New(message string) error { + return original.New(message) +} + +func NewWithFmt(message string, args ...interface{}) error { + return original.New(fmt.Sprintf(message, args...)) +} + +func NewWithError(message string, err error) error { + return NewWithFmt("%s: %s", message, err.Error()) +} diff --git a/vsphere/errors/govc_error.go b/vsphere/errors/govc_error.go new file mode 100644 index 0000000..b036c13 --- /dev/null +++ b/vsphere/errors/govc_error.go @@ -0,0 +1,18 @@ +package errors + +import "fmt" + +type GovcNotFoundError struct { + path string +} + +func NewGovcNotFoundError(path string) error { + err := GovcNotFoundError{ + path: path, + } + return &err +} + +func (err *GovcNotFoundError) Error() string { + return fmt.Sprintf("govc not found: %s", err.path) +} diff --git a/vsphere/errors/guest_error.go b/vsphere/errors/guest_error.go new file mode 100644 index 0000000..9c5df4a --- /dev/null +++ b/vsphere/errors/guest_error.go @@ -0,0 +1,22 @@ +package errors + +import "fmt" + +type GuestError struct { + vm string + operation string + reason string +} + +func NewGuestError(vm, operation, reason string) error { + err := GuestError{ + vm: vm, + operation: operation, + reason: reason, + } + return &err +} + +func (err *GuestError) Error() string { + return fmt.Sprintf("Unable to %s on vm %s due to %s", err.operation, err.vm, err.reason) +} diff --git a/vsphere/errors/login_error.go b/vsphere/errors/login_error.go new file mode 100644 index 0000000..23abc26 --- /dev/null +++ b/vsphere/errors/login_error.go @@ -0,0 +1,13 @@ +package errors + +type InvalidLoginError struct { +} + +func NewInvalidLoginError() error { + err := InvalidLoginError{} + return &err +} + +func (err *InvalidLoginError) Error() string { + return "cannot complete operation due to incorrect vSphere username or password" +} diff --git a/vsphere/errors/state_error.go b/vsphere/errors/state_error.go new file mode 100644 index 0000000..964b9bf --- /dev/null +++ b/vsphere/errors/state_error.go @@ -0,0 +1,18 @@ +package errors + +import "fmt" + +type InvalidStateError struct { + vm string +} + +func NewInvalidStateError(vm string) error { + err := InvalidStateError{ + vm: vm, + } + return &err +} + +func (err *InvalidStateError) Error() string { + return fmt.Sprintf("Machine %s state invalid", err.vm) +} diff --git a/vsphere/errors/vm_error.go b/vsphere/errors/vm_error.go new file mode 100644 index 0000000..7d12d16 --- /dev/null +++ b/vsphere/errors/vm_error.go @@ -0,0 +1,22 @@ +package errors + +import "fmt" + +type VmError struct { + operation string + vm string + reason string +} + +func NewVmError(operation, vm, reason string) error { + err := VmError{ + vm: vm, + operation: operation, + reason: reason, + } + return &err +} + +func (err *VmError) Error() string { + return fmt.Sprintf("Unable to %s docker host %s: %s", err.operation, err.vm, err.reason) +} diff --git a/vsphere/govc.go b/vsphere/govc.go new file mode 100644 index 0000000..c6e24ec --- /dev/null +++ b/vsphere/govc.go @@ -0,0 +1,55 @@ +package vsphere + +import ( + "bytes" + "log" + "os" + "os/exec" + "strings" + + "github.com/boot2docker/boot2docker-cli/vsphere/errors" +) + +func init() { +} + +func govc(args ...string) error { + err := lookPath(cfg.Govc) + if err != nil { + return errors.NewGovcNotFoundError(cfg.Govc) + } + + cmd := exec.Command(cfg.Govc, args...) + if verbose { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + log.Printf("executing: %v %v", cfg.Govc, strings.Join(args, " ")) + } + if err := cmd.Run(); err != nil { + return err + } + return nil +} + +func govcOutErr(args ...string) (string, string, error) { + err := lookPath(cfg.Govc) + if err != nil { + return "", "", errors.NewGovcNotFoundError(cfg.Govc) + } + + cmd := exec.Command(cfg.Govc, args...) + if verbose { + log.Printf("executing: %v %v", cfg.Govc, strings.Join(args, " ")) + } + var stdout bytes.Buffer + var stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err = cmd.Run() + return stdout.String(), stderr.String(), err +} + +func lookPath(file string) error { + _, err := exec.LookPath(file) + return err +} diff --git a/vsphere/machine.go b/vsphere/machine.go new file mode 100644 index 0000000..13aec57 --- /dev/null +++ b/vsphere/machine.go @@ -0,0 +1,462 @@ +package vsphere + +import ( + "fmt" + "os" + "strconv" + "strings" + + "github.com/boot2docker/boot2docker-cli/driver" + "github.com/boot2docker/boot2docker-cli/vsphere/errors" + flag "github.com/ogier/pflag" +) + +type DriverCfg struct { + Govc string // Path to govc binary + VcenterIp string // vCenter URL + VcenterUser string // vCenter User + VcenterDC string // target vCenter Datacenter + VcenterDS string // target vCenter Datastore + VcenterNet string // vCenter VM Network + VcenterPool string // target vCenter Resource Pool + VcenterHostIp string // target vCenter Host Ip + Cpu string // CPU number of the virtual machine +} + +var ( + verbose bool // Verbose mode (Local copy of B2D.Verbose). + cfg DriverCfg +) + +const ( + DATASTORE_DIR = "boot2docker-iso" + DATASTORE_ISO_NAME = "boot2docker.iso" + DEFAULT_CPU_NUMBER = 2 +) + +func init() { + if err := driver.Register("vsphere", InitFunc); err != nil { + fmt.Fprintf(os.Stderr, "Failed to initialize driver. Error : %s", err.Error()) + os.Exit(1) + } + if err := driver.RegisterConfig("vsphere", ConfigFlags); err != nil { + fmt.Fprintf(os.Stderr, "Failed to initialize driver config. Error : %s", err.Error()) + os.Exit(1) + } +} + +// Initialize the Machine. +func InitFunc(mc *driver.MachineConfig) (driver.Machine, error) { + verbose = mc.Verbose + + m, err := GetMachine(mc) + if _, ok := err.(*errors.GovcNotFoundError); ok { + return nil, err + } + + if err != nil && mc.Init == true { + return CreateMachine(mc) + } + return m, err +} + +// Add cmdline params for this driver +func ConfigFlags(B2D *driver.MachineConfig, flags *flag.FlagSet) error { + flags.StringVar(&cfg.Govc, "govc", "govc", "Path to GOVC Binary") + flags.StringVar(&cfg.VcenterIp, "vcenter-ip", "", "vCenter URL") + flags.StringVar(&cfg.VcenterUser, "vcenter-user", "", "vCenter User") + flags.StringVar(&cfg.VcenterDC, "vcenter-datacenter", "", "vCenter Datacenter") + flags.StringVar(&cfg.VcenterDS, "vcenter-datastore", "", "vCenter Datastore") + flags.StringVar(&cfg.VcenterNet, "vcenter-vm-network", "", "vCenter VM Network") + flags.StringVar(&cfg.VcenterPool, "vcenter-pool", "", "vCenter Target Resource Pool") + flags.StringVar(&cfg.VcenterHostIp, "vcenter-host-ip", "", "vCenter Target Host IP") + + return nil +} + +// GetMachine fetches the machine information from a vCenter +func GetMachine(mc *driver.MachineConfig) (*Machine, error) { + err := GetDriverCfg(mc) + if err != nil { + return nil, err + } + + if mc.Init == false { + fmt.Fprintf(os.Stdout, "Connecting to vSphere environment %s...\n", cfg.VcenterIp) + } + + vcConn := NewVcConn(&cfg) + err = vcConn.Login() + if err != nil { + return nil, err + } + + stdout, err := vcConn.VmInfo(mc.VM) + if err != nil { + return nil, err + } + + m := &Machine{ + Name: mc.VM, + State: driver.Poweroff, + SshPubKey: mc.SSHKey + ".pub", + VcenterIp: cfg.VcenterIp, + VcenterUser: cfg.VcenterUser, + Datacenter: cfg.VcenterDC, + Network: cfg.VcenterNet, + } + + ParseVmProperty(stdout, m) + + return m, nil +} + +// create a new machine in vsphere includes the following steps: +// 1. create a directory in vsphere datastore to include the B2D ISO; +// 2. uploads the ISO to the corresponding datastore; +// 3. bootup the virtual machine with the ISO mounted; +func CreateMachine(mc *driver.MachineConfig) (*Machine, error) { + err := GetDriverCfg(mc) + if err != nil { + return nil, err + } + + vcConn := NewVcConn(&cfg) + err = vcConn.DatastoreMkdir(DATASTORE_DIR) + if err != nil { + return nil, err + } + + err = vcConn.DatastoreUpload(mc.ISO) + if err != nil { + return nil, err + } + + memory := strconv.Itoa(int(mc.Memory)) + isoPath := fmt.Sprintf("%s/%s", DATASTORE_DIR, DATASTORE_ISO_NAME) + err = vcConn.VmCreate(isoPath, memory, mc.VM) + if err != nil { + return nil, err + } + + fmt.Fprintf(os.Stdout, "Configuring the virtual machine %s... ", mc.VM) + diskSize := strconv.Itoa(int(mc.DiskSize)) + err = vcConn.VmDiskCreate(mc.VM, diskSize) + if err != nil { + fmt.Fprintf(os.Stderr, "failed!\n") + return nil, err + } + + err = vcConn.VmAttachNetwork(mc.VM) + if err != nil { + fmt.Fprintf(os.Stderr, "failed!\n") + return nil, err + } + + fmt.Fprintf(os.Stdout, "ok!\n") + cpu, err := strconv.ParseUint(cfg.Cpu, 10, 32) + if err != nil { + return nil, err + } + + m := &Machine{ + Name: mc.VM, + State: driver.Poweroff, + CPUs: uint(cpu), + Memory: mc.Memory, + VcenterIp: cfg.VcenterIp, + VcenterUser: cfg.VcenterUser, + Datacenter: cfg.VcenterDC, + Network: cfg.VcenterNet, + SshPubKey: mc.SSHKey + ".pub", + } + return m, nil +} + +func ParseVmProperty(stdout string, m *Machine) { + currentCpu := strings.Trim(strings.Split(strings.Split(stdout, "CPU:")[1], "vCPU")[0], " ") + if cpus, err := strconv.ParseUint(currentCpu, 10, 32); err == nil { + m.CPUs = uint(cpus) + } + currentMem := strings.Trim(strings.Split(strings.Split(stdout, "Memory:")[1], "MB")[0], " ") + if mem, err := strconv.ParseUint(currentMem, 10, 32); err == nil { + m.Memory = uint(mem) + } + if strings.Contains(stdout, "poweredOn") { + m.State = driver.Running + m.VmIp = strings.Trim(strings.Trim(strings.Split(stdout, "IP address:")[1], " "), "\n") + } +} + +func GetDriverCfg(mc *driver.MachineConfig) error { + vcenterIp := mc.DriverCfg["vsphere"].(map[string]interface{})["VcenterIp"] + if vcenterIp == nil { + if cfg.VcenterIp == "" { + return errors.NewIncompleteVcConfigError("vCenter IP") + } + } else { + cfg.VcenterIp = vcenterIp.(string) + } + vcenterUser := mc.DriverCfg["vsphere"].(map[string]interface{})["VcenterUser"] + if vcenterUser == nil { + if cfg.VcenterUser == "" { + return errors.NewIncompleteVcConfigError("vCenter User") + } + } else { + cfg.VcenterUser = vcenterUser.(string) + } + vcenterDC := mc.DriverCfg["vsphere"].(map[string]interface{})["VcenterDatacenter"] + if vcenterDC == nil { + if cfg.VcenterDC == "" { + return errors.NewIncompleteVcConfigError("vCenter Datacenter") + } + } else { + cfg.VcenterDC = vcenterDC.(string) + } + vcenterDS := mc.DriverCfg["vsphere"].(map[string]interface{})["VcenterDatastore"] + if vcenterDS == nil { + if cfg.VcenterDS == "" { + return errors.NewIncompleteVcConfigError("vCenter Datastore") + } + } else { + cfg.VcenterDS = vcenterDS.(string) + } + vcenterNet := mc.DriverCfg["vsphere"].(map[string]interface{})["VcenterNetwork"] + if vcenterNet == nil { + if cfg.VcenterNet == "" { + return errors.NewIncompleteVcConfigError("vCenter Network") + } + } else { + cfg.VcenterNet = vcenterNet.(string) + } + cpu := mc.DriverCfg["vsphere"].(map[string]interface{})["VmCPU"] + if cpu == nil { + if cfg.Cpu == "" { + cfg.Cpu = strconv.Itoa(DEFAULT_CPU_NUMBER) + } + } else { + cfg.Cpu = strconv.Itoa(int(cpu.(int64))) + } + + // govc path information are optional as user may want to use the default + govc := mc.DriverCfg["vsphere"].(map[string]interface{})["Govc"] + if govc != nil { + cfg.Govc = govc.(string) + } + + // vcenter resource pool and host ip are nullable configurations + pool := mc.DriverCfg["vsphere"].(map[string]interface{})["VcenterPool"] + if pool != nil { + cfg.VcenterPool = pool.(string) + } + hostIp := mc.DriverCfg["vsphere"].(map[string]interface{})["VcenterHostIp"] + if hostIp != nil { + cfg.VcenterHostIp = hostIp.(string) + } + return nil +} + +// Machine information. +type Machine struct { + Name string + State driver.MachineState + CPUs uint + Memory uint + VcenterIp string // the vcenter the machine belongs to + VcenterUser string // the vcenter user/admin to own the machine + Datacenter string // the datacenter the machine locates + Network string // the network the machine is using + VmIp string // the Ip address of the machine + SshPubKey string // pass SSH here so the vm knows the source of authorized_keys +} + +// Refresh reloads the machine information. +func (m *Machine) Refresh() error { + vcConn := NewVcConn(&cfg) + stdout, err := vcConn.VmInfo(m.Name) + if err != nil { + return err + } + ParseVmProperty(stdout, m) + return nil +} + +// Start starts the machine. +// for vSphere driver, the start process includes the following changes +// 1. start the docker virtual machine; +// 2. fetch the ip address from the virtual machine (with open-vmtools); +// 3. upload the ssh key to the virtual machine; +func (m *Machine) Start() error { + switch m.State { + case driver.Running: + msg := fmt.Sprintf("VM %s has already been started", m.Name) + fmt.Println(msg) + return nil + case driver.Poweroff: + // TODO add transactional or error handling in the following steps + vcConn := NewVcConn(&cfg) + err := vcConn.VmPowerOn(m.Name) + if err != nil { + return err + } + // this step waits for the vm to start and fetch its ip address; + // this guarantees that the opem-vmtools has started working... + _, err = vcConn.VmFetchIp(m.Name) + if err != nil { + return err + } + + fmt.Fprintf(os.Stdout, "Configuring virtual machine %s... ", m.Name) + err = vcConn.GuestMkdir("docker", "tcuser", m.Name, "/home/docker/.ssh") + if err != nil { + fmt.Fprintf(os.Stdout, "failed!\n") + return err + } + err = vcConn.GuestUpload("docker", "tcuser", m.Name, m.SshPubKey, + "/home/docker/.ssh/authorized_keys") + if err != nil { + fmt.Fprintf(os.Stdout, "failed!\n") + return err + } + fmt.Fprintf(os.Stdout, "ok!\n") + } + return nil +} + +// Suspend suspends the machine and saves its state to disk. +func (m *Machine) Save() error { + return driver.ErrNotSupported +} + +// Pause pauses the execution of the machine. +func (m *Machine) Pause() error { + return driver.ErrNotSupported +} + +// Currently make stop equivalent to poweroff as there is no shutdown guestOS API +// yet with current open-vmtools and govc +func (m *Machine) Stop() error { + vcConn := NewVcConn(&cfg) + err := vcConn.VmPowerOff(m.Name) + if err != nil { + return err + } + m.State = driver.Poweroff + return err +} + +// Poweroff forcefully stops the machine. State is lost and might corrupt the disk image. +func (m *Machine) Poweroff() error { + vcConn := NewVcConn(&cfg) + err := vcConn.VmPowerOff(m.Name) + if err != nil { + return err + } + m.State = driver.Poweroff + return err +} + +// Restart gracefully restarts the machine. +func (m *Machine) Restart() error { + switch m.State { + case driver.Running: + if err := m.Stop(); err != nil { + return err + } + case driver.Poweroff: + fmt.Fprintf(os.Stdout, "Machine %s already stopped, starting it... \n", m.Name) + } + return m.Start() +} + +// Reset forcefully restarts the machine. State is lost and might corrupt the disk image. +func (m *Machine) Reset() error { + return m.Restart() +} + +// Get current name +func (m *Machine) GetName() string { + return m.Name +} + +// Get machine address +func (m *Machine) GetAddr() string { + return m.VmIp +} + +// Get current state +func (m *Machine) GetState() driver.MachineState { + return m.State +} + +// Get serial file +func (m *Machine) GetSerialFile() string { + return "" +} + +// Get Docker port +func (m *Machine) GetDockerPort() uint { + return 2375 +} + +// Get SSH port +func (m *Machine) GetSSHPort() uint { + return 22 +} + +// Delete deletes the machine and associated disk images. +func (m *Machine) Delete() error { + if m.State == driver.Running { + msg := fmt.Sprintf("Please poweroff machine %s before delete", m.Name) + fmt.Println(msg) + return errors.NewInvalidStateError(m.Name) + } + vcConn := NewVcConn(&cfg) + err := vcConn.VmDestroy(m.Name) + if err != nil { + return err + } + return nil +} + +// Modify changes the settings of the machine. +func (m *Machine) Modify() error { + fmt.Printf("Modify %s: %s\n", m.Name, m.State) + return m.Refresh() +} + +// AddNATPF adds a NAT port forarding rule to the n-th NIC with the given name. +func (m *Machine) AddNATPF(n int, name string, rule driver.PFRule) error { + fmt.Println("Add NAT PF") + return nil +} + +// DelNATPF deletes the NAT port forwarding rule with the given name from the n-th NIC. +func (m *Machine) DelNATPF(n int, name string) error { + fmt.Println("Del NAT PF") + return nil +} + +// SetNIC set the n-th NIC. +func (m *Machine) SetNIC(n int, nic driver.NIC) error { + fmt.Println("Set NIC") + return nil +} + +// AddStorageCtl adds a storage controller with the given name. +func (m *Machine) AddStorageCtl(name string, ctl driver.StorageController) error { + fmt.Println("Add storage ctl") + return nil +} + +// DelStorageCtl deletes the storage controller with the given name. +func (m *Machine) DelStorageCtl(name string) error { + fmt.Println("Del storage ctl") + return nil +} + +// AttachStorage attaches a storage medium to the named storage controller. +func (m *Machine) AttachStorage(ctlName string, medium driver.StorageMedium) error { + fmt.Println("Attach storage") + return nil +} diff --git a/vsphere/vcenter.go b/vsphere/vcenter.go new file mode 100644 index 0000000..eaec694 --- /dev/null +++ b/vsphere/vcenter.go @@ -0,0 +1,311 @@ +package vsphere + +import ( + "fmt" + "os" + "strings" + + "github.com/boot2docker/boot2docker-cli/vsphere/errors" + "github.com/howeyc/gopass" +) + +type VcConn struct { + cfg *DriverCfg + password string +} + +func NewVcConn(cfg *DriverCfg) VcConn { + return VcConn{ + cfg: cfg, + password: "", + } +} + +func (conn VcConn) Login() error { + err := conn.queryAboutInfo() + if err == nil { + return nil + } + if _, ok := err.(*errors.GovcNotFoundError); ok { + return err + } + + fmt.Fprintf(os.Stdout, "Enter vCenter Password: ") + password := gopass.GetPasswd() + conn.password = string(password[:]) + + err = conn.queryAboutInfo() + if err == nil { + return nil + } + return err +} + +func (conn VcConn) DatastoreLs(path string) (string, error) { + args := []string{"datastore.ls"} + args = conn.AppendConnectionString(args) + args = append(args, fmt.Sprintf("--ds=%s", conn.cfg.VcenterDS)) + args = append(args, path) + stdout, stderr, err := govcOutErr(args...) + if stderr == "" && err == nil { + return stdout, nil + } + return "", errors.NewDatastoreError(conn.cfg.VcenterDC, "ls", stderr) +} + +func (conn VcConn) DatastoreMkdir(dirName string) error { + _, err := conn.DatastoreLs(dirName) + if err == nil { + return nil + } + + fmt.Fprintf(os.Stdout, "Creating directory %s on datastore %s of vCenter %s... ", + dirName, conn.cfg.VcenterDS, conn.cfg.VcenterIp) + + args := []string{"datastore.mkdir"} + args = conn.AppendConnectionString(args) + args = append(args, fmt.Sprintf("--ds=%s", conn.cfg.VcenterDS)) + args = append(args, dirName) + _, stderr, err := govcOutErr(args...) + if stderr == "" && err == nil { + fmt.Fprintf(os.Stdout, "ok!\n") + return nil + } else { + fmt.Fprintf(os.Stderr, "failed!\n") + return errors.NewDatastoreError(conn.cfg.VcenterDS, "mkdir", stderr) + } +} + +func (conn VcConn) DatastoreUpload(localPath string) error { + stdout, err := conn.DatastoreLs(DATASTORE_DIR) + if err == nil && strings.Contains(stdout, DATASTORE_ISO_NAME) { + fmt.Fprintf(os.Stdout, "boot2docker ISO already uploaded, skipping upload... \n") + return nil + } + + fmt.Fprintf(os.Stdout, "Uploading %s to %s on datastore %s of vCenter %s... ", + localPath, DATASTORE_DIR, conn.cfg.VcenterDS, conn.cfg.VcenterIp) + + dsPath := fmt.Sprintf("%s/%s", DATASTORE_DIR, DATASTORE_ISO_NAME) + args := []string{"datastore.upload"} + args = conn.AppendConnectionString(args) + args = append(args, fmt.Sprintf("--ds=%s", conn.cfg.VcenterDS)) + args = append(args, localPath) + args = append(args, dsPath) + _, stderr, err := govcOutErr(args...) + if stderr == "" && err == nil { + fmt.Fprintf(os.Stdout, "ok!\n") + return nil + } else { + fmt.Fprintf(os.Stderr, "failed!\n") + return errors.NewDatastoreError(conn.cfg.VcenterDC, "upload", stderr) + } +} + +func (conn VcConn) VmInfo(vmName string) (string, error) { + args := []string{"vm.info"} + args = conn.AppendConnectionString(args) + args = append(args, fmt.Sprintf("--dc=%s", conn.cfg.VcenterDC)) + args = append(args, vmName) + + stdout, stderr, err := govcOutErr(args...) + if strings.Contains(stdout, "Name") && stderr == "" && err == nil { + return stdout, nil + } else { + return "", errors.NewVmError("find", vmName, "VM not found") + } +} + +func (conn VcConn) VmCreate(isoPath, memory, vmName string) error { + fmt.Fprintf(os.Stdout, "Creating virtual machine %s of vCenter %s... ", + vmName, conn.cfg.VcenterIp) + + args := []string{"vm.create"} + args = conn.AppendConnectionString(args) + args = append(args, fmt.Sprintf("--net=%s", conn.cfg.VcenterNet)) + args = append(args, fmt.Sprintf("--dc=%s", conn.cfg.VcenterDC)) + args = append(args, fmt.Sprintf("--ds=%s", conn.cfg.VcenterDS)) + args = append(args, fmt.Sprintf("--iso=%s", isoPath)) + args = append(args, fmt.Sprintf("--m=%s", memory)) + args = append(args, fmt.Sprintf("--c=%s", conn.cfg.Cpu)) + args = append(args, "--disk.controller=scsi") + args = append(args, "--on=false") + if conn.cfg.VcenterPool != "" { + args = append(args, fmt.Sprintf("--pool=%s", conn.cfg.VcenterPool)) + } + if conn.cfg.VcenterHostIp != "" { + args = append(args, fmt.Sprintf("--host.ip=%s", conn.cfg.VcenterHostIp)) + } + args = append(args, vmName) + _, stderr, err := govcOutErr(args...) + + if stderr == "" && err == nil { + fmt.Fprintf(os.Stdout, "ok!\n") + return nil + } else { + fmt.Fprintf(os.Stderr, "failed!\n") + return errors.NewVmError("create", vmName, stderr) + } +} + +func (conn VcConn) VmPowerOn(vmName string) error { + fmt.Fprintf(os.Stdout, "Powering on virtual machine %s of vCenter %s... ", + vmName, conn.cfg.VcenterIp) + + args := []string{"vm.power"} + args = conn.AppendConnectionString(args) + args = append(args, "-on") + args = append(args, vmName) + _, stderr, err := govcOutErr(args...) + + if stderr == "" && err == nil { + fmt.Fprintf(os.Stdout, "ok!\n") + return nil + } else { + fmt.Fprintf(os.Stderr, "failed!\n") + return errors.NewVmError("power on", vmName, stderr) + } +} + +func (conn VcConn) VmPowerOff(vmName string) error { + fmt.Fprintf(os.Stdout, "Powering off virtual machine %s of vCenter %s... ", + vmName, conn.cfg.VcenterIp) + + args := []string{"vm.power"} + args = conn.AppendConnectionString(args) + args = append(args, "-off") + args = append(args, vmName) + _, stderr, err := govcOutErr(args...) + + if stderr == "" && err == nil { + fmt.Fprintf(os.Stdout, "ok!\n") + return nil + } else { + fmt.Fprintf(os.Stderr, "failed!\n") + return errors.NewVmError("power on", vmName, stderr) + } +} + +func (conn VcConn) VmDestroy(vmName string) error { + fmt.Fprintf(os.Stdout, "Deleting virtual machine %s of vCenter %s... ", + vmName, conn.cfg.VcenterIp) + + args := []string{"vm.destroy"} + args = conn.AppendConnectionString(args) + args = append(args, fmt.Sprintf("--dc=%s", conn.cfg.VcenterDC)) + args = append(args, vmName) + _, stderr, err := govcOutErr(args...) + + if stderr == "" && err == nil { + fmt.Fprintf(os.Stdout, "ok!\n") + return nil + } else { + fmt.Fprintf(os.Stderr, "failed!\n") + return errors.NewVmError("delete", vmName, stderr) + } + +} + +func (conn VcConn) VmDiskCreate(vmName, diskSize string) error { + args := []string{"vm.disk.create"} + args = conn.AppendConnectionString(args) + args = append(args, fmt.Sprintf("--vm=%s", vmName)) + args = append(args, fmt.Sprintf("--ds=%s", conn.cfg.VcenterDS)) + args = append(args, fmt.Sprintf("--name=%s", vmName)) + args = append(args, fmt.Sprintf("--size=%sMiB", diskSize)) + + _, stderr, err := govcOutErr(args...) + if stderr == "" && err == nil { + return nil + } else { + return errors.NewVmError("add network", vmName, stderr) + } +} + +func (conn VcConn) VmAttachNetwork(vmName string) error { + args := []string{"vm.network.add"} + args = conn.AppendConnectionString(args) + args = append(args, fmt.Sprintf("--vm=%s", vmName)) + args = append(args, fmt.Sprintf("--net=%s", conn.cfg.VcenterNet)) + + _, stderr, err := govcOutErr(args...) + if stderr == "" && err == nil { + return nil + } else { + return errors.NewVmError("add network", vmName, stderr) + } +} + +func (conn VcConn) VmFetchIp(vmName string) (string, error) { + fmt.Fprintf(os.Stdout, "Fetching IP on virtual machine %s of vCenter %s... ", + vmName, conn.cfg.VcenterIp) + + args := []string{"vm.ip"} + args = conn.AppendConnectionString(args) + args = append(args, vmName) + stdout, stderr, err := govcOutErr(args...) + + if stderr == "" && err == nil { + fmt.Fprintf(os.Stdout, "ok!\n") + return stdout, nil + } else { + fmt.Fprintf(os.Stderr, "failed!\n") + return "", errors.NewVmError("fetching IP", vmName, stderr) + } +} + +func (conn VcConn) GuestMkdir(guestUser, guestPass, vmName, dirName string) error { + args := []string{"guest.mkdir"} + args = conn.AppendConnectionString(args) + args = append(args, fmt.Sprintf("--l=%s:%s", guestUser, guestPass)) + args = append(args, fmt.Sprintf("--vm=%s", vmName)) + args = append(args, "-p") + args = append(args, dirName) + _, stderr, err := govcOutErr(args...) + + if stderr == "" && err == nil { + return nil + } else { + return errors.NewGuestError("mkdir", vmName, stderr) + } +} + +func (conn VcConn) GuestUpload(guestUser, guestPass, vmName, localPath, remotePath string) error { + args := []string{"guest.upload"} + args = conn.AppendConnectionString(args) + args = append(args, fmt.Sprintf("--l=%s:%s", guestUser, guestPass)) + args = append(args, fmt.Sprintf("--vm=%s", vmName)) + args = append(args, "-f") + args = append(args, localPath) + args = append(args, remotePath) + _, stderr, err := govcOutErr(args...) + + if stderr == "" && err == nil { + return nil + } else { + return errors.NewGuestError("upload", vmName, stderr) + } +} + +func (conn VcConn) AppendConnectionString(args []string) []string { + if conn.password == "" { + args = append(args, fmt.Sprintf("--u=%s@%s", conn.cfg.VcenterUser, cfg.VcenterIp)) + } else { + args = append(args, fmt.Sprintf("--u=%s:%s@%s", conn.cfg.VcenterUser, conn.password, conn.cfg.VcenterIp)) + } + args = append(args, "--k=true") + return args +} + +func (conn VcConn) queryAboutInfo() error { + args := []string{"about"} + args = conn.AppendConnectionString(args) + stdout, _, err := govcOutErr(args...) + if strings.Contains(stdout, "Name") { + return nil + } + if _, ok := err.(*errors.GovcNotFoundError); ok { + return err + } + return errors.NewInvalidLoginError() +}