// SPDX-License-Identifier: GPL-3.0-or-later package pipeline import ( "errors" "fmt" "strings" "github.com/netdata/netdata/go/go.d.plugin/agent/discovery/sd/model" ) type selector interface { matches(model.Tags) bool } type ( exactSelector string trueSelector struct{} negSelector struct{ selector } orSelector struct{ lhs, rhs selector } andSelector struct{ lhs, rhs selector } ) func (s exactSelector) matches(tags model.Tags) bool { _, ok := tags[string(s)]; return ok } func (s trueSelector) matches(model.Tags) bool { return true } func (s negSelector) matches(tags model.Tags) bool { return !s.selector.matches(tags) } func (s orSelector) matches(tags model.Tags) bool { return s.lhs.matches(tags) || s.rhs.matches(tags) } func (s andSelector) matches(tags model.Tags) bool { return s.lhs.matches(tags) && s.rhs.matches(tags) } func (s exactSelector) String() string { return "{" + string(s) + "}" } func (s negSelector) String() string { return "{!" + stringify(s.selector) + "}" } func (s trueSelector) String() string { return "{*}" } func (s orSelector) String() string { return "{" + stringify(s.lhs) + "|" + stringify(s.rhs) + "}" } func (s andSelector) String() string { return "{" + stringify(s.lhs) + ", " + stringify(s.rhs) + "}" } func stringify(sr selector) string { return strings.Trim(fmt.Sprintf("%s", sr), "{}") } func parseSelector(line string) (sr selector, err error) { words := strings.Fields(line) if len(words) == 0 { return trueSelector{}, nil } var srs []selector for _, word := range words { if idx := strings.IndexByte(word, '|'); idx > 0 { sr, err = parseOrSelectorWord(word) } else { sr, err = parseSingleSelectorWord(word) } if err != nil { return nil, fmt.Errorf("selector '%s' contains selector '%s' with forbidden symbol", line, word) } srs = append(srs, sr) } switch len(srs) { case 0: return trueSelector{}, nil case 1: return srs[0], nil default: return newAndSelector(srs[0], srs[1], srs[2:]...), nil } } func parseOrSelectorWord(orWord string) (sr selector, err error) { var srs []selector for _, word := range strings.Split(orWord, "|") { if sr, err = parseSingleSelectorWord(word); err != nil { return nil, err } srs = append(srs, sr) } switch len(srs) { case 0: return trueSelector{}, nil case 1: return srs[0], nil default: return newOrSelector(srs[0], srs[1], srs[2:]...), nil } } func parseSingleSelectorWord(word string) (selector, error) { if len(word) == 0 { return nil, errors.New("empty word") } neg := word[0] == '!' if neg { word = word[1:] } if len(word) == 0 { return nil, errors.New("empty word") } if word != "*" && !isSelectorWordValid(word) { return nil, errors.New("forbidden symbol") } var sr selector switch word { case "*": sr = trueSelector{} default: sr = exactSelector(word) } if neg { return negSelector{sr}, nil } return sr, nil } func newAndSelector(lhs, rhs selector, others ...selector) selector { m := andSelector{lhs: lhs, rhs: rhs} switch len(others) { case 0: return m default: return newAndSelector(m, others[0], others[1:]...) } } func newOrSelector(lhs, rhs selector, others ...selector) selector { m := orSelector{lhs: lhs, rhs: rhs} switch len(others) { case 0: return m default: return newOrSelector(m, others[0], others[1:]...) } } func isSelectorWordValid(word string) bool { // valid: // * // ^[a-zA-Z][a-zA-Z0-9=_.]*$ if len(word) == 0 { return false } if word == "*" { return true } for i, b := range word { switch { case b >= 'a' && b <= 'z': case b >= 'A' && b <= 'Z': case b >= '0' && b <= '9' && i > 0: case (b == '=' || b == '_' || b == '.') && i > 0: default: return false } } return true }