From 1cc525ac939f76570fff59379381ce6bf9479c49 Mon Sep 17 00:00:00 2001 From: YuriBocharov Date: Thu, 7 Mar 2024 18:28:12 -0500 Subject: [PATCH 1/3] feat(cli): make url optional for login command (#10925) Allow `coder login` to log into existing deployment if available. Output to indicate saved URL was used. Update help and error messages to indicate that `coder login` is available as a command. --- cli/login.go | 10 ++++- cli/login_test.go | 56 ++++++++++++++++++++++++++ cli/logout_test.go | 2 +- cli/root.go | 11 +++-- cli/testdata/coder_login_--help.golden | 2 +- 5 files changed, 75 insertions(+), 6 deletions(-) diff --git a/cli/login.go b/cli/login.go index 17cb206e1ef50..650feb2823270 100644 --- a/cli/login.go +++ b/cli/login.go @@ -136,7 +136,7 @@ func (r *RootCmd) login() *clibase.Cmd { useTokenForSession bool ) cmd := &clibase.Cmd{ - Use: "login ", + Use: "login []", Short: "Authenticate with Coder deployment", Middleware: clibase.RequireRangeArgs(0, 1), Handler: func(inv *clibase.Invocation) error { @@ -144,10 +144,18 @@ func (r *RootCmd) login() *clibase.Cmd { rawURL := "" if len(inv.Args) == 0 { rawURL = r.clientURL.String() + if rawURL != "" && rawURL == inv.Environ.Get(envURL) { + _, _ = fmt.Fprintf(inv.Stdout, "Attempting to authenticate with environment URL: %s\n", rawURL) + } } else { rawURL = inv.Args[0] } + if url, err := r.createConfig().URL().Read(); rawURL == "" && err == nil { + _, _ = fmt.Fprintf(inv.Stdout, "Attempting to authenticate with config URL: %s\n", url) + rawURL = url + } + if rawURL == "" { return xerrors.Errorf("no url argument provided") } diff --git a/cli/login_test.go b/cli/login_test.go index 1fb6576c3e31b..662298be7a0c2 100644 --- a/cli/login_test.go +++ b/cli/login_test.go @@ -215,6 +215,62 @@ 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()) + if runtime.GOOS != "windows" { + // For some reason, the match does not show up on Windows. + pty.ExpectMatch(client.SessionToken()) + } + pty.ExpectMatch("Welcome to Coder") + <-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()) + if runtime.GOOS != "windows" { + // For some reason, the match does not show up on Windows. + pty.ExpectMatch(client.SessionToken()) + } + pty.ExpectMatch("Welcome to Coder") + <-doneChan + }) + t.Run("ExistingUserInvalidTokenTTY", func(t *testing.T) { t.Parallel() client := coderdtest.New(t, nil) diff --git a/cli/logout_test.go b/cli/logout_test.go index b7c1a571a6605..493ccb5dd29f2 100644 --- a/cli/logout_test.go +++ b/cli/logout_test.go @@ -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 '.") + assert.ErrorContains(t, err, "You are not logged in. Try logging in using 'coder login'.") }() <-logoutChan diff --git a/cli/root.go b/cli/root.go index b4a381052cda5..4c6d5f213d846 100644 --- a/cli/root.go +++ b/cli/root.go @@ -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 '." + + notLoggedInMessage = "You are not logged in. Try logging in using 'coder login '." + 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 diff --git a/cli/testdata/coder_login_--help.golden b/cli/testdata/coder_login_--help.golden index 7e0b8ce3248dd..f6fe15dc07273 100644 --- a/cli/testdata/coder_login_--help.golden +++ b/cli/testdata/coder_login_--help.golden @@ -1,7 +1,7 @@ coder v0.0.0-devel USAGE: - coder login [flags] + coder login [flags] [] Authenticate with Coder deployment From cd15af81826961d83ee283060289e7f0bf463662 Mon Sep 17 00:00:00 2001 From: YuriBocharov Date: Fri, 8 Mar 2024 15:28:32 -0500 Subject: [PATCH 2/3] feat(cli): output auth text on success only It doesn't make sense to output Authing with ... then fail. This moves the output later in the login process to prevent that. --- cli/login.go | 11 +++++++++-- cli/login_test.go | 16 ++++------------ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/cli/login.go b/cli/login.go index 650feb2823270..422496052ee02 100644 --- a/cli/login.go +++ b/cli/login.go @@ -142,17 +142,21 @@ func (r *RootCmd) login() *clibase.Cmd { 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) { - _, _ = fmt.Fprintf(inv.Stdout, "Attempting to authenticate with environment URL: %s\n", rawURL) + urlSource = "environment" } } else { rawURL = inv.Args[0] + urlSource = "argument" } if url, err := r.createConfig().URL().Read(); rawURL == "" && err == nil { - _, _ = fmt.Fprintf(inv.Stdout, "Attempting to authenticate with config URL: %s\n", url) + urlSource = "config" rawURL = url } @@ -195,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") diff --git a/cli/login_test.go b/cli/login_test.go index 662298be7a0c2..3cf9dc1945b57 100644 --- a/cli/login_test.go +++ b/cli/login_test.go @@ -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" { @@ -232,14 +234,9 @@ func TestLogin(t *testing.T) { assert.NoError(t, err) }() - pty.ExpectMatch(fmt.Sprintf("Attempting to authenticate with config URL: %s", url)) + pty.ExpectMatch(fmt.Sprintf("Attempting to authenticate with config URL: '%s'", url)) pty.ExpectMatch("Paste your token here:") pty.WriteLine(client.SessionToken()) - if runtime.GOOS != "windows" { - // For some reason, the match does not show up on Windows. - pty.ExpectMatch(client.SessionToken()) - } - pty.ExpectMatch("Welcome to Coder") <-doneChan }) @@ -260,14 +257,9 @@ func TestLogin(t *testing.T) { assert.NoError(t, err) }() - pty.ExpectMatch(fmt.Sprintf("Attempting to authenticate with environment URL: %s", url)) + pty.ExpectMatch(fmt.Sprintf("Attempting to authenticate with environment URL: '%s'", url)) pty.ExpectMatch("Paste your token here:") pty.WriteLine(client.SessionToken()) - if runtime.GOOS != "windows" { - // For some reason, the match does not show up on Windows. - pty.ExpectMatch(client.SessionToken()) - } - pty.ExpectMatch("Welcome to Coder") <-doneChan }) From a5e2797057664e5b5dbefdf9759ded57333249cf Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 11 Mar 2024 15:51:56 +0200 Subject: [PATCH 3/3] make gen --- docs/cli/login.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cli/login.md b/docs/cli/login.md index f7604d42db7b0..0cc972421bd7d 100644 --- a/docs/cli/login.md +++ b/docs/cli/login.md @@ -7,7 +7,7 @@ Authenticate with Coder deployment ## Usage ```console -coder login [flags] +coder login [flags] [] ``` ## Options