Skip to content
This repository was archived by the owner on Feb 27, 2018. It is now read-only.

Commit d2f3a3b

Browse files
committed
Merge pull request #242 from nathanleclaire/upgrade_client_binary
Add support for client binary on upgrade
2 parents 37b59b2 + 6b12c9b commit d2f3a3b

5 files changed

Lines changed: 224 additions & 13 deletions

File tree

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,40 @@ You can override the configurations using matching command-line flags. Type
187187
`boot2docker -h` for more information. The configuration file options are
188188
the same as the command-line flags with long names.
189189

190+
## Upgrade
191+
192+
You can use boot2docker-cli to upgrade:
193+
194+
1. The ISO you are using in the VM (and consequently the Docker daemon version)
195+
2. The Docker client binary on your host system
196+
3. The boot2docker-cli binary itself
197+
198+
To do so, run the `boot2docker upgrade` command.
199+
200+
```console
201+
$ boot2docker upgrade
202+
Backing up existing docker binary...
203+
Downloading new docker client binary...
204+
Success: downloaded https://get.docker.com/builds/Darwin/x86_64/docker-latest
205+
to /usr/local/bin/docker
206+
The old version is backed up to ~/.boot2docker.
207+
Backing up existing boot2docker binary...
208+
Downloading new boot2docker client binary...
209+
Success: downloaded https://github.com/boot2docker/boot2docker-cli/releases/download/v1.4.0/boot2docker-v1.4.0-darwin-amd64
210+
to /usr/local/bin/boot2docker
211+
The old version is backed up to ~/.boot2docker.
212+
Latest release for boot2docker/boot2docker is v1.4.0
213+
Downloading boot2docker ISO image...
214+
Success: downloaded https://github.com/boot2docker/boot2docker/releases/download/v1.4.0/boot2docker.iso
215+
to /Users/youruser/.boot2docker/boot2docker.iso
216+
Waiting for VM and Docker daemon to start...
217+
.................ooo
218+
Started.
219+
```
220+
221+
This will back up your current `docker` and `boot2docker` binaries to
222+
`~/.boot2docker` and download the latest ISO, `docker` binary and `boot2docker`
223+
binary in place of the old versions.
190224

191225

192226
## Contribution

cmds.go

