forked from mystiq/dex
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"
|
"net/url"
|
||||||
|
|
||||||
"github.com/coreos/go-oidc/oidc"
|
"github.com/coreos/go-oidc/oidc"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cmdNewClient = &command{
|
cmdNewClient = &cobra.Command{
|
||||||
Name: "new-client",
|
Use: "new-client",
|
||||||
Summary: "Create a new client with the provided redirect URL(s)",
|
Short: "Create a new client with one or more redirect URLs.",
|
||||||
Usage: "<URL>...",
|
Long: "Create a new client with one or more redirect URLs,",
|
||||||
Run: runNewClient,
|
Example: ` dexctl new-client --db-url=${DB_URL} 'https://example.com/callback'`,
|
||||||
|
Run: wrapRun(runNewClient),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
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 {
|
if len(args) < 1 {
|
||||||
stderr("Provide at least one redirect URL.")
|
stderr("Provide at least one redirect URL.")
|
||||||
return 2
|
return 2
|
||||||
|
|
|
@ -4,30 +4,33 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/coreos/dex/connector"
|
"github.com/coreos/dex/connector"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cmdGetConnectorConfigs = &command{
|
cmdGetConnectorConfigs = &cobra.Command{
|
||||||
Name: "get-connector-configs",
|
Use: "get-connector-configs",
|
||||||
Summary: "Enumerate current IdP connector configs.",
|
Short: "Enumerate current IdP connector configs.",
|
||||||
Usage: "",
|
Long: "Enumerate current IdP connector configs.",
|
||||||
Run: runGetConnectorConfigs,
|
Example: ` dexctl get-connector-configs --db-url=${DB_URL}`,
|
||||||
|
Run: wrapRun(runGetConnectorConfigs),
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdSetConnectorConfigs = &command{
|
cmdSetConnectorConfigs = &cobra.Command{
|
||||||
Name: "set-connector-configs",
|
Use: "set-connector-configs",
|
||||||
Summary: "Overwrite the current IdP connector configs with those from a local file.",
|
Short: "Overwrite the current IdP connector configs with those from a local file.",
|
||||||
Usage: "<FILE>",
|
Long: "Overwrite the current IdP connector configs with those from a local file.",
|
||||||
Run: runSetConnectorConfigs,
|
Example: ` dexctl set-connector-configs --db-url=${DB_URL} ./static/conn_conf.json`,
|
||||||
|
Run: wrapRun(runSetConnectorConfigs),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
commands = append(commands, cmdSetConnectorConfigs)
|
rootCmd.AddCommand(cmdGetConnectorConfigs)
|
||||||
commands = append(commands, cmdGetConnectorConfigs)
|
rootCmd.AddCommand(cmdSetConnectorConfigs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runSetConnectorConfigs(args []string) int {
|
func runSetConnectorConfigs(cmd *cobra.Command, args []string) int {
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
stderr("Provide a single argument.")
|
stderr("Provide a single argument.")
|
||||||
return 2
|
return 2
|
||||||
|
@ -55,7 +58,7 @@ func runSetConnectorConfigs(args []string) int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func runGetConnectorConfigs(args []string) int {
|
func runGetConnectorConfigs(cmd *cobra.Command, args []string) int {
|
||||||
if len(args) != 0 {
|
if len(args) != 0 {
|
||||||
stderr("Provide zero arguments.")
|
stderr("Provide zero arguments.")
|
||||||
return 2
|
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 (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
pflag "github.com/coreos/dex/pkg/flag"
|
|
||||||
"github.com/coreos/dex/pkg/log"
|
"github.com/coreos/dex/pkg/log"
|
||||||
"github.com/coreos/go-oidc/oidc"
|
"github.com/coreos/go-oidc/oidc"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cliName = "dexctl"
|
rootCmd = &cobra.Command{
|
||||||
cliDescription = "???"
|
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
|
// don't override flags set by command line flags
|
||||||
globalFS = flag.NewFlagSet(cliName, flag.ExitOnError)
|
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 {
|
global struct {
|
||||||
endpoint string
|
endpoint string
|
||||||
|
@ -30,69 +55,23 @@ var (
|
||||||
func init() {
|
func init() {
|
||||||
log.EnableTimestamps()
|
log.EnableTimestamps()
|
||||||
|
|
||||||
globalFS.StringVar(&global.endpoint, "endpoint", "", "URL of dex API")
|
rootCmd.PersistentFlags().StringVar(&global.endpoint, "endpoint", "", "URL of dex API")
|
||||||
globalFS.StringVar(&global.creds.ID, "client-id", "", "dex API user ID")
|
rootCmd.PersistentFlags().StringVar(&global.creds.ID, "client-id", "", "dex API user ID")
|
||||||
globalFS.StringVar(&global.creds.Secret, "client-secret", "", "dex API user password")
|
rootCmd.PersistentFlags().StringVar(&global.creds.Secret, "client-secret", "", "dex API user password")
|
||||||
globalFS.StringVar(&global.dbURL, "db-url", "", "DSN-formatted database connection string")
|
rootCmd.PersistentFlags().StringVar(&global.dbURL, "db-url", "", "DSN-formatted database connection string")
|
||||||
globalFS.BoolVar(&global.help, "help", false, "Print usage information and exit")
|
rootCmd.PersistentFlags().BoolVar(&global.logDebug, "log-debug", false, "Log debug-level information")
|
||||||
globalFS.BoolVar(&global.help, "h", false, "Print usage information and exit")
|
|
||||||
globalFS.BoolVar(&global.logDebug, "log-debug", false, "Log debug-level information")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
err := parseFlags()
|
if err := rootCmd.Execute(); err != nil {
|
||||||
if err != nil {
|
|
||||||
stderr(err.Error())
|
|
||||||
os.Exit(2)
|
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 {
|
func wrapRun(run func(cmd *cobra.Command, args []string) int) func(cmd *cobra.Command, args []string) {
|
||||||
Name string // Name of the command and the string to use to invoke it
|
return func(cmd *cobra.Command, args []string) {
|
||||||
Summary string // One-sentence summary of what the command does
|
os.Exit(run(cmd, args))
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return pflag.SetFlagsFromEnv(globalFS, "DEXCTL")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDriver() (drv driver) {
|
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, " "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue