mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting/allow empty receiver (#33962)
* simplifies yaml unmarshaling: PostableApiReceiver * allow empty receiver type * allows name only receivers (blackhole) * better receiver type parsing * linting
This commit is contained in:
parent
7a55a6385c
commit
3b06f52bab
@ -117,22 +117,16 @@ func (am *ForkedAMSvc) RoutePostAlertingConfig(ctx *models.ReqContext, body apim
|
|||||||
return response.Error(400, err.Error(), nil)
|
return response.Error(400, err.Error(), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
backendType, err := backendType(ctx, am.DatasourceCache)
|
b, err := backendType(ctx, am.DatasourceCache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(400, err.Error(), nil)
|
return response.Error(400, err.Error(), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
payloadType := body.AlertmanagerConfig.Type()
|
if err := body.AlertmanagerConfig.ReceiverType().MatchesBackend(b); err != nil {
|
||||||
|
|
||||||
if backendType != payloadType {
|
|
||||||
return response.Error(
|
return response.Error(
|
||||||
400,
|
400,
|
||||||
fmt.Sprintf(
|
"bad match",
|
||||||
"unexpected backend type (%v) vs payload type (%v)",
|
err,
|
||||||
backendType,
|
|
||||||
payloadType,
|
|
||||||
),
|
|
||||||
nil,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/api/dtos"
|
"github.com/grafana/grafana/pkg/api/dtos"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
@ -381,6 +382,8 @@ func (c *GettableApiAlertingConfig) validate() error {
|
|||||||
hasGrafReceivers = true
|
hasGrafReceivers = true
|
||||||
case AlertmanagerReceiverType:
|
case AlertmanagerReceiverType:
|
||||||
hasAMReceivers = true
|
hasAMReceivers = true
|
||||||
|
default:
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -398,19 +401,6 @@ func (c *GettableApiAlertingConfig) validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type requires validate has been called and just checks the first receiver type
|
|
||||||
func (c *GettableApiAlertingConfig) Type() (backend Backend) {
|
|
||||||
for _, r := range c.Receivers {
|
|
||||||
switch r.Type() {
|
|
||||||
case GrafanaReceiverType:
|
|
||||||
return GrafanaBackend
|
|
||||||
case AlertmanagerReceiverType:
|
|
||||||
return AlertmanagerBackend
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config is the top-level configuration for Alertmanager's config files.
|
// Config is the top-level configuration for Alertmanager's config files.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Global *config.GlobalConfig `yaml:"global,omitempty" json:"global,omitempty"`
|
Global *config.GlobalConfig `yaml:"global,omitempty" json:"global,omitempty"`
|
||||||
@ -448,6 +438,8 @@ func (c *PostableApiAlertingConfig) validate() error {
|
|||||||
hasGrafReceivers = true
|
hasGrafReceivers = true
|
||||||
case AlertmanagerReceiverType:
|
case AlertmanagerReceiverType:
|
||||||
hasAMReceivers = true
|
hasAMReceivers = true
|
||||||
|
default:
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -466,22 +458,26 @@ func (c *PostableApiAlertingConfig) validate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Type requires validate has been called and just checks the first receiver type
|
// Type requires validate has been called and just checks the first receiver type
|
||||||
func (c *PostableApiAlertingConfig) Type() (backend Backend) {
|
func (c *PostableApiAlertingConfig) ReceiverType() ReceiverType {
|
||||||
for _, r := range c.Receivers {
|
for _, r := range c.Receivers {
|
||||||
switch r.Type() {
|
switch r.Type() {
|
||||||
case GrafanaReceiverType:
|
case GrafanaReceiverType:
|
||||||
return GrafanaBackend
|
return GrafanaReceiverType
|
||||||
case AlertmanagerReceiverType:
|
case AlertmanagerReceiverType:
|
||||||
return AlertmanagerBackend
|
return AlertmanagerReceiverType
|
||||||
|
default:
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return EmptyReceiverType
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllReceivers will recursively walk a routing tree and return a list of all the
|
// AllReceivers will recursively walk a routing tree and return a list of all the
|
||||||
// referenced receiver names.
|
// referenced receiver names.
|
||||||
func AllReceivers(route *config.Route) (res []string) {
|
func AllReceivers(route *config.Route) (res []string) {
|
||||||
res = append(res, route.Receiver)
|
if route.Receiver != "" {
|
||||||
|
res = append(res, route.Receiver)
|
||||||
|
}
|
||||||
for _, subRoute := range route.Routes {
|
for _, subRoute := range route.Routes {
|
||||||
res = append(res, AllReceivers(subRoute)...)
|
res = append(res, AllReceivers(subRoute)...)
|
||||||
}
|
}
|
||||||
@ -494,10 +490,52 @@ type PostableGrafanaReceiver models.CreateAlertNotificationCommand
|
|||||||
type ReceiverType int
|
type ReceiverType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
GrafanaReceiverType ReceiverType = iota
|
GrafanaReceiverType ReceiverType = 1 << iota
|
||||||
AlertmanagerReceiverType
|
AlertmanagerReceiverType
|
||||||
|
EmptyReceiverType = GrafanaReceiverType | AlertmanagerReceiverType
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (r ReceiverType) String() string {
|
||||||
|
switch r {
|
||||||
|
case GrafanaReceiverType:
|
||||||
|
return "grafana"
|
||||||
|
case AlertmanagerReceiverType:
|
||||||
|
return "alertmanager"
|
||||||
|
case EmptyReceiverType:
|
||||||
|
return "empty"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can determines whether a receiver type can implement another receiver type.
|
||||||
|
// This is useful as receivers with just names but no contact points
|
||||||
|
// are valid in all backends.
|
||||||
|
func (r ReceiverType) Can(other ReceiverType) bool { return r&other != 0 }
|
||||||
|
|
||||||
|
// MatchesBackend determines if a config payload can be sent to a particular backend type
|
||||||
|
func (r ReceiverType) MatchesBackend(backend Backend) error {
|
||||||
|
msg := func(backend Backend, receiver ReceiverType) error {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"unexpected backend type (%s) for receiver type (%s)",
|
||||||
|
backend.String(),
|
||||||
|
receiver.String(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
var ok bool
|
||||||
|
switch backend {
|
||||||
|
case GrafanaBackend:
|
||||||
|
ok = r.Can(GrafanaReceiverType)
|
||||||
|
case AlertmanagerBackend:
|
||||||
|
ok = r.Can(AlertmanagerReceiverType)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return msg(backend, r)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type GettableApiReceiver struct {
|
type GettableApiReceiver struct {
|
||||||
config.Receiver `yaml:",inline"`
|
config.Receiver `yaml:",inline"`
|
||||||
GettableGrafanaReceivers `yaml:",inline"`
|
GettableGrafanaReceivers `yaml:",inline"`
|
||||||
@ -554,25 +592,14 @@ type PostableApiReceiver struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *PostableApiReceiver) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
func (r *PostableApiReceiver) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
var grafanaReceivers PostableGrafanaReceivers
|
if err := unmarshal(&r.PostableGrafanaReceivers); err != nil {
|
||||||
if err := unmarshal(&grafanaReceivers); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r.PostableGrafanaReceivers = grafanaReceivers
|
|
||||||
|
|
||||||
var cfg config.Receiver
|
if err := unmarshal(&r.Receiver); err != nil {
|
||||||
if err := unmarshal(&cfg); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r.Name = cfg.Name
|
|
||||||
r.EmailConfigs = cfg.EmailConfigs
|
|
||||||
r.PagerdutyConfigs = cfg.PagerdutyConfigs
|
|
||||||
r.SlackConfigs = cfg.SlackConfigs
|
|
||||||
r.WebhookConfigs = cfg.WebhookConfigs
|
|
||||||
r.OpsGenieConfigs = cfg.OpsGenieConfigs
|
|
||||||
r.WechatConfigs = cfg.WechatConfigs
|
|
||||||
r.PushoverConfigs = cfg.PushoverConfigs
|
|
||||||
r.VictorOpsConfigs = cfg.VictorOpsConfigs
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -617,6 +644,13 @@ func (r *PostableApiReceiver) Type() ReceiverType {
|
|||||||
if len(r.PostableGrafanaReceivers.GrafanaManagedReceivers) > 0 {
|
if len(r.PostableGrafanaReceivers.GrafanaManagedReceivers) > 0 {
|
||||||
return GrafanaReceiverType
|
return GrafanaReceiverType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cpy := r.Receiver
|
||||||
|
cpy.Name = ""
|
||||||
|
if reflect.ValueOf(cpy).IsZero() {
|
||||||
|
return EmptyReceiverType
|
||||||
|
}
|
||||||
|
|
||||||
return AlertmanagerReceiverType
|
return AlertmanagerReceiverType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,6 +69,50 @@ func Test_ApiReceiver_Marshaling(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_APIReceiverType(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
desc string
|
||||||
|
input PostableApiReceiver
|
||||||
|
expected ReceiverType
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "empty",
|
||||||
|
input: PostableApiReceiver{
|
||||||
|
Receiver: config.Receiver{
|
||||||
|
Name: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: EmptyReceiverType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "am",
|
||||||
|
input: PostableApiReceiver{
|
||||||
|
Receiver: config.Receiver{
|
||||||
|
Name: "foo",
|
||||||
|
EmailConfigs: []*config.EmailConfig{{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: AlertmanagerReceiverType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "graf",
|
||||||
|
input: PostableApiReceiver{
|
||||||
|
Receiver: config.Receiver{
|
||||||
|
Name: "foo",
|
||||||
|
},
|
||||||
|
PostableGrafanaReceivers: PostableGrafanaReceivers{
|
||||||
|
GrafanaManagedReceivers: []*PostableGrafanaReceiver{{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: GrafanaReceiverType,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
require.Equal(t, tc.expected, tc.input.Type())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Test_AllReceivers(t *testing.T) {
|
func Test_AllReceivers(t *testing.T) {
|
||||||
input := &config.Route{
|
input := &config.Route{
|
||||||
Receiver: "foo",
|
Receiver: "foo",
|
||||||
@ -88,6 +132,10 @@ 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))
|
||||||
|
|
||||||
|
// test empty
|
||||||
|
var empty []string
|
||||||
|
require.Equal(t, empty, AllReceivers(&config.Route{}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
|
func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
|
||||||
@ -405,3 +453,113 @@ func Test_GettableUserConfigRoundtrip(t *testing.T) {
|
|||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.Equal(t, string(yamlEncoded), string(out))
|
require.Equal(t, string(yamlEncoded), string(out))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_ReceiverCompatibility(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
desc string
|
||||||
|
a, b ReceiverType
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "grafana=grafana",
|
||||||
|
a: GrafanaReceiverType,
|
||||||
|
b: GrafanaReceiverType,
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "am=am",
|
||||||
|
a: AlertmanagerReceiverType,
|
||||||
|
b: AlertmanagerReceiverType,
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "empty=grafana",
|
||||||
|
a: EmptyReceiverType,
|
||||||
|
b: AlertmanagerReceiverType,
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "empty=am",
|
||||||
|
a: EmptyReceiverType,
|
||||||
|
b: AlertmanagerReceiverType,
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "empty=empty",
|
||||||
|
a: EmptyReceiverType,
|
||||||
|
b: EmptyReceiverType,
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "graf!=am",
|
||||||
|
a: GrafanaReceiverType,
|
||||||
|
b: AlertmanagerReceiverType,
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "am!=graf",
|
||||||
|
a: AlertmanagerReceiverType,
|
||||||
|
b: GrafanaReceiverType,
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
require.Equal(t, tc.expected, tc.a.Can(tc.b))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_ReceiverMatchesBackend(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
desc string
|
||||||
|
rec ReceiverType
|
||||||
|
b Backend
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "graf=graf",
|
||||||
|
rec: GrafanaReceiverType,
|
||||||
|
b: GrafanaBackend,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "empty=graf",
|
||||||
|
rec: EmptyReceiverType,
|
||||||
|
b: GrafanaBackend,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "am=am",
|
||||||
|
rec: AlertmanagerReceiverType,
|
||||||
|
b: AlertmanagerBackend,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "empty=am",
|
||||||
|
rec: EmptyReceiverType,
|
||||||
|
b: AlertmanagerBackend,
|
||||||
|
err: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "graf!=am",
|
||||||
|
rec: GrafanaReceiverType,
|
||||||
|
b: AlertmanagerBackend,
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "am!=ruler",
|
||||||
|
rec: GrafanaReceiverType,
|
||||||
|
b: LoTexRulerBackend,
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
err := tc.rec.MatchesBackend(tc.b)
|
||||||
|
if tc.err {
|
||||||
|
require.NotNil(t, err)
|
||||||
|
} else {
|
||||||
|
require.Nil(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user