From 9eaffdf5a84b6a6a01e4fdd225a1916508dcd413 Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Wed, 29 Mar 2023 13:34:59 -0400 Subject: [PATCH] Alerting: Remove dependency on alerting package in definitions (#65390) * move export rules to definitions package * move provisioning contact point methods to provisioning package * move AlertRuleGroupWithFolderTitle to ngalert models and adapter functions to api's compat * move rule_types files back to where they were before. --- pkg/services/ngalert/api/api_provisioning.go | 11 +- pkg/services/ngalert/api/compat.go | 90 +++++++++++ .../definitions/provisioning_alert_rules.go | 41 ++++- .../definitions/provisioning_contactpoints.go | 65 -------- pkg/services/ngalert/models/alert_rule.go | 7 + .../ngalert/provisioning/alert_rules.go | 17 +- .../ngalert/provisioning/contactpoints.go | 74 ++++++++- .../alerting/contact_point_types.go | 3 +- .../alerting/{file => }/rules_types.go | 149 +----------------- .../alerting/{file => }/rules_types_test.go | 2 +- pkg/services/provisioning/alerting/types.go | 12 +- 11 files changed, 233 insertions(+), 238 deletions(-) rename pkg/services/provisioning/alerting/{file => }/rules_types.go (52%) rename pkg/services/provisioning/alerting/{file => }/rules_types_test.go (99%) diff --git a/pkg/services/ngalert/api/api_provisioning.go b/pkg/services/ngalert/api/api_provisioning.go index 4f42c0afb70..532d08de954 100644 --- a/pkg/services/ngalert/api/api_provisioning.go +++ b/pkg/services/ngalert/api/api_provisioning.go @@ -14,7 +14,6 @@ import ( alerting_models "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/provisioning" "github.com/grafana/grafana/pkg/services/ngalert/store" - "github.com/grafana/grafana/pkg/services/provisioning/alerting/file" "github.com/grafana/grafana/pkg/util" ) @@ -64,8 +63,8 @@ type AlertRuleService interface { GetRuleGroup(ctx context.Context, orgID int64, folder, group string) (alerting_models.AlertRuleGroup, error) ReplaceRuleGroup(ctx context.Context, orgID int64, group alerting_models.AlertRuleGroup, userID int64, provenance alerting_models.Provenance) error GetAlertRuleWithFolderTitle(ctx context.Context, orgID int64, ruleUID string) (provisioning.AlertRuleWithFolderTitle, error) - GetAlertRuleGroupWithFolderTitle(ctx context.Context, orgID int64, folder, group string) (file.AlertRuleGroupWithFolderTitle, error) - GetAlertGroupsWithFolderTitle(ctx context.Context, orgID int64) ([]file.AlertRuleGroupWithFolderTitle, error) + GetAlertRuleGroupWithFolderTitle(ctx context.Context, orgID int64, folder, group string) (alerting_models.AlertRuleGroupWithFolderTitle, error) + GetAlertGroupsWithFolderTitle(ctx context.Context, orgID int64) ([]alerting_models.AlertRuleGroupWithFolderTitle, error) } func (srv *ProvisioningSrv) RouteGetPolicyTree(c *contextmodel.ReqContext) response.Response { @@ -347,7 +346,7 @@ func (srv *ProvisioningSrv) RouteGetAlertRulesExport(c *contextmodel.ReqContext) return ErrResp(http.StatusInternalServerError, err, "failed to get alert rules") } - e, err := file.NewAlertingFileExport(groupsWithTitle) + e, err := AlertingFileExportFromAlertRuleGroupWithFolderTitle(groupsWithTitle) if err != nil { return ErrResp(http.StatusInternalServerError, err, "failed to create alerting file export") } @@ -365,7 +364,7 @@ func (srv *ProvisioningSrv) RouteGetAlertRuleGroupExport(c *contextmodel.ReqCont return ErrResp(http.StatusInternalServerError, err, "failed to get alert rule group") } - e, err := file.NewAlertingFileExport([]file.AlertRuleGroupWithFolderTitle{g}) + e, err := AlertingFileExportFromAlertRuleGroupWithFolderTitle([]alerting_models.AlertRuleGroupWithFolderTitle{g}) if err != nil { return ErrResp(http.StatusInternalServerError, err, "failed to create alerting file export") } @@ -383,7 +382,7 @@ func (srv *ProvisioningSrv) RouteGetAlertRuleExport(c *contextmodel.ReqContext, return ErrResp(http.StatusInternalServerError, err, "") } - e, err := file.NewAlertingFileExport([]file.AlertRuleGroupWithFolderTitle{{ + e, err := AlertingFileExportFromAlertRuleGroupWithFolderTitle([]alerting_models.AlertRuleGroupWithFolderTitle{{ AlertRuleGroup: &alerting_models.AlertRuleGroup{ Title: rule.AlertRule.RuleGroup, FolderUID: rule.AlertRule.NamespaceUID, diff --git a/pkg/services/ngalert/api/compat.go b/pkg/services/ngalert/api/compat.go index 0686b7e2aac..546695c0b37 100644 --- a/pkg/services/ngalert/api/compat.go +++ b/pkg/services/ngalert/api/compat.go @@ -1,6 +1,7 @@ package api import ( + "encoding/json" "time" "github.com/prometheus/common/model" @@ -125,3 +126,92 @@ func ApiAlertRuleGroupFromAlertRuleGroup(d models.AlertRuleGroup) definitions.Al 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, + Interval: model.Duration(time.Duration(d.Interval) * time.Second), + 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) + } + + var dashboardUID string + if rule.DashboardUID != nil { + dashboardUID = *rule.DashboardUID + } + + var panelID int64 + if rule.PanelID != nil { + panelID = *rule.PanelID + } + + return definitions.AlertRuleExport{ + UID: rule.UID, + Title: rule.Title, + For: model.Duration(rule.For), + Condition: rule.Condition, + Data: data, + DashboardUID: dashboardUID, + PanelID: panelID, + NoDataState: definitions.NoDataState(rule.NoDataState), + ExecErrState: definitions.ExecutionErrorState(rule.ExecErrState), + Annotations: rule.Annotations, + Labels: rule.Labels, + IsPaused: rule.IsPaused, + }, 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]interface{} + err := json.Unmarshal(query.Model, &mdl) + if err != nil { + return definitions.AlertQueryExport{}, err + } + return definitions.AlertQueryExport{ + RefID: query.RefID, + QueryType: query.QueryType, + RelativeTimeRange: definitions.RelativeTimeRange{ + From: definitions.Duration(query.RelativeTimeRange.From), + To: definitions.Duration(query.RelativeTimeRange.To), + }, + DatasourceUID: query.DatasourceUID, + Model: mdl, + }, nil +} diff --git a/pkg/services/ngalert/api/tooling/definitions/provisioning_alert_rules.go b/pkg/services/ngalert/api/tooling/definitions/provisioning_alert_rules.go index 0629c19861d..ce9a42f7a69 100644 --- a/pkg/services/ngalert/api/tooling/definitions/provisioning_alert_rules.go +++ b/pkg/services/ngalert/api/tooling/definitions/provisioning_alert_rules.go @@ -4,8 +4,6 @@ import ( "time" "github.com/prometheus/common/model" - - "github.com/grafana/grafana/pkg/services/provisioning/alerting/file" ) // swagger:route GET /api/v1/provisioning/alert-rules provisioning stable RouteGetAlertRules @@ -217,4 +215,41 @@ type AlertRuleGroup struct { // AlertingFileExport is the full provisioned file export. // swagger:model -type AlertingFileExport = file.AlertingFileExport +type AlertingFileExport struct { + APIVersion int64 `json:"apiVersion" yaml:"apiVersion"` + Groups []AlertRuleGroupExport `json:"groups" yaml:"groups"` +} + +// AlertRuleGroupExport is the provisioned file export of AlertRuleGroupV1. +type AlertRuleGroupExport struct { + OrgID int64 `json:"orgId" yaml:"orgId"` + Name string `json:"name" yaml:"name"` + Folder string `json:"folder" yaml:"folder"` + Interval model.Duration `json:"interval" yaml:"interval"` + Rules []AlertRuleExport `json:"rules" yaml:"rules"` +} + +// AlertRuleExport is the provisioned file export of models.AlertRule. +type AlertRuleExport struct { + UID string `json:"uid" yaml:"uid"` + Title string `json:"title" yaml:"title"` + Condition string `json:"condition" yaml:"condition"` + Data []AlertQueryExport `json:"data" yaml:"data"` + DashboardUID string `json:"dasboardUid,omitempty" yaml:"dashboardUid,omitempty"` + PanelID int64 `json:"panelId,omitempty" yaml:"panelId,omitempty"` + NoDataState NoDataState `json:"noDataState" yaml:"noDataState"` + ExecErrState ExecutionErrorState `json:"execErrState" yaml:"execErrState"` + For model.Duration `json:"for" yaml:"for"` + Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"` + Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` + IsPaused bool `json:"isPaused" yaml:"isPaused"` +} + +// AlertQueryExport is the provisioned export of models.AlertQuery. +type AlertQueryExport struct { + RefID string `json:"refId" yaml:"refId"` + QueryType string `json:"queryType,omitempty" yaml:"queryType,omitempty"` + RelativeTimeRange RelativeTimeRange `json:"relativeTimeRange,omitempty" yaml:"relativeTimeRange,omitempty"` + DatasourceUID string `json:"datasourceUid" yaml:"datasourceUid"` + Model map[string]interface{} `json:"model" yaml:"model"` +} diff --git a/pkg/services/ngalert/api/tooling/definitions/provisioning_contactpoints.go b/pkg/services/ngalert/api/tooling/definitions/provisioning_contactpoints.go index 4b432b7ce39..12e1ef21814 100644 --- a/pkg/services/ngalert/api/tooling/definitions/provisioning_contactpoints.go +++ b/pkg/services/ngalert/api/tooling/definitions/provisioning_contactpoints.go @@ -1,15 +1,7 @@ package definitions import ( - "fmt" - - "github.com/grafana/alerting/logging" - alertingNotify "github.com/grafana/alerting/notify" - "github.com/grafana/alerting/receivers" - "github.com/grafana/grafana/pkg/components/simplejson" - "github.com/grafana/grafana/pkg/services/ngalert/notifier/channels_config" - "github.com/grafana/grafana/pkg/setting" ) // swagger:route GET /api/v1/provisioning/contact-points provisioning stable RouteGetContactpoints @@ -101,63 +93,6 @@ type EmbeddedContactPoint struct { const RedactedValue = "[REDACTED]" -func (e *EmbeddedContactPoint) Valid(decryptFunc receivers.GetDecryptedValueFn) error { - if e.Type == "" { - return fmt.Errorf("type should not be an empty string") - } - if e.Settings == nil { - return fmt.Errorf("settings should not be empty") - } - factory, exists := alertingNotify.Factory(e.Type) - if !exists { - return fmt.Errorf("unknown type '%s'", e.Type) - } - jsonBytes, err := e.Settings.MarshalJSON() - if err != nil { - return err - } - cfg, _ := receivers.NewFactoryConfig(&receivers.NotificationChannelConfig{ - Settings: jsonBytes, - Type: e.Type, - }, nil, decryptFunc, nil, nil, func(ctx ...interface{}) logging.Logger { - return &logging.FakeLogger{} - }, setting.BuildVersion) - if _, err := factory(cfg); err != nil { - return err - } - return nil -} - -func (e *EmbeddedContactPoint) SecretKeys() ([]string, error) { - notifiers := channels_config.GetAvailableNotifiers() - for _, n := range notifiers { - if n.Type == e.Type { - secureFields := []string{} - for _, field := range n.Options { - if field.Secure { - secureFields = append(secureFields, field.PropertyName) - } - } - return secureFields, nil - } - } - return nil, fmt.Errorf("no secrets configured for type '%s'", e.Type) -} - -func (e *EmbeddedContactPoint) ExtractSecrets() (map[string]string, error) { - secrets := map[string]string{} - secretKeys, err := e.SecretKeys() - if err != nil { - return nil, err - } - for _, secretKey := range secretKeys { - secretValue := e.Settings.Get(secretKey).MustString() - e.Settings.Del(secretKey) - secrets[secretKey] = secretValue - } - return secrets, nil -} - func (e *EmbeddedContactPoint) ResourceID() string { return e.UID } diff --git a/pkg/services/ngalert/models/alert_rule.go b/pkg/services/ngalert/models/alert_rule.go index 01e56839dd1..3628ab14842 100644 --- a/pkg/services/ngalert/models/alert_rule.go +++ b/pkg/services/ngalert/models/alert_rule.go @@ -136,6 +136,13 @@ type AlertRuleGroup struct { Rules []AlertRule } +// AlertRuleGroupWithFolderTitle extends AlertRuleGroup with orgID and folder title +type AlertRuleGroupWithFolderTitle struct { + *AlertRuleGroup + OrgID int64 + FolderTitle string +} + // AlertRule is the model for alert rules in unified alerting. type AlertRule struct { ID int64 `xorm:"pk autoincr 'id'"` diff --git a/pkg/services/ngalert/provisioning/alert_rules.go b/pkg/services/ngalert/provisioning/alert_rules.go index fa4941144a3..b2bda5e300c 100644 --- a/pkg/services/ngalert/provisioning/alert_rules.go +++ b/pkg/services/ngalert/provisioning/alert_rules.go @@ -11,7 +11,6 @@ import ( "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/store" - "github.com/grafana/grafana/pkg/services/provisioning/alerting/file" "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/util" ) @@ -407,7 +406,7 @@ func (service *AlertRuleService) deleteRules(ctx context.Context, orgID int64, t } // GetAlertRuleGroupWithFolderTitle returns the alert rule group with folder title. -func (service *AlertRuleService) GetAlertRuleGroupWithFolderTitle(ctx context.Context, orgID int64, namespaceUID, group string) (file.AlertRuleGroupWithFolderTitle, error) { +func (service *AlertRuleService) GetAlertRuleGroupWithFolderTitle(ctx context.Context, orgID int64, namespaceUID, group string) (models.AlertRuleGroupWithFolderTitle, error) { q := models.ListAlertRulesQuery{ OrgID: orgID, NamespaceUIDs: []string{namespaceUID}, @@ -415,10 +414,10 @@ func (service *AlertRuleService) GetAlertRuleGroupWithFolderTitle(ctx context.Co } ruleList, err := service.ruleStore.ListAlertRules(ctx, &q) if err != nil { - return file.AlertRuleGroupWithFolderTitle{}, err + return models.AlertRuleGroupWithFolderTitle{}, err } if len(ruleList) == 0 { - return file.AlertRuleGroupWithFolderTitle{}, store.ErrAlertRuleGroupNotFound + return models.AlertRuleGroupWithFolderTitle{}, store.ErrAlertRuleGroupNotFound } dq := dashboards.GetDashboardQuery{ @@ -427,10 +426,10 @@ func (service *AlertRuleService) GetAlertRuleGroupWithFolderTitle(ctx context.Co } dash, err := service.dashboardService.GetDashboard(ctx, &dq) if err != nil { - return file.AlertRuleGroupWithFolderTitle{}, err + return models.AlertRuleGroupWithFolderTitle{}, err } - res := file.AlertRuleGroupWithFolderTitle{ + res := models.AlertRuleGroupWithFolderTitle{ AlertRuleGroup: &models.AlertRuleGroup{ Title: ruleList[0].RuleGroup, FolderUID: ruleList[0].NamespaceUID, @@ -449,7 +448,7 @@ func (service *AlertRuleService) GetAlertRuleGroupWithFolderTitle(ctx context.Co } // GetAlertGroupsWithFolderTitle returns all groups with folder title that have at least one alert. -func (service *AlertRuleService) GetAlertGroupsWithFolderTitle(ctx context.Context, orgID int64) ([]file.AlertRuleGroupWithFolderTitle, error) { +func (service *AlertRuleService) GetAlertGroupsWithFolderTitle(ctx context.Context, orgID int64) ([]models.AlertRuleGroupWithFolderTitle, error) { q := models.ListAlertRulesQuery{ OrgID: orgID, } @@ -487,13 +486,13 @@ func (service *AlertRuleService) GetAlertGroupsWithFolderTitle(ctx context.Conte folderUidToTitle[dash.UID] = dash.Title } - result := make([]file.AlertRuleGroupWithFolderTitle, 0) + result := make([]models.AlertRuleGroupWithFolderTitle, 0) for groupKey, rules := range groups { title, ok := folderUidToTitle[groupKey.NamespaceUID] if !ok { return nil, fmt.Errorf("cannot find title for folder with uid '%s'", groupKey.NamespaceUID) } - result = append(result, file.AlertRuleGroupWithFolderTitle{ + result = append(result, models.AlertRuleGroupWithFolderTitle{ AlertRuleGroup: &models.AlertRuleGroup{ Title: rules[0].RuleGroup, FolderUID: rules[0].NamespaceUID, diff --git a/pkg/services/ngalert/provisioning/contactpoints.go b/pkg/services/ngalert/provisioning/contactpoints.go index 8a647384fda..55211403654 100644 --- a/pkg/services/ngalert/provisioning/contactpoints.go +++ b/pkg/services/ngalert/provisioning/contactpoints.go @@ -7,13 +7,18 @@ import ( "fmt" "sort" + "github.com/grafana/alerting/logging" + alertingNotify "github.com/grafana/alerting/notify" + "github.com/grafana/alerting/receivers" "github.com/prometheus/alertmanager/config" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/log" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/models" + "github.com/grafana/grafana/pkg/services/ngalert/notifier/channels_config" "github.com/grafana/grafana/pkg/services/secrets" + "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" ) @@ -131,7 +136,7 @@ func (ecp *ContactPointService) getContactPointDecrypted(ctx context.Context, or func (ecp *ContactPointService) CreateContactPoint(ctx context.Context, orgID int64, contactPoint apimodels.EmbeddedContactPoint, provenance models.Provenance) (apimodels.EmbeddedContactPoint, error) { - if err := contactPoint.Valid(ecp.encryptionService.GetDecryptedValue); err != nil { + if err := ValidateContactPoint(contactPoint, ecp.encryptionService.GetDecryptedValue); err != nil { return apimodels.EmbeddedContactPoint{}, fmt.Errorf("%w: %s", ErrValidation, err.Error()) } @@ -140,7 +145,7 @@ func (ecp *ContactPointService) CreateContactPoint(ctx context.Context, orgID in return apimodels.EmbeddedContactPoint{}, err } - extractedSecrets, err := contactPoint.ExtractSecrets() + extractedSecrets, err := RemoveSecretsForContactPoint(&contactPoint) if err != nil { return apimodels.EmbeddedContactPoint{}, err } @@ -240,7 +245,7 @@ func (ecp *ContactPointService) UpdateContactPoint(ctx context.Context, orgID in if err != nil { return err } - secretKeys, err := contactPoint.SecretKeys() + secretKeys, err := GetSecretKeysForContactPointType(contactPoint.Type) if err != nil { return fmt.Errorf("%w: %s", ErrValidation, err.Error()) } @@ -252,7 +257,7 @@ func (ecp *ContactPointService) UpdateContactPoint(ctx context.Context, orgID in } // validate merged values - if err := contactPoint.Valid(ecp.encryptionService.GetDecryptedValue); err != nil { + if err := ValidateContactPoint(contactPoint, ecp.encryptionService.GetDecryptedValue); err != nil { return fmt.Errorf("%w: %s", ErrValidation, err.Error()) } @@ -265,7 +270,7 @@ func (ecp *ContactPointService) UpdateContactPoint(ctx context.Context, orgID in return fmt.Errorf("cannot changed provenance from '%s' to '%s'", storedProvenance, provenance) } // transform to internal model - extractedSecrets, err := contactPoint.ExtractSecrets() + extractedSecrets, err := RemoveSecretsForContactPoint(&contactPoint) if err != nil { return err } @@ -494,3 +499,62 @@ func replaceReferences(oldName, newName string, routes ...*apimodels.Route) { replaceReferences(oldName, newName, route.Routes...) } } + +func ValidateContactPoint(e apimodels.EmbeddedContactPoint, decryptFunc receivers.GetDecryptedValueFn) error { + if e.Type == "" { + return fmt.Errorf("type should not be an empty string") + } + if e.Settings == nil { + return fmt.Errorf("settings should not be empty") + } + factory, exists := alertingNotify.Factory(e.Type) + if !exists { + return fmt.Errorf("unknown type '%s'", e.Type) + } + jsonBytes, err := e.Settings.MarshalJSON() + if err != nil { + return err + } + cfg, _ := receivers.NewFactoryConfig(&receivers.NotificationChannelConfig{ + Settings: jsonBytes, + Type: e.Type, + }, nil, decryptFunc, nil, nil, func(ctx ...interface{}) logging.Logger { + return &logging.FakeLogger{} + }, setting.BuildVersion) + if _, err := factory(cfg); err != nil { + return err + } + return nil +} + +// GetSecretKeysForContactPointType returns settings keys of contact point of the given type that are expected to be secrets. Returns error is contact point type is not known. +func GetSecretKeysForContactPointType(contactPointType string) ([]string, error) { + notifiers := channels_config.GetAvailableNotifiers() + for _, n := range notifiers { + if n.Type == contactPointType { + var secureFields []string + for _, field := range n.Options { + if field.Secure { + secureFields = append(secureFields, field.PropertyName) + } + } + return secureFields, nil + } + } + return nil, fmt.Errorf("no secrets configured for type '%s'", contactPointType) +} + +// RemoveSecretsForContactPoint removes all secrets from the contact point's settings and returns them as a map. Returns error if contact point type is not known. +func RemoveSecretsForContactPoint(e *apimodels.EmbeddedContactPoint) (map[string]string, error) { + s := map[string]string{} + secretKeys, err := GetSecretKeysForContactPointType(e.Type) + if err != nil { + return nil, err + } + for _, secretKey := range secretKeys { + secretValue := e.Settings.Get(secretKey).MustString() + e.Settings.Del(secretKey) + s[secretKey] = secretValue + } + return s, nil +} diff --git a/pkg/services/provisioning/alerting/contact_point_types.go b/pkg/services/provisioning/alerting/contact_point_types.go index a01e3f29101..8fe5d2ac3db 100644 --- a/pkg/services/provisioning/alerting/contact_point_types.go +++ b/pkg/services/provisioning/alerting/contact_point_types.go @@ -8,6 +8,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/models" + "github.com/grafana/grafana/pkg/services/ngalert/provisioning" "github.com/grafana/grafana/pkg/services/provisioning/values" ) @@ -94,7 +95,7 @@ func (config *ReceiverV1) mapToModel(name string) (definitions.EmbeddedContactPo } // As the values are not encrypted when coming from disk files, // we can simply return the fallback for validation. - err := cp.Valid(func(_ context.Context, _ map[string][]byte, _, fallback string) string { + err := provisioning.ValidateContactPoint(cp, func(_ context.Context, _ map[string][]byte, _, fallback string) string { return fallback }) if err != nil { diff --git a/pkg/services/provisioning/alerting/file/rules_types.go b/pkg/services/provisioning/alerting/rules_types.go similarity index 52% rename from pkg/services/provisioning/alerting/file/rules_types.go rename to pkg/services/provisioning/alerting/rules_types.go index 59782b08c71..8c245d0ee9a 100644 --- a/pkg/services/provisioning/alerting/file/rules_types.go +++ b/pkg/services/provisioning/alerting/rules_types.go @@ -1,4 +1,4 @@ -package file +package alerting import ( "encoding/json" @@ -31,11 +31,11 @@ type AlertRuleGroupV1 struct { Rules []AlertRuleV1 `json:"rules" yaml:"rules"` } -func (ruleGroupV1 *AlertRuleGroupV1) MapToModel() (AlertRuleGroupWithFolderTitle, error) { - ruleGroup := AlertRuleGroupWithFolderTitle{AlertRuleGroup: &models.AlertRuleGroup{}} +func (ruleGroupV1 *AlertRuleGroupV1) MapToModel() (models.AlertRuleGroupWithFolderTitle, error) { + ruleGroup := models.AlertRuleGroupWithFolderTitle{AlertRuleGroup: &models.AlertRuleGroup{}} ruleGroup.Title = ruleGroupV1.Name.Value() if strings.TrimSpace(ruleGroup.Title) == "" { - return AlertRuleGroupWithFolderTitle{}, errors.New("rule group has no name set") + return models.AlertRuleGroupWithFolderTitle{}, errors.New("rule group has no name set") } ruleGroup.OrgID = ruleGroupV1.OrgID.Value() if ruleGroup.OrgID < 1 { @@ -43,29 +43,23 @@ func (ruleGroupV1 *AlertRuleGroupV1) MapToModel() (AlertRuleGroupWithFolderTitle } interval, err := model.ParseDuration(ruleGroupV1.Interval.Value()) if err != nil { - return AlertRuleGroupWithFolderTitle{}, err + return models.AlertRuleGroupWithFolderTitle{}, err } ruleGroup.Interval = int64(time.Duration(interval).Seconds()) ruleGroup.FolderTitle = ruleGroupV1.Folder.Value() if strings.TrimSpace(ruleGroup.FolderTitle) == "" { - return AlertRuleGroupWithFolderTitle{}, errors.New("rule group has no folder set") + return models.AlertRuleGroupWithFolderTitle{}, errors.New("rule group has no folder set") } for _, ruleV1 := range ruleGroupV1.Rules { rule, err := ruleV1.mapToModel(ruleGroup.OrgID) if err != nil { - return AlertRuleGroupWithFolderTitle{}, err + return models.AlertRuleGroupWithFolderTitle{}, err } ruleGroup.Rules = append(ruleGroup.Rules, rule) } return ruleGroup, nil } -type AlertRuleGroupWithFolderTitle struct { - *models.AlertRuleGroup - OrgID int64 - FolderTitle string -} - type AlertRuleV1 struct { UID values.StringValue `json:"uid" yaml:"uid"` Title values.StringValue `json:"title" yaml:"title"` @@ -175,132 +169,3 @@ func (queryV1 *QueryV1) mapToModel() (models.AlertQuery, error) { Model: rawMessage, }, nil } - -// Response structs - -// AlertingFileExport is the full provisioned file export. -// swagger:model -type AlertingFileExport struct { - APIVersion int64 `json:"apiVersion" yaml:"apiVersion"` - Groups []AlertRuleGroupExport `json:"groups" yaml:"groups"` -} - -// AlertRuleGroupExport is the provisioned file export of AlertRuleGroupV1. -type AlertRuleGroupExport struct { - OrgID int64 `json:"orgId" yaml:"orgId"` - Name string `json:"name" yaml:"name"` - Folder string `json:"folder" yaml:"folder"` - Interval model.Duration `json:"interval" yaml:"interval"` - Rules []AlertRuleExport `json:"rules" yaml:"rules"` -} - -// AlertRuleExport is the provisioned file export of models.AlertRule. -type AlertRuleExport struct { - UID string `json:"uid" yaml:"uid"` - Title string `json:"title" yaml:"title"` - Condition string `json:"condition" yaml:"condition"` - Data []AlertQueryExport `json:"data" yaml:"data"` - DashboardUID string `json:"dasboardUid,omitempty" yaml:"dashboardUid,omitempty"` - PanelID int64 `json:"panelId,omitempty" yaml:"panelId,omitempty"` - NoDataState models.NoDataState `json:"noDataState" yaml:"noDataState"` - ExecErrState models.ExecutionErrorState `json:"execErrState" yaml:"execErrState"` - For model.Duration `json:"for" yaml:"for"` - Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"` - Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` - IsPaused bool `json:"isPaused" yaml:"isPaused"` -} - -// AlertQueryExport is the provisioned export of models.AlertQuery. -type AlertQueryExport struct { - RefID string `json:"refId" yaml:"refId"` - QueryType string `json:"queryType,omitempty" yaml:"queryType,omitempty"` - RelativeTimeRange models.RelativeTimeRange `json:"relativeTimeRange,omitempty" yaml:"relativeTimeRange,omitempty"` - DatasourceUID string `json:"datasourceUid" yaml:"datasourceUid"` - Model map[string]interface{} `json:"model" yaml:"model"` -} - -// NewAlertingFileExport creates an AlertingFileExport DTO from []AlertRuleGroupWithFolderTitle. -func NewAlertingFileExport(groups []AlertRuleGroupWithFolderTitle) (AlertingFileExport, error) { - f := AlertingFileExport{APIVersion: 1} - for _, group := range groups { - export, err := newAlertRuleGroupExport(group) - if err != nil { - return AlertingFileExport{}, err - } - f.Groups = append(f.Groups, export) - } - return f, nil -} - -// newAlertRuleGroupExport creates a AlertRuleGroupExport DTO from models.AlertRuleGroup. -func newAlertRuleGroupExport(d AlertRuleGroupWithFolderTitle) (AlertRuleGroupExport, error) { - rules := make([]AlertRuleExport, 0, len(d.Rules)) - for i := range d.Rules { - alert, err := newAlertRuleExport(d.Rules[i]) - if err != nil { - return AlertRuleGroupExport{}, err - } - rules = append(rules, alert) - } - return AlertRuleGroupExport{ - OrgID: d.OrgID, - Name: d.Title, - Folder: d.FolderTitle, - Interval: model.Duration(time.Duration(d.Interval) * time.Second), - Rules: rules, - }, nil -} - -// newAlertRuleExport creates a AlertRuleExport DTO from models.AlertRule. -func newAlertRuleExport(rule models.AlertRule) (AlertRuleExport, error) { - data := make([]AlertQueryExport, 0, len(rule.Data)) - for i := range rule.Data { - query, err := newAlertQueryExport(rule.Data[i]) - if err != nil { - return AlertRuleExport{}, err - } - data = append(data, query) - } - - var dashboardUID string - if rule.DashboardUID != nil { - dashboardUID = *rule.DashboardUID - } - - var panelID int64 - if rule.PanelID != nil { - panelID = *rule.PanelID - } - - return AlertRuleExport{ - UID: rule.UID, - Title: rule.Title, - For: model.Duration(rule.For), - Condition: rule.Condition, - Data: data, - DashboardUID: dashboardUID, - PanelID: panelID, - NoDataState: rule.NoDataState, - ExecErrState: rule.ExecErrState, - Annotations: rule.Annotations, - Labels: rule.Labels, - IsPaused: rule.IsPaused, - }, nil -} - -// newAlertQueryExport creates a AlertQueryExport DTO from models.AlertQuery. -func newAlertQueryExport(query models.AlertQuery) (AlertQueryExport, error) { - // We unmarshal the json.RawMessage model into a map in order to facilitate yaml marshalling. - var mdl map[string]interface{} - err := json.Unmarshal(query.Model, &mdl) - if err != nil { - return AlertQueryExport{}, err - } - return AlertQueryExport{ - RefID: query.RefID, - QueryType: query.QueryType, - RelativeTimeRange: query.RelativeTimeRange, - DatasourceUID: query.DatasourceUID, - Model: mdl, - }, nil -} diff --git a/pkg/services/provisioning/alerting/file/rules_types_test.go b/pkg/services/provisioning/alerting/rules_types_test.go similarity index 99% rename from pkg/services/provisioning/alerting/file/rules_types_test.go rename to pkg/services/provisioning/alerting/rules_types_test.go index abc2836f01a..dcca42e0574 100644 --- a/pkg/services/provisioning/alerting/file/rules_types_test.go +++ b/pkg/services/provisioning/alerting/rules_types_test.go @@ -1,4 +1,4 @@ -package file +package alerting import ( "testing" diff --git a/pkg/services/provisioning/alerting/types.go b/pkg/services/provisioning/alerting/types.go index 6a3331c095b..7e0e795fa30 100644 --- a/pkg/services/provisioning/alerting/types.go +++ b/pkg/services/provisioning/alerting/types.go @@ -3,7 +3,7 @@ package alerting import ( "fmt" - "github.com/grafana/grafana/pkg/services/provisioning/alerting/file" + "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/provisioning/values" ) @@ -16,8 +16,8 @@ type OrgID int64 type AlertingFile struct { configVersion Filename string - Groups []file.AlertRuleGroupWithFolderTitle - DeleteRules []file.RuleDelete + Groups []models.AlertRuleGroupWithFolderTitle + DeleteRules []RuleDelete ContactPoints []ContactPoint DeleteContactPoints []DeleteContactPoint Policies []NotificiationPolicy @@ -31,8 +31,8 @@ type AlertingFile struct { type AlertingFileV1 struct { configVersion Filename string - Groups []file.AlertRuleGroupV1 `json:"groups" yaml:"groups"` - DeleteRules []file.RuleDeleteV1 `json:"deleteRules" yaml:"deleteRules"` + Groups []AlertRuleGroupV1 `json:"groups" yaml:"groups"` + DeleteRules []RuleDeleteV1 `json:"deleteRules" yaml:"deleteRules"` ContactPoints []ContactPointV1 `json:"contactPoints" yaml:"contactPoints"` DeleteContactPoints []DeleteContactPointV1 `json:"deleteContactPoints" yaml:"deleteContactPoints"` Policies []NotificiationPolicyV1 `json:"policies" yaml:"policies"` @@ -133,7 +133,7 @@ func (fileV1 *AlertingFileV1) mapRules(alertingFile *AlertingFile) error { if orgID < 1 { orgID = 1 } - ruleDelete := file.RuleDelete{ + ruleDelete := RuleDelete{ UID: ruleDeleteV1.UID.Value(), OrgID: orgID, }