cmd: use spf13/cobra for dexctl cli logic

This commit is contained in:
Eric Chiang 2015-12-28 15:55:11 -08:00
parent 71f5021678
commit 8e5115ce73
5 changed files with 138 additions and 217 deletions

View file

@ -4,22 +4,24 @@ import (
"net/url"
"github.com/coreos/go-oidc/oidc"
"github.com/spf13/cobra"
)
var (
cmdNewClient = &command{
Name: "new-client",
Summary: "Create a new client with the provided redirect URL(s)",
Usage: "<URL>...",
Run: runNewClient,
cmdNewClient = &cobra.Command{
Use: "new-client",
Short: "Create a new client with one or more redirect URLs.",
Long: "Create a new client with one or more redirect URLs,",
Example: ` dexctl new-client --db-url=${DB_URL} 'https://example.com/callback'`,
Run: wrapRun(runNewClient),
}
)
func init() {
commands = append(commands, cmdNewClient)
rootCmd.AddCommand(cmdNewClient)
}
func runNewClient(args []string) int {
func runNewClient(cmd *cobra.Command, args []string) int {
if len(args) < 1 {
stderr("Provide at least one redirect URL.")
return 2

View file

@ -4,30 +4,33 @@ import (
"fmt"
"github.com/coreos/dex/connector"
"github.com/spf13/cobra"
)
var (
cmdGetConnectorConfigs = &command{
Name: "get-connector-configs",
Summary: "Enumerate current IdP connector configs.",
Usage: "",
Run: runGetConnectorConfigs,
cmdGetConnectorConfigs = &cobra.Command{
Use: "get-connector-configs",
Short: "Enumerate current IdP connector configs.",
Long: "Enumerate current IdP connector configs.",
Example: ` dexctl get-connector-configs --db-url=${DB_URL}`,
Run: wrapRun(runGetConnectorConfigs),
}
cmdSetConnectorConfigs = &command{
Name: "set-connector-configs",
Summary: "Overwrite the current IdP connector configs with those from a local file.",
Usage: "<FILE>",
Run: runSetConnectorConfigs,
cmdSetConnectorConfigs = &cobra.Command{
Use: "set-connector-configs",
Short: "Overwrite the current IdP connector configs with those from a local file.",
Long: "Overwrite the current IdP connector configs with those from a local file.",
Example: ` dexctl set-connector-configs --db-url=${DB_URL} ./static/conn_conf.json`,
Run: wrapRun(runSetConnectorConfigs),
}
)
func init() {
commands = append(commands, cmdSetConnectorConfigs)
commands = append(commands, cmdGetConnectorConfigs)
rootCmd.AddCommand(cmdGetConnectorConfigs)
rootCmd.AddCommand(cmdSetConnectorConfigs)
}
func runSetConnectorConfigs(args []string) int {
func runSetConnectorConfigs(cmd *cobra.Command, args []string) int {
if len(args) != 1 {
stderr("Provide a single argument.")
return 2
@ -55,7 +58,7 @@ func runSetConnectorConfigs(args []string) int {
return 0
}
func runGetConnectorConfigs(args []string) int {
func runGetConnectorConfigs(cmd *cobra.Command, args []string) int {
if len(args) != 0 {
stderr("Provide zero arguments.")
return 2

View file

@ -1,135 +0,0 @@
package main
import (
"flag"
"fmt"
"os"
"strings"
"text/tabwriter"
"text/template"
)
var (
cmdHelp = &command{
Name: "help",
Summary: "Show a list of commands or help for one command",
Usage: "[COMMAND]",
Run: runHelp,
}
globalUsageTemplate *template.Template
commandUsageTemplate *template.Template
templFuncs = template.FuncMap{
"descToLines": func(s string) []string {
// trim leading/trailing whitespace and split into slice of lines
return strings.Split(strings.Trim(s, "\n\t "), "\n")
},
"printOption": func(name, defvalue, usage string) string {
prefix := "--"
if len(name) == 1 {
prefix = "-"
}
return fmt.Sprintf("\n\t%s%s=%s\t%s", prefix, name, defvalue, usage)
},
}
tabOut *tabwriter.Writer
)
func init() {
tabOut = tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
commands = append(commands, cmdHelp)
globalUsageTemplate = template.Must(template.New("global_usage").Funcs(templFuncs).Parse(`
NAME:
{{printf "\t%s - %s" .Executable .Description}}
USAGE:
{{printf "\t%s" .Executable}} [global options] <command> [command options] [arguments...]
COMMANDS:{{range .Commands}}
{{printf "\t%s\t%s" .Name .Summary}}{{end}}
GLOBAL OPTIONS:{{range .Flags}}{{printOption .Name .DefValue .Usage}}{{end}}
Global options can also be configured via upper-case environment variables prefixed with "DEXCTL_"
For example, "some-flag" => "DEXCTL_SOME_FLAG"
Run "{{.Executable}} help <command>" for more details on a specific command.
`[1:]))
commandUsageTemplate = template.Must(template.New("command_usage").Funcs(templFuncs).Parse(`
NAME:
{{printf "\t%s - %s" .Cmd.Name .Cmd.Summary}}
USAGE:
{{printf "\t%s %s %s" .Executable .Cmd.Name .Cmd.Usage}}
DESCRIPTION:
{{range $line := descToLines .Cmd.Description}}{{printf "\t%s" $line}}
{{end}}
{{if .CmdFlags}}OPTIONS:{{range .CmdFlags}}
{{printOption .Name .DefValue .Usage}}{{end}}
{{end}}For help on global options run "{{.Executable}} help"
`[1:]))
}
func runHelp(args []string) (exit int) {
if len(args) < 1 {
printGlobalUsage()
return
}
var cmd *command
for _, c := range commands {
if c.Name == args[0] {
cmd = c
break
}
}
if cmd == nil {
stderr("Unrecognized command: %s", args[0])
return 1
}
printCommandUsage(cmd)
return
}
func printGlobalUsage() {
globalUsageTemplate.Execute(tabOut, struct {
Executable string
Commands []*command
Flags []*flag.Flag
Description string
}{
cliName,
commands,
getFlags(globalFS),
cliDescription,
})
tabOut.Flush()
}
func printCommandUsage(cmd *command) {
commandUsageTemplate.Execute(tabOut, struct {
Executable string
Cmd *command
CmdFlags []*flag.Flag
}{
cliName,
cmd,
getFlags(&cmd.Flags),
})
tabOut.Flush()
}
func getFlags(flagset *flag.FlagSet) (flags []*flag.Flag) {
flags = make([]*flag.Flag, 0)
flagset.VisitAll(func(f *flag.Flag) {
flags = append(flags, f)
})
return
}

View file

@ -2,21 +2,46 @@ package main
import (
"errors"
"flag"
"net/http"
"os"
"strings"
pflag "github.com/coreos/dex/pkg/flag"
"github.com/coreos/dex/pkg/log"
"github.com/coreos/go-oidc/oidc"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
var (
cliName = "dexctl"
cliDescription = "???"
rootCmd = &cobra.Command{
Use: "dexctl",
Short: "A command line tool for interacting with the dex system",
Long: "",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// initialize flags from environment
fs := cmd.Flags()
commands []*command
globalFS = flag.NewFlagSet(cliName, flag.ExitOnError)
// don't override flags set by command line flags
alreadySet := make(map[string]bool)
fs.Visit(func(f *pflag.Flag) { alreadySet[f.Name] = true })
var err error
fs.VisitAll(func(f *pflag.Flag) {
if err != nil || alreadySet[f.Name] {
return
}
key := "DEXCTL_" + strings.ToUpper(strings.Replace(f.Name, "-", "_", -1))
if val := os.Getenv(key); val != "" {
err = fs.Set(f.Name, val)
}
})
return err
},
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
os.Exit(2)
},
}
global struct {
endpoint string
@ -30,69 +55,23 @@ var (
func init() {
log.EnableTimestamps()
globalFS.StringVar(&global.endpoint, "endpoint", "", "URL of dex API")
globalFS.StringVar(&global.creds.ID, "client-id", "", "dex API user ID")
globalFS.StringVar(&global.creds.Secret, "client-secret", "", "dex API user password")
globalFS.StringVar(&global.dbURL, "db-url", "", "DSN-formatted database connection string")
globalFS.BoolVar(&global.help, "help", false, "Print usage information and exit")
globalFS.BoolVar(&global.help, "h", false, "Print usage information and exit")
globalFS.BoolVar(&global.logDebug, "log-debug", false, "Log debug-level information")
rootCmd.PersistentFlags().StringVar(&global.endpoint, "endpoint", "", "URL of dex API")
rootCmd.PersistentFlags().StringVar(&global.creds.ID, "client-id", "", "dex API user ID")
rootCmd.PersistentFlags().StringVar(&global.creds.Secret, "client-secret", "", "dex API user password")
rootCmd.PersistentFlags().StringVar(&global.dbURL, "db-url", "", "DSN-formatted database connection string")
rootCmd.PersistentFlags().BoolVar(&global.logDebug, "log-debug", false, "Log debug-level information")
}
func main() {
err := parseFlags()
if err != nil {
stderr(err.Error())
if err := rootCmd.Execute(); err != nil {
os.Exit(2)
}
if global.logDebug {
log.EnableDebug()
}
args := globalFS.Args()
if len(args) < 1 || global.help {
args = []string{"help"}
}
var cmd *command
for _, c := range commands {
if c.Name == args[0] {
cmd = c
if err := c.Flags.Parse(args[1:]); err != nil {
stderr("%v", err)
os.Exit(2)
}
break
}
}
if cmd == nil {
stderr("%v: unknown subcommand: %q", cliName, args[0])
stderr("Run '%v help' for usage.", cliName)
os.Exit(2)
}
os.Exit(cmd.Run(cmd.Flags.Args()))
}
type command struct {
Name string // Name of the command and the string to use to invoke it
Summary string // One-sentence summary of what the command does
Usage string // Usage options/arguments
Description string // Detailed description of command
Flags flag.FlagSet // Set of flags associated with this command
Run func(args []string) int // Run a command with the given arguments, return exit status
}
func parseFlags() error {
if err := globalFS.Parse(os.Args[1:]); err != nil {
return err
func wrapRun(run func(cmd *cobra.Command, args []string) int) func(cmd *cobra.Command, args []string) {
return func(cmd *cobra.Command, args []string) {
os.Exit(run(cmd, args))
}
return pflag.SetFlagsFromEnv(globalFS, "DEXCTL")
}
func getDriver() (drv driver) {

72
functional/dexctl_test.go Normal file
View file

@ -0,0 +1,72 @@
package functional
import (
"io/ioutil"
"os"
"os/exec"
"strings"
"testing"
)
var connConfigExample = []byte(`[
{
"type": "local",
"id": "local",
"passwordInfos": [
{
"userId":"elroy-id",
"passwordPlaintext": "bones"
},
{
"userId":"penny",
"passwordPlaintext": "kibble"
}
]
}
]`)
func TestDexctlCommands(t *testing.T) {
tempFile, err := ioutil.TempFile("", "dexctl_functional_tests_")
if err != nil {
t.Fatal(err)
}
connConfig := tempFile.Name()
defer os.Remove(connConfig)
if _, err := tempFile.Write(connConfigExample); err != nil {
t.Fatal(err)
}
tempFile.Close()
tests := []struct {
args []string
env []string
expErr bool
}{
{
args: []string{"new-client", "https://example.com/callback"},
env: []string{"DEXCTL_DB_URL=" + dsn},
},
{
args: []string{"new-client", "--db-url", dsn, "https://example.com/callback"},
},
{
args: []string{"set-connector-configs", connConfig},
env: []string{"DEXCTL_DB_URL=" + dsn},
},
{
args: []string{"set-connector-configs", "--db-url", dsn, connConfig},
},
}
for _, tt := range tests {
cmd := exec.Command("../bin/dexctl", tt.args...)
cmd.Env = tt.env
out, err := cmd.CombinedOutput()
if !tt.expErr && err != nil {
t.Errorf("cmd 'dexctl %s' failed: %v %s", strings.Join(tt.args, " "), err, out)
} else if tt.expErr && err == nil {
t.Errorf("expected cmd 'dexctl %s' to fail", strings.Join(tt.args, " "))
}
}
}