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

Commit 64a406e

Browse files
authored
feat(cli): compare hotfix version metadata (#450)
* refactor: stop hard-coding tgz and zip in unit tests * feat(cli): compare cli.N build metadata to support hotfixes, add hotfix versions to asset urls * chore: update unit tests
1 parent 6640696 commit 64a406e

File tree

2 files changed

+265
-38
lines changed

2 files changed

+265
-38
lines changed

internal/cmd/update.go

+89-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ import (
1717
"os/exec"
1818
"path"
1919
"path/filepath"
20+
"regexp"
2021
"runtime"
22+
"strconv"
2123
"strings"
2224
"time"
2325

@@ -139,16 +141,26 @@ func (u *updater) Run(ctx context.Context, force bool, coderURLArg string, versi
139141
currentVersion, err := semver.NewVersion(u.versionF())
140142
if err != nil {
141143
clog.LogWarn("failed to determine current version of coder-cli", clog.Causef(err.Error()))
142-
} else if currentVersion.Compare(desiredVersion) == 0 {
144+
} else if compareVersions(currentVersion, desiredVersion) == 0 {
143145
clog.LogInfo("Up to date!")
144146
return nil
145147
}
146148

147149
if !force {
148-
label := fmt.Sprintf("Do you want to download version %d.%d.%d instead",
150+
prerelease := ""
151+
if desiredVersion.Prerelease() != "" {
152+
prerelease = "-" + desiredVersion.Prerelease()
153+
}
154+
hotfix := ""
155+
if hotfixVersion(desiredVersion) != "" {
156+
hotfix = hotfixVersion(desiredVersion)
157+
}
158+
label := fmt.Sprintf("Do you want to download version %d.%d.%d%s%s instead",
149159
desiredVersion.Major(),
150160
desiredVersion.Minor(),
151161
desiredVersion.Patch(),
162+
prerelease,
163+
hotfix,
152164
)
153165
if _, err := u.confirmF(label); err != nil {
154166
return clog.Fatal("user cancelled operation", clog.Tipf(`use "--force" to update without confirmation`))
@@ -218,7 +230,7 @@ func (u *updater) Run(ctx context.Context, force bool, coderURLArg string, versi
218230
return clog.Fatal("failed to update coder binary", clog.Causef(err.Error()))
219231
}
220232

221-
clog.LogSuccess("Updated coder CLI to version " + desiredVersion.String())
233+
clog.LogSuccess("Updated coder CLI")
222234
return nil
223235
}
224236

@@ -308,6 +320,7 @@ func queryGithubAssetURL(httpClient getter, version *semver.Version, ostype stri
308320
fmt.Fprint(&b, "-")
309321
fmt.Fprint(&b, version.Prerelease())
310322
}
323+
fmt.Fprintf(&b, "%s", hotfixVersion(version)) // this will be empty if no hotfix
311324

312325
urlString := fmt.Sprintf("https://api.github.com/repos/cdr/coder-cli/releases/tags/v%s", b.String())
313326
clog.LogInfo("query github releases", fmt.Sprintf("url: %q", urlString))
@@ -493,3 +506,76 @@ func HasFilePathPrefix(s, prefix string) bool {
493506
func defaultExec(ctx context.Context, cmd string, args ...string) ([]byte, error) {
494507
return exec.CommandContext(ctx, cmd, args...).CombinedOutput()
495508
}
509+
510+
// hotfixExpr matches the build metadata used for identifying CLI hotfixes.
511+
var hotfixExpr = regexp.MustCompile(`(?i)^.*?cli\.(\d+).*?$`)
512+
513+
// hotfixVersion returns the hotfix build metadata tag if it is present in v
514+
// and an empty string otherwise.
515+
func hotfixVersion(v *semver.Version) string {
516+
match := hotfixExpr.FindStringSubmatch(v.Metadata())
517+
if len(match) < 2 {
518+
return ""
519+
}
520+
521+
return fmt.Sprintf("+cli.%s", match[1])
522+
}
523+
524+
// compareVersions performs a NON-SEMVER-COMPLIANT comparison of two versions.
525+
// If the two versions differ as per SemVer, then that result is returned.
526+
// Otherwise, the build metadata of the two versions are compared based on
527+
// the `cli.N` hotfix metadata.
528+
//
529+
// Examples:
530+
// compareVersions(semver.MustParse("v1.0.0"), semver.MustParse("v1.0.0"))
531+
// 0
532+
// compareVersions(semver.MustParse("v1.0.0"), semver.MustParse("v1.0.1"))
533+
// 1
534+
// compareVersions(semver.MustParse("v1.0.1"), semver.MustParse("v1.0.0"))
535+
// -1
536+
// compareVersions(semver.MustParse("v1.0.0+cli.0"), semver.MustParse("v1.0.0"))
537+
// 1
538+
// compareVersions(semver.MustParse("v1.0.0+cli.0"), semver.MustParse("v1.0.0+cli.0"))
539+
// 0
540+
// compareVersions(semver.MustParse("v1.0.0"), semver.MustParse("v1.0.0+cli.0"))
541+
// -1
542+
// compareVersions(semver.MustParse("v1.0.0+cli.1"), semver.MustParse("v1.0.0+cli.0"))
543+
// 1
544+
// compareVersions(semver.MustParse("v1.0.0+cli.0"), semver.MustParse("v1.0.0+cli.1"))
545+
// -1
546+
//
547+
func compareVersions(a, b *semver.Version) int {
548+
semverComparison := a.Compare(b)
549+
if semverComparison != 0 {
550+
return semverComparison
551+
}
552+
553+
matchA := hotfixExpr.FindStringSubmatch(a.Metadata())
554+
matchB := hotfixExpr.FindStringSubmatch(b.Metadata())
555+
556+
hotfixA := -1
557+
hotfixB := -1
558+
559+
// extract hotfix versions from the metadata of a and b
560+
if len(matchA) > 1 {
561+
if n, err := strconv.Atoi(matchA[1]); err == nil {
562+
hotfixA = n
563+
}
564+
}
565+
if len(matchB) > 1 {
566+
if n, err := strconv.Atoi(matchB[1]); err == nil {
567+
hotfixB = n
568+
}
569+
}
570+
571+
// compare hotfix versions
572+
if hotfixA < hotfixB {
573+
return -1
574+
}
575+
if hotfixA > hotfixB {
576+
return 1
577+
}
578+
// both versions are the same if their semver and hotfix
579+
// metadata are the same.
580+
return 0
581+
}

0 commit comments

Comments
 (0)