Skip to content

Commit

Permalink
Added logout ability (argocd logout) (#1582)
Browse files Browse the repository at this point in the history
  • Loading branch information
simster7 authored and alexec committed Jun 7, 2019
1 parent 0088955 commit 4860f2c
Show file tree
Hide file tree
Showing 7 changed files with 285 additions and 5 deletions.
63 changes: 59 additions & 4 deletions cmd/argocd/commands/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"strings"
"text/tabwriter"

"github.com/spf13/pflag"

log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"

Expand All @@ -18,16 +20,36 @@ import (

// NewContextCommand returns a new instance of an `argocd ctx` command
func NewContextCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var delete bool
var command = &cobra.Command{
Use: "context",
Aliases: []string{"ctx"},
Short: "Switch between contexts",
Run: func(c *cobra.Command, args []string) {

localCfg, err := localconfig.ReadLocalConfig(clientOpts.ConfigPath)
errors.CheckError(err)

deletePresentContext := false
c.Flags().Visit(func(f *pflag.Flag) {
if f.Name == "delete" {
deletePresentContext = true
}
})

if len(args) == 0 {
printArgoCDContexts(clientOpts.ConfigPath)
return
if deletePresentContext {
err := deleteContext(localCfg.CurrentContext, clientOpts.ConfigPath)
errors.CheckError(err)
return
} else {
printArgoCDContexts(clientOpts.ConfigPath)
return
}
}

ctxName := args[0]

argoCDDir, err := localconfig.DefaultConfigDir()
errors.CheckError(err)
prevCtxFile := path.Join(argoCDDir, ".prev-ctx")
Expand All @@ -37,8 +59,6 @@ func NewContextCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
errors.CheckError(err)
ctxName = string(prevCtxBytes)
}
localCfg, err := localconfig.ReadLocalConfig(clientOpts.ConfigPath)
errors.CheckError(err)
if localCfg.CurrentContext == ctxName {
fmt.Printf("Already at context '%s'\n", localCfg.CurrentContext)
return
Expand All @@ -48,16 +68,51 @@ func NewContextCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
}
prevCtx := localCfg.CurrentContext
localCfg.CurrentContext = ctxName

err = localconfig.WriteLocalConfig(*localCfg, clientOpts.ConfigPath)
errors.CheckError(err)
err = ioutil.WriteFile(prevCtxFile, []byte(prevCtx), 0644)
errors.CheckError(err)
fmt.Printf("Switched to context '%s'\n", localCfg.CurrentContext)
},
}
command.Flags().BoolVar(&delete, "delete", false, "Delete the context instead of switching to it")
return command
}

func deleteContext(context, configPath string) error {

localCfg, err := localconfig.ReadLocalConfig(configPath)
errors.CheckError(err)
if localCfg == nil {
return fmt.Errorf("Nothing to logout from")
}

serverName, ok := localCfg.RemoveContext(context)
if !ok {
return fmt.Errorf("Context %s does not exist", context)
}
_ = localCfg.RemoveUser(context)
_ = localCfg.RemoveServer(serverName)

if localCfg.IsEmpty() {
err = localconfig.DeleteLocalConfig(configPath)
errors.CheckError(err)
} else {
if localCfg.CurrentContext == context {
localCfg.CurrentContext = localCfg.Contexts[0].Name
}
err = localconfig.ValidateLocalConfig(*localCfg)
if err != nil {
return fmt.Errorf("Error in logging out")
}
err = localconfig.WriteLocalConfig(*localCfg, configPath)
errors.CheckError(err)
}
fmt.Printf("Context '%s' deleted\n", context)
return nil
}

