mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Allow more characters in label names so notifications are sent (#38629)
Remove validation for labels to be accepted in the Alertmanager, This helps with datasources that produce non-compatible labels. Adds an "object_matchers" to alert manager routers so we can support labels names with extended characters beyond prometheus/openmetrics. It only does this for the internal Grafana managed Alert Manager. This requires a change to alert manager, so for now we use grafana/alertmanager which is a slight fork, with the intention of going back to upstream. The frontend handles the migration of "matchers" -> "object_matchers" when the route is edited and saved. Once this is done, downgrades will not work old versions will not recognize the "object_matchers". Co-authored-by: Kyle Brandt <kyle@grafana.com> Co-authored-by: Nathan Rodman <nathanrodman@gmail.com>
This commit is contained in:
@@ -5,12 +5,15 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/pkg/errors"
|
||||
amv2 "github.com/prometheus/alertmanager/api/v2/models"
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/pkg/labels"
|
||||
"github.com/prometheus/common/model"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
@@ -214,7 +217,7 @@ func (s *GettableStatus) UnmarshalJSON(b []byte) error {
|
||||
s.Cluster = amStatus.Cluster
|
||||
s.Config = &PostableApiAlertingConfig{Config: Config{
|
||||
Global: c.Global,
|
||||
Route: c.Route,
|
||||
Route: AsGrafanaRoute(c.Route),
|
||||
InhibitRules: c.InhibitRules,
|
||||
Templates: c.Templates,
|
||||
}}
|
||||
@@ -556,7 +559,7 @@ func (c *GettableApiAlertingConfig) validate() error {
|
||||
return fmt.Errorf("cannot mix Alertmanager & Grafana receiver types")
|
||||
}
|
||||
|
||||
for _, receiver := range AllReceivers(c.Route) {
|
||||
for _, receiver := range AllReceivers(c.Route.AsAMRoute()) {
|
||||
_, ok := receivers[receiver]
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected receiver (%s) is undefined", receiver)
|
||||
@@ -569,11 +572,124 @@ func (c *GettableApiAlertingConfig) validate() error {
|
||||
// Config is the top-level configuration for Alertmanager's config files.
|
||||
type Config struct {
|
||||
Global *config.GlobalConfig `yaml:"global,omitempty" json:"global,omitempty"`
|
||||
Route *config.Route `yaml:"route,omitempty" json:"route,omitempty"`
|
||||
Route *Route `yaml:"route,omitempty" json:"route,omitempty"`
|
||||
InhibitRules []*config.InhibitRule `yaml:"inhibit_rules,omitempty" json:"inhibit_rules,omitempty"`
|
||||
Templates []string `yaml:"templates" json:"templates"`
|
||||
}
|
||||
|
||||
// A Route is a node that contains definitions of how to handle alerts. This is modified
|
||||
// from the upstream alertmanager in that it adds the ObjectMatchers property.
|
||||
type Route struct {
|
||||
Receiver string `yaml:"receiver,omitempty" json:"receiver,omitempty"`
|
||||
|
||||
GroupByStr []string `yaml:"group_by,omitempty" json:"group_by,omitempty"`
|
||||
GroupBy []model.LabelName `yaml:"-" json:"-"`
|
||||
GroupByAll bool `yaml:"-" json:"-"`
|
||||
// Deprecated. Remove before v1.0 release.
|
||||
Match map[string]string `yaml:"match,omitempty" json:"match,omitempty"`
|
||||
// Deprecated. Remove before v1.0 release.
|
||||
MatchRE config.MatchRegexps `yaml:"match_re,omitempty" json:"match_re,omitempty"`
|
||||
Matchers config.Matchers `yaml:"matchers,omitempty" json:"matchers,omitempty"`
|
||||
ObjectMatchers ObjectMatchers `yaml:"object_matchers,omitempty" json:"object_matchers,omitempty"`
|
||||
MuteTimeIntervals []string `yaml:"mute_time_intervals,omitempty" json:"mute_time_intervals,omitempty"`
|
||||
Continue bool `yaml:"continue" json:"continue,omitempty"`
|
||||
Routes []*Route `yaml:"routes,omitempty" json:"routes,omitempty"`
|
||||
|
||||
GroupWait *model.Duration `yaml:"group_wait,omitempty" json:"group_wait,omitempty"`
|
||||
GroupInterval *model.Duration `yaml:"group_interval,omitempty" json:"group_interval,omitempty"`
|
||||
RepeatInterval *model.Duration `yaml:"repeat_interval,omitempty" json:"repeat_interval,omitempty"`
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface for Route. This is a copy of alertmanager's upstream except it removes validation on the label key.
|
||||
func (r *Route) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
type plain Route
|
||||
if err := unmarshal((*plain)(r)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, l := range r.GroupByStr {
|
||||
if l == "..." {
|
||||
r.GroupByAll = true
|
||||
} else {
|
||||
r.GroupBy = append(r.GroupBy, model.LabelName(l))
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.GroupBy) > 0 && r.GroupByAll {
|
||||
return fmt.Errorf("cannot have wildcard group_by (`...`) and other other labels at the same time")
|
||||
}
|
||||
|
||||
groupBy := map[model.LabelName]struct{}{}
|
||||
|
||||
for _, ln := range r.GroupBy {
|
||||
if _, ok := groupBy[ln]; ok {
|
||||
return fmt.Errorf("duplicated label %q in group_by", ln)
|
||||
}
|
||||
groupBy[ln] = struct{}{}
|
||||
}
|
||||
|
||||
if r.GroupInterval != nil && time.Duration(*r.GroupInterval) == time.Duration(0) {
|
||||
return fmt.Errorf("group_interval cannot be zero")
|
||||
}
|
||||
if r.RepeatInterval != nil && time.Duration(*r.RepeatInterval) == time.Duration(0) {
|
||||
return fmt.Errorf("repeat_interval cannot be zero")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return 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,
|
||||
GroupByStr: r.GroupByStr,
|
||||
GroupBy: r.GroupBy,
|
||||
GroupByAll: r.GroupByAll,
|
||||
Match: r.Match,
|
||||
MatchRE: r.MatchRE,
|
||||
Matchers: append(r.Matchers, r.ObjectMatchers...),
|
||||
MuteTimeIntervals: r.MuteTimeIntervals,
|
||||
Continue: r.Continue,
|
||||
|
||||
GroupWait: r.GroupWait,
|
||||
GroupInterval: r.GroupInterval,
|
||||
RepeatInterval: r.RepeatInterval,
|
||||
|
||||
Routes: make([]*config.Route, 0, len(r.Routes)),
|
||||
}
|
||||
for _, rt := range r.Routes {
|
||||
amRoute.Routes = append(amRoute.Routes, rt.AsAMRoute())
|
||||
}
|
||||
|
||||
return amRoute
|
||||
}
|
||||
|
||||
// Return a Grafana route from an alertmanager route. The Matchers are converted to ObjectMatchers.
|
||||
func AsGrafanaRoute(r *config.Route) *Route {
|
||||
gRoute := &Route{
|
||||
Receiver: r.Receiver,
|
||||
GroupByStr: r.GroupByStr,
|
||||
GroupBy: r.GroupBy,
|
||||
GroupByAll: r.GroupByAll,
|
||||
Match: r.Match,
|
||||
MatchRE: r.MatchRE,
|
||||
ObjectMatchers: ObjectMatchers(r.Matchers),
|
||||
MuteTimeIntervals: r.MuteTimeIntervals,
|
||||
Continue: r.Continue,
|
||||
|
||||
GroupWait: r.GroupWait,
|
||||
GroupInterval: r.GroupInterval,
|
||||
RepeatInterval: r.RepeatInterval,
|
||||
|
||||
Routes: make([]*Route, 0, len(r.Routes)),
|
||||
}
|
||||
for _, rt := range r.Routes {
|
||||
gRoute.Routes = append(gRoute.Routes, AsGrafanaRoute(rt))
|
||||
}
|
||||
|
||||
return gRoute
|
||||
}
|
||||
|
||||
// Config is the entrypoint for the embedded Alertmanager config with the exception of receivers.
|
||||
// Prometheus historically uses yaml files as the method of configuration and thus some
|
||||
// post-validation is included in the UnmarshalYAML method. Here we simply run this with
|
||||
@@ -686,7 +802,7 @@ func (c *PostableApiAlertingConfig) validate() error {
|
||||
}
|
||||
}
|
||||
|
||||
for _, receiver := range AllReceivers(c.Route) {
|
||||
for _, receiver := range AllReceivers(c.Route.AsAMRoute()) {
|
||||
_, ok := receivers[receiver]
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected receiver (%s) is undefined", receiver)
|
||||
@@ -972,3 +1088,90 @@ func processReceiverConfigs(c []*PostableApiReceiver) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ObjectMatchers is Matchers with a different Unmarshal and Marshal methods that accept matchers as objects
|
||||
// that have already been parsed.
|
||||
type ObjectMatchers labels.Matchers
|
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface for Matchers.
|
||||
func (m *ObjectMatchers) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var rawMatchers [][3]string
|
||||
if err := unmarshal(&rawMatchers); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, rawMatcher := range rawMatchers {
|
||||
var matchType labels.MatchType
|
||||
switch rawMatcher[1] {
|
||||
case "=":
|
||||
matchType = labels.MatchEqual
|
||||
case "!=":
|
||||
matchType = labels.MatchNotEqual
|
||||
case "=~":
|
||||
matchType = labels.MatchRegexp
|
||||
case "!~":
|
||||
matchType = labels.MatchNotRegexp
|
||||
default:
|
||||
return fmt.Errorf("unsupported match type %q in matcher", rawMatcher[1])
|
||||
}
|
||||
|
||||
matcher, err := labels.NewMatcher(matchType, rawMatcher[0], rawMatcher[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*m = append(*m, matcher)
|
||||
}
|
||||
sort.Sort(labels.Matchers(*m))
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface for Matchers.
|
||||
func (m *ObjectMatchers) UnmarshalJSON(data []byte) error {
|
||||
var rawMatchers [][3]string
|
||||
if err := json.Unmarshal(data, &rawMatchers); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, rawMatcher := range rawMatchers {
|
||||
var matchType labels.MatchType
|
||||
switch rawMatcher[1] {
|
||||
case "=":
|
||||
matchType = labels.MatchEqual
|
||||
case "!=":
|
||||
matchType = labels.MatchNotEqual
|
||||
case "=~":
|
||||
matchType = labels.MatchRegexp
|
||||
case "!~":
|
||||
matchType = labels.MatchNotRegexp
|
||||
default:
|
||||
return fmt.Errorf("unsupported match type %q in matcher", rawMatcher[1])
|
||||
}
|
||||
|
||||
matcher, err := labels.NewMatcher(matchType, rawMatcher[0], rawMatcher[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*m = append(*m, matcher)
|
||||
}
|
||||
sort.Sort(labels.Matchers(*m))
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalYAML implements the yaml.Marshaler interface for Matchers.
|
||||
func (m ObjectMatchers) MarshalYAML() (interface{}, error) {
|
||||
result := make([][3]string, len(m))
|
||||
for i, matcher := range m {
|
||||
result[i] = [3]string{matcher.Name, matcher.Type.String(), matcher.Value}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface for Matchers.
|
||||
func (m ObjectMatchers) MarshalJSON() ([]byte, error) {
|
||||
if len(m) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
result := make([][3]string, len(m))
|
||||
for i, matcher := range m {
|
||||
result[i] = [3]string{matcher.Name, matcher.Type.String(), matcher.Value}
|
||||
}
|
||||
return json.Marshal(result)
|
||||
}
|
||||
|
||||
@@ -115,12 +115,12 @@ func Test_APIReceiverType(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_AllReceivers(t *testing.T) {
|
||||
input := &config.Route{
|
||||
input := &Route{
|
||||
Receiver: "foo",
|
||||
Routes: []*config.Route{
|
||||
Routes: []*Route{
|
||||
{
|
||||
Receiver: "bar",
|
||||
Routes: []*config.Route{
|
||||
Routes: []*Route{
|
||||
{
|
||||
Receiver: "bazz",
|
||||
},
|
||||
@@ -132,11 +132,12 @@ func Test_AllReceivers(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
require.Equal(t, []string{"foo", "bar", "bazz", "buzz"}, AllReceivers(input))
|
||||
require.Equal(t, []string{"foo", "bar", "bazz", "buzz"}, AllReceivers(input.AsAMRoute()))
|
||||
|
||||
// test empty
|
||||
var empty []string
|
||||
require.Equal(t, empty, AllReceivers(&config.Route{}))
|
||||
emptyRoute := &Route{}
|
||||
require.Equal(t, empty, AllReceivers(emptyRoute.AsAMRoute()))
|
||||
}
|
||||
|
||||
func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
|
||||
@@ -149,9 +150,9 @@ func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
|
||||
desc: "success am",
|
||||
input: PostableApiAlertingConfig{
|
||||
Config: Config{
|
||||
Route: &config.Route{
|
||||
Route: &Route{
|
||||
Receiver: "am",
|
||||
Routes: []*config.Route{
|
||||
Routes: []*Route{
|
||||
{
|
||||
Receiver: "am",
|
||||
},
|
||||
@@ -172,9 +173,9 @@ func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
|
||||
desc: "success graf",
|
||||
input: PostableApiAlertingConfig{
|
||||
Config: Config{
|
||||
Route: &config.Route{
|
||||
Route: &Route{
|
||||
Receiver: "graf",
|
||||
Routes: []*config.Route{
|
||||
Routes: []*Route{
|
||||
{
|
||||
Receiver: "graf",
|
||||
},
|
||||
@@ -197,9 +198,9 @@ func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
|
||||
desc: "failure undefined am receiver",
|
||||
input: PostableApiAlertingConfig{
|
||||
Config: Config{
|
||||
Route: &config.Route{
|
||||
Route: &Route{
|
||||
Receiver: "am",
|
||||
Routes: []*config.Route{
|
||||
Routes: []*Route{
|
||||
{
|
||||
Receiver: "unmentioned",
|
||||
},
|
||||
@@ -221,9 +222,9 @@ func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
|
||||
desc: "failure undefined graf receiver",
|
||||
input: PostableApiAlertingConfig{
|
||||
Config: Config{
|
||||
Route: &config.Route{
|
||||
Route: &Route{
|
||||
Receiver: "graf",
|
||||
Routes: []*config.Route{
|
||||
Routes: []*Route{
|
||||
{
|
||||
Receiver: "unmentioned",
|
||||
},
|
||||
@@ -263,8 +264,8 @@ func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
|
||||
desc: "failure graf no default receiver",
|
||||
input: PostableApiAlertingConfig{
|
||||
Config: Config{
|
||||
Route: &config.Route{
|
||||
Routes: []*config.Route{
|
||||
Route: &Route{
|
||||
Routes: []*Route{
|
||||
{
|
||||
Receiver: "graf",
|
||||
},
|
||||
@@ -288,9 +289,9 @@ func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
|
||||
desc: "failure graf root route with matchers",
|
||||
input: PostableApiAlertingConfig{
|
||||
Config: Config{
|
||||
Route: &config.Route{
|
||||
Route: &Route{
|
||||
Receiver: "graf",
|
||||
Routes: []*config.Route{
|
||||
Routes: []*Route{
|
||||
{
|
||||
Receiver: "graf",
|
||||
},
|
||||
@@ -315,9 +316,9 @@ func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
|
||||
desc: "failure graf nested route duplicate group by labels",
|
||||
input: PostableApiAlertingConfig{
|
||||
Config: Config{
|
||||
Route: &config.Route{
|
||||
Route: &Route{
|
||||
Receiver: "graf",
|
||||
Routes: []*config.Route{
|
||||
Routes: []*Route{
|
||||
{
|
||||
Receiver: "graf",
|
||||
GroupByStr: []string{"foo", "bar", "foo"},
|
||||
@@ -481,9 +482,9 @@ alertmanager_config: |
|
||||
AlertmanagerConfig: GettableApiAlertingConfig{
|
||||
Config: Config{
|
||||
Templates: []string{},
|
||||
Route: &config.Route{
|
||||
Route: &Route{
|
||||
Receiver: "am",
|
||||
Routes: []*config.Route{
|
||||
Routes: []*Route{
|
||||
{
|
||||
Receiver: "am",
|
||||
},
|
||||
|
||||
@@ -88,7 +88,7 @@ type AlertingRule struct {
|
||||
Query string `json:"query,omitempty"`
|
||||
Duration float64 `json:"duration,omitempty"`
|
||||
// required: true
|
||||
Annotations labels `json:"annotations,omitempty"`
|
||||
Annotations overrideLabels `json:"annotations,omitempty"`
|
||||
// required: true
|
||||
Alerts []*Alert `json:"alerts,omitempty"`
|
||||
Rule
|
||||
@@ -100,8 +100,8 @@ type Rule struct {
|
||||
// required: true
|
||||
Name string `json:"name"`
|
||||
// required: true
|
||||
Query string `json:"query"`
|
||||
Labels labels `json:"labels"`
|
||||
Query string `json:"query"`
|
||||
Labels overrideLabels `json:"labels"`
|
||||
// required: true
|
||||
Health string `json:"health"`
|
||||
LastError string `json:"lastError"`
|
||||
@@ -115,9 +115,9 @@ type Rule struct {
|
||||
// swagger:model
|
||||
type Alert struct {
|
||||
// required: true
|
||||
Labels labels `json:"labels"`
|
||||
Labels overrideLabels `json:"labels"`
|
||||
// required: true
|
||||
Annotations labels `json:"annotations"`
|
||||
Annotations overrideLabels `json:"annotations"`
|
||||
// required: true
|
||||
State string `json:"state"`
|
||||
ActiveAt *time.Time `json:"activeAt"`
|
||||
@@ -127,4 +127,4 @@ type Alert struct {
|
||||
|
||||
// override the labels type with a map for generation.
|
||||
// The custom marshaling for labels.Labels ends up doing this anyways.
|
||||
type labels map[string]string
|
||||
type overrideLabels map[string]string
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
"x-go-name": "ActiveAt"
|
||||
},
|
||||
"annotations": {
|
||||
"$ref": "#/definitions/labels"
|
||||
"$ref": "#/definitions/overrideLabels"
|
||||
},
|
||||
"labels": {
|
||||
"$ref": "#/definitions/labels"
|
||||
"$ref": "#/definitions/overrideLabels"
|
||||
},
|
||||
"state": {
|
||||
"type": "string",
|
||||
@@ -179,7 +179,7 @@
|
||||
"x-go-name": "Alerts"
|
||||
},
|
||||
"annotations": {
|
||||
"$ref": "#/definitions/labels"
|
||||
"$ref": "#/definitions/overrideLabels"
|
||||
},
|
||||
"duration": {
|
||||
"format": "double",
|
||||
@@ -196,7 +196,7 @@
|
||||
"x-go-name": "Health"
|
||||
},
|
||||
"labels": {
|
||||
"$ref": "#/definitions/labels"
|
||||
"$ref": "#/definitions/overrideLabels"
|
||||
},
|
||||
"lastError": {
|
||||
"type": "string",
|
||||
@@ -607,6 +607,13 @@
|
||||
"type": "array",
|
||||
"x-go-name": "SlackConfigs"
|
||||
},
|
||||
"sns_configs": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/SNSConfig"
|
||||
},
|
||||
"type": "array",
|
||||
"x-go-name": "SNSConfigs"
|
||||
},
|
||||
"victorops_configs": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/VictorOpsConfig"
|
||||
@@ -1164,6 +1171,10 @@
|
||||
"type": "object",
|
||||
"x-go-package": "github.com/prometheus/common/config"
|
||||
},
|
||||
"ObjectMatchers": {
|
||||
"$ref": "#/definitions/Matchers",
|
||||
"description": "ObjectMatchers is Matchers with a different Unmarshal and Marshal methods that accept matchers as objects\nthat have already been parsed."
|
||||
},
|
||||
"OpsGenieConfig": {
|
||||
"properties": {
|
||||
"api_key": {
|
||||
@@ -1216,6 +1227,10 @@
|
||||
"tags": {
|
||||
"type": "string",
|
||||
"x-go-name": "Tags"
|
||||
},
|
||||
"update_alerts": {
|
||||
"type": "boolean",
|
||||
"x-go-name": "UpdateAlerts"
|
||||
}
|
||||
},
|
||||
"title": "OpsGenieConfig configures notifications via OpsGenie.",
|
||||
@@ -1454,6 +1469,13 @@
|
||||
"type": "array",
|
||||
"x-go-name": "SlackConfigs"
|
||||
},
|
||||
"sns_configs": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/SNSConfig"
|
||||
},
|
||||
"type": "array",
|
||||
"x-go-name": "SNSConfigs"
|
||||
},
|
||||
"victorops_configs": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/VictorOpsConfig"
|
||||
@@ -1747,6 +1769,13 @@
|
||||
"type": "array",
|
||||
"x-go-name": "SlackConfigs"
|
||||
},
|
||||
"sns_configs": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/SNSConfig"
|
||||
},
|
||||
"type": "array",
|
||||
"x-go-name": "SNSConfigs"
|
||||
},
|
||||
"victorops_configs": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/VictorOpsConfig"
|
||||
@@ -1803,6 +1832,7 @@
|
||||
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
},
|
||||
"Route": {
|
||||
"description": "A Route is a node that contains definitions of how to handle alerts. This is modified\nfrom the upstream alertmanager in that it adds the ObjectMatchers property.",
|
||||
"properties": {
|
||||
"continue": {
|
||||
"type": "boolean",
|
||||
@@ -1842,6 +1872,9 @@
|
||||
"type": "array",
|
||||
"x-go-name": "MuteTimeIntervals"
|
||||
},
|
||||
"object_matchers": {
|
||||
"$ref": "#/definitions/ObjectMatchers"
|
||||
},
|
||||
"receiver": {
|
||||
"type": "string",
|
||||
"x-go-name": "Receiver"
|
||||
@@ -1857,9 +1890,8 @@
|
||||
"x-go-name": "Routes"
|
||||
}
|
||||
},
|
||||
"title": "A Route is a node that contains definitions of how to handle alerts.",
|
||||
"type": "object",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/config"
|
||||
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
},
|
||||
"Rule": {
|
||||
"description": "adapted from cortex",
|
||||
@@ -1874,7 +1906,7 @@
|
||||
"x-go-name": "Health"
|
||||
},
|
||||
"labels": {
|
||||
"$ref": "#/definitions/labels"
|
||||
"$ref": "#/definitions/overrideLabels"
|
||||
},
|
||||
"lastError": {
|
||||
"type": "string",
|
||||
@@ -2013,6 +2045,53 @@
|
||||
"type": "string",
|
||||
"x-go-package": "github.com/prometheus/client_golang/api/prometheus/v1"
|
||||
},
|
||||
"SNSConfig": {
|
||||
"properties": {
|
||||
"api_url": {
|
||||
"type": "string",
|
||||
"x-go-name": "APIUrl"
|
||||
},
|
||||
"attributes": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "object",
|
||||
"x-go-name": "Attributes"
|
||||
},
|
||||
"http_config": {
|
||||
"$ref": "#/definitions/HTTPClientConfig"
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"x-go-name": "Message"
|
||||
},
|
||||
"phone_number": {
|
||||
"type": "string",
|
||||
"x-go-name": "PhoneNumber"
|
||||
},
|
||||
"send_resolved": {
|
||||
"type": "boolean",
|
||||
"x-go-name": "VSendResolved"
|
||||
},
|
||||
"sigv4": {
|
||||
"$ref": "#/definitions/SigV4Config"
|
||||
},
|
||||
"subject": {
|
||||
"type": "string",
|
||||
"x-go-name": "Subject"
|
||||
},
|
||||
"target_arn": {
|
||||
"type": "string",
|
||||
"x-go-name": "TargetARN"
|
||||
},
|
||||
"topic_arn": {
|
||||
"type": "string",
|
||||
"x-go-name": "TopicARN"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/config"
|
||||
},
|
||||
"Sample": {
|
||||
"properties": {
|
||||
"Metric": {
|
||||
@@ -2040,6 +2119,28 @@
|
||||
"$ref": "#/definitions/URL",
|
||||
"title": "SecretURL is a URL that must not be revealed on marshaling."
|
||||
},
|
||||
"SigV4Config": {
|
||||
"description": "SigV4Config is the configuration for signing remote write requests with\nAWS's SigV4 verification process. Empty values will be retrieved using the\nAWS default credentials chain.",
|
||||
"properties": {
|
||||
"AccessKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"Profile": {
|
||||
"type": "string"
|
||||
},
|
||||
"Region": {
|
||||
"type": "string"
|
||||
},
|
||||
"RoleARN": {
|
||||
"type": "string"
|
||||
},
|
||||
"SecretKey": {
|
||||
"$ref": "#/definitions/Secret"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"x-go-package": "github.com/prometheus/common/sigv4"
|
||||
},
|
||||
"SlackAction": {
|
||||
"description": "See https://api.slack.com/docs/message-attachments#action_fields and https://api.slack.com/docs/message-buttons\nfor more information.",
|
||||
"properties": {
|
||||
@@ -2548,7 +2649,6 @@
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
|
||||
},
|
||||
"alertGroup": {
|
||||
"description": "AlertGroup alert group",
|
||||
"properties": {
|
||||
"alerts": {
|
||||
"description": "alerts",
|
||||
@@ -2570,14 +2670,17 @@
|
||||
"labels",
|
||||
"receiver"
|
||||
],
|
||||
"type": "object"
|
||||
"type": "object",
|
||||
"x-go-name": "AlertGroup",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
|
||||
},
|
||||
"alertGroups": {
|
||||
"description": "AlertGroups alert groups",
|
||||
"items": {
|
||||
"$ref": "#/definitions/alertGroup"
|
||||
},
|
||||
"type": "array"
|
||||
"type": "array",
|
||||
"x-go-name": "AlertGroups",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
|
||||
},
|
||||
"alertStatus": {
|
||||
"description": "AlertStatus alert status",
|
||||
@@ -2697,6 +2800,7 @@
|
||||
"$ref": "#/definitions/Duration"
|
||||
},
|
||||
"gettableAlert": {
|
||||
"description": "GettableAlert gettable alert",
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"$ref": "#/definitions/labelSet"
|
||||
@@ -2755,17 +2859,14 @@
|
||||
"status",
|
||||
"updatedAt"
|
||||
],
|
||||
"type": "object",
|
||||
"x-go-name": "GettableAlert",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
|
||||
"type": "object"
|
||||
},
|
||||
"gettableAlerts": {
|
||||
"description": "GettableAlerts gettable alerts",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gettableAlert"
|
||||
},
|
||||
"type": "array",
|
||||
"x-go-name": "GettableAlerts",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
|
||||
"type": "array"
|
||||
},
|
||||
"gettableSilence": {
|
||||
"properties": {
|
||||
@@ -2824,12 +2925,11 @@
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
|
||||
},
|
||||
"gettableSilences": {
|
||||
"description": "GettableSilences gettable silences",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gettableSilence"
|
||||
},
|
||||
"type": "array",
|
||||
"x-go-name": "GettableSilences",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
|
||||
"type": "array"
|
||||
},
|
||||
"labelSet": {
|
||||
"additionalProperties": {
|
||||
@@ -2840,15 +2940,6 @@
|
||||
"x-go-name": "LabelSet",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
|
||||
},
|
||||
"labels": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "The custom marshaling for labels.Labels ends up doing this anyways.",
|
||||
"title": "override the labels type with a map for generation.",
|
||||
"type": "object",
|
||||
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
},
|
||||
"matcher": {
|
||||
"description": "Matcher matcher",
|
||||
"properties": {
|
||||
@@ -2891,6 +2982,15 @@
|
||||
"x-go-name": "Matchers",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
|
||||
},
|
||||
"overrideLabels": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "The custom marshaling for labels.Labels ends up doing this anyways.",
|
||||
"title": "override the labels type with a map for generation.",
|
||||
"type": "object",
|
||||
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
},
|
||||
"peerStatus": {
|
||||
"description": "PeerStatus peer status",
|
||||
"properties": {
|
||||
@@ -2958,7 +3058,6 @@
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
|
||||
},
|
||||
"postableSilence": {
|
||||
"description": "PostableSilence postable silence",
|
||||
"properties": {
|
||||
"comment": {
|
||||
"description": "comment",
|
||||
@@ -2998,9 +3097,12 @@
|
||||
"matchers",
|
||||
"startsAt"
|
||||
],
|
||||
"type": "object"
|
||||
"type": "object",
|
||||
"x-go-name": "PostableSilence",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
|
||||
},
|
||||
"receiver": {
|
||||
"description": "Receiver receiver",
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "name",
|
||||
@@ -3011,9 +3113,7 @@
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object",
|
||||
"x-go-name": "Receiver",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
|
||||
"type": "object"
|
||||
},
|
||||
"silence": {
|
||||
"description": "Silence silence",
|
||||
|
||||
@@ -1024,10 +1024,10 @@
|
||||
"x-go-name": "ActiveAt"
|
||||
},
|
||||
"annotations": {
|
||||
"$ref": "#/definitions/labels"
|
||||
"$ref": "#/definitions/overrideLabels"
|
||||
},
|
||||
"labels": {
|
||||
"$ref": "#/definitions/labels"
|
||||
"$ref": "#/definitions/overrideLabels"
|
||||
},
|
||||
"state": {
|
||||
"type": "string",
|
||||
@@ -1189,7 +1189,7 @@
|
||||
"x-go-name": "Alerts"
|
||||
},
|
||||
"annotations": {
|
||||
"$ref": "#/definitions/labels"
|
||||
"$ref": "#/definitions/overrideLabels"
|
||||
},
|
||||
"duration": {
|
||||
"type": "number",
|
||||
@@ -1206,7 +1206,7 @@
|
||||
"x-go-name": "Health"
|
||||
},
|
||||
"labels": {
|
||||
"$ref": "#/definitions/labels"
|
||||
"$ref": "#/definitions/overrideLabels"
|
||||
},
|
||||
"lastError": {
|
||||
"type": "string",
|
||||
@@ -1611,6 +1611,13 @@
|
||||
},
|
||||
"x-go-name": "SlackConfigs"
|
||||
},
|
||||
"sns_configs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/SNSConfig"
|
||||
},
|
||||
"x-go-name": "SNSConfigs"
|
||||
},
|
||||
"victorops_configs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@@ -2168,6 +2175,10 @@
|
||||
},
|
||||
"x-go-package": "github.com/prometheus/common/config"
|
||||
},
|
||||
"ObjectMatchers": {
|
||||
"description": "ObjectMatchers is Matchers with a different Unmarshal and Marshal methods that accept matchers as objects\nthat have already been parsed.",
|
||||
"$ref": "#/definitions/Matchers"
|
||||
},
|
||||
"OpsGenieConfig": {
|
||||
"type": "object",
|
||||
"title": "OpsGenieConfig configures notifications via OpsGenie.",
|
||||
@@ -2222,6 +2233,10 @@
|
||||
"tags": {
|
||||
"type": "string",
|
||||
"x-go-name": "Tags"
|
||||
},
|
||||
"update_alerts": {
|
||||
"type": "boolean",
|
||||
"x-go-name": "UpdateAlerts"
|
||||
}
|
||||
},
|
||||
"x-go-package": "github.com/prometheus/alertmanager/config"
|
||||
@@ -2459,6 +2474,13 @@
|
||||
},
|
||||
"x-go-name": "SlackConfigs"
|
||||
},
|
||||
"sns_configs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/SNSConfig"
|
||||
},
|
||||
"x-go-name": "SNSConfigs"
|
||||
},
|
||||
"victorops_configs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@@ -2753,6 +2775,13 @@
|
||||
},
|
||||
"x-go-name": "SlackConfigs"
|
||||
},
|
||||
"sns_configs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/SNSConfig"
|
||||
},
|
||||
"x-go-name": "SNSConfigs"
|
||||
},
|
||||
"victorops_configs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@@ -2807,8 +2836,8 @@
|
||||
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
},
|
||||
"Route": {
|
||||
"description": "A Route is a node that contains definitions of how to handle alerts. This is modified\nfrom the upstream alertmanager in that it adds the ObjectMatchers property.",
|
||||
"type": "object",
|
||||
"title": "A Route is a node that contains definitions of how to handle alerts.",
|
||||
"properties": {
|
||||
"continue": {
|
||||
"type": "boolean",
|
||||
@@ -2848,6 +2877,9 @@
|
||||
},
|
||||
"x-go-name": "MuteTimeIntervals"
|
||||
},
|
||||
"object_matchers": {
|
||||
"$ref": "#/definitions/ObjectMatchers"
|
||||
},
|
||||
"receiver": {
|
||||
"type": "string",
|
||||
"x-go-name": "Receiver"
|
||||
@@ -2863,7 +2895,7 @@
|
||||
"x-go-name": "Routes"
|
||||
}
|
||||
},
|
||||
"x-go-package": "github.com/prometheus/alertmanager/config"
|
||||
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
},
|
||||
"Rule": {
|
||||
"description": "adapted from cortex",
|
||||
@@ -2885,7 +2917,7 @@
|
||||
"x-go-name": "Health"
|
||||
},
|
||||
"labels": {
|
||||
"$ref": "#/definitions/labels"
|
||||
"$ref": "#/definitions/overrideLabels"
|
||||
},
|
||||
"lastError": {
|
||||
"type": "string",
|
||||
@@ -3017,6 +3049,53 @@
|
||||
"title": "RuleType models the type of a rule.",
|
||||
"x-go-package": "github.com/prometheus/client_golang/api/prometheus/v1"
|
||||
},
|
||||
"SNSConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"api_url": {
|
||||
"type": "string",
|
||||
"x-go-name": "APIUrl"
|
||||
},
|
||||
"attributes": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"x-go-name": "Attributes"
|
||||
},
|
||||
"http_config": {
|
||||
"$ref": "#/definitions/HTTPClientConfig"
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"x-go-name": "Message"
|
||||
},
|
||||
"phone_number": {
|
||||
"type": "string",
|
||||
"x-go-name": "PhoneNumber"
|
||||
},
|
||||
"send_resolved": {
|
||||
"type": "boolean",
|
||||
"x-go-name": "VSendResolved"
|
||||
},
|
||||
"sigv4": {
|
||||
"$ref": "#/definitions/SigV4Config"
|
||||
},
|
||||
"subject": {
|
||||
"type": "string",
|
||||
"x-go-name": "Subject"
|
||||
},
|
||||
"target_arn": {
|
||||
"type": "string",
|
||||
"x-go-name": "TargetARN"
|
||||
},
|
||||
"topic_arn": {
|
||||
"type": "string",
|
||||
"x-go-name": "TopicARN"
|
||||
}
|
||||
},
|
||||
"x-go-package": "github.com/prometheus/alertmanager/config"
|
||||
},
|
||||
"Sample": {
|
||||
"type": "object",
|
||||
"title": "Sample is a single sample belonging to a metric.",
|
||||
@@ -3044,6 +3123,28 @@
|
||||
"title": "SecretURL is a URL that must not be revealed on marshaling.",
|
||||
"$ref": "#/definitions/URL"
|
||||
},
|
||||
"SigV4Config": {
|
||||
"description": "SigV4Config is the configuration for signing remote write requests with\nAWS's SigV4 verification process. Empty values will be retrieved using the\nAWS default credentials chain.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"AccessKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"Profile": {
|
||||
"type": "string"
|
||||
},
|
||||
"Region": {
|
||||
"type": "string"
|
||||
},
|
||||
"RoleARN": {
|
||||
"type": "string"
|
||||
},
|
||||
"SecretKey": {
|
||||
"$ref": "#/definitions/Secret"
|
||||
}
|
||||
},
|
||||
"x-go-package": "github.com/prometheus/common/sigv4"
|
||||
},
|
||||
"SlackAction": {
|
||||
"description": "See https://api.slack.com/docs/message-attachments#action_fields and https://api.slack.com/docs/message-buttons\nfor more information.",
|
||||
"type": "object",
|
||||
@@ -3552,7 +3653,6 @@
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
|
||||
},
|
||||
"alertGroup": {
|
||||
"description": "AlertGroup alert group",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"alerts",
|
||||
@@ -3575,14 +3675,17 @@
|
||||
"$ref": "#/definitions/receiver"
|
||||
}
|
||||
},
|
||||
"x-go-name": "AlertGroup",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models",
|
||||
"$ref": "#/definitions/alertGroup"
|
||||
},
|
||||
"alertGroups": {
|
||||
"description": "AlertGroups alert groups",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/alertGroup"
|
||||
},
|
||||
"x-go-name": "AlertGroups",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models",
|
||||
"$ref": "#/definitions/alertGroups"
|
||||
},
|
||||
"alertStatus": {
|
||||
@@ -3703,6 +3806,7 @@
|
||||
"$ref": "#/definitions/Duration"
|
||||
},
|
||||
"gettableAlert": {
|
||||
"description": "GettableAlert gettable alert",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"labels",
|
||||
@@ -3762,17 +3866,14 @@
|
||||
"x-go-name": "UpdatedAt"
|
||||
}
|
||||
},
|
||||
"x-go-name": "GettableAlert",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models",
|
||||
"$ref": "#/definitions/gettableAlert"
|
||||
},
|
||||
"gettableAlerts": {
|
||||
"description": "GettableAlerts gettable alerts",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gettableAlert"
|
||||
},
|
||||
"x-go-name": "GettableAlerts",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models",
|
||||
"$ref": "#/definitions/gettableAlerts"
|
||||
},
|
||||
"gettableSilence": {
|
||||
@@ -3833,12 +3934,11 @@
|
||||
"$ref": "#/definitions/gettableSilence"
|
||||
},
|
||||
"gettableSilences": {
|
||||
"description": "GettableSilences gettable silences",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gettableSilence"
|
||||
},
|
||||
"x-go-name": "GettableSilences",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models",
|
||||
"$ref": "#/definitions/gettableSilences"
|
||||
},
|
||||
"labelSet": {
|
||||
@@ -3850,15 +3950,6 @@
|
||||
"x-go-name": "LabelSet",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
|
||||
},
|
||||
"labels": {
|
||||
"description": "The custom marshaling for labels.Labels ends up doing this anyways.",
|
||||
"type": "object",
|
||||
"title": "override the labels type with a map for generation.",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
},
|
||||
"matcher": {
|
||||
"description": "Matcher matcher",
|
||||
"type": "object",
|
||||
@@ -3901,6 +3992,15 @@
|
||||
"x-go-name": "Matchers",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
|
||||
},
|
||||
"overrideLabels": {
|
||||
"description": "The custom marshaling for labels.Labels ends up doing this anyways.",
|
||||
"type": "object",
|
||||
"title": "override the labels type with a map for generation.",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
},
|
||||
"peerStatus": {
|
||||
"description": "PeerStatus peer status",
|
||||
"type": "object",
|
||||
@@ -3968,7 +4068,6 @@
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
|
||||
},
|
||||
"postableSilence": {
|
||||
"description": "PostableSilence postable silence",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"comment",
|
||||
@@ -4009,9 +4108,12 @@
|
||||
"x-go-name": "StartsAt"
|
||||
}
|
||||
},
|
||||
"x-go-name": "PostableSilence",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models",
|
||||
"$ref": "#/definitions/postableSilence"
|
||||
},
|
||||
"receiver": {
|
||||
"description": "Receiver receiver",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name"
|
||||
@@ -4023,8 +4125,6 @@
|
||||
"x-go-name": "Name"
|
||||
}
|
||||
},
|
||||
"x-go-name": "Receiver",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models",
|
||||
"$ref": "#/definitions/receiver"
|
||||
},
|
||||
"silence": {
|
||||
|
||||
@@ -10,9 +10,11 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
gokit_log "github.com/go-kit/kit/log"
|
||||
amv2 "github.com/prometheus/alertmanager/api/v2/models"
|
||||
@@ -39,6 +41,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
pb "github.com/prometheus/alertmanager/silence/silencepb"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -57,6 +60,24 @@ const (
|
||||
memoryAlertsGCInterval = 30 * time.Minute
|
||||
)
|
||||
|
||||
func init() {
|
||||
silence.ValidateMatcher = func(m *pb.Matcher) error {
|
||||
switch m.Type {
|
||||
case pb.Matcher_EQUAL, pb.Matcher_NOT_EQUAL:
|
||||
if !model.LabelValue(m.Pattern).IsValid() {
|
||||
return fmt.Errorf("invalid label value %q", m.Pattern)
|
||||
}
|
||||
case pb.Matcher_REGEXP, pb.Matcher_NOT_REGEXP:
|
||||
if _, err := regexp.Compile(m.Pattern); err != nil {
|
||||
return fmt.Errorf("invalid regular expression %q: %s", m.Pattern, err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown matcher type %q", m.Type)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type ClusterPeer interface {
|
||||
AddState(string, cluster.State, prometheus.Registerer) cluster.ClusterChannel
|
||||
Position() int
|
||||
@@ -392,7 +413,7 @@ func (am *Alertmanager) applyConfig(cfg *apimodels.PostableUserConfig, rawConfig
|
||||
routingStage[name] = notify.MultiStage{meshStage, silencingStage, inhibitionStage, stage}
|
||||
}
|
||||
|
||||
am.route = dispatch.NewRoute(cfg.AlertmanagerConfig.Route, nil)
|
||||
am.route = dispatch.NewRoute(cfg.AlertmanagerConfig.Route.AsAMRoute(), nil)
|
||||
am.dispatcher = dispatch.NewDispatcher(am.alerts, am.route, routingStage, am.marker, am.timeoutFunc, &nilLimits{}, am.gokitLogger, am.dispatcherMetrics)
|
||||
|
||||
am.wg.Add(1)
|
||||
@@ -638,22 +659,14 @@ func validateLabelSet(ls model.LabelSet) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// isValidLabelName is ln.IsValid() while additionally allowing spaces.
|
||||
// The regex for Prometheus data model is ^[a-zA-Z_][a-zA-Z0-9_]*$
|
||||
// while we will follow ^[a-zA-Z_][a-zA-Z0-9_ ]*$
|
||||
// isValidLabelName is ln.IsValid() without restrictions other than it can not be empty.
|
||||
// The regex for Prometheus data model is ^[a-zA-Z_][a-zA-Z0-9_]*$.
|
||||
func isValidLabelName(ln model.LabelName) bool {
|
||||
if len(ln) == 0 {
|
||||
return false
|
||||
}
|
||||
for i, b := range ln {
|
||||
if !((b >= 'a' && b <= 'z') ||
|
||||
(b >= 'A' && b <= 'Z') ||
|
||||
b == '_' ||
|
||||
(i > 0 && (b == ' ' || (b >= '0' && b <= '9')))) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
return utf8.ValidString(string(ln))
|
||||
}
|
||||
|
||||
// AlertValidationError is the error capturing the validation errors
|
||||
|
||||
@@ -208,48 +208,57 @@ func TestPutAlert(t *testing.T) {
|
||||
}
|
||||
},
|
||||
}, {
|
||||
title: "Invalid labels",
|
||||
title: "Special characters in labels",
|
||||
postableAlerts: apimodels.PostableAlerts{
|
||||
PostableAlerts: []models.PostableAlert{
|
||||
{
|
||||
Alert: models.Alert{
|
||||
Labels: models.LabelSet{"alertname$": "Alert1"},
|
||||
Labels: models.LabelSet{"alertname$": "Alert1", "az3-- __...++!!!£@@312312": "1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expError: &AlertValidationError{
|
||||
Alerts: []models.PostableAlert{
|
||||
expAlerts: func(now time.Time) []*types.Alert {
|
||||
return []*types.Alert{
|
||||
{
|
||||
Alert: models.Alert{
|
||||
Labels: models.LabelSet{"alertname$": "Alert1"},
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"alertname$": "Alert1", "az3-- __...++!!!£@@312312": "1"},
|
||||
Annotations: model.LabelSet{},
|
||||
StartsAt: now,
|
||||
EndsAt: now.Add(defaultResolveTimeout),
|
||||
GeneratorURL: "",
|
||||
},
|
||||
UpdatedAt: now,
|
||||
Timeout: true,
|
||||
},
|
||||
},
|
||||
Errors: []error{errors.New("invalid label set: invalid name \"alertname$\"")},
|
||||
}
|
||||
},
|
||||
}, {
|
||||
title: "Invalid annotation",
|
||||
title: "Special characters in annotations",
|
||||
postableAlerts: apimodels.PostableAlerts{
|
||||
PostableAlerts: []models.PostableAlert{
|
||||
{
|
||||
Annotations: models.LabelSet{"msg$": "Alert4 annotation"},
|
||||
Annotations: models.LabelSet{"az3-- __...++!!!£@@312312": "Alert4 annotation"},
|
||||
Alert: models.Alert{
|
||||
Labels: models.LabelSet{"alertname": "Alert1"},
|
||||
Labels: models.LabelSet{"alertname": "Alert4"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expError: &AlertValidationError{
|
||||
Alerts: []models.PostableAlert{
|
||||
expAlerts: func(now time.Time) []*types.Alert {
|
||||
return []*types.Alert{
|
||||
{
|
||||
Annotations: models.LabelSet{"msg$": "Alert4 annotation"},
|
||||
Alert: models.Alert{
|
||||
Labels: models.LabelSet{"alertname": "Alert1"},
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"alertname": "Alert4"},
|
||||
Annotations: model.LabelSet{"az3-- __...++!!!£@@312312": "Alert4 annotation"},
|
||||
StartsAt: now,
|
||||
EndsAt: now.Add(defaultResolveTimeout),
|
||||
GeneratorURL: "",
|
||||
},
|
||||
UpdatedAt: now,
|
||||
Timeout: true,
|
||||
},
|
||||
},
|
||||
Errors: []error{errors.New("invalid annotations: invalid name \"msg$\"")},
|
||||
}
|
||||
},
|
||||
}, {
|
||||
title: "No labels after removing empty",
|
||||
|
||||
Reference in New Issue
Block a user