diff --git a/.travis.yml b/.travis.yml index 3391bd5..f6e163f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,21 +1,18 @@ language: go -# this should be exactly the same as what's in the Dockerfile -go: 1.3 +go: + - 1.4.1 # let us have pretty experimental Docker-based Travis workers sudo: false env: - - _GOOS=linux _GOARCH=amd64 - - _GOOS=darwin _GOARCH=amd64 - - _GOOS=windows _GOARCH=amd64 + - GIMME_OS=linux GIMME_ARCH=amd64 + - GIMME_OS=darwin GIMME_ARCH=amd64 + - GIMME_OS=windows GIMME_ARCH=amd64 install: - env | sort - - gvm cross "$_GOOS" "$_GOARCH" - - export GOOS="$_GOOS" GOARCH="$_GOARCH" - - go env - go get -d -v ./... script: diff --git a/DEPRECATION_WARNING b/DEPRECATION_WARNING new file mode 100644 index 0000000..32d7c87 --- /dev/null +++ b/DEPRECATION_WARNING @@ -0,0 +1,7 @@ + + WARNING: The 'boot2docker' command line interface (not to be confused with + 'boot2docker' the operating system) is officially deprecated. + + Please switch to Docker Machine (https://docs.docker.com/machine/) ASAP. + + Docker Toolbox (https://docker.com/toolbox) is the recommended install method. diff --git a/Dockerfile b/Dockerfile index 71515cc..a5a19eb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,13 @@ # Dockerfile to cross compile boot2docker-cli -FROM golang:1.3-cross +FROM golang:1.4-cross -ADD . /go/src/github.com/boot2docker/boot2docker-cli WORKDIR /go/src/github.com/boot2docker/boot2docker-cli # Download (but not install) dependencies -RUN go get -d -v ./... +RUN go get -v github.com/BurntSushi/toml +RUN go get -v github.com/ogier/pflag + +ADD . /go/src/github.com/boot2docker/boot2docker-cli CMD ["make", "all"] diff --git a/Makefile b/Makefile index 1d69821..6d66304 100644 --- a/Makefile +++ b/Makefile @@ -12,14 +12,13 @@ default: dockerbuild @true # stop from matching "%" later -# Build binaries in Docker container. The `|| true` hack is a temporary fix for -# https://github.com/dotcloud/docker/issues/3986 +# Build binaries in Docker container. dockerbuild: clean docker build -t "$(DOCKER_IMAGE)" . docker run --name "$(DOCKER_CONTAINER)" "$(DOCKER_IMAGE)" - docker cp "$(DOCKER_CONTAINER)":"$(DOCKER_SRC_PATH)"/$(PREFIX)-$(VERSION)-darwin-$(GOARCH) . || true - docker cp "$(DOCKER_CONTAINER)":"$(DOCKER_SRC_PATH)"/$(PREFIX)-$(VERSION)-linux-$(GOARCH) . || true - docker cp "$(DOCKER_CONTAINER)":"$(DOCKER_SRC_PATH)"/$(PREFIX)-$(VERSION)-windows-$(GOARCH).exe . || true + docker cp "$(DOCKER_CONTAINER)":"$(DOCKER_SRC_PATH)"/$(PREFIX)-$(VERSION)-darwin-$(GOARCH) . + docker cp "$(DOCKER_CONTAINER)":"$(DOCKER_SRC_PATH)"/$(PREFIX)-$(VERSION)-linux-$(GOARCH) . + docker cp "$(DOCKER_CONTAINER)":"$(DOCKER_SRC_PATH)"/$(PREFIX)-$(VERSION)-windows-$(GOARCH).exe . docker rm "$(DOCKER_CONTAINER)" diff --git a/README.md b/README.md index dc15748..f9b5c5d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ +# Deprecated + +This project is officially deprecated in favor of [Docker +Machine](https://docs.docker.com/machine/). The code and documentation here +only exist as a reference for users who have not yet switched over (but please +do soon). The recommended way to install Machine is with the [Docker +Toolbox](https://docker.com/toolbox). + # boot2docker command line management tool This tool downloads the boot2docker ISO image, creates a VirtualBox virtual @@ -9,7 +17,7 @@ login via SSH. On Windows, [MSYS SSH](http://www.mingw.org/) provides a first class way to connect to the boot2docker VM using `boot2docker.exe ssh`. -> **Note:** Docker now has an [IANA registered IP Port: 2375]( http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=docker) +> **Note:** Docker now has an [IANA registered IP Port: 2376]( http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=docker) > , so the use of port 4243 is deprecated. This also means that new Boot2Docker > ISO releases and management tool are not compatible. @@ -46,7 +54,7 @@ and `make` installed. ### Install from source -You need to have the [Go compiler (v1.3 or higher)](http://golang.org) installed, and `$GOPATH` +You need to have the [Go compiler (v1.4 or higher)](http://golang.org) installed, and `$GOPATH` [properly setup](http://golang.org/doc/code.html#GOPATH). Then run go get github.com/boot2docker/boot2docker-cli @@ -69,7 +77,7 @@ You can cross compile to OS X, Windows, and Linux. For that you need to first [make your Go compiler ready for cross compiling to the target platforms](http://stackoverflow.com/questions/12168873/cross-compile-go-on-osx). -Please make sure you build with golang v1.3 or later - it is required for +Please make sure you build with golang v1.4 or later - it is required for `boot2docker download` to work on OS X. We provide a Makefile to make the process a bit easier. @@ -123,7 +131,7 @@ The `boot2docker` binary reads configuration from `$BOOT2DOCKER_PROFILE` if set, `%USERPROFILE%/.boot2docker/profile`. `boot2docker config` will tell you where it is looking for the file, and will also output the settings that are in use, so you can initialise a default file to customize using -`boot2docker config > /home/sven/.boot2docker/profile`. +`boot2docker config > ~/.boot2docker/profile`. Currently you can configure the following options (undefined options take default values): @@ -142,6 +150,13 @@ SSHKey = "/Users/sven/.ssh/id_boot2docker" # name of boot2docker virtual machine VM = "boot2docker-vm" +# URL pointing either to a Github "/releases" API endpoint to automatically +# retrieve the `boot2docker.iso` asset from the latest released version of a +# repo, or directly to an ISO image +ISOURL = "https://api.github.com/repos/boot2docker/boot2docker/releases" +#ISOURL = "https://github.com/boot2docker/boot2docker/releases/download/v1.0.0/boot2docker.iso" +#ISOURL = "https://internal.corp.org/b2d.iso" + # path to boot2docker ISO image ISO = "/Users/sven/.boot2docker/boot2docker.iso" @@ -151,11 +166,14 @@ DiskSize = 20000 # VM memory size in MB Memory = 2048 +# Number of CPUs +CPUs = 1 + # host port forwarding to port 22 in the VM SSHPort = 2022 -# host port forwarding to port 2375 in the VM -DockerPort = 2375 +# host port forwarding to port 2376 in the VM +DockerPort = 2376 # host-only network host IP HostIP = "192.168.59.3" @@ -180,6 +198,40 @@ You can override the configurations using matching command-line flags. Type `boot2docker -h` for more information. The configuration file options are the same as the command-line flags with long names. +## Upgrade + +You can use boot2docker-cli to upgrade: + +1. The ISO you are using in the VM (and consequently the Docker daemon version) +2. The Docker client binary on your host system +3. The boot2docker-cli binary itself + +To do so, run the `boot2docker upgrade` command. + +```console +$ boot2docker upgrade +Backing up existing docker binary... +Downloading new docker client binary... +Success: downloaded https://get.docker.com/builds/Darwin/x86_64/docker-latest + to /usr/local/bin/docker + The old version is backed up to ~/.boot2docker. +Backing up existing boot2docker binary... +Downloading new boot2docker client binary... +Success: downloaded https://github.com/boot2docker/boot2docker-cli/releases/download/v1.4.0/boot2docker-v1.4.0-darwin-amd64 + to /usr/local/bin/boot2docker + The old version is backed up to ~/.boot2docker. +Latest release for boot2docker/boot2docker is v1.4.0 +Downloading boot2docker ISO image... +Success: downloaded https://github.com/boot2docker/boot2docker/releases/download/v1.4.0/boot2docker.iso + to /Users/youruser/.boot2docker/boot2docker.iso +Waiting for VM and Docker daemon to start... +.................ooo +Started. +``` + +This will back up your current `docker` and `boot2docker` binaries to +`~/.boot2docker` and download the latest ISO, `docker` binary and `boot2docker` +binary in place of the old versions. ## Contribution diff --git a/VERSION b/VERSION index 79127d8..804a616 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v1.2.0 +v1.8.0 diff --git a/cmds.go b/cmds.go index f82c979..3cd391e 100644 --- a/cmds.go +++ b/cmds.go @@ -3,18 +3,24 @@ package main import ( "encoding/json" "fmt" + "io/ioutil" "os" "os/exec" + "path/filepath" + "regexp" "runtime" "strings" "time" + "github.com/boot2docker/boot2docker-cli/driver" _ "github.com/boot2docker/boot2docker-cli/dummy" _ "github.com/boot2docker/boot2docker-cli/virtualbox" - - "github.com/boot2docker/boot2docker-cli/driver" ) +func vmNotRunningError(vmName string) error { + return fmt.Errorf("VM %q is not running. (Did you run `boot2docker up`?)", vmName) +} + // Initialize the boot2docker VM from scratch. func cmdInit() error { B2D.Init = false @@ -59,6 +65,8 @@ func cmdInit() error { if err != nil { return fmt.Errorf("Failed to initialize machine %q: %s", B2D.VM, err) } + fmt.Printf("Initialization of virtual machine %q complete.\n", B2D.VM) + fmt.Printf("Use `boot2docker up` to start it.\n") return nil } @@ -81,17 +89,17 @@ 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(600 * time.Millisecond) + time.Sleep(time.Duration(B2D.Waittime) * time.Millisecond) natSSH := fmt.Sprintf("localhost:%d", m.GetSSHPort()) IP := "" - for i := 1; i < 30; i++ { + for i := 1; i < B2D.Retries; i++ { print(".") if B2D.Serial && runtime.GOOS != "windows" { if IP, err = RequestIPFromSerialPort(m.GetSerialFile()); err == nil { break } } - if err := read(natSSH, 1, 300*time.Millisecond); err == nil { + if err := read(natSSH, 1, time.Duration(B2D.Waittime)*time.Millisecond); err == nil { if IP, err = RequestIPFromSSH(m); err == nil { break } @@ -102,23 +110,23 @@ func cmdUp() error { fmt.Printf("\nWaiting for Docker daemon to start...\n") } - time.Sleep(300 * time.Millisecond) + time.Sleep(time.Duration(B2D.Waittime) * time.Millisecond) socket := "" - for i := 1; i < 30; i++ { - print(".") + for i := 1; i < B2D.Retries; i++ { + print("o") if socket, err = RequestSocketFromSSH(m); err == nil { break } if B2D.Verbose { fmt.Printf("Error requesting socket: %s\n", err) } - time.Sleep(300 * time.Millisecond) + time.Sleep(600 * time.Millisecond) } fmt.Printf("\nStarted.\n") if socket == "" { // lets try one more time - time.Sleep(600 * time.Millisecond) + time.Sleep(time.Duration(B2D.Waittime) * time.Millisecond) fmt.Printf(" Trying to get Docker socket one more time\n") if socket, err = RequestSocketFromSSH(m); err != nil { @@ -127,27 +135,22 @@ func cmdUp() error { } // Copying the certs here - someone might have have written a Windows API client. certPath, err := RequestCertsUsingSSH(m) - if err != nil && B2D.Verbose { + if err != nil { // These errors are not fatal - fmt.Fprintf(os.Stderr, "Error copying Certificates: %s\n", err) + fmt.Fprintf(os.Stderr, "Warning: error copying certificates: %s\n", err) } - switch runtime.GOOS { - case "windows": - fmt.Printf("Docker client does not run on Windows for now. Please use\n") - fmt.Printf(" \"%s\" ssh\n", os.Args[0]) - fmt.Printf("to SSH into the VM instead.\n") - default: - if socket == "" { - fmt.Fprintf(os.Stderr, "Auto detection of the VM's Docker socket failed.\n") - fmt.Fprintf(os.Stderr, "Please run `boot2docker -v up` to diagnose.\n") + + if socket == "" { + fmt.Fprintf(os.Stderr, "Auto detection of the VM's Docker socket failed.\n") + fmt.Fprintf(os.Stderr, "Please run `boot2docker -v up` to diagnose.\n") + } else { + // Check if $DOCKER_* ENV vars are properly configured. + if !checkEnvironment(socket, certPath) { + fmt.Printf("\nTo connect the Docker client to the Docker daemon, please set:\n") + printExport(socket, certPath) + fmt.Printf("\nOr run: `eval \"$(boot2docker shellinit)\"`\n") } else { - // Check if $DOCKER_HOST ENV var is properly configured. - if os.Getenv("DOCKER_HOST") != socket || os.Getenv("DOCKER_CERT_PATH") != certPath { - fmt.Printf("\nTo connect the Docker client to the Docker daemon, please set:\n") - printExport(socket, certPath) - } else { - fmt.Printf("Your DOCKER_HOST env variable is already set correctly.\n") - } + fmt.Printf("Your environment variables are already set correctly.\n") } } fmt.Printf("\n") @@ -162,7 +165,7 @@ func cmdShellInit() error { } if m.GetState() != driver.Running { - return fmt.Errorf("VM %q is not running.", B2D.VM) + return vmNotRunningError(B2D.VM) } socket, err := RequestSocketFromSSH(m) @@ -171,25 +174,135 @@ func cmdShellInit() error { } certPath, err := RequestCertsUsingSSH(m) - if err != nil && B2D.Verbose { + if err != nil { // These errors are not fatal - fmt.Fprintf(os.Stderr, "Error copying Certificates: %s\n", err) + fmt.Fprintf(os.Stderr, "Warning: error copying certificates: %s\n", err) + } + + // Check if $DOCKER_* ENV vars are properly configured. + if !checkEnvironment(socket, certPath) { + printExport(socket, certPath) + } else { + fmt.Fprintf(os.Stderr, "Your environment variables are already set correctly.\n") } - printExport(socket, certPath) return nil } +func checkEnvironment(socket, certPath string) bool { + for name, value := range exports(socket, certPath) { + value = strings.Trim(value, `"'`) // remove surrounding quotes + if os.Getenv(name) != value { + return false + } + } + + return true +} + func printExport(socket, certPath string) { - fmt.Printf(" export DOCKER_HOST=%s\n", socket) - if certPath == "" { - if os.Getenv("DOCKER_CERT_PATH") != "" { - fmt.Println(" unset DOCKER_CERT_PATH") + values := exports(socket, certPath) + if runtime.GOOS == "windows" && !isUnixShellOnWindows() { + printExportWindows(values) + } else { + printExportUnix(values) + } +} + +func isUnixShellOnWindows() bool { + return os.Getenv("TERM") != "" +} + +func printExportUnix(values map[string]string) { + for name, value := range values { + switch filepath.Base(os.Getenv("SHELL")) { + case "fish": + if value == "" { + fmt.Printf(" set -e %s\n", name) + } else { + fmt.Printf(" set -x %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) + } + } + } +} + +func printExportWindows(values map[string]string) { + // Print cmd.exe instructions to stderr + fmt.Fprintln(os.Stderr, "If you are running inside Windows Command Prompt (cmd.exe), copy and paste the") + fmt.Fprintln(os.Stderr, "following commands to your terminal to set the environment variables:") + for name, value := range values { + if value == "" { + fmt.Fprintf(os.Stderr, " set %s=\n", name) + } else { + fmt.Fprintf(os.Stderr, " set %s=%s\n", name, value) + } + } + fmt.Fprintln(os.Stderr, "") + // Print powershell instructions to stderr + fmt.Fprintln(os.Stderr, "If you are running inside PowerShell, copy or paste the following commands") + fmt.Fprintln(os.Stderr, `to your shell or run "boot2docker shellinit | Invoke-Expression" to set the`) + fmt.Fprintln(os.Stderr, "environment variables:") + + // Print powershell exports to stdout + for name, value := range values { + if value == "" { + fmt.Printf(" Remove-Item Env:\\%s\n", name) + } else { + fmt.Printf(" $Env:%s = \"%s\"\n", name, value) } + } +} + +func exports(socket, certPath string) map[string]string { + out := make(map[string]string) + + out["DOCKER_HOST"] = socket + out["DOCKER_CERT_PATH"] = certPath + + if runtime.GOOS == "windows" && isUnixShellOnWindows() { + // Surround Windows-style paths with single quotes in exported path otherwise + // bash swallows the backslashes in export statements like: + // export DOCKER_CERT_PATH=C:\Users\ahmet\.boot2docker\certs\boot2docker-vm + out["DOCKER_CERT_PATH"] = fmt.Sprintf("'%s'", out["DOCKER_CERT_PATH"]) + } + + if certPath == "" { + out["DOCKER_TLS_VERIFY"] = "" } else { - // Assume Docker 1.2.0 with TLS on... - fmt.Printf(" export DOCKER_CERT_PATH=%s\n", certPath) + out["DOCKER_TLS_VERIFY"] = "1" + } + + //if a http_proxy is set, we need to make sure the boot2docker ip + //is added to the NO_PROXY environment variable + if os.Getenv("http_proxy") != "" || os.Getenv("HTTP_PROXY") != "" { + //get the ip from socket/DOCKER_HOST + re := regexp.MustCompile("tcp://([^:]+):") + if matches := re.FindStringSubmatch(socket); len(matches) == 2 { + ip := matches[1] + name := "no_proxy" + val := os.Getenv("no_proxy") + if val == "" { + name = "NO_PROXY" + val = os.Getenv("NO_PROXY") + } + + switch { + case val == "": + out[name] = ip + case strings.Contains(val, ip): + out[name] = val + default: + out[name] = fmt.Sprintf("%s,%s", val, ip) + } + } } + return out } // Tell the user the config (and later let them set it?) @@ -243,17 +356,109 @@ func cmdPoweroff() error { // Upgrade the boot2docker ISO - preserving server state func cmdUpgrade() error { + if err := upgradeBoot2DockerBinary(); err != nil { + return fmt.Errorf("Error upgrading boot2docker binary: %s", err) + } m, err := driver.GetMachine(&B2D) - if err == nil && m.GetState() == driver.Running { - // Windows won't let us move the ISO aside while it's in use - if cmdStop() == nil && cmdDownload() == nil { - return cmdUp() - } else { - return nil + if err == nil { + if m.GetState() == driver.Running || m.GetState() == driver.Saved || m.GetState() == driver.Paused { + // Windows won't let us move the ISO aside while it's in use + if err = cmdStop(); err == nil { + if err = cmdDownload(); err == nil { + err = cmdUp() + } + } + return err + } + } + return cmdDownload() +} + +func upgradeBoot2DockerBinary() error { + var ( + goos, arch, ext string + ) + latestVersion, err := getLatestReleaseName("https://api.github.com/repos/boot2docker/boot2docker-cli/releases") + if err != nil { + return fmt.Errorf("Error attempting to get the latest boot2docker-cli release: %s", err) + } + baseUrl := "https://github.com/boot2docker/boot2docker-cli/releases/download" + + ext = "" + + switch runtime.GOARCH { + case "amd64": + arch = "amd64" + default: + return fmt.Errorf("Architecture not supported") + } + + switch runtime.GOOS { + case "darwin", "linux": + goos = runtime.GOOS + case "windows": + goos = "windows" + arch = "amd64" + ext = ".exe" + default: + return fmt.Errorf("Operating system not supported") + } + binaryUrl := fmt.Sprintf("%s/%s/boot2docker-%s-%s-%s%s", baseUrl, latestVersion, latestVersion, goos, arch, ext) + currentBoot2DockerVersion := Version + if err := attemptUpgrade(binaryUrl, "boot2docker", latestVersion, currentBoot2DockerVersion); err != nil { + return fmt.Errorf("Error attempting upgrade: %s", err) + } + return nil +} + +func attemptUpgrade(binaryUrl, binaryName, latestVersion, localVersion string) error { + if (latestVersion != localVersion && !strings.Contains(latestVersion, "rc")) || B2D.ForceUpgradeDownload { + if err := backupAndDownload(binaryUrl, binaryName, localVersion); err != nil { + return fmt.Errorf("Error attempting backup and download of Docker client binary: %s", err) } } else { - return cmdDownload() + fmt.Printf("%s is up to date (%s), skipping upgrade...\n", binaryName, localVersion) + } + return nil +} + +func backupAndDownload(binaryUrl, binaryName, localVersion string) error { + binaryPath, err := exec.LookPath(binaryName) + if err != nil { + return fmt.Errorf("Error attempting to locate local binary: %s", err) + } + path := strings.TrimSpace(string(binaryPath)) + + fmt.Println("Backing up existing", binaryName, "binary...") + if err := backupBinary(binaryName, localVersion, path); err != nil { + return fmt.Errorf("Error backing up docker client: %s", err) + } + + fmt.Println("Downloading new", binaryName, "client binary...") + if err := download(path, binaryUrl); err != nil { + return fmt.Errorf("Error attempting to download new client binary: %s", err) + } + if err := os.Chmod(path, 0755); err != nil { + return err + } + fmt.Printf("Success: downloaded %s\n\tto %s\n\tThe old version is backed up to ~/.boot2docker.\n", binaryUrl, path) + return nil +} + +func backupBinary(binaryName, localVersion, path string) error { + dir, err := cfgDir(".boot2docker") + if err != nil { + return fmt.Errorf("Error getting boot2docker config dir: %s", err) + } + buf, err := ioutil.ReadFile(path) + if err != nil { + return fmt.Errorf("Error opening binary for reading at %s: %s", path, err) + } + backupName := fmt.Sprintf("%s-%s", binaryName, localVersion) + if err := ioutil.WriteFile(filepath.Join(dir, backupName), buf, 0755); err != nil { + return fmt.Errorf("Error creating backup file: %s", err) } + return nil } // Gracefully stop and then start the VM. @@ -301,9 +506,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 } @@ -317,29 +526,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) @@ -348,7 +534,7 @@ func cmdSSH() error { } if m.GetState() != driver.Running { - return fmt.Errorf("VM %q is not running.", B2D.VM) + return vmNotRunningError(B2D.VM) } // find the ssh cmd string and then pass any remaining strings to ssh @@ -372,7 +558,7 @@ func cmdIP() error { } if m.GetState() != driver.Running { - return fmt.Errorf("VM %q is not running.", B2D.VM) + return vmNotRunningError(B2D.VM) } IP := "" @@ -394,27 +580,38 @@ func cmdIP() error { } } if IP != "" { - fmt.Fprintf(os.Stderr, "\nThe VM's Host only interface IP address is: ") - fmt.Printf("%s", IP) - fmt.Fprintf(os.Stderr, "\n\n") + fmt.Println(IP) } else { fmt.Fprintf(os.Stderr, "\nFailed to get VM Host only IP address.\n") - fmt.Fprintf(os.Stderr, "\tWas the VM initilized using boot2docker?\n") + fmt.Fprintf(os.Stderr, "\tWas the VM initialized using boot2docker?\n") } return nil } // Download the boot2docker ISO image. func cmdDownload() error { - fmt.Println("Downloading boot2docker ISO image...") - url := "https://api.github.com/repos/boot2docker/boot2docker/releases" - tag, err := getLatestReleaseName(url) - if err != nil { - return fmt.Errorf("Failed to get latest release: %s", err) + url := B2D.ISOURL + + // match github (enterprise) release urls: + // https://api.github.com/repos/../../relases or + // https://some.github.enterprise/api/v3/repos/../../relases + re := regexp.MustCompile("https://([^/]+)(/api/v3)?/repos/([^/]+)/([^/]+)/releases") + if matches := re.FindStringSubmatch(url); len(matches) == 5 { + tag, err := getLatestReleaseName(url) + if err != nil { + return fmt.Errorf("Failed to get latest release: %s", err) + } + host := matches[1] + org := matches[3] + repo := matches[4] + if host == "api.github.com" { + host = "github.com" + } + fmt.Printf("Latest release for %s/%s/%s is %s\n", host, org, repo, tag) + url = fmt.Sprintf("https://%s/%s/%s/releases/download/%s/boot2docker.iso", host, org, repo, tag) } - fmt.Printf("Latest release is %s\n", tag) - url = fmt.Sprintf("https://github.com/boot2docker/boot2docker/releases/download/%s/boot2docker.iso", tag) + fmt.Println("Downloading boot2docker ISO image...") if err := download(B2D.ISO, url); err != nil { return fmt.Errorf("Failed to download ISO image: %s", err) } diff --git a/config.go b/config.go index 99db276..d29b324 100644 --- a/config.go +++ b/config.go @@ -22,11 +22,10 @@ var ( func homeDir() (string, error) { dir := "" - - // *nix and MSYS Windows - if dir = os.Getenv("HOME"); dir == "" { - // Windows (if not running under MSYS) + if runtime.GOOS == "windows" { dir = os.Getenv("USERPROFILE") + } else { + dir = os.Getenv("HOME") } if _, err := os.Stat(dir); err != nil { return "", err @@ -91,8 +90,16 @@ func config() (*flag.FlagSet, error) { // removed for now, requires re-parsing a new config file which is too messy //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.") + // clobber (overwrite client binary) by default on OSX. it's more likely that + // users have installed through package manager on Linux, and if so, they should + // upgrade that way. + flags.BoolVar(&B2D.Clobber, "clobber", (runtime.GOOS == "darwin"), "overwrite Docker client binary on boot2docker upgrade") + + flags.BoolVar(&B2D.ForceUpgradeDownload, "force-upgrade-download", false, "always download on boot2docker upgrade, never skip") + // 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 // which cannot run, because it fails to have have the boot2docker.iso and the ssh keys @@ -108,8 +115,9 @@ func config() (*flag.FlagSet, error) { flags.StringVar(&B2D.SSHKey, "sshkey", filepath.Join(sshdir, "id_boot2docker"), "path to SSH key to use.") flags.UintVarP(&B2D.DiskSize, "disksize", "s", 20000, "boot2docker disk image size (in MB).") flags.UintVarP(&B2D.Memory, "memory", "m", 2048, "virtual machine memory size (in MB).") + flags.UintVarP(&B2D.CPUs, "cpus", "c", uint(runtime.NumCPU()), "number of CPUs for boot2docker.") flags.Uint16Var(&B2D.SSHPort, "sshport", 2022, "host SSH port (forward to port 22 in VM).") - flags.Uint16Var(&B2D.DockerPort, "dockerport", 0, "host Docker port (forward to port 2375 in VM). (deprecated - use with care)") + flags.Uint16Var(&B2D.DockerPort, "dockerport", 0, "host Docker port (forward to port 2376 in VM). (deprecated - use with care)") flags.IPVar(&B2D.HostIP, "hostip", net.ParseIP("192.168.59.3"), "VirtualBox host-only network IP address.") flags.IPMaskVar(&B2D.NetMask, "netmask", flag.ParseIPv4Mask("255.255.255.0"), "VirtualBox host-only network mask.") flags.BoolVar(&B2D.DHCPEnabled, "dhcp", true, "enable VirtualBox host-only network DHCP.") @@ -117,6 +125,9 @@ func config() (*flag.FlagSet, error) { flags.IPVar(&B2D.LowerIP, "lowerip", net.ParseIP("192.168.59.103"), "VirtualBox host-only network DHCP lower bound.") flags.IPVar(&B2D.UpperIP, "upperip", net.ParseIP("192.168.59.254"), "VirtualBox host-only network DHCP upper bound.") + flags.IntVar(&B2D.Waittime, "waittime", 300, "Time in milliseconds to wait between port knocking retries during 'start'") + flags.IntVar(&B2D.Retries, "retries", 75, "number of port knocking retries during 'start'") + if runtime.GOOS != "windows" { //SerialFile ~~ filepath.Join(dir, B2D.vm+".sock") flags.StringVar(&B2D.SerialFile, "serialfile", "", "path to the serial socket/pipe.") @@ -157,11 +168,18 @@ func config() (*flag.FlagSet, error) { return nil, err } + leftovers := flags.Args() + + if B2D.Verbose || (len(leftovers) > 0 && leftovers[0] == "version") { + fmt.Printf("Boot2Docker-cli version: %s\nGit commit: %s\n", Version, GitSHA) + } + return flags, nil } func usageShort() { - fmt.Fprintf(os.Stderr, "Usage: %s [] {help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|ip|socket|shellinit|delete|destroy|download|version} []\n", os.Args[0]) + binName := filepath.Base(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", binName) } func usageLong(flags *flag.FlagSet) { @@ -183,8 +201,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/docs/images/bad_host.png b/docs/images/bad_host.png new file mode 100644 index 0000000..cdc78de Binary files /dev/null and b/docs/images/bad_host.png differ diff --git a/docs/images/cool_view.png b/docs/images/cool_view.png new file mode 100644 index 0000000..8eebf21 Binary files /dev/null and b/docs/images/cool_view.png differ diff --git a/docs/images/good_host.png b/docs/images/good_host.png new file mode 100644 index 0000000..58b9783 Binary files /dev/null and b/docs/images/good_host.png differ diff --git a/docs/images/kitematic.png b/docs/images/kitematic.png new file mode 100644 index 0000000..5bb221c Binary files /dev/null and b/docs/images/kitematic.png differ diff --git a/docs/images/newsite_view.png b/docs/images/newsite_view.png new file mode 100644 index 0000000..27b6b1a Binary files /dev/null and b/docs/images/newsite_view.png differ diff --git a/docs/images/windows-boot2docker-cmd.png b/docs/images/windows-boot2docker-cmd.png new file mode 100644 index 0000000..09e3206 Binary files /dev/null and b/docs/images/windows-boot2docker-cmd.png differ diff --git a/docs/images/windows-boot2docker-powershell.png b/docs/images/windows-boot2docker-powershell.png new file mode 100644 index 0000000..b1ef896 Binary files /dev/null and b/docs/images/windows-boot2docker-powershell.png differ diff --git a/docs/images/windows-boot2docker-start.png b/docs/images/windows-boot2docker-start.png new file mode 100644 index 0000000..5e3563b Binary files /dev/null and b/docs/images/windows-boot2docker-start.png differ diff --git a/docs/images/windows-installer.png b/docs/images/windows-installer.png new file mode 100644 index 0000000..7580ed6 Binary files /dev/null and b/docs/images/windows-installer.png differ diff --git a/docs/mac.md b/docs/mac.md new file mode 100644 index 0000000..ee5fcaa --- /dev/null +++ b/docs/mac.md @@ -0,0 +1,358 @@ + + +# Mac OS X + +You can install Docker using Boot2Docker to run `docker` commands at your command-line. +Choose this installation if you are familiar with the command-line or plan to +contribute to the Docker project on GitHub. + +[Download Kitematic](https://kitematic.com/download) + +Alternatively, you may want to try Kitematic, an application that lets you set up Docker and +run containers using a graphical user interface (GUI). + +## Command-line Docker with Boot2Docker + +Because the Docker daemon uses Linux-specific kernel features, you can't run +Docker natively in OS X. Instead, you must install the Boot2Docker application. +The application includes a VirtualBox Virtual Machine (VM), Docker itself, and the +Boot2Docker management tool. + +The Boot2Docker management tool is a lightweight Linux virtual machine made +specifically to run the Docker daemon on Mac OS X. The VirtualBox VM runs +completely from RAM, is a small ~24MB download, and boots in approximately 5s. + +**Requirements** + +Your Mac must be running OS X 10.6 "Snow Leopard" or newer to run Boot2Docker. + +### Learn the key concepts before installing + +In a Docker installation on Linux, your machine is both the localhost and the +Docker host. In networking, localhost means your computer. The Docker host is +the machine on which the containers run. + +On a typical Linux installation, the Docker client, the Docker daemon, and any +containers run directly on your localhost. This means you can address ports on a +Docker container using standard localhost addressing such as `localhost:8000` or +`0.0.0.0:8376`. + +![Linux Architecture Diagram](/installation/images/linux_docker_host.svg) + +In an OS X installation, the `docker` daemon is running inside a Linux virtual +machine provided by Boot2Docker. + +![OSX Architecture Diagram](/installation/images/mac_docker_host.svg) + +In OS X, the Docker host address is the address of the Linux VM. +When you start the `boot2docker` process, the VM is assigned an IP address. Under +`boot2docker` ports on a container map to ports on the VM. To see this in +practice, work through the exercises on this page. + + +### Installation + +1. Go to the [boot2docker/osx-installer ]( + https://github.com/boot2docker/osx-installer/releases/latest) release page. + +4. Download Boot2Docker by clicking `Boot2Docker-x.x.x.pkg` in the "Downloads" + section. + +3. Install Boot2Docker by double-clicking the package. + + The installer places Boot2Docker and VirtualBox in your "Applications" folder. + +The installation places the `docker` and `boot2docker` binaries in your +`/usr/local/bin` directory. + + +## Start the Boot2Docker Application + +To run a Docker container, you first start the `boot2docker` VM and then issue +`docker` commands to create, load, and manage containers. You can launch +`boot2docker` from your Applications folder or from the command line. + +> **NOTE**: Boot2Docker is designed as a development tool. You should not use +> it in production environments. + +### From the Applications folder + +When you launch the "Boot2Docker" application from your "Applications" folder, the +application: + +* opens a terminal window + +* creates a $HOME/.boot2docker directory + +* creates a VirtualBox ISO and certs + +* starts a VirtualBox VM running the `docker` daemon + +Once the launch completes, you can run `docker` commands. A good way to verify +your setup succeeded is to run the `hello-world` container. + + $ docker run hello-world + Unable to find image 'hello-world:latest' locally + 511136ea3c5a: Pull complete + 31cbccb51277: Pull complete + e45a5af57b00: Pull complete + hello-world:latest: The image you are pulling has been verified. + Important: image verification is a tech preview feature and should not be + relied on to provide security. + Status: Downloaded newer image for hello-world:latest + Hello from Docker. + This message shows that your installation appears to be working correctly. + + To generate this message, Docker took the following steps: + 1. The Docker client contacted the Docker daemon. + 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. + (Assuming it was not already locally available.) + 3. The Docker daemon created a new container from that image which runs the + executable that produces the output you are currently reading. + 4. The Docker daemon streamed that output to the Docker client, which sent it + to your terminal. + + To try something more ambitious, you can run an Ubuntu container with: + $ docker run -it ubuntu bash + + For more examples and ideas, visit: + http://docs.docker.com/userguide/ + + +A more typical way to start and stop `boot2docker` is using the command line. + +### From your command line + +Initialize and run `boot2docker` from the command line, do the following: + +1. Create a new Boot2Docker VM. + + $ boot2docker init + + This creates a new virtual machine. You only need to run this command once. + +2. Start the `boot2docker` VM. + + $ boot2docker start + +3. Display the environment variables for the Docker client. + + $ boot2docker shellinit + Writing /Users/mary/.boot2docker/certs/boot2docker-vm/ca.pem + Writing /Users/mary/.boot2docker/certs/boot2docker-vm/cert.pem + Writing /Users/mary/.boot2docker/certs/boot2docker-vm/key.pem + export DOCKER_HOST=tcp://192.168.59.103:2376 + export DOCKER_CERT_PATH=/Users/mary/.boot2docker/certs/boot2docker-vm + export DOCKER_TLS_VERIFY=1 + + The specific paths and address on your machine will be different. + +4. To set the environment variables in your shell do the following: + + $ eval "$(boot2docker shellinit)" + + You can also set them manually by using the `export` commands `boot2docker` + returns. + +5. Run the `hello-world` container to verify your setup. + + $ docker run hello-world + + +## Basic Boot2Docker exercises + +At this point, you should have `boot2docker` running and the `docker` client +environment initialized. To verify this, run the following commands: + + $ boot2docker status + $ docker version + +Work through this section to try some practical container tasks using `boot2docker` VM. + +### Access container ports + +1. Start an NGINX container on the DOCKER_HOST. + + $ docker run -d -P --name web nginx + + Normally, the `docker run` commands starts a container, runs it, and then + exits. The `-d` flag keeps the container running in the background + after the `docker run` command completes. The `-P` flag publishes exposed ports from the + container to your local host; this lets you access them from your Mac. + +2. Display your running container with `docker ps` command + + CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES + 5fb65ff765e9 nginx:latest "nginx -g 'daemon of 3 minutes ago Up 3 minutes 0.0.0.0:49156->443/tcp, 0.0.0.0:49157->80/tcp web + + At this point, you can see `nginx` is running as a daemon. + +3. View just the container's ports. + + $ docker port web + 443/tcp -> 0.0.0.0:49156 + 80/tcp -> 0.0.0.0:49157 + + This tells you that the `web` container's port `80` is mapped to port + `49157` on your Docker host. + +4. Enter the `http://localhost:49157` address (`localhost` is `0.0.0.0`) in your browser: + + ![Bad Address](/installation/images/bad_host.png) + + This didn't work. The reason it doesn't work is your `DOCKER_HOST` address is + not the localhost address (0.0.0.0) but is instead the address of the + `boot2docker` VM. + +5. Get the address of the `boot2docker` VM. + + $ boot2docker ip + 192.168.59.103 + +6. Enter the `http://192.168.59.103:49157` address in your browser: + + ![Correct Addressing](/installation/images/good_host.png) + + Success! + +7. To stop and then remove your running `nginx` container, do the following: + + $ docker stop web + $ docker rm web + +### Mount a volume on the container + +When you start `boot2docker`, it automatically shares your `/Users` directory +with the VM. You can use this share point to mount directories onto your container. +The next exercise demonstrates how to do this. + +1. Change to your user `$HOME` directory. + + $ cd $HOME + +2. Make a new `site` directory. + + $ mkdir site + +3. Change into the `site` directory. + + $ cd site + +4. Create a new `index.html` file. + + $ echo "my new site" > index.html + +5. Start a new `nginx` container and replace the `html` folder with your `site` directory. + + $ docker run -d -P -v $HOME/site:/usr/share/nginx/html --name mysite nginx + +6. Get the `mysite` container's port. + + $ docker port mysite + 80/tcp -> 0.0.0.0:49166 + 443/tcp -> 0.0.0.0:49165 + +7. Open the site in a browser: + + ![My site page](/installation/images/newsite_view.png) + +8. Try adding a page to your `$HOME/site` in real time. + + $ echo "This is cool" > cool.html + +9. Open the new page in the browser. + + ![Cool page](/installation/images/cool_view.png) + +9. Stop and then remove your running `mysite` container. + + $ docker stop mysite + $ docker rm mysite + +## Upgrade Boot2Docker + +If you running Boot2Docker 1.4.1 or greater, you can upgrade Boot2Docker from +the command line. If you are running an older version, you should use the +package provided by the `boot2docker` repository. + +### From the command line + +To upgrade from 1.4.1 or greater, you can do this: + +1. Open a terminal on your local machine. + +2. Stop the `boot2docker` application. + + $ boot2docker stop + +3. Run the upgrade command. + + $ boot2docker upgrade + + +### Use the installer + +To upgrade any version of Boot2Docker, do this: + +1. Open a terminal on your local machine. + +2. Stop the `boot2docker` application. + + $ boot2docker stop + +3. Go to the [boot2docker/osx-installer ]( + https://github.com/boot2docker/osx-installer/releases/latest) release page. + +4. Download Boot2Docker by clicking `Boot2Docker-x.x.x.pkg` in the "Downloads" + section. + +2. Install Boot2Docker by double-clicking the package. + + The installer places Boot2Docker in your "Applications" folder. + + +## Uninstallation + +1. Go to the [boot2docker/osx-installer ]( + https://github.com/boot2docker/osx-installer/releases/latest) release page. + +2. Download the source code by clicking `Source code (zip)` or + `Source code (tar.gz)` in the "Downloads" section. + +3. Extract the source code. + +4. Open a terminal on your local machine. + +5. Change to the directory where you extracted the source code: + + $ cd + +6. Make sure the uninstall.sh script is executable: + + $ chmod +x uninstall.sh + +7. Run the uninstall.sh script: + + $ ./uninstall.sh + + +## Learning more and acknowledgement + +Use `boot2docker help` to list the full command line reference. For more +information about using SSH or SCP to access the Boot2Docker VM, see the README +at [Boot2Docker repository](https://github.com/boot2docker/boot2docker). + +Thanks to Chris Jones whose [blog](http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide) +inspired me to redo this page. + +Continue with the [Docker User Guide](/userguide). diff --git a/docs/windows.md b/docs/windows.md new file mode 100644 index 0000000..974e687 --- /dev/null +++ b/docs/windows.md @@ -0,0 +1,172 @@ + + +# Windows +> **Note:** +> Docker has been tested on Windows 7 and 8.1; it may also run on older versions. +> Your processor needs to support hardware virtualization. + +The Docker Engine uses Linux-specific kernel features, so to run it on Windows +we need to use a lightweight virtual machine (VM). You use the **Windows Docker +Client** to control the virtualized Docker Engine to build, run, and manage +Docker containers. + +To make this process easier, we've designed a helper application called +[Boot2Docker](https://github.com/boot2docker/boot2docker) creates a Linux virtual +machine on Windows to run Docker on a Linux operating system. + +Although you will be using Windows Docker client, the docker engine hosting the +containers will still be running on Linux. Until the Docker engine for Windows +is developed, you can launch only Linux containers from your Windows machine. + +![Windows Architecture Diagram](/installation/images/win_docker_host.svg) + +## Demonstration + + + +## Installation + +1. Download the latest release of the + [Docker for Windows Installer](https://github.com/boot2docker/windows-installer/releases/latest). +2. Run the installer, which will install Docker Client for Windows, VirtualBox, + Git for Windows (MSYS-git), the boot2docker Linux ISO, and the Boot2Docker + management tool. + ![](/installation/images/windows-installer.png) +3. Run the **Boot2Docker Start** shortcut from your Desktop or “Program Files → + Boot2Docker for Windows”. + The Start script will ask you to enter an ssh key passphrase - the simplest + (but least secure) is to just hit [Enter]. + +4. The **Boot2Docker Start** will start a unix shell already configured to manage + Docker running inside the virtual machine. Run `docker version` to see + if it is working correctly: + +![](/installation/images/windows-boot2docker-start.png) + +## Running Docker + +> **Note:** if you are using a remote Docker daemon, such as Boot2Docker, +> then _do not_ type the `sudo` before the `docker` commands shown in the +> documentation's examples. + +**Boot2Docker Start** will automatically start a shell with environment variables +correctly set so you can start using Docker right away: + +Let's try the `hello-world` example image. Run + + $ docker run hello-world + +This should download the very small `hello-world` image and print a +`Hello from Docker.` message. + +## Using Docker from Windows Command Line Prompt (cmd.exe) + +Launch a Windows Command Line Prompt (cmd.exe). + +Boot2Docker command requires `ssh.exe` to be in the PATH, therefore we need to +include `bin` folder of the Git installation (which has ssh.exe) to the `%PATH%` +environment variable by running: + + set PATH=%PATH%;"c:\Program Files (x86)\Git\bin" + +and then we can run the `boot2docker start` command to start the Boot2Docker VM. +(Run `boot2docker init` command if you get an error saying machine does not +exist.) Then copy the instructions for cmd.exe to set the environment variables +to your console window and you are ready to run docker commands such as +`docker ps`: + +![](/installation/images/windows-boot2docker-cmd.png) + +## Using Docker from PowerShell + +Launch a PowerShell window, then you need to add `ssh.exe` to your PATH: + + $Env:Path = "${Env:Path};c:\Program Files (x86)\Git\bin" + +and after running `boot2docker start` command it will print PowerShell commands +to set the environment variables to connect Docker running inside VM. Run these +commands and you are ready to run docker commands such as `docker ps`: + +![](/installation/images/windows-boot2docker-powershell.png) + +> NOTE: You can alternatively run `boot2docker shellinit | Invoke-Expression` +> command to set the environment variables instead of copying and pasting on +> PowerShell. + +# Further Details + +The Boot2Docker management tool provides several commands: + + $ boot2docker + Usage: boot2docker.exe [] {help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|ip|shellinit|delete|download|upgrade|version} [] + +## Upgrading + +1. Download the latest release of the [Docker for Windows Installer]( + https://github.com/boot2docker/windows-installer/releases/latest) + +2. Run the installer, which will update the Boot2Docker management tool. + +3. To upgrade your existing virtual machine, open a terminal and run: + + boot2docker stop + boot2docker download + boot2docker start + +## Container port redirection + +If you are curious, the username for the boot2docker default user is `docker` +and the password is `tcuser`. + +The latest version of `boot2docker` sets up a host only network adaptor which +provides access to the container's ports. + +If you run a container with an exposed port: + + docker run --rm -i -t -p 80:80 nginx + +Then you should be able to access that nginx server using the IP address reported +to you using: + + boot2docker ip + +Typically, it is 192.168.59.103, but it could get changed by Virtualbox's DHCP +implementation. + +For further information or to report issues, please see the [Boot2Docker site](http://boot2docker.io) + +## Login with PUTTY instead of using the CMD + +Boot2Docker generates and uses the public/private key pair in your `%USERPROFILE%\.ssh` +directory so to log in you need to use the private key from this same directory. + +The private key needs to be converted into the format PuTTY uses. + +You can do this with +[puttygen](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html): + +- Open `puttygen.exe` and load ("File"->"Load" menu) the private key from + `%USERPROFILE%\.ssh\id_boot2docker` +- then click: "Save Private Key". +- Then use the saved file to login with PuTTY using `docker@127.0.0.1:2022`. + +## Uninstallation + +You can uninstall Boot2Docker using Window's standard process for removing programs. +This process does not remove the `docker-install.exe` file. You must delete that file +yourself. + +## References + +If you have Docker hosts running and if you don't wish to do a +Boot2Docker installation, you can install the docker.exe using +unofficial Windows package manager Chocolately. For information +on how to do this, see [Docker package on Chocolatey](http://chocolatey.org/packages/docker). diff --git a/driver/config.go b/driver/config.go index 5d11167..b3841e5 100644 --- a/driver/config.go +++ b/driver/config.go @@ -2,8 +2,9 @@ package driver import ( "fmt" - flag "github.com/ogier/pflag" "net" + + flag "github.com/ogier/pflag" ) // Machine config. @@ -14,18 +15,22 @@ type MachineConfig struct { Driver string // basic config - SSH string // SSH client executable - SSHGen string // SSH keygen executable - SSHKey string // SSH key to send to the vm - VM string // virtual machine name - Dir string // boot2docker directory - ISO string // boot2docker ISO image path - DiskSize uint // VM disk image size (MB) - Memory uint // VM memory size (MB) + Clobber bool + ForceUpgradeDownload bool + SSH string // SSH client executable + SSHGen string // SSH keygen executable + SSHKey string // SSH key to send to the vm + VM string // virtual machine name + Dir string // boot2docker directory + ISOURL string // Source URL to retrieve the ISO from + ISO string // boot2docker ISO image path + DiskSize uint // VM disk image size (MB) + Memory uint // VM memory size (MB) + CPUs uint // Number of CPUs // NAT network: port forwarding SSHPort uint16 // host SSH port (forward to port 22 in VM) - DockerPort uint16 // host Docker port (forward to port 2375 in VM) + DockerPort uint16 // host Docker port (forward to port 2376 in VM) // host-only network HostIP net.IP @@ -39,6 +44,10 @@ type MachineConfig struct { Serial bool SerialFile string + // boot2docker init retry settings + Waittime int + Retries int + DriverCfg map[string]interface{} } diff --git a/driver/driver.go b/driver/driver.go index 1111fc9..1efa0b9 100644 --- a/driver/driver.go +++ b/driver/driver.go @@ -12,7 +12,7 @@ type MachineState string const ( // Known ports SSHPort = 22 - DockerPort = 2375 + DockerPort = 2376 // VM states Poweroff = MachineState("poweroff") @@ -51,8 +51,9 @@ var ( // All registred machines machines map[string]InitFunc - ErrNotSupported = errors.New("machine not supported") - ErrMachineNotExist = errors.New("machine not exist") + ErrNotSupported = errors.New("driver not supported") + ErrMachineNotExist = errors.New("machine does not exist (Did you run `boot2docker init`?)") + ErrMachineExist = errors.New("machine already exists") ErrPrerequisites = errors.New("prerequisites for machine not satisfied (hypervisor installed?)") ) diff --git a/main.go b/main.go index 3594d8e..48416ca 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,8 @@ package main import ( "fmt" + "io/ioutil" + "net/http" "os" ) @@ -11,6 +13,17 @@ var ( GitSHA string ) +const ( + hardcodedWarning = ` + WARNING: The 'boot2docker' command line interface is officially deprecated. + + Please switch to Docker Machine (https://docs.docker.com/machine/) ASAP. + + Docker Toolbox (https://docker.com/toolbox) is the recommended install method. +` + warningURL = "https://raw.githubusercontent.com/boot2docker/boot2docker-cli/master/DEPRECATION_WARNING" +) + type unknownCommandError struct { cmd string } @@ -48,8 +61,10 @@ func run() error { case "config", "cfg": return cmdConfig() case "init": + printDeprecationWarning() return cmdInit() case "up", "start", "boot", "resume": + printDeprecationWarning() return cmdUp() case "save", "suspend": return cmdSave() @@ -65,9 +80,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() @@ -78,7 +91,7 @@ func run() error { case "upgrade": return cmdUpgrade() case "version": - fmt.Printf("Client version: %s\nGit commit: %s\n", Version, GitSHA) + // Version is now printed by the call to config() return nil case "help": flags.Usage() @@ -90,3 +103,27 @@ func run() error { return unknownCommandError{cmd: cmd} } } + +func printDeprecationWarning() { + var ( + warning string + ) + + // Try to get the warning from the Github raw URL. If there's any + // failure along the way, e.g. network, just fall back to the default + // warning hardcoded in the source. + resp, err := http.Get(warningURL) + if err != nil || resp.StatusCode != http.StatusOK { + warning = hardcodedWarning + } else { + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + warning = hardcodedWarning + } else { + warning = string(body) + } + } + + fmt.Fprintln(os.Stderr, warning) +} diff --git a/util.go b/util.go index 19f245f..869afd6 100644 --- a/util.go +++ b/util.go @@ -21,12 +21,17 @@ import ( "github.com/boot2docker/boot2docker-cli/driver" ) +var ( + // We're looking to get e.g. "1.2.0" from "Docker version 1.2.0, build fa7b24f" + versionRe = regexp.MustCompile(`(\d+\.?){3}`) +) + // Try if addr tcp://addr is readable for n times at wait interval. func read(addr string, n int, wait time.Duration) error { var lastErr error for i := 0; i < n; i++ { if B2D.Verbose { - fmt.Printf("Connecting to tcp://%v (attempt #%d)", addr, i) + fmt.Printf("Connecting to tcp://%v (attempt #%d)\n", addr, i) } conn, err := net.DialTimeout("tcp", addr, 1*time.Second) if err != nil { @@ -105,7 +110,12 @@ func getLatestReleaseName(url string) (string, error) { defer rsp.Body.Close() var t []struct { - TagName string `json:"tag_name"` + // ".../tags" endpoints + Name string `json:"name"` + + // ".../releases" endpoints + TagName string `json:"tag_name"` + Prerelease bool `json:"prerelease"` } body, err := ioutil.ReadAll(rsp.Body) if err != nil { @@ -123,9 +133,35 @@ func getLatestReleaseName(url string) (string, error) { return "", fmt.Errorf("Error getting releases: %s\n see %s", e.Message, e.DocumentationUrl) } if len(t) == 0 { - return "", fmt.Errorf("no releases found") + return "", fmt.Errorf("no releases found at %q", url) + } + + // Looking up by tag instead of release. + // Github API call for docker releases yields nothing, + // so we use tags API call in this case. + if strings.Contains(url, "tags") { + return t[0].Name, nil + } + + for _, rel := range t { + if rel.Prerelease { + // skip "pre-releases" (RCs, etc) entirely + continue + } + return rel.TagName, nil } - return t[0].TagName, nil + + return "", fmt.Errorf("no non-prerelease releases found at %q", url) +} + +func getLocalClientVersion() (string, error) { + versionOutput, err := exec.Command("docker", "-v").Output() + if err != nil { + return "", err + } + versionNumber := versionRe.FindString(string(versionOutput)) + + return versionNumber, nil } func cmdInteractive(m driver.Machine, args ...string) error { @@ -310,11 +346,13 @@ func RequestCertsUsingSSH(m driver.Machine) (string, error) { b, err := cmd.Output() if err == nil { - dir, err := cfgDir(".docker") + dir, err := cfgDir(".boot2docker") if err != nil { return "", err } + certDir = filepath.Join(dir, "certs", m.GetName()) + // Open the tar archive for reading. r := bytes.NewReader(b) tr := tar.NewReader(r) @@ -330,12 +368,11 @@ func RequestCertsUsingSSH(m driver.Machine) (string, error) { return "", err } filename := filepath.Base(hdr.Name) - certDir = filepath.Join(dir, m.GetName()) if err := os.MkdirAll(certDir, 0755); err != nil { return "", err } certFile := filepath.Join(certDir, filename) - fmt.Printf("Writing %s:\n", certFile) + fmt.Fprintf(os.Stderr, "Writing %s\n", certFile) f, err := os.Create(certFile) if err != nil { return "", err diff --git a/virtualbox/hostonlynet.go b/virtualbox/hostonlynet.go index 305c245..6e25699 100644 --- a/virtualbox/hostonlynet.go +++ b/virtualbox/hostonlynet.go @@ -100,7 +100,7 @@ func HostonlyNets() (map[string]*HostonlyNet, error) { case "IPV6Address": n.IPv6.IP = net.ParseIP(val) case "IPV6NetworkMaskPrefixLength": - l, err := strconv.ParseUint(val, 10, 7) + l, err := strconv.ParseUint(val, 10, 8) if err != nil { return nil, err } diff --git a/virtualbox/machine.go b/virtualbox/machine.go index ff5b8d4..acbd146 100644 --- a/virtualbox/machine.go +++ b/virtualbox/machine.go @@ -28,7 +28,6 @@ const ( F_cpuhotplug F_pae F_longmode - F_synthcpu F_hpet F_hwvirtex F_triplefaultreset @@ -139,7 +138,7 @@ func ConfigFlags(B2D *driver.MachineConfig, flags *flag.FlagSet) error { if shareDefault != "disable" { defaultText = "(defaults to '" + shareDefault + "' if no shares are specified; use 'disable' to explicitly prevent any shares from being created) " } - flags.Var(&cfg.shares, "vbox-share", fmt.Sprintf("%sList of directories to share during 'init' via VirtualBox Guest Additions, with optional labels", defaultText)) + flags.Var(&cfg.shares, "vbox-share", fmt.Sprintf("%sList of directories to share during 'up|start|boot' via VirtualBox Guest Additions, with optional labels", defaultText)) return nil } @@ -161,6 +160,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) @@ -194,7 +194,12 @@ 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 { @@ -238,7 +243,8 @@ func (m *Machine) Stop() error { } } - for m.State != driver.Poweroff { // busy wait until the machine is stopped + // busy wait until the machine is stopped + for i := 0; i < 10; i++ { if err := vbm("controlvm", m.Name, "acpipowerbutton"); err != nil { return err } @@ -246,8 +252,12 @@ func (m *Machine) Stop() error { if err := m.Refresh(); err != nil { return err } + if m.State == driver.Poweroff { + return nil + } } - return nil + + return fmt.Errorf("timed out waiting for VM to stop") } // Poweroff forcefully stops the machine. State is lost and might corrupt the disk image. @@ -322,7 +332,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 } @@ -347,6 +357,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": @@ -435,7 +447,7 @@ func CreateMachine(mc *driver.MachineConfig) (*Machine, error) { } for _, m := range machineNames { if m == mc.VM { - return nil, ErrMachineExist + return nil, driver.ErrMachineExist } } @@ -453,7 +465,11 @@ func CreateMachine(mc *driver.MachineConfig) (*Machine, error) { // Configure VM for Boot2docker SetExtra(mc.VM, "VBoxInternal/CPUM/EnableHVP", "1") m.OSType = "Linux26_64" - m.CPUs = uint(runtime.NumCPU()) + if mc.CPUs > 0 { + m.CPUs = mc.CPUs + } else { + m.CPUs = uint(runtime.NumCPU()) + } if m.CPUs > 32 { m.CPUs = 32 } @@ -503,7 +519,7 @@ func CreateMachine(mc *driver.MachineConfig) (*Machine, error) { } // Set VM storage - if err := m.AddStorageCtl("SATA", driver.StorageController{SysBus: driver.SysBusSATA, HostIOCache: true, Bootable: true}); err != nil { + if err := m.AddStorageCtl("SATA", driver.StorageController{SysBus: driver.SysBusSATA, HostIOCache: true, Bootable: true, Ports: 4}); err != nil { return m, err } @@ -577,12 +593,16 @@ func CreateMachine(mc *driver.MachineConfig) (*Machine, error) { return m, err } + return m, nil +} + +func (m *Machine) setUpShares() error { // let VBoxService do nice magic automounting (when it's used) - if err := vbm("guestproperty", "set", mc.VM, "/VirtualBox/GuestAdd/SharedFolders/MountPrefix", "/"); err != nil { - return nil, err + if err := vbm("guestproperty", "set", m.Name, "/VirtualBox/GuestAdd/SharedFolders/MountPrefix", "/"); err != nil { + return err } - if err := vbm("guestproperty", "set", mc.VM, "/VirtualBox/GuestAdd/SharedFolders/MountDir", "/"); err != nil { - return nil, err + if err := vbm("guestproperty", "set", m.Name, "/VirtualBox/GuestAdd/SharedFolders/MountDir", "/"); err != nil { + return err } // set up some shared folders as appropriate @@ -594,21 +614,20 @@ func CreateMachine(mc *driver.MachineConfig) (*Machine, error) { continue } if _, err := os.Stat(shareDir); err != nil { - return nil, err + return err } // woo, shareDir exists! let's carry on! - if err := vbm("sharedfolder", "add", mc.VM, "--name", shareName, "--hostpath", shareDir, "--automount"); err != nil { - return nil, err + if err := vbm("sharedfolder", "add", m.Name, "--name", shareName, "--hostpath", shareDir, "--automount"); err != nil { + return err } // enable symlinks - if err := vbm("setextradata", mc.VM, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/"+shareName, "1"); err != nil { - return nil, err + if err := vbm("setextradata", m.Name, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/"+shareName, "1"); err != nil { + return err } } - - return m, nil + return nil } // Modify changes the settings of the machine. @@ -617,10 +636,16 @@ func (m *Machine) Modify() error { "--firmware", "bios", "--bioslogofadein", "off", "--bioslogofadeout", "off", - "--natdnshostresolver1", "on", "--bioslogodisplaytime", "0", "--biosbootmenu", "disabled", + // the DNS Host Resolver doesn't support SRV records + // the DNS proxy has performance issues + // direct DNS pass-through doesn't support roaming laptops well + // we can't win, so let's go direct and at least get performance + "--natdnshostresolver1", "off", + "--natdnsproxy1", "off", + "--ostype", m.OSType, "--cpus", fmt.Sprintf("%d", m.CPUs), "--memory", fmt.Sprintf("%d", m.Memory), @@ -632,7 +657,6 @@ func (m *Machine) Modify() error { "--cpuhotplug", m.Flag.Get(F_cpuhotplug), "--pae", m.Flag.Get(F_pae), "--longmode", m.Flag.Get(F_longmode), - "--synthcpu", m.Flag.Get(F_synthcpu), "--hpet", m.Flag.Get(F_hpet), "--hwvirtex", m.Flag.Get(F_hwvirtex), "--triplefaultreset", m.Flag.Get(F_triplefaultreset), @@ -664,7 +688,7 @@ func (m *Machine) Modify() error { // 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 { - return vbm("controlvm", m.Name, fmt.Sprintf("natpf%d", n), + return vbm("modifyvm", m.Name, fmt.Sprintf("--natpf%d", n), fmt.Sprintf("%s,%s", name, rule.Format())) } diff --git a/virtualbox/vbm.go b/virtualbox/vbm.go index 641c071..ed166dd 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.Unsetenv("DYLD_LIBRARY_PATH") + os.Unsetenv("LD_LIBRARY_PATH") + } } var ( @@ -25,9 +31,7 @@ var ( ) var ( - ErrMachineExist = errors.New("machine already exists") - ErrMachineNotExist = errors.New("machine does not exist") - ErrVBMNotFound = errors.New("VBoxManage not found") + ErrVBMNotFound = errors.New("VBoxManage not found") ) func vbm(args ...string) error {