Initial QSfera import
This commit is contained in:
+205
@@ -0,0 +1,205 @@
|
||||
// Copyright 2023 The Prometheus 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 compat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/prometheus/common/promslog"
|
||||
|
||||
"github.com/prometheus/alertmanager/featurecontrol"
|
||||
"github.com/prometheus/alertmanager/matcher/parse"
|
||||
"github.com/prometheus/alertmanager/pkg/labels"
|
||||
)
|
||||
|
||||
var (
|
||||
isValidLabelName = isValidClassicLabelName(promslog.NewNopLogger())
|
||||
parseMatcher = ClassicMatcherParser(promslog.NewNopLogger())
|
||||
parseMatchers = ClassicMatchersParser(promslog.NewNopLogger())
|
||||
)
|
||||
|
||||
// IsValidLabelName returns true if the string is a valid label name.
|
||||
func IsValidLabelName(name model.LabelName) bool {
|
||||
return isValidLabelName(name)
|
||||
}
|
||||
|
||||
type ParseMatcher func(input, origin string) (*labels.Matcher, error)
|
||||
|
||||
type ParseMatchers func(input, origin string) (labels.Matchers, error)
|
||||
|
||||
// Matcher parses the matcher in the input string. It returns an error
|
||||
// if the input is invalid or contains two or more matchers.
|
||||
func Matcher(input, origin string) (*labels.Matcher, error) {
|
||||
return parseMatcher(input, origin)
|
||||
}
|
||||
|
||||
// Matchers parses one or more matchers in the input string. It returns
|
||||
// an error if the input is invalid.
|
||||
func Matchers(input, origin string) (labels.Matchers, error) {
|
||||
return parseMatchers(input, origin)
|
||||
}
|
||||
|
||||
// InitFromFlags initializes the compat package from the flagger.
|
||||
func InitFromFlags(l *slog.Logger, f featurecontrol.Flagger) {
|
||||
if f.ClassicMode() {
|
||||
isValidLabelName = isValidClassicLabelName(l)
|
||||
parseMatcher = ClassicMatcherParser(l)
|
||||
parseMatchers = ClassicMatchersParser(l)
|
||||
} else if f.UTF8StrictMode() {
|
||||
isValidLabelName = isValidUTF8LabelName(l)
|
||||
parseMatcher = UTF8MatcherParser(l)
|
||||
parseMatchers = UTF8MatchersParser(l)
|
||||
} else {
|
||||
isValidLabelName = isValidUTF8LabelName(l)
|
||||
parseMatcher = FallbackMatcherParser(l)
|
||||
parseMatchers = FallbackMatchersParser(l)
|
||||
}
|
||||
}
|
||||
|
||||
// ClassicMatcherParser uses the pkg/labels parser to parse the matcher in
|
||||
// the input string.
|
||||
func ClassicMatcherParser(l *slog.Logger) ParseMatcher {
|
||||
return func(input, origin string) (matcher *labels.Matcher, err error) {
|
||||
l.Debug("Parsing with classic matchers parser", "input", input, "origin", origin)
|
||||
return labels.ParseMatcher(input)
|
||||
}
|
||||
}
|
||||
|
||||
// ClassicMatchersParser uses the pkg/labels parser to parse zero or more
|
||||
// matchers in the input string. It returns an error if the input is invalid.
|
||||
func ClassicMatchersParser(l *slog.Logger) ParseMatchers {
|
||||
return func(input, origin string) (matchers labels.Matchers, err error) {
|
||||
l.Debug("Parsing with classic matchers parser", "input", input, "origin", origin)
|
||||
return labels.ParseMatchers(input)
|
||||
}
|
||||
}
|
||||
|
||||
// UTF8MatcherParser uses the new matcher/parse parser to parse the matcher
|
||||
// in the input string. If this fails it does not revert to the pkg/labels parser.
|
||||
func UTF8MatcherParser(l *slog.Logger) ParseMatcher {
|
||||
return func(input, origin string) (matcher *labels.Matcher, err error) {
|
||||
l.Debug("Parsing with UTF-8 matchers parser", "input", input, "origin", origin)
|
||||
if strings.HasPrefix(input, "{") || strings.HasSuffix(input, "}") {
|
||||
return nil, fmt.Errorf("unexpected open or close brace: %s", input)
|
||||
}
|
||||
return parse.Matcher(input)
|
||||
}
|
||||
}
|
||||
|
||||
// UTF8MatchersParser uses the new matcher/parse parser to parse zero or more
|
||||
// matchers in the input string. If this fails it does not revert to the
|
||||
// pkg/labels parser.
|
||||
func UTF8MatchersParser(l *slog.Logger) ParseMatchers {
|
||||
return func(input, origin string) (matchers labels.Matchers, err error) {
|
||||
l.Debug("Parsing with UTF-8 matchers parser", "input", input, "origin", origin)
|
||||
return parse.Matchers(input)
|
||||
}
|
||||
}
|
||||
|
||||
// FallbackMatcherParser uses the new matcher/parse parser to parse zero or more
|
||||
// matchers in the string. If this fails it reverts to the pkg/labels parser and
|
||||
// emits a warning log line.
|
||||
func FallbackMatcherParser(l *slog.Logger) ParseMatcher {
|
||||
return func(input, origin string) (matcher *labels.Matcher, err error) {
|
||||
l.Debug("Parsing with UTF-8 matchers parser, with fallback to classic matchers parser", "input", input, "origin", origin)
|
||||
if strings.HasPrefix(input, "{") || strings.HasSuffix(input, "}") {
|
||||
return nil, fmt.Errorf("unexpected open or close brace: %s", input)
|
||||
}
|
||||
// Parse the input in both parsers to look for disagreement and incompatible
|
||||
// inputs.
|
||||
nMatcher, nErr := parse.Matcher(input)
|
||||
cMatcher, cErr := labels.ParseMatcher(input)
|
||||
if nErr != nil {
|
||||
// If the input is invalid in both parsers, return the error.
|
||||
if cErr != nil {
|
||||
return nil, cErr
|
||||
}
|
||||
// The input is valid in the pkg/labels parser, but not the matcher/parse
|
||||
// parser. This means the input is not forwards compatible.
|
||||
suggestion := cMatcher.String()
|
||||
l.Warn("Alertmanager is moving to a new parser for labels and matchers, and this input is incompatible. Alertmanager has instead parsed the input using the classic matchers parser as a fallback. To make this input compatible with the UTF-8 matchers parser please make sure all regular expressions and values are double-quoted and backslashes are escaped. If you are still seeing this message please open an issue.", "input", input, "origin", origin, "err", nErr, "suggestion", suggestion)
|
||||
return cMatcher, nil
|
||||
}
|
||||
// If the input is valid in both parsers, but produces different results,
|
||||
// then there is disagreement.
|
||||
if cErr == nil && !reflect.DeepEqual(nMatcher, cMatcher) {
|
||||
l.Warn("Matchers input has disagreement", "input", input, "origin", origin)
|
||||
return cMatcher, nil
|
||||
}
|
||||
return nMatcher, nil
|
||||
}
|
||||
}
|
||||
|
||||
// FallbackMatchersParser uses the new matcher/parse parser to parse the
|
||||
// matcher in the input string. If this fails it falls back to the pkg/labels
|
||||
// parser and emits a warning log line.
|
||||
func FallbackMatchersParser(l *slog.Logger) ParseMatchers {
|
||||
return func(input, origin string) (matchers labels.Matchers, err error) {
|
||||
l.Debug("Parsing with UTF-8 matchers parser, with fallback to classic matchers parser", "input", input, "origin", origin)
|
||||
// Parse the input in both parsers to look for disagreement and incompatible
|
||||
// inputs.
|
||||
nMatchers, nErr := parse.Matchers(input)
|
||||
cMatchers, cErr := labels.ParseMatchers(input)
|
||||
if nErr != nil {
|
||||
// If the input is invalid in both parsers, return the error.
|
||||
if cErr != nil {
|
||||
return nil, cErr
|
||||
}
|
||||
// The input is valid in the pkg/labels parser, but not the matcher/parse
|
||||
// parser. This means the input is not forwards compatible.
|
||||
var sb strings.Builder
|
||||
for i, n := range cMatchers {
|
||||
sb.WriteString(n.String())
|
||||
if i < len(cMatchers)-1 {
|
||||
sb.WriteRune(',')
|
||||
}
|
||||
}
|
||||
suggestion := sb.String()
|
||||
// The input is valid in the pkg/labels parser, but not the
|
||||
// new matcher/parse parser.
|
||||
l.Warn("Alertmanager is moving to a new parser for labels and matchers, and this input is incompatible. Alertmanager has instead parsed the input using the classic matchers parser as a fallback. To make this input compatible with the UTF-8 matchers parser please make sure all regular expressions and values are double-quoted and backslashes are escaped. If you are still seeing this message please open an issue.", "input", input, "origin", origin, "err", nErr, "suggestion", suggestion)
|
||||
return cMatchers, nil
|
||||
}
|
||||
// If the input is valid in both parsers, but produces different results,
|
||||
// then there is disagreement. We need to compare to labels.Matchers(cMatchers)
|
||||
// as cMatchers is a []*labels.Matcher not labels.Matchers.
|
||||
if cErr == nil && !reflect.DeepEqual(nMatchers, labels.Matchers(cMatchers)) {
|
||||
l.Warn("Matchers input has disagreement", "input", input, "origin", origin)
|
||||
return cMatchers, nil
|
||||
}
|
||||
return nMatchers, nil
|
||||
}
|
||||
}
|
||||
|
||||
// isValidClassicLabelName returns true if the string is a valid classic label name.
|
||||
func isValidClassicLabelName(_ *slog.Logger) func(model.LabelName) bool {
|
||||
return func(name model.LabelName) bool {
|
||||
return model.LegacyValidation.IsValidLabelName(string(name))
|
||||
}
|
||||
}
|
||||
|
||||
// isValidUTF8LabelName returns true if the string is a valid UTF-8 label name.
|
||||
func isValidUTF8LabelName(_ *slog.Logger) func(model.LabelName) bool {
|
||||
return func(name model.LabelName) bool {
|
||||
if len(name) == 0 {
|
||||
return false
|
||||
}
|
||||
return utf8.ValidString(string(name))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user