Skip to content

Commit 7651619

Browse files
authored
Merge pull request #183 from carolynvs/install-fallback-location
Retry failed release downloads from test location
2 parents f3e3cc9 + b326b11 commit 7651619

8 files changed

Lines changed: 338 additions & 162 deletions

File tree

dvm-helper/dockerversion/dockerversion.go

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ package dockerversion
22

33
import (
44
"fmt"
5+
"log"
6+
"net/http"
7+
"path/filepath"
58
"sort"
69
"strings"
710

811
"github.com/Masterminds/semver"
12+
"github.com/howtowhale/dvm/dvm-helper/internal/downloader"
913
"github.com/pkg/errors"
1014
)
1115

@@ -37,12 +41,9 @@ func Parse(value string) Version {
3741
return v
3842
}
3943

40-
func (version Version) BuildDownloadURL(mirror string) (url string, archived bool, checksumed bool, err error) {
44+
func (version Version) buildDownloadURL(mirror string, forcePrerelease bool) (url string, archived bool, checksumed bool, err error) {
4145
var releaseSlug, versionSlug, extSlug string
4246

43-
archivedReleaseCutoff, _ := semver.NewVersion("1.11.0-rc1")
44-
dockerStoreCutoff, _ := semver.NewVersion("17.06.0-ce")
45-
4647
var edgeVersion Version
4748
if version.IsEdge() {
4849
edgeVersion, err = findLatestEdgeVersion(mirror)
@@ -52,7 +53,7 @@ func (version Version) BuildDownloadURL(mirror string) (url string, archived boo
5253
}
5354

5455
// Docker Store Download
55-
if version.IsEdge() || !version.semver.LessThan(dockerStoreCutoff) {
56+
if version.shouldBeInDockerStore() {
5657
archived = true
5758
checksumed = false
5859
extSlug = archiveFileExt
@@ -62,7 +63,7 @@ func (version Version) BuildDownloadURL(mirror string) (url string, archived boo
6263
if version.IsEdge() {
6364
releaseSlug = "edge"
6465
versionSlug = edgeVersion.String()
65-
} else if version.IsPrerelease() {
66+
} else if version.IsPrerelease() || forcePrerelease {
6667
releaseSlug = "test"
6768
versionSlug = version.String()
6869
} else {
@@ -74,7 +75,7 @@ func (version Version) BuildDownloadURL(mirror string) (url string, archived boo
7475
mirror, mobyOS, releaseSlug, dockerArch, versionSlug, extSlug)
7576
return
7677
} else { // Original Download
77-
archived = !version.semver.LessThan(archivedReleaseCutoff)
78+
archived = version.shouldBeArchived()
7879
checksumed = true
7980
versionSlug = version.String()
8081
if archived {
@@ -97,6 +98,70 @@ func (version Version) BuildDownloadURL(mirror string) (url string, archived boo
9798
}
9899
}
99100

101+
// Download a Docker release.
102+
// version - the desired version.
103+
// mirrorURL - optional alternate download location.
104+
// binaryPath - full path to where the Docker client binary should be saved.
105+
func (version Version) Download(mirrorURL string, binaryPath string, l *log.Logger) error {
106+
err := version.download(false, mirrorURL, binaryPath, l)
107+
if err != nil && !version.IsPrerelease() && version.shouldBeInDockerStore() {
108+
// Docker initially publishes non-rc version versions to the test location
109+
// and then later republishes to the stable location
110+
// Retry stable versions against test to find "unstable" stable versions. :-)
111+
l.Printf("Could not find a stable release for %s, checking for a test release\n", version)
112+
retryErr := version.download(true, mirrorURL, binaryPath, l)
113+
return errors.Wrapf(retryErr, "Attempted to fallback to downloading from the prerelease location after downloading from the stable location failed: %s", err.Error())
114+
}
115+
return err
116+
}
117+
118+
func (version Version) download(forcePrerelease bool, mirrorURL string, binaryPath string, l *log.Logger) error {
119+
url, archived, checksumed, err := version.buildDownloadURL(mirrorURL, forcePrerelease)
120+
if err != nil {
121+
return errors.Wrapf(err, "Unable to determine the download URL for %s", version)
122+
}
123+
124+
l.Printf("Checking if %s can be found at %s", version, url)
125+
head, err := http.Head(url)
126+
if err != nil {
127+
return errors.Wrapf(err, "Unable to determine if %s is a valid version", version)
128+
}
129+
if head.StatusCode >= 400 {
130+
return errors.Errorf("Version %s not found (%v) - try `dvm ls-remote` to browse available versions", version, head.StatusCode)
131+
}
132+
133+
d := downloader.New(l)
134+
binaryName := filepath.Base(binaryPath)
135+
136+
if archived {
137+
archivedFile := filepath.Join("docker", binaryName)
138+
if checksumed {
139+
return d.DownloadArchivedFileWithChecksum(url, archivedFile, binaryPath)
140+
}
141+
return d.DownloadArchivedFile(url, archivedFile, binaryPath)
142+
}
143+
144+
if checksumed {
145+
return d.DownloadFileWithChecksum(url, binaryPath)
146+
}
147+
return d.DownloadFile(url, binaryPath)
148+
}
149+
150+
func (version Version) shouldBeInDockerStore() bool {
151+
if version.IsEdge() {
152+
return true
153+
}
154+
155+
dockerStoreCutoff, _ := semver.NewVersion("17.06.0-ce")
156+
157+
return version.semver != nil && !version.semver.LessThan(dockerStoreCutoff)
158+
}
159+
160+
func (version Version) shouldBeArchived() bool {
161+
archivedReleaseCutoff, _ := semver.NewVersion("1.11.0-rc1")
162+
return version.semver != nil && !version.semver.LessThan(archivedReleaseCutoff)
163+
}
164+
100165
func (version Version) IsPrerelease() bool {
101166
if version.semver == nil {
102167
return false

dvm-helper/dockerversion/dockerversion_test.go

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

33
import (
44
"fmt"
5+
"io/ioutil"
56
"net/http"
7+
"path/filepath"
68
"testing"
79

10+
"log"
11+
812
"github.com/pkg/errors"
913
"github.com/stretchr/testify/assert"
1014
)
@@ -172,7 +176,7 @@ func TestVersion_BuildDownloadURL(t *testing.T) {
172176

173177
for version, testcase := range testcases {
174178
t.Run(version.String(), func(t *testing.T) {
175-
gotURL, gotArchived, gotChecksumed, err := version.BuildDownloadURL("")
179+
gotURL, gotArchived, gotChecksumed, err := version.buildDownloadURL("", false)
176180
if err != nil {
177181
t.Fatal(err)
178182
}
@@ -201,26 +205,12 @@ func TestVersion_BuildDownloadURL(t *testing.T) {
201205

202206
func TestVersion_DownloadEdgeRelease(t *testing.T) {
203207
version := Parse("edge")
208+
tempDir, _ := ioutil.TempDir("", "dvmtest")
209+
destPath := filepath.Join(tempDir, "docker")
204210

205-
url, archived, checksumed, err := version.BuildDownloadURL("")
211+
l := log.New(ioutil.Discard, "", log.LstdFlags)
212+
err := version.Download("", destPath, l)
206213
if err != nil {
207214
t.Fatalf("%#v", err)
208215
}
209-
210-
if !archived {
211-
t.Fatal("Expected the edge release to be archived.")
212-
}
213-
214-
if checksumed {
215-
t.Fatal("Expected the edge release to NOT be checksumed.")
216-
}
217-
218-
response, err := http.DefaultClient.Get(url)
219-
if err != nil {
220-
t.Fatalf("%#v", errors.Wrapf(err, "Unable to download release from %s", response))
221-
}
222-
223-
if response.StatusCode != 200 {
224-
t.Fatalf("Unexpected status code (%d) when downloading %s", response.StatusCode, url)
225-
}
226216
}

dvm-helper/dvm-helper.go

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import (
44
"context"
55
"fmt"
66
"io/ioutil"
7+
"log"
78
neturl "net/url"
89
"os"
910
"os/exec"
10-
"path"
1111
"path/filepath"
1212
"regexp"
1313
"strings"
@@ -404,7 +404,7 @@ func list(pattern string) {
404404
func install(version dockerversion.Version) {
405405
versionDir := getVersionDir(version)
406406

407-
if version.IsEdge() && pathExists(versionDir) {
407+
if version.IsEdge() {
408408
// Always install latest of edge build
409409
err := os.RemoveAll(versionDir)
410410
if err != nil {
@@ -428,29 +428,20 @@ func install(version dockerversion.Version) {
428428
}
429429

430430
func downloadRelease(version dockerversion.Version) {
431-
url, archived, checksumed, err := version.BuildDownloadURL(mirrorURL)
431+
destPath := filepath.Join(getVersionDir(version), getBinaryName())
432+
err := version.Download(mirrorURL, destPath, getDebugLogger())
432433
if err != nil {
433-
die("Unable to determine the download URL for %s", err, retCodeRuntimeError, version)
434+
die("", err, retCodeRuntimeError)
434435
}
435436

436-
binaryName := getBinaryName()
437-
binaryPath := filepath.Join(getVersionDir(version), binaryName)
438-
if archived {
439-
archivedFile := path.Join("docker", binaryName)
440-
if checksumed {
441-
downloadArchivedFileWithChecksum(url, archivedFile, binaryPath)
442-
} else {
443-
downloadArchivedFile(url, archivedFile, binaryPath)
444-
}
445-
} else {
446-
if checksumed {
447-
downloadFileWithChecksum(url, binaryPath)
448-
} else {
449-
downloadFile(url, binaryPath)
450-
}
437+
writeDebug("Downloaded Docker %s to %s", version, destPath)
438+
}
451439

440+
func getDebugLogger() *log.Logger {
441+
if debug {
442+
return log.New(color.Output, "", log.LstdFlags)
452443
}
453-
writeDebug("Downloaded Docker %s to %s.", version, binaryPath)
444+
return log.New(ioutil.Discard, "", log.LstdFlags)
454445
}
455446

456447
func uninstall(version dockerversion.Version) {

dvm-helper/dvm-helper.nix.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,28 @@ package main
55
import (
66
"os"
77
"path/filepath"
8+
9+
"github.com/howtowhale/dvm/dvm-helper/internal/downloader"
810
)
911

1012
const binaryFileExt string = ""
1113

1214
func upgradeSelf(version string) {
15+
d := downloader.New(getDebugLogger())
16+
1317
binaryURL := buildDvmReleaseURL(version, dvmOS, dvmArch, "dvm-helper")
1418
binaryPath := filepath.Join(dvmDir, "dvm-helper", "dvm-helper")
15-
downloadFileWithChecksum(binaryURL, binaryPath)
19+
err := d.DownloadFileWithChecksum(binaryURL, binaryPath)
20+
if err != nil {
21+
die("", err, retCodeRuntimeError)
22+
}
1623

1724
scriptURL := buildDvmReleaseURL(version, "dvm.sh")
1825
scriptPath := filepath.Join(dvmDir, "dvm.sh")
19-
downloadFile(scriptURL, scriptPath)
26+
err = d.DownloadFile(scriptURL, scriptPath)
27+
if err != nil {
28+
die("", err, retCodeRuntimeError)
29+
}
2030
}
2131

2232
func getCleanPathRegex() string {

dvm-helper/dvm-helper.windows.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,37 @@ import (
66
"fmt"
77
"os"
88
"path/filepath"
9+
"strings"
10+
11+
"github.com/howtowhale/dvm/dvm-helper/internal/downloader"
912
)
10-
import "strings"
1113

1214
const dvmOS string = "Windows"
1315
const binaryFileExt string = ".exe"
1416

1517
func upgradeSelf(version string) {
18+
d := downloader.New(getDebugLogger())
19+
1620
binaryURL := buildDvmReleaseURL(version, dvmOS, dvmArch, "dvm-helper.exe")
1721
binaryPath := filepath.Join(dvmDir, ".tmp", "dvm-helper.exe")
18-
downloadFileWithChecksum(binaryURL, binaryPath)
22+
err := d.DownloadFileWithChecksum(binaryURL, binaryPath)
23+
if err != nil {
24+
die("", err, retCodeRuntimeError)
25+
}
1926

2027
psScriptURL := buildDvmReleaseURL(version, "dvm.ps1")
2128
psScriptPath := filepath.Join(dvmDir, "dvm.ps1")
22-
downloadFile(psScriptURL, psScriptPath)
29+
err = d.DownloadFile(psScriptURL, psScriptPath)
30+
if err != nil {
31+
die("", err, retCodeRuntimeError)
32+
}
2333

2434
cmdScriptURL := buildDvmReleaseURL(version, "dvm.cmd")
2535
cmdScriptPath := filepath.Join(dvmDir, "dvm.cmd")
26-
downloadFile(cmdScriptURL, cmdScriptPath)
36+
err = d.DownloadFile(cmdScriptURL, cmdScriptPath)
37+
if err != nil {
38+
die("", err, retCodeRuntimeError)
39+
}
2740

2841
writeUpgradeScript()
2942
}

dvm-helper/dvm-helper_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,35 @@ func TestInstallPrereleases(t *testing.T) {
161161
assert.NotEmpty(t, output, "Should have captured stdout")
162162
assert.Contains(t, output, "Now using Docker 1.12.5-rc1", "Should have installed a prerelease version")
163163
}
164+
165+
// install a version from the test location that is missing the -rc suffix
166+
func TestInstallNonPrereleaseTestRelease(t *testing.T) {
167+
_, github := createMockDVM(nil)
168+
defer github.Close()
169+
170+
outputCapture := &bytes.Buffer{}
171+
color.Output = outputCapture
172+
173+
dvm := makeCliApp()
174+
dvm.Run([]string{"dvm-helper", "--debug", "install", "17.10.0-ce"})
175+
176+
output := outputCapture.String()
177+
assert.NotEmpty(t, output, "Should have captured stdout")
178+
assert.Contains(t, output, "Now using Docker 17.10.0-ce", "Should have installed a test version")
179+
}
180+
181+
// install something that used to be a test release and is now considered stable
182+
func TestInstallStabilizedTestRelease(t *testing.T) {
183+
_, github := createMockDVM(nil)
184+
defer github.Close()
185+
186+
outputCapture := &bytes.Buffer{}
187+
color.Output = outputCapture
188+
189+
dvm := makeCliApp()
190+
dvm.Run([]string{"dvm-helper", "--debug", "install", "17.09.0-ce"})
191+
192+
output := outputCapture.String()
193+
assert.NotEmpty(t, output, "Should have captured stdout")
194+
assert.Contains(t, output, "Now using Docker 17.09.0-ce", "Should have installed a stable version")
195+
}

0 commit comments

Comments
 (0)