Alerting: Add file provisioning for contact points (#51924)
This commit is contained in:
1 parent
e791a4e576
commit
d9cace4dca
31 files changed
+611
-184
No files matched your search
@@ -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)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
+17
-16
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
+4
-38
@@ -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 {
|
||||
+2
-42
@@ -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) == "" {
|
||||
+8
-8
@@ -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)
|
||||
})
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+9
@@ -0,0 +1,9 @@
|
||||
apiVersion: 1
|
||||
contactPoints:
|
||||
- name: cp_1
|
||||
orgId: 1337
|
||||
receivers:
|
||||
- uid: first_uid
|
||||
type: prometheus-alertmanager
|
||||
settings:
|
||||
url: http://test:9000
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
apiVersion: 1
|
||||
contactPoints:
|
||||
- name: cp_1
|
||||
receivers:
|
||||
- uid: first_uid
|
||||
type: prometheus-alertmanager
|
||||
settings:
|
||||
url: http://test:9000
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
apiVersion: 1
|
||||
contactPoints:
|
||||
- name: cp_1
|
||||
receivers:
|
||||
- uid: ""
|
||||
type: prometheus-alertmanager
|
||||
settings:
|
||||
url: http://test:9000
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
apiVersion: 1
|
||||
contactPoints:
|
||||
- name: cp_1
|
||||
receivers:
|
||||
- type: prometheus-alertmanager
|
||||
settings:
|
||||
url: http://test:9000
|
||||
+14
@@ -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
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
apiVersion: 1
|
||||
contactPoints:
|
||||
- name: cp_1
|
||||
receivers:
|
||||
- uid: " "
|
||||
type: prometheus-alertmanager
|
||||
settings:
|
||||
url: http://test:9000
|
||||
@@ -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