The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/services/ngalert/api/compat.go

477 lines
16 KiB

package api
import (
"bytes"
"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),
// Recording Rule fields will be implemented in the future.
// For now, no rules can be recording rules. So, we force these to be empty.
Record: nil,
}, 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,
}
}
// AlertingFileExportFromAlertRuleGroupWithFolderFullpath creates an definitions.AlertingFileExport DTO from []models.AlertRuleGroupWithFolderTitle.
func AlertingFileExportFromAlertRuleGroupWithFolderFullpath(groups []models.AlertRuleGroupWithFolderFullpath) (definitions.AlertingFileExport, error) {
f := definitions.AlertingFileExport{APIVersion: 1}
for _, group := range groups {
export, err := AlertRuleGroupExportFromAlertRuleGroupWithFolderFullpath(group)
if err != nil {
return definitions.AlertingFileExport{}, err
}
f.Groups = append(f.Groups, export)
}
return f, nil
}
// AlertRuleGroupExportFromAlertRuleGroupWithFolderFullpath creates a definitions.AlertRuleGroupExport DTO from models.AlertRuleGroup.
func AlertRuleGroupExportFromAlertRuleGroupWithFolderFullpath(d models.AlertRuleGroupWithFolderFullpath) (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.FolderFullpath,
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
}
func encodeQueryModel(m map[string]any) (string, error) {
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
err := enc.Encode(m)
if err != nil {
return "", err
}
return string(bytes.TrimRight(buf.Bytes(), "\n")), 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
}
modelString, err := encodeQueryModel(mdl)
if err != nil {
return definitions.AlertQueryExport{}, err
}
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: modelString,
}, 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,
},
}
}
func ApiRecordFromModelRecord(r *models.Record) *definitions.Record {
if r == nil {
return nil
}
return &definitions.Record{
Metric: r.Metric,
From: r.From,
}
}
func ModelRecordFromApiRecord(r *definitions.Record) *models.Record {
if r == nil {
return nil
}
return &models.Record{
Metric: r.Metric,
From: r.From,
}
}