func printArgoCDContexts(configPath string) {
localCfg, err := localconfig.ReadLocalConfig(configPath)
errors.CheckError(err)
Expand Down
60 changes: 60 additions & 0 deletions cmd/argocd/commands/context_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package commands

import (
"io/ioutil"
"os"
"testing"

"github.com/stretchr/testify/assert"

"github.com/argoproj/argo-cd/util/localconfig"
)

const testConfig = `contexts:
- name: argocd.example.com:443
server: argocd.example.com:443
user: argocd.example.com:443
- name: localhost:8080
server: localhost:8080
user: localhost:8080
current-context: localhost:8080
servers:
- server: argocd.example.com:443
- plain-text: true
server: localhost:8080
users:
- auth-token: vErrYS3c3tReFRe$hToken
name: argocd.example.com:443
refresh-token: vErrYS3c3tReFRe$hToken
- auth-token: vErrYS3c3tReFRe$hToken
name: localhost:8080`

const testConfigFilePath = "./testdata/config"

func TestContextDelete(t *testing.T) {

// Write the test config file
err := ioutil.WriteFile(testConfigFilePath, []byte(testConfig), os.ModePerm)
assert.NoError(t, err)

localConfig, err := localconfig.ReadLocalConfig(testConfigFilePath)
assert.NoError(t, err)
assert.Equal(t, localConfig.CurrentContext, "localhost:8080")
assert.Contains(t, localConfig.Contexts, localconfig.ContextRef{Name: "localhost:8080", Server: "localhost:8080", User: "localhost:8080"})

err = deleteContext("localhost:8080", testConfigFilePath)
assert.NoError(t, err)

localConfig, err = localconfig.ReadLocalConfig(testConfigFilePath)
assert.NoError(t, err)
assert.Equal(t, localConfig.CurrentContext, "argocd.example.com:443")
assert.NotContains(t, localConfig.Contexts, localconfig.ContextRef{Name: "localhost:8080", Server: "localhost:8080", User: "localhost:8080"})
assert.NotContains(t, localConfig.Servers, localconfig.Server{PlainText: true, Server: "localhost:8080"})
assert.NotContains(t, localConfig.Users, localconfig.User{AuthToken: "vErrYS3c3tReFRe$hToken", Name: "localhost:8080"})
assert.Contains(t, localConfig.Contexts, localconfig.ContextRef{Name: "argocd.example.com:443", Server: "argocd.example.com:443", User: "argocd.example.com:443"})

// Write the file again so that no conflicts are made in git
err = ioutil.WriteFile(testConfigFilePath, []byte(testConfig), os.ModePerm)
assert.NoError(t, err)

}
50 changes: 50 additions & 0 deletions cmd/argocd/commands/logout.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package commands

import (
"fmt"
"os"

log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"

"github.com/argoproj/argo-cd/errors"
argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
"github.com/argoproj/argo-cd/util/localconfig"
)

// NewLogoutCommand returns a new instance of `argocd logout` command
func NewLogoutCommand(globalClientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "logout CONTEXT",
Short: "Log out from Argo CD",
Long: "Log out from Argo CD",
Run: func(c *cobra.Command, args []string) {
if len(args) == 0 {
c.HelpFunc()(c, args)
os.Exit(1)
}
context := args[0]

localCfg, err := localconfig.ReadLocalConfig(globalClientOpts.ConfigPath)
errors.CheckError(err)
if localCfg == nil {
log.Fatalf("Nothing to logout from")
}

ok := localCfg.RemoveToken(context)
if !ok {
log.Fatalf("Context %s does not exist", context)
}

err = localconfig.ValidateLocalConfig(*localCfg)
if err != nil {
log.Fatalf("Error in logging out: %s", err)
}
err = localconfig.WriteLocalConfig(*localCfg, globalClientOpts.ConfigPath)
errors.CheckError(err)

fmt.Printf("Logged out from '%s'\n", context)
},
}
return command
}
39 changes: 39 additions & 0 deletions cmd/argocd/commands/logout_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package commands

import (
"io/ioutil"
"os"
"testing"

"github.com/argoproj/argo-cd/pkg/apiclient"

"github.com/stretchr/testify/assert"

"github.com/argoproj/argo-cd/util/localconfig"
)

