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

157 lines
4.2 KiB
Plaintext

{
package bexpr
import (
"strconv"
"strings"
)
}
Input <- _? "(" _? expr:OrExpression _? ")" _? EOF {
return expr, nil
} / _? expr:OrExpression _? EOF {
return expr, nil
}
OrExpression <- left:AndExpression _ "or" _ right:OrExpression {
return &BinaryExpression{
Operator: BinaryOpOr,
Left: left.(Expression),
Right: right.(Expression),
}, nil
} / expr:AndExpression {
return expr, nil
}
AndExpression <- left:NotExpression _ "and" _ right:AndExpression {
return &BinaryExpression{
Operator: BinaryOpAnd,
Left: left.(Expression),
Right: right.(Expression),
}, nil
} / expr:NotExpression {
return expr, nil
}
NotExpression <- "not" _ expr:NotExpression {
if unary, ok := expr.(*UnaryExpression); ok && unary.Operator == UnaryOpNot {
// small optimization to get rid unnecessary levels of AST nodes
// for things like: not not foo == 3 which is equivalent to foo == 3
return unary.Operand, nil
}
return &UnaryExpression{
Operator: UnaryOpNot,
Operand: expr.(Expression),
}, nil
} / expr:ParenthesizedExpression {
return expr, nil
}
ParenthesizedExpression "grouping" <- "(" _? expr:OrExpression _? ")" {
return expr, nil
} / expr:MatchExpression {
return expr, nil
} / "(" _? OrExpression _? !")" &{
return false, errors.New("Unmatched parentheses")
}
MatchExpression "match" <- MatchSelectorOpValue / MatchSelectorOp / MatchValueOpSelector
MatchSelectorOpValue "match" <- selector:Selector operator:(MatchEqual / MatchNotEqual / MatchContains / MatchNotContains) value:Value {
return &MatchExpression{Selector: selector.(Selector), Operator: operator.(MatchOperator), Value: value.(*MatchValue)}, nil
}
MatchSelectorOp "match" <- selector:Selector operator:(MatchIsEmpty / MatchIsNotEmpty) {
return &MatchExpression{Selector: selector.(Selector), Operator: operator.(MatchOperator), Value: nil}, nil
}
MatchValueOpSelector "match" <- value:Value operator:(MatchIn / MatchNotIn) selector:Selector {
return &MatchExpression{Selector: selector.(Selector), Operator: operator.(MatchOperator), Value: value.(*MatchValue)}, nil
} / Value operator:(MatchIn / MatchNotIn) !Selector &{
return false, errors.New("Invalid selector")
}
MatchEqual <- _? "==" _? {
return MatchEqual, nil
}
MatchNotEqual <- _? "!=" _? {
return MatchNotEqual, nil
}
MatchIsEmpty <- _ "is" _ "empty" {
return MatchIsEmpty, nil
}
MatchIsNotEmpty <- _"is" _ "not" _ "empty" {
return MatchIsNotEmpty, nil
}
MatchIn <- _ "in" _ {
return MatchIn, nil
}
MatchNotIn <- _ "not" _ "in" _ {
return MatchNotIn, nil
}
MatchContains <- _ "contains" _ {
return MatchIn, nil
}
MatchNotContains <- _ "not" _ "contains" _ {
return MatchNotIn, nil
}
Selector "selector" <- first:Identifier rest:SelectorOrIndex* {
sel := Selector{
first.(string),
}
if rest != nil {
for _, v := range rest.([]interface{}) {
sel = append(sel, v.(string))
}
}
return sel, nil
}
Identifier <- [a-zA-Z] [a-zA-Z0-9_]* {
return string(c.text), nil
}
SelectorOrIndex <- "." ident:Identifier {
return ident, nil
} / expr:IndexExpression {
return expr, nil
}
IndexExpression "index" <- "[" _? lit:StringLiteral _? "]" {
return lit, nil
} / "[" _? !StringLiteral &{
return false, errors.New("Invalid index")
} / "[" _? StringLiteral _? !"]" &{
return false, errors.New("Unclosed index expression")
}
Value "value" <- selector:Selector { return &MatchValue{Raw:strings.Join(selector.(Selector), ".")}, nil }
/ n:NumberLiteral { return &MatchValue{Raw: n.(string)}, nil }
/ s:StringLiteral { return &MatchValue{Raw: s.(string)}, nil}
NumberLiteral "number" <- "-"? IntegerOrFloat &AfterNumbers {
return string(c.text), nil
} / "-"? IntegerOrFloat !AfterNumbers &{
return false, errors.New("Invalid number literal")
}
AfterNumbers <- &(_ / EOF / ")")
IntegerOrFloat <- ("0" / [1-9][0-9]*) ("." [0-9]+)?
StringLiteral "string" <- ('`' RawStringChar* '`' / '"' DoubleStringChar* '"') {
return strconv.Unquote(string(c.text))
} / ('`' RawStringChar* / '"' DoubleStringChar*) EOF &{
return false, errors.New("Unterminated string literal")
}
RawStringChar <- !'`' .
DoubleStringChar <- !'"' .
_ "whitespace" <- [ \t\r\n]+
EOF <- !.