From 8985398c7e48121a075ad0d8915617b644b9e675 Mon Sep 17 00:00:00 2001 From: Kyle Havlovitz Date: Wed, 8 Feb 2017 16:56:58 -0500 Subject: [PATCH 1/9] Convert event command to use base.Command --- command/base/command.go | 8 +++ command/event.go | 51 +++++++------------ command/event_test.go | 10 +++- commands.go | 25 ++++++--- ....html.markdown => event.html.markdown.erb} | 11 ++-- 5 files changed, 56 insertions(+), 49 deletions(-) rename website/source/docs/commands/{event.html.markdown => event.html.markdown.erb} (82%) diff --git a/command/base/command.go b/command/base/command.go index 3d4020924f..40c578eea1 100644 --- a/command/base/command.go +++ b/command/base/command.go @@ -58,6 +58,10 @@ func (c *Command) HTTPClient() (*api.Client, error) { return api.NewClient(config) } +func (c *Command) HTTPDatacenter() string { + return c.datacenter.String() +} + // httpFlagsClient is the list of flags that apply to HTTP connections. func (c *Command) httpFlagsClient(f *flag.FlagSet) *flag.FlagSet { if f == nil { @@ -198,6 +202,10 @@ func printTitle(w io.Writer, s string) { func printFlag(w io.Writer, f *flag.Flag) { example, _ := flag.UnquoteUsage(f) if example != "" { + // Change 'value' to 'string' for consistency + if example == "value" { + example = "string" + } fmt.Fprintf(w, " -%s=<%s>\n", f.Name, example) } else { fmt.Fprintf(w, " -%s\n", f.Name) diff --git a/command/event.go b/command/event.go index da4394b53e..75b7733557 100644 --- a/command/event.go +++ b/command/event.go @@ -1,19 +1,18 @@ package command import ( - "flag" "fmt" "regexp" "strings" consulapi "github.com/hashicorp/consul/api" - "github.com/mitchellh/cli" + "github.com/hashicorp/consul/command/base" ) // EventCommand is a Command implementation that is used to // fire new events type EventCommand struct { - Ui cli.Ui + base.Command } func (c *EventCommand) Help() string { @@ -24,33 +23,25 @@ Usage: consul event [options] [payload] a name, but a payload is optional. Events support filtering using regular expressions on node name, service, and tag definitions. -Options: +` + c.Command.Help() - -http-addr=127.0.0.1:8500 HTTP address of the Consul agent. - -datacenter="" Datacenter to dispatch in. Defaults to that of agent. - -name="" Name of the event. - -node="" Regular expression to filter on node names - -service="" Regular expression to filter on service instances - -tag="" Regular expression to filter on service tags. Must be used - with -service. - -token="" ACL token to use during requests. Defaults to that - of the agent. -` return strings.TrimSpace(helpText) } func (c *EventCommand) Run(args []string) int { - var datacenter, name, node, service, tag, token string - cmdFlags := flag.NewFlagSet("event", flag.ContinueOnError) - cmdFlags.Usage = func() { c.Ui.Output(c.Help()) } - cmdFlags.StringVar(&datacenter, "datacenter", "", "") - cmdFlags.StringVar(&name, "name", "", "") - cmdFlags.StringVar(&node, "node", "", "") - cmdFlags.StringVar(&service, "service", "", "") - cmdFlags.StringVar(&tag, "tag", "", "") - cmdFlags.StringVar(&token, "token", "", "") - httpAddr := HTTPAddrFlag(cmdFlags) - if err := cmdFlags.Parse(args); err != nil { + var name, node, service, tag string + + f := c.Command.NewFlagSet(c) + f.StringVar(&name, "name", "", + "Name of the event.") + f.StringVar(&node, "node", "", + "Regular expression to filter on node names.") + f.StringVar(&service, "service", "", + "Regular expression to filter on service instances") + f.StringVar(&tag, "tag", "", + "Regular expression to filter on service tags. Must be used with -service.") + + if err := c.Command.Parse(args); err != nil { return 1 } @@ -88,7 +79,7 @@ func (c *EventCommand) Run(args []string) int { // Check for a payload var payload []byte - args = cmdFlags.Args() + args = f.Args() switch len(args) { case 0: case 1: @@ -101,7 +92,7 @@ func (c *EventCommand) Run(args []string) int { } // Create and test the HTTP client - client, err := HTTPClient(*httpAddr) + client, err := c.Command.HTTPClient() if err != nil { c.Ui.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 @@ -121,13 +112,9 @@ func (c *EventCommand) Run(args []string) int { ServiceFilter: service, TagFilter: tag, } - opts := &consulapi.WriteOptions{ - Datacenter: datacenter, - Token: token, - } // Fire the event - id, _, err := event.Fire(params, opts) + id, _, err := event.Fire(params, nil) if err != nil { c.Ui.Error(fmt.Sprintf("Error firing event: %s", err)) return 1 diff --git a/command/event_test.go b/command/event_test.go index ee4b929a03..0b7a824204 100644 --- a/command/event_test.go +++ b/command/event_test.go @@ -1,13 +1,14 @@ package command import ( + "github.com/hashicorp/consul/command/base" "github.com/mitchellh/cli" "strings" "testing" ) func TestEventCommand_implements(t *testing.T) { - var _ cli.Command = &WatchCommand{} + var _ cli.Command = &EventCommand{} } func TestEventCommandRun(t *testing.T) { @@ -15,7 +16,12 @@ func TestEventCommandRun(t *testing.T) { defer a1.Shutdown() ui := new(cli.MockUi) - c := &EventCommand{Ui: ui} + c := &EventCommand{ + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetClientHTTP, + }, + } args := []string{"-http-addr=" + a1.httpAddr, "-name=cmd"} code := c.Run(args) diff --git a/commands.go b/commands.go index 3a9ee39672..c8a85b30c1 100644 --- a/commands.go +++ b/commands.go @@ -41,14 +41,20 @@ func init() { "event": func() (cli.Command, error) { return &command.EventCommand{ - Ui: ui, + Command: base.Command{ + Flags: base.FlagSetHTTP, + Ui: ui, + }, }, nil }, "exec": func() (cli.Command, error) { return &command.ExecCommand{ ShutdownCh: makeShutdownCh(), - Ui: ui, + Command: base.Command{ + Flags: base.FlagSetHTTP, + Ui: ui, + }, }, nil }, @@ -61,6 +67,15 @@ func init() { }, nil }, + "info": func() (cli.Command, error) { + return &command.InfoCommand{ + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetClientHTTP, + }, + }, nil + }, + "kv": func() (cli.Command, error) { return &command.KVCommand{ Ui: ui, @@ -156,12 +171,6 @@ func init() { }, nil }, - "info": func() (cli.Command, error) { - return &command.InfoCommand{ - Ui: ui, - }, nil - }, - "reload": func() (cli.Command, error) { return &command.ReloadCommand{ Ui: ui, diff --git a/website/source/docs/commands/event.html.markdown b/website/source/docs/commands/event.html.markdown.erb similarity index 82% rename from website/source/docs/commands/event.html.markdown rename to website/source/docs/commands/event.html.markdown.erb index 51a3831eeb..799cd9140a 100644 --- a/website/source/docs/commands/event.html.markdown +++ b/website/source/docs/commands/event.html.markdown.erb @@ -39,13 +39,12 @@ Usage: `consul event [options] [payload]` The only required option is `-name` which specifies the event name. An optional payload can be provided as the final argument. -The list of available flags are: +#### API Options -* `-http-addr` - Address to the HTTP server of the agent you want to contact - to send this command. If this isn't specified, the command will contact - `127.0.0.1:8500` which is the default HTTP address of a Consul agent. +<%= partial "docs/commands/http_api_options_client" %> +<%= partial "docs/commands/http_api_options_server" %> -* `-datacenter` - Datacenter to query. Defaults to that of agent. +#### Command Options * `-name` - The name of the event. @@ -57,5 +56,3 @@ The list of available flags are: a matching tag. This must be used with `-service`. As an example, you may do `-service mysql -tag secondary`. -* `-token` - The ACL token to use when firing the event. This token must have - write-level privileges for the event specified. Defaults to that of the agent. From aa1c464961667e51db4a6ffb4b4aef6890cf5ec2 Mon Sep 17 00:00:00 2001 From: Kyle Havlovitz Date: Wed, 8 Feb 2017 16:57:46 -0500 Subject: [PATCH 2/9] Convert exec command to use base.Command --- command/exec.go | 68 ++++++++----------- command/exec_test.go | 45 ++++++------ ...c.html.markdown => exec.html.markdown.erb} | 14 ++-- 3 files changed, 52 insertions(+), 75 deletions(-) rename website/source/docs/commands/{exec.html.markdown => exec.html.markdown.erb} (81%) diff --git a/command/exec.go b/command/exec.go index 8823116e8d..2e59f2818c 100644 --- a/command/exec.go +++ b/command/exec.go @@ -3,7 +3,6 @@ package command import ( "bytes" "encoding/json" - "flag" "fmt" "io" "os" @@ -15,6 +14,7 @@ import ( "unicode" consulapi "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/command/base" "github.com/mitchellh/cli" ) @@ -53,9 +53,7 @@ const ( // rExecConf is used to pass around configuration type rExecConf struct { - datacenter string - prefix string - token string + prefix string foreignDC bool localDC string @@ -118,8 +116,9 @@ type rExecExit struct { // ExecCommand is a Command implementation that is used to // do remote execution of commands type ExecCommand struct { + base.Command + ShutdownCh <-chan struct{} - Ui cli.Ui conf rExecConf client *consulapi.Client sessionID string @@ -127,24 +126,29 @@ type ExecCommand struct { } func (c *ExecCommand) Run(args []string) int { - cmdFlags := flag.NewFlagSet("exec", flag.ContinueOnError) - cmdFlags.Usage = func() { c.Ui.Output(c.Help()) } - cmdFlags.StringVar(&c.conf.datacenter, "datacenter", "", "") - cmdFlags.StringVar(&c.conf.node, "node", "", "") - cmdFlags.StringVar(&c.conf.service, "service", "", "") - cmdFlags.StringVar(&c.conf.tag, "tag", "", "") - cmdFlags.StringVar(&c.conf.prefix, "prefix", rExecPrefix, "") - cmdFlags.DurationVar(&c.conf.replWait, "wait-repl", rExecReplicationWait, "") - cmdFlags.DurationVar(&c.conf.wait, "wait", rExecQuietWait, "") - cmdFlags.BoolVar(&c.conf.verbose, "verbose", false, "") - cmdFlags.StringVar(&c.conf.token, "token", "", "") - httpAddr := HTTPAddrFlag(cmdFlags) - if err := cmdFlags.Parse(args); err != nil { + f := c.Command.NewFlagSet(c) + f.StringVar(&c.conf.node, "node", "", + "Regular expression to filter on node names.") + f.StringVar(&c.conf.service, "service", "", + "Regular expression to filter on service instances.") + f.StringVar(&c.conf.tag, "tag", "", + "Regular expression to filter on service tags. Must be used with -service.") + f.StringVar(&c.conf.prefix, "prefix", rExecPrefix, + "Prefix in the KV store to use for request data.") + f.DurationVar(&c.conf.wait, "wait", rExecQuietWait, + "Period to wait with no responses before terminating execution.") + f.DurationVar(&c.conf.replWait, "wait-repl", rExecReplicationWait, + "Period to wait for replication before firing event. This is an "+ + "optimization to allow stale reads to be performed.") + f.BoolVar(&c.conf.verbose, "verbose", false, + "Enables verbose output.") + + if err := c.Command.Parse(args); err != nil { return 1 } // Join the commands to execute - c.conf.cmd = strings.Join(cmdFlags.Args(), " ") + c.conf.cmd = strings.Join(f.Args(), " ") // If there is no command, read stdin for a script input if c.conf.cmd == "-" { @@ -175,11 +179,7 @@ func (c *ExecCommand) Run(args []string) int { } // Create and test the HTTP client - client, err := HTTPClientConfig(func(clientConf *consulapi.Config) { - clientConf.Address = *httpAddr - clientConf.Datacenter = c.conf.datacenter - clientConf.Token = c.conf.token - }) + client, err := c.Command.HTTPClient() if err != nil { c.Ui.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 @@ -192,7 +192,7 @@ func (c *ExecCommand) Run(args []string) int { c.client = client // Check if this is a foreign datacenter - if c.conf.datacenter != "" && c.conf.datacenter != info["Config"]["Datacenter"] { + if c.Command.HTTPDatacenter() != "" && c.Command.HTTPDatacenter() != info["Config"]["Datacenter"] { if c.conf.verbose { c.Ui.Info("Remote exec in foreign datacenter, using Session TTL") } @@ -489,7 +489,7 @@ func (c *ExecCommand) createSessionForeign() (string, error) { node := services[0].Node.Node if c.conf.verbose { c.Ui.Info(fmt.Sprintf("Binding session to remote node %s@%s", - node, c.conf.datacenter)) + node, c.Command.HTTPDatacenter())) } session := c.client.Session() @@ -618,22 +618,8 @@ Usage: consul exec [options] [-|command...] definitions. If a command is '-', stdin will be read until EOF and used as a script input. -Options: +` + c.Command.Help() - -http-addr=127.0.0.1:8500 HTTP address of the Consul agent. - -datacenter="" Datacenter to dispatch in. Defaults to that of agent. - -prefix="_rexec" Prefix in the KV store to use for request data - -node="" Regular expression to filter on node names - -service="" Regular expression to filter on service instances - -tag="" Regular expression to filter on service tags. Must be used - with -service. - -wait=2s Period to wait with no responses before terminating execution. - -wait-repl=200ms Period to wait for replication before firing event. This is an - optimization to allow stale reads to be performed. - -verbose Enables verbose output - -token="" ACL token to use during requests. Defaults to that - of the agent. -` return strings.TrimSpace(helpText) } diff --git a/command/exec_test.go b/command/exec_test.go index 3f53b1a0fa..e1f9aef8de 100644 --- a/command/exec_test.go +++ b/command/exec_test.go @@ -8,10 +8,21 @@ import ( consulapi "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/command/agent" + "github.com/hashicorp/consul/command/base" "github.com/hashicorp/consul/testutil" "github.com/mitchellh/cli" ) +func testExecCommand(t *testing.T) (*cli.MockUi, *ExecCommand) { + ui := new(cli.MockUi) + return ui, &ExecCommand{ + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetHTTP, + }, + } +} + func TestExecCommand_implements(t *testing.T) { var _ cli.Command = &ExecCommand{} } @@ -21,8 +32,7 @@ func TestExecCommandRun(t *testing.T) { defer a1.Shutdown() waitForLeader(t, a1.httpAddr) - ui := new(cli.MockUi) - c := &ExecCommand{Ui: ui} + ui, c := testExecCommand(t) args := []string{"-http-addr=" + a1.httpAddr, "-wait=10s", "uptime"} code := c.Run(args) @@ -57,8 +67,7 @@ func TestExecCommandRun_CrossDC(t *testing.T) { waitForLeader(t, a1.httpAddr) waitForLeader(t, a2.httpAddr) - ui := new(cli.MockUi) - c := &ExecCommand{Ui: ui} + ui, c := testExecCommand(t) args := []string{"-http-addr=" + a1.httpAddr, "-wait=400ms", "-datacenter=dc2", "uptime"} @@ -130,11 +139,8 @@ func TestExecCommand_Sessions(t *testing.T) { t.Fatalf("err: %v", err) } - ui := new(cli.MockUi) - c := &ExecCommand{ - Ui: ui, - client: client, - } + _, c := testExecCommand(t) + c.client = client id, err := c.createSession() if err != nil { @@ -174,11 +180,8 @@ func TestExecCommand_Sessions_Foreign(t *testing.T) { t.Fatalf("err: %v", err) } - ui := new(cli.MockUi) - c := &ExecCommand{ - Ui: ui, - client: client, - } + _, c := testExecCommand(t) + c.client = client c.conf.foreignDC = true c.conf.localDC = "dc1" @@ -228,11 +231,8 @@ func TestExecCommand_UploadDestroy(t *testing.T) { t.Fatalf("err: %v", err) } - ui := new(cli.MockUi) - c := &ExecCommand{ - Ui: ui, - client: client, - } + _, c := testExecCommand(t) + c.client = client id, err := c.createSession() if err != nil { @@ -288,11 +288,8 @@ func TestExecCommand_StreamResults(t *testing.T) { t.Fatalf("err: %v", err) } - ui := new(cli.MockUi) - c := &ExecCommand{ - Ui: ui, - client: client, - } + _, c := testExecCommand(t) + c.client = client c.conf.prefix = "_rexec" id, err := c.createSession() diff --git a/website/source/docs/commands/exec.html.markdown b/website/source/docs/commands/exec.html.markdown.erb similarity index 81% rename from website/source/docs/commands/exec.html.markdown rename to website/source/docs/commands/exec.html.markdown.erb index 89ba0d62e0..6cc5a67949 100644 --- a/website/source/docs/commands/exec.html.markdown +++ b/website/source/docs/commands/exec.html.markdown.erb @@ -37,14 +37,12 @@ The only required option is a command to execute. This is either given as trailing arguments, or by specifying `-`; STDIN will be read to completion as a script to evaluate. -The list of available flags are: +#### API Options -* `-http-addr` - Address to the HTTP server of the agent you want to contact - to send this command. If this isn't specified, the command will contact - `127.0.0.1:8500` which is the default HTTP address of a Consul agent. +<%= partial "docs/commands/http_api_options_client" %> +<%= partial "docs/commands/http_api_options_server" %> -* `-datacenter` - Datacenter to query. Defaults to that of agent. In version - 0.4, that is the only supported value. +#### Command Options * `-prefix` - Key prefix in the KV store to use for storing request data. Defaults to `_rexec`. @@ -67,7 +65,3 @@ The list of available flags are: to 200 msec. * `-verbose` - Enables verbose output. - -* `-token` - The ACL token to use during requests. This token must have access - to the prefix in the KV store as well as exec "write" access for the `_rexec` - event. Defaults to that of the agent. From a3d02a4cbc69411cf30a327ad64cb989ddfaa33e Mon Sep 17 00:00:00 2001 From: Kyle Havlovitz Date: Wed, 8 Feb 2017 16:58:04 -0500 Subject: [PATCH 3/9] Convert info command to use base.Command --- command/info.go | 34 +++++++++++-------- command/info_test.go | 10 ++++-- ...o.html.markdown => info.html.markdown.erb} | 8 ++--- website/source/layouts/docs.erb | 8 ++--- 4 files changed, 33 insertions(+), 27 deletions(-) rename website/source/docs/commands/{info.html.markdown => info.html.markdown.erb} (84%) diff --git a/command/info.go b/command/info.go index b69f35ee8b..26b46ab935 100644 --- a/command/info.go +++ b/command/info.go @@ -1,9 +1,8 @@ package command import ( - "flag" "fmt" - "github.com/mitchellh/cli" + "github.com/hashicorp/consul/command/base" "sort" "strings" ) @@ -11,7 +10,7 @@ import ( // InfoCommand is a Command implementation that queries a running // Consul agent for various debugging statistics for operators type InfoCommand struct { - Ui cli.Ui + base.Command } func (i *InfoCommand) Help() string { @@ -20,33 +19,34 @@ Usage: consul info [options] Provides debugging information for operators -Options: +` + i.Command.Help() - -rpc-addr=127.0.0.1:8400 RPC address of the Consul agent. -` return strings.TrimSpace(helpText) } func (i *InfoCommand) Run(args []string) int { - cmdFlags := flag.NewFlagSet("info", flag.ContinueOnError) - cmdFlags.Usage = func() { i.Ui.Output(i.Help()) } - rpcAddr := RPCAddrFlag(cmdFlags) - if err := cmdFlags.Parse(args); err != nil { + i.Command.NewFlagSet(i) + + if err := i.Command.Parse(args); err != nil { return 1 } - client, err := RPCClient(*rpcAddr) + client, err := i.Command.HTTPClient() if err != nil { i.Ui.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 } - defer client.Close() - stats, err := client.Stats() + self, err := client.Agent().Self() if err != nil { i.Ui.Error(fmt.Sprintf("Error querying agent: %s", err)) return 1 } + stats, ok := self["Stats"] + if !ok { + i.Ui.Error(fmt.Sprintf("Agent response did not contain 'Stats' key: %v", self)) + return 1 + } // Get the keys in sorted order keys := make([]string, 0, len(stats)) @@ -60,7 +60,11 @@ func (i *InfoCommand) Run(args []string) int { i.Ui.Output(key + ":") // Sort the sub-keys - subvals := stats[key] + subvals, ok := stats[key].(map[string]interface{}) + if !ok { + i.Ui.Error(fmt.Sprintf("Got invalid subkey in stats: %v", subvals)) + return 1 + } subkeys := make([]string, 0, len(subvals)) for k := range subvals { subkeys = append(subkeys, k) @@ -77,5 +81,5 @@ func (i *InfoCommand) Run(args []string) int { } func (i *InfoCommand) Synopsis() string { - return "Provides debugging information for operators" + return "Provides debugging information for operators." } diff --git a/command/info_test.go b/command/info_test.go index 3e48f95ca1..2305ce8a66 100644 --- a/command/info_test.go +++ b/command/info_test.go @@ -1,6 +1,7 @@ package command import ( + "github.com/hashicorp/consul/command/base" "github.com/mitchellh/cli" "strings" "testing" @@ -15,8 +16,13 @@ func TestInfoCommandRun(t *testing.T) { defer a1.Shutdown() ui := new(cli.MockUi) - c := &InfoCommand{Ui: ui} - args := []string{"-rpc-addr=" + a1.addr} + c := &InfoCommand{ + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetClientHTTP, + }, + } + args := []string{"-http-addr=" + a1.httpAddr} code := c.Run(args) if code != 0 { diff --git a/website/source/docs/commands/info.html.markdown b/website/source/docs/commands/info.html.markdown.erb similarity index 84% rename from website/source/docs/commands/info.html.markdown rename to website/source/docs/commands/info.html.markdown.erb index aaf3c7af8d..e4adf95bc2 100644 --- a/website/source/docs/commands/info.html.markdown +++ b/website/source/docs/commands/info.html.markdown.erb @@ -72,10 +72,6 @@ serf_wan: Usage: `consul info` -The command-line flags are all optional. The list of available flags are: - -* `-rpc-addr` - Address to the RPC server of the agent you want to contact - to send this command. If this isn't specified, the command checks the - CONSUL_RPC_ADDR env variable. If this isn't set, the default RPC - address will be set to "127.0.0.1:8400". +#### API Options +<%= partial "docs/commands/http_api_options_client" %> diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index 852be192e0..60300df626 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -87,6 +87,10 @@ force-leave + > + info + + > join @@ -143,10 +147,6 @@ operator - > - info - - > reload From 8775b031d3c20ad66e8ac5e90f47a9b4a0838917 Mon Sep 17 00:00:00 2001 From: Kyle Havlovitz Date: Wed, 8 Feb 2017 17:14:02 -0500 Subject: [PATCH 4/9] Convert join command to use base.Command --- command/join.go | 37 +++++++++---------- command/join_test.go | 26 ++++++++----- commands.go | 15 +++++--- ...n.html.markdown => join.html.markdown.erb} | 10 ++--- 4 files changed, 48 insertions(+), 40 deletions(-) rename website/source/docs/commands/{join.html.markdown => join.html.markdown.erb} (81%) diff --git a/command/join.go b/command/join.go index ea22e1906b..88255e54bf 100644 --- a/command/join.go +++ b/command/join.go @@ -1,16 +1,15 @@ package command import ( - "flag" "fmt" - "github.com/mitchellh/cli" + "github.com/hashicorp/consul/command/base" "strings" ) // JoinCommand is a Command implementation that tells a running Consul // agent to join another. type JoinCommand struct { - Ui cli.Ui + base.Command } func (c *JoinCommand) Help() string { @@ -20,26 +19,21 @@ Usage: consul join [options] address ... Tells a running Consul agent (with "consul agent") to join the cluster by specifying at least one existing member. -Options: +` + c.Command.Help() - -rpc-addr=127.0.0.1:8400 RPC address of the Consul agent. - -wan Joins a server to another server in the WAN pool -` return strings.TrimSpace(helpText) } func (c *JoinCommand) Run(args []string) int { var wan bool - cmdFlags := flag.NewFlagSet("join", flag.ContinueOnError) - cmdFlags.Usage = func() { c.Ui.Output(c.Help()) } - cmdFlags.BoolVar(&wan, "wan", false, "wan") - rpcAddr := RPCAddrFlag(cmdFlags) - if err := cmdFlags.Parse(args); err != nil { + f := c.Command.NewFlagSet(c) + f.BoolVar(&wan, "wan", false, "Joins a server to another server in the WAN pool.") + if err := c.Command.Parse(args); err != nil { return 1 } - addrs := cmdFlags.Args() + addrs := f.Args() if len(addrs) == 0 { c.Ui.Error("At least one address to join must be specified.") c.Ui.Error("") @@ -47,21 +41,24 @@ func (c *JoinCommand) Run(args []string) int { return 1 } - client, err := RPCClient(*rpcAddr) + client, err := c.Command.HTTPClient() if err != nil { c.Ui.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 } - defer client.Close() - n, err := client.Join(addrs, wan) - if err != nil { - c.Ui.Error(fmt.Sprintf("Error joining the cluster: %s", err)) - return 1 + joins := 0 + for _, addr := range addrs { + err := client.Agent().Join(addr, wan) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error joining address '%s': %s", addr, err)) + } else { + joins++ + } } c.Ui.Output(fmt.Sprintf( - "Successfully joined cluster by contacting %d nodes.", n)) + "Successfully joined cluster by contacting %d nodes.", joins)) return 0 } diff --git a/command/join_test.go b/command/join_test.go index bbbf2023ac..5f6e26798e 100644 --- a/command/join_test.go +++ b/command/join_test.go @@ -2,11 +2,22 @@ package command import ( "fmt" + "github.com/hashicorp/consul/command/base" "github.com/mitchellh/cli" "strings" "testing" ) +func testJoinCommand(t *testing.T) (*cli.MockUi, *JoinCommand) { + ui := new(cli.MockUi) + return ui, &JoinCommand{ + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetClientHTTP, + }, + } +} + func TestJoinCommand_implements(t *testing.T) { var _ cli.Command = &JoinCommand{} } @@ -17,10 +28,9 @@ func TestJoinCommandRun(t *testing.T) { defer a1.Shutdown() defer a2.Shutdown() - ui := new(cli.MockUi) - c := &JoinCommand{Ui: ui} + ui, c := testJoinCommand(t) args := []string{ - "-rpc-addr=" + a1.addr, + "-http-addr=" + a1.httpAddr, fmt.Sprintf("127.0.0.1:%d", a2.config.Ports.SerfLan), } @@ -40,10 +50,9 @@ func TestJoinCommandRun_wan(t *testing.T) { defer a1.Shutdown() defer a2.Shutdown() - ui := new(cli.MockUi) - c := &JoinCommand{Ui: ui} + ui, c := testJoinCommand(t) args := []string{ - "-rpc-addr=" + a1.addr, + "-http-addr=" + a1.httpAddr, "-wan", fmt.Sprintf("127.0.0.1:%d", a2.config.Ports.SerfWan), } @@ -59,9 +68,8 @@ func TestJoinCommandRun_wan(t *testing.T) { } func TestJoinCommandRun_noAddrs(t *testing.T) { - ui := new(cli.MockUi) - c := &JoinCommand{Ui: ui} - args := []string{"-rpc-addr=foo"} + ui, c := testJoinCommand(t) + args := []string{"-http-addr=foo"} code := c.Run(args) if code != 1 { diff --git a/commands.go b/commands.go index c8a85b30c1..36a96c6c18 100644 --- a/commands.go +++ b/commands.go @@ -76,6 +76,15 @@ func init() { }, nil }, + "join": func() (cli.Command, error) { + return &command.JoinCommand{ + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetClientHTTP, + }, + }, nil + }, + "kv": func() (cli.Command, error) { return &command.KVCommand{ Ui: ui, @@ -112,12 +121,6 @@ func init() { }, nil }, - "join": func() (cli.Command, error) { - return &command.JoinCommand{ - Ui: ui, - }, nil - }, - "keygen": func() (cli.Command, error) { return &command.KeygenCommand{ Ui: ui, diff --git a/website/source/docs/commands/join.html.markdown b/website/source/docs/commands/join.html.markdown.erb similarity index 81% rename from website/source/docs/commands/join.html.markdown rename to website/source/docs/commands/join.html.markdown.erb index cd9d624fcf..298ab5114e 100644 --- a/website/source/docs/commands/join.html.markdown +++ b/website/source/docs/commands/join.html.markdown.erb @@ -31,14 +31,14 @@ You may call join with multiple addresses if you want to try to join multiple clusters. Consul will attempt to join all addresses, and the join command will fail only if Consul was unable to join with any. -The command-line flags are all optional. The list of available flags are: +#### API Options + +<%= partial "docs/commands/http_api_options_client" %> + +#### Command Options * `-wan` - For agents running in server mode, the agent will attempt to join other servers gossiping in a WAN cluster. This is used to form a bridge between multiple datacenters. -* `-rpc-addr` - Address to the RPC server of the agent you want to contact - to send this command. If this isn't specified, the command checks the - CONSUL_RPC_ADDR env variable. If this isn't set, the default RPC - address will be set to "127.0.0.1:8400". From d67151908d0fe64678d7ba95e5011163be752f72 Mon Sep 17 00:00:00 2001 From: Kyle Havlovitz Date: Wed, 8 Feb 2017 17:19:17 -0500 Subject: [PATCH 5/9] Convert keygen command to use base.Command --- command/keygen.go | 15 +++++++++++---- command/keygen_test.go | 8 +++++++- commands.go | 15 +++++++++------ 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/command/keygen.go b/command/keygen.go index f0f9d70c26..1fcfab168f 100644 --- a/command/keygen.go +++ b/command/keygen.go @@ -6,16 +6,21 @@ import ( "fmt" "strings" - "github.com/mitchellh/cli" + "github.com/hashicorp/consul/command/base" ) // KeygenCommand is a Command implementation that generates an encryption // key for use in `consul agent`. type KeygenCommand struct { - Ui cli.Ui + base.Command } -func (c *KeygenCommand) Run(_ []string) int { +func (c *KeygenCommand) Run(args []string) int { + c.Command.NewFlagSet(c) + if err := c.Command.Parse(args); err != nil { + return 1 + } + key := make([]byte, 16) n, err := rand.Reader.Read(key) if err != nil { @@ -42,6 +47,8 @@ Usage: consul keygen Generates a new encryption key that can be used to configure the agent to encrypt traffic. The output of this command is already in the proper format that the agent expects. -` + +` + c.Command.Help() + return strings.TrimSpace(helpText) } diff --git a/command/keygen_test.go b/command/keygen_test.go index f94392538d..481a568e12 100644 --- a/command/keygen_test.go +++ b/command/keygen_test.go @@ -2,6 +2,7 @@ package command import ( "encoding/base64" + "github.com/hashicorp/consul/command/base" "github.com/mitchellh/cli" "testing" ) @@ -12,7 +13,12 @@ func TestKeygenCommand_implements(t *testing.T) { func TestKeygenCommand(t *testing.T) { ui := new(cli.MockUi) - c := &KeygenCommand{Ui: ui} + c := &KeygenCommand{ + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetNone, + }, + } code := c.Run(nil) if code != 0 { t.Fatalf("bad: %d", code) diff --git a/commands.go b/commands.go index 36a96c6c18..c3a21e9905 100644 --- a/commands.go +++ b/commands.go @@ -85,6 +85,15 @@ func init() { }, nil }, + "keygen": func() (cli.Command, error) { + return &command.KeygenCommand{ + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetNone, + }, + }, nil + }, + "kv": func() (cli.Command, error) { return &command.KVCommand{ Ui: ui, @@ -121,12 +130,6 @@ func init() { }, nil }, - "keygen": func() (cli.Command, error) { - return &command.KeygenCommand{ - Ui: ui, - }, nil - }, - "keyring": func() (cli.Command, error) { return &command.KeyringCommand{ Ui: ui, From abdf1fbab389a1e373b3620afd75adbf3ba64351 Mon Sep 17 00:00:00 2001 From: Kyle Havlovitz Date: Wed, 8 Feb 2017 18:25:47 -0500 Subject: [PATCH 6/9] Convert keyring command to use base.Command --- api/api.go | 16 ++ command/agent/operator_endpoint.go | 9 +- command/keyring.go | 144 +++++------------- command/keyring_test.go | 54 ++++--- commands.go | 15 +- ...tml.markdown => keyring.html.markdown.erb} | 12 +- 6 files changed, 107 insertions(+), 143 deletions(-) rename website/source/docs/commands/{keyring.html.markdown => keyring.html.markdown.erb} (91%) diff --git a/api/api.go b/api/api.go index 9a59b724cb..5ac7225981 100644 --- a/api/api.go +++ b/api/api.go @@ -79,6 +79,11 @@ type QueryOptions struct { // metadata key/value pairs. Currently, only one key/value pair can // be provided for filtering. NodeMeta map[string]string + + // RelayFactor is used in keyring operations to cause reponses to be + // relayed back to the sender through N other random nodes. Must be + // a value from 0 to 5 (inclusive). + RelayFactor uint8 } // WriteOptions are used to parameterize a write @@ -90,6 +95,11 @@ type WriteOptions struct { // Token is used to provide a per-request ACL token // which overrides the agent's default token. Token string + + // RelayFactor is used in keyring operations to cause reponses to be + // relayed back to the sender through N other random nodes. Must be + // a value from 0 to 5 (inclusive). + RelayFactor uint8 } // QueryMeta is used to return meta data about a query @@ -396,6 +406,9 @@ func (r *request) setQueryOptions(q *QueryOptions) { r.params.Add("node-meta", key+":"+value) } } + if q.RelayFactor != 0 { + r.params.Set("relay-factor", strconv.Itoa(int(q.RelayFactor))) + } } // durToMsec converts a duration to a millisecond specified string. If the @@ -437,6 +450,9 @@ func (r *request) setWriteOptions(q *WriteOptions) { if q.Token != "" { r.header.Set("X-Consul-Token", q.Token) } + if q.RelayFactor != 0 { + r.header.Set("relay-factor", strconv.Itoa(int(q.RelayFactor))) + } } // toHTTP converts the request to an HTTP request diff --git a/command/agent/operator_endpoint.go b/command/agent/operator_endpoint.go index c45f89aa5f..e7e42bb7ad 100644 --- a/command/agent/operator_endpoint.go +++ b/command/agent/operator_endpoint.go @@ -154,7 +154,14 @@ func keyringErrorsOrNil(responses []*structs.KeyringResponse) error { var errs error for _, response := range responses { if response.Error != "" { - errs = multierror.Append(errs, fmt.Errorf(response.Error)) + pool := response.Datacenter + " (LAN)" + if response.WAN { + pool = "WAN" + } + errs = multierror.Append(errs, fmt.Errorf("%s error: %s", pool, response.Error)) + for key, message := range response.Messages { + errs = multierror.Append(errs, fmt.Errorf("%s: %s", key, message)) + } } } return errs diff --git a/command/keyring.go b/command/keyring.go index 863326f99e..fd1ea16451 100644 --- a/command/keyring.go +++ b/command/keyring.go @@ -1,37 +1,46 @@ package command import ( - "flag" "fmt" "strings" + consulapi "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/command/agent" + "github.com/hashicorp/consul/command/base" "github.com/mitchellh/cli" ) // KeyringCommand is a Command implementation that handles querying, installing, // and removing gossip encryption keys from a keyring. type KeyringCommand struct { - Ui cli.Ui + base.Command } func (c *KeyringCommand) Run(args []string) int { - var installKey, useKey, removeKey, token string + var installKey, useKey, removeKey string var listKeys bool var relay int - cmdFlags := flag.NewFlagSet("keys", flag.ContinueOnError) - cmdFlags.Usage = func() { c.Ui.Output(c.Help()) } + f := c.Command.NewFlagSet(c) - cmdFlags.StringVar(&installKey, "install", "", "install key") - cmdFlags.StringVar(&useKey, "use", "", "use key") - cmdFlags.StringVar(&removeKey, "remove", "", "remove key") - cmdFlags.BoolVar(&listKeys, "list", false, "list keys") - cmdFlags.StringVar(&token, "token", "", "acl token") - cmdFlags.IntVar(&relay, "relay-factor", 0, "relay factor") + f.StringVar(&installKey, "install", "", + "Install a new encryption key. This will broadcast the new key to "+ + "all members in the cluster.") + f.StringVar(&useKey, "use", "", + "Change the primary encryption key, which is used to encrypt "+ + "messages. The key must already be installed before this operation "+ + "can succeed.") + f.StringVar(&removeKey, "remove", "", + "Remove the given key from the cluster. This operation may only be "+ + "performed on keys which are not currently the primary key.") + f.BoolVar(&listKeys, "list", false, + "List all keys currently in use within the cluster.") + f.IntVar(&relay, "relay-factor", 0, + "Added in Consul 0.7.4, setting this to a non-zero value will cause nodes "+ + "to relay their response to the operation through this many randomly-chosen "+ + "other nodes in the cluster. The maximum allowed value is 5.") - rpcAddr := RPCAddrFlag(cmdFlags) - if err := cmdFlags.Parse(args); err != nil { + if err := c.Command.Parse(args); err != nil { return 1 } @@ -66,124 +75,69 @@ func (c *KeyringCommand) Run(args []string) int { } // All other operations will require a client connection - client, err := RPCClient(*rpcAddr) + client, err := c.Command.HTTPClient() if err != nil { c.Ui.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 } - defer client.Close() if listKeys { c.Ui.Info("Gathering installed encryption keys...") - r, err := client.ListKeys(token, relayFactor) + responses, err := client.Operator().KeyringList(&consulapi.QueryOptions{RelayFactor: relayFactor}) if err != nil { c.Ui.Error(fmt.Sprintf("error: %s", err)) return 1 } - if rval := c.handleResponse(r.Info, r.Messages); rval != 0 { - return rval - } - c.handleList(r.Info, r.Keys) + c.handleList(responses) return 0 } + opts := &consulapi.WriteOptions{RelayFactor: relayFactor} if installKey != "" { c.Ui.Info("Installing new gossip encryption key...") - r, err := client.InstallKey(installKey, token, relayFactor) + err := client.Operator().KeyringInstall(installKey, opts) if err != nil { c.Ui.Error(fmt.Sprintf("error: %s", err)) return 1 } - return c.handleResponse(r.Info, r.Messages) + return 0 } if useKey != "" { c.Ui.Info("Changing primary gossip encryption key...") - r, err := client.UseKey(useKey, token, relayFactor) + err := client.Operator().KeyringUse(useKey, opts) if err != nil { c.Ui.Error(fmt.Sprintf("error: %s", err)) return 1 } - return c.handleResponse(r.Info, r.Messages) + return 0 } if removeKey != "" { c.Ui.Info("Removing gossip encryption key...") - r, err := client.RemoveKey(removeKey, token, relayFactor) + err := client.Operator().KeyringRemove(removeKey, opts) if err != nil { c.Ui.Error(fmt.Sprintf("error: %s", err)) return 1 } - return c.handleResponse(r.Info, r.Messages) + return 0 } // Should never make it here return 0 } -func (c *KeyringCommand) handleResponse( - info []agent.KeyringInfo, - messages []agent.KeyringMessage) int { - - var rval int - - for _, i := range info { - if i.Error != "" { - pool := i.Pool - if pool != "WAN" { - pool = i.Datacenter + " (LAN)" - } - - c.Ui.Error("") - c.Ui.Error(fmt.Sprintf("%s error: %s", pool, i.Error)) - - for _, msg := range messages { - if msg.Datacenter != i.Datacenter || msg.Pool != i.Pool { - continue - } - c.Ui.Error(fmt.Sprintf(" %s: %s", msg.Node, msg.Message)) - } - rval = 1 - } - } - - if rval == 0 { - c.Ui.Info("Done!") - } - - return rval -} - -func (c *KeyringCommand) handleList( - info []agent.KeyringInfo, - keys []agent.KeyringEntry) { - - installed := make(map[string]map[string][]int) - for _, key := range keys { - var nodes int - for _, i := range info { - if i.Datacenter == key.Datacenter && i.Pool == key.Pool { - nodes = i.NumNodes - } +func (c *KeyringCommand) handleList(responses []*consulapi.KeyringResponse) { + for _, response := range responses { + pool := response.Datacenter + " (LAN)" + if response.WAN { + pool = "WAN" } - pool := key.Pool - if pool != "WAN" { - pool = key.Datacenter + " (LAN)" - } - - if _, ok := installed[pool]; !ok { - installed[pool] = map[string][]int{key.Key: []int{key.Count, nodes}} - } else { - installed[pool][key.Key] = []int{key.Count, nodes} - } - } - - for pool, keys := range installed { c.Ui.Output("") c.Ui.Output(pool + ":") - for key, num := range keys { - c.Ui.Output(fmt.Sprintf(" %s [%d/%d]", key, num[0], num[1])) + for key, num := range response.Keys { + c.Ui.Output(fmt.Sprintf(" %s [%d/%d]", key, num, response.NumNodes)) } } } @@ -205,26 +159,8 @@ Usage: consul keyring [options] are no errors. If any node fails to reply or reports failure, the exit code will be 1. -Options: +` + c.Command.Help() - -install= Install a new encryption key. This will broadcast - the new key to all members in the cluster. - -list List all keys currently in use within the cluster. - -remove= Remove the given key from the cluster. This - operation may only be performed on keys which are - not currently the primary key. - -token="" ACL token to use during requests. Defaults to that - of the agent. - -relay-factor Added in Consul 0.7.4, setting this to a non-zero - value will cause nodes to relay their response to - the operation through this many randomly-chosen - other nodes in the cluster. The maximum allowed - value is 5. - -use= Change the primary encryption key, which is used to - encrypt messages. The key must already be installed - before this operation can succeed. - -rpc-addr=127.0.0.1:8400 RPC address of the Consul agent. -` return strings.TrimSpace(helpText) } diff --git a/command/keyring_test.go b/command/keyring_test.go index 77027c47fc..d2c19e9686 100644 --- a/command/keyring_test.go +++ b/command/keyring_test.go @@ -5,9 +5,20 @@ import ( "testing" "github.com/hashicorp/consul/command/agent" + "github.com/hashicorp/consul/command/base" "github.com/mitchellh/cli" ) +func testKeyringCommand(t *testing.T) (*cli.MockUi, *KeyringCommand) { + ui := new(cli.MockUi) + return ui, &KeyringCommand{ + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetClientHTTP, + }, + } +} + func TestKeyringCommand_implements(t *testing.T) { var _ cli.Command = &KeyringCommand{} } @@ -23,7 +34,7 @@ func TestKeyringCommandRun(t *testing.T) { defer a1.Shutdown() // The LAN and WAN keyrings were initialized with key1 - out := listKeys(t, a1.addr) + out := listKeys(t, a1.httpAddr) if !strings.Contains(out, "dc1 (LAN):\n "+key1) { t.Fatalf("bad: %#v", out) } @@ -35,10 +46,10 @@ func TestKeyringCommandRun(t *testing.T) { } // Install the second key onto the keyring - installKey(t, a1.addr, key2) + installKey(t, a1.httpAddr, key2) // Both keys should be present - out = listKeys(t, a1.addr) + out = listKeys(t, a1.httpAddr) for _, key := range []string{key1, key2} { if !strings.Contains(out, key) { t.Fatalf("bad: %#v", out) @@ -46,11 +57,11 @@ func TestKeyringCommandRun(t *testing.T) { } // Rotate to key2, remove key1 - useKey(t, a1.addr, key2) - removeKey(t, a1.addr, key1) + useKey(t, a1.httpAddr, key2) + removeKey(t, a1.httpAddr, key1) // Only key2 is present now - out = listKeys(t, a1.addr) + out = listKeys(t, a1.httpAddr) if !strings.Contains(out, "dc1 (LAN):\n "+key2) { t.Fatalf("bad: %#v", out) } @@ -63,8 +74,7 @@ func TestKeyringCommandRun(t *testing.T) { } func TestKeyringCommandRun_help(t *testing.T) { - ui := new(cli.MockUi) - c := &KeyringCommand{Ui: ui} + ui, c := testKeyringCommand(t) code := c.Run(nil) if code != 1 { t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) @@ -77,9 +87,8 @@ func TestKeyringCommandRun_help(t *testing.T) { } func TestKeyringCommandRun_failedConnection(t *testing.T) { - ui := new(cli.MockUi) - c := &KeyringCommand{Ui: ui} - args := []string{"-list", "-rpc-addr=127.0.0.1:0"} + ui, c := testKeyringCommand(t) + args := []string{"-list", "-http-addr=127.0.0.1:0"} code := c.Run(args) if code != 1 { t.Fatalf("bad: %d, %#v", code, ui.ErrorWriter.String()) @@ -90,8 +99,7 @@ func TestKeyringCommandRun_failedConnection(t *testing.T) { } func TestKeyringCommandRun_invalidRelayFactor(t *testing.T) { - ui := new(cli.MockUi) - c := &KeyringCommand{Ui: ui} + ui, c := testKeyringCommand(t) args := []string{"-list", "-relay-factor=6"} code := c.Run(args) @@ -101,10 +109,9 @@ func TestKeyringCommandRun_invalidRelayFactor(t *testing.T) { } func listKeys(t *testing.T, addr string) string { - ui := new(cli.MockUi) - c := &KeyringCommand{Ui: ui} + ui, c := testKeyringCommand(t) - args := []string{"-list", "-rpc-addr=" + addr} + args := []string{"-list", "-http-addr=" + addr} code := c.Run(args) if code != 0 { t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) @@ -114,10 +121,9 @@ func listKeys(t *testing.T, addr string) string { } func installKey(t *testing.T, addr string, key string) { - ui := new(cli.MockUi) - c := &KeyringCommand{Ui: ui} + ui, c := testKeyringCommand(t) - args := []string{"-install=" + key, "-rpc-addr=" + addr} + args := []string{"-install=" + key, "-http-addr=" + addr} code := c.Run(args) if code != 0 { t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) @@ -125,10 +131,9 @@ func installKey(t *testing.T, addr string, key string) { } func useKey(t *testing.T, addr string, key string) { - ui := new(cli.MockUi) - c := &KeyringCommand{Ui: ui} + ui, c := testKeyringCommand(t) - args := []string{"-use=" + key, "-rpc-addr=" + addr} + args := []string{"-use=" + key, "-http-addr=" + addr} code := c.Run(args) if code != 0 { t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) @@ -136,10 +141,9 @@ func useKey(t *testing.T, addr string, key string) { } func removeKey(t *testing.T, addr string, key string) { - ui := new(cli.MockUi) - c := &KeyringCommand{Ui: ui} + ui, c := testKeyringCommand(t) - args := []string{"-remove=" + key, "-rpc-addr=" + addr} + args := []string{"-remove=" + key, "-http-addr=" + addr} code := c.Run(args) if code != 0 { t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) diff --git a/commands.go b/commands.go index c3a21e9905..5ad8f5f365 100644 --- a/commands.go +++ b/commands.go @@ -94,6 +94,15 @@ func init() { }, nil }, + "keyring": func() (cli.Command, error) { + return &command.KeyringCommand{ + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetClientHTTP, + }, + }, nil + }, + "kv": func() (cli.Command, error) { return &command.KVCommand{ Ui: ui, @@ -130,12 +139,6 @@ func init() { }, nil }, - "keyring": func() (cli.Command, error) { - return &command.KeyringCommand{ - Ui: ui, - }, nil - }, - "leave": func() (cli.Command, error) { return &command.LeaveCommand{ Ui: ui, diff --git a/website/source/docs/commands/keyring.html.markdown b/website/source/docs/commands/keyring.html.markdown.erb similarity index 91% rename from website/source/docs/commands/keyring.html.markdown rename to website/source/docs/commands/keyring.html.markdown.erb index b34c3dbfc7..871bb8a3ae 100644 --- a/website/source/docs/commands/keyring.html.markdown +++ b/website/source/docs/commands/keyring.html.markdown.erb @@ -33,7 +33,11 @@ Usage: `consul keyring [options]` Only one actionable argument may be specified per run, including `-list`, `-install`, `-remove`, and `-use`. -The list of available flags are: +#### API Options + +<%= partial "docs/commands/http_api_options_client" %> + +#### Command Options * `-list` - List all keys currently in use within the cluster. @@ -46,16 +50,10 @@ The list of available flags are: * `-remove` - Remove the given key from the cluster. This operation may only be performed on keys which are not currently the primary key. -* `-token=""` - ACL token to use during requests. Defaults to that of the agent. - * `-relay-factor` - Added in Consul 0.7.4, setting this to a non-zero value will cause nodes to relay their response to the operation through this many randomly-chosen other nodes in the cluster. The maximum allowed value is 5. -* `-rpc-addr` - Address to the RPC server of the agent you want to contact - to send this command. If this isn't specified, the command will contact - "127.0.0.1:8400" which is the default RPC address of a Consul agent. - ## Output The output of the `consul keyring -list` command consolidates information from From 14a7ffc0981c3f058d79ecea53f3c5fc401c10c8 Mon Sep 17 00:00:00 2001 From: Kyle Havlovitz Date: Wed, 8 Feb 2017 19:26:24 -0500 Subject: [PATCH 7/9] Convert kv commands to use base.Command --- command/base/command.go | 9 ++++ command/kv_command.go | 3 +- command/kv_delete.go | 56 ++++++++------------- command/kv_delete_test.go | 23 ++++++--- command/kv_export.go | 30 +++--------- command/kv_export_test.go | 8 ++- command/kv_get.go | 78 +++++++++++------------------ command/kv_get_test.go | 38 ++++++++------- command/kv_import.go | 32 +++--------- command/kv_import_test.go | 6 ++- command/kv_put.go | 100 ++++++++++++++------------------------ command/kv_put_test.go | 47 +++++++++--------- commands.go | 30 +++++++++--- 13 files changed, 205 insertions(+), 255 deletions(-) diff --git a/command/base/command.go b/command/base/command.go index 40c578eea1..2741ea2cba 100644 --- a/command/base/command.go +++ b/command/base/command.go @@ -62,6 +62,12 @@ func (c *Command) HTTPDatacenter() string { return c.datacenter.String() } +func (c *Command) HTTPStale() bool { + var stale bool + c.stale.Merge(&stale) + return stale +} + // httpFlagsClient is the list of flags that apply to HTTP connections. func (c *Command) httpFlagsClient(f *flag.FlagSet) *flag.FlagSet { if f == nil { @@ -134,6 +140,9 @@ func (c *Command) Parse(args []string) error { // Help returns the help for this flagSet. func (c *Command) Help() string { + if c.flagSet == nil { + return "" + } return c.helpFlagsFor(c.flagSet) } diff --git a/command/kv_command.go b/command/kv_command.go index 28bd71a942..5e6e0461c4 100644 --- a/command/kv_command.go +++ b/command/kv_command.go @@ -3,13 +3,14 @@ package command import ( "strings" + "github.com/hashicorp/consul/command/base" "github.com/mitchellh/cli" ) // KVCommand is a Command implementation that just shows help for // the subcommands nested below it. type KVCommand struct { - Ui cli.Ui + base.Command } func (c *KVCommand) Run(args []string) int { diff --git a/command/kv_delete.go b/command/kv_delete.go index 85b30b4c8f..839d2aa4b9 100644 --- a/command/kv_delete.go +++ b/command/kv_delete.go @@ -1,18 +1,17 @@ package command import ( - "flag" "fmt" "strings" "github.com/hashicorp/consul/api" - "github.com/mitchellh/cli" + "github.com/hashicorp/consul/command/base" ) // KVDeleteCommand is a Command implementation that is used to delete a key or // prefix of keys from the key-value store. type KVDeleteCommand struct { - Ui cli.Ui + base.Command } func (c *KVDeleteCommand) Help() string { @@ -33,40 +32,30 @@ Usage: consul kv delete [options] KEY_OR_PREFIX This will delete the keys named "foo", "food", and "foo/bar/zip" if they existed. -` + apiOptsText + ` +` + c.Command.Help() -KV Delete Options: - - -cas Perform a Check-And-Set operation. Specifying this - value also requires the -modify-index flag to be set. - The default value is false. - - -modify-index= Unsigned integer representing the ModifyIndex of the - key. This is used in combination with the -cas flag. - - -recurse Recursively delete all keys with the path. The default - value is false. -` return strings.TrimSpace(helpText) } func (c *KVDeleteCommand) Run(args []string) int { - cmdFlags := flag.NewFlagSet("get", flag.ContinueOnError) - cmdFlags.Usage = func() { c.Ui.Output(c.Help()) } - datacenter := cmdFlags.String("datacenter", "", "") - token := cmdFlags.String("token", "", "") - cas := cmdFlags.Bool("cas", false, "") - modifyIndex := cmdFlags.Uint64("modify-index", 0, "") - recurse := cmdFlags.Bool("recurse", false, "") - httpAddr := HTTPAddrFlag(cmdFlags) - if err := cmdFlags.Parse(args); err != nil { + f := c.Command.NewFlagSet(c) + cas := f.Bool("cas", false, + "Perform a Check-And-Set operation. Specifying this value also requires "+ + "the -modify-index flag to be set. The default value is false.") + modifyIndex := f.Uint64("modify-index", 0, + "Unsigned integer representing the ModifyIndex of the key. This is "+ + "used in combination with the -cas flag.") + recurse := f.Bool("recurse", false, + "Recursively delete all keys with the path. The default value is false.") + + if err := c.Command.Parse(args); err != nil { return 1 } key := "" // Check for arg validation - args = cmdFlags.Args() + args = f.Args() switch len(args) { case 0: key = "" @@ -109,22 +98,15 @@ func (c *KVDeleteCommand) Run(args []string) int { } // Create and test the HTTP client - conf := api.DefaultConfig() - conf.Address = *httpAddr - conf.Token = *token - client, err := api.NewClient(conf) + client, err := c.Command.HTTPClient() if err != nil { c.Ui.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 } - wo := &api.WriteOptions{ - Datacenter: *datacenter, - } - switch { case *recurse: - if _, err := client.KV().DeleteTree(key, wo); err != nil { + if _, err := client.KV().DeleteTree(key, nil); err != nil { c.Ui.Error(fmt.Sprintf("Error! Did not delete prefix %s: %s", key, err)) return 1 } @@ -137,7 +119,7 @@ func (c *KVDeleteCommand) Run(args []string) int { ModifyIndex: *modifyIndex, } - success, _, err := client.KV().DeleteCAS(pair, wo) + success, _, err := client.KV().DeleteCAS(pair, nil) if err != nil { c.Ui.Error(fmt.Sprintf("Error! Did not delete key %s: %s", key, err)) return 1 @@ -150,7 +132,7 @@ func (c *KVDeleteCommand) Run(args []string) int { c.Ui.Info(fmt.Sprintf("Success! Deleted key: %s", key)) return 0 default: - if _, err := client.KV().Delete(key, wo); err != nil { + if _, err := client.KV().Delete(key, nil); err != nil { c.Ui.Error(fmt.Sprintf("Error deleting key %s: %s", key, err)) return 1 } diff --git a/command/kv_delete_test.go b/command/kv_delete_test.go index ac8572aadd..518492daa9 100644 --- a/command/kv_delete_test.go +++ b/command/kv_delete_test.go @@ -6,9 +6,20 @@ import ( "testing" "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/command/base" "github.com/mitchellh/cli" ) +func testKVDeleteCommand(t *testing.T) (*cli.MockUi, *KVDeleteCommand) { + ui := new(cli.MockUi) + return ui, &KVDeleteCommand{ + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetHTTP, + }, + } +} + func TestKVDeleteCommand_implements(t *testing.T) { var _ cli.Command = &KVDeleteCommand{} } @@ -18,8 +29,7 @@ func TestKVDeleteCommand_noTabs(t *testing.T) { } func TestKVDeleteCommand_Validation(t *testing.T) { - ui := new(cli.MockUi) - c := &KVDeleteCommand{Ui: ui} + ui, c := testKVDeleteCommand(t) cases := map[string]struct { args []string @@ -73,8 +83,7 @@ func TestKVDeleteCommand_Run(t *testing.T) { defer srv.Shutdown() waitForLeader(t, srv.httpAddr) - ui := new(cli.MockUi) - c := &KVDeleteCommand{Ui: ui} + ui, c := testKVDeleteCommand(t) pair := &api.KVPair{ Key: "foo", @@ -109,8 +118,7 @@ func TestKVDeleteCommand_Recurse(t *testing.T) { defer srv.Shutdown() waitForLeader(t, srv.httpAddr) - ui := new(cli.MockUi) - c := &KVDeleteCommand{Ui: ui} + ui, c := testKVDeleteCommand(t) keys := []string{"foo/a", "foo/b", "food"} @@ -152,8 +160,7 @@ func TestKVDeleteCommand_CAS(t *testing.T) { defer srv.Shutdown() waitForLeader(t, srv.httpAddr) - ui := new(cli.MockUi) - c := &KVDeleteCommand{Ui: ui} + ui, c := testKVDeleteCommand(t) pair := &api.KVPair{ Key: "foo", diff --git a/command/kv_export.go b/command/kv_export.go index 77d7f4d463..bb1482d263 100644 --- a/command/kv_export.go +++ b/command/kv_export.go @@ -3,18 +3,17 @@ package command import ( "encoding/base64" "encoding/json" - "flag" "fmt" "strings" "github.com/hashicorp/consul/api" - "github.com/mitchellh/cli" + "github.com/hashicorp/consul/command/base" ) // KVExportCommand is a Command implementation that is used to export // a KV tree as JSON type KVExportCommand struct { - Ui cli.Ui + base.Command } func (c *KVExportCommand) Synopsis() string { @@ -33,29 +32,20 @@ Usage: consul kv export [KEY_OR_PREFIX] For a full list of options and examples, please see the Consul documentation. -` + apiOptsText + ` +` + c.Command.Help() -KV Export Options: - - None. -` return strings.TrimSpace(helpText) } func (c *KVExportCommand) Run(args []string) int { - cmdFlags := flag.NewFlagSet("export", flag.ContinueOnError) - - datacenter := cmdFlags.String("datacenter", "", "") - token := cmdFlags.String("token", "", "") - stale := cmdFlags.Bool("stale", false, "") - httpAddr := HTTPAddrFlag(cmdFlags) - if err := cmdFlags.Parse(args); err != nil { + f := c.Command.NewFlagSet(c) + if err := c.Command.Parse(args); err != nil { return 1 } key := "" // Check for arg validation - args = cmdFlags.Args() + args = f.Args() switch len(args) { case 0: key = "" @@ -74,18 +64,14 @@ func (c *KVExportCommand) Run(args []string) int { } // Create and test the HTTP client - conf := api.DefaultConfig() - conf.Address = *httpAddr - conf.Token = *token - client, err := api.NewClient(conf) + client, err := c.Command.HTTPClient() if err != nil { c.Ui.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 } pairs, _, err := client.KV().List(key, &api.QueryOptions{ - Datacenter: *datacenter, - AllowStale: *stale, + AllowStale: c.Command.HTTPStale(), }) if err != nil { c.Ui.Error(fmt.Sprintf("Error querying Consul agent: %s", err)) diff --git a/command/kv_export_test.go b/command/kv_export_test.go index 9fa64982d9..51dbda8227 100644 --- a/command/kv_export_test.go +++ b/command/kv_export_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/command/base" "github.com/mitchellh/cli" ) @@ -15,7 +16,12 @@ func TestKVExportCommand_Run(t *testing.T) { waitForLeader(t, srv.httpAddr) ui := new(cli.MockUi) - c := &KVExportCommand{Ui: ui} + c := KVExportCommand{ + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetHTTP, + }, + } keys := map[string]string{ "foo/a": "a", diff --git a/command/kv_get.go b/command/kv_get.go index c49d983b48..dc509b243f 100644 --- a/command/kv_get.go +++ b/command/kv_get.go @@ -3,20 +3,19 @@ package command import ( "bytes" "encoding/base64" - "flag" "fmt" "io" "strings" "text/tabwriter" "github.com/hashicorp/consul/api" - "github.com/mitchellh/cli" + "github.com/hashicorp/consul/command/base" ) // KVGetCommand is a Command implementation that is used to fetch the value of // a key from the key-value store. type KVGetCommand struct { - Ui cli.Ui + base.Command } func (c *KVGetCommand) Help() string { @@ -51,54 +50,39 @@ Usage: consul kv get [options] [KEY_OR_PREFIX] For a full list of options and examples, please see the Consul documentation. -` + apiOptsText + ` +` + c.Command.Help() -KV Get Options: - - -base64 Base64 encode the value. The default value is false. - - -detailed Provide additional metadata about the key in addition - to the value such as the ModifyIndex and any flags - that may have been set on the key. The default value - is false. - - -keys List keys which start with the given prefix, but not - their values. This is especially useful if you only - need the key names themselves. This option is commonly - combined with the -separator option. The default value - is false. - - -recurse Recursively look at all keys prefixed with the given - path. The default value is false. - - -separator= String to use as a separator between keys. The default - value is "/", but this option is only taken into - account when paired with the -keys flag. - -` return strings.TrimSpace(helpText) } func (c *KVGetCommand) Run(args []string) int { - cmdFlags := flag.NewFlagSet("get", flag.ContinueOnError) - cmdFlags.Usage = func() { c.Ui.Output(c.Help()) } - datacenter := cmdFlags.String("datacenter", "", "") - token := cmdFlags.String("token", "", "") - stale := cmdFlags.Bool("stale", false, "") - detailed := cmdFlags.Bool("detailed", false, "") - keys := cmdFlags.Bool("keys", false, "") - base64encode := cmdFlags.Bool("base64", false, "") - recurse := cmdFlags.Bool("recurse", false, "") - separator := cmdFlags.String("separator", "/", "") - httpAddr := HTTPAddrFlag(cmdFlags) - if err := cmdFlags.Parse(args); err != nil { + f := c.Command.NewFlagSet(c) + detailed := f.Bool("detailed", false, + "Provide additional metadata about the key in addition to the value such "+ + "as the ModifyIndex and any flags that may have been set on the key. "+ + "The default value is false.") + keys := f.Bool("keys", false, + "List keys which start with the given prefix, but not their values. "+ + "This is especially useful if you only need the key names themselves. "+ + "This option is commonly combined with the -separator option. The default "+ + "value is false.") + base64encode := f.Bool("base64", false, + "Base64 encode the value. The default value is false.") + recurse := f.Bool("recurse", false, + "Recursively look at all keys prefixed with the given path. The default "+ + "value is false.") + separator := f.String("separator", "/", + "String to use as a separator between keys. The default value is \"/\", "+ + "but this option is only taken into account when paired with the -keys flag.") + + if err := c.Command.Parse(args); err != nil { return 1 } key := "" // Check for arg validation - args = cmdFlags.Args() + args = f.Args() switch len(args) { case 0: key = "" @@ -124,10 +108,7 @@ func (c *KVGetCommand) Run(args []string) int { } // Create and test the HTTP client - conf := api.DefaultConfig() - conf.Address = *httpAddr - conf.Token = *token - client, err := api.NewClient(conf) + client, err := c.Command.HTTPClient() if err != nil { c.Ui.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 @@ -136,8 +117,7 @@ func (c *KVGetCommand) Run(args []string) int { switch { case *keys: keys, _, err := client.KV().Keys(key, *separator, &api.QueryOptions{ - Datacenter: *datacenter, - AllowStale: *stale, + AllowStale: c.Command.HTTPStale(), }) if err != nil { c.Ui.Error(fmt.Sprintf("Error querying Consul agent: %s", err)) @@ -151,8 +131,7 @@ func (c *KVGetCommand) Run(args []string) int { return 0 case *recurse: pairs, _, err := client.KV().List(key, &api.QueryOptions{ - Datacenter: *datacenter, - AllowStale: *stale, + AllowStale: c.Command.HTTPStale(), }) if err != nil { c.Ui.Error(fmt.Sprintf("Error querying Consul agent: %s", err)) @@ -184,8 +163,7 @@ func (c *KVGetCommand) Run(args []string) int { return 0 default: pair, _, err := client.KV().Get(key, &api.QueryOptions{ - Datacenter: *datacenter, - AllowStale: *stale, + AllowStale: c.Command.HTTPStale(), }) if err != nil { c.Ui.Error(fmt.Sprintf("Error querying Consul agent: %s", err)) diff --git a/command/kv_get_test.go b/command/kv_get_test.go index 979df1adc6..773edd95a4 100644 --- a/command/kv_get_test.go +++ b/command/kv_get_test.go @@ -6,9 +6,20 @@ import ( "testing" "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/command/base" "github.com/mitchellh/cli" ) +func testKVGetCommand(t *testing.T) (*cli.MockUi, *KVGetCommand) { + ui := new(cli.MockUi) + return ui, &KVGetCommand{ + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetHTTP, + }, + } +} + func TestKVGetCommand_implements(t *testing.T) { var _ cli.Command = &KVGetCommand{} } @@ -18,8 +29,7 @@ func TestKVGetCommand_noTabs(t *testing.T) { } func TestKVGetCommand_Validation(t *testing.T) { - ui := new(cli.MockUi) - c := &KVGetCommand{Ui: ui} + ui, c := testKVGetCommand(t) cases := map[string]struct { args []string @@ -61,8 +71,7 @@ func TestKVGetCommand_Run(t *testing.T) { defer srv.Shutdown() waitForLeader(t, srv.httpAddr) - ui := new(cli.MockUi) - c := &KVGetCommand{Ui: ui} + ui, c := testKVGetCommand(t) pair := &api.KVPair{ Key: "foo", @@ -94,8 +103,7 @@ func TestKVGetCommand_Missing(t *testing.T) { defer srv.Shutdown() waitForLeader(t, srv.httpAddr) - ui := new(cli.MockUi) - c := &KVGetCommand{Ui: ui} + _, c := testKVGetCommand(t) args := []string{ "-http-addr=" + srv.httpAddr, @@ -113,8 +121,7 @@ func TestKVGetCommand_Empty(t *testing.T) { defer srv.Shutdown() waitForLeader(t, srv.httpAddr) - ui := new(cli.MockUi) - c := &KVGetCommand{Ui: ui} + ui, c := testKVGetCommand(t) pair := &api.KVPair{ Key: "empty", @@ -141,8 +148,7 @@ func TestKVGetCommand_Detailed(t *testing.T) { defer srv.Shutdown() waitForLeader(t, srv.httpAddr) - ui := new(cli.MockUi) - c := &KVGetCommand{Ui: ui} + ui, c := testKVGetCommand(t) pair := &api.KVPair{ Key: "foo", @@ -184,8 +190,7 @@ func TestKVGetCommand_Keys(t *testing.T) { defer srv.Shutdown() waitForLeader(t, srv.httpAddr) - ui := new(cli.MockUi) - c := &KVGetCommand{Ui: ui} + ui, c := testKVGetCommand(t) keys := []string{"foo/bar", "foo/baz", "foo/zip"} for _, key := range keys { @@ -218,8 +223,7 @@ func TestKVGetCommand_Recurse(t *testing.T) { defer srv.Shutdown() waitForLeader(t, srv.httpAddr) - ui := new(cli.MockUi) - c := &KVGetCommand{Ui: ui} + ui, c := testKVGetCommand(t) keys := map[string]string{ "foo/a": "a", @@ -257,8 +261,7 @@ func TestKVGetCommand_RecurseBase64(t *testing.T) { defer srv.Shutdown() waitForLeader(t, srv.httpAddr) - ui := new(cli.MockUi) - c := &KVGetCommand{Ui: ui} + ui, c := testKVGetCommand(t) keys := map[string]string{ "foo/a": "Hello World 1", @@ -297,8 +300,7 @@ func TestKVGetCommand_DetailedBase64(t *testing.T) { defer srv.Shutdown() waitForLeader(t, srv.httpAddr) - ui := new(cli.MockUi) - c := &KVGetCommand{Ui: ui} + ui, c := testKVGetCommand(t) pair := &api.KVPair{ Key: "foo", diff --git a/command/kv_import.go b/command/kv_import.go index c236067444..cf8986bef0 100644 --- a/command/kv_import.go +++ b/command/kv_import.go @@ -5,7 +5,6 @@ import ( "encoding/base64" "encoding/json" "errors" - "flag" "fmt" "io" "io/ioutil" @@ -13,13 +12,13 @@ import ( "strings" "github.com/hashicorp/consul/api" - "github.com/mitchellh/cli" + "github.com/hashicorp/consul/command/base" ) // KVImportCommand is a Command implementation that is used to import // a KV tree stored as JSON type KVImportCommand struct { - Ui cli.Ui + base.Command // testStdin is the input for testing. testStdin io.Reader @@ -50,27 +49,20 @@ Usage: consul kv import [DATA] For a full list of options and examples, please see the Consul documentation. -` + apiOptsText + ` +` + c.Command.Help() -KV Import Options: - - None. -` return strings.TrimSpace(helpText) } func (c *KVImportCommand) Run(args []string) int { - cmdFlags := flag.NewFlagSet("import", flag.ContinueOnError) + f := c.Command.NewFlagSet(c) - datacenter := cmdFlags.String("datacenter", "", "") - token := cmdFlags.String("token", "", "") - httpAddr := HTTPAddrFlag(cmdFlags) - if err := cmdFlags.Parse(args); err != nil { + if err := c.Command.Parse(args); err != nil { return 1 } // Check for arg validation - args = cmdFlags.Args() + args = f.Args() data, err := c.dataFromArgs(args) if err != nil { c.Ui.Error(fmt.Sprintf("Error! %s", err)) @@ -78,10 +70,7 @@ func (c *KVImportCommand) Run(args []string) int { } // Create and test the HTTP client - conf := api.DefaultConfig() - conf.Address = *httpAddr - conf.Token = *token - client, err := api.NewClient(conf) + client, err := c.Command.HTTPClient() if err != nil { c.Ui.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 @@ -106,12 +95,7 @@ func (c *KVImportCommand) Run(args []string) int { Value: value, } - wo := &api.WriteOptions{ - Datacenter: *datacenter, - Token: *token, - } - - if _, err := client.KV().Put(pair, wo); err != nil { + if _, err := client.KV().Put(pair, nil); err != nil { c.Ui.Error(fmt.Sprintf("Error! Failed writing data for key %s: %s", pair.Key, err)) return 1 } diff --git a/command/kv_import_test.go b/command/kv_import_test.go index 817cd788fd..9f8326ad3e 100644 --- a/command/kv_import_test.go +++ b/command/kv_import_test.go @@ -4,6 +4,7 @@ import ( "strings" "testing" + "github.com/hashicorp/consul/command/base" "github.com/mitchellh/cli" ) @@ -27,7 +28,10 @@ func TestKVImportCommand_Run(t *testing.T) { ui := new(cli.MockUi) c := &KVImportCommand{ - Ui: ui, + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetHTTP, + }, testStdin: strings.NewReader(json), } diff --git a/command/kv_put.go b/command/kv_put.go index 5e5b333b19..7bbb56c257 100644 --- a/command/kv_put.go +++ b/command/kv_put.go @@ -3,7 +3,6 @@ package command import ( "bytes" "encoding/base64" - "flag" "fmt" "io" "io/ioutil" @@ -11,13 +10,13 @@ import ( "strings" "github.com/hashicorp/consul/api" - "github.com/mitchellh/cli" + "github.com/hashicorp/consul/command/base" ) // KVPutCommand is a Command implementation that is used to write data to the // key-value store. type KVPutCommand struct { - Ui cli.Ui + base.Command // testStdin is the input for testing. testStdin io.Reader @@ -57,62 +56,45 @@ Usage: consul kv put [options] KEY [DATA] Additional flags and more advanced use cases are detailed below. -` + apiOptsText + ` +` + c.Command.Help() -KV Put Options: - - -acquire Obtain a lock on the key. If the key does not exist, - this operation will create the key and obtain the - lock. The session must already exist and be specified - via the -session flag. The default value is false. - - -base64 Treat the data as base 64 encoded. The default value - is false. - - -cas Perform a Check-And-Set operation. Specifying this - value also requires the -modify-index flag to be set. - The default value is false. - - -flags= Unsigned integer value to assign to this key-value - pair. This value is not read by Consul, so clients can - use this value however makes sense for their use case. - The default value is 0 (no flags). - - -modify-index= Unsigned integer representing the ModifyIndex of the - key. This is used in combination with the -cas flag. - - -release Forfeit the lock on the key at the given path. This - requires the -session flag to be set. The key must be - held by the session in order to be unlocked. The - default value is false. - - -session= User-defined identifer for this session as a string. - This is commonly used with the -acquire and -release - operations to build robust locking, but it can be set - on any key. The default value is empty (no session). -` return strings.TrimSpace(helpText) } func (c *KVPutCommand) Run(args []string) int { - cmdFlags := flag.NewFlagSet("get", flag.ContinueOnError) - cmdFlags.Usage = func() { c.Ui.Output(c.Help()) } - httpAddr := HTTPAddrFlag(cmdFlags) - datacenter := cmdFlags.String("datacenter", "", "") - token := cmdFlags.String("token", "", "") - cas := cmdFlags.Bool("cas", false, "") - flags := cmdFlags.Uint64("flags", 0, "") - base64encoded := cmdFlags.Bool("base64", false, "") - modifyIndex := cmdFlags.Uint64("modify-index", 0, "") - session := cmdFlags.String("session", "", "") - acquire := cmdFlags.Bool("acquire", false, "") - release := cmdFlags.Bool("release", false, "") - if err := cmdFlags.Parse(args); err != nil { + f := c.Command.NewFlagSet(c) + cas := f.Bool("cas", false, + "Perform a Check-And-Set operation. Specifying this value also "+ + "requires the -modify-index flag to be set. The default value "+ + "is false.") + flags := f.Uint64("flags", 0, + "Unsigned integer value to assign to this key-value pair. This "+ + "value is not read by Consul, so clients can use this value however "+ + "makes sense for their use case. The default value is 0 (no flags).") + base64encoded := f.Bool("base64", false, + "Treat the data as base 64 encoded. The default value is false.") + modifyIndex := f.Uint64("modify-index", 0, + "Unsigned integer representing the ModifyIndex of the key. This is "+ + "used in combination with the -cas flag.") + session := f.String("session", "", + "User-defined identifer for this session as a string. This is commonly "+ + "used with the -acquire and -release operations to build robust locking, "+ + "but it can be set on any key. The default value is empty (no session).") + acquire := f.Bool("acquire", false, + "Obtain a lock on the key. If the key does not exist, this operation "+ + "will create the key and obtain the lock. The session must already "+ + "exist and be specified via the -session flag. The default value is false.") + release := f.Bool("release", false, + "Forfeit the lock on the key at the given path. This requires the "+ + "-session flag to be set. The key must be held by the session in order to "+ + "be unlocked. The default value is false.") + + if err := c.Command.Parse(args); err != nil { return 1 } // Check for arg validation - args = cmdFlags.Args() + args = f.Args() key, data, err := c.dataFromArgs(args) if err != nil { c.Ui.Error(fmt.Sprintf("Error! %s", err)) @@ -140,10 +122,7 @@ func (c *KVPutCommand) Run(args []string) int { } // Create and test the HTTP client - conf := api.DefaultConfig() - conf.Address = *httpAddr - conf.Token = *token - client, err := api.NewClient(conf) + client, err := c.Command.HTTPClient() if err != nil { c.Ui.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) return 1 @@ -157,14 +136,9 @@ func (c *KVPutCommand) Run(args []string) int { Session: *session, } - wo := &api.WriteOptions{ - Datacenter: *datacenter, - Token: *token, - } - switch { case *cas: - ok, _, err := client.KV().CAS(pair, wo) + ok, _, err := client.KV().CAS(pair, nil) if err != nil { c.Ui.Error(fmt.Sprintf("Error! Did not write to %s: %s", key, err)) return 1 @@ -177,7 +151,7 @@ func (c *KVPutCommand) Run(args []string) int { c.Ui.Info(fmt.Sprintf("Success! Data written to: %s", key)) return 0 case *acquire: - ok, _, err := client.KV().Acquire(pair, wo) + ok, _, err := client.KV().Acquire(pair, nil) if err != nil { c.Ui.Error(fmt.Sprintf("Error! Failed writing data: %s", err)) return 1 @@ -190,7 +164,7 @@ func (c *KVPutCommand) Run(args []string) int { c.Ui.Info(fmt.Sprintf("Success! Lock acquired on: %s", key)) return 0 case *release: - ok, _, err := client.KV().Release(pair, wo) + ok, _, err := client.KV().Release(pair, nil) if err != nil { c.Ui.Error(fmt.Sprintf("Error! Failed writing data: %s", key)) return 1 @@ -203,7 +177,7 @@ func (c *KVPutCommand) Run(args []string) int { c.Ui.Info(fmt.Sprintf("Success! Lock released on: %s", key)) return 0 default: - if _, err := client.KV().Put(pair, wo); err != nil { + if _, err := client.KV().Put(pair, nil); err != nil { c.Ui.Error(fmt.Sprintf("Error! Failed writing data: %s", err)) return 1 } diff --git a/command/kv_put_test.go b/command/kv_put_test.go index f691719b15..66a442c065 100644 --- a/command/kv_put_test.go +++ b/command/kv_put_test.go @@ -11,20 +11,30 @@ import ( "testing" "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/command/base" "github.com/mitchellh/cli" ) +func testKVPutCommand(t *testing.T) (*cli.MockUi, *KVPutCommand) { + ui := new(cli.MockUi) + return ui, &KVPutCommand{ + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetHTTP, + }, + } +} + func TestKVPutCommand_implements(t *testing.T) { var _ cli.Command = &KVPutCommand{} } func TestKVPutCommand_noTabs(t *testing.T) { - assertNoTabs(t, new(KVPutCommand)) + assertNoTabs(t, new(KVDeleteCommand)) } func TestKVPutCommand_Validation(t *testing.T) { - ui := new(cli.MockUi) - c := &KVPutCommand{Ui: ui} + ui, c := testKVPutCommand(t) cases := map[string]struct { args []string @@ -78,8 +88,7 @@ func TestKVPutCommand_Run(t *testing.T) { defer srv.Shutdown() waitForLeader(t, srv.httpAddr) - ui := new(cli.MockUi) - c := &KVPutCommand{Ui: ui} + ui, c := testKVPutCommand(t) args := []string{ "-http-addr=" + srv.httpAddr, @@ -106,8 +115,7 @@ func TestKVPutCommand_RunEmptyDataQuoted(t *testing.T) { defer srv.Shutdown() waitForLeader(t, srv.httpAddr) - ui := new(cli.MockUi) - c := &KVPutCommand{Ui: ui} + ui, c := testKVPutCommand(t) args := []string{ "-http-addr=" + srv.httpAddr, @@ -134,8 +142,7 @@ func TestKVPutCommand_RunBase64(t *testing.T) { defer srv.Shutdown() waitForLeader(t, srv.httpAddr) - ui := new(cli.MockUi) - c := &KVPutCommand{Ui: ui} + ui, c := testKVPutCommand(t) const encodedString = "aGVsbG8gd29ybGQK" @@ -170,8 +177,7 @@ func TestKVPutCommand_File(t *testing.T) { defer srv.Shutdown() waitForLeader(t, srv.httpAddr) - ui := new(cli.MockUi) - c := &KVPutCommand{Ui: ui} + ui, c := testKVPutCommand(t) f, err := ioutil.TempFile("", "kv-put-command-file") if err != nil { @@ -203,8 +209,7 @@ func TestKVPutCommand_File(t *testing.T) { } func TestKVPutCommand_FileNoExist(t *testing.T) { - ui := new(cli.MockUi) - c := &KVPutCommand{Ui: ui} + ui, c := testKVPutCommand(t) args := []string{ "foo", "@/nope/definitely/not-a-real-file.txt", @@ -228,11 +233,8 @@ func TestKVPutCommand_Stdin(t *testing.T) { stdinR, stdinW := io.Pipe() - ui := new(cli.MockUi) - c := &KVPutCommand{ - Ui: ui, - testStdin: stdinR, - } + ui, c := testKVPutCommand(t) + c.testStdin = stdinR go func() { stdinW.Write([]byte("bar")) @@ -264,8 +266,7 @@ func TestKVPutCommand_NegativeVal(t *testing.T) { defer srv.Shutdown() waitForLeader(t, srv.httpAddr) - ui := new(cli.MockUi) - c := &KVPutCommand{Ui: ui} + ui, c := testKVPutCommand(t) args := []string{ "-http-addr=" + srv.httpAddr, @@ -292,8 +293,7 @@ func TestKVPutCommand_Flags(t *testing.T) { defer srv.Shutdown() waitForLeader(t, srv.httpAddr) - ui := new(cli.MockUi) - c := &KVPutCommand{Ui: ui} + ui, c := testKVPutCommand(t) args := []string{ "-http-addr=" + srv.httpAddr, @@ -330,8 +330,7 @@ func TestKVPutCommand_CAS(t *testing.T) { t.Fatalf("err: %#v", err) } - ui := new(cli.MockUi) - c := &KVPutCommand{Ui: ui} + ui, c := testKVPutCommand(t) args := []string{ "-http-addr=" + srv.httpAddr, diff --git a/commands.go b/commands.go index 5ad8f5f365..6d4ca3e23b 100644 --- a/commands.go +++ b/commands.go @@ -105,37 +105,55 @@ func init() { "kv": func() (cli.Command, error) { return &command.KVCommand{ - Ui: ui, + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetNone, + }, }, nil }, "kv delete": func() (cli.Command, error) { return &command.KVDeleteCommand{ - Ui: ui, + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetHTTP, + }, }, nil }, "kv get": func() (cli.Command, error) { return &command.KVGetCommand{ - Ui: ui, + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetHTTP, + }, }, nil }, "kv put": func() (cli.Command, error) { return &command.KVPutCommand{ - Ui: ui, + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetHTTP, + }, }, nil }, "kv export": func() (cli.Command, error) { return &command.KVExportCommand{ - Ui: ui, + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetHTTP, + }, }, nil }, "kv import": func() (cli.Command, error) { return &command.KVImportCommand{ - Ui: ui, + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetHTTP, + }, }, nil }, From 746dbc1c51a130aae069ffd9074c261f9593156b Mon Sep 17 00:00:00 2001 From: Kyle Havlovitz Date: Wed, 8 Feb 2017 19:45:13 -0500 Subject: [PATCH 8/9] Make join exit non-zero if no nodes were joined --- command/join.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/command/join.go b/command/join.go index 88255e54bf..3ff34afc52 100644 --- a/command/join.go +++ b/command/join.go @@ -57,6 +57,11 @@ func (c *JoinCommand) Run(args []string) int { } } + if joins == 0 { + c.Ui.Error("Failed to join any nodes.") + return 1 + } + c.Ui.Output(fmt.Sprintf( "Successfully joined cluster by contacting %d nodes.", joins)) return 0 From bad037fb583305ebe94281b95120bba9d6f0066b Mon Sep 17 00:00:00 2001 From: Kyle Havlovitz Date: Thu, 9 Feb 2017 20:49:17 -0500 Subject: [PATCH 9/9] Cleanup and formatting adjustments --- api/api.go | 2 +- command/base/command.go | 8 +++----- command/event.go | 2 +- command/keyring.go | 6 +++--- command/kv_get.go | 4 ++-- website/source/layouts/docs.erb | 4 ++-- 6 files changed, 12 insertions(+), 14 deletions(-) diff --git a/api/api.go b/api/api.go index 5ac7225981..ee7cecfeba 100644 --- a/api/api.go +++ b/api/api.go @@ -451,7 +451,7 @@ func (r *request) setWriteOptions(q *WriteOptions) { r.header.Set("X-Consul-Token", q.Token) } if q.RelayFactor != 0 { - r.header.Set("relay-factor", strconv.Itoa(int(q.RelayFactor))) + r.params.Set("relay-factor", strconv.Itoa(int(q.RelayFactor))) } } diff --git a/command/base/command.go b/command/base/command.go index 2741ea2cba..1710e20598 100644 --- a/command/base/command.go +++ b/command/base/command.go @@ -75,7 +75,7 @@ func (c *Command) httpFlagsClient(f *flag.FlagSet) *flag.FlagSet { } f.Var(&c.httpAddr, "http-addr", - "Address and port to the Consul HTTP agent. The value can be an IP "+ + "The `address` and port of the Consul HTTP agent. The value can be an IP "+ "address or DNS address, but it must also include the port. This can "+ "also be specified via the CONSUL_HTTP_ADDR environment variable. The "+ "default value is 127.0.0.1:8500.") @@ -140,6 +140,8 @@ func (c *Command) Parse(args []string) error { // Help returns the help for this flagSet. func (c *Command) Help() string { + // Some commands with subcommands (kv/snapshot) call this without initializing + // any flags first, so exit early to avoid a panic if c.flagSet == nil { return "" } @@ -211,10 +213,6 @@ func printTitle(w io.Writer, s string) { func printFlag(w io.Writer, f *flag.Flag) { example, _ := flag.UnquoteUsage(f) if example != "" { - // Change 'value' to 'string' for consistency - if example == "value" { - example = "string" - } fmt.Fprintf(w, " -%s=<%s>\n", f.Name, example) } else { fmt.Fprintf(w, " -%s\n", f.Name) diff --git a/command/event.go b/command/event.go index 75b7733557..4f5493d0c7 100644 --- a/command/event.go +++ b/command/event.go @@ -37,7 +37,7 @@ func (c *EventCommand) Run(args []string) int { f.StringVar(&node, "node", "", "Regular expression to filter on node names.") f.StringVar(&service, "service", "", - "Regular expression to filter on service instances") + "Regular expression to filter on service instances.") f.StringVar(&tag, "tag", "", "Regular expression to filter on service tags. Must be used with -service.") diff --git a/command/keyring.go b/command/keyring.go index fd1ea16451..31ae284e15 100644 --- a/command/keyring.go +++ b/command/keyring.go @@ -36,9 +36,9 @@ func (c *KeyringCommand) Run(args []string) int { f.BoolVar(&listKeys, "list", false, "List all keys currently in use within the cluster.") f.IntVar(&relay, "relay-factor", 0, - "Added in Consul 0.7.4, setting this to a non-zero value will cause nodes "+ - "to relay their response to the operation through this many randomly-chosen "+ - "other nodes in the cluster. The maximum allowed value is 5.") + "Setting this to a non-zero value will cause nodes to relay their response "+ + "to the operation through this many randomly-chosen other nodes in the "+ + "cluster. The maximum allowed value is 5.") if err := c.Command.Parse(args); err != nil { return 1 diff --git a/command/kv_get.go b/command/kv_get.go index dc509b243f..94a23bacde 100644 --- a/command/kv_get.go +++ b/command/kv_get.go @@ -57,6 +57,8 @@ Usage: consul kv get [options] [KEY_OR_PREFIX] func (c *KVGetCommand) Run(args []string) int { f := c.Command.NewFlagSet(c) + base64encode := f.Bool("base64", false, + "Base64 encode the value. The default value is false.") detailed := f.Bool("detailed", false, "Provide additional metadata about the key in addition to the value such "+ "as the ModifyIndex and any flags that may have been set on the key. "+ @@ -66,8 +68,6 @@ func (c *KVGetCommand) Run(args []string) int { "This is especially useful if you only need the key names themselves. "+ "This option is commonly combined with the -separator option. The default "+ "value is false.") - base64encode := f.Bool("base64", false, - "Base64 encode the value. The default value is false.") recurse := f.Bool("recurse", false, "Recursively look at all keys prefixed with the given path. The default "+ "value is false.") diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index 60300df626..c2c385eaa9 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -88,8 +88,8 @@ > - info - + info + > join