Skip to content

Improve CLI logout flow #1692

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 6 commits into from
May 24, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 32 additions & 3 deletions cli/logout.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,41 @@ import (
func logout() *cobra.Command {
return &cobra.Command{
Use: "logout",
Short: "Remove local autheticated session",
Short: "Remove the local authenticated session",
RunE: func(cmd *cobra.Command, args []string) error {
var isLoggedOut bool

config := createConfig(cmd)
err := os.RemoveAll(string(config))

err := config.URL().Delete()
if err != nil {
// Only throw error if the URL configuration file is present,
// otherwise the user is already logged out, and we proceed
if !os.IsNotExist(err) {
return xerrors.Errorf("remove URL file: %w", err)
}
isLoggedOut = true
}

err = config.Session().Delete()
if err != nil {
return xerrors.Errorf("remove files at %s: %w", config, err)
// Only throw error if the session configuration file is present,
// otherwise the user is already logged out, and we proceed
if !os.IsNotExist(err) {
return xerrors.Errorf("remove session file: %w", err)
}
isLoggedOut = true
}

err = config.Organization().Delete()
// If the organization configuration file is absent, we still proceed
if err != nil && !os.IsNotExist(err) {
return xerrors.Errorf("remove organization file: %w", err)
}

// If the user was already logged out, we show them a message
if isLoggedOut {
_, _ = fmt.Fprintf(cmd.OutOrStdout(), notLoggedInMessage+"\n")
}

_, _ = fmt.Fprintf(cmd.OutOrStdout(), caret+"Successfully logged out.\n")
Expand Down
110 changes: 96 additions & 14 deletions cli/logout_test.go
Original file line number Diff line number Diff line change
@@ -1,45 +1,127 @@
package cli_test

import (
"os"
"testing"

"github.com/stretchr/testify/require"
"github.com/stretchr/testify/assert"

"github.com/coder/coder/cli/clitest"
"github.com/coder/coder/cli/config"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/pty/ptytest"
)

func TestLogout(t *testing.T) {
t.Parallel()
t.Run("Logout", func(t *testing.T) {
t.Parallel()

pty := ptytest.New(t)
config := login(t, pty)

// ensure session files exist
assert.FileExists(t, string(config.URL()))
assert.FileExists(t, string(config.Session()))

logoutChan := make(chan struct{})
logout, _ := clitest.New(t, "logout", "--global-config", string(config))
logout.SetIn(pty.Input())
logout.SetOut(pty.Output())

go func() {
defer close(logoutChan)
err := logout.Execute()
assert.NoError(t, err)
assert.NoFileExists(t, string(config.URL()))
assert.NoFileExists(t, string(config.Session()))
}()

pty.ExpectMatch("Successfully logged out")
<-logoutChan
})
t.Run("NoURLFile", func(t *testing.T) {
t.Parallel()

pty := ptytest.New(t)
config := login(t, pty)

// ensure session files exist
assert.FileExists(t, string(config.URL()))
assert.FileExists(t, string(config.Session()))

os.RemoveAll(string(config.URL()))

logoutChan := make(chan struct{})
logout, _ := clitest.New(t, "logout", "--global-config", string(config))

logout.SetIn(pty.Input())
logout.SetOut(pty.Output())

go func() {
defer close(logoutChan)
err := logout.Execute()
assert.NoError(t, err)
assert.NoFileExists(t, string(config.URL()))
assert.NoFileExists(t, string(config.Session()))
}()

pty.ExpectMatch("You are not logged in. Try logging in using 'coder login <url>'.")
pty.ExpectMatch("Successfully logged out")
<-logoutChan
})
t.Run("NoSessionFile", func(t *testing.T) {
t.Parallel()

pty := ptytest.New(t)
config := login(t, pty)

// ensure session files exist
assert.FileExists(t, string(config.URL()))
assert.FileExists(t, string(config.Session()))

os.RemoveAll(string(config.Session()))

logoutChan := make(chan struct{})
logout, _ := clitest.New(t, "logout", "--global-config", string(config))

logout.SetIn(pty.Input())
logout.SetOut(pty.Output())

go func() {
defer close(logoutChan)
err := logout.Execute()
assert.NoError(t, err)
assert.NoFileExists(t, string(config.URL()))
assert.NoFileExists(t, string(config.Session()))
}()

pty.ExpectMatch("You are not logged in. Try logging in using 'coder login <url>'.")
pty.ExpectMatch("Successfully logged out")
<-logoutChan
})
}

func login(t *testing.T, pty *ptytest.PTY) config.Root {
t.Helper()

// login
client := coderdtest.New(t, nil)
coderdtest.CreateFirstUser(t, client)

doneChan := make(chan struct{})
root, config := clitest.New(t, "login", "--force-tty", client.URL.String(), "--no-open")
pty := ptytest.New(t)
root, cfg := clitest.New(t, "login", "--force-tty", client.URL.String(), "--no-open")
root.SetIn(pty.Input())
root.SetOut(pty.Output())
go func() {
defer close(doneChan)
err := root.Execute()
require.NoError(t, err)
assert.NoError(t, err)
}()

pty.ExpectMatch("Paste your token here:")
pty.WriteLine(client.SessionToken)
pty.ExpectMatch("Welcome to Coder")
<-doneChan

// ensure session files exist
require.FileExists(t, string(config.URL()))
require.FileExists(t, string(config.Session()))

logout, _ := clitest.New(t, "logout", "--global-config", string(config))
err := logout.Execute()
require.NoError(t, err)
require.NoFileExists(t, string(config.URL()))
require.NoFileExists(t, string(config.Session()))
return cfg
}
19 changes: 10 additions & 9 deletions cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,14 @@ var (
)

const (
varURL = "url"
varToken = "token"
varAgentToken = "agent-token"
varAgentURL = "agent-url"
varGlobalConfig = "global-config"
varNoOpen = "no-open"
varForceTty = "force-tty"
varURL = "url"
varToken = "token"
varAgentToken = "agent-token"
varAgentURL = "agent-url"
varGlobalConfig = "global-config"
varNoOpen = "no-open"
varForceTty = "force-tty"
notLoggedInMessage = "You are not logged in. Try logging in using 'coder login <url>'."
)

func init() {
Expand Down Expand Up @@ -117,7 +118,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("You are not logged in. Try logging in using 'coder login <url>'.")
return nil, xerrors.New(notLoggedInMessage)
}
return nil, err
}
Expand All @@ -132,7 +133,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("You are not logged in. Try logging in using 'coder login <url>'.")
return nil, xerrors.New(notLoggedInMessage)
}
return nil, err
}
Expand Down
1 change: 1 addition & 0 deletions cli/userlist_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
)

func TestUserList(t *testing.T) {
t.Parallel()
t.Run("List", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
Expand Down