diff --git a/go.mod b/go.mod index ce3ec1ab80e..72afe9547c7 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ replace k8s.io/client-go => k8s.io/client-go v0.18.8 require ( cloud.google.com/go/storage v1.14.0 cuelang.org/go v0.3.2 - github.com/Azure/azure-sdk-for-go/sdk/azcore v0.16.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v0.16.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.8.0 github.com/BurntSushi/toml v0.3.1 github.com/Masterminds/semver v1.5.0 diff --git a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go index f8fe91444ac..75da775e78a 100644 --- a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go +++ b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go @@ -6,6 +6,7 @@ import ( "fmt" "reflect" + "github.com/pkg/errors" amv2 "github.com/prometheus/alertmanager/api/v2/models" "github.com/prometheus/alertmanager/config" "gopkg.in/yaml.v3" @@ -484,10 +485,26 @@ func (c *Config) UnmarshalJSON(b []byte) error { } } - if c.Route != nil { - if err := c.Route.UnmarshalYAML(noopUnmarshal); err != nil { - return err - } + if c.Route == nil { + return fmt.Errorf("no routes provided") + } + + // Route is a recursive structure that includes validation in the yaml unmarshaler. + // Therefore, we'll redirect json -> yaml to utilize these. + b, err := yaml.Marshal(c.Route) + if err != nil { + return errors.Wrap(err, "marshaling route to yaml for validation") + } + err = yaml.Unmarshal(b, c.Route) + if err != nil { + return errors.Wrap(err, "unmarshaling route for validations") + } + + if len(c.Route.Receiver) == 0 { + return fmt.Errorf("root route must specify a default receiver") + } + if len(c.Route.Match) > 0 || len(c.Route.MatchRE) > 0 { + return fmt.Errorf("root route must not have any matchers") } for _, r := range c.InhibitRules { diff --git a/pkg/services/ngalert/api/tooling/definitions/alertmanager_test.go b/pkg/services/ngalert/api/tooling/definitions/alertmanager_test.go index fcce3364d43..a246e08a7fc 100644 --- a/pkg/services/ngalert/api/tooling/definitions/alertmanager_test.go +++ b/pkg/services/ngalert/api/tooling/definitions/alertmanager_test.go @@ -243,6 +243,101 @@ func Test_ApiAlertingConfig_Marshaling(t *testing.T) { }, err: true, }, + { + desc: "failure graf no route", + input: PostableApiAlertingConfig{ + Receivers: []*PostableApiReceiver{ + { + Receiver: config.Receiver{ + Name: "graf", + }, + PostableGrafanaReceivers: PostableGrafanaReceivers{ + GrafanaManagedReceivers: []*PostableGrafanaReceiver{{}}, + }, + }, + }, + }, + err: true, + }, + { + desc: "failure graf no default receiver", + input: PostableApiAlertingConfig{ + Config: Config{ + Route: &config.Route{ + Routes: []*config.Route{ + { + Receiver: "graf", + }, + }, + }, + }, + Receivers: []*PostableApiReceiver{ + { + Receiver: config.Receiver{ + Name: "graf", + }, + PostableGrafanaReceivers: PostableGrafanaReceivers{ + GrafanaManagedReceivers: []*PostableGrafanaReceiver{{}}, + }, + }, + }, + }, + err: true, + }, + { + desc: "failure graf root route with matchers", + input: PostableApiAlertingConfig{ + Config: Config{ + Route: &config.Route{ + Receiver: "graf", + Routes: []*config.Route{ + { + Receiver: "graf", + }, + }, + Match: map[string]string{"foo": "bar"}, + }, + }, + Receivers: []*PostableApiReceiver{ + { + Receiver: config.Receiver{ + Name: "graf", + }, + PostableGrafanaReceivers: PostableGrafanaReceivers{ + GrafanaManagedReceivers: []*PostableGrafanaReceiver{{}}, + }, + }, + }, + }, + err: true, + }, + { + desc: "failure graf nested route duplicate group by labels", + input: PostableApiAlertingConfig{ + Config: Config{ + Route: &config.Route{ + Receiver: "graf", + Routes: []*config.Route{ + { + Receiver: "graf", + GroupByStr: []string{"foo", "bar", "foo"}, + }, + }, + }, + }, + Receivers: []*PostableApiReceiver{ + { + Receiver: config.Receiver{ + Name: "graf", + }, + PostableGrafanaReceivers: PostableGrafanaReceivers{ + GrafanaManagedReceivers: []*PostableGrafanaReceiver{{}}, + }, + }, + }, + }, + err: true, + }, } { t.Run(tc.desc, func(t *testing.T) { encoded, err := json.Marshal(tc.input)