Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Commit 3ba1bed

Browse files
committed
internal/cmd/update.go: query github releases api for assets
1 parent 2002876 commit 3ba1bed

File tree

2 files changed

+96
-35
lines changed

2 files changed

+96
-35
lines changed

internal/cmd/update.go

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ import (
3232
"github.com/spf13/cobra"
3333
)
3434

35+
const (
36+
goosWindows = "windows"
37+
goosLinux = "linux"
38+
apiPrivateVersion = "/api/private/version"
39+
)
40+
3541
// updater updates coder-cli.
3642
type updater struct {
3743
confirmF func(string) (string, error)
@@ -143,7 +149,10 @@ func (u *updater) Run(ctx context.Context, force bool, coderURLArg string, versi
143149
}
144150
}
145151

146-
downloadURL := makeDownloadURL(desiredVersion, u.osF(), runtime.GOARCH)
152+
downloadURL, err := queryGithubAssetURL(u.httpClient, desiredVersion, u.osF())
153+
if err != nil {
154+
return clog.Fatal("failed to query github assets url", clog.Causef(err.Error()))
155+
}
147156

148157
var downloadBuf bytes.Buffer
149158
memWriter := bufio.NewWriter(&downloadBuf)
@@ -262,15 +271,7 @@ func defaultConfirm(label string) (string, error) {
262271
return p.Run()
263272
}
264273

