diff --git a/command/base/command.go b/command/base/command.go index 1710e20598..a9fd6db661 100644 --- a/command/base/command.go +++ b/command/base/command.go @@ -58,6 +58,14 @@ func (c *Command) HTTPClient() (*api.Client, error) { return api.NewClient(config) } +func (c *Command) HTTPAddr() string { + return c.httpAddr.String() +} + +func (c *Command) HTTPToken() string { + return c.token.String() +} + func (c *Command) HTTPDatacenter() string { return c.datacenter.String() } diff --git a/command/join_test.go b/command/join_test.go index 5f6e26798e..cc97dfdbfb 100644 --- a/command/join_test.go +++ b/command/join_test.go @@ -2,10 +2,11 @@ package command import ( "fmt" - "github.com/hashicorp/consul/command/base" - "github.com/mitchellh/cli" "strings" "testing" + + "github.com/hashicorp/consul/command/base" + "github.com/mitchellh/cli" ) func testJoinCommand(t *testing.T) (*cli.MockUi, *JoinCommand) { diff --git a/command/leave.go b/command/leave.go index 819e10d67d..c2a7701091 100644 --- a/command/leave.go +++ b/command/leave.go @@ -1,53 +1,47 @@ package command import ( - "flag" "fmt" - "github.com/mitchellh/cli" + "github.com/hashicorp/consul/command/base" "strings" ) // LeaveCommand is a Command implementation that instructs // the Consul agent to gracefully leave the cluster type LeaveCommand struct { - Ui cli.Ui + base.Command } func (c *LeaveCommand) Help() string { helpText := ` -Usage: consul leave +Usage: consul leave [options] Causes the agent to gracefully leave the Consul cluster and shutdown. -Options: +` + c.Command.Help() - -rpc-addr=127.0.0.1:8400 RPC address of the Consul agent. -` return strings.TrimSpace(helpText) } func (c *LeaveCommand) Run(args []string) int { - cmdFlags := flag.NewFlagSet("leave", flag.ContinueOnError) - cmdFlags.Usage = func() { c.Ui.Output(c.Help()) } - rpcAddr := RPCAddrFlag(cmdFlags) - if err := cmdFlags.Parse(args); err != nil { + f := c.Command.NewFlagSet(c) + if err := c.Command.Parse(args); err != nil { return 1 } - nonFlagArgs := cmdFlags.Args() + nonFlagArgs := f.Args() if len(nonFlagArgs) > 0 { c.Ui.Error(fmt.Sprintf("Error found unexpected args: %v", nonFlagArgs)) c.Ui.Output(c.Help()) 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() - if err := client.Leave(); err != nil { + if err := client.Agent().Leave(); err != nil { c.Ui.Error(fmt.Sprintf("Error leaving: %s", err)) return 1 } diff --git a/command/leave_test.go b/command/leave_test.go index 865df8e754..804e9eb07e 100644 --- a/command/leave_test.go +++ b/command/leave_test.go @@ -1,11 +1,22 @@ package command import ( + "github.com/hashicorp/consul/command/base" "github.com/mitchellh/cli" "strings" "testing" ) +func testLeaveCommand(t *testing.T) (*cli.MockUi, *LeaveCommand) { + ui := new(cli.MockUi) + return ui, &LeaveCommand{ + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetClientHTTP, + }, + } +} + func TestLeaveCommand_implements(t *testing.T) { var _ cli.Command = &LeaveCommand{} } @@ -14,9 +25,8 @@ func TestLeaveCommandRun(t *testing.T) { a1 := testAgent(t) defer a1.Shutdown() - ui := new(cli.MockUi) - c := &LeaveCommand{Ui: ui} - args := []string{"-rpc-addr=" + a1.addr} + ui, c := testLeaveCommand(t) + args := []string{"-http-addr=" + a1.httpAddr} code := c.Run(args) if code != 0 { @@ -32,9 +42,8 @@ func TestLeaveCommandFailOnNonFlagArgs(t *testing.T) { a1 := testAgent(t) defer a1.Shutdown() - ui := new(cli.MockUi) - c := &LeaveCommand{Ui: ui} - args := []string{"-rpc-addr=" + a1.addr, "appserver1"} + _, c := testLeaveCommand(t) + args := []string{"-http-addr=" + a1.httpAddr, "appserver1"} code := c.Run(args) if code == 0 { diff --git a/command/maint.go b/command/maint.go index 0151465650..d225f30e9b 100644 --- a/command/maint.go +++ b/command/maint.go @@ -1,18 +1,16 @@ package command import ( - "flag" "fmt" "strings" - "github.com/hashicorp/consul/api" - "github.com/mitchellh/cli" + "github.com/hashicorp/consul/command/base" ) // MaintCommand is a Command implementation that enables or disables // node or service maintenance mode. type MaintCommand struct { - Ui cli.Ui + base.Command } func (c *MaintCommand) Help() string { @@ -40,15 +38,8 @@ Usage: consul maint [options] If no arguments are given, the agent's maintenance status will be shown. This will return blank if nothing is currently under maintenance. -Options: +` + c.Command.Help() - -enable Enable maintenance mode. - -disable Disable maintenance mode. - -reason= Text string describing the maintenance reason - -service= Control maintenance mode for a specific service ID - -token="" ACL token to use. Defaults to that of agent. - -http-addr=127.0.0.1:8500 HTTP address of the Consul agent. -` return strings.TrimSpace(helpText) } @@ -57,19 +48,15 @@ func (c *MaintCommand) Run(args []string) int { var disable bool var reason string var serviceID string - var token string - cmdFlags := flag.NewFlagSet("maint", flag.ContinueOnError) - cmdFlags.Usage = func() { c.Ui.Output(c.Help()) } + f := c.Command.NewFlagSet(c) - cmdFlags.BoolVar(&enable, "enable", false, "enable maintenance mode") - cmdFlags.BoolVar(&disable, "disable", false, "disable maintenance mode") - cmdFlags.StringVar(&reason, "reason", "", "maintenance reason") - cmdFlags.StringVar(&serviceID, "service", "", "service maintenance") - cmdFlags.StringVar(&token, "token", "", "") - httpAddr := HTTPAddrFlag(cmdFlags) + f.BoolVar(&enable, "enable", false, "Enable maintenance mode.") + f.BoolVar(&disable, "disable", false, "Disable maintenance mode.") + f.StringVar(&reason, "reason", "", "Text describing the maintenance reason.") + f.StringVar(&serviceID, "service", "", "Control maintenance mode for a specific service ID.") - if err := cmdFlags.Parse(args); err != nil { + if err := c.Command.Parse(args); err != nil { return 1 } @@ -88,10 +75,7 @@ func (c *MaintCommand) 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 diff --git a/command/maint_test.go b/command/maint_test.go index 0ac534e13a..83b3805493 100644 --- a/command/maint_test.go +++ b/command/maint_test.go @@ -4,17 +4,27 @@ import ( "strings" "testing" + "github.com/hashicorp/consul/command/base" "github.com/hashicorp/consul/consul/structs" "github.com/mitchellh/cli" ) +func testMaintCommand(t *testing.T) (*cli.MockUi, *MaintCommand) { + ui := new(cli.MockUi) + return ui, &MaintCommand{ + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetClientHTTP, + }, + } +} + func TestMaintCommand_implements(t *testing.T) { var _ cli.Command = &MaintCommand{} } func TestMaintCommandRun_ConflictingArgs(t *testing.T) { - ui := new(cli.MockUi) - c := &MaintCommand{Ui: ui} + _, c := testMaintCommand(t) if code := c.Run([]string{"-enable", "-disable"}); code != 1 { t.Fatalf("expected return code 1, got %d", code) @@ -53,8 +63,7 @@ func TestMaintCommandRun_NoArgs(t *testing.T) { a1.agent.EnableNodeMaintenance("broken 2", "") // Run consul maint with no args (list mode) - ui := new(cli.MockUi) - c := &MaintCommand{Ui: ui} + ui, c := testMaintCommand(t) args := []string{"-http-addr=" + a1.httpAddr} code := c.Run(args) @@ -84,8 +93,7 @@ func TestMaintCommandRun_EnableNodeMaintenance(t *testing.T) { a1 := testAgent(t) defer a1.Shutdown() - ui := new(cli.MockUi) - c := &MaintCommand{Ui: ui} + ui, c := testMaintCommand(t) args := []string{ "-http-addr=" + a1.httpAddr, @@ -106,8 +114,7 @@ func TestMaintCommandRun_DisableNodeMaintenance(t *testing.T) { a1 := testAgent(t) defer a1.Shutdown() - ui := new(cli.MockUi) - c := &MaintCommand{Ui: ui} + ui, c := testMaintCommand(t) args := []string{ "-http-addr=" + a1.httpAddr, @@ -136,8 +143,7 @@ func TestMaintCommandRun_EnableServiceMaintenance(t *testing.T) { t.Fatalf("err: %v", err) } - ui := new(cli.MockUi) - c := &MaintCommand{Ui: ui} + ui, c := testMaintCommand(t) args := []string{ "-http-addr=" + a1.httpAddr, @@ -168,8 +174,7 @@ func TestMaintCommandRun_DisableServiceMaintenance(t *testing.T) { t.Fatalf("err: %v", err) } - ui := new(cli.MockUi) - c := &MaintCommand{Ui: ui} + ui, c := testMaintCommand(t) args := []string{ "-http-addr=" + a1.httpAddr, @@ -190,8 +195,7 @@ func TestMaintCommandRun_ServiceMaintenance_NoService(t *testing.T) { a1 := testAgent(t) defer a1.Shutdown() - ui := new(cli.MockUi) - c := &MaintCommand{Ui: ui} + ui, c := testMaintCommand(t) args := []string{ "-http-addr=" + a1.httpAddr, diff --git a/command/members.go b/command/members.go index fde3c18914..5264c43cbd 100644 --- a/command/members.go +++ b/command/members.go @@ -1,21 +1,22 @@ package command import ( - "flag" "fmt" - "github.com/hashicorp/consul/command/agent" - "github.com/mitchellh/cli" - "github.com/ryanuber/columnize" "net" "regexp" "sort" "strings" + + consulapi "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/command/base" + "github.com/hashicorp/serf/serf" + "github.com/ryanuber/columnize" ) // MembersCommand is a Command implementation that queries a running // Consul agent what members are part of the cluster currently. type MembersCommand struct { - Ui cli.Ui + base.Command } func (c *MembersCommand) Help() string { @@ -24,18 +25,8 @@ Usage: consul members [options] Outputs the members of a running Consul agent. -Options: +` + c.Command.Help() - -detailed Provides detailed information about nodes - - -rpc-addr=127.0.0.1:8400 RPC address of the Consul agent. - - -status= If provided, output is filtered to only nodes matching - the regular expression for status - - -wan If the agent is in server mode, this can be used to return - the other peers in the WAN pool -` return strings.TrimSpace(helpText) } @@ -43,13 +34,18 @@ func (c *MembersCommand) Run(args []string) int { var detailed bool var wan bool var statusFilter string - cmdFlags := flag.NewFlagSet("members", flag.ContinueOnError) - cmdFlags.Usage = func() { c.Ui.Output(c.Help()) } - cmdFlags.BoolVar(&detailed, "detailed", false, "detailed output") - cmdFlags.BoolVar(&wan, "wan", false, "wan members") - cmdFlags.StringVar(&statusFilter, "status", ".*", "status filter") - rpcAddr := RPCAddrFlag(cmdFlags) - if err := cmdFlags.Parse(args); err != nil { + + f := c.Command.NewFlagSet(c) + f.BoolVar(&detailed, "detailed", false, + "Provides detailed information about nodes.") + f.BoolVar(&wan, "wan", false, + "If the agent is in server mode, this can be used to return the other "+ + "peers in the WAN pool.") + f.StringVar(&statusFilter, "status", ".*", + "If provided, output is filtered to only nodes matching the regular "+ + "expression for status.") + + if err := c.Command.Parse(args); err != nil { return 1 } @@ -60,19 +56,13 @@ func (c *MembersCommand) 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() - var members []agent.Member - if wan { - members, err = client.WANMembers() - } else { - members, err = client.LANMembers() - } + members, err := client.Agent().Members(wan) if err != nil { c.Ui.Error(fmt.Sprintf("Error retrieving members: %s", err)) return 1 @@ -82,7 +72,8 @@ func (c *MembersCommand) Run(args []string) int { n := len(members) for i := 0; i < n; i++ { member := members[i] - if !statusRe.MatchString(member.Status) { + statusString := serf.MemberStatus(member.Status).String() + if !statusRe.MatchString(statusString) { members[i], members[n-1] = members[n-1], members[i] i-- n-- @@ -114,7 +105,7 @@ func (c *MembersCommand) Run(args []string) int { } // so we can sort members by name -type ByMemberName []agent.Member +type ByMemberName []*consulapi.AgentMember func (m ByMemberName) Len() int { return len(m) } func (m ByMemberName) Swap(i, j int) { m[i], m[j] = m[j], m[i] } @@ -122,12 +113,12 @@ func (m ByMemberName) Less(i, j int) bool { return m[i].Name < m[j].Name } // standardOutput is used to dump the most useful information about nodes // in a more human-friendly format -func (c *MembersCommand) standardOutput(members []agent.Member) []string { +func (c *MembersCommand) standardOutput(members []*consulapi.AgentMember) []string { result := make([]string, 0, len(members)) header := "Node|Address|Status|Type|Build|Protocol|DC" result = append(result, header) for _, member := range members { - addr := net.TCPAddr{IP: member.Addr, Port: int(member.Port)} + addr := net.TCPAddr{IP: net.ParseIP(member.Addr), Port: int(member.Port)} protocol := member.Tags["vsn"] build := member.Tags["build"] if build == "" { @@ -137,18 +128,19 @@ func (c *MembersCommand) standardOutput(members []agent.Member) []string { } dc := member.Tags["dc"] + statusString := serf.MemberStatus(member.Status).String() switch member.Tags["role"] { case "node": line := fmt.Sprintf("%s|%s|%s|client|%s|%s|%s", - member.Name, addr.String(), member.Status, build, protocol, dc) + member.Name, addr.String(), statusString, build, protocol, dc) result = append(result, line) case "consul": line := fmt.Sprintf("%s|%s|%s|server|%s|%s|%s", - member.Name, addr.String(), member.Status, build, protocol, dc) + member.Name, addr.String(), statusString, build, protocol, dc) result = append(result, line) default: line := fmt.Sprintf("%s|%s|%s|unknown|||", - member.Name, addr.String(), member.Status) + member.Name, addr.String(), statusString) result = append(result, line) } } @@ -157,7 +149,7 @@ func (c *MembersCommand) standardOutput(members []agent.Member) []string { // detailedOutput is used to dump all known information about nodes in // their raw format -func (c *MembersCommand) detailedOutput(members []agent.Member) []string { +func (c *MembersCommand) detailedOutput(members []*consulapi.AgentMember) []string { result := make([]string, 0, len(members)) header := "Node|Address|Status|Tags" result = append(result, header) @@ -177,9 +169,9 @@ func (c *MembersCommand) detailedOutput(members []agent.Member) []string { tags := strings.Join(tagPairs, ",") - addr := net.TCPAddr{IP: member.Addr, Port: int(member.Port)} + addr := net.TCPAddr{IP: net.ParseIP(member.Addr), Port: int(member.Port)} line := fmt.Sprintf("%s|%s|%s|%s", - member.Name, addr.String(), member.Status, tags) + member.Name, addr.String(), serf.MemberStatus(member.Status).String(), tags) result = append(result, line) } return result diff --git a/command/members_test.go b/command/members_test.go index 12b1e4d176..05da35ed88 100644 --- a/command/members_test.go +++ b/command/members_test.go @@ -2,11 +2,22 @@ package command import ( "fmt" + "github.com/hashicorp/consul/command/base" "github.com/mitchellh/cli" "strings" "testing" ) +func testMembersCommand(t *testing.T) (*cli.MockUi, *MembersCommand) { + ui := new(cli.MockUi) + return ui, &MembersCommand{ + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetClientHTTP, + }, + } +} + func TestMembersCommand_implements(t *testing.T) { var _ cli.Command = &MembersCommand{} } @@ -15,9 +26,8 @@ func TestMembersCommandRun(t *testing.T) { a1 := testAgent(t) defer a1.Shutdown() - ui := new(cli.MockUi) - c := &MembersCommand{Ui: ui} - args := []string{"-rpc-addr=" + a1.addr} + ui, c := testMembersCommand(t) + args := []string{"-http-addr=" + a1.httpAddr} code := c.Run(args) if code != 0 { @@ -44,9 +54,8 @@ func TestMembersCommandRun_WAN(t *testing.T) { a1 := testAgent(t) defer a1.Shutdown() - ui := new(cli.MockUi) - c := &MembersCommand{Ui: ui} - args := []string{"-rpc-addr=" + a1.addr, "-wan"} + ui, c := testMembersCommand(t) + args := []string{"-http-addr=" + a1.httpAddr, "-wan"} code := c.Run(args) if code != 0 { @@ -62,10 +71,9 @@ func TestMembersCommandRun_statusFilter(t *testing.T) { a1 := testAgent(t) defer a1.Shutdown() - ui := new(cli.MockUi) - c := &MembersCommand{Ui: ui} + ui, c := testMembersCommand(t) args := []string{ - "-rpc-addr=" + a1.addr, + "-http-addr=" + a1.httpAddr, "-status=a.*e", } @@ -83,10 +91,9 @@ func TestMembersCommandRun_statusFilter_failed(t *testing.T) { a1 := testAgent(t) defer a1.Shutdown() - ui := new(cli.MockUi) - c := &MembersCommand{Ui: ui} + ui, c := testMembersCommand(t) args := []string{ - "-rpc-addr=" + a1.addr, + "-http-addr=" + a1.httpAddr, "-status=(fail|left)", } diff --git a/command/monitor.go b/command/monitor.go index f24646662c..07aded7f3b 100644 --- a/command/monitor.go +++ b/command/monitor.go @@ -1,19 +1,19 @@ package command import ( - "flag" "fmt" - "github.com/hashicorp/logutils" - "github.com/mitchellh/cli" "strings" "sync" + + "github.com/hashicorp/consul/command/base" ) // MonitorCommand is a Command implementation that queries a running // Consul agent what members are part of the cluster currently. type MonitorCommand struct { + base.Command + ShutdownCh <-chan struct{} - Ui cli.Ui lock sync.Mutex quitting bool @@ -29,40 +29,34 @@ Usage: consul monitor [options] example your agent may only be logging at INFO level, but with the monitor you can see the DEBUG level logs. -Options: +` + c.Command.Help() - -log-level=info Log level of the agent. - -rpc-addr=127.0.0.1:8400 RPC address of the Consul agent. -` return strings.TrimSpace(helpText) } func (c *MonitorCommand) Run(args []string) int { var logLevel string - cmdFlags := flag.NewFlagSet("monitor", flag.ContinueOnError) - cmdFlags.Usage = func() { c.Ui.Output(c.Help()) } - cmdFlags.StringVar(&logLevel, "log-level", "INFO", "log level") - rpcAddr := RPCAddrFlag(cmdFlags) - if err := cmdFlags.Parse(args); err != nil { + + f := c.Command.NewFlagSet(c) + f.StringVar(&logLevel, "log-level", "INFO", "Log level of the agent.") + + if err := c.Command.Parse(args); err != nil { 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() - logCh := make(chan string, 1024) - monHandle, err := client.Monitor(logutils.LogLevel(logLevel), logCh) + eventDoneCh := make(chan struct{}) + logCh, err := client.Agent().Monitor(logLevel, eventDoneCh, nil) if err != nil { c.Ui.Error(fmt.Sprintf("Error starting monitor: %s", err)) return 1 } - defer client.Stop(monHandle) - eventDoneCh := make(chan struct{}) go func() { defer close(eventDoneCh) OUTER: diff --git a/command/operator.go b/command/operator.go index b049e325a9..cf16040b7a 100644 --- a/command/operator.go +++ b/command/operator.go @@ -1,24 +1,24 @@ package command import ( - "flag" "fmt" "strings" + "flag" "github.com/hashicorp/consul/api" - "github.com/mitchellh/cli" + "github.com/hashicorp/consul/command/base" "github.com/ryanuber/columnize" ) // OperatorCommand is used to provide various low-level tools for Consul // operators. type OperatorCommand struct { - Ui cli.Ui + base.Command } func (c *OperatorCommand) Help() string { helpText := ` -Usage: consul operator [common options] [action] [options] +Usage: consul operator [action] [options] Provides cluster-level tools for Consul operators, such as interacting with the Raft subsystem. NOTE: Use this command with extreme caution, as improper @@ -31,11 +31,6 @@ Usage: consul operator [common options] [action] [options] Run consul operator with no arguments for help on that subcommand. -Common Options: - - -http-addr=127.0.0.1:8500 HTTP address of the Consul agent. - -token="" ACL token to use. Defaults to that of agent. - Subcommands: raft View and modify Consul's Raft configuration. @@ -73,17 +68,15 @@ func (c *OperatorCommand) Synopsis() string { } const raftHelp = ` -Raft Subcommand Actions: +Operator Raft Subcommand: - raft -list-peers -stale=[true|false] + The raft subcommand can be used in two modes: + + consul operator raft -list-peers Displays the current Raft peer configuration. - The -stale argument defaults to "false" which means the leader provides the - result. If the cluster is in an outage state without a leader, you may need - to set -stale to "true" to get the configuration from a non-leader server. - - raft -remove-peer -address="IP:port" + consul operator raft -remove-peer -address="IP:port" Removes Consul server with given -address from the Raft configuration. @@ -93,33 +86,39 @@ Raft Subcommand Actions: affects the Raft quorum. If the server still shows in the output of the "consul members" command, it is preferable to clean up by simply running "consul force-leave" instead of this command. + ` // raft handles the raft subcommands. func (c *OperatorCommand) raft(args []string) error { - cmdFlags := flag.NewFlagSet("raft", flag.ContinueOnError) - cmdFlags.Usage = func() { c.Ui.Output(c.Help()) } + f := c.Command.NewFlagSet(c) // Parse verb arguments. var listPeers, removePeer bool - cmdFlags.BoolVar(&listPeers, "list-peers", false, "") - cmdFlags.BoolVar(&removePeer, "remove-peer", false, "") + f.BoolVar(&listPeers, "list-peers", false, + "If this flag is provided, the current Raft peer configuration will be "+ + "displayed. If the cluster is in an outage state without a leader, you may need "+ + "to set -stale to 'true' to get the configuration from a non-leader server.") + f.BoolVar(&removePeer, "remove-peer", false, + "If this flag is provided, the Consul server with the given -address will be "+ + "removed from the Raft configuration.") // Parse other arguments. - var stale bool - var address, token string - cmdFlags.StringVar(&address, "address", "", "") - cmdFlags.BoolVar(&stale, "stale", false, "") - cmdFlags.StringVar(&token, "token", "", "") - httpAddr := HTTPAddrFlag(cmdFlags) - if err := cmdFlags.Parse(args); err != nil { + var address string + f.StringVar(&address, "address", "", + "The address to remove from the Raft configuration.") + + if err := c.Command.Parse(args); err != nil { + if err == flag.ErrHelp { + c.Ui.Output("") + c.Ui.Output(strings.TrimSpace(raftHelp + c.Command.Help())) + return nil + } return err } // Set up a client. - conf := api.DefaultConfig() - conf.Address = *httpAddr - client, err := api.NewClient(conf) + client, err := c.Command.HTTPClient() if err != nil { return fmt.Errorf("error connecting to Consul agent: %s", err) } @@ -129,8 +128,7 @@ func (c *OperatorCommand) raft(args []string) error { if listPeers { // Fetch the current configuration. q := &api.QueryOptions{ - AllowStale: stale, - Token: token, + AllowStale: c.Command.HTTPStale(), } reply, err := operator.RaftGetConfiguration(q) if err != nil { @@ -156,17 +154,14 @@ func (c *OperatorCommand) raft(args []string) error { } // Try to kick the peer. - w := &api.WriteOptions{ - Token: token, - } - if err := operator.RaftRemovePeerByAddress(address, w); err != nil { + if err := operator.RaftRemovePeerByAddress(address, nil); err != nil { return err } c.Ui.Output(fmt.Sprintf("Removed peer with address %q", address)) } else { c.Ui.Output(c.Help()) c.Ui.Output("") - c.Ui.Output(strings.TrimSpace(raftHelp)) + c.Ui.Output(strings.TrimSpace(raftHelp + c.Command.Help())) } return nil diff --git a/command/operator_test.go b/command/operator_test.go index e65434b75d..d31cac0074 100644 --- a/command/operator_test.go +++ b/command/operator_test.go @@ -4,9 +4,20 @@ import ( "strings" "testing" + "github.com/hashicorp/consul/command/base" "github.com/mitchellh/cli" ) +func testOperatorCommand(t *testing.T) (*cli.MockUi, *OperatorCommand) { + ui := new(cli.MockUi) + return ui, &OperatorCommand{ + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetHTTP, + }, + } +} + func TestOperator_Implements(t *testing.T) { var _ cli.Command = &OperatorCommand{} } @@ -16,8 +27,7 @@ func TestOperator_Raft_ListPeers(t *testing.T) { defer a1.Shutdown() waitForLeader(t, a1.httpAddr) - ui := new(cli.MockUi) - c := &OperatorCommand{Ui: ui} + ui, c := testOperatorCommand(t) args := []string{"raft", "-http-addr=" + a1.httpAddr, "-list-peers"} code := c.Run(args) @@ -35,8 +45,7 @@ func TestOperator_Raft_RemovePeer(t *testing.T) { defer a1.Shutdown() waitForLeader(t, a1.httpAddr) - ui := new(cli.MockUi) - c := &OperatorCommand{Ui: ui} + ui, c := testOperatorCommand(t) args := []string{"raft", "-http-addr=" + a1.httpAddr, "-remove-peer", "-address=nope"} code := c.Run(args) diff --git a/command/reload.go b/command/reload.go index c956eaf096..1f82e16a8f 100644 --- a/command/reload.go +++ b/command/reload.go @@ -1,16 +1,15 @@ package command import ( - "flag" "fmt" - "github.com/mitchellh/cli" + "github.com/hashicorp/consul/command/base" "strings" ) // ReloadCommand is a Command implementation that instructs // the Consul agent to reload configurations type ReloadCommand struct { - Ui cli.Ui + base.Command } func (c *ReloadCommand) Help() string { @@ -20,29 +19,25 @@ Usage: consul reload Causes the agent to reload configurations. This can be used instead of sending the SIGHUP signal to the agent. -Options: +` + c.Command.Help() - -rpc-addr=127.0.0.1:8400 RPC address of the Consul agent. -` return strings.TrimSpace(helpText) } func (c *ReloadCommand) Run(args []string) int { - cmdFlags := flag.NewFlagSet("reload", flag.ContinueOnError) - cmdFlags.Usage = func() { c.Ui.Output(c.Help()) } - rpcAddr := RPCAddrFlag(cmdFlags) - if err := cmdFlags.Parse(args); err != nil { + c.Command.NewFlagSet(c) + + if err := c.Command.Parse(args); err != nil { 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() - if err := client.Reload(); err != nil { + if err := client.Agent().Reload(); err != nil { c.Ui.Error(fmt.Sprintf("Error reloading: %s", err)) return 1 } diff --git a/command/reload_test.go b/command/reload_test.go index f34c987496..d7039458ff 100644 --- a/command/reload_test.go +++ b/command/reload_test.go @@ -1,9 +1,11 @@ package command import ( - "github.com/mitchellh/cli" "strings" "testing" + + "github.com/hashicorp/consul/command/base" + "github.com/mitchellh/cli" ) func TestReloadCommand_implements(t *testing.T) { @@ -11,12 +13,24 @@ func TestReloadCommand_implements(t *testing.T) { } func TestReloadCommandRun(t *testing.T) { - a1 := testAgent(t) + reloadCh := make(chan chan error) + a1 := testAgentWithConfigReload(t, nil, reloadCh) defer a1.Shutdown() + // Setup a dummy response to errCh to simulate a successful reload + go func() { + errCh := <-reloadCh + errCh <- nil + }() + ui := new(cli.MockUi) - c := &ReloadCommand{Ui: ui} - args := []string{"-rpc-addr=" + a1.addr} + c := &ReloadCommand{ + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetClientHTTP, + }, + } + args := []string{"-http-addr=" + a1.httpAddr} code := c.Run(args) if code != 0 { diff --git a/command/rtt.go b/command/rtt.go index 88a3ba1400..85500df0ad 100644 --- a/command/rtt.go +++ b/command/rtt.go @@ -1,19 +1,17 @@ package command import ( - "flag" "fmt" "strings" - "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/command/base" "github.com/hashicorp/serf/coordinate" - "github.com/mitchellh/cli" ) // RTTCommand is a Command implementation that allows users to query the // estimated round trip time between nodes using network coordinates. type RTTCommand struct { - Ui cli.Ui + base.Command } func (c *RTTCommand) Help() string { @@ -36,28 +34,24 @@ Usage: consul rtt [options] node1 [node2] because they are maintained by independent Serf gossip pools, so they are not compatible. -Options: +` + c.Command.Help() - -wan Use WAN coordinates instead of LAN coordinates. - -http-addr=127.0.0.1:8500 HTTP address of the Consul agent. -` return strings.TrimSpace(helpText) } func (c *RTTCommand) Run(args []string) int { var wan bool - cmdFlags := flag.NewFlagSet("rtt", flag.ContinueOnError) - cmdFlags.Usage = func() { c.Ui.Output(c.Help()) } + f := c.Command.NewFlagSet(c) - cmdFlags.BoolVar(&wan, "wan", false, "wan") - httpAddr := HTTPAddrFlag(cmdFlags) - if err := cmdFlags.Parse(args); err != nil { + f.BoolVar(&wan, "wan", false, "Use WAN coordinates instead of LAN coordinates.") + + if err := c.Command.Parse(args); err != nil { return 1 } // They must provide at least one node. - nodes := cmdFlags.Args() + nodes := f.Args() if len(nodes) < 1 || len(nodes) > 2 { c.Ui.Error("One or two node names must be specified") c.Ui.Error("") @@ -66,9 +60,7 @@ func (c *RTTCommand) Run(args []string) int { } // Create and test the HTTP client. - conf := api.DefaultConfig() - conf.Address = *httpAddr - 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 diff --git a/command/rtt_test.go b/command/rtt_test.go index 9fcabb8ad4..57495e1f5a 100644 --- a/command/rtt_test.go +++ b/command/rtt_test.go @@ -7,19 +7,29 @@ import ( "time" "github.com/hashicorp/consul/command/agent" + "github.com/hashicorp/consul/command/base" "github.com/hashicorp/consul/consul/structs" "github.com/hashicorp/consul/testutil" "github.com/hashicorp/serf/coordinate" "github.com/mitchellh/cli" ) +func testRTTCommand(t *testing.T) (*cli.MockUi, *RTTCommand) { + ui := new(cli.MockUi) + return ui, &RTTCommand{ + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetClientHTTP, + }, + } +} + func TestRTTCommand_Implements(t *testing.T) { var _ cli.Command = &RTTCommand{} } func TestRTTCommand_Run_BadArgs(t *testing.T) { - ui := new(cli.MockUi) - c := &RTTCommand{Ui: ui} + _, c := testRTTCommand(t) if code := c.Run([]string{}); code != 1 { t.Fatalf("expected return code 1, got %d", code) @@ -90,8 +100,7 @@ func TestRTTCommand_Run_LAN(t *testing.T) { } // Ask for the RTT of two known nodes - ui := new(cli.MockUi) - c := &RTTCommand{Ui: ui} + ui, c := testRTTCommand(t) args := []string{ "-http-addr=" + a.httpAddr, a.config.NodeName, @@ -118,8 +127,7 @@ func TestRTTCommand_Run_LAN(t *testing.T) { // Default to the agent's node. { - ui := new(cli.MockUi) - c := &RTTCommand{Ui: ui} + ui, c := testRTTCommand(t) args := []string{ "-http-addr=" + a.httpAddr, "dogs", @@ -138,8 +146,7 @@ func TestRTTCommand_Run_LAN(t *testing.T) { // Try an unknown node. { - ui := new(cli.MockUi) - c := &RTTCommand{Ui: ui} + ui, c := testRTTCommand(t) args := []string{ "-http-addr=" + a.httpAddr, a.config.NodeName, @@ -162,8 +169,7 @@ func TestRTTCommand_Run_WAN(t *testing.T) { // We can't easily inject WAN coordinates, so we will just query the // node with itself. { - ui := new(cli.MockUi) - c := &RTTCommand{Ui: ui} + ui, c := testRTTCommand(t) args := []string{ "-wan", "-http-addr=" + a.httpAddr, @@ -183,8 +189,7 @@ func TestRTTCommand_Run_WAN(t *testing.T) { // Default to the agent's node. { - ui := new(cli.MockUi) - c := &RTTCommand{Ui: ui} + ui, c := testRTTCommand(t) args := []string{ "-wan", "-http-addr=" + a.httpAddr, @@ -203,8 +208,7 @@ func TestRTTCommand_Run_WAN(t *testing.T) { // Try an unknown node. { - ui := new(cli.MockUi) - c := &RTTCommand{Ui: ui} + ui, c := testRTTCommand(t) args := []string{ "-wan", "-http-addr=" + a.httpAddr, diff --git a/command/snapshot_inspect.go b/command/snapshot_inspect.go index aaeab2445f..88b5680835 100644 --- a/command/snapshot_inspect.go +++ b/command/snapshot_inspect.go @@ -2,20 +2,19 @@ package command import ( "bytes" - "flag" "fmt" "os" "strings" "text/tabwriter" + "github.com/hashicorp/consul/command/base" "github.com/hashicorp/consul/snapshot" - "github.com/mitchellh/cli" ) // SnapshotInspectCommand is a Command implementation that is used to display // metadata about a snapshot file type SnapshotInspectCommand struct { - Ui cli.Ui + base.Command } func (c *SnapshotInspectCommand) Help() string { @@ -35,15 +34,15 @@ Usage: consul snapshot inspect [options] FILE } func (c *SnapshotInspectCommand) Run(args []string) int { - cmdFlags := flag.NewFlagSet("get", flag.ContinueOnError) - cmdFlags.Usage = func() { c.Ui.Output(c.Help()) } - if err := cmdFlags.Parse(args); err != nil { + flagSet := c.Command.NewFlagSet(c) + + if err := c.Command.Parse(args); err != nil { return 1 } var file string - args = cmdFlags.Args() + args = flagSet.Args() switch len(args) { case 0: c.Ui.Error("Missing FILE argument") diff --git a/command/snapshot_inspect_test.go b/command/snapshot_inspect_test.go index 557d3ba822..282927469d 100644 --- a/command/snapshot_inspect_test.go +++ b/command/snapshot_inspect_test.go @@ -8,9 +8,20 @@ import ( "strings" "testing" + "github.com/hashicorp/consul/command/base" "github.com/mitchellh/cli" ) +func testSnapshotInspectCommand(t *testing.T) (*cli.MockUi, *SnapshotInspectCommand) { + ui := new(cli.MockUi) + return ui, &SnapshotInspectCommand{ + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetNone, + }, + } +} + func TestSnapshotInspectCommand_implements(t *testing.T) { var _ cli.Command = &SnapshotInspectCommand{} } @@ -20,8 +31,7 @@ func TestSnapshotInspectCommand_noTabs(t *testing.T) { } func TestSnapshotInspectCommand_Validation(t *testing.T) { - ui := new(cli.MockUi) - c := &SnapshotInspectCommand{Ui: ui} + ui, c := testSnapshotInspectCommand(t) cases := map[string]struct { args []string @@ -63,8 +73,6 @@ func TestSnapshotInspectCommand_Run(t *testing.T) { defer srv.Shutdown() waitForLeader(t, srv.httpAddr) - ui := new(cli.MockUi) - dir, err := ioutil.TempDir("", "snapshot") if err != nil { t.Fatalf("err: %v", err) @@ -93,10 +101,10 @@ func TestSnapshotInspectCommand_Run(t *testing.T) { } // Inspect the snapshot - inspect := &SnapshotInspectCommand{Ui: ui} + ui, c := testSnapshotInspectCommand(t) args := []string{file} - code := inspect.Run(args) + code := c.Run(args) if code != 0 { t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) } diff --git a/command/snapshot_restore.go b/command/snapshot_restore.go index 7ca8ff6655..ade002b38d 100644 --- a/command/snapshot_restore.go +++ b/command/snapshot_restore.go @@ -1,19 +1,17 @@ package command import ( - "flag" "fmt" "os" "strings" - "github.com/hashicorp/consul/api" - "github.com/mitchellh/cli" + "github.com/hashicorp/consul/command/base" ) // SnapshotRestoreCommand is a Command implementation that is used to restore // the state of the Consul servers for disaster recovery. type SnapshotRestoreCommand struct { - Ui cli.Ui + base.Command } func (c *SnapshotRestoreCommand) Help() string { @@ -38,24 +36,21 @@ Usage: consul snapshot restore [options] FILE For a full list of options and examples, please see the Consul documentation. -` + apiOptsText +` + c.Command.Help() return strings.TrimSpace(helpText) } func (c *SnapshotRestoreCommand) 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", "", "") - httpAddr := HTTPAddrFlag(cmdFlags) - if err := cmdFlags.Parse(args); err != nil { + flagSet := c.Command.NewFlagSet(c) + + if err := c.Command.Parse(args); err != nil { return 1 } var file string - args = cmdFlags.Args() + args = flagSet.Args() switch len(args) { case 0: c.Ui.Error("Missing FILE argument") @@ -68,11 +63,7 @@ func (c *SnapshotRestoreCommand) Run(args []string) int { } // Create and test the HTTP client - conf := api.DefaultConfig() - conf.Datacenter = *datacenter - 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 diff --git a/command/snapshot_restore_test.go b/command/snapshot_restore_test.go index e9f1b4ae6b..c8013bdb49 100644 --- a/command/snapshot_restore_test.go +++ b/command/snapshot_restore_test.go @@ -8,9 +8,20 @@ import ( "strings" "testing" + "github.com/hashicorp/consul/command/base" "github.com/mitchellh/cli" ) +func testSnapshotRestoreCommand(t *testing.T) (*cli.MockUi, *SnapshotRestoreCommand) { + ui := new(cli.MockUi) + return ui, &SnapshotRestoreCommand{ + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetHTTP, + }, + } +} + func TestSnapshotRestoreCommand_implements(t *testing.T) { var _ cli.Command = &SnapshotRestoreCommand{} } @@ -20,8 +31,7 @@ func TestSnapshotRestoreCommand_noTabs(t *testing.T) { } func TestSnapshotRestoreCommand_Validation(t *testing.T) { - ui := new(cli.MockUi) - c := &SnapshotRestoreCommand{Ui: ui} + ui, c := testSnapshotRestoreCommand(t) cases := map[string]struct { args []string @@ -63,8 +73,7 @@ func TestSnapshotRestoreCommand_Run(t *testing.T) { defer srv.Shutdown() waitForLeader(t, srv.httpAddr) - ui := new(cli.MockUi) - c := &SnapshotSaveCommand{Ui: ui} + ui, c := testSnapshotRestoreCommand(t) dir, err := ioutil.TempDir("", "snapshot") if err != nil { diff --git a/command/snapshot_save.go b/command/snapshot_save.go index ddfa56a118..7a325a80a6 100644 --- a/command/snapshot_save.go +++ b/command/snapshot_save.go @@ -1,21 +1,20 @@ package command import ( - "flag" "fmt" "io" "os" "strings" "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/command/base" "github.com/hashicorp/consul/snapshot" - "github.com/mitchellh/cli" ) // SnapshotSaveCommand is a Command implementation that is used to save the // state of the Consul servers for disaster recovery. type SnapshotSaveCommand struct { - Ui cli.Ui + base.Command } func (c *SnapshotSaveCommand) Help() string { @@ -40,25 +39,21 @@ Usage: consul snapshot save [options] FILE For a full list of options and examples, please see the Consul documentation. -` + apiOptsText +` + c.Command.Help() return strings.TrimSpace(helpText) } func (c *SnapshotSaveCommand) 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, "") - httpAddr := HTTPAddrFlag(cmdFlags) - if err := cmdFlags.Parse(args); err != nil { + flagSet := c.Command.NewFlagSet(c) + + if err := c.Command.Parse(args); err != nil { return 1 } var file string - args = cmdFlags.Args() + args = flagSet.Args() switch len(args) { case 0: c.Ui.Error("Missing FILE argument") @@ -71,11 +66,7 @@ func (c *SnapshotSaveCommand) Run(args []string) int { } // Create and test the HTTP client - conf := api.DefaultConfig() - conf.Datacenter = *datacenter - 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 @@ -83,7 +74,7 @@ func (c *SnapshotSaveCommand) Run(args []string) int { // Take the snapshot. snap, qm, err := client.Snapshot().Save(&api.QueryOptions{ - AllowStale: *stale, + AllowStale: c.Command.HTTPStale(), }) if err != nil { c.Ui.Error(fmt.Sprintf("Error saving snapshot: %s", err)) diff --git a/command/snapshot_save_test.go b/command/snapshot_save_test.go index e1eeb87c63..d4edf37aac 100644 --- a/command/snapshot_save_test.go +++ b/command/snapshot_save_test.go @@ -7,9 +7,20 @@ import ( "strings" "testing" + "github.com/hashicorp/consul/command/base" "github.com/mitchellh/cli" ) +func testSnapshotSaveCommand(t *testing.T) (*cli.MockUi, *SnapshotSaveCommand) { + ui := new(cli.MockUi) + return ui, &SnapshotSaveCommand{ + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetHTTP, + }, + } +} + func TestSnapshotSaveCommand_implements(t *testing.T) { var _ cli.Command = &SnapshotSaveCommand{} } @@ -19,8 +30,7 @@ func TestSnapshotSaveCommand_noTabs(t *testing.T) { } func TestSnapshotSaveCommand_Validation(t *testing.T) { - ui := new(cli.MockUi) - c := &SnapshotSaveCommand{Ui: ui} + ui, c := testSnapshotSaveCommand(t) cases := map[string]struct { args []string @@ -62,8 +72,7 @@ func TestSnapshotSaveCommand_Run(t *testing.T) { defer srv.Shutdown() waitForLeader(t, srv.httpAddr) - ui := new(cli.MockUi) - c := &SnapshotSaveCommand{Ui: ui} + ui, c := testSnapshotSaveCommand(t) dir, err := ioutil.TempDir("", "snapshot") if err != nil { diff --git a/command/util_test.go b/command/util_test.go index 646c1a5265..9309129b90 100644 --- a/command/util_test.go +++ b/command/util_test.go @@ -44,7 +44,7 @@ func (a *agentWrapper) Shutdown() { } func testAgent(t *testing.T) *agentWrapper { - return testAgentWithConfig(t, func(c *agent.Config) {}) + return testAgentWithConfig(t, nil) } func testAgentWithAPIClient(t *testing.T) (*agentWrapper, *api.Client) { @@ -57,6 +57,10 @@ func testAgentWithAPIClient(t *testing.T) (*agentWrapper, *api.Client) { } func testAgentWithConfig(t *testing.T, cb func(c *agent.Config)) *agentWrapper { + return testAgentWithConfigReload(t, cb, nil) +} + +func testAgentWithConfigReload(t *testing.T, cb func(c *agent.Config), reloadCh chan chan error) *agentWrapper { l, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("err: %s", err) @@ -66,7 +70,9 @@ func testAgentWithConfig(t *testing.T, cb func(c *agent.Config)) *agentWrapper { mult := io.MultiWriter(os.Stderr, lw) conf := nextConfig() - cb(conf) + if cb != nil { + cb(conf) + } dir, err := ioutil.TempDir("", "agent") if err != nil { @@ -74,7 +80,7 @@ func testAgentWithConfig(t *testing.T, cb func(c *agent.Config)) *agentWrapper { } conf.DataDir = dir - a, err := agent.Create(conf, lw, nil, nil) + a, err := agent.Create(conf, lw, nil, reloadCh) if err != nil { os.RemoveAll(dir) t.Fatalf(fmt.Sprintf("err: %v", err)) diff --git a/command/version_test.go b/command/version_test.go index da7aa8608e..2a645690fa 100644 --- a/command/version_test.go +++ b/command/version_test.go @@ -1,8 +1,9 @@ package command import ( - "github.com/mitchellh/cli" "testing" + + "github.com/mitchellh/cli" ) func TestVersionCommand_implements(t *testing.T) { diff --git a/command/watch.go b/command/watch.go index a6ec677a19..bcdfac6c2c 100644 --- a/command/watch.go +++ b/command/watch.go @@ -3,22 +3,21 @@ package command import ( "bytes" "encoding/json" - "flag" "fmt" "os" "strconv" "strings" "github.com/hashicorp/consul/command/agent" + "github.com/hashicorp/consul/command/base" "github.com/hashicorp/consul/watch" - "github.com/mitchellh/cli" ) // WatchCommand is a Command implementation that is used to setup // a "watch" which uses a sub-process type WatchCommand struct { + base.Command ShutdownCh <-chan struct{} - Ui cli.Ui } func (c *WatchCommand) Help() string { @@ -32,49 +31,36 @@ Usage: consul watch [options] [child...] Providing the watch type is required, and other parameters may be required or supported depending on the watch type. -Options: +` + c.Command.Help() - -http-addr=127.0.0.1:8500 HTTP address of the Consul agent. - -datacenter="" Datacenter to query. Defaults to that of agent. - -token="" ACL token to use. Defaults to that of agent. - -stale=[true|false] Specifies if watch data is permitted to be stale. - Defaults to false. - -Watch Specification: - - -key=val Specifies the key to watch. Only for 'key' type. - -name=val Specifies an event name to watch. Only for 'event' type. - -passingonly=[true|false] Specifies if only hosts passing all checks are displayed. - Optional for 'service' type. Defaults false. - -prefix=val Specifies the key prefix to watch. Only for 'keyprefix' type. - -service=val Specifies the service to watch. Required for 'service' type, - optional for 'checks' type. - -state=val Specifies the states to watch. Optional for 'checks' type. - -tag=val Specifies the service tag to filter on. Optional for 'service' - type. - -type=val Specifies the watch type. One of key, keyprefix - services, nodes, service, checks, or event. -` return strings.TrimSpace(helpText) } func (c *WatchCommand) Run(args []string) int { - var watchType, datacenter, token, key, prefix, service, tag, passingOnly, stale, state, name string - cmdFlags := flag.NewFlagSet("watch", flag.ContinueOnError) - cmdFlags.Usage = func() { c.Ui.Output(c.Help()) } - cmdFlags.StringVar(&watchType, "type", "", "") - cmdFlags.StringVar(&datacenter, "datacenter", "", "") - cmdFlags.StringVar(&token, "token", "", "") - cmdFlags.StringVar(&key, "key", "", "") - cmdFlags.StringVar(&prefix, "prefix", "", "") - cmdFlags.StringVar(&service, "service", "", "") - cmdFlags.StringVar(&tag, "tag", "", "") - cmdFlags.StringVar(&passingOnly, "passingonly", "", "") - cmdFlags.StringVar(&stale, "stale", "", "") - cmdFlags.StringVar(&state, "state", "", "") - cmdFlags.StringVar(&name, "name", "", "") - httpAddr := HTTPAddrFlag(cmdFlags) - if err := cmdFlags.Parse(args); err != nil { + var watchType, key, prefix, service, tag, passingOnly, state, name string + + f := c.Command.NewFlagSet(c) + f.StringVar(&watchType, "type", "", + "Specifies the watch type. One of key, keyprefix services, nodes, "+ + "service, checks, or event.") + f.StringVar(&key, "key", "", + "Specifies the key to watch. Only for 'key' type.") + f.StringVar(&prefix, "prefix", "", + "Specifies the key prefix to watch. Only for 'keyprefix' type.") + f.StringVar(&service, "service", "", + "Specifies the service to watch. Required for 'service' type, "+ + "optional for 'checks' type.") + f.StringVar(&tag, "tag", "", + "Specifies the service tag to filter on. Optional for 'service' type.") + f.StringVar(&passingOnly, "passingonly", "", + "Specifies if only hosts passing all checks are displayed. "+ + "Optional for 'service' type, must be one of `[true|false]`. Defaults false.") + f.StringVar(&state, "state", "", + "Specifies the states to watch. Optional for 'checks' type.") + f.StringVar(&name, "name", "", + "Specifies an event name to watch. Only for 'event' type.") + + if err := c.Command.Parse(args); err != nil { return 1 } @@ -87,18 +73,18 @@ func (c *WatchCommand) Run(args []string) int { } // Grab the script to execute if any - script := strings.Join(cmdFlags.Args(), " ") + script := strings.Join(f.Args(), " ") // Compile the watch parameters params := make(map[string]interface{}) if watchType != "" { params["type"] = watchType } - if datacenter != "" { - params["datacenter"] = datacenter + if c.Command.HTTPDatacenter() != "" { + params["datacenter"] = c.Command.HTTPDatacenter() } - if token != "" { - params["token"] = token + if c.Command.HTTPToken() != "" { + params["token"] = c.Command.HTTPToken() } if key != "" { params["key"] = key @@ -112,13 +98,8 @@ func (c *WatchCommand) Run(args []string) int { if tag != "" { params["tag"] = tag } - if stale != "" { - b, err := strconv.ParseBool(stale) - if err != nil { - c.Ui.Error(fmt.Sprintf("Failed to parse stale flag: %s", err)) - return 1 - } - params["stale"] = b + if c.Command.HTTPStale() { + params["stale"] = c.Command.HTTPStale() } if state != "" { params["state"] = state @@ -143,7 +124,7 @@ func (c *WatchCommand) 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 @@ -213,7 +194,7 @@ func (c *WatchCommand) Run(args []string) int { }() // Run the watch - if err := wp.Run(*httpAddr); err != nil { + if err := wp.Run(c.Command.HTTPAddr()); err != nil { c.Ui.Error(fmt.Sprintf("Error querying Consul agent: %s", err)) return 1 } diff --git a/command/watch_test.go b/command/watch_test.go index eaa9376d5a..8325981b8e 100644 --- a/command/watch_test.go +++ b/command/watch_test.go @@ -1,9 +1,11 @@ package command import ( - "github.com/mitchellh/cli" "strings" "testing" + + "github.com/hashicorp/consul/command/base" + "github.com/mitchellh/cli" ) func TestWatchCommand_implements(t *testing.T) { @@ -15,7 +17,12 @@ func TestWatchCommandRun(t *testing.T) { defer a1.Shutdown() ui := new(cli.MockUi) - c := &WatchCommand{Ui: ui} + c := &WatchCommand{ + Command: base.Command{ + Ui: ui, + Flags: base.FlagSetHTTP, + }, + } args := []string{"-http-addr=" + a1.httpAddr, "-type=nodes"} code := c.Run(args) diff --git a/commands.go b/commands.go index 6d4ca3e23b..6ead48b954 100644 --- a/commands.go +++ b/commands.go @@ -159,7 +159,10 @@ func init() { "leave": func() (cli.Command, error) { return &command.LeaveCommand{ - Ui: ui, + Command: base.Command{ + Flags: base.FlagSetClientHTTP, + Ui: ui, + }, }, nil }, @@ -175,38 +178,56 @@ func init() { "maint": func() (cli.Command, error) { return &command.MaintCommand{ - Ui: ui, + Command: base.Command{ + Flags: base.FlagSetClientHTTP, + Ui: ui, + }, }, nil }, "members": func() (cli.Command, error) { return &command.MembersCommand{ - Ui: ui, + Command: base.Command{ + Flags: base.FlagSetClientHTTP, + Ui: ui, + }, }, nil }, "monitor": func() (cli.Command, error) { return &command.MonitorCommand{ ShutdownCh: makeShutdownCh(), - Ui: ui, + Command: base.Command{ + Flags: base.FlagSetClientHTTP, + Ui: ui, + }, }, nil }, "operator": func() (cli.Command, error) { return &command.OperatorCommand{ - Ui: ui, + Command: base.Command{ + Flags: base.FlagSetHTTP, + Ui: ui, + }, }, nil }, "reload": func() (cli.Command, error) { return &command.ReloadCommand{ - Ui: ui, + Command: base.Command{ + Flags: base.FlagSetClientHTTP, + Ui: ui, + }, }, nil }, "rtt": func() (cli.Command, error) { return &command.RTTCommand{ - Ui: ui, + Command: base.Command{ + Flags: base.FlagSetClientHTTP, + Ui: ui, + }, }, nil }, @@ -218,19 +239,28 @@ func init() { "snapshot restore": func() (cli.Command, error) { return &command.SnapshotRestoreCommand{ - Ui: ui, + Command: base.Command{ + Flags: base.FlagSetHTTP, + Ui: ui, + }, }, nil }, "snapshot save": func() (cli.Command, error) { return &command.SnapshotSaveCommand{ - Ui: ui, + Command: base.Command{ + Flags: base.FlagSetHTTP, + Ui: ui, + }, }, nil }, "snapshot inspect": func() (cli.Command, error) { return &command.SnapshotInspectCommand{ - Ui: ui, + Command: base.Command{ + Flags: base.FlagSetNone, + Ui: ui, + }, }, nil }, @@ -244,7 +274,10 @@ func init() { "watch": func() (cli.Command, error) { return &command.WatchCommand{ ShutdownCh: makeShutdownCh(), - Ui: ui, + Command: base.Command{ + Flags: base.FlagSetHTTP, + Ui: ui, + }, }, nil }, } diff --git a/website/source/docs/commands/leave.html.markdown b/website/source/docs/commands/leave.html.markdown.erb similarity index 69% rename from website/source/docs/commands/leave.html.markdown rename to website/source/docs/commands/leave.html.markdown.erb index be55e61cbc..1b436ff1b8 100644 --- a/website/source/docs/commands/leave.html.markdown +++ b/website/source/docs/commands/leave.html.markdown.erb @@ -21,12 +21,8 @@ non-graceful leave can affect cluster availability. ## Usage -Usage: `consul leave` +Usage: `consul leave [options]` -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/docs/commands/maint.html.markdown b/website/source/docs/commands/maint.html.markdown.erb similarity index 84% rename from website/source/docs/commands/maint.html.markdown rename to website/source/docs/commands/maint.html.markdown.erb index f906332e23..f1f9dcd793 100644 --- a/website/source/docs/commands/maint.html.markdown +++ b/website/source/docs/commands/maint.html.markdown.erb @@ -24,9 +24,11 @@ health check. Usage: `consul maint [options]` -All of the command line arguments are optional. +#### API Options -The list of available flags are: +<%= partial "docs/commands/http_api_options_client" %> + +#### Command Options * `-enable` - Enable maintenance mode on a given service or node. If combined with the `-service` flag, we operate on a specific service ID. @@ -44,12 +46,6 @@ The list of available flags are: providing this flag, the `-enable` and `-disable` flags functionality is modified to operate on the given service ID. -* `-token` - ACL token to use. Defaults to that of agent. - -* `-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. - ## List mode If neither `-enable` nor `-disable` are passed, the `maint` command will diff --git a/website/source/docs/commands/members.html.markdown b/website/source/docs/commands/members.html.markdown.erb similarity index 77% rename from website/source/docs/commands/members.html.markdown rename to website/source/docs/commands/members.html.markdown.erb index 2c39b02458..8eb3fc3570 100644 --- a/website/source/docs/commands/members.html.markdown +++ b/website/source/docs/commands/members.html.markdown.erb @@ -22,16 +22,15 @@ that the failure is actually just a network partition. Usage: `consul members [options]` -The command-line flags are all optional. The list of available flags are: +#### API Options + +<%= partial "docs/commands/http_api_options_client" %> + +#### Command Options * `-detailed` - If provided, output shows more detailed information about each node. -* `-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". - * `-status` - If provided, output is filtered to only nodes matching the regular expression for status diff --git a/website/source/docs/commands/monitor.html.markdown b/website/source/docs/commands/monitor.html.markdown.erb similarity index 76% rename from website/source/docs/commands/monitor.html.markdown rename to website/source/docs/commands/monitor.html.markdown.erb index 0d609dc3b2..fed4084a6e 100644 --- a/website/source/docs/commands/monitor.html.markdown +++ b/website/source/docs/commands/monitor.html.markdown.erb @@ -22,14 +22,13 @@ logs and watch the debug logs if necessary. Usage: `consul monitor [options]` -The command-line flags are all optional. The list of available flags are: +#### API Options + +<%= partial "docs/commands/http_api_options_client" %> + +#### Command Options * `-log-level` - The log level of the messages to show. By default this is "info". This log level can be more verbose than what the agent is configured to run at. Available log levels are "trace", "debug", "info", "warn", and "err". - -* `-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". diff --git a/website/source/docs/commands/operator.html.markdown b/website/source/docs/commands/operator.html.markdown.erb similarity index 89% rename from website/source/docs/commands/operator.html.markdown rename to website/source/docs/commands/operator.html.markdown.erb index 24001366d7..a25eb69a0c 100644 --- a/website/source/docs/commands/operator.html.markdown +++ b/website/source/docs/commands/operator.html.markdown.erb @@ -28,20 +28,17 @@ endpoint. ## Usage -Usage: `consul operator [common options] [action] [options]` +Usage: `consul operator [action] [options]` Run `consul operator ` with no arguments for help on that subcommand. The following subcommands are available: * `raft` - View and modify Consul's Raft configuration. -Options common to all subcommands include: +#### 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. - -* `-token` - ACL token to use. Defaults to that of agent. +<%= partial "docs/commands/http_api_options_client" %> +<%= partial "docs/commands/http_api_options_server" %> ## Raft Operations diff --git a/website/source/docs/commands/reload.html.markdown b/website/source/docs/commands/reload.html.markdown.erb similarity index 73% rename from website/source/docs/commands/reload.html.markdown rename to website/source/docs/commands/reload.html.markdown.erb index f1a59f745e..8dac42d5ec 100644 --- a/website/source/docs/commands/reload.html.markdown +++ b/website/source/docs/commands/reload.html.markdown.erb @@ -29,10 +29,6 @@ section on the agent options page for details on which options are supported. Usage: `consul reload` -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/docs/commands/rtt.html.markdown b/website/source/docs/commands/rtt.html.markdown.erb similarity index 87% rename from website/source/docs/commands/rtt.html.markdown rename to website/source/docs/commands/rtt.html.markdown.erb index 19441ac652..9b5bf71722 100644 --- a/website/source/docs/commands/rtt.html.markdown +++ b/website/source/docs/commands/rtt.html.markdown.erb @@ -24,7 +24,11 @@ At least one node name is required. If the second node name isn't given, it is set to the agent's node name. These are the node names as known to Consul as the `consul members` command would show, not IP addresses. -The list of available flags are: +#### API Options + +<%= partial "docs/commands/http_api_options_client" %> + +#### Command Options * `-wan` - Instructs the command to use WAN coordinates instead of LAN coordinates. By default, the two nodes are assumed to be nodes in the local @@ -33,11 +37,6 @@ The list of available flags are: and the datacenter (eg. "myserver.dc1"). It is not possible to measure between LAN coordinates and WAN coordinates, so both nodes must be in the same pool. - -* `-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. - The following environment variables control accessing the HTTP server via SSL: * `CONSUL_HTTP_SSL` Set this to enable SSL diff --git a/website/source/docs/commands/snapshot/restore.html.markdown.erb b/website/source/docs/commands/snapshot/restore.html.markdown.erb index 8a28440ad3..27c0f6e5b0 100644 --- a/website/source/docs/commands/snapshot/restore.html.markdown.erb +++ b/website/source/docs/commands/snapshot/restore.html.markdown.erb @@ -28,6 +28,7 @@ Usage: `consul snapshot restore [options] FILE` #### API Options <%= partial "docs/commands/http_api_options_client" %> +<%= partial "docs/commands/http_api_options_server" %> ## Examples diff --git a/website/source/docs/commands/snapshot/save.html.markdown.erb b/website/source/docs/commands/snapshot/save.html.markdown.erb index e6029172ad..14033c8e18 100644 --- a/website/source/docs/commands/snapshot/save.html.markdown.erb +++ b/website/source/docs/commands/snapshot/save.html.markdown.erb @@ -23,6 +23,7 @@ Usage: `consul snapshot save [options] FILE` #### API Options <%= partial "docs/commands/http_api_options_client" %> +<%= partial "docs/commands/http_api_options_server" %> ## Examples @@ -41,7 +42,7 @@ After the snapshot is written to the given file it is read back and verified for integrity. To create a potentially stale snapshot from any available server, use the stale -consisentcy mode: +consistency mode: ```text $ consul snapshot save -stale backup.snap diff --git a/website/source/docs/commands/version.html.markdown b/website/source/docs/commands/version.html.markdown new file mode 100644 index 0000000000..0ed1a44610 --- /dev/null +++ b/website/source/docs/commands/version.html.markdown @@ -0,0 +1,20 @@ +--- +layout: "docs" +page_title: "Commands: Version" +sidebar_current: "docs-commands-version" +description: |- + The `version` command prints the version of Consul and the protocol versions it understands for speaking to other agents. + +--- + +# Consul Version + +Command: `consul version` + +The `version` command prints the version of Consul and the protocol versions it understands for speaking to other agents. + +```text +$ consul version +Consul v0.7.4 +Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents) +``` diff --git a/website/source/docs/commands/watch.html.markdown b/website/source/docs/commands/watch.html.markdown.erb similarity index 78% rename from website/source/docs/commands/watch.html.markdown rename to website/source/docs/commands/watch.html.markdown.erb index 14cf8d2a0e..b940aa8b40 100644 --- a/website/source/docs/commands/watch.html.markdown +++ b/website/source/docs/commands/watch.html.markdown.erb @@ -27,18 +27,12 @@ data view. Depending on the type, various options may be required or optionally provided. There is more documentation on watch [specifications here](/docs/agent/watches.html). -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 the agent. - -* `-token` - ACL token to use. Defaults to that of the agent. - -* `-stale=[true|false]` - Specifies if watch data is permitted to be stale. Defaults - to false. +#### Command Options * `-key` - Key to watch. Only for `key` type. diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index c2c385eaa9..b36421f9c3 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -173,6 +173,10 @@ + > + version + + > watch