Matt Keeler afa1cc98d1
Implement data filtering of some endpoints (#5579)
Fixes: #4222 

# Data Filtering

This PR will implement filtering for the following endpoints:

## Supported HTTP Endpoints

- `/agent/checks`
- `/agent/services`
- `/catalog/nodes`
- `/catalog/service/:service`
- `/catalog/connect/:service`
- `/catalog/node/:node`
- `/health/node/:node`
- `/health/checks/:service`
- `/health/service/:service`
- `/health/connect/:service`
- `/health/state/:state`
- `/internal/ui/nodes`
- `/internal/ui/services`

More can be added going forward and any endpoint which is used to list some data is a good candidate.

## Usage

When using the HTTP API a `filter` query parameter can be used to pass a filter expression to Consul. Filter Expressions take the general form of:

```
<selector> == <value>
<selector> != <value>
<value> in <selector>
<value> not in <selector>
<selector> contains <value>
<selector> not contains <value>
<selector> is empty
<selector> is not empty
not <other expression>
<expression 1> and <expression 2>
<expression 1> or <expression 2>
```

Normal boolean logic and precedence is supported. All of the actual filtering and evaluation logic is coming from the [go-bexpr](https://github.com/hashicorp/go-bexpr) library

## Other changes

Adding the `Internal.ServiceDump` RPC endpoint. This will allow the UI to filter services better.
2019-04-16 12:00:15 -04:00

132 lines
2.7 KiB
Go

package bexpr
import (
"fmt"
"io"
"strings"
)
// TODO - Probably should make most of what is in here un-exported
//go:generate pigeon -o grammar.go -optimize-parser grammar.peg
//go:generate goimports -w grammar.go
type Expression interface {
ExpressionDump(w io.Writer, indent string, level int)
}
type UnaryOperator int
const (
UnaryOpNot UnaryOperator = iota
)
func (op UnaryOperator) String() string {
switch op {
case UnaryOpNot:
return "Not"
default:
return "UNKNOWN"
}
}
type BinaryOperator int
const (
BinaryOpAnd BinaryOperator = iota
BinaryOpOr
)
func (op BinaryOperator) String() string {
switch op {
case BinaryOpAnd:
return "And"
case BinaryOpOr:
return "Or"
default:
return "UNKNOWN"
}
}
type MatchOperator int
const (
MatchEqual MatchOperator = iota
MatchNotEqual
MatchIn
MatchNotIn
MatchIsEmpty
MatchIsNotEmpty
)
func (op MatchOperator) String() string {
switch op {
case MatchEqual:
return "Equal"
case MatchNotEqual:
return "Not Equal"
case MatchIn:
return "In"
case MatchNotIn:
return "Not In"
case MatchIsEmpty:
return "Is Empty"
case MatchIsNotEmpty:
return "Is Not Empty"
default:
return "UNKNOWN"
}
}
type MatchValue struct {
Raw string
Converted interface{}
}
type UnaryExpression struct {
Operator UnaryOperator
Operand Expression
}
type BinaryExpression struct {
Left Expression
Operator BinaryOperator
Right Expression
}
type Selector []string
func (sel Selector) String() string {
return strings.Join([]string(sel), ".")
}
type MatchExpression struct {
Selector Selector
Operator MatchOperator
Value *MatchValue
}
func (expr *UnaryExpression) ExpressionDump(w io.Writer, indent string, level int) {
localIndent := strings.Repeat(indent, level)
fmt.Fprintf(w, "%s%s {\n", localIndent, expr.Operator.String())
expr.Operand.ExpressionDump(w, indent, level+1)
fmt.Fprintf(w, "%s}\n", localIndent)
}
func (expr *BinaryExpression) ExpressionDump(w io.Writer, indent string, level int) {
localIndent := strings.Repeat(indent, level)
fmt.Fprintf(w, "%s%s {\n", localIndent, expr.Operator.String())
expr.Left.ExpressionDump(w, indent, level+1)
expr.Right.ExpressionDump(w, indent, level+1)
fmt.Fprintf(w, "%s}\n", localIndent)
}
func (expr *MatchExpression) ExpressionDump(w io.Writer, indent string, level int) {
switch expr.Operator {
case MatchEqual, MatchNotEqual, MatchIn, MatchNotIn:
fmt.Fprintf(w, "%[1]s%[3]s {\n%[2]sSelector: %[4]v\n%[2]sValue: %[5]q\n%[1]s}\n", strings.Repeat(indent, level), strings.Repeat(indent, level+1), expr.Operator.String(), expr.Selector, expr.Value.Raw)
default:
fmt.Fprintf(w, "%[1]s%[3]s {\n%[2]sSelector: %[4]v\n%[1]s}\n", strings.Repeat(indent, level), strings.Repeat(indent, level+1), expr.Operator.String(), expr.Selector)
}
}