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-11-06 09:09:57 +00:00
"log/slog"
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
2023-11-24 11:26:39 +00:00
"github.com/prometheus/common/model"
2024-11-06 09:09:57 +00:00
"github.com/prometheus/common/promslog"
2023-10-19 11:00:01 +00:00
"github.com/prometheus/alertmanager/featurecontrol"
2024-06-21 14:17:27 +00:00
"github.com/prometheus/alertmanager/matcher/parse"
2023-10-19 11:00:01 +00:00
"github.com/prometheus/alertmanager/pkg/labels"
)
var (
2024-11-06 09:09:57 +00:00
isValidLabelName = isValidClassicLabelName ( promslog . NewNopLogger ( ) )
parseMatcher = ClassicMatcherParser ( promslog . NewNopLogger ( ) )
parseMatchers = ClassicMatchersParser ( promslog . NewNopLogger ( ) )
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-11-06 09:09:57 +00:00
func InitFromFlags ( l * slog . Logger , 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 )
Remove metrics from compat package (#3714)
This commit removes the metrics from the compat package
in favour of the existing logging and the additional tools
at hand, such as amtool, to validate Alertmanager configurations.
Due to the global nature of the compat package, a consequence
of config.Load, these metrics have proven to be less useful
in practice than expected, both in Alertmanager and other projects
such as Mimir.
There are a number of reasons for this:
1. Because the compat package is global, these metrics cannot be
reset each time config.Load is called, as in multi-tenant
projects like Mimir loading a config for one tenant would reset
the metrics for all tenants. This is also the reason the metrics
are counters and not gauges.
2. Since the metrics are counters, it is difficult to create
meaningful dashboards for Alertmanager as, unlike in Mimir,
configurations are not reloaded at fixed intervals, and as such,
operators cannot use rate to track configuration changes
over time.
In Alertmanager, there are much better tools available to validate
that an Alertmanager configuration is compatible with the UTF-8
parser, including both the existing logging from Alertmanager
server and amtool check-config.
In other projects like Mimir, we can track configurations for
individual tenants using log aggregation and storage systems
such as Loki. This gives operators far more information than
what is possible with the metrics, including the timestamp,
input and ID of tenant configurations that are incompatible
or have disagreement.
Signed-off-by: George Robinson <george.robinson@grafana.com>
2024-02-08 09:59:03 +00:00
parseMatcher = ClassicMatcherParser ( l )
parseMatchers = ClassicMatchersParser ( l )
2023-11-24 10:01:40 +00:00
} else if f . UTF8StrictMode ( ) {
2023-11-24 11:26:39 +00:00
isValidLabelName = isValidUTF8LabelName ( l )
Remove metrics from compat package (#3714)
This commit removes the metrics from the compat package
in favour of the existing logging and the additional tools
at hand, such as amtool, to validate Alertmanager configurations.
Due to the global nature of the compat package, a consequence
of config.Load, these metrics have proven to be less useful
in practice than expected, both in Alertmanager and other projects
such as Mimir.
There are a number of reasons for this:
1. Because the compat package is global, these metrics cannot be
reset each time config.Load is called, as in multi-tenant
projects like Mimir loading a config for one tenant would reset
the metrics for all tenants. This is also the reason the metrics
are counters and not gauges.
2. Since the metrics are counters, it is difficult to create
meaningful dashboards for Alertmanager as, unlike in Mimir,
configurations are not reloaded at fixed intervals, and as such,
operators cannot use rate to track configuration changes
over time.
In Alertmanager, there are much better tools available to validate
that an Alertmanager configuration is compatible with the UTF-8
parser, including both the existing logging from Alertmanager
server and amtool check-config.
In other projects like Mimir, we can track configurations for
individual tenants using log aggregation and storage systems
such as Loki. This gives operators far more information than
what is possible with the metrics, including the timestamp,
input and ID of tenant configurations that are incompatible
or have disagreement.
Signed-off-by: George Robinson <george.robinson@grafana.com>
2024-02-08 09:59:03 +00:00
parseMatcher = UTF8MatcherParser ( l )
parseMatchers = UTF8MatchersParser ( l )
2023-10-19 11:00:01 +00:00
} else {
2023-11-24 11:26:39 +00:00
isValidLabelName = isValidUTF8LabelName ( l )
Remove metrics from compat package (#3714)
This commit removes the metrics from the compat package
in favour of the existing logging and the additional tools
at hand, such as amtool, to validate Alertmanager configurations.
Due to the global nature of the compat package, a consequence
of config.Load, these metrics have proven to be less useful
in practice than expected, both in Alertmanager and other projects
such as Mimir.
There are a number of reasons for this:
1. Because the compat package is global, these metrics cannot be
reset each time config.Load is called, as in multi-tenant
projects like Mimir loading a config for one tenant would reset
the metrics for all tenants. This is also the reason the metrics
are counters and not gauges.
2. Since the metrics are counters, it is difficult to create
meaningful dashboards for Alertmanager as, unlike in Mimir,
configurations are not reloaded at fixed intervals, and as such,
operators cannot use rate to track configuration changes
over time.
In Alertmanager, there are much better tools available to validate
that an Alertmanager configuration is compatible with the UTF-8
parser, including both the existing logging from Alertmanager
server and amtool check-config.
In other projects like Mimir, we can track configurations for
individual tenants using log aggregation and storage systems
such as Loki. This gives operators far more information than
what is possible with the metrics, including the timestamp,
input and ID of tenant configurations that are incompatible
or have disagreement.
Signed-off-by: George Robinson <george.robinson@grafana.com>
2024-02-08 09:59:03 +00:00
parseMatcher = FallbackMatcherParser ( l )
parseMatchers = FallbackMatchersParser ( l )
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-11-06 09:09:57 +00:00
func ClassicMatcherParser ( l * slog . Logger ) ParseMatcher {
2024-01-05 10:21:20 +00:00
return func ( input , origin string ) ( matcher * labels . Matcher , err error ) {
2024-11-06 09:09:57 +00:00
l . Debug ( "Parsing with classic matchers parser" , "input" , input , "origin" , origin )
2024-01-05 10:21:20 +00:00
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-11-06 09:09:57 +00:00
func ClassicMatchersParser ( l * slog . Logger ) ParseMatchers {
2024-01-05 10:21:20 +00:00
return func ( input , origin string ) ( matchers labels . Matchers , err error ) {
2024-11-06 09:09:57 +00:00
l . Debug ( "Parsing with classic matchers parser" , "input" , input , "origin" , origin )
2024-01-05 10:21:20 +00:00
return labels . ParseMatchers ( input )
2023-10-19 11:00:01 +00:00
}
}
2024-06-21 14:17:27 +00:00
// UTF8MatcherParser uses the new matcher/parse parser to parse the matcher
2024-01-05 10:21:20 +00:00
// in the input string. If this fails it does not revert to the pkg/labels parser.
2024-11-06 09:09:57 +00:00
func UTF8MatcherParser ( l * slog . Logger ) ParseMatcher {
2024-01-05 10:21:20 +00:00
return func ( input , origin string ) ( matcher * labels . Matcher , err error ) {
2024-11-06 09:09:57 +00:00
l . Debug ( "Parsing with UTF-8 matchers parser" , "input" , input , "origin" , origin )
2024-01-05 10:21:20 +00:00
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-06-21 14:17:27 +00:00
// UTF8MatchersParser uses the new matcher/parse parser to parse zero or more
2024-01-05 10:21:20 +00:00
// matchers in the input string. If this fails it does not revert to the
// pkg/labels parser.
2024-11-06 09:09:57 +00:00
func UTF8MatchersParser ( l * slog . Logger ) ParseMatchers {
2024-01-05 10:21:20 +00:00
return func ( input , origin string ) ( matchers labels . Matchers , err error ) {
2024-11-06 09:09:57 +00:00
l . Debug ( "Parsing with UTF-8 matchers parser" , "input" , input , "origin" , origin )
2024-01-05 10:21:20 +00:00
return parse . Matchers ( input )
2023-10-19 11:00:01 +00:00
}
}
2024-06-21 14:17:27 +00:00
// FallbackMatcherParser uses the new matcher/parse parser to parse zero or more
2024-01-05 10:21:20 +00:00
// matchers in the string. If this fails it reverts to the pkg/labels parser and
// emits a warning log line.
2024-11-06 09:09:57 +00:00
func FallbackMatcherParser ( l * slog . Logger ) ParseMatcher {
2024-01-05 10:21:20 +00:00
return func ( input , origin string ) ( matcher * labels . Matcher , err error ) {
2024-11-06 09:09:57 +00:00
l . Debug ( "Parsing with UTF-8 matchers parser, with fallback to classic matchers parser" , "input" , input , "origin" , origin )
2024-01-05 10:21:20 +00:00
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-06-21 14:17:27 +00:00
// The input is valid in the pkg/labels parser, but not the matcher/parse
2024-01-05 10:21:20 +00:00
// parser. This means the input is not forwards compatible.
suggestion := cMatcher . String ( )
2024-11-06 09:09:57 +00:00
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. If you are still seeing this message please open an issue." , "input" , input , "origin" , origin , "err" , nErr , "suggestion" , suggestion )
2024-01-05 10:21:20 +00:00
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 ) {
2024-11-06 09:09:57 +00:00
l . Warn ( "Matchers input has disagreement" , "input" , input , "origin" , origin )
2024-01-05 10:21:20 +00:00
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-06-21 14:17:27 +00:00
// FallbackMatchersParser uses the new matcher/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.
2024-11-06 09:09:57 +00:00
func FallbackMatchersParser ( l * slog . Logger ) ParseMatchers {
2024-01-05 10:21:20 +00:00
return func ( input , origin string ) ( matchers labels . Matchers , err error ) {
2024-11-06 09:09:57 +00:00
l . Debug ( "Parsing with UTF-8 matchers parser, with fallback to classic matchers parser" , "input" , input , "origin" , origin )
2024-01-05 10:21:20 +00:00
// 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
}
2024-06-21 14:17:27 +00:00
// The input is valid in the pkg/labels parser, but not the matcher/parse
2024-01-05 10:21:20 +00:00
// parser. This means the input is not forwards compatible.
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
2024-06-21 14:17:27 +00:00
// new matcher/parse parser.
2024-11-06 09:09:57 +00:00
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. If you are still seeing this message please open an issue." , "input" , input , "origin" , origin , "err" , nErr , "suggestion" , suggestion )
2024-01-05 10:21:20 +00:00
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 ) ) {
2024-11-06 09:09:57 +00:00
l . Warn ( "Matchers input has disagreement" , "input" , input , "origin" , origin )
2024-01-05 10:21:20 +00:00
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.
2024-11-06 09:09:57 +00:00
func isValidClassicLabelName ( _ * slog . Logger ) func ( model . LabelName ) bool {
2023-11-24 11:26:39 +00:00
return func ( name model . LabelName ) bool {
return name . IsValid ( )
}
}
// isValidUTF8LabelName returns true if the string is a valid UTF-8 label name.
2024-11-06 09:09:57 +00:00
func isValidUTF8LabelName ( _ * slog . Logger ) func ( model . LabelName ) bool {
2023-11-24 11:26:39 +00:00
return func ( name model . LabelName ) bool {
2024-01-15 17:30:29 +00:00
if len ( name ) == 0 {
return false
}
2023-11-24 11:26:39 +00:00
return utf8 . ValidString ( string ( name ) )
}
}