mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
This adds provisioning endpoints for downloading alert rules and alert rule groups in a format that is compatible with file provisioning. Each endpoint supports both json and yaml response types via Accept header as well as a query parameter download=true/false that will set Content-Disposition to recommend initiating a download or inline display. This also makes some package changes to keep structs with potential to drift closer together. Eventually, other alerting file structs should also move into this new file package, but the rest require some refactoring that is out of scope for this PR.
130 lines
4.0 KiB
Go
130 lines
4.0 KiB
Go
package alerting
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
|
"github.com/grafana/grafana/pkg/infra/slugify"
|
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
|
alert_models "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
|
|
"github.com/grafana/grafana/pkg/util"
|
|
)
|
|
|
|
type AlertRuleProvisioner interface {
|
|
Provision(ctx context.Context, files []*AlertingFile) error
|
|
}
|
|
|
|
func NewAlertRuleProvisioner(
|
|
logger log.Logger,
|
|
dashboardService dashboards.DashboardService,
|
|
dashboardProvService dashboards.DashboardProvisioningService,
|
|
ruleService provisioning.AlertRuleService) AlertRuleProvisioner {
|
|
return &defaultAlertRuleProvisioner{
|
|
logger: logger,
|
|
dashboardService: dashboardService,
|
|
dashboardProvService: dashboardProvService,
|
|
ruleService: ruleService,
|
|
}
|
|
}
|
|
|
|
type defaultAlertRuleProvisioner struct {
|
|
logger log.Logger
|
|
dashboardService dashboards.DashboardService
|
|
dashboardProvService dashboards.DashboardProvisioningService
|
|
ruleService provisioning.AlertRuleService
|
|
}
|
|
|
|
func (prov *defaultAlertRuleProvisioner) Provision(ctx context.Context,
|
|
files []*AlertingFile) error {
|
|
for _, file := range files {
|
|
for _, group := range file.Groups {
|
|
folderUID, err := prov.getOrCreateFolderUID(ctx, group.FolderTitle, group.OrgID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
prov.logger.Debug("provisioning alert rule group",
|
|
"org", group.OrgID,
|
|
"folder", group.FolderTitle,
|
|
"folderUID", folderUID,
|
|
"name", group.Title)
|
|
for _, rule := range group.Rules {
|
|
rule.NamespaceUID = folderUID
|
|
rule.RuleGroup = group.Title
|
|
err = prov.provisionRule(ctx, group.OrgID, rule)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
err = prov.ruleService.UpdateRuleGroup(ctx, group.OrgID, folderUID, group.Title, group.Interval)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for _, deleteRule := range file.DeleteRules {
|
|
err := prov.ruleService.DeleteAlertRule(ctx, deleteRule.OrgID,
|
|
deleteRule.UID, alert_models.ProvenanceFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (prov *defaultAlertRuleProvisioner) provisionRule(
|
|
ctx context.Context,
|
|
orgID int64,
|
|
rule alert_models.AlertRule) error {
|
|
prov.logger.Debug("provisioning alert rule", "uid", rule.UID, "org", rule.OrgID)
|
|
_, _, err := prov.ruleService.GetAlertRule(ctx, orgID, rule.UID)
|
|
if err != nil && !errors.Is(err, alert_models.ErrAlertRuleNotFound) {
|
|
return err
|
|
} else if err != nil {
|
|
prov.logger.Debug("creating rule", "uid", rule.UID, "org", rule.OrgID)
|
|
// 0 is passed as userID as then the quota logic will only check for
|
|
// the organization quota, as we don't have any user scope here.
|
|
_, err = prov.ruleService.CreateAlertRule(ctx, rule, alert_models.ProvenanceFile, 0)
|
|
} else {
|
|
prov.logger.Debug("updating rule", "uid", rule.UID, "org", rule.OrgID)
|
|
_, err = prov.ruleService.UpdateAlertRule(ctx, rule, alert_models.ProvenanceFile)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (prov *defaultAlertRuleProvisioner) getOrCreateFolderUID(
|
|
ctx context.Context, folderName string, orgID int64) (string, error) {
|
|
cmd := &dashboards.GetDashboardQuery{
|
|
Slug: slugify.Slugify(folderName),
|
|
OrgID: orgID,
|
|
}
|
|
cmdResult, err := prov.dashboardService.GetDashboard(ctx, cmd)
|
|
if err != nil && !errors.Is(err, dashboards.ErrDashboardNotFound) {
|
|
return "", err
|
|
}
|
|
|
|
// dashboard folder not found. create one.
|
|
if errors.Is(err, dashboards.ErrDashboardNotFound) {
|
|
dash := &dashboards.SaveDashboardDTO{}
|
|
dash.Dashboard = dashboards.NewDashboardFolder(folderName)
|
|
dash.Dashboard.IsFolder = true
|
|
dash.Overwrite = true
|
|
dash.OrgID = orgID
|
|
dash.Dashboard.SetUID(util.GenerateShortUID())
|
|
dbDash, err := prov.dashboardProvService.SaveFolderForProvisionedDashboards(ctx, dash)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return dbDash.UID, nil
|
|
}
|
|
|
|
if !cmdResult.IsFolder {
|
|
return "", fmt.Errorf("got invalid response. expected folder, found dashboard")
|
|
}
|
|
|
|
return cmdResult.UID, nil
|
|
}
|