@@ -4,11 +4,13 @@ import (
4
4
"fmt"
5
5
"net/url"
6
6
"os"
7
+ "strconv"
7
8
"strings"
8
9
"time"
9
10
10
11
"golang.org/x/xerrors"
11
12
13
+ "github.com/charmbracelet/lipgloss"
12
14
"github.com/kirsle/configdir"
13
15
"github.com/mattn/go-isatty"
14
16
"github.com/spf13/cobra"
@@ -40,7 +42,13 @@ const (
40
42
varForceTty = "force-tty"
41
43
notLoggedInMessage = "You are not logged in. Try logging in using 'coder login <url>'."
42
44
43
- envSessionToken = "CODER_SESSION_TOKEN"
45
+ noVersionCheckFlag = "no-version-warning"
46
+ envNoVersionCheck = "CODER_NO_VERSION_WARNING"
47
+ )
48
+
49
+ var (
50
+ errUnauthenticated = xerrors .New (notLoggedInMessage )
51
+ envSessionToken = "CODER_SESSION_TOKEN"
44
52
)
45
53
46
54
func init () {
@@ -53,12 +61,47 @@ func init() {
53
61
}
54
62
55
63
func Root () * cobra.Command {
64
+ var varSuppressVersion bool
65
+
56
66
cmd := & cobra.Command {
57
67
Use : "coder" ,
58
68
SilenceErrors : true ,
59
69
SilenceUsage : true ,
60
70
Long : `Coder — A tool for provisioning self-hosted development environments.
61
71
` ,
72
+ PersistentPreRun : func (cmd * cobra.Command , args []string ) {
73
+ err := func () error {
74
+ if varSuppressVersion {
75
+ return nil
76
+ }
77
+
78
+ // Login handles checking the versions itself since it
79
+ // has a handle to an unauthenticated client.
80
+ // Server is skipped for obvious reasons.
81
+ if cmd .Name () == "login" || cmd .Name () == "server" {
82
+ return nil
83
+ }
84
+
85
+ client , err := createClient (cmd )
86
+ // If the client is unauthenticated we can ignore the check.
87
+ // The child commands should handle an unauthenticated client.
88
+ if xerrors .Is (err , errUnauthenticated ) {
89
+ return nil
90
+ }
91
+ if err != nil {
92
+ return xerrors .Errorf ("create client: %w" , err )
93
+ }
94
+ return checkVersions (cmd , client )
95
+ }()
96
+ if err != nil {
97
+ // Just log the error here. We never want to fail a command
98
+ // due to a pre-run.
99
+ _ , _ = fmt .Fprintf (cmd .ErrOrStderr (),
100
+ cliui .Styles .Warn .Render ("check versions error: %s" ), err )
101
+ _ , _ = fmt .Fprintln (cmd .ErrOrStderr ())
102
+ }
103
+ },
104
+
62
105
Example : ` Start a Coder server.
63
106
` + cliui .Styles .Code .Render ("$ coder server" ) + `
64
107
@@ -97,6 +140,7 @@ func Root() *cobra.Command {
97
140
cmd .SetUsageTemplate (usageTemplate ())
98
141
99
142
cmd .PersistentFlags ().String (varURL , "" , "Specify the URL to your deployment." )
143
+ cliflag .BoolVarP (cmd .PersistentFlags (), & varSuppressVersion , noVersionCheckFlag , "" , envNoVersionCheck , false , "Suppress warning when client and server versions do not match." )
100
144
cliflag .String (cmd .PersistentFlags (), varToken , "" , envSessionToken , "" , fmt .Sprintf ("Specify an authentication token. For security reasons setting %s is preferred." , envSessionToken ))
101
145
cliflag .String (cmd .PersistentFlags (), varAgentToken , "" , "CODER_AGENT_TOKEN" , "" , "Specify an agent authentication token." )
102
146
_ = cmd .PersistentFlags ().MarkHidden (varAgentToken )
@@ -142,7 +186,7 @@ func createClient(cmd *cobra.Command) (*codersdk.Client, error) {
142
186
if err != nil {
143
187
// If the configuration files are absent, the user is logged out
144
188
if os .IsNotExist (err ) {
145
- return nil , xerrors . New ( notLoggedInMessage )
189
+ return nil , errUnauthenticated
146
190
}
147
191
return nil , err
148
192
}
@@ -157,7 +201,7 @@ func createClient(cmd *cobra.Command) (*codersdk.Client, error) {
157
201
if err != nil {
158
202
// If the configuration files are absent, the user is logged out
159
203
if os .IsNotExist (err ) {
160
- return nil , xerrors . New ( notLoggedInMessage )
204
+ return nil , errUnauthenticated
161
205
}
162
206
return nil , err
163
207
}
@@ -331,3 +375,30 @@ func FormatCobraError(err error, cmd *cobra.Command) string {
331
375
helpErrMsg := fmt .Sprintf ("Run '%s --help' for usage." , cmd .CommandPath ())
332
376
return cliui .Styles .Error .Render (err .Error () + "\n " + helpErrMsg )
333
377
}
378
+
379
+ func checkVersions (cmd * cobra.Command , client * codersdk.Client ) error {
380
+ flag := cmd .Flag ("no-version-warning" )
381
+ if suppress , _ := strconv .ParseBool (flag .Value .String ()); suppress {
382
+ return nil
383
+ }
384
+
385
+ clientVersion := buildinfo .Version ()
386
+
387
+ info , err := client .BuildInfo (cmd .Context ())
388
+ if err != nil {
389
+ return xerrors .Errorf ("build info: %w" , err )
390
+ }
391
+
392
+ fmtWarningText := `version mismatch: client %s, server %s
393
+ download the server version with: 'curl -L https://coder.com/install.sh | sh -s -- --version %s'
394
+ `
395
+
396
+ if ! buildinfo .VersionsMatch (clientVersion , info .Version ) {
397
+ warn := cliui .Styles .Warn .Copy ().Align (lipgloss .Left )
398
+ // Trim the leading 'v', our install.sh script does not handle this case well.
399
+ _ , _ = fmt .Fprintf (cmd .OutOrStdout (), warn .Render (fmtWarningText ), clientVersion , info .Version , strings .TrimPrefix (info .CanonicalVersion (), "v" ))
400
+ _ , _ = fmt .Fprintln (cmd .OutOrStdout ())
401
+ }
402
+
403
+ return nil
404
+ }
0 commit comments