From bbe7a00db52bf1cd850426ae3a37e05a3813428a Mon Sep 17 00:00:00 2001 From: Soshi Katsuta Date: Tue, 27 Oct 2015 21:32:37 +0900 Subject: [PATCH] libmachine/mcnutils: check the version of cached iso and download the latest one when it is out-of-date Signed-off-by: Soshi Katsuta --- libmachine/mcnutils/b2d.go | 361 ++++++++++++------ libmachine/mcnutils/b2d_test.go | 268 +++++++++++-- libmachine/provision/boot2docker.go | 10 - .../virtualbox/create-with-upgrading.bats | 27 ++ 4 files changed, 516 insertions(+), 150 deletions(-) create mode 100644 test/integration/virtualbox/create-with-upgrading.bats diff --git a/libmachine/mcnutils/b2d.go b/libmachine/mcnutils/b2d.go index 6e9d8e28f5..4b9bb67b87 100644 --- a/libmachine/mcnutils/b2d.go +++ b/libmachine/mcnutils/b2d.go @@ -2,6 +2,7 @@ package mcnutils import ( "encoding/json" + "errors" "fmt" "io" "io/ioutil" @@ -15,10 +16,22 @@ import ( "github.com/docker/machine/libmachine/log" ) +const ( + defaultURL = "https://api.github.com/repos/boot2docker/boot2docker/releases/latest" + defaultISOFilename = "boot2docker.iso" + defaultVolumeIDOffset = int64(0x8028) + defaultVolumeIDLength = 32 +) + var ( GithubAPIToken string ) +var ( + errGitHubAPIResponse = errors.New(`Error getting a version tag from the Github API response. +You may be getting rate limited by Github.`) +) + func defaultTimeout(network, addr string) (net.Conn, error) { return net.Dial(network, addr) } @@ -37,28 +50,7 @@ func getClient() *http.Client { return &client } -type B2dUtils struct { - storePath string - isoFilename string - commonIsoPath string - imgCachePath string - githubAPIBaseURL string - githubBaseURL string -} - -func NewB2dUtils(storePath string) *B2dUtils { - imgCachePath := filepath.Join(storePath, "cache") - isoFilename := "boot2docker.iso" - - return &B2dUtils{ - storePath: storePath, - isoFilename: isoFilename, - imgCachePath: imgCachePath, - commonIsoPath: filepath.Join(imgCachePath, isoFilename), - } -} - -func (b *B2dUtils) getReleasesRequest(apiURL string) (*http.Request, error) { +func getRequest(apiURL string) (*http.Request, error) { req, err := http.NewRequest("GET", apiURL, nil) if err != nil { return nil, err @@ -71,68 +63,92 @@ func (b *B2dUtils) getReleasesRequest(apiURL string) (*http.Request, error) { return req, nil } -// GetLatestBoot2DockerReleaseURL gets the latest boot2docker release tag name (e.g. "v0.6.0"). -// FIXME: find or create some other way to get the "latest release" of boot2docker since the GitHub API has a pretty low rate limit on API requests -func (b *B2dUtils) GetLatestBoot2DockerReleaseURL(apiURL string) (string, error) { +// releaseGetter is a client that gets release information of a product and downloads it. +type releaseGetter interface { + // filename returns filename of the product. + filename() string + // getReleaseTag gets a release tag from the given URL. + getReleaseTag(apiURL string) (string, error) + // getReleaseURL gets the latest release download URL from the given URL. + getReleaseURL(apiURL string) (string, error) + // download downloads a file from the given dlURL and saves it under dir. + download(dir, file, dlURL string) error +} + +// b2dReleaseGetter implements the releaseGetter interface for getting the release of Boot2Docker. +type b2dReleaseGetter struct { + isoFilename string +} + +func (b *b2dReleaseGetter) filename() string { + if b == nil { + return "" + } + return b.isoFilename +} + +// getReleaseTag gets the release tag of Boot2Docker from apiURL. +func (*b2dReleaseGetter) getReleaseTag(apiURL string) (string, error) { if apiURL == "" { - apiURL = "https://api.github.com/repos/boot2docker/boot2docker/releases" + apiURL = defaultURL } - isoURL := "" + + client := getClient() + req, err := getRequest(apiURL) + if err != nil { + return "", err + } + rsp, err := client.Do(req) + if err != nil { + return "", err + } + defer rsp.Body.Close() + + var t struct { + TagName string `json:"tag_name"` + } + if err := json.NewDecoder(rsp.Body).Decode(&t); err != nil { + return "", err + } + if t.TagName == "" { + return "", errGitHubAPIResponse + } + return t.TagName, nil +} + +// getReleaseURL gets the latest release URL of Boot2Docker. +// FIXME: find or create some other way to get the "latest release" of boot2docker since the GitHub API has a pretty low rate limit on API requests +func (b *b2dReleaseGetter) getReleaseURL(apiURL string) (string, error) { + if apiURL == "" { + apiURL = defaultURL + } + // match github (enterprise) release urls: - // https://api.github.com/repos/../../releases or - // https://some.github.enterprise/api/v3/repos/../../releases - re := regexp.MustCompile("(https?)://([^/]+)(/api/v3)?/repos/([^/]+)/([^/]+)/releases") - if matches := re.FindStringSubmatch(apiURL); len(matches) == 6 { - scheme := matches[1] - host := matches[2] - org := matches[4] - repo := matches[5] - if host == "api.github.com" { - host = "github.com" - } - client := getClient() - req, err := b.getReleasesRequest(apiURL) - if err != nil { - return "", err - } - rsp, err := client.Do(req) - if err != nil { - return "", err - } - defer rsp.Body.Close() - - var t []struct { - TagName string `json:"tag_name"` - } - if err := json.NewDecoder(rsp.Body).Decode(&t); err != nil { - return "", fmt.Errorf("Error demarshaling the Github API response: %s\nYou may be getting rate limited by Github.", err) - } - if len(t) == 0 { - return "", fmt.Errorf("no releases found") - } - - tag := t[0].TagName - log.Infof("Latest release for %s/%s/%s is %s", host, org, repo, tag) - isoURL = fmt.Sprintf("%s://%s/%s/%s/releases/download/%s/boot2docker.iso", scheme, host, org, repo, tag) - } else { - //does not match a github releases api url - isoURL = apiURL + // https://api.github.com/repos/../../releases/latest or + // https://some.github.enterprise/api/v3/repos/../../releases/latest + re := regexp.MustCompile("(https?)://([^/]+)(/api/v3)?/repos/([^/]+)/([^/]+)/releases/latest") + matches := re.FindStringSubmatch(apiURL) + if len(matches) != 6 { + // does not match a github releases api URL + return apiURL, nil } - return isoURL, nil -} - -func removeFileIfExists(name string) error { - if _, err := os.Stat(name); err == nil { - if err := os.Remove(name); err != nil { - return fmt.Errorf("Error removing temporary download file: %s", err) - } + scheme, host, org, repo := matches[1], matches[2], matches[4], matches[5] + if host == "api.github.com" { + host = "github.com" } - return nil + + tag, err := b.getReleaseTag(apiURL) + if err != nil { + return "", err + } + + log.Infof("Latest release for %s/%s/%s is %s", host, org, repo, tag) + url := fmt.Sprintf("%s://%s/%s/%s/releases/download/%s/%s", scheme, host, org, repo, tag, b.isoFilename) + return url, nil } -// DownloadISO downloads boot2docker ISO image for the given tag and save it at dest. -func (b *B2dUtils) DownloadISO(dir, file, isoURL string) error { +func (*b2dReleaseGetter) download(dir, file, isoURL string) error { u, err := url.Parse(isoURL) var src io.ReadCloser @@ -195,6 +211,103 @@ func (b *B2dUtils) DownloadISO(dir, file, isoURL string) error { return nil } +// iso is an ISO volume. +type iso interface { + // path returns the path of the ISO. + path() string + // exists reports whether the ISO exists. + exists() bool + // version returns version information of the ISO. + version() (string, error) +} + +// b2dISO represents a Boot2Docker ISO. It implements the ISO interface. +type b2dISO struct { + // path of Boot2Docker ISO + commonIsoPath string + + // offset and length of ISO volume ID + // cf. http://serverfault.com/questions/361474/is-there-a-way-to-change-a-iso-files-volume-id-from-the-command-line + volumeIDOffset int64 + volumeIDLength int +} + +func (b *b2dISO) path() string { + if b == nil { + return "" + } + return b.commonIsoPath +} + +func (b *b2dISO) exists() bool { + if b == nil { + return false + } + + _, err := os.Stat(b.commonIsoPath) + return !os.IsNotExist(err) +} + +// version scans the volume ID in b and returns its version tag. +func (b *b2dISO) version() (string, error) { + if b == nil { + return "", nil + } + + iso, err := os.Open(b.commonIsoPath) + if err != nil { + return "", err + } + defer iso.Close() + + isoMetadata := make([]byte, b.volumeIDLength) + _, err = iso.ReadAt(isoMetadata, b.volumeIDOffset) + if err != nil { + return "", err + } + + verRegex := regexp.MustCompile(`v\d+\.\d+\.\d+`) + ver := string(verRegex.Find(isoMetadata)) + log.Debug("local Boot2Docker ISO version: ", ver) + return ver, nil +} + +func removeFileIfExists(name string) error { + if _, err := os.Stat(name); err == nil { + if err := os.Remove(name); err != nil { + return fmt.Errorf("Error removing temporary download file: %s", err) + } + } + return nil +} + +type B2dUtils struct { + releaseGetter + iso + storePath string + imgCachePath string +} + +func NewB2dUtils(storePath string) *B2dUtils { + imgCachePath := filepath.Join(storePath, "cache") + + return &B2dUtils{ + releaseGetter: &b2dReleaseGetter{isoFilename: defaultISOFilename}, + iso: &b2dISO{ + commonIsoPath: filepath.Join(imgCachePath, defaultISOFilename), + volumeIDOffset: defaultVolumeIDOffset, + volumeIDLength: defaultVolumeIDLength, + }, + storePath: storePath, + imgCachePath: imgCachePath, + } +} + +// DownloadISO downloads boot2docker ISO image for the given tag and save it at dest. +func (b *B2dUtils) DownloadISO(dir, file, isoURL string) error { + return b.download(dir, file, isoURL) +} + type ReaderWithProgress struct { io.ReadCloser out io.Writer @@ -229,7 +342,7 @@ func (r *ReaderWithProgress) Close() error { } func (b *B2dUtils) DownloadLatestBoot2Docker(apiURL string) error { - latestReleaseURL, err := b.GetLatestBoot2DockerReleaseURL(apiURL) + latestReleaseURL, err := b.getReleaseURL(apiURL) if err != nil { return err } @@ -238,8 +351,8 @@ func (b *B2dUtils) DownloadLatestBoot2Docker(apiURL string) error { } func (b *B2dUtils) DownloadISOFromURL(latestReleaseURL string) error { - log.Infof("Downloading %s to %s...", latestReleaseURL, b.commonIsoPath) - if err := b.DownloadISO(b.imgCachePath, b.isoFilename, latestReleaseURL); err != nil { + log.Infof("Downloading %s to %s...", latestReleaseURL, b.path()) + if err := b.DownloadISO(b.imgCachePath, b.filename(), latestReleaseURL); err != nil { return err } @@ -249,7 +362,7 @@ func (b *B2dUtils) DownloadISOFromURL(latestReleaseURL string) error { func (b *B2dUtils) CopyIsoToMachineDir(isoURL, machineName string) error { // TODO: This is a bit off-color. machineDir := filepath.Join(b.storePath, "machines", machineName) - machineIsoPath := filepath.Join(machineDir, b.isoFilename) + machineIsoPath := filepath.Join(machineDir, b.filename()) // just in case the cache dir has been manually deleted, // check for it and recreate it if it's gone @@ -260,39 +373,69 @@ func (b *B2dUtils) CopyIsoToMachineDir(isoURL, machineName string) error { } } - // By default just copy the existing "cached" iso to - // the machine's directory... + // By default just copy the existing "cached" iso to the machine's directory... if isoURL == "" { - if err := b.copyDefaultIsoToMachine(machineIsoPath); err != nil { - return err - } - } else { - //if ISO is specified, check if it matches a github releases url or fallback - //to a direct download - if downloadURL, err := b.GetLatestBoot2DockerReleaseURL(isoURL); err == nil { - log.Infof("Downloading %s from %s...", b.isoFilename, downloadURL) - if err := b.DownloadISO(machineDir, b.isoFilename, downloadURL); err != nil { - return err - } - } else { - return err - } + return b.copyDefaultISOToMachine(machineIsoPath) } - return nil -} - -func (b *B2dUtils) copyDefaultIsoToMachine(machineIsoPath string) error { - if _, err := os.Stat(b.commonIsoPath); os.IsNotExist(err) { - log.Info("No default boot2docker iso found locally, downloading the latest release...") - if err := b.DownloadLatestBoot2Docker(""); err != nil { - return err - } - } - - if err := CopyFile(b.commonIsoPath, machineIsoPath); err != nil { + // if ISO is specified, check if it matches a github releases url or fallback to a direct download + downloadURL, err := b.getReleaseURL(isoURL) + if err != nil { return err } - return nil + log.Infof("Downloading %s from %s...", b.filename(), downloadURL) + return b.DownloadISO(machineDir, b.filename(), downloadURL) +} + +func (b *B2dUtils) copyDefaultISOToMachine(machineIsoPath string) error { + // just in case the cache dir has been manually deleted, + // check for it and recreate it if it's gone + if _, err := os.Stat(b.imgCachePath); os.IsNotExist(err) { + log.Infof("Image cache directory does not exist, creating it at %s...", b.imgCachePath) + if err := os.Mkdir(b.imgCachePath, 0700); err != nil { + return err + } + } + + exists := b.exists() + latest := b.isLatest() + + if exists && latest { + log.Infof("Latest Boot2Docker ISO found locally, copying it to %s...", machineIsoPath) + return CopyFile(b.path(), machineIsoPath) + } + + if !exists { + log.Info("No default Boot2Docker ISO found locally, downloading the latest release...") + } else if !latest { + log.Info("Default Boot2Docker ISO is out-of-date, downloading the latest release...") + } + if err := b.DownloadLatestBoot2Docker(""); err != nil { + return err + } + + log.Infof("Copying %s to %s...", b.path(), machineIsoPath) + return CopyFile(b.path(), machineIsoPath) +} + +// isLatest checks the latest release tag and +// reports whether the local ISO cache is the latest version. +// +// It returns false if failing to get the local ISO version +// and true if failing to fetch the latest release tag. +func (b *B2dUtils) isLatest() bool { + localVer, err := b.version() + if err != nil { + log.Warn("Unable to get the local Boot2Docker ISO version: ", err) + return false + } + + latestVer, err := b.getReleaseTag("") + if err != nil { + log.Warn("Unable to get the latest Boot2Docker ISO release version: ", err) + return true + } + + return localVer == latestVer } diff --git a/libmachine/mcnutils/b2d_test.go b/libmachine/mcnutils/b2d_test.go index a1c1ea1185..e3242089fb 100644 --- a/libmachine/mcnutils/b2d_test.go +++ b/libmachine/mcnutils/b2d_test.go @@ -1,37 +1,84 @@ package mcnutils import ( + "bytes" + "errors" "fmt" "io/ioutil" "net/http" "net/http/httptest" + "os" "path/filepath" "testing" - "bytes" - + "github.com/docker/machine/libmachine/log" "github.com/stretchr/testify/assert" ) -func TestGetLatestBoot2DockerReleaseUrl(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - respText := `[{"tag_name": "0.1"}]` - w.Write([]byte(respText)) - })) +func TestGetReleaseURL(t *testing.T) { + ts := newTestServer(`{"tag_name": "v0.1"}`) defer ts.Close() - b := NewB2dUtils("/tmp/isos") - isoURL, err := b.GetLatestBoot2DockerReleaseURL(ts.URL + "/repos/org/repo/releases") + testCases := []struct { + apiURL string + isoURL string + }{ + {ts.URL + "/repos/org/repo/releases/latest", ts.URL + "/org/repo/releases/download/v0.1/boot2docker.iso"}, + {"http://dummy.com/boot2docker.iso", "http://dummy.com/boot2docker.iso"}, + } - assert.NoError(t, err) - assert.Equal(t, fmt.Sprintf("%s/org/repo/releases/download/0.1/boot2docker.iso", ts.URL), isoURL) + for _, tt := range testCases { + b := NewB2dUtils("/tmp/isos") + isoURL, err := b.getReleaseURL(tt.apiURL) + + assert.NoError(t, err) + assert.Equal(t, isoURL, tt.isoURL) + } } -func TestDownloadIso(t *testing.T) { +func TestGetReleaseURLError(t *testing.T) { + // GitHub API error response in case of rate limit + ts := newTestServer(`{"message": "API rate limit exceeded for 127.0.0.1.", + "documentation_url": "https://developer.github.com/v3/#rate-limiting"}`) + defer ts.Close() + + testCases := []struct { + apiURL string + }{ + {ts.URL + "/repos/org/repo/releases/latest"}, + {"http://127.0.0.1/repos/org/repo/releases/latest"}, // dummy API URL. cannot connect it. + } + + for _, tt := range testCases { + b := NewB2dUtils("/tmp/isos") + _, err := b.getReleaseURL(tt.apiURL) + + assert.Error(t, err) + } +} + +func TestVersion(t *testing.T) { + want := "v0.1.0" + isopath, off, err := newDummyISO("", defaultISOFilename, want) + defer removeFileIfExists(isopath) + + assert.NoError(t, err) + + b := &b2dISO{ + commonIsoPath: isopath, + volumeIDOffset: off, + volumeIDLength: defaultVolumeIDLength, + } + + got, err := b.version() + + assert.NoError(t, err) + assert.Equal(t, want, string(got)) +} + +func TestDownloadISO(t *testing.T) { testData := "test-download" - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(testData)) - })) + ts := newTestServer(testData) defer ts.Close() filename := "test" @@ -51,25 +98,23 @@ func TestDownloadIso(t *testing.T) { assert.Equal(t, testData, string(data)) } -func TestGetReleasesRequestNoToken(t *testing.T) { - GithubAPIToken = "" +func TestGetRequest(t *testing.T) { + testCases := []struct { + token string + want string + }{ + {"", ""}, + {"CATBUG", "token CATBUG"}, + } - b2d := NewB2dUtils("/tmp/store") - req, err := b2d.getReleasesRequest("http://some.github.api") + for _, tt := range testCases { + GithubAPIToken = tt.token - assert.NoError(t, err) - assert.Empty(t, req.Header.Get("Authorization")) -} + req, err := getRequest("http://some.github.api") -func TestGetReleasesRequest(t *testing.T) { - expectedToken := "CATBUG" - GithubAPIToken = expectedToken - - b2d := NewB2dUtils("/tmp/store") - req, err := b2d.getReleasesRequest("http://some.github.api") - - assert.NoError(t, err) - assert.Equal(t, fmt.Sprintf("token %s", expectedToken), req.Header.Get("Authorization")) + assert.NoError(t, err) + assert.Equal(t, tt.want, req.Header.Get("Authorization")) + } } type MockReadCloser struct { @@ -110,3 +155,164 @@ func TestReaderWithProgress(t *testing.T) { readerWithProgress.Close() assert.Equal(t, "0%....10%....20%....30%....40%....50%....60%....70%....80%....90%....100%\n", output.String()) } + +type mockReleaseGetter struct { + ver string + apiErr error + verCh chan<- string +} + +func (m *mockReleaseGetter) filename() string { + return defaultISOFilename +} + +func (m *mockReleaseGetter) getReleaseTag(apiURL string) (string, error) { + return m.ver, m.apiErr +} + +func (m *mockReleaseGetter) getReleaseURL(apiURL string) (string, error) { + return "http://127.0.0.1/dummy", m.apiErr +} + +func (m *mockReleaseGetter) download(dir, file, isoURL string) error { + path := filepath.Join(dir, file) + var err error + if _, e := os.Stat(path); os.IsNotExist(e) { + err = ioutil.WriteFile(path, dummyISOData(" ", m.ver), 0644) + } + + // send a signal of downloading the latest version + m.verCh <- m.ver + return err +} + +type mockISO struct { + isopath string + exist bool + ver string + verCh <-chan string +} + +func (m *mockISO) path() string { + return m.isopath +} + +func (m *mockISO) exists() bool { + return m.exist +} + +func (m *mockISO) version() (string, error) { + select { + // receive version of a downloaded iso + case ver := <-m.verCh: + return ver, nil + default: + return m.ver, nil + } +} + +func TestCopyDefaultISOToMachine(t *testing.T) { + apiErr := errors.New("api error") + + testCases := []struct { + machineName string + create bool + localVer string + latestVer string + apiErr error + wantVer string + }{ + {"none", false, "", "v1.0.0", nil, "v1.0.0"}, // none => downloading + {"latest", true, "v1.0.0", "v1.0.0", nil, "v1.0.0"}, // latest iso => as is + {"old-badurl", true, "v0.1.0", "", apiErr, "v0.1.0"}, // old iso with bad api => as is + {"old", true, "v0.1.0", "v1.0.0", nil, "v1.0.0"}, // old iso => updating + } + + var isopath string + var err error + verCh := make(chan string, 1) + for _, tt := range testCases { + if tt.create { + isopath, _, err = newDummyISO("cache", defaultISOFilename, tt.localVer) + } else { + if dir, e := ioutil.TempDir("", "machine-test"); e == nil { + isopath = filepath.Join(dir, "cache", defaultISOFilename) + } + } + + // isopath: "$TMPDIR/machine-test-xxxxxx/cache/boot2docker.iso" + // tmpDir: "$TMPDIR/machine-test-xxxxxx" + imgCachePath := filepath.Dir(isopath) + storePath := filepath.Dir(imgCachePath) + + b := &B2dUtils{ + releaseGetter: &mockReleaseGetter{ + ver: tt.latestVer, + apiErr: tt.apiErr, + verCh: verCh, + }, + iso: &mockISO{ + isopath: isopath, + exist: tt.create, + ver: tt.localVer, + verCh: verCh, + }, + storePath: storePath, + imgCachePath: imgCachePath, + } + + dir := filepath.Join(storePath, tt.machineName) + err = os.MkdirAll(dir, 0700) + + assert.NoError(t, err, "machine: %s", tt.machineName) + + dest := filepath.Join(dir, b.filename()) + err = b.copyDefaultISOToMachine(dest) + _, pathErr := os.Stat(dest) + + assert.NoError(t, err, "machine: %s", tt.machineName) + assert.True(t, !os.IsNotExist(pathErr), "machine: %s", tt.machineName) + + ver, err := b.version() + + assert.NoError(t, err, "machine: %s", tt.machineName) + assert.Equal(t, tt.wantVer, ver, "machine: %s", tt.machineName) + + err = removeFileIfExists(isopath) + assert.NoError(t, err, "machine: %s", tt.machineName) + } +} + +// newTestServer creates a new httptest.Server that returns respText as a response body. +func newTestServer(respText string) *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(respText)) + })) +} + +// newDummyISO creates a dummy ISO file that contains the given version info, +// and returns its path and offset value to fetch the version info. +func newDummyISO(dir, name, version string) (string, int64, error) { + tmpDir, err := ioutil.TempDir("", "machine-test-") + if err != nil { + return "", 0, err + } + + tmpDir = filepath.Join(tmpDir, dir) + if e := os.MkdirAll(tmpDir, 755); e != nil { + return "", 0, err + } + + isopath := filepath.Join(tmpDir, name) + log.Info("TEST: dummy ISO created at ", isopath) + + // dummy ISO data mimicking the real byte data of a Boot2Docker ISO image + padding := " " + data := dummyISOData(padding, version) + return isopath, int64(len(padding)), ioutil.WriteFile(isopath, data, 0644) +} + +// dummyISOData returns mock data that contains given padding and version. +func dummyISOData(padding, version string) []byte { + return []byte(fmt.Sprintf("%sBoot2Docker-%s ", padding, version)) +} diff --git a/libmachine/provision/boot2docker.go b/libmachine/provision/boot2docker.go index 4ee8aa3bd9..ddf54d4481 100644 --- a/libmachine/provision/boot2docker.go +++ b/libmachine/provision/boot2docker.go @@ -66,16 +66,6 @@ func (provisioner *Boot2DockerProvisioner) upgradeIso() error { } json.Unmarshal(jsonDriver, &d) - log.Info("Downloading latest boot2docker iso...") - - // Usually we call this implicitly, but call it here explicitly to get - // the latest default boot2docker ISO. - if d.Boot2DockerURL == "" { - if err := b2dutils.DownloadLatestBoot2Docker(d.Boot2DockerURL); err != nil { - return err - } - } - log.Info("Stopping machine to do the upgrade...") if err := provisioner.Driver.Stop(); err != nil { diff --git a/test/integration/virtualbox/create-with-upgrading.bats b/test/integration/virtualbox/create-with-upgrading.bats new file mode 100644 index 0000000000..705d22b231 --- /dev/null +++ b/test/integration/virtualbox/create-with-upgrading.bats @@ -0,0 +1,27 @@ +#!/usr/bin/env bats + +load ${BASE_TEST_DIR}/helpers.bash + +only_if_env DRIVER virtualbox + +export CACHE_DIR="$MACHINE_STORAGE_PATH/cache" +export ISO_PATH="$CACHE_DIR/boot2docker.iso" +export OLD_ISO_URL="https://github.com/boot2docker/boot2docker/releases/download/v1.4.1/boot2docker.iso" + +@test "$DRIVER: download the old version iso" { + run mkdir -p $CACHE_DIR + run curl $OLD_ISO_URL -L -o $ISO_PATH + echo ${output} + [ "$status" -eq 0 ] +} + +@test "$DRIVER: create with upgrading" { + run machine create -d $DRIVER $NAME + echo ${output} + [ "$status" -eq 0 ] +} + +@test "$DRIVER: create is correct version" { + SERVER_VERSION=$(docker $(machine config $NAME) version | grep 'Server version' | awk '{ print $3; }') + [[ "$SERVER_VERSION" != "1.4.1" ]] +}