diff --git a/command/exec.go b/command/exec.go index 8823116e8..2e59f2818 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 3f53b1a0f..e1f9aef8d 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 89ba0d62e..6cc5a6794 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.