306 lines
10 KiB
Go
306 lines
10 KiB
Go
package store
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
|
"github.com/grafana/grafana/pkg/util"
|
|
|
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
)
|
|
|
|
// We only care about the data source UIDs.
|
|
type compactQuery struct {
|
|
DatasourceUID string `json:"datasourceUid"`
|
|
}
|
|
|
|
// AlertRuleConvertOptions controls which fields to parse during conversion from alertRule to models.AlertRule.
|
|
// By default all fields are included. Set Exclude* to true to skip parsing expensive fields.
|
|
type AlertRuleConvertOptions struct {
|
|
ExcludeAlertQueries bool // Only parse datasource UIDs from queries
|
|
ExcludeNotificationSettings bool
|
|
ExcludeMetadata bool
|
|
}
|
|
|
|
func alertRuleToModelsAlertRule(ar alertRule, l log.Logger) (models.AlertRule, error) {
|
|
return convertAlertRuleToModel(ar, l, AlertRuleConvertOptions{})
|
|
}
|
|
|
|
// convertAlertRuleToModel creates a models.AlertRule from an alertRule.
|
|
// opts.Exclude* fields control which expensive fields to skip parsing, reducing JSON serializations.
|
|
func convertAlertRuleToModel(ar alertRule, l log.Logger, opts AlertRuleConvertOptions) (models.AlertRule, error) {
|
|
var data []models.AlertQuery
|
|
if opts.ExcludeAlertQueries {
|
|
var cqs []compactQuery
|
|
if err := json.Unmarshal([]byte(ar.Data), &cqs); err != nil {
|
|
return models.AlertRule{}, fmt.Errorf("failed to parse data: %w", err)
|
|
}
|
|
for _, cq := range cqs {
|
|
data = append(data, models.AlertQuery{DatasourceUID: cq.DatasourceUID})
|
|
}
|
|
} else {
|
|
if err := json.Unmarshal([]byte(ar.Data), &data); err != nil {
|
|
return models.AlertRule{}, fmt.Errorf("failed to parse data: %w", err)
|
|
}
|
|
}
|
|
|
|
result := models.AlertRule{
|
|
ID: ar.ID,
|
|
OrgID: ar.OrgID,
|
|
GUID: ar.GUID,
|
|
Title: ar.Title,
|
|
Condition: ar.Condition,
|
|
Data: data,
|
|
Updated: ar.Updated,
|
|
IntervalSeconds: ar.IntervalSeconds,
|
|
Version: ar.Version,
|
|
UID: ar.UID,
|
|
NamespaceUID: ar.NamespaceUID,
|
|
DashboardUID: ar.DashboardUID,
|
|
PanelID: ar.PanelID,
|
|
RuleGroupIndex: ar.RuleGroupIndex,
|
|
For: ar.For,
|
|
KeepFiringFor: ar.KeepFiringFor,
|
|
IsPaused: ar.IsPaused,
|
|
MissingSeriesEvalsToResolve: ar.MissingSeriesEvalsToResolve,
|
|
}
|
|
|
|
if ar.RuleGroup == "" {
|
|
noGroupRuleGroup, err := models.NewNoGroupRuleGroup(ar.UID)
|
|
if err != nil {
|
|
return models.AlertRule{}, fmt.Errorf("failed to create no group rule group: %w", err)
|
|
}
|
|
result.RuleGroup = noGroupRuleGroup.String()
|
|
} else {
|
|
result.RuleGroup = ar.RuleGroup
|
|
}
|
|
|
|
if ar.UpdatedBy != nil {
|
|
result.UpdatedBy = util.Pointer(models.UserUID(*ar.UpdatedBy))
|
|
}
|
|
|
|
var err error
|
|
if ar.NoDataState != "" {
|
|
result.NoDataState, err = models.NoDataStateFromString(ar.NoDataState)
|
|
if err != nil {
|
|
l.Warn("Unknown NoDataState value, defaulting to NoData", append(result.GetKey().LogContext(), "original", ar.NoDataState)...)
|
|
result.NoDataState = models.NoData
|
|
}
|
|
}
|
|
if ar.ExecErrState != "" {
|
|
result.ExecErrState, err = models.ErrStateFromString(ar.ExecErrState)
|
|
if err != nil {
|
|
l.Warn("Unknown ExecErrState value, defaulting to Error", append(result.GetKey().LogContext(), "original", ar.ExecErrState)...)
|
|
result.ExecErrState = models.ErrorErrState
|
|
}
|
|
}
|
|
|
|
if ar.Record != "" {
|
|
var record models.Record
|
|
err = json.Unmarshal([]byte(ar.Record), &record)
|
|
if err != nil {
|
|
return models.AlertRule{}, fmt.Errorf("failed to parse record: %w", err)
|
|
}
|
|
result.Record = &record
|
|
}
|
|
|
|
if ar.Labels != "" {
|
|
err = json.Unmarshal([]byte(ar.Labels), &result.Labels)
|
|
if err != nil {
|
|
return models.AlertRule{}, fmt.Errorf("failed to parse labels: %w", err)
|
|
}
|
|
}
|
|
|
|
if ar.Annotations != "" {
|
|
err = json.Unmarshal([]byte(ar.Annotations), &result.Annotations)
|
|
if err != nil {
|
|
return models.AlertRule{}, fmt.Errorf("failed to parse annotations: %w", err)
|
|
}
|
|
}
|
|
|
|
if !opts.ExcludeNotificationSettings && ar.NotificationSettings != "" {
|
|
ns, err := parseNotificationSettings(ar.NotificationSettings)
|
|
if err != nil {
|
|
return models.AlertRule{}, fmt.Errorf("failed to parse notification settings: %w", err)
|
|
}
|
|
result.NotificationSettings = ns
|
|
}
|
|
|
|
if !opts.ExcludeMetadata && ar.Metadata != "" {
|
|
err = json.Unmarshal([]byte(ar.Metadata), &result.Metadata)
|
|
if err != nil {
|
|
return models.AlertRule{}, fmt.Errorf("failed to metadata: %w", err)
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func parseNotificationSettings(s string) ([]models.NotificationSettings, error) {
|
|
var result []models.NotificationSettings
|
|
if err := json.Unmarshal([]byte(s), &result); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func alertRuleFromModelsAlertRule(ar models.AlertRule) (alertRule, error) {
|
|
result := alertRule{
|
|
ID: ar.ID,
|
|
GUID: ar.GUID,
|
|
OrgID: ar.OrgID,
|
|
Title: ar.Title,
|
|
Condition: ar.Condition,
|
|
Updated: ar.Updated,
|
|
IntervalSeconds: ar.IntervalSeconds,
|
|
Version: ar.Version,
|
|
UID: ar.UID,
|
|
NamespaceUID: ar.NamespaceUID,
|
|
DashboardUID: ar.DashboardUID,
|
|
PanelID: ar.PanelID,
|
|
RuleGroupIndex: ar.RuleGroupIndex,
|
|
NoDataState: ar.NoDataState.String(),
|
|
ExecErrState: ar.ExecErrState.String(),
|
|
For: ar.For,
|
|
KeepFiringFor: ar.KeepFiringFor,
|
|
IsPaused: ar.IsPaused,
|
|
MissingSeriesEvalsToResolve: ar.MissingSeriesEvalsToResolve,
|
|
}
|
|
|
|
if models.IsNoGroupRuleGroup(ar.RuleGroup) {
|
|
result.RuleGroup = ""
|
|
} else {
|
|
result.RuleGroup = ar.RuleGroup
|
|
}
|
|
|
|
if ar.UpdatedBy != nil {
|
|
result.UpdatedBy = util.Pointer(string(*ar.UpdatedBy))
|
|
}
|
|
|
|
// Serialize complex types to JSON strings
|
|
data, err := json.Marshal(ar.Data)
|
|
if err != nil {
|
|
return alertRule{}, fmt.Errorf("failed to marshal data: %w", err)
|
|
}
|
|
result.Data = string(data)
|
|
|
|
if ar.Record != nil {
|
|
recordData, err := json.Marshal(ar.Record)
|
|
if err != nil {
|
|
return alertRule{}, fmt.Errorf("failed to marshal record: %w", err)
|
|
}
|
|
result.Record = string(recordData)
|
|
}
|
|
|
|
if len(ar.Annotations) > 0 {
|
|
annotationsData, err := json.Marshal(ar.Annotations)
|
|
if err != nil {
|
|
return alertRule{}, fmt.Errorf("failed to marshal annotations: %w", err)
|
|
}
|
|
result.Annotations = string(annotationsData)
|
|
}
|
|
|
|
if len(ar.Labels) > 0 {
|
|
labelsData, err := json.Marshal(ar.Labels)
|
|
if err != nil {
|
|
return alertRule{}, fmt.Errorf("failed to marshal labels: %w", err)
|
|
}
|
|
result.Labels = string(labelsData)
|
|
}
|
|
|
|
if len(ar.NotificationSettings) > 0 {
|
|
notificationSettingsData, err := json.Marshal(ar.NotificationSettings)
|
|
if err != nil {
|
|
return alertRule{}, fmt.Errorf("failed to marshal notification settings: %w", err)
|
|
}
|
|
result.NotificationSettings = string(notificationSettingsData)
|
|
}
|
|
|
|
metadata, err := json.Marshal(ar.Metadata)
|
|
if err != nil {
|
|
return alertRule{}, fmt.Errorf("failed to metadata: %w", err)
|
|
}
|
|
result.Metadata = string(metadata)
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func alertRuleToAlertRuleVersion(rule alertRule) alertRuleVersion {
|
|
return alertRuleVersion{
|
|
RuleOrgID: rule.OrgID,
|
|
RuleGUID: rule.GUID,
|
|
RuleUID: rule.UID,
|
|
RuleNamespaceUID: rule.NamespaceUID,
|
|
RuleGroup: rule.RuleGroup,
|
|
RuleGroupIndex: rule.RuleGroupIndex,
|
|
ParentVersion: 0,
|
|
RestoredFrom: 0,
|
|
Version: rule.Version,
|
|
Created: rule.Updated, // assuming the Updated time as the creation time
|
|
CreatedBy: rule.UpdatedBy,
|
|
Message: "", // Message is set by caller when creating versions
|
|
Title: rule.Title,
|
|
Condition: rule.Condition,
|
|
Data: rule.Data,
|
|
IntervalSeconds: rule.IntervalSeconds,
|
|
Record: rule.Record,
|
|
NoDataState: rule.NoDataState,
|
|
ExecErrState: rule.ExecErrState,
|
|
For: rule.For,
|
|
KeepFiringFor: rule.KeepFiringFor,
|
|
Annotations: rule.Annotations,
|
|
Labels: rule.Labels,
|
|
IsPaused: rule.IsPaused,
|
|
NotificationSettings: rule.NotificationSettings,
|
|
Metadata: rule.Metadata,
|
|
MissingSeriesEvalsToResolve: rule.MissingSeriesEvalsToResolve,
|
|
}
|
|
}
|
|
|
|
func alertRuleVersionToAlertRule(version alertRuleVersion) alertRule {
|
|
return alertRule{
|
|
ID: version.ID,
|
|
GUID: version.RuleGUID,
|
|
OrgID: version.RuleOrgID,
|
|
Title: version.Title,
|
|
Condition: version.Condition,
|
|
Data: version.Data,
|
|
Updated: version.Created,
|
|
UpdatedBy: version.CreatedBy,
|
|
IntervalSeconds: version.IntervalSeconds,
|
|
Version: version.Version,
|
|
UID: version.RuleUID,
|
|
NamespaceUID: version.RuleNamespaceUID,
|
|
// Versions do not store Dashboard\Panel as separate column.
|
|
// However, these fields are part of annotations and information in these fields is redundant
|
|
DashboardUID: nil,
|
|
PanelID: nil,
|
|
RuleGroup: version.RuleGroup,
|
|
RuleGroupIndex: version.RuleGroupIndex,
|
|
Record: version.Record,
|
|
NoDataState: version.NoDataState,
|
|
ExecErrState: version.ExecErrState,
|
|
For: version.For,
|
|
KeepFiringFor: version.KeepFiringFor,
|
|
Annotations: version.Annotations,
|
|
Labels: version.Labels,
|
|
IsPaused: version.IsPaused,
|
|
NotificationSettings: version.NotificationSettings,
|
|
Metadata: version.Metadata,
|
|
MissingSeriesEvalsToResolve: version.MissingSeriesEvalsToResolve,
|
|
}
|
|
}
|
|
|
|
func alertRuleVersionToModelsAlertRuleVersion(version alertRuleVersion, l log.Logger) (models.AlertRuleVersion, error) {
|
|
result, err := alertRuleToModelsAlertRule(alertRuleVersionToAlertRule(version), l)
|
|
if err != nil {
|
|
return models.AlertRuleVersion{}, err
|
|
}
|
|
|
|
return models.AlertRuleVersion{
|
|
AlertRule: result,
|
|
Message: version.Message,
|
|
}, nil
|
|
}
|