Skip to content

Commit

Permalink
Support UTF-8 label matchers: Use compat package in Alertmanager serv…
Browse files Browse the repository at this point in the history
…er (#3567)

* Support UTF-8 label matchers: Use compat package in Alertmanager server

This pull request adds use of the compat package in Alertmanager server that will allow users to switch between the new matchers/parse parser and the old pkg/labels parser. The new matchers/parse parser uses a fallback mechanism where if the input cannot be parsed in the new parser it then attempts to use the old parser. If an input is parsed in the old parser but not the new parser then a warning log is emitted.

Signed-off-by: George Robinson <george.robinson@grafana.com>

---------

Signed-off-by: George Robinson <george.robinson@grafana.com>
  • Loading branch information
grobinson-grafana authored Nov 24, 2023
1 parent b4f7027 commit 70bd5da
Show file tree
Hide file tree
Showing 15 changed files with 775 additions and 59 deletions.
5 changes: 5 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/prometheus/alertmanager/cluster"
"github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/dispatch"
"github.com/prometheus/alertmanager/featurecontrol"
"github.com/prometheus/alertmanager/provider"
"github.com/prometheus/alertmanager/silence"
"github.com/prometheus/alertmanager/types"
Expand Down Expand Up @@ -67,6 +68,9 @@ type Options struct {
Concurrency int
// Logger is used for logging, if nil, no logging will happen.
Logger log.Logger
// FeatureFlags contains the set of feature flags. If nil, NoopFlags are used,
// and all controlled features are disabled.
FeatureFlags featurecontrol.Flagger
// Registry is used to register Prometheus metrics. If nil, no metrics
// registration will happen.
Registry prometheus.Registerer
Expand Down Expand Up @@ -117,6 +121,7 @@ func New(opts Options) (*API, error) {
opts.Silences,
opts.Peer,
log.With(l, "version", "v2"),
opts.FeatureFlags,
opts.Registry,
)
if err != nil {
Expand Down
27 changes: 14 additions & 13 deletions api/v2/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ import (
"github.com/prometheus/alertmanager/cluster"
"github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/dispatch"
"github.com/prometheus/alertmanager/featurecontrol"
"github.com/prometheus/alertmanager/matchers/compat"
"github.com/prometheus/alertmanager/pkg/labels"
"github.com/prometheus/alertmanager/provider"
"github.com/prometheus/alertmanager/silence"
Expand All @@ -70,6 +72,7 @@ type API struct {

logger log.Logger
m *metrics.Alerts
ff featurecontrol.Flagger

Handler http.Handler
}
Expand All @@ -88,8 +91,12 @@ func NewAPI(
silences *silence.Silences,
peer cluster.ClusterPeer,
l log.Logger,
ff featurecontrol.Flagger,
r prometheus.Registerer,
) (*API, error) {
if ff == nil {
ff = featurecontrol.NoopFlags{}
}
api := API{
alerts: alerts,
getAlertStatus: sf,
Expand All @@ -98,6 +105,7 @@ func NewAPI(
silences: silences,
logger: l,
m: metrics.NewAlerts(r),
ff: ff,
uptime: time.Now(),
}

Expand Down Expand Up @@ -347,7 +355,7 @@ func (api *API) postAlertsHandler(params alert_ops.PostAlertsParams) middleware.
for _, a := range alerts {
removeEmptyLabels(a.Labels)

if err := a.Validate(); err != nil {
if err := a.Validate(api.ff); err != nil {
validationErrs.Add(err)
api.m.Invalid().Inc()
continue
Expand Down Expand Up @@ -505,17 +513,10 @@ func matchFilterLabels(matchers []*labels.Matcher, sms map[string]string) bool {
func (api *API) getSilencesHandler(params silence_ops.GetSilencesParams) middleware.Responder {
logger := api.requestLogger(params.HTTPRequest)

matchers := []*labels.Matcher{}
if params.Filter != nil {
for _, matcherString := range params.Filter {
matcher, err := labels.ParseMatcher(matcherString)
if err != nil {
level.Debug(logger).Log("msg", "Failed to parse matchers", "err", err)
return silence_ops.NewGetSilencesBadRequest().WithPayload(err.Error())
}

matchers = append(matchers, matcher)
}
matchers, err := parseFilter(params.Filter)
if err != nil {
level.Debug(logger).Log("msg", "Failed to parse matchers", "err", err)
return silence_ops.NewGetSilencesBadRequest().WithPayload(err.Error())
}

psils, _, err := api.silences.Query()
Expand Down Expand Up @@ -682,7 +683,7 @@ func (api *API) postSilencesHandler(params silence_ops.PostSilencesParams) middl
func parseFilter(filter []string) ([]*labels.Matcher, error) {
matchers := make([]*labels.Matcher, 0, len(filter))
for _, matcherString := range filter {
matcher, err := labels.ParseMatcher(matcherString)
matcher, err := compat.Matcher(matcherString)
if err != nil {
return nil, err
}
Expand Down
3 changes: 2 additions & 1 deletion cli/alert_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"context"
"errors"
"fmt"
"strconv"

"github.com/alecthomas/kingpin/v2"

Expand Down Expand Up @@ -82,7 +83,7 @@ func (a *alertQueryCmd) queryAlerts(ctx context.Context, _ *kingpin.ParseContext
m := a.matcherGroups[0]
_, err := compat.Matcher(m)
if err != nil {
a.matcherGroups[0] = fmt.Sprintf("alertname=%s", m)
a.matcherGroups[0] = fmt.Sprintf("alertname=%s", strconv.Quote(m))
}
}

Expand Down
26 changes: 15 additions & 11 deletions cmd/alertmanager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import (
"github.com/prometheus/alertmanager/dispatch"
"github.com/prometheus/alertmanager/featurecontrol"
"github.com/prometheus/alertmanager/inhibit"
"github.com/prometheus/alertmanager/matchers/compat"
"github.com/prometheus/alertmanager/nflog"
"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/provider/mem"
Expand Down Expand Up @@ -174,11 +175,12 @@ func run() int {
level.Info(logger).Log("msg", "Starting Alertmanager", "version", version.Info())
level.Info(logger).Log("build_context", version.BuildContext())

featureConfig, err := featurecontrol.NewFlags(logger, *featureFlags)
ff, err := featurecontrol.NewFlags(logger, *featureFlags)
if err != nil {
level.Error(logger).Log("msg", "error parsing the feature flag list", "err", err)
return 1
}
compat.InitFromFlags(logger, ff)

err = os.MkdirAll(*dataDir, 0o777)
if err != nil {
Expand Down Expand Up @@ -249,6 +251,7 @@ func run() int {
Retention: *retention,
Logger: log.With(logger, "component", "silences"),
Metrics: prometheus.DefaultRegisterer,
FeatureFlags: ff,
}

silences, err := silence.New(silenceOpts)
Expand Down Expand Up @@ -317,15 +320,16 @@ func run() int {
}

api, err := api.New(api.Options{
Alerts: alerts,
Silences: silences,
StatusFunc: marker.Status,
Peer: clusterPeer,
Timeout: *httpTimeout,
Concurrency: *getConcurrency,
Logger: log.With(logger, "component", "api"),
Registry: prometheus.DefaultRegisterer,
GroupFunc: groupFn,
Alerts: alerts,
Silences: silences,
StatusFunc: marker.Status,
Peer: clusterPeer,
Timeout: *httpTimeout,
Concurrency: *getConcurrency,
Logger: log.With(logger, "component", "api"),
FeatureFlags: ff,
Registry: prometheus.DefaultRegisterer,
GroupFunc: groupFn,
})
if err != nil {
level.Error(logger).Log("err", errors.Wrap(err, "failed to create API"))
Expand Down Expand Up @@ -356,7 +360,7 @@ func run() int {
)

dispMetrics := dispatch.NewDispatcherMetrics(false, prometheus.DefaultRegisterer)
pipelineBuilder := notify.NewPipelineBuilder(prometheus.DefaultRegisterer, featureConfig)
pipelineBuilder := notify.NewPipelineBuilder(prometheus.DefaultRegisterer, ff)
configLogger := log.With(logger, "component", "configuration")
configCoordinator := config.NewCoordinator(
*configFile,
Expand Down
5 changes: 3 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/prometheus/common/model"
"gopkg.in/yaml.v2"

"github.com/prometheus/alertmanager/matchers/compat"
"github.com/prometheus/alertmanager/pkg/labels"
"github.com/prometheus/alertmanager/timeinterval"
)
Expand Down Expand Up @@ -1005,7 +1006,7 @@ func (m *Matchers) UnmarshalYAML(unmarshal func(interface{}) error) error {
return err
}
for _, line := range lines {
pm, err := labels.ParseMatchers(line)
pm, err := compat.Matchers(line)
if err != nil {
return err
}
Expand All @@ -1031,7 +1032,7 @@ func (m *Matchers) UnmarshalJSON(data []byte) error {
return err
}
for _, line := range lines {
pm, err := labels.ParseMatchers(line)
pm, err := compat.Matchers(line)
if err != nil {
return err
}
Expand Down
24 changes: 12 additions & 12 deletions featurecontrol/featurecontrol.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,26 @@ import (
const (
FeatureReceiverNameInMetrics = "receiver-name-in-metrics"
FeatureClassicMode = "classic-mode"
FeatureUTF8Mode = "utf8-mode"
FeatureUTF8StrictMode = "utf8-strict-mode"
)

var AllowedFlags = []string{
FeatureReceiverNameInMetrics,
FeatureClassicMode,
FeatureUTF8Mode,
FeatureUTF8StrictMode,
}

type Flagger interface {
EnableReceiverNamesInMetrics() bool
ClassicMode() bool
UTF8Mode() bool
UTF8StrictMode() bool
}

type Flags struct {
logger log.Logger
enableReceiverNamesInMetrics bool
classicMode bool
utf8Mode bool
utf8StrictMode bool
}

func (f *Flags) EnableReceiverNamesInMetrics() bool {
Expand All @@ -55,8 +55,8 @@ func (f *Flags) ClassicMode() bool {
return f.classicMode
}

func (f *Flags) UTF8Mode() bool {
return f.utf8Mode
func (f *Flags) UTF8StrictMode() bool {
return f.utf8StrictMode
}

type flagOption func(flags *Flags)
Expand All @@ -73,9 +73,9 @@ func enableClassicMode() flagOption {
}
}

func enableUTF8Mode() flagOption {
func enableUTF8StrictMode() flagOption {
return func(configs *Flags) {
configs.utf8Mode = true
configs.utf8StrictMode = true
}
}

Expand All @@ -95,8 +95,8 @@ func NewFlags(logger log.Logger, features string) (Flagger, error) {
case FeatureClassicMode:
opts = append(opts, enableClassicMode())
level.Warn(logger).Log("msg", "Classic mode enabled")
case FeatureUTF8Mode:
opts = append(opts, enableUTF8Mode())
case FeatureUTF8StrictMode:
opts = append(opts, enableUTF8StrictMode())
level.Warn(logger).Log("msg", "UTF-8 mode enabled")
default:
return nil, fmt.Errorf("Unknown option '%s' for --enable-feature", feature)
Expand All @@ -107,7 +107,7 @@ func NewFlags(logger log.Logger, features string) (Flagger, error) {
opt(fc)
}

if fc.classicMode && fc.utf8Mode {
if fc.classicMode && fc.utf8StrictMode {
return nil, errors.New("cannot have both classic and UTF-8 modes enabled")
}

Expand All @@ -120,4 +120,4 @@ func (n NoopFlags) EnableReceiverNamesInMetrics() bool { return false }

func (n NoopFlags) ClassicMode() bool { return false }

func (n NoopFlags) UTF8Mode() bool { return false }
func (n NoopFlags) UTF8StrictMode() bool { return false }
2 changes: 1 addition & 1 deletion matchers/compat/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func InitFromFlags(l log.Logger, f featurecontrol.Flagger) {
if f.ClassicMode() {
parseMatcher = classicMatcherParser(l)
parseMatchers = classicMatchersParser(l)
} else if f.UTF8Mode() {
} else if f.UTF8StrictMode() {
parseMatcher = utf8MatcherParser(l)
parseMatchers = utf8MatchersParser(l)
} else {
Expand Down
Loading

0 comments on commit 70bd5da

Please sign in to comment.