Files
grafana/pkg/services/ngalert/api/tooling/definitions/alertmanager.go
T
Owen Diehl e37a780e14 Inhouse alerting api (#33129)
* init

* autogens AM route

* POST dashboards/db spec

* POST alert-notifications spec

* fix description

* re inits vendor, updates grafana to master

* go mod updates

* alerting routes

* renames to receivers

* prometheus endpoints

* align config endpoint with cortex, include templates

* Change grafana receiver type

* Update receivers.go

* rename struct to stop swagger thrashing

* add rules API

* index html

* standalone swagger ui html page

* Update README.md

* Expose GrafanaManagedAlert properties

* Some fixes

- /api/v1/rules/{Namespace} should return a map
- update ExtendedUpsertAlertDefinitionCommand properties

* am alerts routes

* rename prom swagger section for clarity, remove example endpoints

* Add missing json and yaml tags

* folder perms

* make folders POST again

* fix grafana receiver type

* rename fodler->namespace for perms

* make ruler json again

* PR fixes

* silences

* fix Ok -> Ack

* Add id to POST /api/v1/silences (#9)

Signed-off-by: Ganesh Vernekar <cs15btech11018@iith.ac.in>

* Add POST /api/v1/alerts (#10)

Signed-off-by: Ganesh Vernekar <cs15btech11018@iith.ac.in>

* fix silences

* Add testing endpoints

* removes grpc replace directives

* [wip] starts validation

* pkg cleanup

* go mod tidy

* ignores vendor dir

* Change response type for Cortex/Loki alerts

* receiver unmarshaling tests

* ability to split routes between AM & Grafana

* api marshaling & validation

* begins work on routing lib

* [hack] ignores embedded field in generation

* path specific datasource for alerting

* align endpoint names with cloud

* single route per Alerting config

* removes unused routing pkg

* regens spec

* adds datasource param to ruler/prom route paths

* Modifications for supporting migration

* Apply suggestions from code review

* hack for cleaning circular refs in swagger definition

* generates files

* minor fixes for prom endpoints

* decorate prom apis with required: true where applicable

* Revert "generates files"

This reverts commit ef7e975584.

* removes server autogen

* Update imported structs from ngalert

* Fix listing rules response

* Update github.com/prometheus/common dependency

* Update get silence response

* Update get silences response

* adds ruler validation & backend switching

* Fix GET /alertmanager/{DatasourceId}/config/api/v1/alerts response

* Distinct gettable and postable grafana receivers

* Remove permissions routes

* Latest JSON specs

* Fix testing routes

* inline yaml annotation on apirulenode

* yaml test & yamlv3 + comments

* Fix yaml annotations for embedded type

* Rename DatasourceId path parameter

* Implement Backend.String()

* backend zero value is a real backend

* exports DiscoveryBase

* Fix GO initialisms

* Silences: Use PostableSilence as the base struct for creating silences

* Use type alias instead of struct embedding

* More fixes to alertmanager silencing routes

* post and spec JSONs

* Split rule config to postable/gettable

* Fix empty POST /silences payload

Recreating the generated JSON specs fixes the issue
without further modifications

* better yaml unmarshaling for nested yaml docs in cortex-am configs

* regens spec

* re-adds config.receivers

* omitempty to align with prometheus API behavior

* Prefix routes with /api

* Update Alertmanager models

* Make adjustments to follow the Alertmanager API

* ruler: add for and annotations to grafana alert (#45)

* Modify testing API routes

* Fix grafana rule for field type

* Move PostableUserConfig validation to this library

* Fix PostableUserConfig YAML encoding/decoding

* Use common fields for grafana and lotex rules

* Add namespace id in GettableGrafanaRule

* Apply suggestions from code review

* fixup

* more changes

* Apply suggestions from code review

* aligns structure pre merge

* fix new imports & tests

* updates tooling readme

* goimports

* lint

* more linting!!

* revive lint

Co-authored-by: Sofia Papagiannaki <papagian@gmail.com>
Co-authored-by: Domas <domasx2@gmail.com>
Co-authored-by: Sofia Papagiannaki <papagian@users.noreply.github.com>
Co-authored-by: Ganesh Vernekar <15064823+codesome@users.noreply.github.com>
Co-authored-by: gotjosh <josue@grafana.com>
Co-authored-by: David Parrott <stomp.box.yo@gmail.com>
Co-authored-by: Kyle Brandt <kyle@grafana.com>
2021-04-19 14:26:04 -04:00

566 lines
16 KiB
Go

package api
import (
"encoding/json"
"fmt"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/models"
amv2 "github.com/prometheus/alertmanager/api/v2/models"
"github.com/prometheus/alertmanager/config"
"gopkg.in/yaml.v3"
)
// swagger:route POST /api/alertmanager/{Recipient}/config/api/v1/alerts alertmanager RoutePostAlertingConfig
//
// sets an Alerting config
//
// Responses:
// 201: Ack
// 400: ValidationError
// swagger:route GET /api/alertmanager/{Recipient}/config/api/v1/alerts alertmanager RouteGetAlertingConfig
//
// gets an Alerting config
//
// Responses:
// 200: GettableUserConfig
// 400: ValidationError
// swagger:route DELETE /api/alertmanager/{Recipient}/config/api/v1/alerts alertmanager RouteDeleteAlertingConfig
//
// deletes the Alerting config for a tenant
//
// Responses:
// 200: Ack
// 400: ValidationError
// swagger:route GET /api/alertmanager/{Recipient}/api/v2/alerts alertmanager RouteGetAMAlerts
//
// get alertmanager alerts
//
// Responses:
// 200: GettableAlerts
// 400: ValidationError
// swagger:route POST /api/alertmanager/{Recipient}/api/v2/alerts alertmanager RoutePostAMAlerts
//
// create alertmanager alerts
//
// Responses:
// 200: Ack
// 400: ValidationError
// swagger:route GET /api/alertmanager/{Recipient}/api/v2/alerts/groups alertmanager RouteGetAMAlertGroups
//
// get alertmanager alerts
//
// Responses:
// 200: AlertGroups
// 400: ValidationError
// swagger:route GET /api/alertmanager/{Recipient}/api/v2/silences alertmanager RouteGetSilences
//
// get silences
//
// Responses:
// 200: GettableSilences
// 400: ValidationError
// swagger:route POST /api/alertmanager/{Recipient}/api/v2/silences alertmanager RouteCreateSilence
//
// create silence
//
// Responses:
// 201: GettableSilence
// 400: ValidationError
// swagger:route GET /api/alertmanager/{Recipient}/api/v2/silence/{SilenceId} alertmanager RouteGetSilence
//
// get silence
//
// Responses:
// 200: GettableSilence
// 400: ValidationError
// swagger:route DELETE /api/alertmanager/{Recipient}/api/v2/silence/{SilenceId} alertmanager RouteDeleteSilence
//
// delete silence
//
// Responses:
// 200: Ack
// 400: ValidationError
// swagger:parameters RouteCreateSilence
type CreateSilenceParams struct {
// in:body
Silence PostableSilence
}
// swagger:parameters RouteGetSilence RouteDeleteSilence
type GetDeleteSilenceParams struct {
// in:path
SilenceId string
}
// swagger:parameters RouteGetSilences
type GetSilencesParams struct {
// in:query
Filter []string `json:"filter"`
}
// swagger:model
type PostableSilence = amv2.PostableSilence
// swagger:model
type GettableSilences = amv2.GettableSilences
// swagger:model
type GettableSilence = amv2.GettableSilence
// swagger:model
type GettableAlerts = amv2.GettableAlerts
// swagger:model
type GettableAlert = amv2.GettableAlert
// swagger:model
type AlertGroups = amv2.AlertGroups
// swagger:model
type AlertGroup = amv2.AlertGroup
// swagger:model
type Receiver = amv2.Receiver
// swagger:parameters RouteGetAMAlerts RouteGetAMAlertGroups
type AlertsParams struct {
// Show active alerts
// in: query
// required: false
// default: true
Active bool `json:"active"`
// Show silenced alerts
// in: query
// required: false
// default: true
Silenced bool `json:"silenced"`
// Show inhibited alerts
// in: query
// required: false
// default: true
Inhibited bool `json:"inhibited"`
// A list of matchers to filter alerts by
// in: query
// required: false
Matchers []string `json:"filter"`
// A regex matching receivers to filter alerts by
// in: query
// required: false
Receivers string `json:"receiver"`
}
// swagger:parameters RoutePostAMAlerts
type PostableAlerts struct {
// in:body
PostableAlerts []amv2.PostableAlert `yaml:"" json:""`
}
// swagger:parameters RoutePostAlertingConfig
type BodyAlertingConfig struct {
// in:body
Body PostableUserConfig
}
// alertmanager routes
// swagger:parameters RoutePostAlertingConfig RouteGetAlertingConfig RouteDeleteAlertingConfig RouteGetAMAlerts RoutePostAMAlerts RouteGetAMAlertGroups RouteGetSilences RouteCreateSilence RouteGetSilence RouteDeleteSilence RoutePostAlertingConfig
// ruler routes
// swagger:parameters RouteGetRulesConfig RoutePostNameRulesConfig RouteGetNamespaceRulesConfig RouteDeleteNamespaceRulesConfig RouteGetRulegGroupConfig RouteDeleteRuleGroupConfig
// prom routes
// swagger:parameters RouteGetRuleStatuses RouteGetAlertStatuses
// testing routes
// swagger:parameters RouteTestReceiverConfig RouteTestRuleConfig
type DatasourceReference struct {
// Recipient should be "grafana" for requests to be handled by grafana
// and the numeric datasource id for requests to be forwarded to a datasource
// in:path
Recipient string
}
// swagger:model
type PostableUserConfig struct {
TemplateFiles map[string]string `yaml:"template_files" json:"template_files"`
AlertmanagerConfig PostableApiAlertingConfig `yaml:"alertmanager_config" json:"alertmanager_config"`
}
func (c *PostableUserConfig) UnmarshalJSON(b []byte) error {
type plain PostableUserConfig
if err := json.Unmarshal(b, (*plain)(c)); err != nil {
return err
}
return c.validate()
}
func (c *PostableUserConfig) validate() error {
// Taken from https://github.com/prometheus/alertmanager/blob/master/config/config.go#L170-L191
// Check if we have a root route. We cannot check for it in the
// UnmarshalYAML method because it won't be called if the input is empty
// (e.g. the config file is empty or only contains whitespace).
if c.AlertmanagerConfig.Route == nil {
return fmt.Errorf("no route provided in config")
}
// Check if continue in root route.
if c.AlertmanagerConfig.Route.Continue {
return fmt.Errorf("cannot have continue in root route")
}
return nil
}
// MarshalYAML implements yaml.Marshaller.
func (c *PostableUserConfig) MarshalYAML() (interface{}, error) {
yml, err := yaml.Marshal(c.AlertmanagerConfig)
if err != nil {
return nil, err
}
// cortex/loki actually pass the AM config as a string.
cortexPostableUserConfig := struct {
TemplateFiles map[string]string `yaml:"template_files" json:"template_files"`
AlertmanagerConfig string `yaml:"alertmanager_config" json:"alertmanager_config"`
}{
TemplateFiles: c.TemplateFiles,
AlertmanagerConfig: string(yml),
}
return cortexPostableUserConfig, nil
}
func (c *PostableUserConfig) UnmarshalYAML(value *yaml.Node) error {
// cortex/loki actually pass the AM config as a string.
type cortexPostableUserConfig struct {
TemplateFiles map[string]string `yaml:"template_files" json:"template_files"`
AlertmanagerConfig string `yaml:"alertmanager_config" json:"alertmanager_config"`
}
var tmp cortexPostableUserConfig
if err := value.Decode(&tmp); err != nil {
return err
}
if err := yaml.Unmarshal([]byte(tmp.AlertmanagerConfig), &c.AlertmanagerConfig); err != nil {
return err
}
c.TemplateFiles = tmp.TemplateFiles
return nil
}
// swagger:model
type GettableUserConfig struct {
TemplateFiles map[string]string `yaml:"template_files" json:"template_files"`
AlertmanagerConfig GettableApiAlertingConfig `yaml:"alertmanager_config" json:"alertmanager_config"`
}
func (c *GettableUserConfig) UnmarshalYAML(value *yaml.Node) error {
// cortex/loki actually pass the AM config as a string.
type cortexGettableUserConfig struct {
TemplateFiles map[string]string `yaml:"template_files" json:"template_files"`
AlertmanagerConfig string `yaml:"alertmanager_config" json:"alertmanager_config"`
}
var tmp cortexGettableUserConfig
if err := value.Decode(&tmp); err != nil {
return err
}
if err := yaml.Unmarshal([]byte(tmp.AlertmanagerConfig), &c.AlertmanagerConfig); err != nil {
return err
}
c.TemplateFiles = tmp.TemplateFiles
return nil
}
type GettableApiAlertingConfig struct {
Config `yaml:",inline"`
// Override with our superset receiver type
Receivers []*GettableApiReceiver `yaml:"receivers,omitempty" json:"receivers,omitempty"`
}
func (c *GettableApiAlertingConfig) UnmarshalJSON(b []byte) error {
type plain GettableApiAlertingConfig
if err := json.Unmarshal(b, (*plain)(c)); err != nil {
return err
}
return c.validate()
}
// validate ensures that the two routing trees use the correct receiver types.
func (c *GettableApiAlertingConfig) validate() error {
receivers := make(map[string]struct{}, len(c.Receivers))
var hasGrafReceivers, hasAMReceivers bool
for _, r := range c.Receivers {
receivers[r.Name] = struct{}{}
switch r.Type() {
case GrafanaReceiverType:
hasGrafReceivers = true
case AlertmanagerReceiverType:
hasAMReceivers = true
}
}
if hasGrafReceivers && hasAMReceivers {
return fmt.Errorf("cannot mix Alertmanager & Grafana receiver types")
}
for _, receiver := range AllReceivers(c.Route) {
_, ok := receivers[receiver]
if !ok {
return fmt.Errorf("unexpected receiver (%s) is undefined", receiver)
}
}
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.
type Config struct {
Global *config.GlobalConfig `yaml:"global,omitempty" json:"global,omitempty"`
Route *config.Route `yaml:"route,omitempty" json:"route,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"`
}
type PostableApiAlertingConfig struct {
Config `yaml:",inline"`
// Override with our superset receiver type
Receivers []*PostableApiReceiver `yaml:"receivers,omitempty" json:"receivers,omitempty"`
}
func (c *PostableApiAlertingConfig) UnmarshalJSON(b []byte) error {
type plain PostableApiAlertingConfig
if err := json.Unmarshal(b, (*plain)(c)); err != nil {
return err
}
return c.validate()
}
// validate ensures that the two routing trees use the correct receiver types.
func (c *PostableApiAlertingConfig) validate() error {
receivers := make(map[string]struct{}, len(c.Receivers))
var hasGrafReceivers, hasAMReceivers bool
for _, r := range c.Receivers {
receivers[r.Name] = struct{}{}
switch r.Type() {
case GrafanaReceiverType:
hasGrafReceivers = true
case AlertmanagerReceiverType:
hasAMReceivers = true
}
}
if hasGrafReceivers && hasAMReceivers {
return fmt.Errorf("cannot mix Alertmanager & Grafana receiver types")
}
for _, receiver := range AllReceivers(c.Route) {
_, ok := receivers[receiver]
if !ok {
return fmt.Errorf("unexpected receiver (%s) is undefined", receiver)
}
}
return nil
}
// Type requires validate has been called and just checks the first receiver type
func (c *PostableApiAlertingConfig) Type() (backend Backend) {
for _, r := range c.Receivers {
switch r.Type() {
case GrafanaReceiverType:
return GrafanaBackend
case AlertmanagerReceiverType:
return AlertmanagerBackend
}
}
return
}
// AllReceivers will recursively walk a routing tree and return a list of all the
// referenced receiver names.
func AllReceivers(route *config.Route) (res []string) {
res = append(res, route.Receiver)
for _, subRoute := range route.Routes {
res = append(res, AllReceivers(subRoute)...)
}
return res
}
type GettableGrafanaReceiver dtos.AlertNotification
type PostableGrafanaReceiver models.CreateAlertNotificationCommand
type ReceiverType int
const (
GrafanaReceiverType ReceiverType = iota
AlertmanagerReceiverType
)
type GettableApiReceiver struct {
config.Receiver `yaml:",inline"`
GettableGrafanaReceivers `yaml:",inline"`
}
func (r *GettableApiReceiver) UnmarshalJSON(b []byte) error {
type plain GettableApiReceiver
if err := json.Unmarshal(b, (*plain)(r)); err != nil {
return err
}
hasGrafanaReceivers := len(r.GettableGrafanaReceivers.GrafanaManagedReceivers) > 0
if hasGrafanaReceivers {
if len(r.EmailConfigs) > 0 {
return fmt.Errorf("cannot have both Alertmanager EmailConfigs & Grafana receivers together")
}
if len(r.PagerdutyConfigs) > 0 {
return fmt.Errorf("cannot have both Alertmanager PagerdutyConfigs & Grafana receivers together")
}
if len(r.SlackConfigs) > 0 {
return fmt.Errorf("cannot have both Alertmanager SlackConfigs & Grafana receivers together")
}
if len(r.WebhookConfigs) > 0 {
return fmt.Errorf("cannot have both Alertmanager WebhookConfigs & Grafana receivers together")
}
if len(r.OpsGenieConfigs) > 0 {
return fmt.Errorf("cannot have both Alertmanager OpsGenieConfigs & Grafana receivers together")
}
if len(r.WechatConfigs) > 0 {
return fmt.Errorf("cannot have both Alertmanager WechatConfigs & Grafana receivers together")
}
if len(r.PushoverConfigs) > 0 {
return fmt.Errorf("cannot have both Alertmanager PushoverConfigs & Grafana receivers together")
}
if len(r.VictorOpsConfigs) > 0 {
return fmt.Errorf("cannot have both Alertmanager VictorOpsConfigs & Grafana receivers together")
}
}
return nil
}
func (r *GettableApiReceiver) Type() ReceiverType {
if len(r.GettableGrafanaReceivers.GrafanaManagedReceivers) > 0 {
return GrafanaReceiverType
}
return AlertmanagerReceiverType
}
type PostableApiReceiver struct {
config.Receiver `yaml:",inline"`
PostableGrafanaReceivers `yaml:",inline"`
}
func (r *PostableApiReceiver) UnmarshalYAML(unmarshal func(interface{}) error) error {
var grafanaReceivers PostableGrafanaReceivers
if err := unmarshal(&grafanaReceivers); err != nil {
return err
}
r.PostableGrafanaReceivers = grafanaReceivers
var cfg config.Receiver
if err := unmarshal(&cfg); err != nil {
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
}
func (r *PostableApiReceiver) UnmarshalJSON(b []byte) error {
type plain PostableApiReceiver
if err := json.Unmarshal(b, (*plain)(r)); err != nil {
return err
}
hasGrafanaReceivers := len(r.PostableGrafanaReceivers.GrafanaManagedReceivers) > 0
if hasGrafanaReceivers {
if len(r.EmailConfigs) > 0 {
return fmt.Errorf("cannot have both Alertmanager EmailConfigs & Grafana receivers together")
}
if len(r.PagerdutyConfigs) > 0 {
return fmt.Errorf("cannot have both Alertmanager PagerdutyConfigs & Grafana receivers together")
}
if len(r.SlackConfigs) > 0 {
return fmt.Errorf("cannot have both Alertmanager SlackConfigs & Grafana receivers together")
}
if len(r.WebhookConfigs) > 0 {
return fmt.Errorf("cannot have both Alertmanager WebhookConfigs & Grafana receivers together")
}
if len(r.OpsGenieConfigs) > 0 {
return fmt.Errorf("cannot have both Alertmanager OpsGenieConfigs & Grafana receivers together")
}
if len(r.WechatConfigs) > 0 {
return fmt.Errorf("cannot have both Alertmanager WechatConfigs & Grafana receivers together")
}
if len(r.PushoverConfigs) > 0 {
return fmt.Errorf("cannot have both Alertmanager PushoverConfigs & Grafana receivers together")
}
if len(r.VictorOpsConfigs) > 0 {
return fmt.Errorf("cannot have both Alertmanager VictorOpsConfigs & Grafana receivers together")
}
}
return nil
}
func (r *PostableApiReceiver) Type() ReceiverType {
if len(r.PostableGrafanaReceivers.GrafanaManagedReceivers) > 0 {
return GrafanaReceiverType
}
return AlertmanagerReceiverType
}
type GettableGrafanaReceivers struct {
GrafanaManagedReceivers []*GettableGrafanaReceiver `yaml:"grafana_managed_receiver_configs,omitempty" json:"grafana_managed_receiver_configs,omitempty"`
}
type PostableGrafanaReceivers struct {
GrafanaManagedReceivers []*PostableGrafanaReceiver `yaml:"grafana_managed_receiver_configs,omitempty" json:"grafana_managed_receiver_configs,omitempty"`
}