From 48ec876af9c5e7c8d799227868a4c2a592823a34 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 30 Oct 2014 15:27:16 -0700 Subject: [PATCH 01/10] godep: bump github.com/codegangsta/cli --- Godeps/Godeps.json | 4 +- .../github.com/codegangsta/cli/.travis.yml | 4 + .../src/github.com/codegangsta/cli/README.md | 54 ++- .../src/github.com/codegangsta/cli/app.go | 22 +- .../github.com/codegangsta/cli/app_test.go | 66 ++- .../cli/autocomplete/bash_autocomplete | 2 +- .../cli/autocomplete/zsh_autocomplete | 5 + .../github.com/codegangsta/cli/cli_test.go | 21 +- .../src/github.com/codegangsta/cli/command.go | 18 +- .../codegangsta/cli/command_test.go | 3 +- .../src/github.com/codegangsta/cli/context.go | 44 ++ .../codegangsta/cli/context_test.go | 11 +- .../src/github.com/codegangsta/cli/flag.go | 200 +++++++-- .../github.com/codegangsta/cli/flag_test.go | 397 +++++++++++++++++- .../src/github.com/codegangsta/cli/help.go | 31 +- 15 files changed, 795 insertions(+), 87 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/zsh_autocomplete diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index ba5d12388..14f860aa7 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -16,8 +16,8 @@ }, { "ImportPath": "github.com/codegangsta/cli", - "Comment": "1.0.0-72-gbb91895", - "Rev": "bb9189510af1f49580c073c9e59e8bf288f0df27" + "Comment": "1.2.0-26-gf7ebb76", + "Rev": "f7ebb761e83e21225d1d8954fde853bf8edd46c4" }, { "ImportPath": "github.com/coreos/go-etcd/etcd", diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/.travis.yml b/Godeps/_workspace/src/github.com/codegangsta/cli/.travis.yml index 2379c611f..baf46abc6 100644 --- a/Godeps/_workspace/src/github.com/codegangsta/cli/.travis.yml +++ b/Godeps/_workspace/src/github.com/codegangsta/cli/.travis.yml @@ -1,2 +1,6 @@ language: go go: 1.1 + +script: +- go vet ./... +- go test -v ./... diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/README.md b/Godeps/_workspace/src/github.com/codegangsta/cli/README.md index 4621310f5..fe4652c95 100644 --- a/Godeps/_workspace/src/github.com/codegangsta/cli/README.md +++ b/Godeps/_workspace/src/github.com/codegangsta/cli/README.md @@ -9,17 +9,17 @@ http://godoc.org/github.com/codegangsta/cli ## Overview Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app. -This is where cli.go comes into play. cli.go makes command line programming fun, organized, and expressive! +**This is where cli.go comes into play.** cli.go makes command line programming fun, organized, and expressive! ## Installation Make sure you have a working Go environment (go 1.1 is *required*). [See the install instructions](http://golang.org/doc/install.html). -To install cli.go, simply run: +To install `cli.go`, simply run: ``` $ go get github.com/codegangsta/cli ``` -Make sure your PATH includes to the `$GOPATH/bin` directory so your commands can be easily used: +Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands can be easily used: ``` export PATH=$PATH:$GOPATH/bin ``` @@ -122,7 +122,7 @@ GLOBAL OPTIONS ``` ### Arguments -You can lookup arguments by calling the `Args` function on cli.Context. +You can lookup arguments by calling the `Args` function on `cli.Context`. ``` go ... @@ -137,7 +137,11 @@ Setting and querying flags is simple. ``` go ... app.Flags = []cli.Flag { - cli.StringFlag{"lang", "english", "language for the greeting"}, + cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + }, } app.Action = func(c *cli.Context) { name := "someone" @@ -155,11 +159,30 @@ app.Action = func(c *cli.Context) { #### Alternate Names -You can set alternate (or short) names for flags by providing a comma-delimited list for the Name. e.g. +You can set alternate (or short) names for flags by providing a comma-delimited list for the `Name`. e.g. ``` go app.Flags = []cli.Flag { - cli.StringFlag{"lang, l", "english", "language for the greeting"}, + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + }, +} +``` + +#### Values from the Environment + +You can also have the default value set from the environment via `EnvVar`. e.g. + +``` go +app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + EnvVar: "APP_LANG", + }, } ``` @@ -214,8 +237,8 @@ app.Commands = []cli.Command{ ### Bash Completion -You can enable completion commands by setting the EnableBashCompletion -flag on the App object. By default, this setting will only auto-complete to +You can enable completion commands by setting the `EnableBashCompletion` +flag on the `App` object. By default, this setting will only auto-complete to show an app's subcommands, but you can write your own completion methods for the App or its subcommands. ```go @@ -237,7 +260,7 @@ app.Commands = []cli.Command{ return } for _, t := range tasks { - println(t) + fmt.Println(t) } }, } @@ -247,11 +270,18 @@ app.Commands = []cli.Command{ #### To Enable -Source the autocomplete/bash_autocomplete file in your .bashrc file while -setting the PROG variable to the name of your program: +Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while +setting the `PROG` variable to the name of your program: `PROG=myprogram source /.../cli/autocomplete/bash_autocomplete` +## Contribution Guidelines +Feel free to put up a pull request to fix a bug or maybe add a feature. I will give it a code review and make sure that it does not break backwards compatibility. If I or any other collaborators agree that it is in line with the vision of the project, we will work with you to get the code into a mergeable state and merge it into the master branch. + +If you are have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together. + +If you feel like you have contributed to the project but have not yet been added as a collaborator, I probably forgot to add you. Hit @codegangsta up over email and we will get it figured out. + ## About cli.go is written by none other than the [Code Gangsta](http://codegangsta.io) diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/app.go b/Godeps/_workspace/src/github.com/codegangsta/cli/app.go index 4efba5e96..66e541c7f 100644 --- a/Godeps/_workspace/src/github.com/codegangsta/cli/app.go +++ b/Godeps/_workspace/src/github.com/codegangsta/cli/app.go @@ -22,6 +22,8 @@ type App struct { Flags []Flag // Boolean to enable bash completion commands EnableBashCompletion bool + // Boolean to hide built-in help command + HideHelp bool // An action to execute when the bash-completion flag is set BashComplete func(context *Context) // An action to execute before any subcommands are run, but after the context is ready @@ -58,16 +60,15 @@ func NewApp() *App { BashComplete: DefaultAppComplete, Action: helpCommand.Action, Compiled: compileTime(), - Author: "Author", - Email: "unknown@email", } } // Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination func (a *App) Run(arguments []string) error { // append help to commands - if a.Command(helpCommand.Name) == nil { + if a.Command(helpCommand.Name) == nil && !a.HideHelp { a.Commands = append(a.Commands, helpCommand) + a.appendFlag(HelpFlag) } //append version/help flags @@ -75,7 +76,6 @@ func (a *App) Run(arguments []string) error { a.appendFlag(BashCompletionFlag) } a.appendFlag(VersionFlag) - a.appendFlag(HelpFlag) // parse flags set := flagSet(a.Name, a.Flags) @@ -131,12 +131,21 @@ func (a *App) Run(arguments []string) error { return nil } +// Another entry point to the cli app, takes care of passing arguments and error handling +func (a *App) RunAndExitOnError() { + if err := a.Run(os.Args); err != nil { + os.Stderr.WriteString(fmt.Sprintln(err)) + os.Exit(1) + } +} + // Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags func (a *App) RunAsSubcommand(ctx *Context) error { // append help to commands if len(a.Commands) > 0 { - if a.Command(helpCommand.Name) == nil { + if a.Command(helpCommand.Name) == nil && !a.HideHelp { a.Commands = append(a.Commands, helpCommand) + a.appendFlag(HelpFlag) } } @@ -144,14 +153,13 @@ func (a *App) RunAsSubcommand(ctx *Context) error { if a.EnableBashCompletion { a.appendFlag(BashCompletionFlag) } - a.appendFlag(HelpFlag) // parse flags set := flagSet(a.Name, a.Flags) set.SetOutput(ioutil.Discard) err := set.Parse(ctx.Args().Tail()) nerr := normalizeFlags(a.Flags, set) - context := NewContext(a, set, set) + context := NewContext(a, set, ctx.globalSet) if nerr != nil { fmt.Println(nerr) diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/app_test.go b/Godeps/_workspace/src/github.com/codegangsta/cli/app_test.go index b7a543169..81d11743e 100644 --- a/Godeps/_workspace/src/github.com/codegangsta/cli/app_test.go +++ b/Godeps/_workspace/src/github.com/codegangsta/cli/app_test.go @@ -2,9 +2,10 @@ package cli_test import ( "fmt" - "github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli" "os" "testing" + + "github.com/codegangsta/cli" ) func ExampleApp() { @@ -42,7 +43,11 @@ func ExampleAppSubcommand() { Usage: "sends a greeting in english", Description: "greets someone in english", Flags: []cli.Flag{ - cli.StringFlag{"name", "Bob", "Name of the person to greet"}, + cli.StringFlag{ + Name: "name", + Value: "Bob", + Usage: "Name of the person to greet", + }, }, Action: func(c *cli.Context) { fmt.Println("Hello,", c.String("name")) @@ -83,12 +88,10 @@ func ExampleAppHelp() { // describeit - use it to see a description // // USAGE: - // command describeit [command options] [arguments...] + // command describeit [arguments...] // // DESCRIPTION: // This is how we describe describeit the function - // - // OPTIONS: } func ExampleAppBashComplete() { @@ -254,11 +257,11 @@ func TestApp_ParseSliceFlags(t *testing.T) { var expectedStringSlice = []string{"8.8.8.8", "8.8.4.4"} if !IntsEquals(parsedIntSlice, expectedIntSlice) { - t.Errorf("%s does not match %s", parsedIntSlice, expectedIntSlice) + t.Errorf("%v does not match %v", parsedIntSlice, expectedIntSlice) } if !StrsEquals(parsedStringSlice, expectedStringSlice) { - t.Errorf("%s does not match %s", parsedStringSlice, expectedStringSlice) + t.Errorf("%v does not match %v", parsedStringSlice, expectedStringSlice) } } @@ -347,6 +350,26 @@ func TestAppHelpPrinter(t *testing.T) { } } +func TestAppVersionPrinter(t *testing.T) { + oldPrinter := cli.VersionPrinter + defer func() { + cli.VersionPrinter = oldPrinter + }() + + var wasCalled = false + cli.VersionPrinter = func(c *cli.Context) { + wasCalled = true + } + + app := cli.NewApp() + ctx := cli.NewContext(app, nil, nil) + cli.ShowVersion(ctx) + + if wasCalled == false { + t.Errorf("Version printer expected to be called, but was not") + } +} + func TestAppCommandNotFound(t *testing.T) { beforeRun, subcommandRun := false, false app := cli.NewApp() @@ -369,3 +392,32 @@ func TestAppCommandNotFound(t *testing.T) { expect(t, beforeRun, true) expect(t, subcommandRun, false) } + +func TestGlobalFlagsInSubcommands(t *testing.T) { + subcommandRun := false + app := cli.NewApp() + + app.Flags = []cli.Flag{ + cli.BoolFlag{Name: "debug, d", Usage: "Enable debugging"}, + } + + app.Commands = []cli.Command{ + cli.Command{ + Name: "foo", + Subcommands: []cli.Command{ + { + Name: "bar", + Action: func(c *cli.Context) { + if c.GlobalBool("debug") { + subcommandRun = true + } + }, + }, + }, + }, + } + + app.Run([]string{"command", "-d", "foo", "bar"}) + + expect(t, subcommandRun, true) +} diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete b/Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete index a860e038d..9b55dd990 100644 --- a/Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete +++ b/Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete @@ -5,7 +5,7 @@ _cli_bash_autocomplete() { COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" - opts=$( ${COMP_WORDS[@]:0:COMP_CWORD} --generate-bash-completion ) + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 } diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/zsh_autocomplete b/Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/zsh_autocomplete new file mode 100644 index 000000000..5430a18f9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/zsh_autocomplete @@ -0,0 +1,5 @@ +autoload -U compinit && compinit +autoload -U bashcompinit && bashcompinit + +script_dir=$(dirname $0) +source ${script_dir}/bash_autocomplete diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/cli_test.go b/Godeps/_workspace/src/github.com/codegangsta/cli/cli_test.go index a2ffeae4e..879a793dc 100644 --- a/Godeps/_workspace/src/github.com/codegangsta/cli/cli_test.go +++ b/Godeps/_workspace/src/github.com/codegangsta/cli/cli_test.go @@ -1,8 +1,9 @@ package cli_test import ( - "github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli" "os" + + "github.com/codegangsta/cli" ) func Example() { @@ -47,7 +48,11 @@ func ExampleSubcommand() { Usage: "sends a greeting in english", Description: "greets someone in english", Flags: []cli.Flag{ - cli.StringFlag{"name", "Bob", "Name of the person to greet"}, + cli.StringFlag{ + Name: "name", + Value: "Bob", + Usage: "Name of the person to greet", + }, }, Action: func(c *cli.Context) { println("Hello, ", c.String("name")) @@ -57,7 +62,11 @@ func ExampleSubcommand() { ShortName: "sp", Usage: "sends a greeting in spanish", Flags: []cli.Flag{ - cli.StringFlag{"surname", "Jones", "Surname of the person to greet"}, + cli.StringFlag{ + Name: "surname", + Value: "Jones", + Usage: "Surname of the person to greet", + }, }, Action: func(c *cli.Context) { println("Hola, ", c.String("surname")) @@ -67,7 +76,11 @@ func ExampleSubcommand() { ShortName: "fr", Usage: "sends a greeting in french", Flags: []cli.Flag{ - cli.StringFlag{"nickname", "Stevie", "Nickname of the person to greet"}, + cli.StringFlag{ + Name: "nickname", + Value: "Stevie", + Usage: "Nickname of the person to greet", + }, }, Action: func(c *cli.Context) { println("Bonjour, ", c.String("nickname")) diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/command.go b/Godeps/_workspace/src/github.com/codegangsta/cli/command.go index 9d8fff481..5622b38f7 100644 --- a/Godeps/_workspace/src/github.com/codegangsta/cli/command.go +++ b/Godeps/_workspace/src/github.com/codegangsta/cli/command.go @@ -29,6 +29,8 @@ type Command struct { Flags []Flag // Treat all flags as normal arguments if true SkipFlagParsing bool + // Boolean to hide built-in help command + HideHelp bool } // Invokes the command given the context, parses ctx.Args() to generate command-specific flags @@ -38,11 +40,13 @@ func (c Command) Run(ctx *Context) error { return c.startApp(ctx) } - // append help to flags - c.Flags = append( - c.Flags, - HelpFlag, - ) + if !c.HideHelp { + // append help to flags + c.Flags = append( + c.Flags, + HelpFlag, + ) + } if ctx.App.EnableBashCompletion { c.Flags = append(c.Flags, BashCompletionFlag) @@ -114,9 +118,13 @@ func (c Command) startApp(ctx *Context) error { app.Usage = c.Usage } + // set CommandNotFound + app.CommandNotFound = ctx.App.CommandNotFound + // set the flags and commands app.Commands = c.Subcommands app.Flags = c.Flags + app.HideHelp = c.HideHelp // bash completion app.EnableBashCompletion = ctx.App.EnableBashCompletion diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/command_test.go b/Godeps/_workspace/src/github.com/codegangsta/cli/command_test.go index 4bebd6855..c0f556ad2 100644 --- a/Godeps/_workspace/src/github.com/codegangsta/cli/command_test.go +++ b/Godeps/_workspace/src/github.com/codegangsta/cli/command_test.go @@ -2,8 +2,9 @@ package cli_test import ( "flag" - "github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli" "testing" + + "github.com/codegangsta/cli" ) func TestCommandDoNotIgnoreFlags(t *testing.T) { diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/context.go b/Godeps/_workspace/src/github.com/codegangsta/cli/context.go index b2c51bbd3..8b44148ec 100644 --- a/Godeps/_workspace/src/github.com/codegangsta/cli/context.go +++ b/Godeps/_workspace/src/github.com/codegangsta/cli/context.go @@ -5,6 +5,7 @@ import ( "flag" "strconv" "strings" + "time" ) // Context is a type that is passed through to @@ -29,6 +30,11 @@ func (c *Context) Int(name string) int { return lookupInt(name, c.flagSet) } +// Looks up the value of a local time.Duration flag, returns 0 if no time.Duration flag exists +func (c *Context) Duration(name string) time.Duration { + return lookupDuration(name, c.flagSet) +} + // Looks up the value of a local float64 flag, returns 0 if no float64 flag exists func (c *Context) Float64(name string) float64 { return lookupFloat64(name, c.flagSet) @@ -69,6 +75,11 @@ func (c *Context) GlobalInt(name string) int { return lookupInt(name, c.globalSet) } +// Looks up the value of a global time.Duration flag, returns 0 if no time.Duration flag exists +func (c *Context) GlobalDuration(name string) time.Duration { + return lookupDuration(name, c.globalSet) +} + // Looks up the value of a global bool flag, returns false if no bool flag exists func (c *Context) GlobalBool(name string) bool { return lookupBool(name, c.globalSet) @@ -105,6 +116,18 @@ func (c *Context) IsSet(name string) bool { return c.setFlags[name] == true } +// Returns a slice of flag names used in this context. +func (c *Context) FlagNames() (names []string) { + for _, flag := range c.Command.Flags { + name := strings.Split(flag.getName(), ",")[0] + if name == "help" { + continue + } + names = append(names, name) + } + return +} + type Args []string // Returns the command line arguments associated with the context. @@ -140,6 +163,15 @@ func (a Args) Present() bool { return len(a) != 0 } +// Swaps arguments at the given indexes +func (a Args) Swap(from, to int) error { + if from >= len(a) || to >= len(a) { + return errors.New("index out of range") + } + a[from], a[to] = a[to], a[from] + return nil +} + func lookupInt(name string, set *flag.FlagSet) int { f := set.Lookup(name) if f != nil { @@ -153,6 +185,18 @@ func lookupInt(name string, set *flag.FlagSet) int { return 0 } +func lookupDuration(name string, set *flag.FlagSet) time.Duration { + f := set.Lookup(name) + if f != nil { + val, err := time.ParseDuration(f.Value.String()) + if err == nil { + return val + } + } + + return 0 +} + func lookupFloat64(name string, set *flag.FlagSet) float64 { f := set.Lookup(name) if f != nil { diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/context_test.go b/Godeps/_workspace/src/github.com/codegangsta/cli/context_test.go index 7c86a4800..b2d241211 100644 --- a/Godeps/_workspace/src/github.com/codegangsta/cli/context_test.go +++ b/Godeps/_workspace/src/github.com/codegangsta/cli/context_test.go @@ -2,8 +2,10 @@ package cli_test import ( "flag" - "github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli" "testing" + "time" + + "github.com/codegangsta/cli" ) func TestNewContext(t *testing.T) { @@ -26,6 +28,13 @@ func TestContext_Int(t *testing.T) { expect(t, c.Int("myflag"), 12) } +func TestContext_Duration(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Duration("myflag", time.Duration(12*time.Second), "doc") + c := cli.NewContext(nil, set, set) + expect(t, c.Duration("myflag"), time.Duration(12*time.Second)) +} + func TestContext_String(t *testing.T) { set := flag.NewFlagSet("test", 0) set.String("myflag", "hello world", "doc") diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/flag.go b/Godeps/_workspace/src/github.com/codegangsta/cli/flag.go index e6f8838a9..b30bca301 100644 --- a/Godeps/_workspace/src/github.com/codegangsta/cli/flag.go +++ b/Godeps/_workspace/src/github.com/codegangsta/cli/flag.go @@ -3,18 +3,28 @@ package cli import ( "flag" "fmt" + "os" "strconv" "strings" + "time" ) // This flag enables bash-completion for all commands and subcommands -var BashCompletionFlag = BoolFlag{"generate-bash-completion", ""} +var BashCompletionFlag = BoolFlag{ + Name: "generate-bash-completion", +} // This flag prints the version for the application -var VersionFlag = BoolFlag{"version, v", "print the version"} +var VersionFlag = BoolFlag{ + Name: "version, v", + Usage: "print the version", +} // This flag prints the help for all commands and subcommands -var HelpFlag = BoolFlag{"help, h", "show help"} +var HelpFlag = BoolFlag{ + Name: "help, h", + Usage: "show help", +} // Flag is a common interface related to parsing flags in cli. // For more advanced flag parsing techniques, it is recomended that @@ -51,16 +61,24 @@ type Generic interface { // GenericFlag is the flag type for types implementing Generic type GenericFlag struct { - Name string - Value Generic - Usage string + Name string + Value Generic + Usage string + EnvVar string } func (f GenericFlag) String() string { - return fmt.Sprintf("%s%s %v\t`%v` %s", prefixFor(f.Name), f.Name, f.Value, "-"+f.Name+" option -"+f.Name+" option", f.Usage) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s%s %v\t`%v` %s", prefixFor(f.Name), f.Name, f.Value, "-"+f.Name+" option -"+f.Name+" option", f.Usage)) } func (f GenericFlag) Apply(set *flag.FlagSet) { + val := f.Value + if f.EnvVar != "" { + if envVal := os.Getenv(f.EnvVar); envVal != "" { + val.Set(envVal) + } + } + eachName(f.Name, func(name string) { set.Var(f.Value, name, f.Usage) }) @@ -86,18 +104,29 @@ func (f *StringSlice) Value() []string { } type StringSliceFlag struct { - Name string - Value *StringSlice - Usage string + Name string + Value *StringSlice + Usage string + EnvVar string } func (f StringSliceFlag) String() string { firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") pref := prefixFor(firstName) - return fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage)) } func (f StringSliceFlag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + if envVal := os.Getenv(f.EnvVar); envVal != "" { + newVal := &StringSlice{} + for _, s := range strings.Split(envVal, ",") { + newVal.Set(s) + } + f.Value = newVal + } + } + eachName(f.Name, func(name string) { set.Var(f.Value, name, f.Usage) }) @@ -129,18 +158,32 @@ func (f *IntSlice) Value() []int { } type IntSliceFlag struct { - Name string - Value *IntSlice - Usage string + Name string + Value *IntSlice + Usage string + EnvVar string } func (f IntSliceFlag) String() string { firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") pref := prefixFor(firstName) - return fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage)) } func (f IntSliceFlag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + if envVal := os.Getenv(f.EnvVar); envVal != "" { + newVal := &IntSlice{} + for _, s := range strings.Split(envVal, ",") { + err := newVal.Set(s) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + } + } + f.Value = newVal + } + } + eachName(f.Name, func(name string) { set.Var(f.Value, name, f.Usage) }) @@ -151,17 +194,28 @@ func (f IntSliceFlag) getName() string { } type BoolFlag struct { - Name string - Usage string + Name string + Usage string + EnvVar string } func (f BoolFlag) String() string { - return fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)) } func (f BoolFlag) Apply(set *flag.FlagSet) { + val := false + if f.EnvVar != "" { + if envVal := os.Getenv(f.EnvVar); envVal != "" { + envValBool, err := strconv.ParseBool(envVal) + if err == nil { + val = envValBool + } + } + } + eachName(f.Name, func(name string) { - set.Bool(name, false, f.Usage) + set.Bool(name, val, f.Usage) }) } @@ -170,17 +224,28 @@ func (f BoolFlag) getName() string { } type BoolTFlag struct { - Name string - Usage string + Name string + Usage string + EnvVar string } func (f BoolTFlag) String() string { - return fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)) } func (f BoolTFlag) Apply(set *flag.FlagSet) { + val := true + if f.EnvVar != "" { + if envVal := os.Getenv(f.EnvVar); envVal != "" { + envValBool, err := strconv.ParseBool(envVal) + if err == nil { + val = envValBool + } + } + } + eachName(f.Name, func(name string) { - set.Bool(name, true, f.Usage) + set.Bool(name, val, f.Usage) }) } @@ -189,9 +254,10 @@ func (f BoolTFlag) getName() string { } type StringFlag struct { - Name string - Value string - Usage string + Name string + Value string + Usage string + EnvVar string } func (f StringFlag) String() string { @@ -204,10 +270,16 @@ func (f StringFlag) String() string { fmtString = "%s %v\t%v" } - return fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage) + return withEnvHint(f.EnvVar, fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage)) } func (f StringFlag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + if envVal := os.Getenv(f.EnvVar); envVal != "" { + f.Value = envVal + } + } + eachName(f.Name, func(name string) { set.String(name, f.Value, f.Usage) }) @@ -218,16 +290,26 @@ func (f StringFlag) getName() string { } type IntFlag struct { - Name string - Value int - Usage string + Name string + Value int + Usage string + EnvVar string } func (f IntFlag) String() string { - return fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage)) } func (f IntFlag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + if envVal := os.Getenv(f.EnvVar); envVal != "" { + envValInt, err := strconv.ParseUint(envVal, 10, 64) + if err == nil { + f.Value = int(envValInt) + } + } + } + eachName(f.Name, func(name string) { set.Int(name, f.Value, f.Usage) }) @@ -237,17 +319,57 @@ func (f IntFlag) getName() string { return f.Name } +type DurationFlag struct { + Name string + Value time.Duration + Usage string + EnvVar string +} + +func (f DurationFlag) String() string { + return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage)) +} + +func (f DurationFlag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + if envVal := os.Getenv(f.EnvVar); envVal != "" { + envValDuration, err := time.ParseDuration(envVal) + if err == nil { + f.Value = envValDuration + } + } + } + + eachName(f.Name, func(name string) { + set.Duration(name, f.Value, f.Usage) + }) +} + +func (f DurationFlag) getName() string { + return f.Name +} + type Float64Flag struct { - Name string - Value float64 - Usage string + Name string + Value float64 + Usage string + EnvVar string } func (f Float64Flag) String() string { - return fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage)) } func (f Float64Flag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + if envVal := os.Getenv(f.EnvVar); envVal != "" { + envValFloat, err := strconv.ParseFloat(envVal, 10) + if err == nil { + f.Value = float64(envValFloat) + } + } + } + eachName(f.Name, func(name string) { set.Float64(name, f.Value, f.Usage) }) @@ -278,3 +400,11 @@ func prefixedNames(fullName string) (prefixed string) { } return } + +func withEnvHint(envVar, str string) string { + envText := "" + if envVar != "" { + envText = fmt.Sprintf(" [$%s]", envVar) + } + return str + envText +} diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/flag_test.go b/Godeps/_workspace/src/github.com/codegangsta/cli/flag_test.go index c6409f599..bc5059ca1 100644 --- a/Godeps/_workspace/src/github.com/codegangsta/cli/flag_test.go +++ b/Godeps/_workspace/src/github.com/codegangsta/cli/flag_test.go @@ -1,12 +1,13 @@ package cli_test import ( - "github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli" - "fmt" + "os" "reflect" "strings" "testing" + + "github.com/codegangsta/cli" ) var boolFlagTests = []struct { @@ -52,6 +53,71 @@ func TestStringFlagHelpOutput(t *testing.T) { } } +func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { + + os.Setenv("APP_FOO", "derp") + for _, test := range stringFlagTests { + flag := cli.StringFlag{Name: test.name, Value: test.value, EnvVar: "APP_FOO"} + output := flag.String() + + if !strings.HasSuffix(output, " [$APP_FOO]") { + t.Errorf("%s does not end with [$APP_FOO]", output) + } + } +} + +var stringSliceFlagTests = []struct { + name string + value *cli.StringSlice + expected string +}{ + {"help", func() *cli.StringSlice { + s := &cli.StringSlice{} + s.Set("") + return s + }(), "--help '--help option --help option'\t"}, + {"h", func() *cli.StringSlice { + s := &cli.StringSlice{} + s.Set("") + return s + }(), "-h '-h option -h option'\t"}, + {"h", func() *cli.StringSlice { + s := &cli.StringSlice{} + s.Set("") + return s + }(), "-h '-h option -h option'\t"}, + {"test", func() *cli.StringSlice { + s := &cli.StringSlice{} + s.Set("Something") + return s + }(), "--test '--test option --test option'\t"}, +} + +func TestStringSliceFlagHelpOutput(t *testing.T) { + + for _, test := range stringSliceFlagTests { + flag := cli.StringSliceFlag{Name: test.name, Value: test.value} + output := flag.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { + + os.Setenv("APP_QWWX", "11,4") + for _, test := range stringSliceFlagTests { + flag := cli.StringSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_QWWX"} + output := flag.String() + + if !strings.HasSuffix(output, " [$APP_QWWX]") { + t.Errorf("%q does not end with [$APP_QWWX]", output) + } + } +} + var intFlagTests = []struct { name string expected string @@ -72,6 +138,92 @@ func TestIntFlagHelpOutput(t *testing.T) { } } +func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { + + os.Setenv("APP_BAR", "2") + for _, test := range intFlagTests { + flag := cli.IntFlag{Name: test.name, EnvVar: "APP_BAR"} + output := flag.String() + + if !strings.HasSuffix(output, " [$APP_BAR]") { + t.Errorf("%s does not end with [$APP_BAR]", output) + } + } +} + +var durationFlagTests = []struct { + name string + expected string +}{ + {"help", "--help '0'\t"}, + {"h", "-h '0'\t"}, +} + +func TestDurationFlagHelpOutput(t *testing.T) { + + for _, test := range durationFlagTests { + flag := cli.DurationFlag{Name: test.name} + output := flag.String() + + if output != test.expected { + t.Errorf("%s does not match %s", output, test.expected) + } + } +} + +func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { + + os.Setenv("APP_BAR", "2h3m6s") + for _, test := range durationFlagTests { + flag := cli.DurationFlag{Name: test.name, EnvVar: "APP_BAR"} + output := flag.String() + + if !strings.HasSuffix(output, " [$APP_BAR]") { + t.Errorf("%s does not end with [$APP_BAR]", output) + } + } +} + +var intSliceFlagTests = []struct { + name string + value *cli.IntSlice + expected string +}{ + {"help", &cli.IntSlice{}, "--help '--help option --help option'\t"}, + {"h", &cli.IntSlice{}, "-h '-h option -h option'\t"}, + {"h", &cli.IntSlice{}, "-h '-h option -h option'\t"}, + {"test", func() *cli.IntSlice { + i := &cli.IntSlice{} + i.Set("9") + return i + }(), "--test '--test option --test option'\t"}, +} + +func TestIntSliceFlagHelpOutput(t *testing.T) { + + for _, test := range intSliceFlagTests { + flag := cli.IntSliceFlag{Name: test.name, Value: test.value} + output := flag.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { + + os.Setenv("APP_SMURF", "42,3") + for _, test := range intSliceFlagTests { + flag := cli.IntSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"} + output := flag.String() + + if !strings.HasSuffix(output, " [$APP_SMURF]") { + t.Errorf("%q does not end with [$APP_SMURF]", output) + } + } +} + var float64FlagTests = []struct { name string expected string @@ -92,6 +244,54 @@ func TestFloat64FlagHelpOutput(t *testing.T) { } } +func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { + + os.Setenv("APP_BAZ", "99.4") + for _, test := range float64FlagTests { + flag := cli.Float64Flag{Name: test.name, EnvVar: "APP_BAZ"} + output := flag.String() + + if !strings.HasSuffix(output, " [$APP_BAZ]") { + t.Errorf("%s does not end with [$APP_BAZ]", output) + } + } +} + +var genericFlagTests = []struct { + name string + value cli.Generic + expected string +}{ + {"help", &Parser{}, "--help \t`-help option -help option` "}, + {"h", &Parser{}, "-h \t`-h option -h option` "}, + {"test", &Parser{}, "--test \t`-test option -test option` "}, +} + +func TestGenericFlagHelpOutput(t *testing.T) { + + for _, test := range genericFlagTests { + flag := cli.GenericFlag{Name: test.name} + output := flag.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { + + os.Setenv("APP_ZAP", "3") + for _, test := range genericFlagTests { + flag := cli.GenericFlag{Name: test.name, EnvVar: "APP_ZAP"} + output := flag.String() + + if !strings.HasSuffix(output, " [$APP_ZAP]") { + t.Errorf("%s does not end with [$APP_ZAP]", output) + } + } +} + func TestParseMultiString(t *testing.T) { (&cli.App{ Flags: []cli.Flag{ @@ -108,6 +308,23 @@ func TestParseMultiString(t *testing.T) { }).Run([]string{"run", "-s", "10"}) } +func TestParseMultiStringFromEnv(t *testing.T) { + os.Setenv("APP_COUNT", "20") + (&cli.App{ + Flags: []cli.Flag{ + cli.StringFlag{Name: "count, c", EnvVar: "APP_COUNT"}, + }, + Action: func(ctx *cli.Context) { + if ctx.String("count") != "20" { + t.Errorf("main name not set") + } + if ctx.String("c") != "20" { + t.Errorf("short name not set") + } + }, + }).Run([]string{"run"}) +} + func TestParseMultiStringSlice(t *testing.T) { (&cli.App{ Flags: []cli.Flag{ @@ -124,6 +341,24 @@ func TestParseMultiStringSlice(t *testing.T) { }).Run([]string{"run", "-s", "10", "-s", "20"}) } +func TestParseMultiStringSliceFromEnv(t *testing.T) { + os.Setenv("APP_INTERVALS", "20,30,40") + + (&cli.App{ + Flags: []cli.Flag{ + cli.StringSliceFlag{Name: "intervals, i", Value: &cli.StringSlice{}, EnvVar: "APP_INTERVALS"}, + }, + Action: func(ctx *cli.Context) { + if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) { + t.Errorf("short name not set from env") + } + }, + }).Run([]string{"run"}) +} + func TestParseMultiInt(t *testing.T) { a := cli.App{ Flags: []cli.Flag{ @@ -141,6 +376,93 @@ func TestParseMultiInt(t *testing.T) { a.Run([]string{"run", "-s", "10"}) } +func TestParseMultiIntFromEnv(t *testing.T) { + os.Setenv("APP_TIMEOUT_SECONDS", "10") + a := cli.App{ + Flags: []cli.Flag{ + cli.IntFlag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, + }, + Action: func(ctx *cli.Context) { + if ctx.Int("timeout") != 10 { + t.Errorf("main name not set") + } + if ctx.Int("t") != 10 { + t.Errorf("short name not set") + } + }, + } + a.Run([]string{"run"}) +} + +func TestParseMultiIntSlice(t *testing.T) { + (&cli.App{ + Flags: []cli.Flag{ + cli.IntSliceFlag{Name: "serve, s", Value: &cli.IntSlice{}}, + }, + Action: func(ctx *cli.Context) { + if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { + t.Errorf("main name not set") + } + if !reflect.DeepEqual(ctx.IntSlice("s"), []int{10, 20}) { + t.Errorf("short name not set") + } + }, + }).Run([]string{"run", "-s", "10", "-s", "20"}) +} + +func TestParseMultiIntSliceFromEnv(t *testing.T) { + os.Setenv("APP_INTERVALS", "20,30,40") + + (&cli.App{ + Flags: []cli.Flag{ + cli.IntSliceFlag{Name: "intervals, i", Value: &cli.IntSlice{}, EnvVar: "APP_INTERVALS"}, + }, + Action: func(ctx *cli.Context) { + if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) { + t.Errorf("short name not set from env") + } + }, + }).Run([]string{"run"}) +} + +func TestParseMultiFloat64(t *testing.T) { + a := cli.App{ + Flags: []cli.Flag{ + cli.Float64Flag{Name: "serve, s"}, + }, + Action: func(ctx *cli.Context) { + if ctx.Float64("serve") != 10.2 { + t.Errorf("main name not set") + } + if ctx.Float64("s") != 10.2 { + t.Errorf("short name not set") + } + }, + } + a.Run([]string{"run", "-s", "10.2"}) +} + +func TestParseMultiFloat64FromEnv(t *testing.T) { + os.Setenv("APP_TIMEOUT_SECONDS", "15.5") + a := cli.App{ + Flags: []cli.Flag{ + cli.Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, + }, + Action: func(ctx *cli.Context) { + if ctx.Float64("timeout") != 15.5 { + t.Errorf("main name not set") + } + if ctx.Float64("t") != 15.5 { + t.Errorf("short name not set") + } + }, + } + a.Run([]string{"run"}) +} + func TestParseMultiBool(t *testing.T) { a := cli.App{ Flags: []cli.Flag{ @@ -158,6 +480,59 @@ func TestParseMultiBool(t *testing.T) { a.Run([]string{"run", "--serve"}) } +func TestParseMultiBoolFromEnv(t *testing.T) { + os.Setenv("APP_DEBUG", "1") + a := cli.App{ + Flags: []cli.Flag{ + cli.BoolFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, + }, + Action: func(ctx *cli.Context) { + if ctx.Bool("debug") != true { + t.Errorf("main name not set from env") + } + if ctx.Bool("d") != true { + t.Errorf("short name not set from env") + } + }, + } + a.Run([]string{"run"}) +} + +func TestParseMultiBoolT(t *testing.T) { + a := cli.App{ + Flags: []cli.Flag{ + cli.BoolTFlag{Name: "serve, s"}, + }, + Action: func(ctx *cli.Context) { + if ctx.BoolT("serve") != true { + t.Errorf("main name not set") + } + if ctx.BoolT("s") != true { + t.Errorf("short name not set") + } + }, + } + a.Run([]string{"run", "--serve"}) +} + +func TestParseMultiBoolTFromEnv(t *testing.T) { + os.Setenv("APP_DEBUG", "0") + a := cli.App{ + Flags: []cli.Flag{ + cli.BoolTFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, + }, + Action: func(ctx *cli.Context) { + if ctx.BoolT("debug") != false { + t.Errorf("main name not set from env") + } + if ctx.BoolT("d") != false { + t.Errorf("short name not set from env") + } + }, + } + a.Run([]string{"run"}) +} + type Parser [2]string func (p *Parser) Set(value string) error { @@ -192,3 +567,21 @@ func TestParseGeneric(t *testing.T) { } a.Run([]string{"run", "-s", "10,20"}) } + +func TestParseGenericFromEnv(t *testing.T) { + os.Setenv("APP_SERVE", "20,30") + a := cli.App{ + Flags: []cli.Flag{ + cli.GenericFlag{Name: "serve, s", Value: &Parser{}, EnvVar: "APP_SERVE"}, + }, + Action: func(ctx *cli.Context) { + if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"20", "30"}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"20", "30"}) { + t.Errorf("short name not set from env") + } + }, + } + a.Run([]string{"run"}) +} diff --git a/Godeps/_workspace/src/github.com/codegangsta/cli/help.go b/Godeps/_workspace/src/github.com/codegangsta/cli/help.go index 7c0400591..5020cb6f3 100644 --- a/Godeps/_workspace/src/github.com/codegangsta/cli/help.go +++ b/Godeps/_workspace/src/github.com/codegangsta/cli/help.go @@ -14,17 +14,21 @@ var AppHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: - {{.Name}} [global options] command [command options] [arguments...] + {{.Name}} {{if .Flags}}[global options] {{end}}command{{if .Flags}} [command options]{{end}} [arguments...] VERSION: - {{.Version}} + {{.Version}}{{if or .Author .Email}} + +AUTHOR:{{if .Author}} + {{.Author}}{{if .Email}} - <{{.Email}}>{{end}}{{else}} + {{.Email}}{{end}}{{end}} COMMANDS: {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} - {{end}} + {{end}}{{if .Flags}} GLOBAL OPTIONS: {{range .Flags}}{{.}} - {{end}} + {{end}}{{end}} ` // The text template for the command help topic. @@ -34,14 +38,14 @@ var CommandHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: - command {{.Name}} [command options] [arguments...] + command {{.Name}}{{if .Flags}} [command options]{{end}} [arguments...]{{if .Description}} DESCRIPTION: - {{.Description}} + {{.Description}}{{end}}{{if .Flags}} OPTIONS: {{range .Flags}}{{.}} - {{end}} + {{end}}{{ end }} ` // The text template for the subcommand help topic. @@ -51,14 +55,14 @@ var SubcommandHelpTemplate = `NAME: {{.Name}} - {{.Usage}} USAGE: - {{.Name}} [global options] command [command options] [arguments...] + {{.Name}} command{{if .Flags}} [command options]{{end}} [arguments...] COMMANDS: {{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} - {{end}} + {{end}}{{if .Flags}} OPTIONS: {{range .Flags}}{{.}} - {{end}} + {{end}}{{end}} ` var helpCommand = Command{ @@ -92,6 +96,9 @@ var helpSubcommand = Command{ // Prints help for the App var HelpPrinter = printHelp +// Prints version for the App +var VersionPrinter = printVersion + func ShowAppHelp(c *Context) { HelpPrinter(AppHelpTemplate, c.App) } @@ -129,6 +136,10 @@ func ShowSubcommandHelp(c *Context) { // Prints the version number of the App func ShowVersion(c *Context) { + VersionPrinter(c) +} + +func printVersion(c *Context) { fmt.Printf("%v version %v\n", c.App.Name, c.App.Version) } From bc62b05c7f758844e9492e621bdfb7498a234492 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 30 Oct 2014 15:30:10 -0700 Subject: [PATCH 02/10] etcdctl: break out getPeersFlagValue --- etcdctl/command/handle.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/etcdctl/command/handle.go b/etcdctl/command/handle.go index d8b70759b..503fe8872 100644 --- a/etcdctl/command/handle.go +++ b/etcdctl/command/handle.go @@ -37,11 +37,7 @@ func createHttpPath(addr string) (string, error) { return u.String(), nil } -// rawhandle wraps the command function handlers and sets up the -// environment but performs no output formatting. -func rawhandle(c *cli.Context, fn handlerFunc) (*etcd.Response, error) { - sync := !c.GlobalBool("no-sync") - +func getPeersFlagValue(c *cli.Context) []string { peerstr := c.GlobalString("peers") // Use an environment variable if nothing was supplied on the @@ -55,7 +51,15 @@ func rawhandle(c *cli.Context, fn handlerFunc) (*etcd.Response, error) { peerstr = "127.0.0.1:4001" } - peers := strings.Split(peerstr, ",") + return strings.Split(peerstr, ",") +} + +// rawhandle wraps the command function handlers and sets up the +// environment but performs no output formatting. +func rawhandle(c *cli.Context, fn handlerFunc) (*etcd.Response, error) { + sync := !c.GlobalBool("no-sync") + + peers := getPeersFlagValue(c) // If no sync, create http path for each peer address if !sync { From dee912f2fdb6333822a2194f428dfc7d7b5de7ab Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 30 Oct 2014 15:33:22 -0700 Subject: [PATCH 03/10] etcdctl: break out mustNewMembersAPI --- etcdctl/command/member_commands.go | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/etcdctl/command/member_commands.go b/etcdctl/command/member_commands.go index 0c8f6573e..c713fe9f8 100644 --- a/etcdctl/command/member_commands.go +++ b/etcdctl/command/member_commands.go @@ -33,18 +33,22 @@ func NewMemberCommand() cli.Command { } } -func actionMemberList(c *cli.Context) { - if len(c.Args()) != 0 { - fmt.Fprintln(os.Stderr, "No arguments accepted") - os.Exit(1) - } - +func mustNewMembersAPI(c *cli.Context) client.MembersAPI { mAPI, err := client.NewMembersAPI(&http.Transport{}, "http://127.0.0.1:4001", client.DefaultRequestTimeout) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } + return mAPI +} + +func actionMemberList(c *cli.Context) { + if len(c.Args()) != 0 { + fmt.Fprintln(os.Stderr, "No arguments accepted") + os.Exit(1) + } + mAPI := mustNewMembersAPI(c) members, err := mAPI.List() if err != nil { fmt.Fprintln(os.Stderr, err.Error()) @@ -63,12 +67,7 @@ func actionMemberAdd(c *cli.Context) { os.Exit(1) } - mAPI, err := client.NewMembersAPI(&http.Transport{}, "http://127.0.0.1:4001", client.DefaultRequestTimeout) - if err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(1) - } - + mAPI := mustNewMembersAPI(c) url := args[0] m, err := mAPI.Add(url) if err != nil { @@ -86,12 +85,7 @@ func actionMemberRemove(c *cli.Context) { os.Exit(1) } - mAPI, err := client.NewMembersAPI(&http.Transport{}, "http://127.0.0.1:4001", client.DefaultRequestTimeout) - if err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(1) - } - + mAPI := mustNewMembersAPI(c) mID := args[0] if err := mAPI.Remove(mID); err != nil { fmt.Fprintln(os.Stderr, err.Error()) From 7c1f4a9baf4df77a3317c7d127cf2c53c510a670 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 30 Oct 2014 15:42:34 -0700 Subject: [PATCH 04/10] client: explicitly carry API prefix around --- client/keys.go | 39 ++++++++++++++++++++++++--------------- client/keys_test.go | 15 ++++++++++++++- client/members.go | 9 +++++---- client/members_test.go | 8 ++++---- 4 files changed, 47 insertions(+), 24 deletions(-) diff --git a/client/keys.go b/client/keys.go index 60fff6231..7d7daeb34 100644 --- a/client/keys.go +++ b/client/keys.go @@ -55,8 +55,6 @@ func newHTTPKeysAPIWithPrefix(tr *http.Transport, ep string, to time.Duration, p return nil, err } - u.Path = path.Join(u.Path, prefix) - c := &httpClient{ transport: tr, endpoint: *u, @@ -65,6 +63,7 @@ func newHTTPKeysAPIWithPrefix(tr *http.Transport, ep string, to time.Duration, p kAPI := httpKeysAPI{ client: c, + prefix: prefix, } return &kAPI, nil @@ -102,12 +101,14 @@ func (n *Node) String() string { type httpKeysAPI struct { client *httpClient + prefix string } func (k *httpKeysAPI) Create(key, val string, ttl time.Duration) (*Response, error) { create := &createAction{ - Key: key, - Value: val, + Prefix: k.prefix, + Key: key, + Value: val, } if ttl >= 0 { uttl := uint64(ttl.Seconds()) @@ -124,6 +125,7 @@ func (k *httpKeysAPI) Create(key, val string, ttl time.Duration) (*Response, err func (k *httpKeysAPI) Get(key string) (*Response, error) { get := &getAction{ + Prefix: k.prefix, Key: key, Recursive: false, } @@ -140,6 +142,7 @@ func (k *httpKeysAPI) Watch(key string, idx uint64) Watcher { return &httpWatcher{ client: k.client, nextWait: waitAction{ + Prefix: k.prefix, Key: key, WaitIndex: idx, Recursive: false, @@ -151,6 +154,7 @@ func (k *httpKeysAPI) RecursiveWatch(key string, idx uint64) Watcher { return &httpWatcher{ client: k.client, nextWait: waitAction{ + Prefix: k.prefix, Key: key, WaitIndex: idx, Recursive: true, @@ -179,21 +183,24 @@ func (hw *httpWatcher) Next() (*Response, error) { return resp, nil } -// v2KeysURL forms a URL representing the location of a key. The provided -// endpoint must be the root of the etcd keys API. For example, a valid -// endpoint probably has the path "/v2/keys". -func v2KeysURL(ep url.URL, key string) *url.URL { - ep.Path = path.Join(ep.Path, key) +// v2KeysURL forms a URL representing the location of a key. +// The endpoint argument represents the base URL of an etcd +// server. The prefix is the path needed to route from the +// provided endpoint's path to the root of the keys API +// (typically "/v2/keys"). +func v2KeysURL(ep url.URL, prefix, key string) *url.URL { + ep.Path = path.Join(ep.Path, prefix, key) return &ep } type getAction struct { + Prefix string Key string Recursive bool } func (g *getAction) httpRequest(ep url.URL) *http.Request { - u := v2KeysURL(ep, g.Key) + u := v2KeysURL(ep, g.Prefix, g.Key) params := u.Query() params.Set("recursive", strconv.FormatBool(g.Recursive)) @@ -204,13 +211,14 @@ func (g *getAction) httpRequest(ep url.URL) *http.Request { } type waitAction struct { + Prefix string Key string WaitIndex uint64 Recursive bool } func (w *waitAction) httpRequest(ep url.URL) *http.Request { - u := v2KeysURL(ep, w.Key) + u := v2KeysURL(ep, w.Prefix, w.Key) params := u.Query() params.Set("wait", "true") @@ -223,13 +231,14 @@ func (w *waitAction) httpRequest(ep url.URL) *http.Request { } type createAction struct { - Key string - Value string - TTL *uint64 + Prefix string + Key string + Value string + TTL *uint64 } func (c *createAction) httpRequest(ep url.URL) *http.Request { - u := v2KeysURL(ep, c.Key) + u := v2KeysURL(ep, c.Prefix, c.Key) params := u.Query() params.Set("prevExist", "false") diff --git a/client/keys_test.go b/client/keys_test.go index d2428c72d..ad7b4b1c0 100644 --- a/client/keys_test.go +++ b/client/keys_test.go @@ -29,12 +29,14 @@ import ( func TestV2KeysURLHelper(t *testing.T) { tests := []struct { endpoint url.URL + prefix string key string want url.URL }{ // key is empty, no problem { endpoint: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"}, + prefix: "", key: "", want: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"}, }, @@ -42,6 +44,7 @@ func TestV2KeysURLHelper(t *testing.T) { // key is joined to path { endpoint: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"}, + prefix: "", key: "/foo/bar", want: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys/foo/bar"}, }, @@ -49,6 +52,7 @@ func TestV2KeysURLHelper(t *testing.T) { // key is joined to path when path is empty { endpoint: url.URL{Scheme: "http", Host: "example.com", Path: ""}, + prefix: "", key: "/foo/bar", want: url.URL{Scheme: "http", Host: "example.com", Path: "/foo/bar"}, }, @@ -56,6 +60,7 @@ func TestV2KeysURLHelper(t *testing.T) { // Host field carries through with port { endpoint: url.URL{Scheme: "http", Host: "example.com:8080", Path: "/v2/keys"}, + prefix: "", key: "", want: url.URL{Scheme: "http", Host: "example.com:8080", Path: "/v2/keys"}, }, @@ -63,13 +68,21 @@ func TestV2KeysURLHelper(t *testing.T) { // Scheme carries through { endpoint: url.URL{Scheme: "https", Host: "example.com", Path: "/v2/keys"}, + prefix: "", key: "", want: url.URL{Scheme: "https", Host: "example.com", Path: "/v2/keys"}, }, + // Prefix is applied + { + endpoint: url.URL{Scheme: "https", Host: "example.com", Path: "/foo"}, + prefix: "/bar", + key: "/baz", + want: url.URL{Scheme: "https", Host: "example.com", Path: "/foo/bar/baz"}, + }, } for i, tt := range tests { - got := v2KeysURL(tt.endpoint, tt.key) + got := v2KeysURL(tt.endpoint, tt.prefix, tt.key) if tt.want != *got { t.Errorf("#%d: want=%#v, got=%#v", i, tt.want, *got) } diff --git a/client/members.go b/client/members.go index d178b9a76..61f9a9cde 100644 --- a/client/members.go +++ b/client/members.go @@ -39,8 +39,6 @@ func NewMembersAPI(tr *http.Transport, ep string, to time.Duration) (MembersAPI, return nil, err } - u.Path = path.Join(u.Path, DefaultV2MembersPrefix) - c := &httpClient{ transport: tr, endpoint: *u, @@ -65,7 +63,8 @@ type httpMembersAPI struct { } func (m *httpMembersAPI) List() ([]httptypes.Member, error) { - code, body, err := m.client.doWithTimeout(&membersAPIActionList{}) + req := &membersAPIActionList{} + code, body, err := m.client.doWithTimeout(req) if err != nil { return nil, err } @@ -119,6 +118,7 @@ func (m *httpMembersAPI) Remove(memberID string) error { type membersAPIActionList struct{} func (l *membersAPIActionList) httpRequest(ep url.URL) *http.Request { + ep.Path = path.Join(ep.Path, DefaultV2MembersPrefix) req, _ := http.NewRequest("GET", ep.String(), nil) return req } @@ -128,7 +128,7 @@ type membersAPIActionRemove struct { } func (d *membersAPIActionRemove) httpRequest(ep url.URL) *http.Request { - ep.Path = path.Join(ep.Path, d.memberID) + ep.Path = path.Join(ep.Path, DefaultV2MembersPrefix, d.memberID) req, _ := http.NewRequest("DELETE", ep.String(), nil) return req } @@ -138,6 +138,7 @@ type membersAPIActionAdd struct { } func (a *membersAPIActionAdd) httpRequest(ep url.URL) *http.Request { + ep.Path = path.Join(ep.Path, DefaultV2MembersPrefix) m := httptypes.MemberCreateRequest{PeerURLs: a.peerURLs} b, _ := json.Marshal(&m) req, _ := http.NewRequest("POST", ep.String(), bytes.NewReader(b)) diff --git a/client/members_test.go b/client/members_test.go index 7d693a79f..a8975c0cc 100644 --- a/client/members_test.go +++ b/client/members_test.go @@ -25,7 +25,7 @@ import ( ) func TestMembersAPIActionList(t *testing.T) { - ep := url.URL{Scheme: "http", Host: "example.com/v2/members"} + ep := url.URL{Scheme: "http", Host: "example.com"} act := &membersAPIActionList{} wantURL := &url.URL{ @@ -42,7 +42,7 @@ func TestMembersAPIActionList(t *testing.T) { } func TestMembersAPIActionAdd(t *testing.T) { - ep := url.URL{Scheme: "http", Host: "example.com/v2/admin/members"} + ep := url.URL{Scheme: "http", Host: "example.com"} act := &membersAPIActionAdd{ peerURLs: types.URLs([]url.URL{ url.URL{Scheme: "https", Host: "127.0.0.1:8081"}, @@ -53,7 +53,7 @@ func TestMembersAPIActionAdd(t *testing.T) { wantURL := &url.URL{ Scheme: "http", Host: "example.com", - Path: "/v2/admin/members", + Path: "/v2/members", } wantHeader := http.Header{ "Content-Type": []string{"application/json"}, @@ -68,7 +68,7 @@ func TestMembersAPIActionAdd(t *testing.T) { } func TestMembersAPIActionRemove(t *testing.T) { - ep := url.URL{Scheme: "http", Host: "example.com/v2/members"} + ep := url.URL{Scheme: "http", Host: "example.com"} act := &membersAPIActionRemove{memberID: "XXX"} wantURL := &url.URL{ From 9d07db44320a2bc08fc1469c770dd6c9a92bb3b5 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 30 Oct 2014 17:00:16 -0700 Subject: [PATCH 05/10] client: move timeout into caller of httpClient --- client/http.go | 6 ------ client/keys.go | 19 ++++++++++++------- client/members.go | 20 ++++++++++++++------ 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/client/http.go b/client/http.go index 625fc19ed..6fee346d5 100644 --- a/client/http.go +++ b/client/http.go @@ -53,12 +53,6 @@ type httpClient struct { timeout time.Duration } -func (c *httpClient) doWithTimeout(act httpAction) (int, []byte, error) { - ctx, cancel := context.WithTimeout(context.Background(), c.timeout) - defer cancel() - return c.do(ctx, act) -} - func (c *httpClient) do(ctx context.Context, act httpAction) (int, []byte, error) { req := act.httpRequest(c.endpoint) diff --git a/client/keys.go b/client/keys.go index 7d7daeb34..3ca55792b 100644 --- a/client/keys.go +++ b/client/keys.go @@ -58,12 +58,12 @@ func newHTTPKeysAPIWithPrefix(tr *http.Transport, ep string, to time.Duration, p c := &httpClient{ transport: tr, endpoint: *u, - timeout: to, } kAPI := httpKeysAPI{ - client: c, - prefix: prefix, + client: c, + prefix: prefix, + timeout: to, } return &kAPI, nil @@ -100,8 +100,9 @@ func (n *Node) String() string { } type httpKeysAPI struct { - client *httpClient - prefix string + client *httpClient + prefix string + timeout time.Duration } func (k *httpKeysAPI) Create(key, val string, ttl time.Duration) (*Response, error) { @@ -115,7 +116,9 @@ func (k *httpKeysAPI) Create(key, val string, ttl time.Duration) (*Response, err create.TTL = &uttl } - code, body, err := k.client.doWithTimeout(create) + ctx, cancel := context.WithTimeout(context.Background(), k.timeout) + code, body, err := k.client.do(ctx, create) + cancel() if err != nil { return nil, err } @@ -130,7 +133,9 @@ func (k *httpKeysAPI) Get(key string) (*Response, error) { Recursive: false, } - code, body, err := k.client.doWithTimeout(get) + ctx, cancel := context.WithTimeout(context.Background(), k.timeout) + code, body, err := k.client.do(ctx, get) + cancel() if err != nil { return nil, err } diff --git a/client/members.go b/client/members.go index 61f9a9cde..d49340eaf 100644 --- a/client/members.go +++ b/client/members.go @@ -25,6 +25,7 @@ import ( "path" "time" + "github.com/coreos/etcd/Godeps/_workspace/src/code.google.com/p/go.net/context" "github.com/coreos/etcd/etcdserver/etcdhttp/httptypes" "github.com/coreos/etcd/pkg/types" ) @@ -42,11 +43,11 @@ func NewMembersAPI(tr *http.Transport, ep string, to time.Duration) (MembersAPI, c := &httpClient{ transport: tr, endpoint: *u, - timeout: to, } mAPI := httpMembersAPI{ - client: c, + client: c, + timeout: to, } return &mAPI, nil @@ -59,12 +60,15 @@ type MembersAPI interface { } type httpMembersAPI struct { - client *httpClient + client *httpClient + timeout time.Duration } func (m *httpMembersAPI) List() ([]httptypes.Member, error) { req := &membersAPIActionList{} - code, body, err := m.client.doWithTimeout(req) + ctx, cancel := context.WithTimeout(context.Background(), m.timeout) + code, body, err := m.client.do(ctx, req) + cancel() if err != nil { return nil, err } @@ -88,7 +92,9 @@ func (m *httpMembersAPI) Add(peerURL string) (*httptypes.Member, error) { } req := &membersAPIActionAdd{peerURLs: urls} - code, body, err := m.client.doWithTimeout(req) + ctx, cancel := context.WithTimeout(context.Background(), m.timeout) + code, body, err := m.client.do(ctx, req) + cancel() if err != nil { return nil, err } @@ -107,7 +113,9 @@ func (m *httpMembersAPI) Add(peerURL string) (*httptypes.Member, error) { func (m *httpMembersAPI) Remove(memberID string) error { req := &membersAPIActionRemove{memberID: memberID} - code, _, err := m.client.doWithTimeout(req) + ctx, cancel := context.WithTimeout(context.Background(), m.timeout) + code, _, err := m.client.do(ctx, req) + cancel() if err != nil { return err } From 323fb1ec85d6288524e7b0cb95a4a299e98fe3fc Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 30 Oct 2014 17:04:06 -0700 Subject: [PATCH 06/10] client: introduce httpActionDo interface --- client/http.go | 4 ++++ client/keys.go | 4 ++-- client/members.go | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/client/http.go b/client/http.go index 6fee346d5..5bdf0eebe 100644 --- a/client/http.go +++ b/client/http.go @@ -42,6 +42,10 @@ type httpAction interface { httpRequest(url.URL) *http.Request } +type httpActionDo interface { + do(context.Context, httpAction) (int, []byte, error) +} + type roundTripResponse struct { resp *http.Response err error diff --git a/client/keys.go b/client/keys.go index 3ca55792b..878e99ca4 100644 --- a/client/keys.go +++ b/client/keys.go @@ -100,7 +100,7 @@ func (n *Node) String() string { } type httpKeysAPI struct { - client *httpClient + client httpActionDo prefix string timeout time.Duration } @@ -168,7 +168,7 @@ func (k *httpKeysAPI) RecursiveWatch(key string, idx uint64) Watcher { } type httpWatcher struct { - client *httpClient + client httpActionDo nextWait waitAction } diff --git a/client/members.go b/client/members.go index d49340eaf..abd211a23 100644 --- a/client/members.go +++ b/client/members.go @@ -60,7 +60,7 @@ type MembersAPI interface { } type httpMembersAPI struct { - client *httpClient + client httpActionDo timeout time.Duration } From 8d519ffdb85e2075b38a91fc1a95fa386e747e1b Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 30 Oct 2014 17:10:01 -0700 Subject: [PATCH 07/10] client: introduce httpClusterClient --- client/cluster.go | 52 +++++++++++++++++++++++++++++++++++++++++++++++ client/keys.go | 7 +------ client/members.go | 7 +------ 3 files changed, 54 insertions(+), 12 deletions(-) create mode 100644 client/cluster.go diff --git a/client/cluster.go b/client/cluster.go new file mode 100644 index 000000000..3fcc8ec04 --- /dev/null +++ b/client/cluster.go @@ -0,0 +1,52 @@ +/* + Copyright 2014 CoreOS, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package client + +import ( + "net/http" + "net/url" + + "github.com/coreos/etcd/Godeps/_workspace/src/code.google.com/p/go.net/context" +) + +func newHTTPClusterClient(tr *http.Transport, eps []string) (*httpClusterClient, error) { + c := httpClusterClient{ + endpoints: make([]*httpClient, len(eps)), + } + + for i, ep := range eps { + u, err := url.Parse(ep) + if err != nil { + return nil, err + } + c.endpoints[i] = &httpClient{ + transport: tr, + endpoint: *u, + } + } + + return &c, nil +} + +type httpClusterClient struct { + endpoints []*httpClient +} + +func (c *httpClusterClient) do(ctx context.Context, act httpAction) (int, []byte, error) { + //TODO(bcwaldon): introduce retry logic so all endpoints are attempted + return c.endpoints[0].do(ctx, act) +} diff --git a/client/keys.go b/client/keys.go index 878e99ca4..4e1c5d2cf 100644 --- a/client/keys.go +++ b/client/keys.go @@ -50,16 +50,11 @@ func NewDiscoveryKeysAPI(tr *http.Transport, ep string, to time.Duration) (KeysA } func newHTTPKeysAPIWithPrefix(tr *http.Transport, ep string, to time.Duration, prefix string) (*httpKeysAPI, error) { - u, err := url.Parse(ep) + c, err := newHTTPClusterClient(tr, []string{ep}) if err != nil { return nil, err } - c := &httpClient{ - transport: tr, - endpoint: *u, - } - kAPI := httpKeysAPI{ client: c, prefix: prefix, diff --git a/client/members.go b/client/members.go index abd211a23..eb798980c 100644 --- a/client/members.go +++ b/client/members.go @@ -35,16 +35,11 @@ var ( ) func NewMembersAPI(tr *http.Transport, ep string, to time.Duration) (MembersAPI, error) { - u, err := url.Parse(ep) + c, err := newHTTPClusterClient(tr, []string{ep}) if err != nil { return nil, err } - c := &httpClient{ - transport: tr, - endpoint: *u, - } - mAPI := httpMembersAPI{ client: c, timeout: to, From 8b8b3efdaa78c007554acb875c73fd2fce4cfbf1 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 30 Oct 2014 17:22:47 -0700 Subject: [PATCH 08/10] client: accept slice of endpoints --- client/cluster.go | 1 + client/keys.go | 12 ++++++------ client/members.go | 4 ++-- discovery/discovery.go | 2 +- etcdctl/command/member_commands.go | 2 +- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/client/cluster.go b/client/cluster.go index 3fcc8ec04..b57f57c8d 100644 --- a/client/cluster.go +++ b/client/cluster.go @@ -33,6 +33,7 @@ func newHTTPClusterClient(tr *http.Transport, eps []string) (*httpClusterClient, if err != nil { return nil, err } + c.endpoints[i] = &httpClient{ transport: tr, endpoint: *u, diff --git a/client/keys.go b/client/keys.go index 4e1c5d2cf..4235c05cc 100644 --- a/client/keys.go +++ b/client/keys.go @@ -41,16 +41,16 @@ var ( ErrKeyExists = errors.New("client: key already exists") ) -func NewKeysAPI(tr *http.Transport, ep string, to time.Duration) (KeysAPI, error) { - return newHTTPKeysAPIWithPrefix(tr, ep, to, DefaultV2KeysPrefix) +func NewKeysAPI(tr *http.Transport, eps []string, to time.Duration) (KeysAPI, error) { + return newHTTPKeysAPIWithPrefix(tr, eps, to, DefaultV2KeysPrefix) } -func NewDiscoveryKeysAPI(tr *http.Transport, ep string, to time.Duration) (KeysAPI, error) { - return newHTTPKeysAPIWithPrefix(tr, ep, to, "") +func NewDiscoveryKeysAPI(tr *http.Transport, eps []string, to time.Duration) (KeysAPI, error) { + return newHTTPKeysAPIWithPrefix(tr, eps, to, "") } -func newHTTPKeysAPIWithPrefix(tr *http.Transport, ep string, to time.Duration, prefix string) (*httpKeysAPI, error) { - c, err := newHTTPClusterClient(tr, []string{ep}) +func newHTTPKeysAPIWithPrefix(tr *http.Transport, eps []string, to time.Duration, prefix string) (*httpKeysAPI, error) { + c, err := newHTTPClusterClient(tr, eps) if err != nil { return nil, err } diff --git a/client/members.go b/client/members.go index eb798980c..5eeaea3f9 100644 --- a/client/members.go +++ b/client/members.go @@ -34,8 +34,8 @@ var ( DefaultV2MembersPrefix = "/v2/members" ) -func NewMembersAPI(tr *http.Transport, ep string, to time.Duration) (MembersAPI, error) { - c, err := newHTTPClusterClient(tr, []string{ep}) +func NewMembersAPI(tr *http.Transport, eps []string, to time.Duration) (MembersAPI, error) { + c, err := newHTTPClusterClient(tr, eps) if err != nil { return nil, err } diff --git a/discovery/discovery.go b/discovery/discovery.go index a4d1089bd..b16f38713 100644 --- a/discovery/discovery.go +++ b/discovery/discovery.go @@ -106,7 +106,7 @@ func New(durl string, id types.ID, config string) (Discoverer, error) { if err != nil { return nil, err } - c, err := client.NewDiscoveryKeysAPI(&http.Transport{Proxy: pf}, u.String(), client.DefaultRequestTimeout) + c, err := client.NewDiscoveryKeysAPI(&http.Transport{Proxy: pf}, []string{u.String()}, client.DefaultRequestTimeout) if err != nil { return nil, err } diff --git a/etcdctl/command/member_commands.go b/etcdctl/command/member_commands.go index c713fe9f8..caf182e97 100644 --- a/etcdctl/command/member_commands.go +++ b/etcdctl/command/member_commands.go @@ -34,7 +34,7 @@ func NewMemberCommand() cli.Command { } func mustNewMembersAPI(c *cli.Context) client.MembersAPI { - mAPI, err := client.NewMembersAPI(&http.Transport{}, "http://127.0.0.1:4001", client.DefaultRequestTimeout) + mAPI, err := client.NewMembersAPI(&http.Transport{}, []string{"http://127.0.0.1:4001"}, client.DefaultRequestTimeout) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) From f0c3385cfc10909d4b8d9a1e58abeabdc96dca49 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 30 Oct 2014 17:24:40 -0700 Subject: [PATCH 09/10] etcdctl: wire up --peers for member commands --- etcdctl/command/member_commands.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/etcdctl/command/member_commands.go b/etcdctl/command/member_commands.go index caf182e97..ae2fd3252 100644 --- a/etcdctl/command/member_commands.go +++ b/etcdctl/command/member_commands.go @@ -34,7 +34,14 @@ func NewMemberCommand() cli.Command { } func mustNewMembersAPI(c *cli.Context) client.MembersAPI { - mAPI, err := client.NewMembersAPI(&http.Transport{}, []string{"http://127.0.0.1:4001"}, client.DefaultRequestTimeout) + peers := getPeersFlagValue(c) + for i, p := range peers { + if !strings.HasPrefix(p, "http") && !strings.HasPrefix(p, "https") { + peers[i] = fmt.Sprintf("http://%s", p) + } + } + + mAPI, err := client.NewMembersAPI(&http.Transport{}, peers, client.DefaultRequestTimeout) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) From eab46927449e2020178462bc03a5f33aecc074c2 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 31 Oct 2014 11:44:28 -0700 Subject: [PATCH 10/10] client: use v2MembersURL helper --- client/members.go | 20 ++++++++++++++------ client/members_test.go | 18 ++++++++++++++++++ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/client/members.go b/client/members.go index 5eeaea3f9..780bf28cb 100644 --- a/client/members.go +++ b/client/members.go @@ -121,8 +121,8 @@ func (m *httpMembersAPI) Remove(memberID string) error { type membersAPIActionList struct{} func (l *membersAPIActionList) httpRequest(ep url.URL) *http.Request { - ep.Path = path.Join(ep.Path, DefaultV2MembersPrefix) - req, _ := http.NewRequest("GET", ep.String(), nil) + u := v2MembersURL(ep) + req, _ := http.NewRequest("GET", u.String(), nil) return req } @@ -131,8 +131,9 @@ type membersAPIActionRemove struct { } func (d *membersAPIActionRemove) httpRequest(ep url.URL) *http.Request { - ep.Path = path.Join(ep.Path, DefaultV2MembersPrefix, d.memberID) - req, _ := http.NewRequest("DELETE", ep.String(), nil) + u := v2MembersURL(ep) + u.Path = path.Join(u.Path, d.memberID) + req, _ := http.NewRequest("DELETE", u.String(), nil) return req } @@ -141,10 +142,10 @@ type membersAPIActionAdd struct { } func (a *membersAPIActionAdd) httpRequest(ep url.URL) *http.Request { - ep.Path = path.Join(ep.Path, DefaultV2MembersPrefix) + u := v2MembersURL(ep) m := httptypes.MemberCreateRequest{PeerURLs: a.peerURLs} b, _ := json.Marshal(&m) - req, _ := http.NewRequest("POST", ep.String(), bytes.NewReader(b)) + req, _ := http.NewRequest("POST", u.String(), bytes.NewReader(b)) req.Header.Set("Content-Type", "application/json") return req } @@ -155,3 +156,10 @@ func assertStatusCode(want, got int) (err error) { } return err } + +// v2MembersURL add the necessary path to the provided endpoint +// to route requests to the default v2 members API. +func v2MembersURL(ep url.URL) *url.URL { + ep.Path = path.Join(ep.Path, DefaultV2MembersPrefix) + return &ep +} diff --git a/client/members_test.go b/client/members_test.go index a8975c0cc..8d1534e49 100644 --- a/client/members_test.go +++ b/client/members_test.go @@ -19,6 +19,7 @@ package client import ( "net/http" "net/url" + "reflect" "testing" "github.com/coreos/etcd/pkg/types" @@ -93,3 +94,20 @@ func TestAssertStatusCode(t *testing.T) { t.Errorf("assertStatusCode found conflict in 400 vs 400: %v", err) } } + +func TestV2MembersURL(t *testing.T) { + got := v2MembersURL(url.URL{ + Scheme: "http", + Host: "foo.example.com:4002", + Path: "/pants", + }) + want := &url.URL{ + Scheme: "http", + Host: "foo.example.com:4002", + Path: "/pants/v2/members", + } + + if !reflect.DeepEqual(want, got) { + t.Fatalf("v2MembersURL got %#v, want %#v", got, want) + } +}