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

Commit 26984a2

Browse files
committed
internal/cmd/update.go: allow explicitly specifying version
1 parent 60a75a1 commit 26984a2

File tree

3 files changed

+103
-54
lines changed

3 files changed

+103
-54
lines changed

docs/coder_update.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ coder update [flags]
1313
### Options
1414

1515
```
16-
--coder string coder instance against which to match version
17-
--force do not prompt for confirmation
18-
-h, --help help for update
16+
--coder string query this coder instance for the matching version
17+
--force do not prompt for confirmation
18+
-h, --help help for update
19+
--version string explicitly specify which version to fetch and install
1920
```
2021

2122
### Options inherited from parent commands

internal/cmd/update.go

Lines changed: 54 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ type updater struct {
4545

4646
func updateCmd() *cobra.Command {
4747
var (
48-
force bool
49-
coderURL string
48+
force bool
49+
coderURL string
50+
versionArg string
5051
)
5152

5253
cmd := &cobra.Command{
@@ -73,12 +74,13 @@ func updateCmd() *cobra.Command {
7374
osF: func() string { return runtime.GOOS },
7475
versionF: func() string { return version.Version },
7576
}
76-
return updater.Run(ctx, force, coderURL)
77+
return updater.Run(ctx, force, coderURL, versionArg)
7778
},
7879
}
7980

8081
cmd.Flags().BoolVar(&force, "force", false, "do not prompt for confirmation")
81-
cmd.Flags().StringVar(&coderURL, "coder", "", "coder instance against which to match version")
82+
cmd.Flags().StringVar(&coderURL, "coder", "", "query this coder instance for the matching version")
83+
cmd.Flags().StringVar(&versionArg, "version", "", "explicitly specify which version to fetch and install")
8284

8385
return cmd
8486
}
@@ -87,7 +89,7 @@ type getter interface {
8789
Get(url string) (*http.Response, error)
8890
}
8991

