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

Show warning on coder-cli / Coder API version mismatch #158

Merged
merged 2 commits into from
Oct 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ci/steps/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ build() {
echo "Building coder-cli for $GOOS-$GOARCH..."

tmpdir=$(mktemp -d)
go build -ldflags "-X main.version=${tag}" -o "$tmpdir/coder" ../../cmd/coder
go build -ldflags "-X cdr.dev/coder-cli/internal/version.Version=${tag}" -o "$tmpdir/coder" ../../cmd/coder

pushd "$tmpdir"
if [[ "$GOOS" == "windows" ]]; then
Expand Down
6 changes: 2 additions & 4 deletions cmd/coder/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@ import (

"cdr.dev/coder-cli/internal/clog"
"cdr.dev/coder-cli/internal/cmd"
"cdr.dev/coder-cli/internal/version"
"cdr.dev/coder-cli/internal/x/xterminal"
)

// Using a global for the version so it can be set at build time using ldflags.
var version = "unknown"

func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
Expand All @@ -39,7 +37,7 @@ func main() {
}()

app := cmd.Make()
app.Version = fmt.Sprintf("%s %s %s/%s", version, runtime.Version(), runtime.GOOS, runtime.GOARCH)
app.Version = fmt.Sprintf("%s %s %s/%s", version.Version, runtime.Version(), runtime.GOOS, runtime.GOARCH)

if err := app.ExecuteContext(ctx); err != nil {
clog.Log(err)
Expand Down
22 changes: 22 additions & 0 deletions coder-sdk/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package coder

import (
"context"
"net/http"
)

// APIVersion parses the coder-version http header from an authenticated request.
func (c Client) APIVersion(ctx context.Context) (string, error) {
const coderVersionHeaderKey = "coder-version"
resp, err := c.request(ctx, http.MethodGet, "/api/users/"+Me, nil)
if err != nil {
return "", err
}

version := resp.Header.Get(coderVersionHeaderKey)
if version == "" {
version = "unknown"
}

return version, nil
}
16 changes: 13 additions & 3 deletions internal/cmd/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"context"
"fmt"
"net/http"
"net/url"

Expand All @@ -10,6 +11,7 @@ import (
"cdr.dev/coder-cli/coder-sdk"
"cdr.dev/coder-cli/internal/clog"
"cdr.dev/coder-cli/internal/config"
"cdr.dev/coder-cli/internal/version"
)

var errNeedLogin = clog.Fatal(
Expand All @@ -18,6 +20,7 @@ var errNeedLogin = clog.Fatal(
)

func newClient() (*coder.Client, error) {
ctx := context.Background()
sessionToken, err := config.Session.Read()
if err != nil {
return nil, errNeedLogin
Expand All @@ -38,9 +41,7 @@ func newClient() (*coder.Client, error) {
Token: sessionToken,
}

// Make sure we can make a request so the final
// error is more clean.
_, err = c.Me(context.Background())
apiVersion, err := c.APIVersion(ctx)
if err != nil {
var he *coder.HTTPError
if xerrors.As(err, &he) {
Expand All @@ -52,5 +53,14 @@ func newClient() (*coder.Client, error) {
return nil, err
}

if !version.VersionsMatch(apiVersion) {
clog.Log(clog.Warn(
"version mismatch detected",
fmt.Sprintf("coder-cli version: %s", version.Version),
fmt.Sprintf("Coder API version: %s", apiVersion), clog.BlankLine,
clog.Tipf("download the appropriate version here: https://github.com/cdr/coder-cli/releases"),
Comment on lines +57 to +61
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to make sure, this is thrown to stderr correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, all log levels are thrown in stderr.

))
}

return c, nil
}
18 changes: 18 additions & 0 deletions internal/version/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package version

import (
"strings"
)

// Version is populated at compile-time with the current coder-cli version.
var Version string = "unknown"

// VersionMatch compares the given APIVersion to the compile-time injected coder-cli version.
func VersionsMatch(apiVersion string) bool {
withoutPatchRelease := strings.Split(Version, ".")
if len(withoutPatchRelease) < 3 {
return false
}
majorMinor := strings.Join(withoutPatchRelease[:len(withoutPatchRelease)-1], ".")
return strings.HasPrefix(strings.TrimPrefix(apiVersion, "v"), strings.TrimPrefix(majorMinor, "v"))
}
29 changes: 29 additions & 0 deletions internal/version/version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package version

import (
"testing"

"cdr.dev/slog/sloggers/slogtest/assert"
)

func TestVersion(t *testing.T) {
Version = "1.12.1"
match := VersionsMatch("1.12.2")
assert.True(t, "versions match", match)

Version = "v1.14.1"
match = VersionsMatch("1.15.2")
assert.True(t, "versions do not match", !match)

Version = "v1.15.4"
match = VersionsMatch("1.15.2")
assert.True(t, "versions do match", match)

Version = "1.15.4"
match = VersionsMatch("v1.15.2")
assert.True(t, "versions do match", match)

Version = "1.15.4"
match = VersionsMatch("v2.15.2")
assert.True(t, "versions do not match", !match)
}