Skip to content

Commit fe44291

Browse files
committed
feat: add version checking to CLI
1 parent 1157303 commit fe44291

File tree

2 files changed

+105
-1
lines changed

2 files changed

+105
-1
lines changed

buildinfo/buildinfo.go

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package buildinfo
33
import (
44
"fmt"
55
"runtime/debug"
6+
"strings"
67
"sync"
78
"time"
89

@@ -24,6 +25,11 @@ var (
2425
tag string
2526
)
2627

28+
const (
29+
// develPrefix is prefixed to developer versions of the application.
30+
develPrefix = "v0.0.0-devel"
31+
)
32+
2733
// Version returns the semantic version of the build.
2834
// Use golang.org/x/mod/semver to compare versions.
2935
func Version() string {
@@ -35,7 +41,7 @@ func Version() string {
3541
if tag == "" {
3642
// This occurs when the tag hasn't been injected,
3743
// like when using "go run".
38-
version = "v0.0.0-devel" + revision
44+
version = develPrefix + revision
3945
return
4046
}
4147
version = "v" + tag
@@ -48,6 +54,34 @@ func Version() string {
4854
return version
4955
}
5056

57+
// VersionsMatch compares the two versions. It assumes the versions match if
58+
// the major and the minor versions are equivalent. Patch versions are
59+
// disregarded. If it detects that either version is a developer build it
60+
// returns true.
61+
func VersionsMatch(v1, v2 string) bool {
62+
// Developer versions are disregarded...hopefully they know what they are
63+
// doing.
64+
if strings.HasPrefix(v1, develPrefix) || strings.HasPrefix(v2, develPrefix) {
65+
return true
66+
}
67+
68+
v1Toks := strings.Split(v1, ".")
69+
v2Toks := strings.Split(v2, ".")
70+
71+
// Versions should be formatted as "<major>.<minor>.<patch>".
72+
// We assume malformed versions are evidence of a bug and return false.
73+
if len(v1Toks) < 3 || len(v2Toks) < 3 {
74+
return false
75+
}
76+
77+
// Slice off the patch suffix. Patch versions should be non-breaking
78+
// changes.
79+
v1MajorMinor := strings.Join(v1Toks[:2], ".")
80+
v2MajorMinor := strings.Join(v2Toks[:2], ".")
81+
82+
return v1MajorMinor == v2MajorMinor
83+
}
84+
5185
// ExternalURL returns a URL referencing the current Coder version.
5286
// For production builds, this will link directly to a release.
5387
// For development builds, this will link to a commit.

buildinfo/buildinfo_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package buildinfo_test
22

33
import (
4+
"fmt"
45
"testing"
56

67
"github.com/stretchr/testify/require"
@@ -29,4 +30,73 @@ func TestBuildInfo(t *testing.T) {
2930
_, valid := buildinfo.Time()
3031
require.False(t, valid)
3132
})
33+
34+
t.Run("VersionsMatch", func(t *testing.T) {
35+
t.Parallel()
36+
37+
type testcase struct {
38+
name string
39+
v1 string
40+
v2 string
41+
expectMatch bool
42+
}
43+
44+
cases := []testcase{
45+
{
46+
name: "OK",
47+
v1: "v1.2.3",
48+
v2: "v1.2.3",
49+
expectMatch: true,
50+
},
51+
// Test that we return false if a version is malformed.
52+
{
53+
name: "MalformedIgnored",
54+
v1: "v1.2.3",
55+
v2: "v1.2",
56+
expectMatch: false,
57+
},
58+
// Test that we return true if a developer version is detected.
59+
// Developers do not need to be warned of mismatched versions.
60+
{
61+
name: "DevelIgnored",
62+
v1: "v0.0.0-devel+123abac",
63+
v2: "v1.2.3",
64+
expectMatch: true,
65+
},
66+
{
67+
name: "MajorMismatch",
68+
v1: "v1.2.3",
69+
v2: "v0.1.2",
70+
expectMatch: false,
71+
},
72+
{
73+
name: "MinorMismatch",
74+
v1: "v1.2.3",
75+
v2: "v1.3.2",
76+
expectMatch: false,
77+
},
78+
// Different patches are ok, breaking changes are not allowed
79+
// in patches.
80+
{
81+
name: "PatchMismatch",
82+
v1: "v1.2.3+hash.whocares",
83+
v2: "v1.2.4+somestuff.hm.ok",
84+
expectMatch: true,
85+
},
86+
}
87+
88+
for _, c := range cases {
89+
// It's very important to do this since we're running the tests
90+
// in parallel. Otherwise you will likely get the last element
91+
// in the list since the goroutines will likely start executing
92+
// after the for loop has completed.
93+
c := c
94+
t.Run(c.name, func(t *testing.T) {
95+
t.Parallel()
96+
require.Equal(t, c.expectMatch, buildinfo.VersionsMatch(c.v1, c.v2),
97+
fmt.Sprintf("expected match=%v for version %s and %s", c.expectMatch, c.v1, c.v2),
98+
)
99+
})
100+
}
101+
})
32102
}

0 commit comments

Comments
 (0)