diff --git a/ci/steps/build.sh b/ci/steps/build.sh index 6307c0d7..64ef65b2 100755 --- a/ci/steps/build.sh +++ b/ci/steps/build.sh @@ -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 diff --git a/cmd/coder/main.go b/cmd/coder/main.go index 8ba4ab19..d59a1abe 100644 --- a/cmd/coder/main.go +++ b/cmd/coder/main.go @@ -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() @@ -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) diff --git a/coder-sdk/version.go b/coder-sdk/version.go new file mode 100644 index 00000000..a2211afe --- /dev/null +++ b/coder-sdk/version.go @@ -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 +} diff --git a/internal/cmd/auth.go b/internal/cmd/auth.go index b0105ac4..b048593c 100644 --- a/internal/cmd/auth.go +++ b/internal/cmd/auth.go @@ -2,6 +2,7 @@ package cmd import ( "context" + "fmt" "net/http" "net/url" @@ -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( @@ -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 @@ -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) { @@ -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"), + )) + } + return c, nil } diff --git a/internal/version/version.go b/internal/version/version.go new file mode 100644 index 00000000..00effefa --- /dev/null +++ b/internal/version/version.go @@ -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")) +} diff --git a/internal/version/version_test.go b/internal/version/version_test.go new file mode 100644 index 00000000..9791717e --- /dev/null +++ b/internal/version/version_test.go @@ -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) +}
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: