feat(cli): make url optional for login command (#10925) (#12466)

Allow `coder login` to log into existing deployment if available.

Update help and error messages to indicate that `coder login` is
available as a command.

Fixes #10925
Fixes #9551
This commit is contained in:
elasticspoon 2024-03-11 10:14:19 -04:00 committed by GitHub
parent bed61f7d2a
commit 773862a9f5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 75 additions and 7 deletions

View File

@ -136,16 +136,28 @@ func (r *RootCmd) login() *clibase.Cmd {
useTokenForSession bool
)
cmd := &clibase.Cmd{
Use: "login <url>",
Use: "login [<url>]",
Short: "Authenticate with Coder deployment",
Middleware: clibase.RequireRangeArgs(0, 1),
Handler: func(inv *clibase.Invocation) error {
ctx := inv.Context()
rawURL := ""
var urlSource string
if len(inv.Args) == 0 {
rawURL = r.clientURL.String()
urlSource = "flag"
if rawURL != "" && rawURL == inv.Environ.Get(envURL) {
urlSource = "environment"
}
} else {
rawURL = inv.Args[0]
urlSource = "argument"
}
if url, err := r.createConfig().URL().Read(); rawURL == "" && err == nil {
urlSource = "config"
rawURL = url
}
if rawURL == "" {
@ -187,6 +199,9 @@ func (r *RootCmd) login() *clibase.Cmd {
if err != nil {
return xerrors.Errorf("Failed to check server %q for first user, is the URL correct and is coder accessible from your browser? Error - has initial user: %w", serverURL.String(), err)
}
_, _ = fmt.Fprintf(inv.Stdout, "Attempting to authenticate with %s URL: '%s'\n", urlSource, serverURL)
if !hasFirstUser {
_, _ = fmt.Fprintf(inv.Stdout, Caret+"Your Coder deployment hasn't been set up!\n")

View File

@ -116,6 +116,7 @@ func TestLogin(t *testing.T) {
clitest.Start(t, inv)
pty.ExpectMatch(fmt.Sprintf("Attempting to authenticate with flag URL: '%s'", client.URL.String()))
matches := []string{
"first user?", "yes",
"username", "testuser",
@ -205,6 +206,7 @@ func TestLogin(t *testing.T) {
assert.NoError(t, err)
}()
pty.ExpectMatch(fmt.Sprintf("Attempting to authenticate with argument URL: '%s'", client.URL.String()))
pty.ExpectMatch("Paste your token here:")
pty.WriteLine(client.SessionToken())
if runtime.GOOS != "windows" {
@ -215,6 +217,52 @@ func TestLogin(t *testing.T) {
<-doneChan
})
t.Run("ExistingUserURLSavedInConfig", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
url := client.URL.String()
coderdtest.CreateFirstUser(t, client)
inv, root := clitest.New(t, "login", "--no-open")
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
pty := ptytest.New(t).Attach(inv)
go func() {
defer close(doneChan)
err := inv.Run()
assert.NoError(t, err)
}()
pty.ExpectMatch(fmt.Sprintf("Attempting to authenticate with config URL: '%s'", url))
pty.ExpectMatch("Paste your token here:")
pty.WriteLine(client.SessionToken())
<-doneChan
})
t.Run("ExistingUserURLSavedInEnv", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
url := client.URL.String()
coderdtest.CreateFirstUser(t, client)
inv, _ := clitest.New(t, "login", "--no-open")
inv.Environ.Set("CODER_URL", url)
doneChan := make(chan struct{})
pty := ptytest.New(t).Attach(inv)
go func() {
defer close(doneChan)
err := inv.Run()
assert.NoError(t, err)
}()
pty.ExpectMatch(fmt.Sprintf("Attempting to authenticate with environment URL: '%s'", url))
pty.ExpectMatch("Paste your token here:")
pty.WriteLine(client.SessionToken())
<-doneChan
})
t.Run("ExistingUserInvalidTokenTTY", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)

View File

@ -119,7 +119,7 @@ func TestLogout(t *testing.T) {
go func() {
defer close(logoutChan)
err = logout.Run()
assert.ErrorContains(t, err, "You are not logged in. Try logging in using 'coder login <url>'.")
assert.ErrorContains(t, err, "You are not logged in. Try logging in using 'coder login'.")
}()
<-logoutChan

View File

@ -65,7 +65,9 @@ const (
varVerbose = "verbose"
varOrganizationSelect = "organization"
varDisableDirect = "disable-direct-connections"
notLoggedInMessage = "You are not logged in. Try logging in using 'coder login <url>'."
notLoggedInMessage = "You are not logged in. Try logging in using 'coder login <url>'."
notLoggedInURLSavedMessage = "You are not logged in. Try logging in using 'coder login'."
envNoVersionCheck = "CODER_NO_VERSION_WARNING"
envNoFeatureWarning = "CODER_NO_FEATURE_WARNING"
@ -77,7 +79,10 @@ const (
envURL = "CODER_URL"
)
var errUnauthenticated = xerrors.New(notLoggedInMessage)
var (
errUnauthenticated = xerrors.New(notLoggedInMessage)
errUnauthenticatedURLSaved = xerrors.New(notLoggedInURLSavedMessage)
)
func (r *RootCmd) Core() []*clibase.Cmd {
// Please re-sort this list alphabetically if you change it!
@ -574,7 +579,7 @@ func (r *RootCmd) initClientInternal(client *codersdk.Client, allowTokenMissing
// If the configuration files are absent, the user is logged out
if os.IsNotExist(err) {
if !allowTokenMissing {
return errUnauthenticated
return errUnauthenticatedURLSaved
}
} else if err != nil {
return err

View File

@ -1,7 +1,7 @@
coder v0.0.0-devel
USAGE:
coder login [flags] <url>
coder login [flags] [<url>]
Authenticate with Coder deployment

2
docs/cli/login.md generated
View File

@ -7,7 +7,7 @@ Authenticate with Coder deployment
## Usage
```console
coder login [flags] <url>
coder login [flags] [<url>]
```
## Options