2022-02-10 14:33:27 +00:00
package cli
import (
2022-07-26 13:27:48 +00:00
"flag"
2022-05-19 22:35:59 +00:00
"fmt"
2022-02-10 14:33:27 +00:00
"net/url"
"os"
2022-05-23 17:19:33 +00:00
"strings"
2022-07-12 20:24:53 +00:00
"text/template"
2022-04-05 01:35:03 +00:00
"time"
2022-02-10 14:33:27 +00:00
2022-05-23 18:51:49 +00:00
"golang.org/x/xerrors"
2022-06-29 22:49:40 +00:00
"github.com/charmbracelet/lipgloss"
2022-02-10 14:33:27 +00:00
"github.com/kirsle/configdir"
"github.com/mattn/go-isatty"
"github.com/spf13/cobra"
2022-04-07 17:18:58 +00:00
"github.com/coder/coder/buildinfo"
2022-04-30 16:40:30 +00:00
"github.com/coder/coder/cli/cliflag"
2022-03-22 19:17:50 +00:00
"github.com/coder/coder/cli/cliui"
2022-02-10 14:33:27 +00:00
"github.com/coder/coder/cli/config"
2022-08-22 22:02:50 +00:00
"github.com/coder/coder/coderd"
2022-02-10 14:33:27 +00:00
"github.com/coder/coder/codersdk"
)
2022-02-21 18:47:08 +00:00
var (
2022-03-22 19:17:50 +00:00
caret = cliui . Styles . Prompt . String ( )
2022-05-09 22:42:02 +00:00
// Applied as annotations to workspace commands
// so they display in a separated "help" section.
workspaceCommand = map [ string ] string {
2022-07-12 20:24:53 +00:00
"workspaces" : "" ,
2022-05-09 22:42:02 +00:00
}
2022-02-21 18:47:08 +00:00
)
2022-02-10 14:33:27 +00:00
const (
2022-08-29 18:30:06 +00:00
varURL = "url"
varToken = "token"
varAgentToken = "agent-token"
varAgentURL = "agent-url"
varGlobalConfig = "global-config"
varNoOpen = "no-open"
varNoVersionCheck = "no-version-warning"
varNoFeatureWarning = "no-feature-warning"
varForceTty = "force-tty"
varVerbose = "verbose"
notLoggedInMessage = "You are not logged in. Try logging in using 'coder login <url>'."
envNoVersionCheck = "CODER_NO_VERSION_WARNING"
envNoFeatureWarning = "CODER_NO_FEATURE_WARNING"
2022-06-29 22:49:40 +00:00
)
var (
errUnauthenticated = xerrors . New ( notLoggedInMessage )
envSessionToken = "CODER_SESSION_TOKEN"
2022-02-10 14:33:27 +00:00
)
2022-05-18 14:10:40 +00:00
func init ( ) {
2022-07-12 20:24:53 +00:00
// Set cobra template functions in init to avoid conflicts in tests.
cobra . AddTemplateFuncs ( templateFunctions )
2022-05-18 14:10:40 +00:00
}
2022-08-22 22:02:50 +00:00
func Core ( ) [ ] * cobra . Command {
return [ ] * cobra . Command {
configSSH ( ) ,
create ( ) ,
deleteWorkspace ( ) ,
dotfiles ( ) ,
gitssh ( ) ,
list ( ) ,
login ( ) ,
logout ( ) ,
parameters ( ) ,
portForward ( ) ,
publickey ( ) ,
resetPassword ( ) ,
schedules ( ) ,
show ( ) ,
ssh ( ) ,
2022-09-05 22:15:49 +00:00
speedtest ( ) ,
2022-08-22 22:02:50 +00:00
start ( ) ,
state ( ) ,
stop ( ) ,
2022-08-26 09:28:38 +00:00
rename ( ) ,
2022-08-22 22:02:50 +00:00
templates ( ) ,
update ( ) ,
users ( ) ,
versionCmd ( ) ,
workspaceAgent ( ) ,
features ( ) ,
}
}
func AGPL ( ) [ ] * cobra . Command {
all := append ( Core ( ) , Server ( coderd . New ) )
return all
}
func Root ( subcommands [ ] * cobra . Command ) * cobra . Command {
2022-02-10 14:33:27 +00:00
cmd := & cobra . Command {
2022-04-11 23:54:30 +00:00
Use : "coder" ,
SilenceErrors : true ,
SilenceUsage : true ,
2022-05-09 22:42:02 +00:00
Long : ` Coder — A tool for provisioning self - hosted development environments .
2022-02-10 14:33:27 +00:00
` ,
2022-06-29 22:49:40 +00:00
PersistentPreRun : func ( cmd * cobra . Command , args [ ] string ) {
2022-08-29 18:30:06 +00:00
if cliflag . IsSetBool ( cmd , varNoVersionCheck ) &&
cliflag . IsSetBool ( cmd , varNoFeatureWarning ) {
return
}
// Login handles checking the versions itself since it
// has a handle to an unauthenticated client.
// Server is skipped for obvious reasons.
if cmd . Name ( ) == "login" || cmd . Name ( ) == "server" || cmd . Name ( ) == "gitssh" {
return
}
client , err := CreateClient ( cmd )
// If we are unable to create a client, presumably the subcommand will fail as well
// so we can bail out here.
if err != nil {
return
}
err = checkVersions ( cmd , client )
2022-06-29 22:49:40 +00:00
if err != nil {
// Just log the error here. We never want to fail a command
// due to a pre-run.
_ , _ = fmt . Fprintf ( cmd . ErrOrStderr ( ) ,
cliui . Styles . Warn . Render ( "check versions error: %s" ) , err )
_ , _ = fmt . Fprintln ( cmd . ErrOrStderr ( ) )
}
2022-08-29 18:30:06 +00:00
checkWarnings ( cmd , client )
2022-06-29 22:49:40 +00:00
} ,
2022-07-11 16:08:09 +00:00
Example : formatExamples (
example {
Description : "Start a Coder server" ,
Command : "coder server" ,
} ,
example {
Description : "Get started by creating a template from an example" ,
Command : "coder templates init" ,
} ,
) ,
2022-02-10 14:33:27 +00:00
}
2022-08-22 22:02:50 +00:00
cmd . AddCommand ( subcommands ... )
2022-03-22 19:17:50 +00:00
2022-05-09 22:42:02 +00:00
cmd . SetUsageTemplate ( usageTemplate ( ) )
2022-05-06 18:46:21 +00:00
cmd . PersistentFlags ( ) . String ( varURL , "" , "Specify the URL to your deployment." )
2022-07-20 20:17:51 +00:00
cliflag . Bool ( cmd . PersistentFlags ( ) , varNoVersionCheck , "" , envNoVersionCheck , false , "Suppress warning when client and server versions do not match." )
2022-08-29 18:30:06 +00:00
cliflag . Bool ( cmd . PersistentFlags ( ) , varNoFeatureWarning , "" , envNoFeatureWarning , false , "Suppress warnings about unlicensed features." )
2022-06-24 23:50:35 +00:00
cliflag . String ( cmd . PersistentFlags ( ) , varToken , "" , envSessionToken , "" , fmt . Sprintf ( "Specify an authentication token. For security reasons setting %s is preferred." , envSessionToken ) )
2022-04-30 16:40:30 +00:00
cliflag . String ( cmd . PersistentFlags ( ) , varAgentToken , "" , "CODER_AGENT_TOKEN" , "" , "Specify an agent authentication token." )
2022-05-10 20:57:07 +00:00
_ = cmd . PersistentFlags ( ) . MarkHidden ( varAgentToken )
2022-04-30 16:40:30 +00:00
cliflag . String ( cmd . PersistentFlags ( ) , varAgentURL , "" , "CODER_AGENT_URL" , "" , "Specify the URL for an agent to access your deployment." )
2022-05-10 20:57:07 +00:00
_ = cmd . PersistentFlags ( ) . MarkHidden ( varAgentURL )
2022-04-30 16:40:30 +00:00
cliflag . String ( cmd . PersistentFlags ( ) , varGlobalConfig , "" , "CODER_CONFIG_DIR" , configdir . LocalConfig ( "coderv2" ) , "Specify the path to the global `coder` config directory." )
cmd . PersistentFlags ( ) . Bool ( varForceTty , false , "Force the `coder` command to run as if connected to a TTY." )
_ = cmd . PersistentFlags ( ) . MarkHidden ( varForceTty )
2022-02-21 17:10:05 +00:00
cmd . PersistentFlags ( ) . Bool ( varNoOpen , false , "Block automatically opening URLs in the browser." )
2022-04-30 16:40:30 +00:00
_ = cmd . PersistentFlags ( ) . MarkHidden ( varNoOpen )
2022-07-20 20:17:51 +00:00
cliflag . Bool ( cmd . PersistentFlags ( ) , varVerbose , "v" , "CODER_VERBOSE" , false , "Enable verbose output" )
2022-02-10 14:33:27 +00:00
return cmd
}
2022-06-06 22:38:51 +00:00
// versionCmd prints the coder version
func versionCmd ( ) * cobra . Command {
return & cobra . Command {
2022-07-11 16:08:09 +00:00
Use : "version" ,
Short : "Show coder version" ,
2022-06-06 22:38:51 +00:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
var str strings . Builder
_ , _ = str . WriteString ( fmt . Sprintf ( "Coder %s" , buildinfo . Version ( ) ) )
buildTime , valid := buildinfo . Time ( )
if valid {
_ , _ = str . WriteString ( " " + buildTime . Format ( time . UnixDate ) )
}
_ , _ = str . WriteString ( "\r\n" + buildinfo . ExternalURL ( ) + "\r\n" )
_ , _ = fmt . Fprint ( cmd . OutOrStdout ( ) , str . String ( ) )
return nil
} ,
}
}
2022-07-26 13:27:48 +00:00
func isTest ( ) bool {
return flag . Lookup ( "test.v" ) != nil
}
2022-08-23 20:55:39 +00:00
// CreateClient returns a new client from the command context.
2022-04-30 16:40:30 +00:00
// It reads from global configuration files if flags are not set.
2022-08-23 20:55:39 +00:00
func CreateClient ( cmd * cobra . Command ) ( * codersdk . Client , error ) {
2022-02-10 14:33:27 +00:00
root := createConfig ( cmd )
2022-04-30 16:40:30 +00:00
rawURL , err := cmd . Flags ( ) . GetString ( varURL )
if err != nil || rawURL == "" {
rawURL , err = root . URL ( ) . Read ( )
if err != nil {
2022-05-23 18:51:49 +00:00
// If the configuration files are absent, the user is logged out
if os . IsNotExist ( err ) {
2022-06-29 22:49:40 +00:00
return nil , errUnauthenticated
2022-05-23 18:51:49 +00:00
}
2022-04-30 16:40:30 +00:00
return nil , err
}
}
2022-05-23 17:19:33 +00:00
serverURL , err := url . Parse ( strings . TrimSpace ( rawURL ) )
2022-04-30 16:40:30 +00:00
if err != nil {
return nil , err
}
token , err := cmd . Flags ( ) . GetString ( varToken )
if err != nil || token == "" {
token , err = root . Session ( ) . Read ( )
if err != nil {
2022-05-23 18:51:49 +00:00
// If the configuration files are absent, the user is logged out
if os . IsNotExist ( err ) {
2022-06-29 22:49:40 +00:00
return nil , errUnauthenticated
2022-05-23 18:51:49 +00:00
}
2022-04-30 16:40:30 +00:00
return nil , err
}
}
client := codersdk . New ( serverURL )
2022-05-23 17:19:33 +00:00
client . SessionToken = strings . TrimSpace ( token )
2022-04-30 16:40:30 +00:00
return client , nil
}
// createAgentClient returns a new client from the command context.
2022-08-23 20:55:39 +00:00
// It works just like CreateClient, but uses the agent token and URL instead.
2022-04-30 16:40:30 +00:00
func createAgentClient ( cmd * cobra . Command ) ( * codersdk . Client , error ) {
rawURL , err := cmd . Flags ( ) . GetString ( varAgentURL )
2022-02-10 14:33:27 +00:00
if err != nil {
return nil , err
}
serverURL , err := url . Parse ( rawURL )
if err != nil {
return nil , err
}
2022-04-30 16:40:30 +00:00
token , err := cmd . Flags ( ) . GetString ( varAgentToken )
2022-02-10 14:33:27 +00:00
if err != nil {
return nil , err
}
client := codersdk . New ( serverURL )
2022-02-12 19:34:04 +00:00
client . SessionToken = token
return client , nil
2022-02-10 14:33:27 +00:00
}
2022-02-12 19:34:04 +00:00
// currentOrganization returns the currently active organization for the authenticated user.
2022-03-22 19:17:50 +00:00
func currentOrganization ( cmd * cobra . Command , client * codersdk . Client ) ( codersdk . Organization , error ) {
2022-04-01 19:42:36 +00:00
orgs , err := client . OrganizationsByUser ( cmd . Context ( ) , codersdk . Me )
2022-02-10 14:33:27 +00:00
if err != nil {
2022-03-22 19:17:50 +00:00
return codersdk . Organization { } , nil
2022-02-10 14:33:27 +00:00
}
// For now, we won't use the config to set this.
// Eventually, we will support changing using "coder switch <org>"
return orgs [ 0 ] , nil
}
2022-06-03 17:47:56 +00:00
// namedWorkspace fetches and returns a workspace by an identifier, which may be either
// a bare name (for a workspace owned by the current user) or a "user/workspace" combination,
// where user is either a username or UUID.
2022-06-03 19:36:08 +00:00
func namedWorkspace ( cmd * cobra . Command , client * codersdk . Client , identifier string ) ( codersdk . Workspace , error ) {
2022-06-03 17:47:56 +00:00
parts := strings . Split ( identifier , "/" )
var owner , name string
switch len ( parts ) {
case 1 :
owner = codersdk . Me
name = parts [ 0 ]
case 2 :
owner = parts [ 0 ]
name = parts [ 1 ]
default :
return codersdk . Workspace { } , xerrors . Errorf ( "invalid workspace name: %q" , identifier )
}
2022-06-10 14:58:42 +00:00
return client . WorkspaceByOwnerAndName ( cmd . Context ( ) , owner , name , codersdk . WorkspaceOptions { } )
2022-06-03 17:47:56 +00:00
}
2022-02-12 19:34:04 +00:00
// createConfig consumes the global configuration flag to produce a config root.
2022-02-10 14:33:27 +00:00
func createConfig ( cmd * cobra . Command ) config . Root {
globalRoot , err := cmd . Flags ( ) . GetString ( varGlobalConfig )
if err != nil {
panic ( err )
}
return config . Root ( globalRoot )
}
// isTTY returns whether the passed reader is a TTY or not.
// This accepts a reader to work with Cobra's "InOrStdin"
// function for simple testing.
fix: Run expect tests on Windows with conpty pseudo-terminal (#276)
This brings together a bunch of random, partially implemented packages for support of the new(ish) Windows [`conpty`](https://devblogs.microsoft.com/commandline/windows-command-line-introducing-the-windows-pseudo-console-conpty/) API - such that we can leverage the `expect` style of CLI tests, but in a way that works in Linux/OSX `pty`s and Windows `conpty`.
These include:
- Vendoring the `go-expect` library from Netflix w/ some tweaks to work cross-platform
- Vendoring the `pty` cross-platform implementation from [waypoint-plugin-sdk](https://github.com/hashicorp/waypoint-plugin-sdk/tree/b55c787a65ff9b7d2b32cfae80681b78f8f2275e/internal/pkg/pty)
- Vendoring the `conpty` Windows-specific implementation from [waypoint-plugin-sdk](https://github.com/hashicorp/waypoint-plugin-sdk/tree/b55c787a65ff9b7d2b32cfae80681b78f8f2275e/internal/pkg/conpty)
- Adjusting the `pty` interface to work with `go-expect` + the cross-plat version
There were several limitations with the current packages:
- `go-expect` requires the same `os.File` (TTY) for input / output, but `conhost` requires separate file handles
- `conpty` does not handle input, only output
- The cross-platform `pty` didn't expose the full set of primitives needed for `console`
Therefore, the following changes were made:
- Handling of `stdin` was added to the `conpty` interface
- We weren't using the full extent of the `go-expect` interface, so some portions were removed (ie, exec'ing a process) to simplify our implementation and make it easier to extend cross-platform
- Instead of `console` exposing just a `Tty`, it exposes an `InTty` and `OutTty`, to help encapsulate the difference on Windows (on Linux, these point to the same pipe)
Future improvements:
- The `isatty` implementation doesn't support accurate detection of `conhost` pty's without an associated process. In lieu of a more robust check, I've added a `--force-tty` flag intended for test case use - that forces the CLI to run in tty mode.
- It seems the windows implementation doesn't support setting a deadline. This is needed for the expect.Timeout API, but isn't used by us yet.
Fixes #241
2022-02-15 01:05:40 +00:00
func isTTY ( cmd * cobra . Command ) bool {
// If the `--force-tty` command is available, and set,
// assume we're in a tty. This is primarily for cases on Windows
// where we may not be able to reliably detect this automatically (ie, tests)
forceTty , err := cmd . Flags ( ) . GetBool ( varForceTty )
if forceTty && err == nil {
return true
}
2022-03-22 19:17:50 +00:00
file , ok := cmd . InOrStdin ( ) . ( * os . File )
2022-02-10 14:33:27 +00:00
if ! ok {
return false
}
return isatty . IsTerminal ( file . Fd ( ) )
}
2022-04-05 01:35:03 +00:00
2022-06-08 08:45:29 +00:00
// isTTYOut returns whether the passed reader is a TTY or not.
// This accepts a reader to work with Cobra's "OutOrStdout"
// function for simple testing.
func isTTYOut ( cmd * cobra . Command ) bool {
// If the `--force-tty` command is available, and set,
// assume we're in a tty. This is primarily for cases on Windows
// where we may not be able to reliably detect this automatically (ie, tests)
forceTty , err := cmd . Flags ( ) . GetBool ( varForceTty )
if forceTty && err == nil {
return true
}
file , ok := cmd . OutOrStdout ( ) . ( * os . File )
if ! ok {
return false
}
return isatty . IsTerminal ( file . Fd ( ) )
}
2022-07-12 20:24:53 +00:00
var templateFunctions = template . FuncMap {
"usageHeader" : usageHeader ,
"isWorkspaceCommand" : isWorkspaceCommand ,
}
func usageHeader ( s string ) string {
// Customizes the color of headings to make subcommands more visually
// appealing.
return cliui . Styles . Placeholder . Render ( s )
}
func isWorkspaceCommand ( cmd * cobra . Command ) bool {
if _ , ok := cmd . Annotations [ "workspaces" ] ; ok {
return true
}
var ws bool
cmd . VisitParents ( func ( cmd * cobra . Command ) {
if _ , ok := cmd . Annotations [ "workspaces" ] ; ok {
ws = true
}
} )
return ws
}
2022-05-09 22:42:02 +00:00
func usageTemplate ( ) string {
2022-05-18 14:10:40 +00:00
// usageHeader is defined in init().
2022-05-16 17:01:42 +00:00
return ` { { usageHeader "Usage:" } }
{ { - if . Runnable } }
{ { . UseLine } }
{ { end } }
{ { - if . HasAvailableSubCommands } }
{ { . CommandPath } } [ command ]
{ { end } }
{ { - if gt ( len . Aliases ) 0 } }
{ { usageHeader "Aliases:" } }
{ { . NameAndAliases } }
{ { end } }
{ { - if . HasExample } }
{ { usageHeader "Get Started:" } }
2022-05-09 22:42:02 +00:00
{ { . Example } }
2022-05-16 17:01:42 +00:00
{ { end } }
2022-07-12 20:24:53 +00:00
{ { - $ isRootHelp := ( not . HasParent ) } }
2022-05-16 17:01:42 +00:00
{ { - if . HasAvailableSubCommands } }
{ { usageHeader "Commands:" } }
{ { - range . Commands } }
2022-07-12 20:24:53 +00:00
{ { - $ isRootWorkspaceCommand := ( and $ isRootHelp ( isWorkspaceCommand . ) ) } }
{ { - if ( or ( and . IsAvailableCommand ( not $ isRootWorkspaceCommand ) ) ( eq . Name "help" ) ) } }
2022-05-16 17:01:42 +00:00
{ { rpad . Name . NamePadding } } { { . Short } }
{ { - end } }
{ { - end } }
{ { end } }
2022-07-12 20:24:53 +00:00
{ { - if ( and $ isRootHelp . HasAvailableSubCommands ) } }
2022-05-16 17:01:42 +00:00
{ { usageHeader "Workspace Commands:" } }
{ { - range . Commands } }
2022-07-12 20:24:53 +00:00
{ { - if ( and . IsAvailableCommand ( isWorkspaceCommand . ) ) } }
2022-05-16 17:01:42 +00:00
{ { rpad . Name . NamePadding } } { { . Short } }
{ { - end } }
{ { - end } }
{ { end } }
2022-05-09 22:42:02 +00:00
2022-05-16 17:01:42 +00:00
{ { - if . HasAvailableLocalFlags } }
{ { usageHeader "Flags:" } }
2022-07-12 20:24:53 +00:00
{ { . LocalFlags . FlagUsagesWrapped 100 | trimTrailingWhitespaces } }
2022-05-16 17:01:42 +00:00
{ { end } }
2022-05-09 22:42:02 +00:00
2022-05-16 17:01:42 +00:00
{ { - if . HasAvailableInheritedFlags } }
{ { usageHeader "Global Flags:" } }
2022-07-12 20:24:53 +00:00
{ { . InheritedFlags . FlagUsagesWrapped 100 | trimTrailingWhitespaces } }
2022-05-16 17:01:42 +00:00
{ { end } }
2022-05-09 22:42:02 +00:00
2022-05-16 17:01:42 +00:00
{ { - if . HasHelpSubCommands } }
{ { usageHeader "Additional help topics:" } }
{ { - range . Commands } }
{ { - if . IsAdditionalHelpTopicCommand } }
{ { rpad . CommandPath . CommandPathPadding } } { { . Short } }
{ { - end } }
{ { - end } }
{ { end } }
2022-05-09 22:42:02 +00:00
2022-05-16 17:01:42 +00:00
{ { - if . HasAvailableSubCommands } }
2022-05-09 22:42:02 +00:00
Use "{{.CommandPath}} [command] --help" for more information about a command .
2022-05-16 17:01:42 +00:00
{ { end } } `
2022-05-09 22:42:02 +00:00
}
2022-07-11 16:08:09 +00:00
// example represents a standard example for command usage, to be used
// with formatExamples.
type example struct {
Description string
Command string
}
2022-08-01 13:29:52 +00:00
// formatExamples formats the examples as width wrapped bulletpoint
2022-07-11 16:08:09 +00:00
// descriptions with the command underneath.
func formatExamples ( examples ... example ) string {
wrap := cliui . Styles . Wrap . Copy ( )
wrap . PaddingLeft ( 4 )
var sb strings . Builder
for i , e := range examples {
if len ( e . Description ) > 0 {
_ , _ = sb . WriteString ( " - " + wrap . Render ( e . Description + ":" ) [ 4 : ] + "\n\n " )
}
// We add 1 space here because `cliui.Styles.Code` adds an extra
// space. This makes the code block align at an even 2 or 6
// spaces for symmetry.
_ , _ = sb . WriteString ( " " + cliui . Styles . Code . Render ( fmt . Sprintf ( "$ %s" , e . Command ) ) )
if i < len ( examples ) - 1 {
_ , _ = sb . WriteString ( "\n\n" )
}
}
return sb . String ( )
}
2022-05-19 22:35:59 +00:00
// FormatCobraError colorizes and adds "--help" docs to cobra commands.
func FormatCobraError ( err error , cmd * cobra . Command ) string {
2022-05-20 09:42:56 +00:00
helpErrMsg := fmt . Sprintf ( "Run '%s --help' for usage." , cmd . CommandPath ( ) )
2022-07-20 20:17:51 +00:00
var (
httpErr * codersdk . Error
output strings . Builder
)
if xerrors . As ( err , & httpErr ) {
_ , _ = fmt . Fprintln ( & output , httpErr . Friendly ( ) )
}
// If the httpErr is nil then we just have a regular error in which
// case we want to print out what's happening.
if httpErr == nil || cliflag . IsSetBool ( cmd , varVerbose ) {
_ , _ = fmt . Fprintln ( & output , err . Error ( ) )
}
_ , _ = fmt . Fprint ( & output , helpErrMsg )
return cliui . Styles . Error . Render ( output . String ( ) )
2022-05-19 22:35:59 +00:00
}
2022-06-29 22:49:40 +00:00
func checkVersions ( cmd * cobra . Command , client * codersdk . Client ) error {
2022-07-20 20:17:51 +00:00
if cliflag . IsSetBool ( cmd , varNoVersionCheck ) {
2022-06-29 22:49:40 +00:00
return nil
}
clientVersion := buildinfo . Version ( )
info , err := client . BuildInfo ( cmd . Context ( ) )
2022-07-21 19:28:24 +00:00
// Avoid printing errors that are connection-related.
if codersdk . IsConnectionErr ( err ) {
return nil
}
2022-06-29 22:49:40 +00:00
if err != nil {
return xerrors . Errorf ( "build info: %w" , err )
}
fmtWarningText := ` version mismatch : client % s , server % s
download the server version with : ' curl - L https : //coder.com/install.sh | sh -s -- --version %s'
`
if ! buildinfo . VersionsMatch ( clientVersion , info . Version ) {
warn := cliui . Styles . Warn . Copy ( ) . Align ( lipgloss . Left )
// Trim the leading 'v', our install.sh script does not handle this case well.
2022-07-26 20:47:12 +00:00
_ , _ = fmt . Fprintf ( cmd . ErrOrStderr ( ) , warn . Render ( fmtWarningText ) , clientVersion , info . Version , strings . TrimPrefix ( info . CanonicalVersion ( ) , "v" ) )
_ , _ = fmt . Fprintln ( cmd . ErrOrStderr ( ) )
2022-06-29 22:49:40 +00:00
}
return nil
}
2022-08-29 18:30:06 +00:00
func checkWarnings ( cmd * cobra . Command , client * codersdk . Client ) {
if cliflag . IsSetBool ( cmd , varNoFeatureWarning ) {
return
}
entitlements , err := client . Entitlements ( cmd . Context ( ) )
if err != nil {
return
}
for _ , w := range entitlements . Warnings {
_ , _ = fmt . Fprintln ( cmd . ErrOrStderr ( ) , cliui . Styles . Warn . Render ( w ) )
}
}