cli: Add `json` and `-t` flags to `server members` command (#16444)
* cli: Add and flags to server members * Update website/content/docs/commands/server/members.mdx Co-authored-by: James Rasell <jrasell@users.noreply.github.com> * Update website/content/docs/commands/server/members.mdx Co-authored-by: James Rasell <jrasell@users.noreply.github.com> * cli: update the server memebers tests to use must * cli: add flags addition to changelog --------- Co-authored-by: James Rasell <jrasell@users.noreply.github.com>
This commit is contained in:
parent
e4e53872be
commit
eeb3766575
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
cli: Added `-json` and `-t` flag to `server members` command
|
||||||
|
```
|
|
@ -35,6 +35,12 @@ Server Members Options:
|
||||||
-verbose
|
-verbose
|
||||||
Show detailed information about each member. This dumps a raw set of tags
|
Show detailed information about each member. This dumps a raw set of tags
|
||||||
which shows more information than the default output format.
|
which shows more information than the default output format.
|
||||||
|
|
||||||
|
-json
|
||||||
|
Output the latest information about each member in a JSON format.
|
||||||
|
|
||||||
|
-t
|
||||||
|
Format and display latest information about each member using a Go template.
|
||||||
`
|
`
|
||||||
return strings.TrimSpace(helpText)
|
return strings.TrimSpace(helpText)
|
||||||
}
|
}
|
||||||
|
@ -43,6 +49,9 @@ func (c *ServerMembersCommand) AutocompleteFlags() complete.Flags {
|
||||||
return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
|
return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
|
||||||
complete.Flags{
|
complete.Flags{
|
||||||
"-detailed": complete.PredictNothing,
|
"-detailed": complete.PredictNothing,
|
||||||
|
"-verbose": complete.PredictNothing,
|
||||||
|
"-json": complete.PredictNothing,
|
||||||
|
"-t": complete.PredictAnything,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,12 +66,15 @@ func (c *ServerMembersCommand) Synopsis() string {
|
||||||
func (c *ServerMembersCommand) Name() string { return "server members" }
|
func (c *ServerMembersCommand) Name() string { return "server members" }
|
||||||
|
|
||||||
func (c *ServerMembersCommand) Run(args []string) int {
|
func (c *ServerMembersCommand) Run(args []string) int {
|
||||||
var detailed, verbose bool
|
var detailed, verbose, json bool
|
||||||
|
var tmpl string
|
||||||
|
|
||||||
flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
|
flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
|
||||||
flags.Usage = func() { c.Ui.Output(c.Help()) }
|
flags.Usage = func() { c.Ui.Output(c.Help()) }
|
||||||
flags.BoolVar(&detailed, "detailed", false, "Show detailed output")
|
flags.BoolVar(&detailed, "detailed", false, "Show detailed output")
|
||||||
flags.BoolVar(&verbose, "verbose", false, "Show detailed output")
|
flags.BoolVar(&verbose, "verbose", false, "Show detailed output")
|
||||||
|
flags.BoolVar(&json, "json", false, "")
|
||||||
|
flags.StringVar(&tmpl, "t", "", "")
|
||||||
|
|
||||||
if err := flags.Parse(args); err != nil {
|
if err := flags.Parse(args); err != nil {
|
||||||
return 1
|
return 1
|
||||||
|
@ -103,6 +115,17 @@ func (c *ServerMembersCommand) Run(args []string) int {
|
||||||
// Sort the members
|
// Sort the members
|
||||||
sort.Sort(api.AgentMembersNameSort(srvMembers.Members))
|
sort.Sort(api.AgentMembersNameSort(srvMembers.Members))
|
||||||
|
|
||||||
|
if json || len(tmpl) > 0 {
|
||||||
|
out, err := Format(json, tmpl, srvMembers.Members)
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Ui.Output(out)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
// Determine the leaders per region.
|
// Determine the leaders per region.
|
||||||
leaders, leaderErr := regionLeaders(client, srvMembers.Members)
|
leaders, leaderErr := regionLeaders(client, srvMembers.Members)
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/nomad/api"
|
||||||
"github.com/hashicorp/nomad/ci"
|
"github.com/hashicorp/nomad/ci"
|
||||||
"github.com/hashicorp/nomad/command/agent"
|
"github.com/hashicorp/nomad/command/agent"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
|
"github.com/shoenig/test/must"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestServerMembersCommand_Implements(t *testing.T) {
|
func TestServerMembersCommand_Implements(t *testing.T) {
|
||||||
|
@ -25,30 +28,46 @@ func TestServerMembersCommand_Run(t *testing.T) {
|
||||||
|
|
||||||
// Get our own node name
|
// Get our own node name
|
||||||
name, err := client.Agent().NodeName()
|
name, err := client.Agent().NodeName()
|
||||||
if err != nil {
|
must.NoError(t, err)
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query the members
|
// Query the members
|
||||||
if code := cmd.Run([]string{"-address=" + url}); code != 0 {
|
code := cmd.Run([]string{"-address=" + url})
|
||||||
t.Fatalf("expected exit 0, got: %d", code)
|
|
||||||
}
|
|
||||||
if out := ui.OutputWriter.String(); !strings.Contains(out, name) {
|
if out := ui.OutputWriter.String(); !strings.Contains(out, name) {
|
||||||
t.Fatalf("expected %q in output, got: %s", name, out)
|
t.Fatalf("expected %q in output, got: %s", name, out)
|
||||||
}
|
}
|
||||||
ui.OutputWriter.Reset()
|
ui.OutputWriter.Reset()
|
||||||
|
|
||||||
// Query members with verbose output
|
// Query members with verbose output
|
||||||
if code := cmd.Run([]string{"-address=" + url, "-verbose"}); code != 0 {
|
code = cmd.Run([]string{"-address=" + url, "-verbose"})
|
||||||
t.Fatalf("expected exit 0, got: %d", code)
|
must.Zero(t, code)
|
||||||
}
|
|
||||||
// Still support previous detailed flag
|
// Still support previous detailed flag
|
||||||
if code := cmd.Run([]string{"-address=" + url, "-detailed"}); code != 0 {
|
code = cmd.Run([]string{"-address=" + url, "-detailed"})
|
||||||
t.Fatalf("expected exit 0, got: %d", code)
|
must.Zero(t, code)
|
||||||
}
|
|
||||||
if out := ui.OutputWriter.String(); !strings.Contains(out, "Tags") {
|
must.StrContains(t, ui.OutputWriter.String(), "Tags")
|
||||||
t.Fatalf("expected tags in output, got: %s", out)
|
|
||||||
}
|
ui.OutputWriter.Reset()
|
||||||
|
|
||||||
|
// List json
|
||||||
|
code = cmd.Run([]string{"-address=" + url, "-json"})
|
||||||
|
must.Zero(t, code)
|
||||||
|
|
||||||
|
outJson := []api.AgentMember{}
|
||||||
|
err = json.Unmarshal(ui.OutputWriter.Bytes(), &outJson)
|
||||||
|
must.NoError(t, err)
|
||||||
|
|
||||||
|
ui.OutputWriter.Reset()
|
||||||
|
|
||||||
|
// Go template to format the output
|
||||||
|
code = cmd.Run([]string{"-address=" + url, "-t", "{{range .}}{{ .Status }}{{end}}"})
|
||||||
|
must.Zero(t, code)
|
||||||
|
|
||||||
|
out := ui.OutputWriter.String()
|
||||||
|
must.StrContains(t, out, "alive")
|
||||||
|
|
||||||
|
ui.ErrorWriter.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMembersCommand_Fails(t *testing.T) {
|
func TestMembersCommand_Fails(t *testing.T) {
|
||||||
|
@ -57,21 +76,17 @@ func TestMembersCommand_Fails(t *testing.T) {
|
||||||
cmd := &ServerMembersCommand{Meta: Meta{Ui: ui}}
|
cmd := &ServerMembersCommand{Meta: Meta{Ui: ui}}
|
||||||
|
|
||||||
// Fails on misuse
|
// Fails on misuse
|
||||||
if code := cmd.Run([]string{"some", "bad", "args"}); code != 1 {
|
code := cmd.Run([]string{"some", "bad", "args"})
|
||||||
t.Fatalf("expected exit code 1, got: %d", code)
|
must.One(t, code)
|
||||||
}
|
|
||||||
if out := ui.ErrorWriter.String(); !strings.Contains(out, commandErrorText(cmd)) {
|
must.StrContains(t, ui.ErrorWriter.String(), commandErrorText(cmd))
|
||||||
t.Fatalf("expected help output, got: %s", out)
|
|
||||||
}
|
|
||||||
ui.ErrorWriter.Reset()
|
ui.ErrorWriter.Reset()
|
||||||
|
|
||||||
// Fails on connection failure
|
// Fails on connection failure
|
||||||
if code := cmd.Run([]string{"-address=nope"}); code != 1 {
|
code = cmd.Run([]string{"-address=nope"})
|
||||||
t.Fatalf("expected exit code 1, got: %d", code)
|
must.One(t, code)
|
||||||
}
|
|
||||||
if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error querying servers") {
|
must.StrContains(t, ui.ErrorWriter.String(), "Error querying servers")
|
||||||
t.Fatalf("expected failed query error, got: %s", out)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that a single server region that left should still
|
// Tests that a single server region that left should still
|
||||||
|
@ -99,35 +114,29 @@ func TestServerMembersCommand_MultiRegion_Leave(t *testing.T) {
|
||||||
addr := fmt.Sprintf("127.0.0.1:%d",
|
addr := fmt.Sprintf("127.0.0.1:%d",
|
||||||
srv1.Agent.Server().GetConfig().SerfConfig.MemberlistConfig.BindPort)
|
srv1.Agent.Server().GetConfig().SerfConfig.MemberlistConfig.BindPort)
|
||||||
|
|
||||||
if _, err := srv2.Agent.Server().Join([]string{addr}); err != nil {
|
_, err := srv2.Agent.Server().Join([]string{addr})
|
||||||
t.Fatalf("Join err: %v", err)
|
must.NoError(t, err)
|
||||||
}
|
|
||||||
ui := cli.NewMockUi()
|
ui := cli.NewMockUi()
|
||||||
cmd := &ServerMembersCommand{Meta: Meta{Ui: ui}}
|
cmd := &ServerMembersCommand{Meta: Meta{Ui: ui}}
|
||||||
|
|
||||||
// Get our own node name
|
// Get our own node name
|
||||||
name, err := client1.Agent().NodeName()
|
name, err := client1.Agent().NodeName()
|
||||||
if err != nil {
|
must.NoError(t, err)
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query the members
|
// Query the members
|
||||||
if code := cmd.Run([]string{"-address=" + url}); code != 0 {
|
code := cmd.Run([]string{"-address=" + url})
|
||||||
t.Fatalf("expected exit 0, got: %d", code)
|
must.Zero(t, code)
|
||||||
}
|
|
||||||
if out := ui.OutputWriter.String(); !strings.Contains(out, name) {
|
must.StrContains(t, ui.OutputWriter.String(), name)
|
||||||
t.Fatalf("expected %q in output, got: %s", name, out)
|
|
||||||
}
|
|
||||||
ui.OutputWriter.Reset()
|
ui.OutputWriter.Reset()
|
||||||
|
|
||||||
// Make one of the servers leave
|
// Make one of the servers leave
|
||||||
srv2.Agent.Leave()
|
srv2.Agent.Leave()
|
||||||
|
|
||||||
// Query again, should still contain expected output
|
// Query again, should still contain expected output
|
||||||
if code := cmd.Run([]string{"-address=" + url}); code != 0 {
|
code = cmd.Run([]string{"-address=" + url})
|
||||||
t.Fatalf("expected exit 0, got: %d", code)
|
must.Zero(t, code)
|
||||||
}
|
|
||||||
if out := ui.OutputWriter.String(); !strings.Contains(out, name) {
|
must.StrContains(t, ui.OutputWriter.String(), name)
|
||||||
t.Fatalf("expected %q in output, got: %s", name, out)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,10 @@ capability.
|
||||||
for each member. This mode reveals additional information not displayed in
|
for each member. This mode reveals additional information not displayed in
|
||||||
the standard output format.
|
the standard output format.
|
||||||
|
|
||||||
|
- `-json`: Output the server memebers in its JSON format.
|
||||||
|
|
||||||
|
- `-t`: Format and display the server memebers using a Go template.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
Default view:
|
Default view:
|
||||||
|
@ -57,3 +61,44 @@ 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-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
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `-json` flag can be used to get the latest server members information in json format:
|
||||||
|
|
||||||
|
```shell-session
|
||||||
|
$ nomad server members -json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Addr": "127.0.0.1",
|
||||||
|
"DelegateCur": 4,
|
||||||
|
"DelegateMax": 5,
|
||||||
|
"DelegateMin": 2,
|
||||||
|
"Name": "bacon-mac.global",
|
||||||
|
"Port": 4648,
|
||||||
|
"ProtocolCur": 2,
|
||||||
|
"ProtocolMax": 5,
|
||||||
|
"ProtocolMin": 1,
|
||||||
|
"Status": "alive",
|
||||||
|
"Tags": {
|
||||||
|
"build": "1.5.0",
|
||||||
|
"region": "global",
|
||||||
|
"rpc_addr": "127.0.0.1",
|
||||||
|
"role": "nomad",
|
||||||
|
"raft_vsn": "3",
|
||||||
|
"bootstrap": "1",
|
||||||
|
"expect": "1",
|
||||||
|
"dc": "dc1",
|
||||||
|
"port": "4647",
|
||||||
|
"revision": "fc40c491cacec3d8ec3f2f98cd82b9068a50797c",
|
||||||
|
"vsn": "1",
|
||||||
|
"id": "354fd9a8-4228-ca8e-8b93-3cf002514910"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use the `-t` flag to format and display the server members information using a Go template:
|
||||||
|
|
||||||
|
```shell-session
|
||||||
|
$ nomad server members -t '{{range .}}{{printf "%s: %s" .Name .Status }}{{end}}'
|
||||||
|
bacon-mac.global: alive
|
||||||
|
```
|
Loading…
Reference in New Issue