90-
func (u *updater) Run(ctx context.Context, force bool, coderURLString string) error {
92+
func (u *updater) Run(ctx context.Context, force bool, coderURLArg string, versionArg string) error {
9193
// Check under following directories and warn if coder binary is under them:
9294
// * C:\Windows\
9395
// * homebrew prefix
@@ -119,34 +121,18 @@ func (u *updater) Run(ctx context.Context, force bool, coderURLString string) er
119121
return clog.Fatal("preflight: missing write permission on current binary")
120122
}
121123

122-
var coderURL *url.URL
123-
if coderURLString == "" {
124-
coderURL, err = getCoderConfigURL()
125-
if err != nil {
126-
return clog.Fatal(
127-
"Unable to automatically determine coder URL",
128-
clog.Causef(err.Error()),
129-
clog.BlankLine,
130-
clog.Tipf("use --coder <url> to specify coder URL"),
131-
)
132-
}
133-
} else {
134-
coderURL, err = url.Parse(coderURLString)
135-
if err != nil {
136-
return clog.Fatal("invalid coder URL", err.Error())
137-
}
138-
}
124+
clog.LogInfo(fmt.Sprintf("Current version of coder-cli is %s", version.Version))
139125

140-
desiredVersion, err := getAPIVersionUnauthed(u.httpClient, *coderURL)
126+
desiredVersion, err := getDesiredVersion(u.httpClient, coderURLArg, versionArg)
141127
if err != nil {
142-
return clog.Fatal("fetch api version", clog.Causef(err.Error()))
128+
return clog.Fatal("failed to determine desired version of coder", clog.Causef(err.Error()))
143129
}
144130

145-
clog.LogInfo(fmt.Sprintf("Coder instance at %q reports version %s", coderURL.String(), desiredVersion.String()))
146-
clog.LogInfo(fmt.Sprintf("Current version of coder-cli is %s", version.Version))
147-
148-
if currentVersion, err := semver.StrictNewVersion(u.versionF()); err == nil {
149-
if desiredVersion.Compare(currentVersion) == 0 {
131+
currentVersion, err := semver.StrictNewVersion(u.versionF())
132+
if err != nil {
133+
clog.LogWarn("failed to determine current version of coder-cli", clog.Causef(err.Error()))
134+
} else {
135+
if currentVersion.Compare(desiredVersion) == 0 {
150136
clog.LogInfo("Up to date!")
151137
return nil
152138
}
@@ -221,6 +207,45 @@ func (u *updater) Run(ctx context.Context, force bool, coderURLString string) er
221207
return nil
222208
}
223209

210+
func getDesiredVersion(httpClient getter, coderURLArg string, versionArg string) (*semver.Version, error) {
211+
var coderURL *url.URL
212+
var desiredVersion *semver.Version
213+
var err error
214+
215+
if coderURLArg != "" && versionArg != "" {
216+
clog.LogWarn(fmt.Sprintf("ignoring the version reported by %q", coderURLArg), clog.Causef("--version flag was specified explicitly"))
217+
}
218+
219+
if versionArg != "" {
220+
desiredVersion, err = semver.StrictNewVersion(versionArg)
221+
if err != nil {
222+
return &semver.Version{}, xerrors.Errorf("parse desired version arg: %w", err)
223+
}
224+
return desiredVersion, nil
225+
}
226+
227+
if coderURLArg == "" {
228+
coderURL, err = getCoderConfigURL()
229+
if err != nil {
230+
return &semver.Version{}, xerrors.Errorf("get coder url: %w", err)
231+
}
232+
} else {
233+
coderURL, err = url.Parse(coderURLArg)
234+
if err != nil {
235+
return &semver.Version{}, xerrors.Errorf("parse coder url arg: %w", err)
236+
}
237+
}
238+
239+
desiredVersion, err = getAPIVersionUnauthed(httpClient, *coderURL)
240+
if err != nil {
241+
return &semver.Version{}, xerrors.Errorf("query coder version: %w", err)
242+
}
243+
244+
clog.LogInfo(fmt.Sprintf("Coder instance at %q reports version %s", coderURL.String(), desiredVersion.String()))
245+
246+
return desiredVersion, nil
247+
}
248+
224249
func defaultConfirm(label string) (string, error) {
225250
p := promptui.Prompt{IsConfirm: true, Label: label}
226251
return p.Run()

internal/cmd/update_test.go

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,24 @@ func Test_updater_run(t *testing.T) {
105105
p.VersionF = func() string { return fakeNewVersion }
106106
u := fromParams(p)
107107
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeNewVersion)
108-
err := u.Run(p.Ctx, false, fakeCoderURL)
108+
err := u.Run(p.Ctx, false, fakeCoderURL, "")
109109
assert.Success(t, "update coder - noop", err)
110110
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeNewVersion)
111111
})
112112

113+
run(t, "update coder - explicit version specified", func(t *testing.T, p *params) {
114+
fakeFile(t, p.Fakefs, fakeExePathLinux, 0755, fakeOldVersion)
115+
p.HTTPClient.M[apiPrivateVersionURL] = newFakeGetterResponse([]byte(fakeOldVersionJSON), 200, variadicS(), nil)
116+
p.HTTPClient.M[fakeReleaseURLLinux] = newFakeGetterResponse(fakeValidTgzBytes, 200, variadicS(), nil)
117+
p.VersionF = func() string { return fakeOldVersion }
118+
p.ConfirmF = fakeConfirmYes
119+
u := fromParams(p)
120+
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeOldVersion)
121+
err := u.Run(p.Ctx, false, fakeCoderURL, fakeNewVersion)
122+
assert.Success(t, "update coder - explicit version specified", err)
123+
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeNewVersion)
124+
})
125+
113126
run(t, "update coder - old to new", func(t *testing.T, p *params) {
114127
fakeFile(t, p.Fakefs, fakeExePathLinux, 0755, fakeOldVersion)
115128
p.HTTPClient.M[apiPrivateVersionURL] = newFakeGetterResponse([]byte(fakeNewVersionJSON), 200, variadicS(), nil)
@@ -118,7 +131,7 @@ func Test_updater_run(t *testing.T) {
118131
p.ConfirmF = fakeConfirmYes
119132
u := fromParams(p)
120133
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeOldVersion)
121-
err := u.Run(p.Ctx, false, fakeCoderURL)
134+
err := u.Run(p.Ctx, false, fakeCoderURL, "")
122135
assert.Success(t, "update coder - old to new", err)
123136
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeNewVersion)
124137
})
@@ -132,7 +145,7 @@ func Test_updater_run(t *testing.T) {
132145
p.ConfirmF = fakeConfirmYes
133146
u := fromParams(p)
134147
assertFileContent(t, p.Fakefs, p.ExecutablePath, fakeOldVersion)
135-
err := u.Run(p.Ctx, false, fakeCoderURL)
148+
err := u.Run(p.Ctx, false, fakeCoderURL, "")
136149
assert.Success(t, "update coder - old to new - binary renamed", err)
137150
assertFileContent(t, p.Fakefs, p.ExecutablePath, fakeNewVersion)
138151
})
@@ -147,7 +160,7 @@ func Test_updater_run(t *testing.T) {
147160
p.ConfirmF = fakeConfirmYes
148161
u := fromParams(p)
149162
assertFileContent(t, p.Fakefs, fakeExePathWindows, fakeOldVersion)
150-
err := u.Run(p.Ctx, false, fakeCoderURL)
163+
err := u.Run(p.Ctx, false, fakeCoderURL, "")
151164
assert.Success(t, "update coder - old to new - windows", err)
152165
assertFileContent(t, p.Fakefs, fakeExePathWindows, fakeNewVersion)
153166
})
@@ -159,7 +172,7 @@ func Test_updater_run(t *testing.T) {
159172
p.VersionF = func() string { return fakeOldVersion }
160173
u := fromParams(p)
161174
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeOldVersion)
162-
err := u.Run(p.Ctx, true, fakeCoderURL)
175+
err := u.Run(p.Ctx, true, fakeCoderURL, "")
163176
assert.Success(t, "update coder - old to new forced", err)
164177
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeNewVersion)
165178
})
@@ -171,33 +184,43 @@ func Test_updater_run(t *testing.T) {
171184
p.ConfirmF = fakeConfirmNo
172185
u := fromParams(p)
173186
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeOldVersion)
174-
err := u.Run(p.Ctx, false, fakeCoderURL)
187+
err := u.Run(p.Ctx, false, fakeCoderURL, "")
175188
assertCLIError(t, "update coder - user cancelled", err, "failed to confirm update", "")
176189
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeOldVersion)
177190
})
178191

