Skip to content

feat: add version checking to CLI #2643

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Jun 29, 2022
Next Next commit
feat: add version checking to CLI
  • Loading branch information
sreya committed Jun 24, 2022
commit fe44291a2f11acb00fc01ea21b837b7e8a020fa4
36 changes: 35 additions & 1 deletion buildinfo/buildinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package buildinfo
import (
"fmt"
"runtime/debug"
"strings"
"sync"
"time"

Expand All @@ -24,6 +25,11 @@ var (
tag string
)

const (
// develPrefix is prefixed to developer versions of the application.
develPrefix = "v0.0.0-devel"
)

// Version returns the semantic version of the build.
// Use golang.org/x/mod/semver to compare versions.
func Version() string {
Expand All @@ -35,7 +41,7 @@ func Version() string {
if tag == "" {
// This occurs when the tag hasn't been injected,
// like when using "go run".
version = "v0.0.0-devel" + revision
version = develPrefix + revision
return
}
version = "v" + tag
Expand All @@ -48,6 +54,34 @@ func Version() string {
return version
}

// VersionsMatch compares the two versions. It assumes the versions match if
// the major and the minor versions are equivalent. Patch versions are
// disregarded. If it detects that either version is a developer build it
// returns true.
func VersionsMatch(v1, v2 string) bool {
// Developer versions are disregarded...hopefully they know what they are
// doing.
if strings.HasPrefix(v1, develPrefix) || strings.HasPrefix(v2, develPrefix) {
return true
}

v1Toks := strings.Split(v1, ".")
v2Toks := strings.Split(v2, ".")

// Versions should be formatted as "<major>.<minor>.<patch>".
// We assume malformed versions are evidence of a bug and return false.
if len(v1Toks) < 3 || len(v2Toks) < 3 {
return false
}

// Slice off the patch suffix. Patch versions should be non-breaking
// changes.
v1MajorMinor := strings.Join(v1Toks[:2], ".")
v2MajorMinor := strings.Join(v2Toks[:2], ".")

return v1MajorMinor == v2MajorMinor
}

// ExternalURL returns a URL referencing the current Coder version.
// For production builds, this will link directly to a release.
// For development builds, this will link to a commit.
Expand Down
70 changes: 70 additions & 0 deletions buildinfo/buildinfo_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package buildinfo_test

import (
"fmt"
"testing"

"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -29,4 +30,73 @@ func TestBuildInfo(t *testing.T) {
_, valid := buildinfo.Time()
require.False(t, valid)
})

t.Run("VersionsMatch", func(t *testing.T) {
t.Parallel()

type testcase struct {
name string
v1 string
v2 string
expectMatch bool
}

cases := []testcase{
{
name: "OK",
v1: "v1.2.3",
v2: "v1.2.3",
expectMatch: true,
},
// Test that we return false if a version is malformed.
{
name: "MalformedIgnored",
v1: "v1.2.3",
v2: "v1.2",
expectMatch: false,
},
// Test that we return true if a developer version is detected.
// Developers do not need to be warned of mismatched versions.
{
name: "DevelIgnored",
v1: "v0.0.0-devel+123abac",
v2: "v1.2.3",
expectMatch: true,
},
{
name: "MajorMismatch",
v1: "v1.2.3",
v2: "v0.1.2",
expectMatch: false,
},
{
name: "MinorMismatch",
v1: "v1.2.3",
v2: "v1.3.2",
expectMatch: false,
},
// Different patches are ok, breaking changes are not allowed
// in patches.
{
name: "PatchMismatch",
v1: "v1.2.3+hash.whocares",
v2: "v1.2.4+somestuff.hm.ok",
expectMatch: true,
},
}

for _, c := range cases {
// It's very important to do this since we're running the tests
// in parallel. Otherwise you will likely get the last element
// in the list since the goroutines will likely start executing
// after the for loop has completed.
c := c
t.Run(c.name, func(t *testing.T) {
t.Parallel()
require.Equal(t, c.expectMatch, buildinfo.VersionsMatch(c.v1, c.v2),
fmt.Sprintf("expected match=%v for version %s and %s", c.expectMatch, c.v1, c.v2),
)
})
}
})
}