Skip to content
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

feat: config init command #291

Merged
merged 12 commits into from
Jun 14, 2021
7 changes: 2 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,16 @@ mocks: $(GOBIN)/mockgen
$(GOBIN)/commander:
cd && GO111MODULE=auto go get github.com/commander-cli/commander/cmd/commander

$(GOBIN)/auth0-cli-config-generator:
go install ./pkg/auth0-cli-config-generator

run-integration:
auth0-cli-config-generator && commander test commander.yaml
auth0 config init && commander test commander.yaml
.PHONY: run-integration

# Delete all test apps created during integration testing
integration-cleanup:
./integration/test-cleanup.sh
.PHONY: integration-cleanup

integration: build $(GOBIN)/auth0-cli-config-generator $(GOBIN)/commander
integration: build $(GOBIN)/commander
$(MAKE) run-integration; \
ret=$$?; \
$(MAKE) integration-cleanup; \
Expand Down
11 changes: 11 additions & 0 deletions internal/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ var requiredScopes = []string{
// RequiredScopes returns the scopes used for login.
func RequiredScopes() []string { return requiredScopes }

// RequiredScopesMin returns minimum scopes used for login in integration tests.
func RequiredScopesMin() []string {
min := []string{}
for _, s := range requiredScopes {
if s != "offline_access" && s != "openid" {
min = append(min, s)
}
}
return min
}

// SecretStore provides access to stored sensitive data.
type SecretStore interface {
// Get gets the secret
Expand Down
14 changes: 13 additions & 1 deletion internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ func (c *cli) init() error {

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

if _, err := os.Stat(c.path); os.IsNotExist(err) {
Expand All @@ -472,6 +472,18 @@ func (c *cli) initContext() (err error) {
return nil
}

func defaultConfigPath() string {
return path.Join(os.Getenv("HOME"), ".config", "auth0", "config.json")
}

func (c *cli) setPath(p string) {
if p == "" {
c.path = defaultConfigPath()
return
}
c.path = p
}

func canPrompt(cmd *cobra.Command) bool {
noInput, err := cmd.Root().Flags().GetBool("no-input")

Expand Down
132 changes: 132 additions & 0 deletions internal/cli/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package cli

import (
"context"
"fmt"
"net/url"
"strings"

"github.com/auth0/auth0-cli/internal/auth"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/oauth2/clientcredentials"
)

var desiredInputs = `Config init is intended for non-interactive use,
ensure the following env variables are set:

AUTH0_CLI_CLIENT_DOMAIN
AUTH0_CLI_CLIENT_ID
AUTH0_CLI_CLIENT_SECRET

Interactive logins should use "auth0 login" instead.`

type params struct {
filePath string
clientDomain string
clientID string
clientSecret string
}

func (p params) validate() error {
as-herzog marked this conversation as resolved.
Show resolved Hide resolved
if p.clientDomain == "" {
return fmt.Errorf("missing client domain:\n%s", desiredInputs)
}

u, err := url.Parse(p.clientDomain)
if err != nil {
return fmt.Errorf("failed to parse client domain: %s", p.clientDomain)
}

if u.Scheme != "" {
return fmt.Errorf("client domain cant include a scheme: %s", p.clientDomain)
}

if p.clientID == "" {
return fmt.Errorf("missing client id:\n%s", desiredInputs)
}

if p.clientSecret == "" {
return fmt.Errorf("missing client secret:\n%s", desiredInputs)
}
return nil
}

func configCmd(cli *cli) *cobra.Command {
cmd := &cobra.Command{
Use: "config",
Short: "Manage auth0-cli config",
Long: "Manage auth0-cli config",
Hidden: true,
}

cmd.AddCommand(initCmd(cli))
return cmd
}

func initCmd(cli *cli) *cobra.Command {
cmd := &cobra.Command{
Use: "init",
Short: "Configure the CLI from environment variables",
RunE: func(command *cobra.Command, args []string) error {
filePath := viper.GetString("FILEPATH")
clientDomain := viper.GetString("CLIENT_DOMAIN")
clientID := viper.GetString("CLIENT_ID")
clientSecret := viper.GetString("CLIENT_SECRET")

cli.setPath(filePath)
p := params{filePath, clientDomain, clientID, clientSecret}
if err := p.validate(); err != nil {
return err
}

u, err := url.Parse("https://" + p.clientDomain)
if err != nil {
return err
}

c := &clientcredentials.Config{
ClientID: p.clientID,
ClientSecret: p.clientSecret,
TokenURL: u.String() + "/oauth/token",
EndpointParams: url.Values{
"client_id": {p.clientID},
"scope": {strings.Join(auth.RequiredScopesMin(), " ")},
"audience": {u.String() + "/api/v2/"},
},
}

token, err := c.Token(context.Background())
if err != nil {
return err
}

t := tenant{
Name: p.clientDomain,
Domain: p.clientDomain,
AccessToken: token.AccessToken,
ExpiresAt: token.Expiry,
Scopes: auth.RequiredScopes(),
}

if err := cli.addTenant(t); err != nil {
return fmt.Errorf("unexpected error adding tenant to config: %w", err)
}
return nil
},
}
viper.SetEnvPrefix("AUTH0_CLI")
viper.AutomaticEnv()

flags := cmd.Flags()
flags.String("filepath", defaultConfigPath(), "Filepath for the auth0 cli config")
_ = viper.BindPFlag("FILEPATH", flags.Lookup("filepath"))
flags.String("client-id", "", "Client ID to set within config")
_ = viper.BindPFlag("CLIENT_ID", flags.Lookup("client-id"))
flags.String("client-secret", "", "Client secret to use to generate token which is set within config")
_ = viper.BindPFlag("CLIENT_SECRET", flags.Lookup("client-secret"))
flags.String("client-domain", "", "Client domain to use to generate token which is set within config")
_ = viper.BindPFlag("CLIENT_DOMAIN", flags.Lookup("client-domain"))

return cmd
}
8 changes: 7 additions & 1 deletion internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ func Execute() {
return nil
}

// config init shouldn't trigger a login.
if cmd.CalledAs() == "init" && cmd.Parent().Use == "config" {
return nil
}

defer cli.tracker.TrackCommandRun(cmd, cli.config.InstallID)

// Initialize everything once. Later callers can then
Expand Down Expand Up @@ -96,6 +101,7 @@ func Execute() {
// order of the comamnds here matters
// so add new commands in a place that reflect its relevance or relation with other commands:
rootCmd.AddCommand(loginCmd(cli))
rootCmd.AddCommand(configCmd(cli))
rootCmd.AddCommand(tenantsCmd(cli))
rootCmd.AddCommand(usersCmd(cli))
rootCmd.AddCommand(appsCmd(cli))
Expand Down Expand Up @@ -144,7 +150,7 @@ func Execute() {
os.Exit(1)
}

ctx, cancel := context.WithTimeout(cli.context, 3 * time.Second)
ctx, cancel := context.WithTimeout(cli.context, 3*time.Second)
// defers are executed in LIFO order
defer cancel()
defer cli.tracker.Wait(ctx) // No event should be tracked after this has run, or it will panic e.g. in earlier deferred functions
Expand Down
Loading