179192
run(t, "update coder - cannot stat", func(t *testing.T, p *params) {
180193
u := fromParams(p)
181-
err := u.Run(p.Ctx, false, fakeCoderURL)
194+
err := u.Run(p.Ctx, false, fakeCoderURL, "")
182195
assertCLIError(t, "update coder - cannot stat", err, "cannot stat current binary", os.ErrNotExist.Error())
183196
})
184197

185198
run(t, "update coder - no permission", func(t *testing.T, p *params) {
186199
fakeFile(t, p.Fakefs, fakeExePathLinux, 0400, fakeOldVersion)
187200
u := fromParams(p)
188201
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeOldVersion)
189-
err := u.Run(p.Ctx, false, fakeCoderURL)
202+
err := u.Run(p.Ctx, false, fakeCoderURL, "")
190203
assertCLIError(t, "update coder - no permission", err, "missing write permission", "")
191204
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeOldVersion)
192205
})
193206

207+
run(t, "update coder - invalid version arg", func(t *testing.T, p *params) {
208+
fakeFile(t, p.Fakefs, fakeExePathLinux, 0755, fakeOldVersion)
209+
p.VersionF = func() string { return fakeOldVersion }
210+
u := fromParams(p)
211+
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeOldVersion)
212+
err := u.Run(p.Ctx, false, fakeCoderURL, "Invalid Semantic Version")
213+
assertCLIError(t, "update coder - invalid version arg", err, "failed to determine desired version of coder", "Invalid Semantic Version")
214+
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeOldVersion)
215+
})
216+
194217
run(t, "update coder - invalid url", func(t *testing.T, p *params) {
195218
fakeFile(t, p.Fakefs, fakeExePathLinux, 0755, fakeOldVersion)
196219
p.VersionF = func() string { return fakeOldVersion }
197220
u := fromParams(p)
198221
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeOldVersion)
199-
err := u.Run(p.Ctx, false, "h$$p://invalid.url")
200-
assertCLIError(t, "update coder - invalid url", err, "invalid coder URL", "first path segment in URL cannot contain colon")
222+
err := u.Run(p.Ctx, false, "h$$p://invalid.url", "")
223+
assertCLIError(t, "update coder - invalid url", err, "failed to determine desired version of coder", "first path segment in URL cannot contain colon")
201224
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeOldVersion)
202225
})
203226

