Skip to content

Commit

Permalink
feat: add JSON output to server members command
Browse files Browse the repository at this point in the history
  • Loading branch information
farbodahm committed Mar 5, 2023
1 parent b07af57 commit 6921eae
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 9 deletions.
85 changes: 76 additions & 9 deletions command/server_members.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ Server Members Options:
-verbose
Show detailed information about each member. This dumps a raw set of tags
which shows more information than the default output format.
-json
Output the Server members information in JSON format.
`
return strings.TrimSpace(helpText)
}
Expand All @@ -43,6 +46,7 @@ func (c *ServerMembersCommand) AutocompleteFlags() complete.Flags {
return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
complete.Flags{
"-detailed": complete.PredictNothing,
"-json": complete.PredictNothing,
})
}

Expand All @@ -57,12 +61,13 @@ func (c *ServerMembersCommand) Synopsis() string {
func (c *ServerMembersCommand) Name() string { return "server members" }

func (c *ServerMembersCommand) Run(args []string) int {
var detailed, verbose bool
var detailed, verbose, json bool

flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
flags.Usage = func() { c.Ui.Output(c.Help()) }
flags.BoolVar(&detailed, "detailed", false, "Show detailed output")
flags.BoolVar(&verbose, "verbose", false, "Show detailed output")
flags.BoolVar(&json, "json", false, "Show output in JSON format")

if err := flags.Parse(args); err != nil {
return 1
Expand Down Expand Up @@ -106,16 +111,34 @@ func (c *ServerMembersCommand) Run(args []string) int {
// Determine the leaders per region.
leaders, leaderErr := regionLeaders(client, srvMembers.Members)

// Format the list
var out []string
if verbose {
out = verboseOutput(srvMembers.Members, leaders)
if !json {
// Format the list
var out []string
if verbose {
out = verboseOutput(srvMembers.Members, leaders)
} else {
out = standardOutput(srvMembers.Members, leaders)
}

// Dump the list
c.Ui.Output(columnize.SimpleFormat(out))
} else {
out = standardOutput(srvMembers.Members, leaders)
}
var output_map []map[string]any
if verbose {
output_map = mapVerboseOutput(srvMembers.Members, leaders)
} else {
output_map = mapStandardOutput(srvMembers.Members, leaders)
}

// Dump the list
c.Ui.Output(columnize.SimpleFormat(out))
json_output, jsonErr := Format(true, "", output_map)
if jsonErr != nil {
c.Ui.Output("")
c.Ui.Warn(fmt.Sprintf("Error formating json: %s", jsonErr))
return 1
}

c.Ui.Output(json_output)
}

// If there were leader errors display a warning
if leaderErr != nil {
Expand Down Expand Up @@ -216,3 +239,47 @@ func isLeader(member *api.AgentMember, leaders map[string]string) bool {
regLeader, ok := leaders[reg]
return ok && regLeader == addr
}

// mapStandardOutput returns the standard output in a map so it can be used to create JSON output
func mapStandardOutput(members []*api.AgentMember, leaders map[string]string) []map[string]any {
membersMap := make([]map[string]any, len(members)-1, len(members))

for _, member := range members {
membersMap = append(membersMap, map[string]any{
"Name": member.Name,
"Address": member.Addr,
"Port": member.Port,
"Status": member.Status,
"Leader": isLeader(member, leaders),
"Raft Version": member.Tags["raft_vsn"],
"Build": member.Tags["build"],
"Datacenter": member.Tags["dc"],
"Region": member.Tags["region"],
})
}

return membersMap
}

// mapStandardOutput returns the verbose output in a map so it can be used to create JSON output
func mapVerboseOutput(members []*api.AgentMember, leaders map[string]string) []map[string]any {
membersMap := make([]map[string]any, len(members)-1, len(members))

for _, member := range members {
membersMap = append(membersMap, map[string]any{
"Name": member.Name,
"Address": member.Addr,
"Port": member.Port,
"Status": member.Status,
"Leader": isLeader(member, leaders),
"Protocol": member.ProtocolCur,
"Raft Version": member.Tags["raft_vsn"],
"Build": member.Tags["build"],
"Datacenter": member.Tags["dc"],
"Region": member.Tags["region"],
"Tags": member.Tags,
})
}

return membersMap
}
24 changes: 24 additions & 0 deletions command/server_members_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package command

import (
"encoding/json"
"fmt"
"strings"
"testing"
Expand Down Expand Up @@ -49,6 +50,29 @@ func TestServerMembersCommand_Run(t *testing.T) {
if out := ui.OutputWriter.String(); !strings.Contains(out, "Tags") {
t.Fatalf("expected tags in output, got: %s", out)
}
// Query members with JSON normal output
ui.OutputWriter.Reset()
ui.ErrorWriter.Reset()
var jsonMap []map[string]interface{}
var out string
if code := cmd.Run([]string{"-address=" + url, "-json"}); code != 0 {
t.Fatalf("expected exit 0, got: %d", code)
}
out = ui.OutputWriter.String()
if err = json.Unmarshal([]byte(out), &jsonMap); err != nil {
t.Fatalf("failed to parse JSON: %s", err)
}

// Query members with JSON verbose output
ui.OutputWriter.Reset()
ui.ErrorWriter.Reset()
if code := cmd.Run([]string{"-address=" + url, "-detailed", "-json"}); code != 0 {
t.Fatalf("expected exit 0, got: %d", code)
}
out = ui.OutputWriter.String()
if err = json.Unmarshal([]byte(out), &jsonMap); err != nil {
t.Fatalf("failed to parse JSON: %s", err)
}
}

func TestMembersCommand_Fails(t *testing.T) {
Expand Down
56 changes: 56 additions & 0 deletions website/content/docs/commands/server/members.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ capability.
for each member. This mode reveals additional information not displayed in
the standard output format.

- `-json`: Output the Server members information in JSON format. Can be used both
for normal output and detailed output.

## Examples

Default view:
Expand All @@ -57,3 +60,56 @@ server-1.global 10.0.0.8 4648 alive true 2 3 1.3.0
server-2.global 10.0.0.9 4648 alive false 2 3 1.3.0 dc1 global id=04594bee-fec9-4cec-f308-eebe82025ae7,dc=dc1,expect=3,rpc_addr=10.0.0.9,raft_vsn=3,port=4647,role=nomad,region=global,build=1.3.0
server-3.global 10.0.0.10 4648 alive false 2 3 1.3.0 dc1 global region=global,dc=dc1,rpc_addr=10.0.0.10,raft_vsn=3,build=1.3.0,expect=3,id=59542f6c-fb0e-50f1-4c9f-98bb593e9fe8,role=nomad,port=4647
```

JSON view:

```shell-session
$ nomad server members -json
[
{
"Leader": true,
"Raft Version": "3",
"Build": "1.5.1-dev",
"Region": "global",
"Name": "server-1.global",
"Status": "alive",
"Datacenter": "dc1",
"Address": "172.18.1.253",
"Port": 4648
}
]
```

JSON verbose view:

```shell-session
$ nomad server members -verbose -json
[
{
"Port": 4648,
"Leader": true,
"Protocol": 2,
"Datacenter": "dc1",
"Tags": {
"raft_vsn": "3",
"region": "global",
"build": "1.5.1-dev",
"bootstrap": "1",
"role": "nomad",
"expect": "1",
"dc": "dc1",
"rpc_addr": "172.18.1.253",
"revision": "b07af5761846a59ac91e4826c9a0f8e38c91f70b+CHANGES",
"id": "34385ed2-1dc8-ad7d-1a3a-ba665fda91c8",
"vsn": "1",
"port": "4647"
},
"Region": "global",
"Name": "server-1.global",
"Address": "172.18.1.253",
"Status": "alive",
"Raft Version": "3",
"Build": "1.5.1-dev"
}
]
```

0 comments on commit 6921eae

Please sign in to comment.