mirror of
https://github.com/coder/coder.git
synced 2026-06-03 04:58:23 +00:00
feat: add customizable upgrade message on client/server version mismatch (#11587)
This commit is contained in:
+2
-1
@@ -18,6 +18,7 @@ import (
|
||||
|
||||
"github.com/coder/pretty"
|
||||
|
||||
"github.com/coder/coder/v2/buildinfo"
|
||||
"github.com/coder/coder/v2/cli/clibase"
|
||||
"github.com/coder/coder/v2/cli/cliui"
|
||||
"github.com/coder/coder/v2/coderd/userpassword"
|
||||
@@ -175,7 +176,7 @@ func (r *RootCmd) login() *clibase.Cmd {
|
||||
// Try to check the version of the server prior to logging in.
|
||||
// It may be useful to warn the user if they are trying to login
|
||||
// on a very old client.
|
||||
err = r.checkVersions(inv, client)
|
||||
err = r.checkVersions(inv, client, buildinfo.Version())
|
||||
if err != nil {
|
||||
// Checking versions isn't a fatal error so we print a warning
|
||||
// and proceed.
|
||||
|
||||
+28
-17
@@ -602,7 +602,7 @@ func (r *RootCmd) PrintWarnings(client *codersdk.Client) clibase.MiddlewareFunc
|
||||
warningErr = make(chan error)
|
||||
)
|
||||
go func() {
|
||||
versionErr <- r.checkVersions(inv, client)
|
||||
versionErr <- r.checkVersions(inv, client, buildinfo.Version())
|
||||
close(versionErr)
|
||||
}()
|
||||
|
||||
@@ -812,7 +812,12 @@ func formatExamples(examples ...example) string {
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (r *RootCmd) checkVersions(i *clibase.Invocation, client *codersdk.Client) error {
|
||||
// checkVersions checks to see if there's a version mismatch between the client
|
||||
// and server and prints a message nudging the user to upgrade if a mismatch
|
||||
// is detected. forceCheck is a test flag and should always be false in production.
|
||||
//
|
||||
//nolint:revive
|
||||
func (r *RootCmd) checkVersions(i *clibase.Invocation, client *codersdk.Client, clientVersion string) error {
|
||||
if r.noVersionCheck {
|
||||
return nil
|
||||
}
|
||||
@@ -820,30 +825,26 @@ func (r *RootCmd) checkVersions(i *clibase.Invocation, client *codersdk.Client)
|
||||
ctx, cancel := context.WithTimeout(i.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
clientVersion := buildinfo.Version()
|
||||
info, err := client.BuildInfo(ctx)
|
||||
serverInfo, err := client.BuildInfo(ctx)
|
||||
// Avoid printing errors that are connection-related.
|
||||
if isConnectionError(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return xerrors.Errorf("build info: %w", err)
|
||||
}
|
||||
|
||||
fmtWarningText := `version mismatch: client %s, server %s
|
||||
`
|
||||
// Our installation script doesn't work on Windows, so instead we direct the user
|
||||
// to the GitHub release page to download the latest installer.
|
||||
if runtime.GOOS == "windows" {
|
||||
fmtWarningText += `download the server version from: https://github.com/coder/coder/releases/v%s`
|
||||
} else {
|
||||
fmtWarningText += `download the server version with: 'curl -L https://coder.com/install.sh | sh -s -- --version %s'`
|
||||
}
|
||||
if !buildinfo.VersionsMatch(clientVersion, serverInfo.Version) {
|
||||
upgradeMessage := defaultUpgradeMessage(serverInfo.CanonicalVersion())
|
||||
if serverInfo.UpgradeMessage != "" {
|
||||
upgradeMessage = serverInfo.UpgradeMessage
|
||||
}
|
||||
|
||||
if !buildinfo.VersionsMatch(clientVersion, info.Version) {
|
||||
warn := cliui.DefaultStyles.Warn
|
||||
_, _ = fmt.Fprintf(i.Stderr, pretty.Sprint(warn, fmtWarningText), clientVersion, info.Version, strings.TrimPrefix(info.CanonicalVersion(), "v"))
|
||||
fmtWarningText := "version mismatch: client %s, server %s\n%s"
|
||||
fmtWarn := pretty.Sprint(cliui.DefaultStyles.Warn, fmtWarningText)
|
||||
warning := fmt.Sprintf(fmtWarn, clientVersion, serverInfo.Version, upgradeMessage)
|
||||
|
||||
_, _ = fmt.Fprint(i.Stderr, warning)
|
||||
_, _ = fmt.Fprintln(i.Stderr)
|
||||
}
|
||||
|
||||
@@ -1216,3 +1217,13 @@ func SlimUnsupported(w io.Writer, cmd string) {
|
||||
//nolint:revive
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func defaultUpgradeMessage(version string) string {
|
||||
// Our installation script doesn't work on Windows, so instead we direct the user
|
||||
// to the GitHub release page to download the latest installer.
|
||||
version = strings.TrimPrefix(version, "v")
|
||||
if runtime.GOOS == "windows" {
|
||||
return fmt.Sprintf("download the server version from: https://github.com/coder/coder/releases/v%s", version)
|
||||
}
|
||||
return fmt.Sprintf("download the server version with: 'curl -L https://coder.com/install.sh | sh -s -- --version %s'", version)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/goleak"
|
||||
|
||||
"github.com/coder/coder/v2/buildinfo"
|
||||
"github.com/coder/coder/v2/cli/cliui"
|
||||
"github.com/coder/coder/v2/coderd"
|
||||
"github.com/coder/coder/v2/coderd/httpapi"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/pretty"
|
||||
)
|
||||
|
||||
func Test_formatExamples(t *testing.T) {
|
||||
@@ -84,3 +96,85 @@ func TestMain(m *testing.M) {
|
||||
goleak.IgnoreTopFunction("github.com/lib/pq.NewDialListener"),
|
||||
)
|
||||
}
|
||||
|
||||
func Test_checkVersions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("CustomUpgradeMessage", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
expectedUpgradeMessage := "My custom upgrade message"
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
httpapi.Write(r.Context(), rw, http.StatusOK, codersdk.BuildInfoResponse{
|
||||
ExternalURL: buildinfo.ExternalURL(),
|
||||
// Provide a version that will not match
|
||||
Version: "v1.0.0",
|
||||
AgentAPIVersion: coderd.AgentAPIVersionREST,
|
||||
// does not matter what the url is
|
||||
DashboardURL: "https://example.com",
|
||||
WorkspaceProxy: false,
|
||||
UpgradeMessage: expectedUpgradeMessage,
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
surl, err := url.Parse(srv.URL)
|
||||
require.NoError(t, err)
|
||||
|
||||
client := codersdk.New(surl)
|
||||
|
||||
r := &RootCmd{}
|
||||
|
||||
cmd, err := r.Command(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
var buf bytes.Buffer
|
||||
inv := cmd.Invoke()
|
||||
inv.Stderr = &buf
|
||||
|
||||
err = r.checkVersions(inv, client, "v2.0.0")
|
||||
require.NoError(t, err)
|
||||
|
||||
fmtOutput := fmt.Sprintf("version mismatch: client v2.0.0, server v1.0.0\n%s", expectedUpgradeMessage)
|
||||
expectedOutput := fmt.Sprintln(pretty.Sprint(cliui.DefaultStyles.Warn, fmtOutput))
|
||||
require.Equal(t, expectedOutput, buf.String())
|
||||
})
|
||||
|
||||
t.Run("DefaultUpgradeMessage", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
httpapi.Write(r.Context(), rw, http.StatusOK, codersdk.BuildInfoResponse{
|
||||
ExternalURL: buildinfo.ExternalURL(),
|
||||
// Provide a version that will not match
|
||||
Version: "v1.0.0",
|
||||
AgentAPIVersion: coderd.AgentAPIVersionREST,
|
||||
// does not matter what the url is
|
||||
DashboardURL: "https://example.com",
|
||||
WorkspaceProxy: false,
|
||||
UpgradeMessage: "",
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
surl, err := url.Parse(srv.URL)
|
||||
require.NoError(t, err)
|
||||
|
||||
client := codersdk.New(surl)
|
||||
|
||||
r := &RootCmd{}
|
||||
|
||||
cmd, err := r.Command(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
var buf bytes.Buffer
|
||||
inv := cmd.Invoke()
|
||||
inv.Stderr = &buf
|
||||
|
||||
err = r.checkVersions(inv, client, "v2.0.0")
|
||||
require.NoError(t, err)
|
||||
|
||||
fmtOutput := fmt.Sprintf("version mismatch: client v2.0.0, server v1.0.0\n%s", defaultUpgradeMessage("v1.0.0"))
|
||||
expectedOutput := fmt.Sprintln(pretty.Sprint(cliui.DefaultStyles.Warn, fmtOutput))
|
||||
require.Equal(t, expectedOutput, buf.String())
|
||||
})
|
||||
}
|
||||
|
||||
+5
@@ -65,6 +65,11 @@ CLIENT OPTIONS:
|
||||
These options change the behavior of how clients interact with the Coder.
|
||||
Clients include the coder cli, vs code extension, and the web UI.
|
||||
|
||||
--cli-upgrade-message string, $CODER_CLI_UPGRADE_MESSAGE
|
||||
The upgrade message to display to users when a client/server mismatch
|
||||
is detected. By default it instructs users to update using 'curl -L
|
||||
https://coder.com/install.sh | sh'.
|
||||
|
||||
--ssh-config-options string-array, $CODER_SSH_CONFIG_OPTIONS
|
||||
These SSH config options will override the default SSH config options.
|
||||
Provide options in "key=value" or "key value" format separated by
|
||||
|
||||
+5
@@ -433,6 +433,11 @@ client:
|
||||
# incorrectly can break SSH to your deployment, use cautiously.
|
||||
# (default: <unset>, type: string-array)
|
||||
sshConfigOptions: []
|
||||
# The upgrade message to display to users when a client/server mismatch is
|
||||
# detected. By default it instructs users to update using 'curl -L
|
||||
# https://coder.com/install.sh | sh'.
|
||||
# (default: <unset>, type: string)
|
||||
cliUpgradeMessage: ""
|
||||
# The renderer to use when opening a web terminal. Valid values are 'canvas',
|
||||
# 'webgl', or 'dom'.
|
||||
# (default: canvas, type: string)
|
||||
|
||||
Reference in New Issue
Block a user