member cli: add -filter expression to flags (#18223)

* member cli: add -filter expression to flags

* changelog

* update doc

* Add test cases

* use quote
This commit is contained in:
cskh 2023-07-25 13:54:52 -04:00 committed by GitHub
parent 090e869a55
commit 31d2813714
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 58 additions and 0 deletions

3
.changelog/18223.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
cli: `consul members` command uses `-filter` expression to filter members based on bexpr.
```

View File

@ -619,6 +619,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) total := len(members)
if err := s.agent.filterMembers(token, &members); err != nil { if err := s.agent.filterMembers(token, &members); err != nil {
return nil, err return nil, err

View File

@ -274,6 +274,8 @@ type MembersOpts struct {
// Segment is the LAN segment to show members for. Setting this to the // Segment is the LAN segment to show members for. Setting this to the
// AllSegments value above will show members in all segments. // AllSegments value above will show members in all segments.
Segment string Segment string
Filter string
} }
// AgentServiceRegistration is used to register a new service // 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") r.params.Set("wan", "1")
} }
if opts.Filter != "" {
r.params.Set("filter", opts.Filter)
}
_, resp, err := a.c.doRequest(r) _, resp, err := a.c.doRequest(r)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -155,6 +155,31 @@ func TestAPI_AgentMembersOpts(t *testing.T) {
if len(members) != 2 { if len(members) != 2 {
t.Fatalf("bad: %v", members) 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))
members, err = agent.MembersOpts(MembersOpts{
WAN: true,
Filter: `Tags["dc"] == "not-Exist"`,
})
if err != nil {
t.Fatalf("err: %v", err)
}
require.Equal(t, 0, len(members))
_, err = agent.MembersOpts(MembersOpts{
WAN: true,
Filter: `Tags["dc"] == invalid-bexpr-value`,
})
require.ErrorContains(t, err, "Failed to create boolean expression evaluator")
} }
func TestAPI_AgentMembers(t *testing.T) { func TestAPI_AgentMembers(t *testing.T) {

View File

@ -33,6 +33,7 @@ type cmd struct {
wan bool wan bool
statusFilter string statusFilter string
segment string segment string
filter string
} }
func New(ui cli.Ui) *cmd { func New(ui cli.Ui) *cmd {
@ -54,6 +55,7 @@ func (c *cmd) init() {
c.flags.StringVar(&c.segment, "segment", consulapi.AllSegments, c.flags.StringVar(&c.segment, "segment", consulapi.AllSegments,
"(Enterprise-only) If provided, output is filtered to only nodes in"+ "(Enterprise-only) If provided, output is filtered to only nodes in"+
"the given segment.") "the given segment.")
c.flags.StringVar(&c.filter, "filter", "", "Filter to use with the request")
c.http = &flags.HTTPFlags{} c.http = &flags.HTTPFlags{}
flags.Merge(c.flags, c.http.ClientFlags()) flags.Merge(c.flags, c.http.ClientFlags())
@ -83,6 +85,7 @@ func (c *cmd) Run(args []string) int {
opts := consulapi.MembersOpts{ opts := consulapi.MembersOpts{
Segment: c.segment, Segment: c.segment,
WAN: c.wan, WAN: c.wan,
Filter: c.filter,
} }
members, err := client.Agent().MembersOpts(opts) members, err := client.Agent().MembersOpts(opts)
if err != nil { if err != nil {

View File

@ -48,6 +48,12 @@ Usage: `consul members [options]`
in the WAN gossip pool. These are generally all the server nodes in in the WAN gossip pool. These are generally all the server nodes in
each datacenter. each datacenter.
- `-filter=<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 #### Enterprise Options
@include 'http_api_partition_options.mdx' @include 'http_api_partition_options.mdx'