cmd: use spf13/cobra for dexctl cli logic
This commit is contained in:
parent
71f5021678
commit
8e5115ce73
5 changed files with 138 additions and 217 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
72
functional/dexctl_test.go
Normal 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, " "))
|
||||
}
|
||||
}
|
||||
}
|
Reference in a new issue