Skip to content

Commit

Permalink
Merge pull request #299 from kamaln7/feature/context-switching
Browse files Browse the repository at this point in the history
Implement context switching, allowing for multiple configured API access keys
  • Loading branch information
jrm780 authored Apr 9, 2018
2 parents 82f2db9 + 95dc11a commit b59b652
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 21 deletions.
2 changes: 2 additions & 0 deletions args.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ package doctl
const (
// ArgAccessToken is the access token to be used for the operations
ArgAccessToken = "access-token"
// ArgContext is the name of the auth context to use
ArgContext = "context"
// ArgActionID is an action id argument.
ArgActionID = "action-id"
// ArgActionAfter is an action after argument.
Expand Down
26 changes: 19 additions & 7 deletions commands/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,9 @@ import (

"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/digitalocean/doctl"
)

// ErrUnknownTerminal signies an unknown terminal. It is returned when doit
// ErrUnknownTerminal signifies an unknown terminal. It is returned when doit
// can't ascertain the current terminal type with requesting an auth token.
var ErrUnknownTerminal = errors.New("unknown terminal")

Expand Down Expand Up @@ -65,15 +64,16 @@ func Auth() *Command {
}

cmdBuilderWithInit(cmd, RunAuthInit(retrieveUserTokenFromCommandLine), "init", "initialize configuration", Writer, false, docCategories("auth"))
cmdBuilderWithInit(cmd, RunAuthSwitch, "switch", "writes the auth context permanently to config", Writer, false, docCategories("auth"))

return cmd
}