265-
func makeDownloadURL(version *semver.Version, ostype, arch string) string {
266-
const template = "https://github.com/cdr/coder-cli/releases/download/v%s/coder-cli-%s-%s.%s"
267-
var ext string
268-
switch ostype {
269-
case "linux":
270-
ext = "tar.gz"
271-
default:
272-
ext = "zip"
273-
}
274+
func queryGithubAssetURL(httpClient getter, version *semver.Version, ostype string) (string, error) {
274275
var b bytes.Buffer
275276
fmt.Fprintf(&b, "%d", version.Major())
276277
fmt.Fprint(&b, ".")
@@ -282,7 +283,41 @@ func makeDownloadURL(version *semver.Version, ostype, arch string) string {
282283
fmt.Fprint(&b, version.Prerelease())
283284
}
284285

285-
return fmt.Sprintf(template, b.String(), ostype, arch, ext)
286+
urlString := fmt.Sprintf("https://api.github.com/repos/cdr/coder-cli/releases/tags/v%s", b.String())
287+
clog.LogInfo("query github releases", fmt.Sprintf("url: %q", urlString))
288+
289+
type asset struct {
290+
BrowserDownloadURL string `json:"browser_download_url"`
291+
Name string `json:"name"`
292+
}
293+
type release struct {
294+
Assets []asset `json:"assets"`
295+
}
296+
var r release
297+
298+
resp, err := httpClient.Get(urlString)
299+
if err != nil {
300+
return "", xerrors.Errorf("query github release url %s: %w", urlString, err)
301+
}
302+
defer resp.Body.Close()
303+
304+
err = json.NewDecoder(resp.Body).Decode(&r)
305+
if err != nil {
306+
return "", xerrors.Errorf("unmarshal github releases api response: %w", err)
307+
}
308+
309+
var assetURLStr string
310+
for _, a := range r.Assets {
311+
if strings.HasPrefix(a.Name, "coder-cli-"+ostype) {
312+
assetURLStr = a.BrowserDownloadURL
313+
}
314+
}
315+
316+
if assetURLStr == "" {
317+
return "", xerrors.Errorf("could not find release for ostype %s", ostype)
318+
}
319+
320+
return assetURLStr, nil
286321
}
287322

288323
func extractFromArchive(path string, archive []byte) ([]byte, error) {

internal/cmd/update_test.go

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,24 @@ import (
2222
)
2323

2424
const (
25-
fakeExePathLinux = "/home/user/bin/coder"
26-
fakeExePathWindows = `C:\Users\user\bin\coder.exe`
27-
fakeCoderURL = "https://my.cdr.dev"
28-
fakeNewVersion = "1.23.4-rc.5+678-gabcdef-12345678"
29-
fakeOldVersion = "1.22.4-rc.5+678-gabcdef-12345678"
30-
fakeReleaseURLLinux = "https://github.com/cdr/coder-cli/releases/download/v1.23.4-rc.5/coder-cli-linux-amd64.tar.gz"
31-
fakeReleaseURLWindows = "https://github.com/cdr/coder-cli/releases/download/v1.23.4-rc.5/coder-cli-windows-amd64.zip"
32-
goosWindows = "windows"
33-
goosLinux = "linux"
25+
fakeExePathLinux = "/home/user/bin/coder"
26+
fakeExePathWindows = `C:\Users\user\bin\coder.exe`
27+
fakeCoderURL = "https://my.cdr.dev"
28+
fakeNewVersion = "1.23.4-rc.5+678-gabcdef-12345678"
29+
fakeOldVersion = "1.22.4-rc.5+678-gabcdef-12345678"
30+
filenameLinux = "coder-cli-linux-amd64.tar.gz"
31+
filenameWindows = "coder-cli-windows.zip"
32+
fakeGithubReleaseURL = "https://api.github.com/repos/cdr/coder-cli/releases/tags/v1.23.4-rc.5"
3433
)
3534

3635
var (
37-
apiPrivateVersionURL = fakeCoderURL + "/api/private/version"
38-
fakeError = xerrors.New("fake error for testing")
39-
fakeNewVersionJSON = fmt.Sprintf(`{"version":%q}`, fakeNewVersion)
40-
fakeOldVersionJSON = fmt.Sprintf(`{"version":%q}`, fakeOldVersion)
36+
apiPrivateVersionURL = fakeCoderURL + apiPrivateVersion
37+
fakeError = xerrors.New("fake error for testing")
38+
fakeNewVersionJSON = fmt.Sprintf(`{"version":%q}`, fakeNewVersion)
39+
fakeOldVersionJSON = fmt.Sprintf(`{"version":%q}`, fakeOldVersion)
40+
fakeAssetURLLinux = "https://github.com/cdr/coder-cli/releases/download/v1.23.4-rc.5/" + filenameLinux
41+
fakeAssetURLWindows = "https://github.com/cdr/coder-cli/releases/download/v1.23.4-rc.5/" + filenameWindows
42+
fakeGithubReleaseJSON = fmt.Sprintf(`{"assets":[{"name":%q,"browser_download_url":%q},{"name":%q,"browser_download_url":%q}]}`, filenameLinux, fakeAssetURLLinux, filenameWindows, fakeAssetURLWindows)
4143
)
4244

4345
func Test_updater_run(t *testing.T) {
@@ -113,7 +115,8 @@ func Test_updater_run(t *testing.T) {
113115
run(t, "update coder - explicit version specified", func(t *testing.T, p *params) {
114116
fakeFile(t, p.Fakefs, fakeExePathLinux, 0755, fakeOldVersion)
115117
p.HTTPClient.M[apiPrivateVersionURL] = newFakeGetterResponse([]byte(fakeOldVersionJSON), 200, variadicS(), nil)
116-
p.HTTPClient.M[fakeReleaseURLLinux] = newFakeGetterResponse(fakeValidTgzBytes, 200, variadicS(), nil)
118+
p.HTTPClient.M[fakeGithubReleaseURL] = newFakeGetterResponse([]byte(fakeGithubReleaseJSON), 200, variadicS(), nil)
119+
p.HTTPClient.M[fakeAssetURLLinux] = newFakeGetterResponse(fakeValidTgzBytes, 200, variadicS(), nil)
117120
p.VersionF = func() string { return fakeOldVersion }
118121
p.ConfirmF = fakeConfirmYes
119122
p.Execer.M[p.ExecutablePath+".new --version"] = fakeExecerResult{[]byte(fakeNewVersion), nil}
@@ -127,7 +130,8 @@ func Test_updater_run(t *testing.T) {
127130
run(t, "update coder - old to new", func(t *testing.T, p *params) {
128131
fakeFile(t, p.Fakefs, fakeExePathLinux, 0755, fakeOldVersion)
129132
p.HTTPClient.M[apiPrivateVersionURL] = newFakeGetterResponse([]byte(fakeNewVersionJSON), 200, variadicS(), nil)
130-
p.HTTPClient.M[fakeReleaseURLLinux] = newFakeGetterResponse(fakeValidTgzBytes, 200, variadicS(), nil)
133+
p.HTTPClient.M[fakeGithubReleaseURL] = newFakeGetterResponse([]byte(fakeGithubReleaseJSON), 200, variadicS(), nil)
134+
p.HTTPClient.M[fakeAssetURLLinux] = newFakeGetterResponse(fakeValidTgzBytes, 200, variadicS(), nil)
131135
p.VersionF = func() string { return fakeOldVersion }
132136
p.ConfirmF = fakeConfirmYes
133137
p.Execer.M[p.ExecutablePath+".new --version"] = fakeExecerResult{[]byte(fakeNewVersion), nil}
@@ -142,7 +146,8 @@ func Test_updater_run(t *testing.T) {
142146
p.ExecutablePath = "/home/user/bin/coder-cli"
143147
fakeFile(t, p.Fakefs, p.ExecutablePath, 0755, fakeOldVersion)
144148
p.HTTPClient.M[apiPrivateVersionURL] = newFakeGetterResponse([]byte(fakeNewVersionJSON), 200, variadicS(), nil)
145-
p.HTTPClient.M[fakeReleaseURLLinux] = newFakeGetterResponse(fakeValidTgzBytes, 200, variadicS(), nil)
149+
p.HTTPClient.M[fakeGithubReleaseURL] = newFakeGetterResponse([]byte(fakeGithubReleaseJSON), 200, variadicS(), nil)
150+
p.HTTPClient.M[fakeAssetURLLinux] = newFakeGetterResponse(fakeValidTgzBytes, 200, variadicS(), nil)
146151
p.VersionF = func() string { return fakeOldVersion }
147152
p.ConfirmF = fakeConfirmYes
148153
p.Execer.M[p.ExecutablePath+".new --version"] = fakeExecerResult{[]byte(fakeNewVersion), nil}
@@ -158,7 +163,8 @@ func Test_updater_run(t *testing.T) {
158163
p.ExecutablePath = fakeExePathWindows
159164
fakeFile(t, p.Fakefs, fakeExePathWindows, 0755, fakeOldVersion)
160165
p.HTTPClient.M[apiPrivateVersionURL] = newFakeGetterResponse([]byte(fakeNewVersionJSON), 200, variadicS(), nil)
161-
p.HTTPClient.M[fakeReleaseURLWindows] = newFakeGetterResponse(fakeValidZipBytes, 200, variadicS(), nil)
166+
p.HTTPClient.M[fakeGithubReleaseURL] = newFakeGetterResponse([]byte(fakeGithubReleaseJSON), 200, variadicS(), nil)
167+
p.HTTPClient.M[fakeAssetURLWindows] = newFakeGetterResponse(fakeValidZipBytes, 200, variadicS(), nil)
162168
p.VersionF = func() string { return fakeOldVersion }
163169
p.ConfirmF = fakeConfirmYes
164170
p.Execer.M[p.ExecutablePath+".new --version"] = fakeExecerResult{[]byte(fakeNewVersion), nil}
@@ -172,7 +178,8 @@ func Test_updater_run(t *testing.T) {
172178
run(t, "update coder - old to new forced", func(t *testing.T, p *params) {
173179
fakeFile(t, p.Fakefs, fakeExePathLinux, 0755, fakeOldVersion)
174180
p.HTTPClient.M[apiPrivateVersionURL] = newFakeGetterResponse([]byte(fakeNewVersionJSON), 200, variadicS(), nil)
175-
p.HTTPClient.M[fakeReleaseURLLinux] = newFakeGetterResponse(fakeValidTgzBytes, 200, variadicS(), nil)
181+
p.HTTPClient.M[fakeGithubReleaseURL] = newFakeGetterResponse([]byte(fakeGithubReleaseJSON), 200, variadicS(), nil)
182+
p.HTTPClient.M[fakeAssetURLLinux] = newFakeGetterResponse(fakeValidTgzBytes, 200, variadicS(), nil)
176183
p.VersionF = func() string { return fakeOldVersion }
177184
p.Execer.M[p.ExecutablePath+".new --version"] = fakeExecerResult{[]byte(fakeNewVersion), nil}
178185
u := fromParams(p)
@@ -240,10 +247,24 @@ func Test_updater_run(t *testing.T) {
240247
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeOldVersion)
241248
})
242249

250+
run(t, "update coder - failed to query github releases", func(t *testing.T, p *params) {
251+
fakeFile(t, p.Fakefs, fakeExePathLinux, 0755, fakeOldVersion)
252+
p.HTTPClient.M[apiPrivateVersionURL] = newFakeGetterResponse([]byte(fakeNewVersionJSON), 200, variadicS(), nil)
253+
p.HTTPClient.M[fakeGithubReleaseURL] = newFakeGetterResponse([]byte{}, 0, variadicS(), fakeError)
254+
p.VersionF = func() string { return fakeOldVersion }
255+
p.ConfirmF = fakeConfirmYes
256+
u := fromParams(p)
257+
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeOldVersion)
258+
err := u.Run(p.Ctx, false, fakeCoderURL, "")
259+
assertCLIError(t, "update coder - failed to query github releases", err, "failed to query github assets", fakeError.Error())
260+
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeOldVersion)
261+
})
262+
243263
run(t, "update coder - failed to fetch URL", func(t *testing.T, p *params) {
244264
fakeFile(t, p.Fakefs, fakeExePathLinux, 0755, fakeOldVersion)
245265
p.HTTPClient.M[apiPrivateVersionURL] = newFakeGetterResponse([]byte(fakeNewVersionJSON), 200, variadicS(), nil)
246-
p.HTTPClient.M[fakeReleaseURLLinux] = newFakeGetterResponse([]byte{}, 0, variadicS(), fakeError)
266+
p.HTTPClient.M[fakeGithubReleaseURL] = newFakeGetterResponse([]byte(fakeGithubReleaseJSON), 200, variadicS(), nil)
267+
p.HTTPClient.M[fakeAssetURLLinux] = newFakeGetterResponse([]byte{}, 0, variadicS(), fakeError)
247268
p.VersionF = func() string { return fakeOldVersion }
248269
p.ConfirmF = fakeConfirmYes
249270
u := fromParams(p)
@@ -256,7 +277,8 @@ func Test_updater_run(t *testing.T) {
256277
run(t, "update coder - release URL 404", func(t *testing.T, p *params) {
257278
fakeFile(t, p.Fakefs, fakeExePathLinux, 0755, fakeOldVersion)
258279
p.HTTPClient.M[apiPrivateVersionURL] = newFakeGetterResponse([]byte(fakeNewVersionJSON), 200, variadicS(), nil)
259-
p.HTTPClient.M[fakeReleaseURLLinux] = newFakeGetterResponse([]byte{}, 404, variadicS(), nil)
280+
p.HTTPClient.M[fakeGithubReleaseURL] = newFakeGetterResponse([]byte(fakeGithubReleaseJSON), 200, variadicS(), nil)
281+
p.HTTPClient.M[fakeAssetURLLinux] = newFakeGetterResponse([]byte{}, 404, variadicS(), nil)
260282
p.VersionF = func() string { return fakeOldVersion }
261283
p.ConfirmF = fakeConfirmYes
262284
u := fromParams(p)
@@ -269,7 +291,8 @@ func Test_updater_run(t *testing.T) {
269291
run(t, "update coder - invalid tgz archive", func(t *testing.T, p *params) {
270292
fakeFile(t, p.Fakefs, fakeExePathLinux, 0755, fakeOldVersion)
271293
p.HTTPClient.M[apiPrivateVersionURL] = newFakeGetterResponse([]byte(fakeNewVersionJSON), 200, variadicS(), nil)
272-
p.HTTPClient.M[fakeReleaseURLLinux] = newFakeGetterResponse([]byte{}, 200, variadicS(), nil)
294+
p.HTTPClient.M[fakeGithubReleaseURL] = newFakeGetterResponse([]byte(fakeGithubReleaseJSON), 200, variadicS(), nil)
295+
p.HTTPClient.M[fakeAssetURLLinux] = newFakeGetterResponse([]byte{}, 200, variadicS(), nil)
273296
p.VersionF = func() string { return fakeOldVersion }
274297
p.ConfirmF = fakeConfirmYes
275298
u := fromParams(p)
@@ -284,7 +307,8 @@ func Test_updater_run(t *testing.T) {
284307
p.ExecutablePath = fakeExePathWindows
285308
fakeFile(t, p.Fakefs, fakeExePathWindows, 0755, fakeOldVersion)
286309
p.HTTPClient.M[apiPrivateVersionURL] = newFakeGetterResponse([]byte(fakeNewVersionJSON), 200, variadicS(), nil)
287-
p.HTTPClient.M[fakeReleaseURLWindows] = newFakeGetterResponse([]byte{}, 200, variadicS(), nil)
310+
p.HTTPClient.M[fakeGithubReleaseURL] = newFakeGetterResponse([]byte(fakeGithubReleaseJSON), 200, variadicS(), nil)
311+
p.HTTPClient.M[fakeAssetURLWindows] = newFakeGetterResponse([]byte{}, 200, variadicS(), nil)
288312
p.VersionF = func() string { return fakeOldVersion }
289313
p.ConfirmF = fakeConfirmYes
290314
u := fromParams(p)
@@ -299,7 +323,8 @@ func Test_updater_run(t *testing.T) {
299323
p.Fakefs = afero.NewReadOnlyFs(rwfs)
300324
fakeFile(t, rwfs, fakeExePathLinux, 0755, fakeOldVersion)
301325
p.HTTPClient.M[apiPrivateVersionURL] = newFakeGetterResponse([]byte(fakeNewVersionJSON), 200, variadicS(), nil)
302-
p.HTTPClient.M[fakeReleaseURLLinux] = newFakeGetterResponse(fakeValidTgzBytes, 200, variadicS(), nil)
326+
p.HTTPClient.M[fakeGithubReleaseURL] = newFakeGetterResponse([]byte(fakeGithubReleaseJSON), 200, variadicS(), nil)
327+
p.HTTPClient.M[fakeAssetURLLinux] = newFakeGetterResponse(fakeValidTgzBytes, 200, variadicS(), nil)
303328
p.VersionF = func() string { return fakeOldVersion }
304329
p.ConfirmF = fakeConfirmYes
305330
u := fromParams(p)
@@ -312,7 +337,8 @@ func Test_updater_run(t *testing.T) {
312337
run(t, "update coder - cannot exec new binary", func(t *testing.T, p *params) {
313338
fakeFile(t, p.Fakefs, fakeExePathLinux, 0755, fakeOldVersion)
314339
p.HTTPClient.M[apiPrivateVersionURL] = newFakeGetterResponse([]byte(fakeNewVersionJSON), 200, variadicS(), nil)
315-
p.HTTPClient.M[fakeReleaseURLLinux] = newFakeGetterResponse(fakeValidTgzBytes, 200, variadicS(), nil)
340+
p.HTTPClient.M[fakeGithubReleaseURL] = newFakeGetterResponse([]byte(fakeGithubReleaseJSON), 200, variadicS(), nil)
341+
p.HTTPClient.M[fakeAssetURLLinux] = newFakeGetterResponse(fakeValidTgzBytes, 200, variadicS(), nil)
316342
p.VersionF = func() string { return fakeOldVersion }
317343
p.ConfirmF = fakeConfirmYes
318344
p.Execer.M[p.ExecutablePath+".new --version"] = fakeExecerResult{nil, fakeError}

0 commit comments

Comments
 (0)