func TestLogout(t *testing.T) {

// Write the test config file
err := ioutil.WriteFile(testConfigFilePath, []byte(testConfig), os.ModePerm)
assert.NoError(t, err)

localConfig, err := localconfig.ReadLocalConfig(testConfigFilePath)
assert.NoError(t, err)
assert.Equal(t, localConfig.CurrentContext, "localhost:8080")
assert.Contains(t, localConfig.Contexts, localconfig.ContextRef{Name: "localhost:8080", Server: "localhost:8080", User: "localhost:8080"})

command := NewLogoutCommand(&apiclient.ClientOptions{ConfigPath: testConfigFilePath})
command.Run(nil, []string{"localhost:8080"})

localConfig, err = localconfig.ReadLocalConfig(testConfigFilePath)
assert.NoError(t, err)
assert.Equal(t, localConfig.CurrentContext, "localhost:8080")
assert.NotContains(t, localConfig.Users, localconfig.User{AuthToken: "vErrYS3c3tReFRe$hToken", Name: "localhost:8080"})
assert.Contains(t, localConfig.Contexts, localconfig.ContextRef{Name: "argocd.example.com:443", Server: "argocd.example.com:443", User: "argocd.example.com:443"})

// Write the file again so that no conflicts are made in git
err = ioutil.WriteFile(testConfigFilePath, []byte(testConfig), os.ModePerm)
assert.NoError(t, err)

}
1 change: 1 addition & 0 deletions cmd/argocd/commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func NewCommand() *cobra.Command {
command.AddCommand(NewContextCommand(&clientOpts))
command.AddCommand(NewProjectCommand(&clientOpts))
command.AddCommand(NewAccountCommand(&clientOpts))
command.AddCommand(NewLogoutCommand(&clientOpts))

defaultLocalConfigPath, err := localconfig.DefaultLocalConfigPath()
errors.CheckError(err)
Expand Down
18 changes: 18 additions & 0 deletions cmd/argocd/commands/testdata/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
contexts:
- name: argocd.example.com:443
server: argocd.example.com:443
user: argocd.example.com:443
- name: localhost:8080
server: localhost:8080
user: localhost:8080
current-context: localhost:8080
servers:
- server: argocd.example.com:443
- plain-text: true
server: localhost:8080
users:
- auth-token: vErrYS3c3tReFRe$hToken
name: argocd.example.com:443
refresh-token: vErrYS3c3tReFRe$hToken
- auth-token: vErrYS3c3tReFRe$hToken
name: localhost:8080
59 changes: 58 additions & 1 deletion util/localconfig/localconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"os/user"
"path"

jwt "github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go"

configUtil "github.com/argoproj/argo-cd/util/config"
)
Expand Down Expand Up @@ -102,6 +102,14 @@ func WriteLocalConfig(config LocalConfig, configPath string) error {
return configUtil.MarshalLocalYAMLFile(configPath, config)
}

func DeleteLocalConfig(configPath string) error {
_, err := os.Stat(configPath)
if os.IsNotExist(err) {
return err
}
return os.Remove(configPath)
}

// ResolveContext resolves the specified context. If unspecified, resolves the current context
func (l *LocalConfig) ResolveContext(name string) (*Context, error) {
if name == "" {
Expand Down Expand Up @@ -146,6 +154,17 @@ func (l *LocalConfig) UpsertServer(server Server) {
l.Servers = append(l.Servers, server)
}

// Returns true if server was removed successfully
func (l *LocalConfig) RemoveServer(serverName string) bool {
for i, s := range l.Servers {
if s.Server == serverName {
l.Servers = append(l.Servers[:i], l.Servers[i+1:]...)
return true
}
}
return false
}

func (l *LocalConfig) GetUser(name string) (*User, error) {
for _, u := range l.Users {
if u.Name == name {
Expand All @@ -165,6 +184,29 @@ func (l *LocalConfig) UpsertUser(user User) {
l.Users = append(l.Users, user)
}

// Returns true if user was removed successfully
func (l *LocalConfig) RemoveUser(serverName string) bool {
for i, u := range l.Users {
if u.Name == serverName {
l.Users = append(l.Users[:i], l.Users[i+1:]...)
return true
}
}
return false
}

// Returns true if user was removed successfully
func (l *LocalConfig) RemoveToken(serverName string) bool {
for i, u := range l.Users {
if u.Name == serverName {
l.Users[i].RefreshToken = ""
l.Users[i].AuthToken = ""
return true
}
}
return false
}

func (l *LocalConfig) UpsertContext(context ContextRef) {
for i, c := range l.Contexts {
if c.Name == context.Name {
Expand All @@ -175,6 +217,21 @@ func (l *LocalConfig) UpsertContext(context ContextRef) {
l.Contexts = append(l.Contexts, context)
}

// Returns true if context was removed successfully
func (l *LocalConfig) RemoveContext(serverName string) (string, bool) {
for i, c := range l.Contexts {
if c.Name == serverName {
l.Contexts = append(l.Contexts[:i], l.Contexts[i+1:]...)
return c.Server, true
}
}
return "", false
}

func (l *LocalConfig) IsEmpty() bool {
return len(l.Servers) == 0
}

// DefaultConfigDir returns the local configuration path for settings such as cached authentication tokens.
func DefaultConfigDir() (string, error) {
usr, err := user.Current()
Expand Down

0 comments on commit 4860f2c

Please sign in to comment.