Lines changed: 143 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package main
33
import (
44
"encoding/json"
55
"fmt"
6+
"io/ioutil"
7+
"net/http"
68
"os"
79
"os/exec"
810
"path/filepath"
@@ -11,10 +13,9 @@ import (
1113
"strings"
1214
"time"
1315

16+
"github.com/boot2docker/boot2docker-cli/driver"
1417
_ "github.com/boot2docker/boot2docker-cli/dummy"
1518
_ "github.com/boot2docker/boot2docker-cli/virtualbox"
16-
17-
"github.com/boot2docker/boot2docker-cli/driver"
1819
)
1920

2021
// Initialize the boot2docker VM from scratch.
@@ -277,6 +278,19 @@ func cmdPoweroff() error {
277278

278279
// Upgrade the boot2docker ISO - preserving server state
279280
func cmdUpgrade() error {
281+
if runtime.GOOS == "darwin" || runtime.GOOS == "linux" {
282+
if B2D.Clobber {
283+
err := upgradeDockerClientBinary()
284+
if err != nil {
285+
return err
286+
}
287+
} else {
288+
fmt.Println("Skipping client binary download, use --clobber=true to enable...")
289+
}
290+
}
291+
if err := upgradeBoot2DockerBinary(); err != nil {
292+
return fmt.Errorf("Error upgrading boot2docker binary: %s", err)
293+
}
280294
m, err := driver.GetMachine(&B2D)
281295
if err == nil {
282296
if m.GetState() == driver.Running || m.GetState() == driver.Saved || m.GetState() == driver.Paused {
@@ -292,6 +306,133 @@ func cmdUpgrade() error {
292306
return cmdDownload()
293307
}
294308

309+
func upgradeBoot2DockerBinary() error {
310+
var (
311+
goos, arch, ext string
312+
)
313+
latestVersion, err := getLatestReleaseName("https://api.github.com/repos/boot2docker/boot2docker-cli/releases")
314+
if err != nil {
315+
return fmt.Errorf("Error attempting to get the latest boot2docker-cli release: %s", err)
316+
}
317+
baseUrl := "https://github.com/boot2docker/boot2docker-cli/releases/download"
318+
319+
ext = ""
320+
321+
switch runtime.GOARCH {
322+
case "amd64":
323+
arch = "amd64"
324+
default:
325+
return fmt.Errorf("Architecture not supported")
326+
}
327+
328+
switch runtime.GOOS {
329+
case "darwin", "linux":
330+
goos = runtime.GOOS
331+
case "windows":
332+
goos = "windows"
333+
arch = "amd64"
334+
ext = ".exe"
335+
default:
336+
return fmt.Errorf("Operating system not supported")
337+
}
338+
binaryUrl := fmt.Sprintf("%s/%s/boot2docker-%s-%s-%s%s", baseUrl, latestVersion, latestVersion, goos, arch, ext)
339+
currentBoot2DockerVersion := Version
340+
if err := attemptUpgrade(binaryUrl, "boot2docker", latestVersion, currentBoot2DockerVersion); err != nil {
341+
return fmt.Errorf("Error attempting upgrade: %s", err)
342+
}
343+
return nil
344+
}
345+
346+
func upgradeDockerClientBinary() error {
347+
var (
348+
clientOs, clientArch string
349+
)
350+
resp, err := http.Get("https://get.docker.com/latest")
351+
if err != nil {
352+
return fmt.Errorf("Error checking the latest version of Docker: %s", err)
353+
}
354+
defer resp.Body.Close()
355+
latestVersionBytes, err := ioutil.ReadAll(resp.Body)
356+
if err != nil {
357+
return fmt.Errorf("Error reading response body on latest version of Docker call: %s", err)
358+
}
359+
latestVersion := strings.TrimSpace(string(latestVersionBytes))
360+
localClientVersion, err := getLocalClientVersion()
361+
if err != nil {
362+
return fmt.Errorf("Error getting local Docker client version: %s", err)
363+
}
364+
switch runtime.GOARCH {
365+
case "amd64":
366+
clientArch = "x86_64"
367+
default:
368+
return fmt.Errorf("Architecture not supported")
369+
}
370+
371+
switch runtime.GOOS {
372+
case "darwin":
373+
clientOs = "Darwin"
374+
case "linux":
375+
clientOs = "Linux"
376+
default:
377+
return fmt.Errorf("Operating system not supported")
378+
}
379+
binaryUrl := fmt.Sprintf("https://get.docker.com/builds/%s/%s/docker-latest", clientOs, clientArch)
380+
if err := attemptUpgrade(binaryUrl, "docker", latestVersion, localClientVersion); err != nil {
381+
return fmt.Errorf("Error attempting upgrade: %s", err)
382+
}
383+
return nil
384+
}
385+
386+
func attemptUpgrade(binaryUrl, binaryName, latestVersion, localVersion string) error {
387+
if (latestVersion != localVersion && !strings.Contains(latestVersion, "rc")) || B2D.ForceUpgradeDownload {
388+
if err := backupAndDownload(binaryUrl, binaryName, localVersion); err != nil {
389+
return fmt.Errorf("Error attempting backup and download of Docker client binary: %s", err)
390+
}
391+
} else {
392+
fmt.Printf("%s is up to date (%s), skipping upgrade...\n", binaryName, localVersion)
393+
}
394+
return nil
395+
}
396+
397+
func backupAndDownload(binaryUrl, binaryName, localVersion string) error {
398+
binaryPath, err := exec.LookPath(binaryName)
399+
if err != nil {
400+
return fmt.Errorf("Error attempting to locate local binary: %s", err)
401+
}
402+
path := strings.TrimSpace(string(binaryPath))
403+
404+
fmt.Println("Backing up existing", binaryName, "binary...")
405+
if err := backupBinary(binaryName, localVersion, path); err != nil {
406+
return fmt.Errorf("Error backing up docker client: %s", err)
407+
}
408+
409+
fmt.Println("Downloading new", binaryName, "client binary...")
410+
if err := download(path, binaryUrl); err != nil {
411+
return fmt.Errorf("Error attempting to download new client binary: %s", err)
412+
}
413+
if err := os.Chmod(path, 0755); err != nil {
414+
return err
415+
}
416+
fmt.Printf("Success: downloaded %s\n\tto %s\n\tThe old version is backed up to ~/.boot2docker.\n", binaryUrl, path)
417+
return nil
418+
}
419+
420+
func backupBinary(binaryName, localVersion, path string) error {
421+
dir, err := cfgDir(".boot2docker")
422+
if err != nil {
423+
return fmt.Errorf("Error getting boot2docker config dir: %s", err)
424+
}
425+
buf, err := ioutil.ReadFile(path)
426+
if err != nil {
427+
return fmt.Errorf("Error opening binary for reading at %s: %s", path, err)
428+
}
429+
backupName := fmt.Sprintf("%s-%s", binaryName, localVersion)
430+
if err := ioutil.WriteFile(filepath.Join(dir, backupName), buf, 0755); err != nil {
431+
return fmt.Errorf("Error creating backup file: %s", err)
432+
}
433+
return nil
434+
}
435+
295436
// Gracefully stop and then start the VM.
296437
func cmdRestart() error {
297438
m, err := driver.GetMachine(&B2D)

config.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@ func config() (*flag.FlagSet, error) {
9494
flags.StringVar(&B2D.ISOURL, "iso-url", "https://api.github.com/repos/boot2docker/boot2docker/releases", "source URL to provision the boot2docker ISO image.")
9595
flags.StringVar(&B2D.ISO, "iso", filepath.Join(dir, "boot2docker.iso"), "path to boot2docker ISO image.")
9696

97+
// clobber (overwrite client binary) by default on OSX. it's more likely that
98+
// users have installed through package manager on Linux, and if so, they should
99+
// upgrade that way.
100+
flags.BoolVar(&B2D.Clobber, "clobber", (runtime.GOOS == "darwin"), "overwrite Docker client binary on boot2docker upgrade")
101+
102+
flags.BoolVar(&B2D.ForceUpgradeDownload, "force-upgrade-download", false, "always download on boot2docker upgrade, never skip")
103+
97104
// Sven disabled this, as it is broken - if I user with a fresh computer downloads
98105
// just the boot2docker-cli, and then runs `boot2docker --init ip`, we create a vm
99106
// which cannot run, because it fails to have have the boot2docker.iso and the ssh keys

driver/config.go

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ package driver
22

33
import (
44
"fmt"
5-
flag "github.com/ogier/pflag"
65
"net"
6+
7+
flag "github.com/ogier/pflag"
78
)
89

910
// Machine config.
@@ -14,15 +15,17 @@ type MachineConfig struct {
1415
Driver string
1516

1617
// basic config
17-
SSH string // SSH client executable
18-
SSHGen string // SSH keygen executable
19-
SSHKey string // SSH key to send to the vm
20-
VM string // virtual machine name
21-
Dir string // boot2docker directory
22-
ISOURL string // Source URL to retrieve the ISO from
23-
ISO string // boot2docker ISO image path
24-
DiskSize uint // VM disk image size (MB)
25-
Memory uint // VM memory size (MB)
18+
Clobber bool
19+
ForceUpgradeDownload bool
20+
SSH string // SSH client executable
21+
SSHGen string // SSH keygen executable
22+
SSHKey string // SSH key to send to the vm
23+
VM string // virtual machine name
24+
Dir string // boot2docker directory
25+
ISOURL string // Source URL to retrieve the ISO from
26+
ISO string // boot2docker ISO image path
27+
DiskSize uint // VM disk image size (MB)
28+
Memory uint // VM memory size (MB)
2629

2730
// NAT network: port forwarding
2831
SSHPort uint16 // host SSH port (forward to port 22 in VM)

util.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ import (
2121
"github.com/boot2docker/boot2docker-cli/driver"
2222
)
2323

24+
var (
25+
// We're looking to get e.g. "1.2.0" from "Docker version 1.2.0, build fa7b24f"
26+
versionRe = regexp.MustCompile(`(\d+\.?){3}`)
27+
)
28+
2429
// Try if addr tcp://addr is readable for n times at wait interval.
2530
func read(addr string, n int, wait time.Duration) error {
2631
var lastErr error
@@ -105,6 +110,7 @@ func getLatestReleaseName(url string) (string, error) {
105110
defer rsp.Body.Close()
106111

107112
var t []struct {
113+
Name string `json:"name"`
108114
TagName string `json:"tag_name"`
109115
}
110116
body, err := ioutil.ReadAll(rsp.Body)
@@ -125,7 +131,27 @@ func getLatestReleaseName(url string) (string, error) {
125131
if len(t) == 0 {
126132
return "", fmt.Errorf("no releases found")
127133
}
128-
return t[0].TagName, nil
134+
135+
// Looking up by tag instead of release.
136+
// Github API call for docker releases yields nothing,
137+
// so we use tags API call in this case.
138+
name := ""
139+
if strings.Contains(url, "tags") {
140+
name = t[0].Name
141+
} else {
142+
name = t[0].TagName
143+
}
144+
return name, nil
145+
}
146+
147+
func getLocalClientVersion() (string, error) {
148+
versionOutput, err := exec.Command("docker", "-v").Output()
149+
if err != nil {
150+
return "", err
151+
}
152+
versionNumber := versionRe.FindString(string(versionOutput))
153+
154+
return versionNumber, nil
129155
}
130156

131157
func cmdInteractive(m driver.Machine, args ...string) error {

0 commit comments

Comments
 (0)