diff --git a/command/members.go b/command/members.go index fde3c18914..11dfabccce 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/commands.go b/commands.go index 8f24dfc542..efd9fc1df5 100644 --- a/commands.go +++ b/commands.go @@ -145,7 +145,10 @@ func init() { "members": func() (cli.Command, error) { return &command.MembersCommand{ - Ui: ui, + Command: base.Command{ + Flags: base.FlagSetClientHTTP, + Ui: ui, + }, }, nil }, 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