[Alerting] Ensure upstream validations are run (#34333)

* use embedded validations via noop yaml unmarshaler

* lint

* fixes integration tests now that groupings are handled
This commit is contained in:
Owen Diehl 2021-05-19 06:22:44 -04:00 committed by GitHub
parent aed1c013c0
commit d6c4c2fcd5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 85 additions and 14 deletions

View File

@ -413,6 +413,17 @@ func (c *GettableApiAlertingConfig) UnmarshalJSON(b []byte) error {
return err return err
} }
// Since Config implements json.Unmarshaler, we must handle _all_ other fields independently.
// Otherwise, the json decoder will detect this and only use the embedded type.
// Additionally, we'll use pointers to slices in order to reference the intended target.
type overrides struct {
Receivers *[]*GettableApiReceiver `yaml:"receivers,omitempty" json:"receivers,omitempty"`
}
if err := json.Unmarshal(b, &overrides{Receivers: &c.Receivers}); err != nil {
return err
}
return c.validate() return c.validate()
} }
@ -452,10 +463,42 @@ type Config struct {
Global *config.GlobalConfig `yaml:"global,omitempty" json:"global,omitempty"` Global *config.GlobalConfig `yaml:"global,omitempty" json:"global,omitempty"`
Route *config.Route `yaml:"route,omitempty" json:"route,omitempty"` Route *config.Route `yaml:"route,omitempty" json:"route,omitempty"`
InhibitRules []*config.InhibitRule `yaml:"inhibit_rules,omitempty" json:"inhibit_rules,omitempty"` InhibitRules []*config.InhibitRule `yaml:"inhibit_rules,omitempty" json:"inhibit_rules,omitempty"`
Receivers []*config.Receiver `yaml:"-" json:"receivers,omitempty"`
Templates []string `yaml:"templates" json:"templates"` Templates []string `yaml:"templates" json:"templates"`
} }
// 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
// a noop unmarshaling function in order to benefit from said validation.
func (c *Config) UnmarshalJSON(b []byte) error {
type plain Config
if err := json.Unmarshal(b, (*plain)(c)); err != nil {
return err
}
noopUnmarshal := func(_ interface{}) error { return nil }
if c.Global != nil {
if err := c.Global.UnmarshalYAML(noopUnmarshal); err != nil {
return err
}
}
if c.Route != nil {
if err := c.Route.UnmarshalYAML(noopUnmarshal); err != nil {
return err
}
}
for _, r := range c.InhibitRules {
if err := r.UnmarshalYAML(noopUnmarshal); err != nil {
return err
}
}
return nil
}
type PostableApiAlertingConfig struct { type PostableApiAlertingConfig struct {
Config `yaml:",inline"` Config `yaml:",inline"`
@ -469,6 +512,17 @@ func (c *PostableApiAlertingConfig) UnmarshalJSON(b []byte) error {
return err return err
} }
// Since Config implements json.Unmarshaler, we must handle _all_ other fields independently.
// Otherwise, the json decoder will detect this and only use the embedded type.
// Additionally, we'll use pointers to slices in order to reference the intended target.
type overrides struct {
Receivers *[]*PostableApiReceiver `yaml:"receivers,omitempty" json:"receivers,omitempty"`
}
if err := json.Unmarshal(b, &overrides{Receivers: &c.Receivers}); err != nil {
return err
}
return c.validate() return c.validate()
} }

View File

@ -7,6 +7,7 @@ import (
"testing" "testing"
"github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/config"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
@ -563,3 +564,14 @@ func Test_ReceiverMatchesBackend(t *testing.T) {
}) })
} }
} }
func Test_Marshaling_Validation(t *testing.T) {
jsonEncoded, err := ioutil.ReadFile("alertmanager_test_artifact.json")
require.Nil(t, err)
var tmp GettableUserConfig
require.Nil(t, json.Unmarshal(jsonEncoded, &tmp))
expected := []model.LabelName{"alertname"}
require.Equal(t, expected, tmp.AlertmanagerConfig.Config.Route.GroupBy)
}

View File

