grafana/pkg/services/ngalert/api/compat.go
Yuri Tseretyan 1eebd2a4de
Alerting: Support for simplified notification settings in rule API (#81011)
* Add notification settings to storage\domain and API models. Settings are a slice to workaround XORM mapping
* Support validation of notification settings when rules are updated

* Implement route generator for Alertmanager configuration. That fetches all notification settings.
* Update multi-tenant Alertmanager to run the generator before applying the configuration.

* Add notification settings labels to state calculation
* update the Multi-tenant Alertmanager to provide validation for notification settings

* update GET API so only admins can see auto-gen
2024-02-15 09:45:10 -05:00

437 lines
16 KiB
Go

package api
import (
"encoding/json"
"time"
jsoniter "github.com/json-iterator/go"
"github.com/prometheus/common/model"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/util"
)
// AlertRuleFromProvisionedAlertRule converts definitions.ProvisionedAlertRule to models.AlertRule
func AlertRuleFromProvisionedAlertRule(a definitions.ProvisionedAlertRule) (models.AlertRule, error) {
return models.AlertRule{
ID: a.ID,
UID: a.UID,
OrgID: a.OrgID,
NamespaceUID: a.FolderUID,
RuleGroup: a.RuleGroup,
Title: a.Title,
Condition: a.Condition,
Data: AlertQueriesFromApiAlertQueries(a.Data),
Updated: a.Updated,
NoDataState: models.NoDataState(a.NoDataState), // TODO there must be a validation
ExecErrState: models.ExecutionErrorState(a.ExecErrState), // TODO there must be a validation
For: time.Duration(a.For),
Annotations: a.Annotations,
Labels: a.Labels,
IsPaused: a.IsPaused,
NotificationSettings: NotificationSettingsFromAlertRuleNotificationSettings(a.NotificationSettings),
}, nil
}
// ProvisionedAlertRuleFromAlertRule converts models.AlertRule to definitions.ProvisionedAlertRule and sets provided provenance status
func ProvisionedAlertRuleFromAlertRule(rule models.AlertRule, provenance models.Provenance) definitions.ProvisionedAlertRule {
return definitions.ProvisionedAlertRule{
ID: rule.ID,
UID: rule.UID,
OrgID: rule.OrgID,
FolderUID: rule.NamespaceUID,
RuleGroup: rule.RuleGroup,
Title: rule.Title,
For: model.Duration(rule.For),
Condition: rule.Condition,
Data: ApiAlertQueriesFromAlertQueries(rule.Data),
Updated: rule.Updated,
NoDataState: definitions.NoDataState(rule.NoDataState), // TODO there may be a validation
ExecErrState: definitions.ExecutionErrorState(rule.ExecErrState), // TODO there may be a validation
Annotations: rule.Annotations,
Labels: rule.Labels,
Provenance: definitions.Provenance(provenance), // TODO validate enum conversion?
IsPaused: rule.IsPaused,
NotificationSettings: AlertRuleNotificationSettingsFromNotificationSettings(rule.NotificationSettings),
}
}
// ProvisionedAlertRuleFromAlertRules converts a collection of models.AlertRule to definitions.ProvisionedAlertRules with provenance status models.ProvenanceNone
func ProvisionedAlertRuleFromAlertRules(rules []*models.AlertRule, provenances map[string]models.Provenance) definitions.ProvisionedAlertRules {
result := make([]definitions.ProvisionedAlertRule, 0, len(rules))
for _, r := range rules {
result = append(result, ProvisionedAlertRuleFromAlertRule(*r, provenances[r.UID]))
}
return result
}
// AlertQueriesFromApiAlertQueries converts a collection of definitions.AlertQuery to collection of models.AlertQuery
func AlertQueriesFromApiAlertQueries(queries []definitions.AlertQuery) []models.AlertQuery {
result := make([]models.AlertQuery, 0, len(queries))
for _, q := range queries {
result = append(result, models.AlertQuery{
RefID: q.RefID,
QueryType: q.QueryType,
RelativeTimeRange: models.RelativeTimeRange{
From: models.Duration(q.RelativeTimeRange.From),
To: models.Duration(q.RelativeTimeRange.To),
},
DatasourceUID: q.DatasourceUID,
Model: q.Model,
})
}
return result
}
// ApiAlertQueriesFromAlertQueries converts a collection of models.AlertQuery to collection of definitions.AlertQuery
func ApiAlertQueriesFromAlertQueries(queries []models.AlertQuery) []definitions.AlertQuery {
result := make([]definitions.AlertQuery, 0, len(queries))
for _, q := range queries {
result = append(result, definitions.AlertQuery{
RefID: q.RefID,
QueryType: q.QueryType,
RelativeTimeRange: definitions.RelativeTimeRange{
From: definitions.Duration(q.RelativeTimeRange.From),
To: definitions.Duration(q.RelativeTimeRange.To),
},
DatasourceUID: q.DatasourceUID,
Model: q.Model,
})
}
return result
}
func AlertRuleGroupFromApiAlertRuleGroup(a definitions.AlertRuleGroup) (models.AlertRuleGroup, error) {
ruleGroup := models.AlertRuleGroup{
Title: a.Title,
FolderUID: a.FolderUID,
Interval: a.Interval,
}
for i := range a.Rules {
converted, err := AlertRuleFromProvisionedAlertRule(a.Rules[i])
if err != nil {
return models.AlertRuleGroup{}, err
}
ruleGroup.Rules = append(ruleGroup.Rules, converted)
}
return ruleGroup, nil
}
func ApiAlertRuleGroupFromAlertRuleGroup(d models.AlertRuleGroup) definitions.AlertRuleGroup {
rules := make([]definitions.ProvisionedAlertRule, 0, len(d.Rules))
for i := range d.Rules {
rules = append(rules, ProvisionedAlertRuleFromAlertRule(d.Rules[i], d.Provenance))
}
return definitions.AlertRuleGroup{
Title: d.Title,
FolderUID: d.FolderUID,
Interval: d.Interval,
Rules: rules,
}
}
// AlertingFileExportFromAlertRuleGroupWithFolderTitle creates an definitions.AlertingFileExport DTO from []models.AlertRuleGroupWithFolderTitle.
func AlertingFileExportFromAlertRuleGroupWithFolderTitle(groups []models.AlertRuleGroupWithFolderTitle) (definitions.AlertingFileExport, error) {
f := definitions.AlertingFileExport{APIVersion: 1}
for _, group := range groups {
export, err := AlertRuleGroupExportFromAlertRuleGroupWithFolderTitle(group)
if err != nil {
return definitions.AlertingFileExport{}, err
}
f.Groups = append(f.Groups, export)
}
return f, nil
}
// AlertRuleGroupExportFromAlertRuleGroupWithFolderTitle creates a definitions.AlertRuleGroupExport DTO from models.AlertRuleGroup.
func AlertRuleGroupExportFromAlertRuleGroupWithFolderTitle(d models.AlertRuleGroupWithFolderTitle) (definitions.AlertRuleGroupExport, error) {
rules := make([]definitions.AlertRuleExport, 0, len(d.Rules))
for i := range d.Rules {
alert, err := AlertRuleExportFromAlertRule(d.Rules[i])
if err != nil {
return definitions.AlertRuleGroupExport{}, err
}
rules = append(rules, alert)
}
return definitions.AlertRuleGroupExport{
OrgID: d.OrgID,
Name: d.Title,
Folder: d.FolderTitle,
FolderUID: d.FolderUID,
Interval: model.Duration(time.Duration(d.Interval) * time.Second),
IntervalSeconds: d.Interval,
Rules: rules,
}, nil
}
// AlertRuleExportFromAlertRule creates a definitions.AlertRuleExport DTO from models.AlertRule.
func AlertRuleExportFromAlertRule(rule models.AlertRule) (definitions.AlertRuleExport, error) {
data := make([]definitions.AlertQueryExport, 0, len(rule.Data))
for i := range rule.Data {
query, err := AlertQueryExportFromAlertQuery(rule.Data[i])
if err != nil {
return definitions.AlertRuleExport{}, err
}
data = append(data, query)
}
result := definitions.AlertRuleExport{
UID: rule.UID,
Title: rule.Title,
For: model.Duration(rule.For),
Condition: rule.Condition,
Data: data,
DashboardUID: rule.DashboardUID,
PanelID: rule.PanelID,
NoDataState: definitions.NoDataState(rule.NoDataState),
ExecErrState: definitions.ExecutionErrorState(rule.ExecErrState),
IsPaused: rule.IsPaused,
NotificationSettings: AlertRuleNotificationSettingsExportFromNotificationSettings(rule.NotificationSettings),
}
if rule.For.Seconds() > 0 {
result.ForString = util.Pointer(model.Duration(rule.For).String())
}
if rule.Annotations != nil {
result.Annotations = &rule.Annotations
}
if rule.Labels != nil {
result.Labels = &rule.Labels
}
return result, nil
}
// AlertQueryExportFromAlertQuery creates a definitions.AlertQueryExport DTO from models.AlertQuery.
func AlertQueryExportFromAlertQuery(query models.AlertQuery) (definitions.AlertQueryExport, error) {
// We unmarshal the json.RawMessage model into a map in order to facilitate yaml marshalling.
var mdl map[string]any
err := json.Unmarshal(query.Model, &mdl)
if err != nil {
return definitions.AlertQueryExport{}, err
}
var queryType *string
if query.QueryType != "" {
queryType = &query.QueryType
}
return definitions.AlertQueryExport{
RefID: query.RefID,
QueryType: queryType,
RelativeTimeRange: definitions.RelativeTimeRangeExport{
FromSeconds: int64(time.Duration(query.RelativeTimeRange.From).Seconds()),
ToSeconds: int64(time.Duration(query.RelativeTimeRange.To).Seconds()),
},
DatasourceUID: query.DatasourceUID,
Model: mdl,
ModelString: string(query.Model),
}, nil
}
// AlertingFileExportFromEmbeddedContactPoints creates a definitions.AlertingFileExport DTO from []definitions.EmbeddedContactPoint.
func AlertingFileExportFromEmbeddedContactPoints(orgID int64, ecps []definitions.EmbeddedContactPoint) (definitions.AlertingFileExport, error) {
f := definitions.AlertingFileExport{APIVersion: 1}
cache := make(map[string]*definitions.ContactPointExport)
contactPoints := make([]*definitions.ContactPointExport, 0)
for _, ecp := range ecps {
c, ok := cache[ecp.Name]
if !ok {
c = &definitions.ContactPointExport{
OrgID: orgID,
Name: ecp.Name,
Receivers: make([]definitions.ReceiverExport, 0),
}
cache[ecp.Name] = c
contactPoints = append(contactPoints, c)
}
recv, err := ReceiverExportFromEmbeddedContactPoint(ecp)
if err != nil {
return definitions.AlertingFileExport{}, err
}
c.Receivers = append(c.Receivers, recv)
}
for _, c := range contactPoints {
f.ContactPoints = append(f.ContactPoints, *c)
}
return f, nil
}
// ReceiverExportFromEmbeddedContactPoint creates a definitions.ReceiverExport DTO from definitions.EmbeddedContactPoint.
func ReceiverExportFromEmbeddedContactPoint(contact definitions.EmbeddedContactPoint) (definitions.ReceiverExport, error) {
raw, err := contact.Settings.MarshalJSON()
if err != nil {
return definitions.ReceiverExport{}, err
}
return definitions.ReceiverExport{
UID: contact.UID,
Type: contact.Type,
Settings: raw,
DisableResolveMessage: contact.DisableResolveMessage,
}, nil
}
// AlertingFileExportFromRoute creates a definitions.AlertingFileExport DTO from definitions.Route.
func AlertingFileExportFromRoute(orgID int64, route definitions.Route) (definitions.AlertingFileExport, error) {
f := definitions.AlertingFileExport{
APIVersion: 1,
Policies: []definitions.NotificationPolicyExport{{
OrgID: orgID,
RouteExport: RouteExportFromRoute(&route),
}},
}
return f, nil
}
// RouteExportFromRoute creates a definitions.RouteExport DTO from definitions.Route.
func RouteExportFromRoute(route *definitions.Route) *definitions.RouteExport {
toStringIfNotNil := func(d *model.Duration) *string {
if d == nil {
return nil
}
s := d.String()
return &s
}
matchers := make([]*definitions.MatcherExport, 0, len(route.ObjectMatchers))
for _, matcher := range route.ObjectMatchers {
matchers = append(matchers, &definitions.MatcherExport{
Label: matcher.Name,
Match: matcher.Type.String(),
Value: matcher.Value,
})
}
export := definitions.RouteExport{
Receiver: route.Receiver,
GroupByStr: NilIfEmpty(util.Pointer(route.GroupByStr)),
Match: route.Match,
MatchRE: route.MatchRE,
Matchers: route.Matchers,
ObjectMatchers: route.ObjectMatchers,
ObjectMatchersSlice: matchers,
MuteTimeIntervals: NilIfEmpty(util.Pointer(route.MuteTimeIntervals)),
Continue: OmitDefault(util.Pointer(route.Continue)),
GroupWait: toStringIfNotNil(route.GroupWait),
GroupInterval: toStringIfNotNil(route.GroupInterval),
RepeatInterval: toStringIfNotNil(route.RepeatInterval),
}
if len(route.Routes) > 0 {
export.Routes = make([]*definitions.RouteExport, 0, len(route.Routes))
for _, r := range route.Routes {
export.Routes = append(export.Routes, RouteExportFromRoute(r))
}
}
return &export
}
// OmitDefault returns nil if the value is the default.
func OmitDefault[T comparable](v *T) *T {
var def T
if v == nil {
return v
}
if *v == def {
return nil
}
return v
}
// NilIfEmpty returns nil if pointer to slice points to the empty slice.
func NilIfEmpty[T any](v *[]T) *[]T {
if v == nil || len(*v) == 0 {
return nil
}
return v
}
func AlertingFileExportFromMuteTimings(orgID int64, m []definitions.MuteTimeInterval) definitions.AlertingFileExport {
f := definitions.AlertingFileExport{
APIVersion: 1,
MuteTimings: make([]definitions.MuteTimeIntervalExport, 0, len(m)),
}
for _, mi := range m {
f.MuteTimings = append(f.MuteTimings, MuteTimeIntervalExportFromMuteTiming(orgID, mi))
}
return f
}
func MuteTimeIntervalExportFromMuteTiming(orgID int64, m definitions.MuteTimeInterval) definitions.MuteTimeIntervalExport {
return definitions.MuteTimeIntervalExport{
OrgID: orgID,
MuteTimeInterval: m.MuteTimeInterval,
}
}
// Converts definitions.MuteTimeIntervalExport to definitions.MuteTimeIntervalExportHcl using JSON marshalling. Returns error if structure could not be marshalled\unmarshalled
func MuteTimingIntervalToMuteTimeIntervalHclExport(m definitions.MuteTimeIntervalExport) (definitions.MuteTimeIntervalExportHcl, error) {
result := definitions.MuteTimeIntervalExportHcl{}
j := jsoniter.ConfigCompatibleWithStandardLibrary
mdata, err := j.Marshal(m)
if err != nil {
return result, err
}
err = j.Unmarshal(mdata, &result)
return result, err
}
// AlertRuleNotificationSettingsFromNotificationSettings converts []models.NotificationSettings to definitions.AlertRuleNotificationSettings
func AlertRuleNotificationSettingsFromNotificationSettings(ns []models.NotificationSettings) *definitions.AlertRuleNotificationSettings {
if len(ns) == 0 {
return nil
}
m := ns[0]
return &definitions.AlertRuleNotificationSettings{
Receiver: m.Receiver,
GroupBy: m.GroupBy,
GroupWait: m.GroupWait,
GroupInterval: m.GroupInterval,
RepeatInterval: m.RepeatInterval,
MuteTimeIntervals: m.MuteTimeIntervals,
}
}
// AlertRuleNotificationSettingsFromNotificationSettings converts []models.NotificationSettings to definitions.AlertRuleNotificationSettingsExport
func AlertRuleNotificationSettingsExportFromNotificationSettings(ns []models.NotificationSettings) *definitions.AlertRuleNotificationSettingsExport {
if len(ns) == 0 {
return nil
}
m := ns[0]
toStringIfNotNil := func(d *model.Duration) *string {
if d == nil {
return nil
}
s := d.String()
return &s
}
return &definitions.AlertRuleNotificationSettingsExport{
Receiver: m.Receiver,
GroupBy: m.GroupBy,
GroupWait: toStringIfNotNil(m.GroupWait),
GroupInterval: toStringIfNotNil(m.GroupInterval),
RepeatInterval: toStringIfNotNil(m.RepeatInterval),
MuteTimeIntervals: m.MuteTimeIntervals,
}
}
// NotificationSettingsFromAlertRuleNotificationSettings converts definitions.AlertRuleNotificationSettings to []models.NotificationSettings
func NotificationSettingsFromAlertRuleNotificationSettings(ns *definitions.AlertRuleNotificationSettings) []models.NotificationSettings {
if ns == nil {
return nil
}
return []models.NotificationSettings{
{
Receiver: ns.Receiver,
GroupBy: ns.GroupBy,
GroupWait: ns.GroupWait,
GroupInterval: ns.GroupInterval,
RepeatInterval: ns.RepeatInterval,
MuteTimeIntervals: ns.MuteTimeIntervals,
},
}
}