Skip to content

Commit

Permalink
Cleanup internal/cli package. Make it work
Browse files Browse the repository at this point in the history
  • Loading branch information
cyx committed Jan 23, 2021
1 parent d5ccd73 commit d8758f5
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 44 deletions.
154 changes: 111 additions & 43 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,111 +6,179 @@ import (
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"sync"
"time"

"github.com/auth0/auth0-cli/internal/display"
"gopkg.in/auth0.v5/management"
)

type data struct {
// config defines the exact set of tenants, access tokens, which only exists
// for a particular user's machine.
type config struct {
DefaultTenant string `json:"default_tenant"`
Tenants map[string]tenant `json:"tenants"`
}

// tenant is the cli's concept of an auth0 tenant. The fields are tailor fit
// specifically for interacting with the management API.
type tenant struct {
Domain string `json:"domain"`

ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`

// TODO(cyx): This will be what we do with device flow.
BearerToken string `json:"bearer_token,omitempty"`
Name string `json:"name"`
Domain string `json:"domain"`
AccessToken string `json:"access_token,omitempty"`
ExpiresAt time.Time `json:expires_at"`
}

// cli provides all the foundational things for all the commands in the CLI,
// specifically:
//
// 1. A management API instance (e.g. go-auth0/auth0)
// 2. A renderer (which provides ansi, coloring, etc).
//
// In addition, it stores a reference to all the flags passed, e.g.:
//
// 1. --format
// 2. --tenant
// 3. --verbose
//
type cli struct {
// core primitives exposed to command builders.
api *management.Management
renderer *display.Renderer

// set of flags which are user specified.
verbose bool
tenant string
format string

// config state management.
initOnce sync.Once
path string
data data
config config
}

// setup will try to initialize the config context, as well as figure out if
// there's a readily available tenant. A management API SDK instance is initialized IFF:
//
// 1. A tenant is found.
// 2. The tenant has an access token.
func (c *cli) setup() error {
c.init()

t, err := c.getTenant()
if err != nil {
return err
}

if t.BearerToken != "" {
if t.AccessToken != "" {
c.api, err = management.New(t.Domain,
management.WithStaticToken(t.BearerToken),
management.WithDebug(c.verbose))
} else {
c.api, err = management.New(t.Domain,
management.WithClientCredentials(t.ClientID, t.ClientSecret),
management.WithStaticToken(t.AccessToken),
management.WithDebug(c.verbose))
}

return err
return nil
}

// getTenant fetches the default tenant configured (or the tenant specified via
// the --tenant flag).
func (c *cli) getTenant() (tenant, error) {
if err := c.init(); err != nil {
return tenant{}, err
}

t, ok := c.data.Tenants[c.tenant]
t, ok := c.config.Tenants[c.tenant]
if !ok {
return tenant{}, fmt.Errorf("Unable to find tenant: %s", c.tenant)
}

return t, nil
}

func (c *cli) init() error {
var err error
c.initOnce.Do(func() {
if c.path == "" {
c.path = path.Join(os.Getenv("HOME"), ".config", "auth0", "config.json")
}
// setTenant assigns an existing, or new tenant. This is expected to be called
// after a login has completed.
func (c *cli) setTenant(ten tenant) error {
c.init()

var buf []byte
if buf, err = ioutil.ReadFile(c.path); err != nil {
return
}
// If there's no existing DefaultTenant yet, might as well set the
// first successfully logged in tenant during onboarding.
if c.config.DefaultTenant == "" {
c.config.DefaultTenant = ten.Name
}

if err = json.Unmarshal(buf, &c.data); err != nil {
return
}
// If we're dealing with an empty file, we'll need to initialize this
// map.
if c.config.Tenants == nil {
c.config.Tenants = map[string]tenant{}
}

if c.tenant == "" && c.data.DefaultTenant == "" {
err = fmt.Errorf("Not yet configured. Try `auth0 login`.")
return
}
c.config.Tenants[ten.Name] = ten

dir := filepath.Dir(c.path)
if _, err := os.Stat(dir); os.IsNotExist(err) {
os.MkdirAll(dir, 0700)
}

if c.tenant == "" {
c.tenant = c.data.DefaultTenant
buf, err := json.MarshalIndent(c.config, "", " ")
if err != nil {
return err
}

if err := ioutil.WriteFile(c.path, buf, 0600); err != nil {
return err
}

return nil
}

func (c *cli) init() error {
var err error
c.initOnce.Do(func() {
// Initialize the context -- e.g. the configuration
// information, tenants, etc.
if err = c.initContext(); err != nil {
return
}
c.renderer.Tenant = c.tenant

// Determine what the desired output format is.
format := strings.ToLower(c.format)
if format != "" && format != string(display.OutputFormatJSON) {
err = fmt.Errorf("Invalid format. Use `--format=json` or omit this option to use the default format.")
return
}

c.renderer = &display.Renderer{
Tenant: c.tenant,
MessageWriter: os.Stderr,
ResultWriter: os.Stdout,
Format: display.OutputFormat(format),
}
c.renderer.Format = display.OutputFormat(format)
})

return err
}

func (c *cli) initContext() (err error) {
if c.path == "" {
c.path = path.Join(os.Getenv("HOME"), ".config", "auth0", "config.json")
}

if _, err := os.Stat(c.path); os.IsNotExist(err) {
return fmt.Errorf("Not yet configured. Try `auth0 login`.")
}

var buf []byte
if buf, err = ioutil.ReadFile(c.path); err != nil {
return err
}

if err := json.Unmarshal(buf, &c.config); err != nil {
return err
}

if c.tenant == "" && c.config.DefaultTenant == "" {
return fmt.Errorf("Not yet configured. Try `auth0 login`.")
}

if c.tenant == "" {
c.tenant = c.config.DefaultTenant
}

return nil
}
12 changes: 11 additions & 1 deletion internal/cli/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cli

import (
"fmt"
"time"

"github.com/auth0/auth0-cli/internal/ansi"
"github.com/auth0/auth0-cli/internal/auth"
Expand Down Expand Up @@ -42,9 +43,18 @@ func loginCmd(cli *cli) *cobra.Command {
return fmt.Errorf("login error: %w", err)
}

// TODO(jfatta): update the configuration with the token, tenant, audience, etc
cli.renderer.Infof("Successfully logged in.")
cli.renderer.Infof("Tenant: %s", res.Tenant)

cli.setTenant(tenant{
Name: res.Tenant,
Domain: res.Domain,
AccessToken: res.AccessToken,
ExpiresAt: time.Now().Add(
time.Duration(res.ExpiresIn) * time.Second,
),
})

return nil
},
}
Expand Down
6 changes: 6 additions & 0 deletions internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ func Execute() {
Long: "Command-line tool to interact with Auth0.\n" + getLogin(&fs, cli),

PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// If the user is trying to login, no need to go
// through setup.
if cmd.Use == "login" {
return nil
}

// Initialize everything once. Later callers can then
// freely assume that config is fully primed and ready
// to go.
Expand Down

0 comments on commit d8758f5

Please sign in to comment.