diff --git a/drivers/hyperv/hyperv.go b/drivers/hyperv/hyperv.go index 7e4e63b194..f7f559312f 100644 --- a/drivers/hyperv/hyperv.go +++ b/drivers/hyperv/hyperv.go @@ -149,7 +149,7 @@ func (d *Driver) Create() error { d.setMachineNameIfNotSet() - b2dutils := mcnutils.NewB2dUtils("", "", d.StorePath) + b2dutils := mcnutils.NewB2dUtils(d.StorePath) if err := b2dutils.CopyIsoToMachineDir(d.boot2DockerURL, d.MachineName); err != nil { return err } diff --git a/drivers/virtualbox/virtualbox.go b/drivers/virtualbox/virtualbox.go index b0c149823e..50f372e192 100644 --- a/drivers/virtualbox/virtualbox.go +++ b/drivers/virtualbox/virtualbox.go @@ -223,7 +223,7 @@ func (d *Driver) IsVTXDisabledInTheVM() (bool, error) { } func (d *Driver) Create() error { - b2dutils := mcnutils.NewB2dUtils("", "", d.StorePath) + b2dutils := mcnutils.NewB2dUtils(d.StorePath) if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil { return err } diff --git a/drivers/vmwarefusion/fusion.go b/drivers/vmwarefusion/fusion.go index 994224bc38..a6b777f418 100644 --- a/drivers/vmwarefusion/fusion.go +++ b/drivers/vmwarefusion/fusion.go @@ -203,7 +203,7 @@ func (d *Driver) PreCreateCheck() error { } func (d *Driver) Create() error { - b2dutils := mcnutils.NewB2dUtils("", "", d.StorePath) + b2dutils := mcnutils.NewB2dUtils(d.StorePath) if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil { return err } diff --git a/drivers/vmwarevsphere/vsphere.go b/drivers/vmwarevsphere/vsphere.go index 844aa1d17d..63f4ce9875 100644 --- a/drivers/vmwarevsphere/vsphere.go +++ b/drivers/vmwarevsphere/vsphere.go @@ -226,7 +226,7 @@ func (d *Driver) Create() error { return err } - b2dutils := mcnutils.NewB2dUtils("", "", d.StorePath) + b2dutils := mcnutils.NewB2dUtils(d.StorePath) if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil { return err } diff --git a/libmachine/mcnutils/b2d.go b/libmachine/mcnutils/b2d.go index 8840163d33..91f52b60e7 100644 --- a/libmachine/mcnutils/b2d.go +++ b/libmachine/mcnutils/b2d.go @@ -10,6 +10,7 @@ import ( "net/url" "os" "path/filepath" + "regexp" "time" "github.com/docker/machine/libmachine/log" @@ -50,33 +51,19 @@ type B2dUtils struct { githubBaseUrl string } -func NewB2dUtils(githubApiBaseUrl, githubBaseUrl, storePath string) *B2dUtils { - defaultBaseApiUrl := "https://api.github.com" - defaultBaseUrl := "https://github.com" +func NewB2dUtils(storePath string) *B2dUtils { imgCachePath := filepath.Join(storePath, "cache") isoFilename := "boot2docker.iso" - if githubApiBaseUrl == "" { - githubApiBaseUrl = defaultBaseApiUrl - } - - if githubBaseUrl == "" { - githubBaseUrl = defaultBaseUrl - } - return &B2dUtils{ - storePath: storePath, - isoFilename: isoFilename, - imgCachePath: imgCachePath, - commonIsoPath: filepath.Join(imgCachePath, isoFilename), - githubApiBaseUrl: githubApiBaseUrl, - githubBaseUrl: githubBaseUrl, + storePath: storePath, + isoFilename: isoFilename, + imgCachePath: imgCachePath, + commonIsoPath: filepath.Join(imgCachePath, isoFilename), } } -func (b *B2dUtils) getReleasesRequest() (*http.Request, error) { - apiUrl := fmt.Sprintf("%s/repos/boot2docker/boot2docker/releases", b.githubApiBaseUrl) - +func (b *B2dUtils) getReleasesRequest(apiUrl string) (*http.Request, error) { req, err := http.NewRequest("GET", apiUrl, nil) if err != nil { return nil, err @@ -91,33 +78,52 @@ func (b *B2dUtils) getReleasesRequest() (*http.Request, error) { // Get 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() (string, error) { - client := getClient() +func (b *B2dUtils) GetLatestBoot2DockerReleaseURL(apiUrl string) (string, error) { + if apiUrl == "" { + apiUrl = "https://api.github.com/repos/boot2docker/boot2docker/releases" + } + isoUrl := "" + // 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() - req, err := b.getReleasesRequest() - if err != nil { - return "", err + 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\n", 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 } - 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 - isoUrl := fmt.Sprintf("%s/boot2docker/boot2docker/releases/download/%s/boot2docker.iso", b.githubBaseUrl, tag) return isoUrl, nil } @@ -188,8 +194,8 @@ func (b *B2dUtils) DownloadISO(dir, file, isoUrl string) error { return nil } -func (b *B2dUtils) DownloadLatestBoot2Docker() error { - latestReleaseUrl, err := b.GetLatestBoot2DockerReleaseURL() +func (b *B2dUtils) DownloadLatestBoot2Docker(apiUrl string) error { + latestReleaseUrl, err := b.GetLatestBoot2DockerReleaseURL(apiUrl) if err != nil { return err } @@ -227,9 +233,14 @@ func (b *B2dUtils) CopyIsoToMachineDir(isoURL, machineName string) error { return err } } else { - // But if ISO is specified go get it directly - log.Infof("Downloading %s from %s...", b.isoFilename, isoURL) - if err := b.DownloadISO(machineDir, b.isoFilename, isoURL); err != nil { + //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 } } @@ -240,7 +251,7 @@ func (b *B2dUtils) CopyIsoToMachineDir(isoURL, machineName string) error { 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 { + if err := b.DownloadLatestBoot2Docker(""); err != nil { return err } } diff --git a/libmachine/mcnutils/b2d_test.go b/libmachine/mcnutils/b2d_test.go index 78f01c0b26..c17c3bf231 100644 --- a/libmachine/mcnutils/b2d_test.go +++ b/libmachine/mcnutils/b2d_test.go @@ -16,15 +16,15 @@ func TestGetLatestBoot2DockerReleaseUrl(t *testing.T) { })) defer ts.Close() - b := NewB2dUtils(ts.URL, ts.URL, "/tmp/isos") - isoUrl, err := b.GetLatestBoot2DockerReleaseURL() + b := NewB2dUtils("/tmp/isos") + isoUrl, err := b.GetLatestBoot2DockerReleaseURL(ts.URL + "/repos/org/repo/releases") if err != nil { t.Fatal(err) } - expectedUrl := fmt.Sprintf("%s/boot2docker/boot2docker/releases/download/0.1/boot2docker.iso", ts.URL) + expectedUrl := fmt.Sprintf("%s/org/repo/releases/download/0.1/boot2docker.iso", ts.URL) if isoUrl != expectedUrl { - t.Fatalf("expected url %s; received %s", isoUrl, expectedUrl) + t.Fatalf("expected url %s; received %s", expectedUrl, isoUrl) } } @@ -42,7 +42,7 @@ func TestDownloadIso(t *testing.T) { t.Fatal(err) } - b := NewB2dUtils(ts.URL, ts.URL, "/tmp/artifacts") + b := NewB2dUtils("/tmp/artifacts") if err := b.DownloadISO(tmpDir, filename, ts.URL); err != nil { t.Fatal(err) } @@ -59,8 +59,8 @@ func TestDownloadIso(t *testing.T) { func TestGetReleasesRequestNoToken(t *testing.T) { GithubApiToken = "" - b2d := NewB2dUtils("", "", "/tmp/store") - req, err := b2d.getReleasesRequest() + b2d := NewB2dUtils("/tmp/store") + req, err := b2d.getReleasesRequest("http://some.github.api") if err != nil { t.Fatal("Expected err to be nil, got ", err) } @@ -73,9 +73,9 @@ func TestGetReleasesRequestNoToken(t *testing.T) { func TestGetReleasesRequest(t *testing.T) { expectedToken := "CATBUG" GithubApiToken = expectedToken - b2d := NewB2dUtils("", "", "/tmp/store") + b2d := NewB2dUtils("/tmp/store") - req, err := b2d.getReleasesRequest() + req, err := b2d.getReleasesRequest("http://some.github.api") if err != nil { t.Fatal("Expected err to be nil, got ", err) } diff --git a/libmachine/provision/boot2docker.go b/libmachine/provision/boot2docker.go index a0e3bbf17b..2f2d449ab1 100644 --- a/libmachine/provision/boot2docker.go +++ b/libmachine/provision/boot2docker.go @@ -2,6 +2,7 @@ package provision import ( "bytes" + "encoding/json" "fmt" "net" "path" @@ -70,16 +71,28 @@ func (provisioner *Boot2DockerProvisioner) upgradeIso() error { // TODO: Ideally, we should not read from mcndirs directory at all. // The driver should be able to communicate how and where to place the // relevant files. - b2dutils := mcnutils.NewB2dUtils("", "", mcndirs.GetBaseDir()) + b2dutils := mcnutils.NewB2dUtils(mcndirs.GetBaseDir()) - // Usually we call this implicitly, but call it here explicitly to get - // the latest boot2docker ISO. - if err := b2dutils.DownloadLatestBoot2Docker(); err != nil { + //Check if the driver has specifed a custom b2d url + jsonDriver, err := json.Marshal(provisioner.GetDriver()) + if err != nil { return err } + var d struct { + Boot2DockerURL string + } + json.Unmarshal(jsonDriver, &d) - // Copy the latest version of boot2docker ISO to the machine's directory - if err := b2dutils.CopyIsoToMachineDir("", machineName); err != nil { + // 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 + } + } + // Either download the latest version of the b2d url that was explicitly + // specified when creating the VM or copy the (updated) default ISO + if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, machineName); err != nil { return err } diff --git a/libmachine/provision/rancheros.go b/libmachine/provision/rancheros.go index 005db00ae6..e3663e0161 100644 --- a/libmachine/provision/rancheros.go +++ b/libmachine/provision/rancheros.go @@ -180,7 +180,7 @@ func (provisioner *RancherProvisioner) upgradeIso() error { // TODO: Ideally, we should not read from mcndirs directory at all. // The driver should be able to communicate how and where to place the // relevant files. - b2dutils := mcnutils.NewB2dUtils("", "", mcndirs.GetBaseDir()) + b2dutils := mcnutils.NewB2dUtils(mcndirs.GetBaseDir()) url, err := provisioner.getLatestISOURL() if err != nil {