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
Prev Previous commit
Next Next commit
add more plumbing
  • Loading branch information
sreya committed Jun 24, 2022
commit 19224b050d56c90a4ad5a201add9d47c4137e916
2 changes: 1 addition & 1 deletion buildinfo/buildinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func Version() string {
version += revision
}
})
return version
return "v1.5.0"
}

// VersionsMatch compares the two versions. It assumes the versions match if
Expand Down
9 changes: 9 additions & 0 deletions cli/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ func login() *cobra.Command {
}

client := codersdk.New(serverURL)

// 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 = checkVersions(cmd, client)
if err != nil {
return xerrors.Errorf("check versions: %w", err)
}

hasInitialUser, err := client.HasFirstUser(cmd.Context())
if err != nil {
return xerrors.Errorf("has initial user: %w", err)
Expand Down
52 changes: 49 additions & 3 deletions cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"golang.org/x/xerrors"

"github.com/charmbracelet/lipgloss"
"github.com/kirsle/configdir"
"github.com/mattn/go-isatty"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -39,6 +40,13 @@ const (
varNoOpen = "no-open"
varForceTty = "force-tty"
notLoggedInMessage = "You are not logged in. Try logging in using 'coder login <url>'."

envNoVersionCheck = "CODER_NO_VERSION_WARNING"
)

var (
errUnauthenticated = xerrors.New(notLoggedInMessage)
varNoVersionCheck = false
)

func init() {
Expand All @@ -57,9 +65,23 @@ func Root() *cobra.Command {
SilenceUsage: true,
Long: `Coder — A tool for provisioning self-hosted development environments.
`,
PersistentPreRun: func(cmd *cobra.Command, _ []string) {
PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
if varNoVersionCheck {
return nil
}

client, err := createClient(cmd)
// If the client is unauthenticated we can ignore the check.
// The child commands should handle an unauthenticated client.
if xerrors.Is(err, errUnauthenticated) {
return nil
}
if err != nil {
return xerrors.Errorf("create client: %w", err)
}
return checkVersions(cmd, client)
},

Example: ` Start a Coder server.
` + cliui.Styles.Code.Render("$ coder server") + `

Expand Down Expand Up @@ -99,6 +121,7 @@ func Root() *cobra.Command {

cmd.PersistentFlags().String(varURL, "", "Specify the URL to your deployment.")
cmd.PersistentFlags().String(varToken, "", "Specify an authentication token.")
cliflag.BoolVarP(cmd.PersistentFlags(), &varNoVersionCheck, "no-version-warning", "", envNoVersionCheck, false, "Suppress warning when client and server versions do not match.")
cliflag.String(cmd.PersistentFlags(), varAgentToken, "", "CODER_AGENT_TOKEN", "", "Specify an agent authentication token.")
_ = cmd.PersistentFlags().MarkHidden(varAgentToken)
cliflag.String(cmd.PersistentFlags(), varAgentURL, "", "CODER_AGENT_URL", "", "Specify the URL for an agent to access your deployment.")
Expand Down Expand Up @@ -143,7 +166,7 @@ func createClient(cmd *cobra.Command) (*codersdk.Client, error) {
if err != nil {
// If the configuration files are absent, the user is logged out
if os.IsNotExist(err) {
return nil, xerrors.New(notLoggedInMessage)
return nil, errUnauthenticated
}
return nil, err
}
Expand All @@ -158,7 +181,7 @@ func createClient(cmd *cobra.Command) (*codersdk.Client, error) {
if err != nil {
// If the configuration files are absent, the user is logged out
if os.IsNotExist(err) {
return nil, xerrors.New(notLoggedInMessage)
return nil, errUnauthenticated
}
return nil, err
}
Expand Down Expand Up @@ -332,3 +355,26 @@ func FormatCobraError(err error, cmd *cobra.Command) string {
helpErrMsg := fmt.Sprintf("Run '%s --help' for usage.", cmd.CommandPath())
return cliui.Styles.Error.Render(err.Error() + "\n" + helpErrMsg)
}

func checkVersions(cmd *cobra.Command, client *codersdk.Client) error {
clientVersion := buildinfo.Version()

info, err := client.BuildInfo(cmd.Context())
if err != nil {
return xerrors.Errorf("build info: %w", err)
}

if !buildinfo.VersionsMatch(clientVersion, info.Version) {
warn := cliui.Styles.Warn.Copy().Align(lipgloss.Left)
_, _ = fmt.Fprintf(cmd.OutOrStdout(), warn.Render("client/server versions do not match"))
fmt.Println()
_, _ = fmt.Fprintf(cmd.OutOrStdout(), warn.Render("client version: %s"), clientVersion)
fmt.Println()
_, _ = fmt.Fprintf(cmd.OutOrStdout(), warn.Render("server version: %s"), info.Version)
fmt.Println()
_, _ = fmt.Fprintf(cmd.OutOrStdout(), warn.Render("download the appropriate version from https://github.com/coder/coder/releases/tag/%s"), info.TrimmedVersion())
fmt.Println()
}

return nil
}
13 changes: 13 additions & 0 deletions codersdk/buildinfo.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package codersdk

import (
"bytes"
"context"
"encoding/json"
"net/http"
Expand All @@ -16,6 +17,18 @@ type BuildInfoResponse struct {
Version string `json:"version"`
}

// TrimmedVersion trims build information from the version.
// E.g. 'v0.7.4-devel+11573034' -> 'v0.7.4'.
func (b BuildInfoResponse) TrimmedVersion() string {
// Linter doesn't like strings.Index...
idx := bytes.Index([]byte(b.Version), []byte("-devel"))
if idx < 0 {
return string(b.Version)
}

return b.Version[:idx]
}

// BuildInfo returns build information for this instance of Coder.
func (c *Client) BuildInfo(ctx context.Context) (BuildInfoResponse, error) {
res, err := c.Request(ctx, http.MethodGet, "/api/v2/buildinfo", nil)
Expand Down