Files
karma/internal/filters/filter.go
2025-10-30 12:59:44 +00:00

123 lines
2.8 KiB
Go

package filters
import (
"fmt"
"regexp"
"slices"
"strings"
"github.com/prymitive/karma/internal/models"
)
// FilterT provides methods for interacting with alert filters
type FilterT interface {
init(name string, matcher *matcherT, rawText string, isValid bool, value string)
Match(alert *models.Alert, matches int) bool
MatchAlertmanager(am *models.AlertmanagerInstance) bool
GetRawText() string
GetHits() int
GetIsValid() bool
GetName() string
GetMatcher() string
GetValue() string
GetIsAlertmanagerFilter() bool
}
type alertFilter struct {
FilterT
Matcher matcherT
Matched string
RawText string
Hits int
IsValid bool
IsAlertmanagerFilter bool
}
func (filter *alertFilter) GetRawText() string {
return filter.RawText
}
func (filter *alertFilter) GetHits() int {
return filter.Hits
}
func (filter *alertFilter) GetIsValid() bool {
return filter.IsValid
}
func (filter *alertFilter) GetName() string {
return filter.Matched
}
func (filter *alertFilter) GetMatcher() string {
if filter.Matcher == nil {
return ""
}
return filter.Matcher.GetOperator()
}
func (filter *alertFilter) GetIsAlertmanagerFilter() bool {
return filter.IsAlertmanagerFilter
}
type newFilterFactory func() FilterT
// NewFilter creates new filter object from filter expression like "key=value"
// expression will be parsed and best filter implementation and value matcher
// will be selected
func NewFilter(expression string) FilterT {
trimmed := strings.Trim(expression, " \t")
invalid := alwaysInvalidFilter{}
invalid.init("", nil, trimmed, false, trimmed)
if trimmed == "" {
return &invalid
}
reExp := fmt.Sprintf("^(?P<matched>(%s))(?P<operator>(%s))(?P<value>(.*))", filterRegex, matcherRegex)
re := regexp.MustCompile(reExp)
match := re.FindStringSubmatch(trimmed)
result := make(map[string]string)
for i, name := range re.SubexpNames() {
if name != "" && i > 0 && i <= len(match) {
result[name] = match[i]
}
}
matched := result["matched"]
operator := result["operator"]
value := result["value"]
if matched == "" && operator == "" && value == "" {
// no "filter=" part, just the value, use fuzzy filter
f := newFuzzyFilter()
matcher, _ := newMatcher(regexpOperator)
f.init("", &matcher, trimmed, true, trimmed)
return f
}
if value == "" {
// there's no value, so it's always invalid
return &invalid
}
// we have "filter=" part, lookup filter that matches
for _, fc := range AllFilters {
f := fc.Factory()
if !fc.LabelRe.MatchString(matched) {
// filter name doesn't match, keep searching
continue
}
if !slices.Contains(fc.SupportedOperators, operator) {
return &invalid
}
// we validate operator above, no need to re-check
matcher, _ := newMatcher(operator)
f.init(matched, &matcher, trimmed, true, value)
return f
}
return &invalid
}