2023-10-19 11:00:01 +00:00
// 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"
2024-01-05 10:21:20 +00:00
"reflect"
2023-10-19 11:00:01 +00:00
"strings"
2023-11-24 11:26:39 +00:00
"unicode/utf8"
2023-10-19 11:00:01 +00:00
"github.com/go-kit/log"
"github.com/go-kit/log/level"
2024-01-05 10:21:20 +00:00
"github.com/prometheus/client_golang/prometheus"
2023-11-24 11:26:39 +00:00
"github.com/prometheus/common/model"
2023-10-19 11:00:01 +00:00
"github.com/prometheus/alertmanager/featurecontrol"
"github.com/prometheus/alertmanager/matchers/parse"
"github.com/prometheus/alertmanager/pkg/labels"
)
var (
2023-11-24 11:26:39 +00:00
isValidLabelName = isValidClassicLabelName ( log . NewNopLogger ( ) )
2024-01-05 10:21:20 +00:00
parseMatcher = ClassicMatcherParser ( log . NewNopLogger ( ) , RegisteredMetrics )
parseMatchers = ClassicMatchersParser ( log . NewNopLogger ( ) , RegisteredMetrics )
2023-10-19 11:00:01 +00:00
)
2023-11-24 11:26:39 +00:00
// IsValidLabelName returns true if the string is a valid label name.
func IsValidLabelName ( name model . LabelName ) bool {
return isValidLabelName ( name )
}
2024-01-05 10:21:20 +00:00
type ParseMatcher func ( input , origin string ) ( * labels . Matcher , error )
2023-10-19 11:00:01 +00:00
2024-01-05 10:21:20 +00:00
type ParseMatchers func ( input , origin string ) ( labels . Matchers , error )
2023-10-19 11:00:01 +00:00
// Matcher parses the matcher in the input string. It returns an error
// if the input is invalid or contains two or more matchers.
2024-01-05 10:21:20 +00:00
func Matcher ( input , origin string ) ( * labels . Matcher , error ) {
return parseMatcher ( input , origin )
2023-10-19 11:00:01 +00:00
}
// Matchers parses one or more matchers in the input string. It returns
// an error if the input is invalid.
2024-01-05 10:21:20 +00:00
func Matchers ( input , origin string ) ( labels . Matchers , error ) {
return parseMatchers ( input , origin )
2023-10-19 11:00:01 +00:00
}
// InitFromFlags initializes the compat package from the flagger.
2024-01-05 10:21:20 +00:00
func InitFromFlags ( l log . Logger , m * Metrics , f featurecontrol . Flagger ) {
2023-11-17 20:07:54 +00:00
if f . ClassicMode ( ) {
2023-11-24 11:26:39 +00:00
isValidLabelName = isValidClassicLabelName ( l )
2024-01-05 10:21:20 +00:00
parseMatcher = ClassicMatcherParser ( l , m )
parseMatchers = ClassicMatchersParser ( l , m )
2023-11-24 10:01:40 +00:00
} else if f . UTF8StrictMode ( ) {
2023-11-24 11:26:39 +00:00
isValidLabelName = isValidUTF8LabelName ( l )
2024-01-05 10:21:20 +00:00
parseMatcher = UTF8MatcherParser ( l , m )
parseMatchers = UTF8MatchersParser ( l , m )
2023-10-19 11:00:01 +00:00
} else {
2023-11-24 11:26:39 +00:00
isValidLabelName = isValidUTF8LabelName ( l )
2024-01-05 10:21:20 +00:00
parseMatcher = FallbackMatcherParser ( l , m )
parseMatchers = FallbackMatchersParser ( l , m )
2023-10-19 11:00:01 +00:00
}
}
2024-01-05 10:21:20 +00:00
// ClassicMatcherParser uses the pkg/labels parser to parse the matcher in
2023-10-19 11:00:01 +00:00
// the input string.
2024-01-05 10:21:20 +00:00
func ClassicMatcherParser ( l log . Logger , m * Metrics ) ParseMatcher {
return func ( input , origin string ) ( matcher * labels . Matcher , err error ) {
defer func ( ) {
lbs := prometheus . Labels { "origin" : origin }
m . Total . With ( lbs ) . Inc ( )
if err != nil {
m . InvalidTotal . With ( lbs ) . Inc ( )
}
} ( )
level . Debug ( l ) . Log ( "msg" , "Parsing with classic matchers parser" , "input" , input )
return labels . ParseMatcher ( input )
2023-10-19 11:00:01 +00:00
}
}
2024-01-05 10:21:20 +00:00
// ClassicMatchersParser uses the pkg/labels parser to parse zero or more
2023-10-19 11:00:01 +00:00
// matchers in the input string. It returns an error if the input is invalid.
2024-01-05 10:21:20 +00:00
func ClassicMatchersParser ( l log . Logger , m * Metrics ) ParseMatchers {
return func ( input , origin string ) ( matchers labels . Matchers , err error ) {
defer func ( ) {
lbs := prometheus . Labels { "origin" : origin }
m . Total . With ( lbs ) . Inc ( )
if err != nil {
m . InvalidTotal . With ( lbs ) . Inc ( )
}
} ( )
level . Debug ( l ) . Log ( "msg" , "Parsing with classic matchers parser" , "input" , input )
return labels . ParseMatchers ( input )
2023-10-19 11:00:01 +00:00
}
}
2024-01-05 10:21:20 +00:00
// UTF8MatcherParser uses the new matchers/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 log . Logger , m * Metrics ) ParseMatcher {
return func ( input , origin string ) ( matcher * labels . Matcher , err error ) {
defer func ( ) {
lbs := prometheus . Labels { "origin" : origin }
m . Total . With ( lbs ) . Inc ( )
if err != nil {
m . InvalidTotal . With ( lbs ) . Inc ( )
}
} ( )
level . Debug ( l ) . Log ( "msg" , "Parsing with UTF-8 matchers parser" , "input" , input )
if strings . HasPrefix ( input , "{" ) || strings . HasSuffix ( input , "}" ) {
return nil , fmt . Errorf ( "unexpected open or close brace: %s" , input )
2023-10-19 11:00:01 +00:00
}
2024-01-05 10:21:20 +00:00
return parse . Matcher ( input )
2023-10-19 11:00:01 +00:00
}
}
2024-01-05 10:21:20 +00:00
// UTF8MatchersParser uses the new matchers/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 log . Logger , m * Metrics ) ParseMatchers {
return func ( input , origin string ) ( matchers labels . Matchers , err error ) {
defer func ( ) {
lbs := prometheus . Labels { "origin" : origin }
m . Total . With ( lbs ) . Inc ( )
if err != nil {
m . InvalidTotal . With ( lbs ) . Inc ( )
}
} ( )
level . Debug ( l ) . Log ( "msg" , "Parsing with UTF-8 matchers parser" , "input" , input )
return parse . Matchers ( input )
2023-10-19 11:00:01 +00:00
}
}
2024-01-05 10:21:20 +00:00
// FallbackMatcherParser uses the new matchers/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 log . Logger , m * Metrics ) ParseMatcher {
return func ( input , origin string ) ( matcher * labels . Matcher , err error ) {
lbs := prometheus . Labels { "origin" : origin }
defer func ( ) {
m . Total . With ( lbs ) . Inc ( )
if err != nil {
m . InvalidTotal . With ( lbs ) . Inc ( )
}
} ( )
level . Debug ( l ) . Log ( "msg" , "Parsing with UTF-8 matchers parser, with fallback to classic matchers parser" , "input" , input )
if strings . HasPrefix ( input , "{" ) || strings . HasSuffix ( input , "}" ) {
return nil , fmt . Errorf ( "unexpected open or close brace: %s" , input )
2023-10-19 11:00:01 +00:00
}
2024-01-05 10:21:20 +00:00
// 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
2023-10-19 11:00:01 +00:00
}
2024-01-05 10:21:20 +00:00
// The input is valid in the pkg/labels parser, but not the matchers/parse
// parser. This means the input is not forwards compatible.
m . IncompatibleTotal . With ( lbs ) . Inc ( )
suggestion := cMatcher . String ( )
level . Warn ( l ) . Log ( "msg" , "Alertmanager is moving to a new parser for labels and matchers, and this input is incompatible. Alertmanager has instead parsed the input using the old matchers parser as a fallback. To make this input compatible with the new parser please make sure all regular expressions and values are double-quoted. If you are still seeing this message please open an issue." , "input" , input , "err" , err , "suggestion" , suggestion )
return cMatcher , nil
}
// If the input is valid in both parsers, but produces different results,
// then there is disagreement.
if nErr == nil && cErr == nil && ! reflect . DeepEqual ( nMatcher , cMatcher ) {
m . DisagreeTotal . With ( lbs ) . Inc ( )
level . Warn ( l ) . Log ( "msg" , "Matchers input has disagreement" , "input" , input )
return cMatcher , nil
2023-10-19 11:00:01 +00:00
}
2024-01-05 10:21:20 +00:00
return nMatcher , nil
2023-10-19 11:00:01 +00:00
}
}
2024-01-04 17:42:58 +00:00
// FallbackMatchersParser uses the new matchers/parse parser to parse the
2024-01-05 10:21:20 +00:00
// 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 log . Logger , m * Metrics ) ParseMatchers {
return func ( input , origin string ) ( matchers labels . Matchers , err error ) {
lbs := prometheus . Labels { "origin" : origin }
defer func ( ) {
m . Total . With ( lbs ) . Inc ( )
if err != nil {
m . InvalidTotal . With ( lbs ) . Inc ( )
2023-10-19 11:00:01 +00:00
}
2024-01-05 10:21:20 +00:00
} ( )
level . Debug ( l ) . Log ( "msg" , "Parsing with UTF-8 matchers parser, with fallback to classic matchers parser" , "input" , input )
// 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 matchers/parse
// parser. This means the input is not forwards compatible.
m . IncompatibleTotal . With ( lbs ) . Inc ( )
2023-10-19 11:00:01 +00:00
var sb strings . Builder
2024-01-05 10:21:20 +00:00
for i , n := range cMatchers {
2023-10-19 11:00:01 +00:00
sb . WriteString ( n . String ( ) )
2024-01-05 10:21:20 +00:00
if i < len ( cMatchers ) - 1 {
2023-10-19 11:00:01 +00:00
sb . WriteRune ( ',' )
}
}
suggestion := sb . String ( )
2024-01-05 10:21:20 +00:00
// The input is valid in the pkg/labels parser, but not the
2023-10-19 11:00:01 +00:00
// new matchers/parse parser.
2024-01-05 10:21:20 +00:00
level . Warn ( l ) . Log ( "msg" , "Alertmanager is moving to a new parser for labels and matchers, and this input is incompatible. Alertmanager has instead parsed the input using the old matchers parser as a fallback. To make this input compatible with the new parser please make sure all regular expressions and values are double-quoted. If you are still seeing this message please open an issue." , "input" , input , "err" , err , "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 nErr == nil && cErr == nil && ! reflect . DeepEqual ( nMatchers , labels . Matchers ( cMatchers ) ) {
m . DisagreeTotal . With ( lbs ) . Inc ( )
level . Warn ( l ) . Log ( "msg" , "Matchers input has disagreement" , "input" , input )
return cMatchers , nil
2023-10-19 11:00:01 +00:00
}
2024-01-05 10:21:20 +00:00
return nMatchers , nil
2023-10-19 11:00:01 +00:00
}
}
2023-11-24 11:26:39 +00:00
// isValidClassicLabelName returns true if the string is a valid classic label name.
func isValidClassicLabelName ( _ log . Logger ) func ( model . LabelName ) bool {
return func ( name model . LabelName ) bool {
return name . IsValid ( )
}
}
// isValidUTF8LabelName returns true if the string is a valid UTF-8 label name.
func isValidUTF8LabelName ( _ log . Logger ) func ( model . LabelName ) bool {
return func ( name model . LabelName ) bool {
return utf8 . ValidString ( string ( name ) )
}
}