mirror of
https://github.com/grafana/grafana.git
synced 2025-07-30 22:22:15 +08:00
Alerting: Add file provisioning for contact points (#51924)
This commit is contained in:

committed by
GitHub

parent
e791a4e576
commit
d9cace4dca
@ -101,7 +101,7 @@ func (hs *HTTPServer) AdminProvisioningReloadNotifications(c *models.ReqContext)
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) AdminProvisioningReloadAlerting(c *models.ReqContext) response.Response {
|
||||
err := hs.ProvisioningService.ProvisionAlertRules(c.Req.Context())
|
||||
err := hs.ProvisioningService.ProvisionAlerting(c.Req.Context())
|
||||
if err != nil {
|
||||
return response.Error(500, "", err)
|
||||
}
|
||||
|
@ -153,7 +153,7 @@ func TestAPI_AdminProvisioningReload_AccessControl(t *testing.T) {
|
||||
},
|
||||
url: "/api/admin/provisioning/alerting/reload",
|
||||
checkCall: func(mock provisioning.ProvisioningServiceMock) {
|
||||
assert.Len(t, mock.Calls.ProvisionAlertRules, 1)
|
||||
assert.Len(t, mock.Calls.ProvisionAlerting, 1)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -1,4 +1,4 @@
|
||||
package rules
|
||||
package alerting
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -22,34 +22,35 @@ func newRulesConfigReader(logger log.Logger) rulesConfigReader {
|
||||
}
|
||||
}
|
||||
|
||||
func (cr *rulesConfigReader) readConfig(ctx context.Context, path string) ([]*RuleFile, error) {
|
||||
var alertRulesFiles []*RuleFile
|
||||
cr.log.Debug("looking for alert rules provisioning files", "path", path)
|
||||
func (cr *rulesConfigReader) readConfig(ctx context.Context, path string) ([]*AlertingFile, error) {
|
||||
var alertFiles []*AlertingFile
|
||||
cr.log.Debug("looking for alerting provisioning files", "path", path)
|
||||
|
||||
files, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
cr.log.Error("can't read alert rules provisioning files from directory", "path", path, "error", err)
|
||||
return alertRulesFiles, nil
|
||||
cr.log.Error("can't read alerting provisioning files from directory", "path", path, "error", err)
|
||||
return alertFiles, nil
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
cr.log.Debug("parsing alert rules provisioning file", "path", path, "file.Name", file.Name())
|
||||
cr.log.Debug("parsing alerting provisioning file", "path", path, "file.Name", file.Name())
|
||||
if !cr.isYAML(file.Name()) && !cr.isJSON(file.Name()) {
|
||||
return nil, fmt.Errorf("file has invalid suffix '%s' (.yaml,.yml,.json accepted)", file.Name())
|
||||
}
|
||||
ruleFileV1, err := cr.parseConfig(path, file)
|
||||
alertFileV1, err := cr.parseConfig(path, file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failure to parse file %s: %w", file.Name(), err)
|
||||
}
|
||||
if ruleFileV1 != nil {
|
||||
ruleFile, err := ruleFileV1.MapToModel()
|
||||
if alertFileV1 != nil {
|
||||
alertFileV1.Filename = file.Name()
|
||||
alertFile, err := alertFileV1.MapToModel()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failure to map file %s: %w", alertFileV1.Filename, err)
|
||||
}
|
||||
alertRulesFiles = append(alertRulesFiles, &ruleFile)
|
||||
alertFiles = append(alertFiles, &alertFile)
|
||||
}
|
||||
}
|
||||
return alertRulesFiles, nil
|
||||
return alertFiles, nil
|
||||
}
|
||||
|
||||
func (cr *rulesConfigReader) isYAML(file string) bool {
|
||||
@ -60,7 +61,7 @@ func (cr *rulesConfigReader) isJSON(file string) bool {
|
||||
return strings.HasSuffix(file, ".json")
|
||||
}
|
||||
|
||||
func (cr *rulesConfigReader) parseConfig(path string, file fs.FileInfo) (*RuleFileV1, error) {
|
||||
func (cr *rulesConfigReader) parseConfig(path string, file fs.FileInfo) (*AlertingFileV1, error) {
|
||||
filename, _ := filepath.Abs(filepath.Join(path, file.Name()))
|
||||
// nolint:gosec
|
||||
// We can ignore the gosec G304 warning on this one because `filename` comes from ps.Cfg.ProvisioningPath
|
||||
@ -68,7 +69,7 @@ func (cr *rulesConfigReader) parseConfig(path string, file fs.FileInfo) (*RuleFi
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var cfg *RuleFileV1
|
||||
var cfg *AlertingFileV1
|
||||
err = yaml.Unmarshal(yamlFile, &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
103
pkg/services/provisioning/alerting/config_reader_test.go
Normal file
103
pkg/services/provisioning/alerting/config_reader_test.go
Normal file
@ -0,0 +1,103 @@
|
||||
package alerting
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
testFileBrokenYAML = "./testdata/common/broken-yaml"
|
||||
testFileEmptyFile = "./testdata/common/empty-file"
|
||||
testFileEmptyFolder = "./testdata/common/empty-folder"
|
||||
testFileSupportedFiletypes = "./testdata/common/supported-filetypes"
|
||||
testFileCorrectProperties = "./testdata/alert_rules/correct-properties"
|
||||
testFileCorrectPropertiesWithOrg = "./testdata/alert_rules/correct-properties-with-org"
|
||||
testFileMultipleRules = "./testdata/alert_rules/multiple-rules"
|
||||
testFileMultipleFiles = "./testdata/alert_rules/multiple-files"
|
||||
testFileCorrectProperties_cp = "./testdata/contact_points/correct-properties"
|
||||
testFileCorrectPropertiesWithOrg_cp = "./testdata/contact_points/correct-properties-with-org"
|
||||
testFileEmptyUID = "./testdata/contact_points/empty-uid"
|
||||
testFileMissingUID = "./testdata/contact_points/missing-uid"
|
||||
testFileWhitespaceUID = "./testdata/contact_points/whitespace-uid"
|
||||
testFileMultipleCps = "./testdata/contact_points/multiple-contact-points"
|
||||
)
|
||||
|
||||
func TestConfigReader(t *testing.T) {
|
||||
configReader := newRulesConfigReader(log.NewNopLogger())
|
||||
ctx := context.Background()
|
||||
t.Run("a broken YAML file should error", func(t *testing.T) {
|
||||
_, err := configReader.readConfig(ctx, testFileBrokenYAML)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("a rule file with correct properties should not error", func(t *testing.T) {
|
||||
ruleFiles, err := configReader.readConfig(ctx, testFileCorrectProperties)
|
||||
require.NoError(t, err)
|
||||
t.Run("when no organization is present it should be set to 1", func(t *testing.T) {
|
||||
require.Equal(t, int64(1), ruleFiles[0].Groups[0].Rules[0].OrgID)
|
||||
})
|
||||
})
|
||||
t.Run("a rule file with correct properties and specific org should not error", func(t *testing.T) {
|
||||
ruleFiles, err := configReader.readConfig(ctx, testFileCorrectPropertiesWithOrg)
|
||||
require.NoError(t, err)
|
||||
t.Run("when an organization is set it should not overwrite if with the default of 1", func(t *testing.T) {
|
||||
require.Equal(t, int64(1337), ruleFiles[0].Groups[0].Rules[0].OrgID)
|
||||
})
|
||||
})
|
||||
t.Run("an empty rule file should not make the config reader error", func(t *testing.T) {
|
||||
_, err := configReader.readConfig(ctx, testFileEmptyFile)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("an empty folder should not make the config reader error", func(t *testing.T) {
|
||||
_, err := configReader.readConfig(ctx, testFileEmptyFolder)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("the config reader should be able to read multiple files in the folder", func(t *testing.T) {
|
||||
ruleFiles, err := configReader.readConfig(ctx, testFileMultipleFiles)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, ruleFiles, 2)
|
||||
})
|
||||
t.Run("the config reader should be able to read multiple rule groups", func(t *testing.T) {
|
||||
ruleFiles, err := configReader.readConfig(ctx, testFileMultipleRules)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, ruleFiles[0].Groups, 2)
|
||||
})
|
||||
t.Run("the config reader should support .yaml,.yml and .json files", func(t *testing.T) {
|
||||
ruleFiles, err := configReader.readConfig(ctx, testFileSupportedFiletypes)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, ruleFiles, 3)
|
||||
})
|
||||
t.Run("a contact point file with correct properties should not error", func(t *testing.T) {
|
||||
file, err := configReader.readConfig(ctx, testFileCorrectProperties_cp)
|
||||
require.NoError(t, err)
|
||||
t.Run("when no organization is present it should be set to 1", func(t *testing.T) {
|
||||
require.Equal(t, int64(1), file[0].ContactPoints[0].OrgID)
|
||||
})
|
||||
})
|
||||
t.Run("a contact point file with correct properties and specific org should not error", func(t *testing.T) {
|
||||
file, err := configReader.readConfig(ctx, testFileCorrectPropertiesWithOrg_cp)
|
||||
require.NoError(t, err)
|
||||
t.Run("when an organization is set it should not overwrite if with the default of 1", func(t *testing.T) {
|
||||
require.Equal(t, int64(1337), file[0].ContactPoints[0].OrgID)
|
||||
})
|
||||
})
|
||||
t.Run("a contact point file with empty UID should fail", func(t *testing.T) {
|
||||
_, err := configReader.readConfig(ctx, testFileEmptyUID)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("a contact point file with missing UID should fail", func(t *testing.T) {
|
||||
_, err := configReader.readConfig(ctx, testFileMissingUID)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("a contact point file with whitespace UID should fail", func(t *testing.T) {
|
||||
_, err := configReader.readConfig(ctx, testFileWhitespaceUID)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("the config reader should be able to read a file with multiple contact points", func(t *testing.T) {
|
||||
file, err := configReader.readConfig(ctx, testFileMultipleCps)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, file[0].ContactPoints, 2)
|
||||
})
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
package alerting
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"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"
|
||||
)
|
||||
|
||||
type ContactPointProvisioner interface {
|
||||
Provision(ctx context.Context, files []*AlertingFile) error
|
||||
Unprovision(ctx context.Context, files []*AlertingFile) error
|
||||
}
|
||||
|
||||
type defaultContactPointProvisioner struct {
|
||||
logger log.Logger
|
||||
contactPointService provisioning.ContactPointService
|
||||
}
|
||||
|
||||
func NewContactPointProvisoner(logger log.Logger,
|
||||
contactPointService provisioning.ContactPointService) ContactPointProvisioner {
|
||||
return &defaultContactPointProvisioner{
|
||||
logger: logger,
|
||||
contactPointService: contactPointService,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *defaultContactPointProvisioner) Provision(ctx context.Context,
|
||||
files []*AlertingFile) error {
|
||||
cpsCache := map[int64][]definitions.EmbeddedContactPoint{}
|
||||
for _, file := range files {
|
||||
for _, contactPointsConfig := range file.ContactPoints {
|
||||
// check if we already fetched the contact points for this org.
|
||||
// if not we fetch them and populate the cache.
|
||||
if _, exists := cpsCache[contactPointsConfig.OrgID]; !exists {
|
||||
cps, err := c.contactPointService.GetContactPoints(ctx, provisioning.ContactPointQuery{
|
||||
OrgID: contactPointsConfig.OrgID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cpsCache[contactPointsConfig.OrgID] = cps
|
||||
}
|
||||
outer:
|
||||
for _, contactPoint := range contactPointsConfig.ContactPoints {
|
||||
for _, fetchedCP := range cpsCache[contactPointsConfig.OrgID] {
|
||||
if fetchedCP.UID == contactPoint.UID {
|
||||
err := c.contactPointService.UpdateContactPoint(ctx,
|
||||
contactPointsConfig.OrgID, contactPoint, models.ProvenanceFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
_, err := c.contactPointService.CreateContactPoint(ctx, contactPointsConfig.OrgID,
|
||||
contactPoint, models.ProvenanceFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *defaultContactPointProvisioner) Unprovision(ctx context.Context,
|
||||
files []*AlertingFile) error {
|
||||
for _, file := range files {
|
||||
for _, cp := range file.DeleteContactPoints {
|
||||
err := c.contactPointService.DeleteContactPoint(ctx, cp.OrgID, cp.UID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
104
pkg/services/provisioning/alerting/contact_point_types.go
Normal file
104
pkg/services/provisioning/alerting/contact_point_types.go
Normal file
@ -0,0 +1,104 @@
|
||||
package alerting
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"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/provisioning/values"
|
||||
)
|
||||
|
||||
type DeleteContactPointV1 struct {
|
||||
OrgID values.Int64Value `json:"orgId" yaml:"orgId"`
|
||||
UID values.StringValue `json:"uid" yaml:"uid"`
|
||||
}
|
||||
|
||||
func (v1 *DeleteContactPointV1) MapToModel() DeleteContactPoint {
|
||||
orgID := v1.OrgID.Value()
|
||||
if orgID < 1 {
|
||||
orgID = 1
|
||||
}
|
||||
return DeleteContactPoint{
|
||||
OrgID: orgID,
|
||||
UID: v1.UID.Value(),
|
||||
}
|
||||
}
|
||||
|
||||
type DeleteContactPoint struct {
|
||||
OrgID int64 `json:"orgId" yaml:"orgId"`
|
||||
UID string `json:"uid" yaml:"uid"`
|
||||
}
|
||||
|
||||
type ContactPointV1 struct {
|
||||
OrgID values.Int64Value `json:"orgId" yaml:"orgId"`
|
||||
Name values.StringValue `json:"name" yaml:"name"`
|
||||
Receivers []ReceiverV1 `json:"receivers" yaml:"receivers"`
|
||||
}
|
||||
|
||||
func (cpV1 *ContactPointV1) MapToModel() (ContactPoint, error) {
|
||||
contactPoint := ContactPoint{}
|
||||
orgID := cpV1.OrgID.Value()
|
||||
if orgID < 1 {
|
||||
orgID = 1
|
||||
}
|
||||
contactPoint.OrgID = orgID
|
||||
name := strings.TrimSpace(cpV1.Name.Value())
|
||||
if name == "" {
|
||||
return ContactPoint{}, fmt.Errorf("no name is set")
|
||||
}
|
||||
for _, receiverV1 := range cpV1.Receivers {
|
||||
embeddedCP, err := receiverV1.mapToModel(name)
|
||||
if err != nil {
|
||||
return ContactPoint{}, fmt.Errorf("%s: %w", name, err)
|
||||
}
|
||||
contactPoint.ContactPoints = append(contactPoint.ContactPoints, embeddedCP)
|
||||
}
|
||||
return contactPoint, nil
|
||||
}
|
||||
|
||||
type ContactPoint struct {
|
||||
OrgID int64 `json:"orgId" yaml:"orgId"`
|
||||
ContactPoints []definitions.EmbeddedContactPoint `json:"configs" yaml:"configs"`
|
||||
}
|
||||
|
||||
type ReceiverV1 struct {
|
||||
UID values.StringValue `json:"uid" yaml:"uid"`
|
||||
Type values.StringValue `json:"type" yaml:"type"`
|
||||
Settings values.JSONValue `json:"settings" yaml:"settings"`
|
||||
DisableResolveMessage values.BoolValue `json:"disableResolveMessage"`
|
||||
}
|
||||
|
||||
func (config *ReceiverV1) mapToModel(name string) (definitions.EmbeddedContactPoint, error) {
|
||||
uid := strings.TrimSpace(config.UID.Value())
|
||||
if uid == "" {
|
||||
return definitions.EmbeddedContactPoint{}, fmt.Errorf("no uid is set")
|
||||
}
|
||||
cpType := strings.TrimSpace(config.Type.Value())
|
||||
if cpType == "" {
|
||||
return definitions.EmbeddedContactPoint{}, fmt.Errorf("no type is set")
|
||||
}
|
||||
if len(config.Settings.Value()) == 0 {
|
||||
return definitions.EmbeddedContactPoint{}, fmt.Errorf("no settings are set")
|
||||
}
|
||||
settings := simplejson.NewFromAny(config.Settings.Raw)
|
||||
cp := definitions.EmbeddedContactPoint{
|
||||
UID: uid,
|
||||
Name: name,
|
||||
Type: cpType,
|
||||
DisableResolveMessage: config.DisableResolveMessage.Value(),
|
||||
Provenance: string(models.ProvenanceFile),
|
||||
Settings: settings,
|
||||
}
|
||||
// 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 {
|
||||
return fallback
|
||||
})
|
||||
if err != nil {
|
||||
return definitions.EmbeddedContactPoint{}, err
|
||||
}
|
||||
return cp, nil
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
package alerting
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/values"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func TestReceivers(t *testing.T) {
|
||||
t.Run("Valid config should not error on mapping", func(t *testing.T) {
|
||||
cp := validReceiverV1(t)
|
||||
_, err := cp.mapToModel("test")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("Invalid config should error on mapping", func(t *testing.T) {
|
||||
cp := validReceiverV1(t)
|
||||
var settings values.JSONValue
|
||||
err := yaml.Unmarshal([]byte(`{"not-valid": "http://test-url"}`), &settings)
|
||||
require.NoError(t, err)
|
||||
cp.Settings = settings
|
||||
_, err = cp.mapToModel("test")
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("Empty config should error on mapping", func(t *testing.T) {
|
||||
cp := validReceiverV1(t)
|
||||
var settings values.JSONValue
|
||||
err := yaml.Unmarshal([]byte(`{}`), &settings)
|
||||
require.NoError(t, err)
|
||||
cp.Settings = settings
|
||||
_, err = cp.mapToModel("test")
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("Missing UID should error on mapping", func(t *testing.T) {
|
||||
cp := validReceiverV1(t)
|
||||
var uid values.StringValue
|
||||
err := yaml.Unmarshal([]byte(""), &uid)
|
||||
require.NoError(t, err)
|
||||
cp.UID = uid
|
||||
_, err = cp.mapToModel("test")
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("Missing type should error on mapping", func(t *testing.T) {
|
||||
cp := validReceiverV1(t)
|
||||
var _type values.StringValue
|
||||
err := yaml.Unmarshal([]byte(""), &_type)
|
||||
require.NoError(t, err)
|
||||
cp.Type = _type
|
||||
_, err = cp.mapToModel("test")
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("Ivalid type should error on mapping", func(t *testing.T) {
|
||||
cp := validReceiverV1(t)
|
||||
var _type values.StringValue
|
||||
err := yaml.Unmarshal([]byte("some-type-that-does-not-exist"), &_type)
|
||||
require.NoError(t, err)
|
||||
cp.Type = _type
|
||||
_, err = cp.mapToModel("test")
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func validReceiverV1(t *testing.T) ReceiverV1 {
|
||||
t.Helper()
|
||||
var (
|
||||
uid values.StringValue
|
||||
_type values.StringValue
|
||||
settings values.JSONValue
|
||||
disableResolveMessage values.BoolValue
|
||||
)
|
||||
err := yaml.Unmarshal([]byte("my_uid"), &uid)
|
||||
require.NoError(t, err)
|
||||
err = yaml.Unmarshal([]byte("prometheus-alertmanager"), &_type)
|
||||
require.NoError(t, err)
|
||||
err = yaml.Unmarshal([]byte(`{"url": "http://test-url"}`), &settings)
|
||||
require.NoError(t, err)
|
||||
err = yaml.Unmarshal([]byte("false"), &disableResolveMessage)
|
||||
require.NoError(t, err)
|
||||
return ReceiverV1{
|
||||
UID: uid,
|
||||
Type: _type,
|
||||
Settings: settings,
|
||||
DisableResolveMessage: disableResolveMessage,
|
||||
}
|
||||
}
|
50
pkg/services/provisioning/alerting/provisioner.go
Normal file
50
pkg/services/provisioning/alerting/provisioner.go
Normal file
@ -0,0 +1,50 @@
|
||||
package alerting
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
|
||||
)
|
||||
|
||||
type ProvisionerConfig struct {
|
||||
Path string
|
||||
DashboardService dashboards.DashboardService
|
||||
DashboardProvService dashboards.DashboardProvisioningService
|
||||
RuleService provisioning.AlertRuleService
|
||||
ContactPointService provisioning.ContactPointService
|
||||
}
|
||||
|
||||
func Provision(ctx context.Context, cfg ProvisionerConfig) error {
|
||||
logger := log.New("provisioning.alerting")
|
||||
cfgReader := newRulesConfigReader(logger)
|
||||
files, err := cfgReader.readConfig(ctx, cfg.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Info("starting to provision alerting")
|
||||
logger.Debug("read all alerting files", "file_count", len(files))
|
||||
ruleProvisioner := NewAlertRuleProvisioner(
|
||||
logger,
|
||||
cfg.DashboardService,
|
||||
cfg.DashboardProvService,
|
||||
cfg.RuleService)
|
||||
err = ruleProvisioner.Provision(ctx, files)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cpProvisioner := NewContactPointProvisoner(logger, cfg.ContactPointService)
|
||||
err = cpProvisioner.Provision(ctx, files)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: provision notificiation policy in between so that when applying it
|
||||
// new objects already exists and old ones are still there
|
||||
err = cpProvisioner.Unprovision(ctx, files)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Info("finished to provision alerting")
|
||||
return nil
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
testFileBrokenYAML = "./testdata/broken-yaml"
|
||||
testFileCorrectProperties = "./testdata/correct-properties"
|
||||
testFileCorrectPropertiesWithOrg = "./testdata/correct-properties-with-org"
|
||||
testFileEmptyFile = "./testdata/empty-file"
|
||||
testFileEmptyFolder = "./testdata/empty-folder"
|
||||
testFileMultipleRules = "./testdata/multiple-rules"
|
||||
testFileMultipleFiles = "./testdata/multiple-files"
|
||||
testFileSupportedFiletypes = "./testdata/supported-filetypes"
|
||||
)
|
||||
|
||||
func TestConfigReader(t *testing.T) {
|
||||
configReader := newRulesConfigReader(log.NewNopLogger())
|
||||
ctx := context.Background()
|
||||
t.Run("a broken YAML file should error", func(t *testing.T) {
|
||||
_, err := configReader.readConfig(ctx, testFileBrokenYAML)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("a rule file with correct properties should not error", func(t *testing.T) {
|
||||
ruleFiles, err := configReader.readConfig(ctx, testFileCorrectProperties)
|
||||
require.NoError(t, err)
|
||||
t.Run("when no organization is present it should be set to 1", func(t *testing.T) {
|
||||
require.Equal(t, int64(1), ruleFiles[0].Groups[0].Rules[0].OrgID)
|
||||
})
|
||||
})
|
||||
t.Run("a rule file with correct properties and specific org should not error", func(t *testing.T) {
|
||||
ruleFiles, err := configReader.readConfig(ctx, testFileCorrectPropertiesWithOrg)
|
||||
require.NoError(t, err)
|
||||
t.Run("when an organization is set it should not overwrite if with the default of 1", func(t *testing.T) {
|
||||
require.Equal(t, int64(1337), ruleFiles[0].Groups[0].Rules[0].OrgID)
|
||||
})
|
||||
})
|
||||
t.Run("an empty rule file should not make the config reader error", func(t *testing.T) {
|
||||
_, err := configReader.readConfig(ctx, testFileEmptyFile)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("an empty folder should not make the config reader error", func(t *testing.T) {
|
||||
_, err := configReader.readConfig(ctx, testFileEmptyFolder)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("the config reader should be able to read multiple files in the folder", func(t *testing.T) {
|
||||
ruleFiles, err := configReader.readConfig(ctx, testFileMultipleFiles)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, ruleFiles, 2)
|
||||
})
|
||||
t.Run("the config reader should be able to read multiple rule groups", func(t *testing.T) {
|
||||
ruleFiles, err := configReader.readConfig(ctx, testFileMultipleRules)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, ruleFiles[0].Groups, 2)
|
||||
})
|
||||
t.Run("the config reader should support .yaml,.yml and .json files", func(t *testing.T) {
|
||||
ruleFiles, err := configReader.readConfig(ctx, testFileSupportedFiletypes)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, ruleFiles, 3)
|
||||
})
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package rules
|
||||
package alerting
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -14,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
type AlertRuleProvisioner interface {
|
||||
Provision(ctx context.Context, path string) error
|
||||
Provision(ctx context.Context, files []*AlertingFile) error
|
||||
}
|
||||
|
||||
func NewAlertRuleProvisioner(
|
||||
@ -24,7 +24,6 @@ func NewAlertRuleProvisioner(
|
||||
ruleService provisioning.AlertRuleService) AlertRuleProvisioner {
|
||||
return &defaultAlertRuleProvisioner{
|
||||
logger: logger,
|
||||
cfgReader: newRulesConfigReader(logger),
|
||||
dashboardService: dashboardService,
|
||||
dashboardProvService: dashboardProvService,
|
||||
ruleService: ruleService,
|
||||
@ -33,47 +32,14 @@ func NewAlertRuleProvisioner(
|
||||
|
||||
type defaultAlertRuleProvisioner struct {
|
||||
logger log.Logger
|
||||
cfgReader rulesConfigReader
|
||||
dashboardService dashboards.DashboardService
|
||||
dashboardProvService dashboards.DashboardProvisioningService
|
||||
ruleService provisioning.AlertRuleService
|
||||
}
|
||||
|
||||
func Provision(
|
||||
ctx context.Context,
|
||||
path string,
|
||||
dashboardService dashboards.DashboardService,
|
||||
dashboardProvisioningService dashboards.DashboardProvisioningService,
|
||||
ruleService provisioning.AlertRuleService,
|
||||
) error {
|
||||
ruleProvisioner := NewAlertRuleProvisioner(
|
||||
log.New("provisioning.alerting"),
|
||||
dashboardService,
|
||||
dashboardProvisioningService,
|
||||
ruleService,
|
||||
)
|
||||
return ruleProvisioner.Provision(ctx, path)
|
||||
}
|
||||
|
||||
func (prov *defaultAlertRuleProvisioner) Provision(ctx context.Context,
|
||||
path string) error {
|
||||
prov.logger.Info("starting to provision the alert rules")
|
||||
ruleFiles, err := prov.cfgReader.readConfig(ctx, path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read alert rules files: %w", err)
|
||||
}
|
||||
prov.logger.Debug("read all alert rules files", "file_count", len(ruleFiles))
|
||||
err = prov.provsionRuleFiles(ctx, ruleFiles)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to provision alert rules: %w", err)
|
||||
}
|
||||
prov.logger.Info("finished to provision the alert rules")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (prov *defaultAlertRuleProvisioner) provsionRuleFiles(ctx context.Context,
|
||||
ruleFiles []*RuleFile) error {
|
||||
for _, file := range ruleFiles {
|
||||
files []*AlertingFile) error {
|
||||
for _, file := range files {
|
||||
for _, group := range file.Groups {
|
||||
folderUID, err := prov.getOrCreateFolderUID(ctx, group.Folder, group.OrgID)
|
||||
if err != nil {
|
@ -1,4 +1,4 @@
|
||||
package rules
|
||||
package alerting
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@ -11,46 +11,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/values"
|
||||
)
|
||||
|
||||
type configVersion struct {
|
||||
APIVersion values.Int64Value `json:"apiVersion" yaml:"apiVersion"`
|
||||
}
|
||||
|
||||
type RuleFile struct {
|
||||
configVersion
|
||||
Groups []AlertRuleGroup
|
||||
DeleteRules []RuleDelete
|
||||
}
|
||||
|
||||
type RuleFileV1 struct {
|
||||
configVersion
|
||||
Groups []AlertRuleGroupV1 `json:"groups" yaml:"groups"`
|
||||
DeleteRules []RuleDeleteV1 `json:"deleteRules" yaml:"deleteRules"`
|
||||
}
|
||||
|
||||
func (ruleFileV1 *RuleFileV1) MapToModel() (RuleFile, error) {
|
||||
ruleFile := RuleFile{}
|
||||
ruleFile.configVersion = ruleFileV1.configVersion
|
||||
for _, groupV1 := range ruleFileV1.Groups {
|
||||
group, err := groupV1.mapToModel()
|
||||
if err != nil {
|
||||
return RuleFile{}, err
|
||||
}
|
||||
ruleFile.Groups = append(ruleFile.Groups, group)
|
||||
}
|
||||
for _, ruleDeleteV1 := range ruleFileV1.DeleteRules {
|
||||
orgID := ruleDeleteV1.OrgID.Value()
|
||||
if orgID < 1 {
|
||||
orgID = 1
|
||||
}
|
||||
ruleDelete := RuleDelete{
|
||||
UID: ruleDeleteV1.UID.Value(),
|
||||
OrgID: orgID,
|
||||
}
|
||||
ruleFile.DeleteRules = append(ruleFile.DeleteRules, ruleDelete)
|
||||
}
|
||||
return ruleFile, nil
|
||||
}
|
||||
|
||||
type RuleDelete struct {
|
||||
UID string
|
||||
OrgID int64
|
||||
@ -69,7 +29,7 @@ type AlertRuleGroupV1 struct {
|
||||
Rules []AlertRuleV1 `json:"rules" yaml:"rules"`
|
||||
}
|
||||
|
||||
func (ruleGroupV1 *AlertRuleGroupV1) mapToModel() (AlertRuleGroup, error) {
|
||||
func (ruleGroupV1 *AlertRuleGroupV1) MapToModel() (AlertRuleGroup, error) {
|
||||
ruleGroup := AlertRuleGroup{}
|
||||
ruleGroup.Name = ruleGroupV1.Name.Value()
|
||||
if strings.TrimSpace(ruleGroup.Name) == "" {
|
@ -1,4 +1,4 @@
|
||||
package rules
|
||||
package alerting
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@ -12,7 +12,7 @@ import (
|
||||
func TestRuleGroup(t *testing.T) {
|
||||
t.Run("a valid rule group should not error", func(t *testing.T) {
|
||||
rg := validRuleGroupV1(t)
|
||||
_, err := rg.mapToModel()
|
||||
_, err := rg.MapToModel()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("a rule group with out a name should error", func(t *testing.T) {
|
||||
@ -21,7 +21,7 @@ func TestRuleGroup(t *testing.T) {
|
||||
err := yaml.Unmarshal([]byte(""), &name)
|
||||
require.NoError(t, err)
|
||||
rg.Name = name
|
||||
_, err = rg.mapToModel()
|
||||
_, err = rg.MapToModel()
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("a rule group with out a folder should error", func(t *testing.T) {
|
||||
@ -30,7 +30,7 @@ func TestRuleGroup(t *testing.T) {
|
||||
err := yaml.Unmarshal([]byte(""), &folder)
|
||||
require.NoError(t, err)
|
||||
rg.Folder = folder
|
||||
_, err = rg.mapToModel()
|
||||
_, err = rg.MapToModel()
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("a rule group with out an interval should error", func(t *testing.T) {
|
||||
@ -39,7 +39,7 @@ func TestRuleGroup(t *testing.T) {
|
||||
err := yaml.Unmarshal([]byte(""), &interval)
|
||||
require.NoError(t, err)
|
||||
rg.Interval = interval
|
||||
_, err = rg.mapToModel()
|
||||
_, err = rg.MapToModel()
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("a rule group with an invalid interval should error", func(t *testing.T) {
|
||||
@ -48,13 +48,13 @@ func TestRuleGroup(t *testing.T) {
|
||||
err := yaml.Unmarshal([]byte("10x"), &interval)
|
||||
require.NoError(t, err)
|
||||
rg.Interval = interval
|
||||
_, err = rg.mapToModel()
|
||||
_, err = rg.MapToModel()
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("a rule group with an empty org id should default to 1", func(t *testing.T) {
|
||||
rg := validRuleGroupV1(t)
|
||||
rg.OrgID = values.Int64Value{}
|
||||
rgMapped, err := rg.mapToModel()
|
||||
rgMapped, err := rg.MapToModel()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(1), rgMapped.OrgID)
|
||||
})
|
||||
@ -64,7 +64,7 @@ func TestRuleGroup(t *testing.T) {
|
||||
err := yaml.Unmarshal([]byte("-1"), &orgID)
|
||||
require.NoError(t, err)
|
||||
rg.OrgID = orgID
|
||||
rgMapped, err := rg.mapToModel()
|
||||
rgMapped, err := rg.MapToModel()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(1), rgMapped.OrgID)
|
||||
})
|
@ -0,0 +1,9 @@
|
||||
apiVersion: 1
|
||||
contactPoints:
|
||||
- name: cp_1
|
||||
orgId: 1337
|
||||
receivers:
|
||||
- uid: first_uid
|
||||
type: prometheus-alertmanager
|
||||
settings:
|
||||
url: http://test:9000
|
@ -0,0 +1,8 @@
|
||||
apiVersion: 1
|
||||
contactPoints:
|
||||
- name: cp_1
|
||||
receivers:
|
||||
- uid: first_uid
|
||||
type: prometheus-alertmanager
|
||||
settings:
|
||||
url: http://test:9000
|
8
pkg/services/provisioning/alerting/testdata/contact_points/empty-uid/contact_points.yml
vendored
Normal file
8
pkg/services/provisioning/alerting/testdata/contact_points/empty-uid/contact_points.yml
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
apiVersion: 1
|
||||
contactPoints:
|
||||
- name: cp_1
|
||||
receivers:
|
||||
- uid: ""
|
||||
type: prometheus-alertmanager
|
||||
settings:
|
||||
url: http://test:9000
|
7
pkg/services/provisioning/alerting/testdata/contact_points/missing-uid/contact_points.yml
vendored
Normal file
7
pkg/services/provisioning/alerting/testdata/contact_points/missing-uid/contact_points.yml
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
apiVersion: 1
|
||||
contactPoints:
|
||||
- name: cp_1
|
||||
receivers:
|
||||
- type: prometheus-alertmanager
|
||||
settings:
|
||||
url: http://test:9000
|
@ -0,0 +1,14 @@
|
||||
apiVersion: 1
|
||||
contactPoints:
|
||||
- name: cp_1
|
||||
receivers:
|
||||
- uid: first_uid
|
||||
type: prometheus-alertmanager
|
||||
settings:
|
||||
url: http://test:9000
|
||||
- name: cp_2
|
||||
receivers:
|
||||
- uid: second_uid
|
||||
type: prometheus-alertmanager
|
||||
settings:
|
||||
url: http://test:9000
|
8
pkg/services/provisioning/alerting/testdata/contact_points/whitespace-uid/contact_points.yml
vendored
Normal file
8
pkg/services/provisioning/alerting/testdata/contact_points/whitespace-uid/contact_points.yml
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
apiVersion: 1
|
||||
contactPoints:
|
||||
- name: cp_1
|
||||
receivers:
|
||||
- uid: " "
|
||||
type: prometheus-alertmanager
|
||||
settings:
|
||||
url: http://test:9000
|
77
pkg/services/provisioning/alerting/types.go
Normal file
77
pkg/services/provisioning/alerting/types.go
Normal file
@ -0,0 +1,77 @@
|
||||
package alerting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/values"
|
||||
)
|
||||
|
||||
type configVersion struct {
|
||||
APIVersion values.Int64Value `json:"apiVersion" yaml:"apiVersion"`
|
||||
}
|
||||
|
||||
type AlertingFile struct {
|
||||
configVersion
|
||||
Groups []AlertRuleGroup
|
||||
DeleteRules []RuleDelete
|
||||
ContactPoints []ContactPoint
|
||||
DeleteContactPoints []DeleteContactPoint
|
||||
}
|
||||
|
||||
type AlertingFileV1 struct {
|
||||
configVersion
|
||||
Filename string
|
||||
Groups []AlertRuleGroupV1 `json:"groups" yaml:"groups"`
|
||||
DeleteRules []RuleDeleteV1 `json:"deleteRules" yaml:"deleteRules"`
|
||||
ContactPoints []ContactPointV1 `json:"contactPoints" yaml:"contactPoints"`
|
||||
DeleteContactPoints []DeleteContactPointV1 `json:"deleteContactPoints" yaml:"deleteContactPoints"`
|
||||
}
|
||||
|
||||
func (fileV1 *AlertingFileV1) MapToModel() (AlertingFile, error) {
|
||||
alertingFile := AlertingFile{}
|
||||
err := fileV1.mapRules(&alertingFile)
|
||||
if err != nil {
|
||||
return AlertingFile{}, fmt.Errorf("failure parsing rules: %w", err)
|
||||
}
|
||||
err = fileV1.mapContactPoint(&alertingFile)
|
||||
if err != nil {
|
||||
return AlertingFile{}, fmt.Errorf("failure parsing contact points: %w", err)
|
||||
}
|
||||
return alertingFile, nil
|
||||
}
|
||||
|
||||
func (fileV1 *AlertingFileV1) mapContactPoint(alertingFile *AlertingFile) error {
|
||||
for _, dcp := range fileV1.DeleteContactPoints {
|
||||
alertingFile.DeleteContactPoints = append(alertingFile.DeleteContactPoints, dcp.MapToModel())
|
||||
}
|
||||
for _, contactPointV1 := range fileV1.ContactPoints {
|
||||
contactPoint, err := contactPointV1.MapToModel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
alertingFile.ContactPoints = append(alertingFile.ContactPoints, contactPoint)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fileV1 *AlertingFileV1) mapRules(alertingFile *AlertingFile) error {
|
||||
for _, groupV1 := range fileV1.Groups {
|
||||
group, err := groupV1.MapToModel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
alertingFile.Groups = append(alertingFile.Groups, group)
|
||||
}
|
||||
for _, ruleDeleteV1 := range fileV1.DeleteRules {
|
||||
orgID := ruleDeleteV1.OrgID.Value()
|
||||
if orgID < 1 {
|
||||
orgID = 1
|
||||
}
|
||||
ruleDelete := RuleDelete{
|
||||
UID: ruleDeleteV1.UID.Value(),
|
||||
OrgID: orgID,
|
||||
}
|
||||
alertingFile.DeleteRules = append(alertingFile.DeleteRules, ruleDelete)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -19,7 +19,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
"github.com/grafana/grafana/pkg/services/notifications"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsettings"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/alerting/rules"
|
||||
prov_alerting "github.com/grafana/grafana/pkg/services/provisioning/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/notifiers"
|
||||
@ -27,6 +27,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/utils"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/services/searchV2"
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
@ -47,6 +48,7 @@ func ProvideService(
|
||||
pluginSettings pluginsettings.Service,
|
||||
searchService searchV2.SearchService,
|
||||
quotaService quota.Service,
|
||||
secrectService secrets.Service,
|
||||
) (*ProvisioningServiceImpl, error) {
|
||||
s := &ProvisioningServiceImpl{
|
||||
Cfg: cfg,
|
||||
@ -59,6 +61,7 @@ func ProvideService(
|
||||
provisionNotifiers: notifiers.Provision,
|
||||
provisionDatasources: datasources.Provision,
|
||||
provisionPlugins: plugins.Provision,
|
||||
provisionAlerting: prov_alerting.Provision,
|
||||
dashboardProvisioningService: dashboardProvisioningService,
|
||||
dashboardService: dashboardService,
|
||||
datasourceService: datasourceService,
|
||||
@ -67,6 +70,7 @@ func ProvideService(
|
||||
pluginsSettings: pluginSettings,
|
||||
searchService: searchService,
|
||||
quotaService: quotaService,
|
||||
secretService: secrectService,
|
||||
log: log.New("provisioning"),
|
||||
}
|
||||
return s, nil
|
||||
@ -79,7 +83,7 @@ type ProvisioningService interface {
|
||||
ProvisionPlugins(ctx context.Context) error
|
||||
ProvisionNotifications(ctx context.Context) error
|
||||
ProvisionDashboards(ctx context.Context) error
|
||||
ProvisionAlertRules(ctx context.Context) error
|
||||
ProvisionAlerting(ctx context.Context) error
|
||||
GetDashboardProvisionerResolvedPath(name string) string
|
||||
GetAllowUIUpdatesFromConfig(name string) bool
|
||||
}
|
||||
@ -93,7 +97,6 @@ func NewProvisioningServiceImpl() *ProvisioningServiceImpl {
|
||||
provisionNotifiers: notifiers.Provision,
|
||||
provisionDatasources: datasources.Provision,
|
||||
provisionPlugins: plugins.Provision,
|
||||
provisionRules: rules.Provision,
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,7 +130,7 @@ type ProvisioningServiceImpl struct {
|
||||
provisionNotifiers func(context.Context, string, notifiers.Manager, notifiers.SQLStore, encryption.Internal, *notifications.NotificationService) error
|
||||
provisionDatasources func(context.Context, string, datasources.Store, datasources.CorrelationsStore, utils.OrgStore) error
|
||||
provisionPlugins func(context.Context, string, plugins.Store, plugifaces.Store, pluginsettings.Service) error
|
||||
provisionRules func(context.Context, string, dashboardservice.DashboardService, dashboardservice.DashboardProvisioningService, provisioning.AlertRuleService) error
|
||||
provisionAlerting func(context.Context, prov_alerting.ProvisionerConfig) error
|
||||
mutex sync.Mutex
|
||||
dashboardProvisioningService dashboardservice.DashboardProvisioningService
|
||||
dashboardService dashboardservice.DashboardService
|
||||
@ -137,6 +140,7 @@ type ProvisioningServiceImpl struct {
|
||||
pluginsSettings pluginsettings.Service
|
||||
searchService searchV2.SearchService
|
||||
quotaService quota.Service
|
||||
secretService secrets.Service
|
||||
}
|
||||
|
||||
func (ps *ProvisioningServiceImpl) RunInitProvisioners(ctx context.Context) error {
|
||||
@ -155,7 +159,7 @@ func (ps *ProvisioningServiceImpl) RunInitProvisioners(ctx context.Context) erro
|
||||
return err
|
||||
}
|
||||
|
||||
err = ps.ProvisionAlertRules(ctx)
|
||||
err = ps.ProvisionAlerting(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -248,8 +252,8 @@ func (ps *ProvisioningServiceImpl) ProvisionDashboards(ctx context.Context) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ps *ProvisioningServiceImpl) ProvisionAlertRules(ctx context.Context) error {
|
||||
alertRulesPath := filepath.Join(ps.Cfg.ProvisioningPath, "alerting")
|
||||
func (ps *ProvisioningServiceImpl) ProvisionAlerting(ctx context.Context) error {
|
||||
alertingPath := filepath.Join(ps.Cfg.ProvisioningPath, "alerting")
|
||||
st := store.DBstore{
|
||||
Cfg: ps.Cfg.UnifiedAlerting,
|
||||
SQLStore: ps.SQLStore,
|
||||
@ -266,8 +270,16 @@ func (ps *ProvisioningServiceImpl) ProvisionAlertRules(ctx context.Context) erro
|
||||
int64(ps.Cfg.UnifiedAlerting.DefaultRuleEvaluationInterval.Seconds()),
|
||||
int64(ps.Cfg.UnifiedAlerting.BaseInterval.Seconds()),
|
||||
ps.log)
|
||||
return rules.Provision(ctx, alertRulesPath, ps.dashboardService,
|
||||
ps.dashboardProvisioningService, *ruleService)
|
||||
contactPointService := provisioning.NewContactPointService(&st, ps.secretService,
|
||||
st, ps.SQLStore, ps.log)
|
||||
cfg := prov_alerting.ProvisionerConfig{
|
||||
Path: alertingPath,
|
||||
RuleService: *ruleService,
|
||||
DashboardService: ps.dashboardService,
|
||||
DashboardProvService: ps.dashboardProvisioningService,
|
||||
ContactPointService: *contactPointService,
|
||||
}
|
||||
return ps.provisionAlerting(ctx, cfg)
|
||||
}
|
||||
|
||||
func (ps *ProvisioningServiceImpl) GetDashboardProvisionerResolvedPath(name string) string {
|
||||
|
@ -8,7 +8,7 @@ type Calls struct {
|
||||
ProvisionPlugins []interface{}
|
||||
ProvisionNotifications []interface{}
|
||||
ProvisionDashboards []interface{}
|
||||
ProvisionAlertRules []interface{}
|
||||
ProvisionAlerting []interface{}
|
||||
GetDashboardProvisionerResolvedPath []interface{}
|
||||
GetAllowUIUpdatesFromConfig []interface{}
|
||||
Run []interface{}
|
||||
@ -72,8 +72,8 @@ func (mock *ProvisioningServiceMock) ProvisionDashboards(ctx context.Context) er
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mock *ProvisioningServiceMock) ProvisionAlertRules(ctx context.Context) error {
|
||||
mock.Calls.ProvisionAlertRules = append(mock.Calls.ProvisionAlertRules, nil)
|
||||
func (mock *ProvisioningServiceMock) ProvisionAlerting(ctx context.Context) error {
|
||||
mock.Calls.ProvisionAlerting = append(mock.Calls.ProvisionAlerting, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user