Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3f96a45
Use compat package in Alertmanager server
grobinson-grafana Oct 20, 2023
48368ab
Fix tests
grobinson-grafana Oct 24, 2023
9d2d9b5
Fix lint
grobinson-grafana Oct 24, 2023
edd98b2
Update ValidateMatcher in silence.go
grobinson-grafana Nov 14, 2023
756a974
Accept alerts with UTF-8 labels
grobinson-grafana Nov 15, 2023
8095f0a
Fix lint
grobinson-grafana Nov 15, 2023
0bd3892
Allow expiration of invalid silences
grobinson-grafana Nov 15, 2023
b9965d1
Add acceptance tests for creating UTF-8 alerts
grobinson-grafana Nov 15, 2023
312a9da
Add tests for classic alerts
grobinson-grafana Nov 15, 2023
8022958
Add acceptance tests for creating silences
grobinson-grafana Nov 15, 2023
976b713
Fix lint
grobinson-grafana Nov 16, 2023
fa2bca6
Fix use of renamed feature flags
grobinson-grafana Nov 21, 2023
a38aeb4
Inject feature flags into APIs and Silencer
grobinson-grafana Nov 21, 2023
fc1a2db
Fix tests
grobinson-grafana Nov 22, 2023
6916407
Use parseFilters
grobinson-grafana Nov 22, 2023
7c5b767
Rename UTF-8 mode to UTF-8 strict mode
grobinson-grafana Nov 22, 2023
38a5bcd
Move UTF-8 tests to separate file
grobinson-grafana Nov 22, 2023
cda2a30
Add tests for filtering alerts and silences
grobinson-grafana Nov 22, 2023
fb98219
Fix license
grobinson-grafana Nov 22, 2023
5ab3d73
Fix lint
grobinson-grafana Nov 22, 2023
9fa29ad
Add test for filtering alert groups
grobinson-grafana Nov 22, 2023
9da9da6
Fix lint again
grobinson-grafana Nov 22, 2023
e3c7ba8
Fix tests
grobinson-grafana Nov 22, 2023
51187ea
Fix comments
grobinson-grafana Nov 22, 2023
da328a7
Add isDelete to setSilence
grobinson-grafana Nov 23, 2023
8c86375
Feedback
grobinson-grafana Nov 23, 2023
d5aebdf
Fix
grobinson-grafana Nov 23, 2023
969ccc3
Rename to skipValidate
grobinson-grafana Nov 23, 2023
7fa308e
Feedback
grobinson-grafana Nov 23, 2023
c3655b4
Fix rebase mistake
grobinson-grafana Nov 24, 2023
318b822
Fix deletion of v1
grobinson-grafana Nov 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not related - just something we missed from before, right? We had before silence_add, silence_query and alert_add so I guess it makes sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, correct! I noticed it as the changes I made in this PR caused a test to fail.

}
}

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
Loading