// RunAuthInit initializes the doctl config. Configuration is stored in $XDG_CONFIG_HOME/doctl. On Unix, if
// XDG_CONFIG_HOME is not set, use $HOME/.config. On Windows use %APPDATA%/doctl/config.
func RunAuthInit( retrieveUserTokenFunc func() (string, error) ) func (c *CmdConfig) error {
return func(c * CmdConfig) error {
token := viper.GetString(doctl.ArgAccessToken)
func RunAuthInit(retrieveUserTokenFunc func() (string, error)) func(c *CmdConfig) error {
return func(c *CmdConfig) error {
token := c.getContextAccessToken()

if token == "" {
in, err := retrieveUserTokenFunc()
Expand All @@ -82,11 +82,11 @@ func RunAuthInit( retrieveUserTokenFunc func() (string, error) ) func (c *CmdCon
}
token = strings.TrimSpace(in)
} else {
fmt.Fprintf(c.Out,"Using token [%v]", token)
fmt.Fprintf(c.Out, "Using token [%v]", token)
fmt.Fprintln(c.Out)
}

viper.Set(doctl.ArgAccessToken, string(token))
c.setContextAccessToken(string(token))

fmt.Fprintln(c.Out)
fmt.Fprint(c.Out, "Validating token... ")
Expand All @@ -108,3 +108,15 @@ func RunAuthInit( retrieveUserTokenFunc func() (string, error) ) func (c *CmdCon
return writeConfig()
}
}

func RunAuthSwitch(c *CmdConfig) error {
context := Context
if context == "" {
context = viper.GetString("context")
}

viper.Set("context", context)

fmt.Printf("Now using context [%s] by default\n", context)
return writeConfig()
}
15 changes: 8 additions & 7 deletions commands/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,23 @@ import (
"io/ioutil"
"testing"

"github.com/digitalocean/doctl/do"
"github.com/stretchr/testify/assert"
"errors"
"github.com/spf13/viper"

"github.com/digitalocean/doctl"
"github.com/digitalocean/doctl/do"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
)

func TestAuthCommand(t *testing.T) {
cmd := Auth()
assert.NotNil(t, cmd)
assertCommandNames(t, cmd, "init")
assertCommandNames(t, cmd, "init", "switch")
}

func TestAuthInit(t *testing.T) {
cfw := cfgFileWriter
viper.Set(doctl.ArgAccessToken, nil);
viper.Set(doctl.ArgAccessToken, nil)
defer func() {
cfgFileWriter = cfw
}()
Expand All @@ -54,10 +55,10 @@ func TestAuthInit(t *testing.T) {

func TestAuthInitWithProvidedToken(t *testing.T) {
cfw := cfgFileWriter
viper.Set(doctl.ArgAccessToken, "valid-token");
viper.Set(doctl.ArgAccessToken, "valid-token")
defer func() {
cfgFileWriter = cfw
viper.Set(doctl.ArgAccessToken, nil);
viper.Set(doctl.ArgAccessToken, nil)
}()

retrieveUserTokenFunc := func() (string, error) {
Expand Down
8 changes: 7 additions & 1 deletion commands/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,12 @@ func withTestClient(t *testing.T, tFn testFn) {
// can stub this out, since the return is dictated by the mocks.
initServices: func(c *CmdConfig) error { return nil },

getContextAccessToken: func() string {
return viper.GetString(doctl.ArgAccessToken)
},

setContextAccessToken: func(token string) { },

Keys: func() do.KeysService { return &tm.keys },
Sizes: func() do.SizesService { return &tm.sizes },
Regions: func() do.RegionsService { return &tm.regions },
Expand Down Expand Up @@ -246,7 +252,7 @@ func NewTestConfig() *TestConfig {

var _ doctl.Config = &TestConfig{}

func (c *TestConfig) GetGodoClient(trace bool) (*godo.Client, error) {
func (c *TestConfig) GetGodoClient(trace bool, accessToken string) (*godo.Client, error) {
return &godo.Client{}, nil
}

Expand Down
47 changes: 46 additions & 1 deletion commands/doit.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ var DoitCmd = &Command{
},
}

// Context holds the current auth context
var Context string

// ApiURL holds the API URL to use.
var ApiURL string

Expand Down Expand Up @@ -83,6 +86,8 @@ func init() {
DoitCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")
DoitCmd.PersistentFlags().BoolVarP(&Trace, "trace", "", false, "trace api access")

DoitCmd.PersistentFlags().StringVarP(&Context, doctl.ArgContext, "", "", "authentication context name")

viper.SetEnvPrefix("DIGITALOCEAN")
viper.BindEnv(doctl.ArgAccessToken, "DIGITALOCEAN_ACCESS_TOKEN")
viper.BindPFlag(doctl.ArgAccessToken, DoitCmd.PersistentFlags().Lookup("access-token"))
Expand Down Expand Up @@ -116,6 +121,7 @@ func initConfig() {
}

viper.SetDefault("output", "text")
viper.SetDefault("context", "default")
}

func findConfig() (string, error) {
Expand Down Expand Up @@ -302,6 +308,8 @@ type CmdConfig struct {
Args []string

initServices func(*CmdConfig) error
getContextAccessToken func() string
setContextAccessToken func(string)

// services
Keys func() do.KeysService
Expand Down Expand Up @@ -335,7 +343,8 @@ func NewCmdConfig(ns string, dc doctl.Config, out io.Writer, args []string, init
Args: args,

initServices: func(c *CmdConfig) error {
godoClient, err := c.Doit.GetGodoClient(Trace)
accessToken := c.getContextAccessToken()
godoClient, err := c.Doit.GetGodoClient(Trace, accessToken)
if err != nil {
return fmt.Errorf("unable to initialize DigitalOcean api client: %s", err)
}
Expand All @@ -362,6 +371,42 @@ func NewCmdConfig(ns string, dc doctl.Config, out io.Writer, args []string, init

return nil
},

getContextAccessToken: func() string {
context := Context
if context == "" {
context = viper.GetString("context")
}
token := ""

switch context {
case "default":
token = viper.GetString(doctl.ArgAccessToken)
default:
contexts := viper.GetStringMapString("auth-contexts")

token = contexts[context]
}

return token
},

setContextAccessToken: func(token string) {
context := Context
if context == "" {
context = viper.GetString("context")
}

switch context {
case "default":
viper.Set(doctl.ArgAccessToken, token)
default:
contexts := viper.GetStringMapString("auth-contexts")
contexts[context] = token

viper.Set("auth-contexts", contexts)
}
},
}

if initGodo {
Expand Down
9 changes: 4 additions & 5 deletions doit.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ func (glv *GithubLatestVersioner) LatestVersion() (string, error) {

// Config is an interface that represent doit's config.
type Config interface {
GetGodoClient(trace bool) (*godo.Client, error)
GetGodoClient(trace bool, accessToken string) (*godo.Client, error)
SSH(user, host, keyPath string, port int, opts ssh.Options) runner.Runner
Set(ns, key string, val interface{})
GetString(ns, key string) (string, error)
Expand All @@ -170,13 +170,12 @@ type LiveConfig struct {
var _ Config = &LiveConfig{}

// GetGodoClient returns a GodoClient.
func (c *LiveConfig) GetGodoClient(trace bool) (*godo.Client, error) {
token := viper.GetString(ArgAccessToken)
if token == "" {
func (c *LiveConfig) GetGodoClient(trace bool, accessToken string) (*godo.Client, error) {
if accessToken == "" {
return nil, fmt.Errorf("access token is required. (hint: run 'doctl auth init')")
}

tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: accessToken})
oauthClient := oauth2.NewClient(oauth2.NoContext, tokenSource)

if trace {
Expand Down

0 comments on commit b59b652

Please sign in to comment.