@@ -207,8 +230,8 @@ func Test_updater_run(t *testing.T) {
207230
p.VersionF = func() string { return fakeOldVersion }
208231
u := fromParams(p)
209232
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeOldVersion)
210-
err := u.Run(p.Ctx, false, fakeCoderURL)
211-
assertCLIError(t, "update coder - fetch api version failure", err, "fetch api version", fakeError.Error())
233+
err := u.Run(p.Ctx, false, fakeCoderURL, "")
234+
assertCLIError(t, "update coder - fetch api version failure", err, "failed to determine desired version of coder", fakeError.Error())
212235
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeOldVersion)
213236
})
214237

@@ -220,7 +243,7 @@ func Test_updater_run(t *testing.T) {
220243
p.ConfirmF = fakeConfirmYes
221244
u := fromParams(p)
222245
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeOldVersion)
223-
err := u.Run(p.Ctx, false, fakeCoderURL)
246+
err := u.Run(p.Ctx, false, fakeCoderURL, "")
224247
assertCLIError(t, "update coder - failed to fetch URL", err, "failed to fetch URL", fakeError.Error())
225248
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeOldVersion)
226249
})
@@ -233,7 +256,7 @@ func Test_updater_run(t *testing.T) {
233256
p.ConfirmF = fakeConfirmYes
234257
u := fromParams(p)
235258
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeOldVersion)
236-
err := u.Run(p.Ctx, false, fakeCoderURL)
259+
err := u.Run(p.Ctx, false, fakeCoderURL, "")
237260
assertCLIError(t, "update coder - release URL 404", err, "failed to fetch release", "status code 404")
238261
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeOldVersion)
239262
})
@@ -246,7 +269,7 @@ func Test_updater_run(t *testing.T) {
246269
p.ConfirmF = fakeConfirmYes
247270
u := fromParams(p)
248271
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeOldVersion)
249-
err := u.Run(p.Ctx, false, fakeCoderURL)
272+
err := u.Run(p.Ctx, false, fakeCoderURL, "")
250273
assertCLIError(t, "update coder - invalid tgz archive", err, "failed to extract coder binary from archive", "unknown archive type")
251274
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeOldVersion)
252275
})
@@ -261,7 +284,7 @@ func Test_updater_run(t *testing.T) {
261284
p.ConfirmF = fakeConfirmYes
262285
u := fromParams(p)
263286
assertFileContent(t, p.Fakefs, p.ExecutablePath, fakeOldVersion)
264-
err := u.Run(p.Ctx, false, fakeCoderURL)
287+
err := u.Run(p.Ctx, false, fakeCoderURL, "")
265288
assertCLIError(t, "update coder - invalid zip archive", err, "failed to extract coder binary from archive", "unknown archive type")
266289
assertFileContent(t, p.Fakefs, p.ExecutablePath, fakeOldVersion)
267290
})
@@ -276,7 +299,7 @@ func Test_updater_run(t *testing.T) {
276299
p.ConfirmF = fakeConfirmYes
277300
u := fromParams(p)
278301
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeOldVersion)
279-
err := u.Run(p.Ctx, false, fakeCoderURL)
302+
err := u.Run(p.Ctx, false, fakeCoderURL, "")
280303
assertCLIError(t, "update coder - read-only fs", err, "failed to create file", "")
281304
assertFileContent(t, p.Fakefs, fakeExePathLinux, fakeOldVersion)
282305
})
@@ -285,35 +308,35 @@ func Test_updater_run(t *testing.T) {
285308
run(t, "update coder - path blocklist - windows", func(t *testing.T, p *params) {
286309
p.ExecutablePath = `C:\Windows\system32\coder.exe`
287310
u := fromParams(p)
288-
err := u.Run(p.Ctx, false, fakeCoderURL)
311+
err := u.Run(p.Ctx, false, fakeCoderURL, "")
289312
assertCLIError(t, "update coder - path blocklist - windows", err, "cowardly refusing to update coder binary", "blocklisted prefix")
290313
})
291314
} else {
292315
run(t, "update coder - path blocklist - coder assets dir", func(t *testing.T, p *params) {
293316
p.ExecutablePath = `/var/tmp/coder/coder`
294317
u := fromParams(p)
295-
err := u.Run(p.Ctx, false, fakeCoderURL)
318+
err := u.Run(p.Ctx, false, fakeCoderURL, "")
296319
assertCLIError(t, "update coder - path blocklist - windows", err, "cowardly refusing to update coder binary", "blocklisted prefix")
297320
})
298321
run(t, "update coder - path blocklist - old homebrew prefix", func(t *testing.T, p *params) {
299322
p.Execer.M["brew --prefix"] = fakeExecerResult{[]byte("/usr/local"), nil}
300323
p.ExecutablePath = `/usr/local/bin/coder`
301324
u := fromParams(p)
302-
err := u.Run(p.Ctx, false, fakeCoderURL)
325+
err := u.Run(p.Ctx, false, fakeCoderURL, "")
303326
assertCLIError(t, "update coder - path blocklist - old homebrew prefix", err, "cowardly refusing to update coder binary", "blocklisted prefix")
304327
})
305328
run(t, "update coder - path blocklist - new homebrew prefix", func(t *testing.T, p *params) {
306329
p.Execer.M["brew --prefix"] = fakeExecerResult{[]byte("/opt/homebrew"), nil}
307330
p.ExecutablePath = `/opt/homebrew/bin/coder`
308331
u := fromParams(p)
309-
err := u.Run(p.Ctx, false, fakeCoderURL)
332+
err := u.Run(p.Ctx, false, fakeCoderURL, "")
310333
assertCLIError(t, "update coder - path blocklist - new homebrew prefix", err, "cowardly refusing to update coder binary", "blocklisted prefix")
311334
})
312335
run(t, "update coder - path blocklist - linuxbrew", func(t *testing.T, p *params) {
313336
p.Execer.M["brew --prefix"] = fakeExecerResult{[]byte("/home/user/.linuxbrew"), nil}
314337
p.ExecutablePath = `/home/user/.linuxbrew/bin/coder`
315338
u := fromParams(p)
316-
err := u.Run(p.Ctx, false, fakeCoderURL)
339+
err := u.Run(p.Ctx, false, fakeCoderURL, "")
317340
assertCLIError(t, "update coder - path blocklist - linuxbrew", err, "cowardly refusing to update coder binary", "blocklisted prefix")
318341
})
319342
}

0 commit comments

Comments
 (0)