@ -23,6 +23,9 @@
], ],
"route": { "route": {
"continue": false, "continue": false,
"group_by": [
"alertname"
],
"receiver": "am", "receiver": "am",
"routes": [ "routes": [
{ {

View File

@ -14,6 +14,8 @@ alertmanager_config: |
name: am name: am
route: route:
continue: false continue: false
group_by:
- alertname
receiver: am receiver: am
routes: routes:
- continue: false - continue: false

View File

@ -748,10 +748,10 @@ var expNotifications = map[string][]string{
"icon_url": "https://awesomeemoji.com/rocket", "icon_url": "https://awesomeemoji.com/rocket",
"attachments": [ "attachments": [
{ {
"title": "Integration Test [FIRING:1] (SlackAlert1 UID_SlackAlert1)", "title": "Integration Test [FIRING:1] SlackAlert1 (UID_SlackAlert1)",
"title_link": "http:/localhost:3000/alerting/list", "title_link": "http:/localhost:3000/alerting/list",
"text": "Integration Test ", "text": "Integration Test ",
"fallback": "Integration Test [FIRING:1] (SlackAlert1 UID_SlackAlert1)", "fallback": "Integration Test [FIRING:1] SlackAlert1 (UID_SlackAlert1)",
"footer": "Grafana v", "footer": "Grafana v",
"footer_icon": "https://grafana.com/assets/img/fav32.png", "footer_icon": "https://grafana.com/assets/img/fav32.png",
"color": "#D63232", "color": "#D63232",
@ -775,10 +775,10 @@ var expNotifications = map[string][]string{
"username": "Integration Test", "username": "Integration Test",
"attachments": [ "attachments": [
{ {
"title": "[FIRING:1] (SlackAlert2 UID_SlackAlert2)", "title": "[FIRING:1] SlackAlert2 (UID_SlackAlert2)",
"title_link": "http:/localhost:3000/alerting/list", "title_link": "http:/localhost:3000/alerting/list",
"text": "\n**Firing**\nLabels:\n - alertname = SlackAlert2\n - __alert_rule_uid__ = UID_SlackAlert2\nAnnotations:\nSource: \n\n\n\n\n", "text": "\n**Firing**\nLabels:\n - alertname = SlackAlert2\n - __alert_rule_uid__ = UID_SlackAlert2\nAnnotations:\nSource: \n\n\n\n\n",
"fallback": "[FIRING:1] (SlackAlert2 UID_SlackAlert2)", "fallback": "[FIRING:1] SlackAlert2 (UID_SlackAlert2)",
"footer": "Grafana v", "footer": "Grafana v",
"footer_icon": "https://grafana.com/assets/img/fav32.png", "footer_icon": "https://grafana.com/assets/img/fav32.png",
"color": "#D63232", "color": "#D63232",
@ -799,11 +799,11 @@ var expNotifications = map[string][]string{
"pagerduty_recvX/pagerduty_testX": { "pagerduty_recvX/pagerduty_testX": {
`{ `{
"routing_key": "pagerduty_recv/pagerduty_test", "routing_key": "pagerduty_recv/pagerduty_test",
"dedup_key": "718643b9694d44f7f2b21458afd1b079cb403cf264e51894ff3c9745238bcced", "dedup_key": "234edb34441f942f713f3c2ccf58b1d719d921b4cbe34e57a1630f1dee847e3b",
"description": "[FIRING:1] (PagerdutyAlert UID_PagerdutyAlert)", "description": "[FIRING:1] PagerdutyAlert (UID_PagerdutyAlert)",
"event_action": "trigger", "event_action": "trigger",
"payload": { "payload": {
"summary": "Integration Test [FIRING:1] (PagerdutyAlert UID_PagerdutyAlert)", "summary": "Integration Test [FIRING:1] PagerdutyAlert (UID_PagerdutyAlert)",
"source": "%s", "source": "%s",
"severity": "warning", "severity": "warning",
"class": "testclass", "class": "testclass",
@ -831,7 +831,7 @@ var expNotifications = map[string][]string{
"link": { "link": {
"messageUrl": "dingtalk://dingtalkclient/page/link?pc_slide=false&url=http%3A%2Flocalhost%3A3000%2Falerting%2Flist", "messageUrl": "dingtalk://dingtalkclient/page/link?pc_slide=false&url=http%3A%2Flocalhost%3A3000%2Falerting%2Flist",
"text": "\n**Firing**\nLabels:\n - alertname = DingDingAlert\n - __alert_rule_uid__ = UID_DingDingAlert\nAnnotations:\nSource: \n\n\n\n\n", "text": "\n**Firing**\nLabels:\n - alertname = DingDingAlert\n - __alert_rule_uid__ = UID_DingDingAlert\nAnnotations:\nSource: \n\n\n\n\n",
"title": "[FIRING:1] (DingDingAlert UID_DingDingAlert)" "title": "[FIRING:1] DingDingAlert (UID_DingDingAlert)"
}, },
"msgtype": "link" "msgtype": "link"
}`, }`,
@ -859,9 +859,9 @@ var expNotifications = map[string][]string{
"title": "Details" "title": "Details"
} }
], ],
"summary": "[FIRING:1] (TeamsAlert UID_TeamsAlert)", "summary": "[FIRING:1] TeamsAlert (UID_TeamsAlert)",
"themeColor": "#D63232", "themeColor": "#D63232",
"title": "[FIRING:1] (TeamsAlert UID_TeamsAlert)" "title": "[FIRING:1] TeamsAlert (UID_TeamsAlert)"
}`, }`,
}, },
"webhook_recv/webhook_test": { "webhook_recv/webhook_test": {
@ -882,7 +882,7 @@ var expNotifications = map[string][]string{
"fingerprint": "929467973978d053" "fingerprint": "929467973978d053"
} }
], ],
"groupLabels": {}, "groupLabels": {"alertname": "WebhookAlert"},
"commonLabels": { "commonLabels": {
"__alert_rule_uid__": "UID_WebhookAlert", "__alert_rule_uid__": "UID_WebhookAlert",
"alertname": "WebhookAlert" "alertname": "WebhookAlert"
@ -890,9 +890,9 @@ var expNotifications = map[string][]string{
"commonAnnotations": {}, "commonAnnotations": {},
"externalURL": "http://localhost:3000/", "externalURL": "http://localhost:3000/",
"version": "1", "version": "1",
"groupKey": "{}/{alertname=\"WebhookAlert\"}:{}", "groupKey": "{}/{alertname=\"WebhookAlert\"}:{alertname=\"WebhookAlert\"}",
"truncatedAlerts": 0, "truncatedAlerts": 0,
"title": "[FIRING:1] (WebhookAlert UID_WebhookAlert)", "title": "[FIRING:1] WebhookAlert (UID_WebhookAlert)",
"state": "alerting", "state": "alerting",
"message": "\n**Firing**\nLabels:\n - alertname = WebhookAlert\n - __alert_rule_uid__ = UID_WebhookAlert\nAnnotations:\nSource: \n\n\n\n\n" "message": "\n**Firing**\nLabels:\n - alertname = WebhookAlert\n - __alert_rule_uid__ = UID_WebhookAlert\nAnnotations:\nSource: \n\n\n\n\n"
}`, }`,