mirror of
https://github.com/TwiN/gatus.git
synced 2024-12-11 09:11:56 +01:00
477 lines
12 KiB
Go
477 lines
12 KiB
Go
/*
|
|
Copyright 2015 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package fields
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"k8s.io/apimachinery/pkg/selection"
|
|
)
|
|
|
|
// Selector represents a field selector.
|
|
type Selector interface {
|
|
// Matches returns true if this selector matches the given set of fields.
|
|
Matches(Fields) bool
|
|
|
|
// Empty returns true if this selector does not restrict the selection space.
|
|
Empty() bool
|
|
|
|
// RequiresExactMatch allows a caller to introspect whether a given selector
|
|
// requires a single specific field to be set, and if so returns the value it
|
|
// requires.
|
|
RequiresExactMatch(field string) (value string, found bool)
|
|
|
|
// Transform returns a new copy of the selector after TransformFunc has been
|
|
// applied to the entire selector, or an error if fn returns an error.
|
|
// If for a given requirement both field and value are transformed to empty
|
|
// string, the requirement is skipped.
|
|
Transform(fn TransformFunc) (Selector, error)
|
|
|
|
// Requirements converts this interface to Requirements to expose
|
|
// more detailed selection information.
|
|
Requirements() Requirements
|
|
|
|
// String returns a human readable string that represents this selector.
|
|
String() string
|
|
|
|
// Make a deep copy of the selector.
|
|
DeepCopySelector() Selector
|
|
}
|
|
|
|
type nothingSelector struct{}
|
|
|
|
func (n nothingSelector) Matches(_ Fields) bool { return false }
|
|
func (n nothingSelector) Empty() bool { return false }
|
|
func (n nothingSelector) String() string { return "" }
|
|
func (n nothingSelector) Requirements() Requirements { return nil }
|
|
func (n nothingSelector) DeepCopySelector() Selector { return n }
|
|
func (n nothingSelector) RequiresExactMatch(field string) (value string, found bool) { return "", false }
|
|
func (n nothingSelector) Transform(fn TransformFunc) (Selector, error) { return n, nil }
|
|
|
|
// Nothing returns a selector that matches no fields
|
|
func Nothing() Selector {
|
|
return nothingSelector{}
|
|
}
|
|
|
|
// Everything returns a selector that matches all fields.
|
|
func Everything() Selector {
|
|
return andTerm{}
|
|
}
|
|
|
|
type hasTerm struct {
|
|
field, value string
|
|
}
|
|
|
|
func (t *hasTerm) Matches(ls Fields) bool {
|
|
return ls.Get(t.field) == t.value
|
|
}
|
|
|
|
func (t *hasTerm) Empty() bool {
|
|
return false
|
|
}
|
|
|
|
func (t *hasTerm) RequiresExactMatch(field string) (value string, found bool) {
|
|
if t.field == field {
|
|
return t.value, true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
func (t *hasTerm) Transform(fn TransformFunc) (Selector, error) {
|
|
field, value, err := fn(t.field, t.value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(field) == 0 && len(value) == 0 {
|
|
return Everything(), nil
|
|
}
|
|
return &hasTerm{field, value}, nil
|
|
}
|
|
|
|
func (t *hasTerm) Requirements() Requirements {
|
|
return []Requirement{{
|
|
Field: t.field,
|
|
Operator: selection.Equals,
|
|
Value: t.value,
|
|
}}
|
|
}
|
|
|
|
func (t *hasTerm) String() string {
|
|
return fmt.Sprintf("%v=%v", t.field, EscapeValue(t.value))
|
|
}
|
|
|
|
func (t *hasTerm) DeepCopySelector() Selector {
|
|
if t == nil {
|
|
return nil
|
|
}
|
|
out := new(hasTerm)
|
|
*out = *t
|
|
return out
|
|
}
|
|
|
|
type notHasTerm struct {
|
|
field, value string
|
|
}
|
|
|
|
func (t *notHasTerm) Matches(ls Fields) bool {
|
|
return ls.Get(t.field) != t.value
|
|
}
|
|
|
|
func (t *notHasTerm) Empty() bool {
|
|
return false
|
|
}
|
|
|
|
func (t *notHasTerm) RequiresExactMatch(field string) (value string, found bool) {
|
|
return "", false
|
|
}
|
|
|
|
func (t *notHasTerm) Transform(fn TransformFunc) (Selector, error) {
|
|
field, value, err := fn(t.field, t.value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(field) == 0 && len(value) == 0 {
|
|
return Everything(), nil
|
|
}
|
|
return ¬HasTerm{field, value}, nil
|
|
}
|
|
|
|
func (t *notHasTerm) Requirements() Requirements {
|
|
return []Requirement{{
|
|
Field: t.field,
|
|
Operator: selection.NotEquals,
|
|
Value: t.value,
|
|
}}
|
|
}
|
|
|
|
func (t *notHasTerm) String() string {
|
|
return fmt.Sprintf("%v!=%v", t.field, EscapeValue(t.value))
|
|
}
|
|
|
|
func (t *notHasTerm) DeepCopySelector() Selector {
|
|
if t == nil {
|
|
return nil
|
|
}
|
|
out := new(notHasTerm)
|
|
*out = *t
|
|
return out
|
|
}
|
|
|
|
type andTerm []Selector
|
|
|
|
func (t andTerm) Matches(ls Fields) bool {
|
|
for _, q := range t {
|
|
if !q.Matches(ls) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (t andTerm) Empty() bool {
|
|
if t == nil {
|
|
return true
|
|
}
|
|
if len([]Selector(t)) == 0 {
|
|
return true
|
|
}
|
|
for i := range t {
|
|
if !t[i].Empty() {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (t andTerm) RequiresExactMatch(field string) (string, bool) {
|
|
if t == nil || len([]Selector(t)) == 0 {
|
|
return "", false
|
|
}
|
|
for i := range t {
|
|
if value, found := t[i].RequiresExactMatch(field); found {
|
|
return value, found
|
|
}
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
func (t andTerm) Transform(fn TransformFunc) (Selector, error) {
|
|
next := make([]Selector, 0, len([]Selector(t)))
|
|
for _, s := range []Selector(t) {
|
|
n, err := s.Transform(fn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !n.Empty() {
|
|
next = append(next, n)
|
|
}
|
|
}
|
|
return andTerm(next), nil
|
|
}
|
|
|
|
func (t andTerm) Requirements() Requirements {
|
|
reqs := make([]Requirement, 0, len(t))
|
|
for _, s := range []Selector(t) {
|
|
rs := s.Requirements()
|
|
reqs = append(reqs, rs...)
|
|
}
|
|
return reqs
|
|
}
|
|
|
|
func (t andTerm) String() string {
|
|
var terms []string
|
|
for _, q := range t {
|
|
terms = append(terms, q.String())
|
|
}
|
|
return strings.Join(terms, ",")
|
|
}
|
|
|
|
func (t andTerm) DeepCopySelector() Selector {
|
|
if t == nil {
|
|
return nil
|
|
}
|
|
out := make([]Selector, len(t))
|
|
for i := range t {
|
|
out[i] = t[i].DeepCopySelector()
|
|
}
|
|
return andTerm(out)
|
|
}
|
|
|
|
// SelectorFromSet returns a Selector which will match exactly the given Set. A
|
|
// nil Set is considered equivalent to Everything().
|
|
func SelectorFromSet(ls Set) Selector {
|
|
if ls == nil {
|
|
return Everything()
|
|
}
|
|
items := make([]Selector, 0, len(ls))
|
|
for field, value := range ls {
|
|
items = append(items, &hasTerm{field: field, value: value})
|
|
}
|
|
if len(items) == 1 {
|
|
return items[0]
|
|
}
|
|
return andTerm(items)
|
|
}
|
|
|
|
// valueEscaper prefixes \,= characters with a backslash
|
|
var valueEscaper = strings.NewReplacer(
|
|
// escape \ characters
|
|
`\`, `\\`,
|
|
// then escape , and = characters to allow unambiguous parsing of the value in a fieldSelector
|
|
`,`, `\,`,
|
|
`=`, `\=`,
|
|
)
|
|
|
|
// EscapeValue escapes an arbitrary literal string for use as a fieldSelector value
|
|
func EscapeValue(s string) string {
|
|
return valueEscaper.Replace(s)
|
|
}
|
|
|
|
// InvalidEscapeSequence indicates an error occurred unescaping a field selector
|
|
type InvalidEscapeSequence struct {
|
|
sequence string
|
|
}
|
|
|
|
func (i InvalidEscapeSequence) Error() string {
|
|
return fmt.Sprintf("invalid field selector: invalid escape sequence: %s", i.sequence)
|
|
}
|
|
|
|
// UnescapedRune indicates an error occurred unescaping a field selector
|
|
type UnescapedRune struct {
|
|
r rune
|
|
}
|
|
|
|
func (i UnescapedRune) Error() string {
|
|
return fmt.Sprintf("invalid field selector: unescaped character in value: %v", i.r)
|
|
}
|
|
|
|
// UnescapeValue unescapes a fieldSelector value and returns the original literal value.
|
|
// May return the original string if it contains no escaped or special characters.
|
|
func UnescapeValue(s string) (string, error) {
|
|
// if there's no escaping or special characters, just return to avoid allocation
|
|
if !strings.ContainsAny(s, `\,=`) {
|
|
return s, nil
|
|
}
|
|
|
|
v := bytes.NewBuffer(make([]byte, 0, len(s)))
|
|
inSlash := false
|
|
for _, c := range s {
|
|
if inSlash {
|
|
switch c {
|
|
case '\\', ',', '=':
|
|
// omit the \ for recognized escape sequences
|
|
v.WriteRune(c)
|
|
default:
|
|
// error on unrecognized escape sequences
|
|
return "", InvalidEscapeSequence{sequence: string([]rune{'\\', c})}
|
|
}
|
|
inSlash = false
|
|
continue
|
|
}
|
|
|
|
switch c {
|
|
case '\\':
|
|
inSlash = true
|
|
case ',', '=':
|
|
// unescaped , and = characters are not allowed in field selector values
|
|
return "", UnescapedRune{r: c}
|
|
default:
|
|
v.WriteRune(c)
|
|
}
|
|
}
|
|
|
|
// Ending with a single backslash is an invalid sequence
|
|
if inSlash {
|
|
return "", InvalidEscapeSequence{sequence: "\\"}
|
|
}
|
|
|
|
return v.String(), nil
|
|
}
|
|
|
|
// ParseSelectorOrDie takes a string representing a selector and returns an
|
|
// object suitable for matching, or panic when an error occur.
|
|
func ParseSelectorOrDie(s string) Selector {
|
|
selector, err := ParseSelector(s)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return selector
|
|
}
|
|
|
|
// ParseSelector takes a string representing a selector and returns an
|
|
// object suitable for matching, or an error.
|
|
func ParseSelector(selector string) (Selector, error) {
|
|
return parseSelector(selector,
|
|
func(lhs, rhs string) (newLhs, newRhs string, err error) {
|
|
return lhs, rhs, nil
|
|
})
|
|
}
|
|
|
|
// ParseAndTransformSelector parses the selector and runs them through the given TransformFunc.
|
|
func ParseAndTransformSelector(selector string, fn TransformFunc) (Selector, error) {
|
|
return parseSelector(selector, fn)
|
|
}
|
|
|
|
// TransformFunc transforms selectors.
|
|
type TransformFunc func(field, value string) (newField, newValue string, err error)
|
|
|
|
// splitTerms returns the comma-separated terms contained in the given fieldSelector.
|
|
// Backslash-escaped commas are treated as data instead of delimiters, and are included in the returned terms, with the leading backslash preserved.
|
|
func splitTerms(fieldSelector string) []string {
|
|
if len(fieldSelector) == 0 {
|
|
return nil
|
|
}
|
|
|
|
terms := make([]string, 0, 1)
|
|
startIndex := 0
|
|
inSlash := false
|
|
for i, c := range fieldSelector {
|
|
switch {
|
|
case inSlash:
|
|
inSlash = false
|
|
case c == '\\':
|
|
inSlash = true
|
|
case c == ',':
|
|
terms = append(terms, fieldSelector[startIndex:i])
|
|
startIndex = i + 1
|
|
}
|
|
}
|
|
|
|
terms = append(terms, fieldSelector[startIndex:])
|
|
|
|
return terms
|
|
}
|
|
|
|
const (
|
|
notEqualOperator = "!="
|
|
doubleEqualOperator = "=="
|
|
equalOperator = "="
|
|
)
|
|
|
|
// termOperators holds the recognized operators supported in fieldSelectors.
|
|
// doubleEqualOperator and equal are equivalent, but doubleEqualOperator is checked first
|
|
// to avoid leaving a leading = character on the rhs value.
|
|
var termOperators = []string{notEqualOperator, doubleEqualOperator, equalOperator}
|
|
|
|
// splitTerm returns the lhs, operator, and rhs parsed from the given term, along with an indicator of whether the parse was successful.
|
|
// no escaping of special characters is supported in the lhs value, so the first occurrence of a recognized operator is used as the split point.
|
|
// the literal rhs is returned, and the caller is responsible for applying any desired unescaping.
|
|
func splitTerm(term string) (lhs, op, rhs string, ok bool) {
|
|
for i := range term {
|
|
remaining := term[i:]
|
|
for _, op := range termOperators {
|
|
if strings.HasPrefix(remaining, op) {
|
|
return term[0:i], op, term[i+len(op):], true
|
|
}
|
|
}
|
|
}
|
|
return "", "", "", false
|
|
}
|
|
|
|
func parseSelector(selector string, fn TransformFunc) (Selector, error) {
|
|
parts := splitTerms(selector)
|
|
sort.StringSlice(parts).Sort()
|
|
var items []Selector
|
|
for _, part := range parts {
|
|
if part == "" {
|
|
continue
|
|
}
|
|
lhs, op, rhs, ok := splitTerm(part)
|
|
if !ok {
|
|
return nil, fmt.Errorf("invalid selector: '%s'; can't understand '%s'", selector, part)
|
|
}
|
|
unescapedRHS, err := UnescapeValue(rhs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch op {
|
|
case notEqualOperator:
|
|
items = append(items, ¬HasTerm{field: lhs, value: unescapedRHS})
|
|
case doubleEqualOperator:
|
|
items = append(items, &hasTerm{field: lhs, value: unescapedRHS})
|
|
case equalOperator:
|
|
items = append(items, &hasTerm{field: lhs, value: unescapedRHS})
|
|
default:
|
|
return nil, fmt.Errorf("invalid selector: '%s'; can't understand '%s'", selector, part)
|
|
}
|
|
}
|
|
if len(items) == 1 {
|
|
return items[0].Transform(fn)
|
|
}
|
|
return andTerm(items).Transform(fn)
|
|
}
|
|
|
|
// OneTermEqualSelector returns an object that matches objects where one field/field equals one value.
|
|
// Cannot return an error.
|
|
func OneTermEqualSelector(k, v string) Selector {
|
|
return &hasTerm{field: k, value: v}
|
|
}
|
|
|
|
// OneTermNotEqualSelector returns an object that matches objects where one field/field does not equal one value.
|
|
// Cannot return an error.
|
|
func OneTermNotEqualSelector(k, v string) Selector {
|
|
return ¬HasTerm{field: k, value: v}
|
|
}
|
|
|
|
// AndSelectors creates a selector that is the logical AND of all the given selectors
|
|
func AndSelectors(selectors ...Selector) Selector {
|
|
return andTerm(selectors)
|
|
}
|