diff --git a/pkg/services/ngalert/CHANGELOG.md b/pkg/services/ngalert/CHANGELOG.md index fd868e58f38..e93c9c65116 100644 --- a/pkg/services/ngalert/CHANGELOG.md +++ b/pkg/services/ngalert/CHANGELOG.md @@ -54,11 +54,12 @@ Scopes must have an order to ensure consistency and ease of search, this helps u - [FEATURE] Indicate whether routes are provisioned when GETting Alertmanager configuration #47857 - [FEATURE] Indicate whether contact point is provisioned when GETting Alertmanager configuration #48323 - [FEATURE] Indicate whether alert rule is provisioned when GETting the rule #48458 +- [FEATURE] Alert rules with associated panels will take screenshots. #49293 #49338 #49374 #49377 #49378 #49379 #49381 #49385 #49439 #49445 - [BUGFIX] Migration: ignore alerts that do not belong to any existing organization\dashboard #49192 - [BUGFIX] Allow anonymous access to alerts #49203 - [BUGFIX] RBAC: replace create\update\delete actions for notification policies by alert.notifications:write #49185 - [BUGFIX] Fix access to alerts for Viewer role with editor permissions in folder #49270 -- [FEATURE] Alert rules with associated panels will take screenshots. #49293 #49338 #49374 #49377 #49378 #49379 #49381 #49385 #49439 #49445 +- [BUGFIX] Alerting: Remove double quotes from double quoted matchers #xxxx - [ENHANCEMENT] Scheduler: ticker to support stopping #48142 ## 8.5.3 diff --git a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go index fa27afd52f3..3ef36cf7dc0 100644 --- a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go +++ b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go @@ -7,6 +7,7 @@ import ( "fmt" "reflect" "sort" + "strings" "time" "github.com/go-openapi/strfmt" @@ -727,7 +728,7 @@ func (r *Route) UnmarshalYAML(unmarshal func(interface{}) error) error { return r.validateChild() } -// Return an alertmanager route from a Grafana route. The ObjectMatchers are converted to Matchers. +// AsAMRoute returns an Alertmanager route from a Grafana route. The ObjectMatchers are converted to Matchers. func (r *Route) AsAMRoute() *config.Route { amRoute := &config.Route{ Receiver: r.Receiver, @@ -753,7 +754,7 @@ func (r *Route) AsAMRoute() *config.Route { return amRoute } -// Return a Grafana route from an alertmanager route. The Matchers are converted to ObjectMatchers. +// AsGrafanaRoute returns a Grafana route from an Alertmanager route. The Matchers are converted to ObjectMatchers. func AsGrafanaRoute(r *config.Route) *Route { gRoute := &Route{ Receiver: r.Receiver, @@ -1226,6 +1227,22 @@ func (m *ObjectMatchers) UnmarshalYAML(unmarshal func(interface{}) error) error return fmt.Errorf("unsupported match type %q in matcher", rawMatcher[1]) } + // When Prometheus serializes a matcher, the value gets wrapped in quotes: + // https://github.com/prometheus/alertmanager/blob/main/pkg/labels/matcher.go#L77 + // Remove these quotes so that we are matching against the right value. + // + // This is a stop-gap solution which will be superceded by https://github.com/grafana/grafana/issues/50040. + // + // The ngalert migration converts matchers into the Prom-style, quotes included. + // The UI then stores the quotes into ObjectMatchers without removing them. + // This approach allows these extra quotes to be stored in the database, and fixes them at read time. + // This works because the database stores matchers as JSON text. + // + // There is a subtle bug here, where users might intentionally add quotes to matchers. This method can remove such quotes. + // Since ObjectMatchers will be deprecated entirely, this bug will go away naturally with time. + rawMatcher[2] = strings.TrimPrefix(rawMatcher[2], "\"") + rawMatcher[2] = strings.TrimSuffix(rawMatcher[2], "\"") + matcher, err := labels.NewMatcher(matchType, rawMatcher[0], rawMatcher[2]) if err != nil { return err @@ -1257,6 +1274,9 @@ func (m *ObjectMatchers) UnmarshalJSON(data []byte) error { return fmt.Errorf("unsupported match type %q in matcher", rawMatcher[1]) } + rawMatcher[2] = strings.TrimPrefix(rawMatcher[2], "\"") + rawMatcher[2] = strings.TrimSuffix(rawMatcher[2], "\"") + matcher, err := labels.NewMatcher(matchType, rawMatcher[0], rawMatcher[2]) if err != nil { return err diff --git a/pkg/services/ngalert/api/tooling/definitions/alertmanager_test.go b/pkg/services/ngalert/api/tooling/definitions/alertmanager_test.go index a5c88d01621..726cea1d75c 100644 --- a/pkg/services/ngalert/api/tooling/definitions/alertmanager_test.go +++ b/pkg/services/ngalert/api/tooling/definitions/alertmanager_test.go @@ -939,6 +939,107 @@ func Test_ReceiverMatchesBackend(t *testing.T) { } } +func TestObjectMatchers_UnmarshalJSON(t *testing.T) { + j := `{ + "receiver": "autogen-contact-point-default", + "routes": [{ + "receiver": "autogen-contact-point-1", + "object_matchers": [ + [ + "a", + "=", + "MFR3Gxrnk" + ], + [ + "b", + "=", + "\"MFR3Gxrnk\"" + ], + [ + "c", + "=~", + "^[a-z0-9-]{1}[a-z0-9-]{0,30}$" + ], + [ + "d", + "=~", + "\"^[a-z0-9-]{1}[a-z0-9-]{0,30}$\"" + ] + ], + "group_interval": "3s", + "repeat_interval": "10s" + }] +}` + var r Route + if err := json.Unmarshal([]byte(j), &r); err != nil { + require.NoError(t, err) + } + + matchers := r.Routes[0].ObjectMatchers + + // Without quotes. + require.Equal(t, matchers[0].Name, "a") + require.Equal(t, matchers[0].Value, "MFR3Gxrnk") + + // With double quotes. + require.Equal(t, matchers[1].Name, "b") + require.Equal(t, matchers[1].Value, "MFR3Gxrnk") + + // Regexp without quotes. + require.Equal(t, matchers[2].Name, "c") + require.Equal(t, matchers[2].Value, "^[a-z0-9-]{1}[a-z0-9-]{0,30}$") + + // Regexp with quotes. + require.Equal(t, matchers[3].Name, "d") + require.Equal(t, matchers[3].Value, "^[a-z0-9-]{1}[a-z0-9-]{0,30}$") +} + +func TestObjectMatchers_UnmarshalYAML(t *testing.T) { + y := `--- +receiver: autogen-contact-point-default +routes: +- receiver: autogen-contact-point-1 + object_matchers: + - - a + - "=" + - MFR3Gxrnk + - - b + - "=" + - '"MFR3Gxrnk"' + - - c + - "=~" + - "^[a-z0-9-]{1}[a-z0-9-]{0,30}$" + - - d + - "=~" + - '"^[a-z0-9-]{1}[a-z0-9-]{0,30}$"' + group_interval: 3s + repeat_interval: 10s +` + + var r Route + if err := yaml.Unmarshal([]byte(y), &r); err != nil { + require.NoError(t, err) + } + + matchers := r.Routes[0].ObjectMatchers + + // Without quotes. + require.Equal(t, matchers[0].Name, "a") + require.Equal(t, matchers[0].Value, "MFR3Gxrnk") + + // With double quotes. + require.Equal(t, matchers[1].Name, "b") + require.Equal(t, matchers[1].Value, "MFR3Gxrnk") + + // Regexp without quotes. + require.Equal(t, matchers[2].Name, "c") + require.Equal(t, matchers[2].Value, "^[a-z0-9-]{1}[a-z0-9-]{0,30}$") + + // Regexp with quotes. + require.Equal(t, matchers[3].Name, "d") + require.Equal(t, matchers[3].Value, "^[a-z0-9-]{1}[a-z0-9-]{0,30}$") +} + func Test_Marshaling_Validation(t *testing.T) { jsonEncoded, err := ioutil.ReadFile("alertmanager_test_artifact.json") require.Nil(t, err)