diff --git a/.changelog/18223.txt b/.changelog/18223.txt new file mode 100644 index 000000000..067ca64f4 --- /dev/null +++ b/.changelog/18223.txt @@ -0,0 +1,3 @@ +```release-note:feature +cli: `consul members` command uses `-filter` expression to filter members based on bexpr. +``` diff --git a/agent/agent_endpoint.go b/agent/agent_endpoint.go index f9e02f8f1..23bfa49c8 100644 --- a/agent/agent_endpoint.go +++ b/agent/agent_endpoint.go @@ -621,6 +621,21 @@ func (s *HTTPHandlers) AgentMembers(resp http.ResponseWriter, req *http.Request) } } + // filter the members by parsed filter expression + var filterExpression string + s.parseFilter(req, &filterExpression) + if filterExpression != "" { + filter, err := bexpr.CreateFilter(filterExpression, nil, members) + if err != nil { + return nil, err + } + raw, err := filter.Execute(members) + if err != nil { + return nil, err + } + members = raw.([]serf.Member) + } + total := len(members) if err := s.agent.filterMembers(token, &members); err != nil { return nil, err diff --git a/api/agent.go b/api/agent.go index f45929cb5..b09ed1c1c 100644 --- a/api/agent.go +++ b/api/agent.go @@ -274,6 +274,8 @@ type MembersOpts struct { // Segment is the LAN segment to show members for. Setting this to the // AllSegments value above will show members in all segments. Segment string + + Filter string } // AgentServiceRegistration is used to register a new service @@ -790,6 +792,10 @@ func (a *Agent) MembersOpts(opts MembersOpts) ([]*AgentMember, error) { r.params.Set("wan", "1") } + if opts.Filter != "" { + r.params.Set("filter", opts.Filter) + } + _, resp, err := a.c.doRequest(r) if err != nil { return nil, err diff --git a/api/agent_test.go b/api/agent_test.go index ebf738154..dce525ebc 100644 --- a/api/agent_test.go +++ b/api/agent_test.go @@ -155,6 +155,16 @@ func TestAPI_AgentMembersOpts(t *testing.T) { if len(members) != 2 { t.Fatalf("bad: %v", members) } + + members, err = agent.MembersOpts(MembersOpts{ + WAN: true, + Filter: `Tags["dc"] == dc2`, + }) + if err != nil { + t.Fatalf("err: %v", err) + } + + require.Equal(t, 1, len(members)) } func TestAPI_AgentMembers(t *testing.T) { diff --git a/command/members/members.go b/command/members/members.go index e6be185e5..9895837f6 100644 --- a/command/members/members.go +++ b/command/members/members.go @@ -33,6 +33,7 @@ type cmd struct { wan bool statusFilter string segment string + filter string } func New(ui cli.Ui) *cmd { @@ -54,6 +55,7 @@ func (c *cmd) init() { c.flags.StringVar(&c.segment, "segment", consulapi.AllSegments, "(Enterprise-only) If provided, output is filtered to only nodes in"+ "the given segment.") + c.flags.StringVar(&c.filter, "filter", "", "Filter to use with the request") c.http = &flags.HTTPFlags{} flags.Merge(c.flags, c.http.ClientFlags()) @@ -83,6 +85,7 @@ func (c *cmd) Run(args []string) int { opts := consulapi.MembersOpts{ Segment: c.segment, WAN: c.wan, + Filter: c.filter, } members, err := client.Agent().MembersOpts(opts) if err != nil { diff --git a/website/content/commands/members.mdx b/website/content/commands/members.mdx index 4e6b73cae..ff1df561a 100644 --- a/website/content/commands/members.mdx +++ b/website/content/commands/members.mdx @@ -48,6 +48,12 @@ Usage: `consul members [options]` in the WAN gossip pool. These are generally all the server nodes in each datacenter. +- `-filter=` - Expression to use for filtering the results, + e.g., `-filter='Tags["dc"] == dc2'`. + See the [`/catalog/nodes` API documentation](/consul/api-docs/catalog#filtering) for a + description of what is filterable. + + #### Enterprise Options @include 'http_api_partition_options.mdx'