mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Support recording rule struct in provisioning API (#87849)
* Support record struct in provisioning API * Update api spec * Use record field * Restrict API endpoints following toggle * Fix swagger spec * Add recording rule validation to store validator
This commit is contained in:
parent
808cf75ff8
commit
d359591dac
@ -164,6 +164,8 @@ func (api *API) RegisterAPIEndpoints(m *metrics.API) {
|
|||||||
templates: api.Templates,
|
templates: api.Templates,
|
||||||
muteTimings: api.MuteTimings,
|
muteTimings: api.MuteTimings,
|
||||||
alertRules: api.AlertRules,
|
alertRules: api.AlertRules,
|
||||||
|
// XXX: Used to flag recording rules, remove when FT is removed
|
||||||
|
featureManager: api.FeatureManager,
|
||||||
}), m)
|
}), m)
|
||||||
|
|
||||||
api.RegisterHistoryApiEndpoints(NewStateHistoryApi(&HistorySrv{
|
api.RegisterHistoryApiEndpoints(NewStateHistoryApi(&HistorySrv{
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||||
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/folder"
|
"github.com/grafana/grafana/pkg/services/folder"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/api/hcl"
|
"github.com/grafana/grafana/pkg/services/ngalert/api/hcl"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
@ -30,6 +31,9 @@ type ProvisioningSrv struct {
|
|||||||
muteTimings MuteTimingService
|
muteTimings MuteTimingService
|
||||||
alertRules AlertRuleService
|
alertRules AlertRuleService
|
||||||
folderSvc folder.Service
|
folderSvc folder.Service
|
||||||
|
|
||||||
|
// XXX: Used to flag recording rules, remove when FT is removed
|
||||||
|
featureManager featuremgmt.FeatureToggles
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContactPointService interface {
|
type ContactPointService interface {
|
||||||
@ -335,6 +339,15 @@ func (srv *ProvisioningSrv) RoutePostAlertRule(c *contextmodel.ReqContext, ar de
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrResp(http.StatusBadRequest, err, "")
|
return ErrResp(http.StatusBadRequest, err, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if upstreamModel.IsRecordingRule() && !srv.featureManager.IsEnabledGlobally(featuremgmt.FlagGrafanaManagedRecordingRules) {
|
||||||
|
return ErrResp(
|
||||||
|
http.StatusBadRequest,
|
||||||
|
fmt.Errorf("%w: recording rules cannot be created on this instance", alerting_models.ErrAlertRuleFailedValidation),
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
provenance := determineProvenance(c)
|
provenance := determineProvenance(c)
|
||||||
createdAlertRule, err := srv.alertRules.CreateAlertRule(c.Req.Context(), c.SignedInUser, upstreamModel, alerting_models.Provenance(provenance))
|
createdAlertRule, err := srv.alertRules.CreateAlertRule(c.Req.Context(), c.SignedInUser, upstreamModel, alerting_models.Provenance(provenance))
|
||||||
if errors.Is(err, alerting_models.ErrAlertRuleFailedValidation) {
|
if errors.Is(err, alerting_models.ErrAlertRuleFailedValidation) {
|
||||||
@ -362,6 +375,15 @@ func (srv *ProvisioningSrv) RoutePutAlertRule(c *contextmodel.ReqContext, ar def
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
ErrResp(http.StatusBadRequest, err, "")
|
ErrResp(http.StatusBadRequest, err, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if updated.IsRecordingRule() && !srv.featureManager.IsEnabledGlobally(featuremgmt.FlagGrafanaManagedRecordingRules) {
|
||||||
|
return ErrResp(
|
||||||
|
http.StatusBadRequest,
|
||||||
|
fmt.Errorf("%w: recording rules cannot be created on this instance", alerting_models.ErrAlertRuleFailedValidation),
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
updated.OrgID = c.SignedInUser.GetOrgID()
|
updated.OrgID = c.SignedInUser.GetOrgID()
|
||||||
updated.UID = UID
|
updated.UID = UID
|
||||||
provenance := determineProvenance(c)
|
provenance := determineProvenance(c)
|
||||||
|
@ -406,6 +406,99 @@ func TestProvisioningApi(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("recording rules", func(t *testing.T) {
|
||||||
|
t.Run("are enabled", func(t *testing.T) {
|
||||||
|
env := createTestEnv(t, testConfig)
|
||||||
|
env.features = featuremgmt.WithFeatures(featuremgmt.FlagGrafanaManagedRecordingRules)
|
||||||
|
|
||||||
|
t.Run("POST returns 201", func(t *testing.T) {
|
||||||
|
sut := createProvisioningSrvSutFromEnv(t, &env)
|
||||||
|
rc := createTestRequestCtx()
|
||||||
|
rule := createTestRecordingRule("rule", 1)
|
||||||
|
|
||||||
|
response := sut.RoutePostAlertRule(&rc, rule)
|
||||||
|
|
||||||
|
require.Equal(t, 201, response.Status())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("PUT returns 200", func(t *testing.T) {
|
||||||
|
sut := createProvisioningSrvSutFromEnv(t, &env)
|
||||||
|
uid := util.GenerateShortUID()
|
||||||
|
rule := createTestAlertRule("rule", 3)
|
||||||
|
rule.UID = uid
|
||||||
|
|
||||||
|
_, err := sut.folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
|
||||||
|
UID: rule.FolderUID,
|
||||||
|
Title: "Folder Title",
|
||||||
|
OrgID: rule.OrgID,
|
||||||
|
SignedInUser: &user.SignedInUser{OrgID: rule.OrgID},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
insertRuleInOrg(t, sut, rule, 3)
|
||||||
|
|
||||||
|
// make rule a recording rule
|
||||||
|
rule.Record = &definitions.Record{
|
||||||
|
Metric: "test_metric",
|
||||||
|
From: "A",
|
||||||
|
}
|
||||||
|
|
||||||
|
rc := createTestRequestCtx()
|
||||||
|
rc.SignedInUser.OrgID = 3
|
||||||
|
|
||||||
|
response := sut.RoutePutAlertRule(&rc, rule, rule.UID)
|
||||||
|
|
||||||
|
require.Equal(t, 200, response.Status())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("are not enabled", func(t *testing.T) {
|
||||||
|
t.Run("POST returns 400", func(t *testing.T) {
|
||||||
|
sut := createProvisioningSrvSut(t)
|
||||||
|
rc := createTestRequestCtx()
|
||||||
|
rule := createTestRecordingRule("rule", 1)
|
||||||
|
|
||||||
|
response := sut.RoutePostAlertRule(&rc, rule)
|
||||||
|
|
||||||
|
require.Equal(t, 400, response.Status())
|
||||||
|
require.NotEmpty(t, response.Body())
|
||||||
|
require.Contains(t, string(response.Body()), "recording rules cannot be created on this instance")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("PUT returns 400", func(t *testing.T) {
|
||||||
|
sut := createProvisioningSrvSut(t)
|
||||||
|
uid := util.GenerateShortUID()
|
||||||
|
rule := createTestAlertRule("rule", 3)
|
||||||
|
rule.UID = uid
|
||||||
|
|
||||||
|
_, err := sut.folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
|
||||||
|
UID: rule.FolderUID,
|
||||||
|
Title: "Folder Title",
|
||||||
|
OrgID: rule.OrgID,
|
||||||
|
SignedInUser: &user.SignedInUser{OrgID: rule.OrgID},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
insertRuleInOrg(t, sut, rule, 3)
|
||||||
|
|
||||||
|
// make rule a recording rule
|
||||||
|
rule.Record = &definitions.Record{
|
||||||
|
Metric: "test_metric",
|
||||||
|
From: "A",
|
||||||
|
}
|
||||||
|
|
||||||
|
rc := createTestRequestCtx()
|
||||||
|
rc.SignedInUser.OrgID = 3
|
||||||
|
|
||||||
|
response := sut.RoutePutAlertRule(&rc, rule, rule.UID)
|
||||||
|
|
||||||
|
require.Equal(t, 400, response.Status())
|
||||||
|
require.NotEmpty(t, response.Body())
|
||||||
|
require.Contains(t, string(response.Body()), "recording rules cannot be created on this instance")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("alert rule groups", func(t *testing.T) {
|
t.Run("alert rule groups", func(t *testing.T) {
|
||||||
t.Run("are present", func(t *testing.T) {
|
t.Run("are present", func(t *testing.T) {
|
||||||
sut := createProvisioningSrvSut(t)
|
sut := createProvisioningSrvSut(t)
|
||||||
@ -1644,6 +1737,7 @@ type testEnvironment struct {
|
|||||||
ac *recordingAccessControlFake
|
ac *recordingAccessControlFake
|
||||||
user *user.SignedInUser
|
user *user.SignedInUser
|
||||||
rulesAuthz *fakes.FakeRuleService
|
rulesAuthz *fakes.FakeRuleService
|
||||||
|
features featuremgmt.FeatureToggles
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTestEnv(t *testing.T, testConfig string) testEnvironment {
|
func createTestEnv(t *testing.T, testConfig string) testEnvironment {
|
||||||
@ -1738,6 +1832,8 @@ func createTestEnv(t *testing.T, testConfig string) testEnvironment {
|
|||||||
|
|
||||||
ruleAuthz := &fakes.FakeRuleService{}
|
ruleAuthz := &fakes.FakeRuleService{}
|
||||||
|
|
||||||
|
features := featuremgmt.WithFeatures()
|
||||||
|
|
||||||
return testEnvironment{
|
return testEnvironment{
|
||||||
secrets: secretsService,
|
secrets: secretsService,
|
||||||
log: log,
|
log: log,
|
||||||
@ -1751,6 +1847,7 @@ func createTestEnv(t *testing.T, testConfig string) testEnvironment {
|
|||||||
ac: ac,
|
ac: ac,
|
||||||
user: user,
|
user: user,
|
||||||
rulesAuthz: ruleAuthz,
|
rulesAuthz: ruleAuthz,
|
||||||
|
features: features,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1773,6 +1870,7 @@ func createProvisioningSrvSutFromEnv(t *testing.T, env *testEnvironment) Provisi
|
|||||||
muteTimings: provisioning.NewMuteTimingService(env.configs, env.prov, env.xact, env.log),
|
muteTimings: provisioning.NewMuteTimingService(env.configs, env.prov, env.xact, env.log),
|
||||||
alertRules: provisioning.NewAlertRuleService(env.store, env.prov, env.folderService, env.quotas, env.xact, 60, 10, 100, env.log, &provisioning.NotificationSettingsValidatorProviderFake{}, env.rulesAuthz),
|
alertRules: provisioning.NewAlertRuleService(env.store, env.prov, env.folderService, env.quotas, env.xact, 60, 10, 100, env.log, &provisioning.NotificationSettingsValidatorProviderFake{}, env.rulesAuthz),
|
||||||
folderSvc: env.folderService,
|
folderSvc: env.folderService,
|
||||||
|
featureManager: env.features,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1973,6 +2071,32 @@ func createTestAlertRule(title string, orgID int64) definitions.ProvisionedAlert
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createTestRecordingRule(title string, orgID int64) definitions.ProvisionedAlertRule {
|
||||||
|
return definitions.ProvisionedAlertRule{
|
||||||
|
UID: title,
|
||||||
|
OrgID: orgID,
|
||||||
|
Title: title,
|
||||||
|
Condition: "A",
|
||||||
|
Data: []definitions.AlertQuery{
|
||||||
|
{
|
||||||
|
RefID: "A",
|
||||||
|
Model: json.RawMessage(testModel),
|
||||||
|
RelativeTimeRange: definitions.RelativeTimeRange{
|
||||||
|
From: definitions.Duration(60),
|
||||||
|
To: definitions.Duration(0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RuleGroup: "my-cool-group",
|
||||||
|
FolderUID: "folder-uid",
|
||||||
|
For: model.Duration(60),
|
||||||
|
Record: &definitions.Record{
|
||||||
|
Metric: "test_record",
|
||||||
|
From: "A",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func insertRule(t *testing.T, srv ProvisioningSrv, rule definitions.ProvisionedAlertRule) {
|
func insertRule(t *testing.T, srv ProvisioningSrv, rule definitions.ProvisionedAlertRule) {
|
||||||
insertRuleInOrg(t, srv, rule, 1)
|
insertRuleInOrg(t, srv, rule, 1)
|
||||||
}
|
}
|
||||||
|
@ -32,9 +32,7 @@ func AlertRuleFromProvisionedAlertRule(a definitions.ProvisionedAlertRule) (mode
|
|||||||
Labels: a.Labels,
|
Labels: a.Labels,
|
||||||
IsPaused: a.IsPaused,
|
IsPaused: a.IsPaused,
|
||||||
NotificationSettings: NotificationSettingsFromAlertRuleNotificationSettings(a.NotificationSettings),
|
NotificationSettings: NotificationSettingsFromAlertRuleNotificationSettings(a.NotificationSettings),
|
||||||
// Recording Rule fields will be implemented in the future.
|
Record: ModelRecordFromApiRecord(a.Record),
|
||||||
// For now, no rules can be recording rules. So, we force these to be empty.
|
|
||||||
Record: nil,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,6 +56,7 @@ func ProvisionedAlertRuleFromAlertRule(rule models.AlertRule, provenance models.
|
|||||||
Provenance: definitions.Provenance(provenance), // TODO validate enum conversion?
|
Provenance: definitions.Provenance(provenance), // TODO validate enum conversion?
|
||||||
IsPaused: rule.IsPaused,
|
IsPaused: rule.IsPaused,
|
||||||
NotificationSettings: AlertRuleNotificationSettingsFromNotificationSettings(rule.NotificationSettings),
|
NotificationSettings: AlertRuleNotificationSettingsFromNotificationSettings(rule.NotificationSettings),
|
||||||
|
Record: ApiRecordFromModelRecord(rule.Record),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,6 +191,7 @@ func AlertRuleExportFromAlertRule(rule models.AlertRule) (definitions.AlertRuleE
|
|||||||
ExecErrState: definitions.ExecutionErrorState(rule.ExecErrState),
|
ExecErrState: definitions.ExecutionErrorState(rule.ExecErrState),
|
||||||
IsPaused: rule.IsPaused,
|
IsPaused: rule.IsPaused,
|
||||||
NotificationSettings: AlertRuleNotificationSettingsExportFromNotificationSettings(rule.NotificationSettings),
|
NotificationSettings: AlertRuleNotificationSettingsExportFromNotificationSettings(rule.NotificationSettings),
|
||||||
|
Record: AlertRuleRecordExportFromRecord(rule.Record),
|
||||||
}
|
}
|
||||||
if rule.For.Seconds() > 0 {
|
if rule.For.Seconds() > 0 {
|
||||||
result.ForString = util.Pointer(model.Duration(rule.For).String())
|
result.ForString = util.Pointer(model.Duration(rule.For).String())
|
||||||
@ -456,11 +456,11 @@ func NotificationSettingsFromAlertRuleNotificationSettings(ns *definitions.Alert
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ApiRecordFromModelRecord(r *models.Record) *definitions.Record {
|
func AlertRuleRecordExportFromRecord(r *models.Record) *definitions.AlertRuleRecordExport {
|
||||||
if r == nil {
|
if r == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return &definitions.Record{
|
return &definitions.AlertRuleRecordExport{
|
||||||
Metric: r.Metric,
|
Metric: r.Metric,
|
||||||
From: r.From,
|
From: r.From,
|
||||||
}
|
}
|
||||||
@ -475,3 +475,13 @@ func ModelRecordFromApiRecord(r *definitions.Record) *models.Record {
|
|||||||
From: r.From,
|
From: r.From,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ApiRecordFromModelRecord(r *models.Record) *definitions.Record {
|
||||||
|
if r == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &definitions.Record{
|
||||||
|
Metric: r.Metric,
|
||||||
|
From: r.From,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -218,6 +218,9 @@
|
|||||||
"format": "int64",
|
"format": "int64",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
|
"record": {
|
||||||
|
"$ref": "#/definitions/AlertRuleRecordExport"
|
||||||
|
},
|
||||||
"title": {
|
"title": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -367,6 +370,18 @@
|
|||||||
"title": "AlertRuleNotificationSettingsExport is the provisioned export of models.NotificationSettings.",
|
"title": "AlertRuleNotificationSettingsExport is the provisioned export of models.NotificationSettings.",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"AlertRuleRecordExport": {
|
||||||
|
"properties": {
|
||||||
|
"from": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"metric": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Record is the provisioned export of models.Record.",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"AlertingFileExport": {
|
"AlertingFileExport": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"apiVersion": {
|
"apiVersion": {
|
||||||
@ -2922,6 +2937,9 @@
|
|||||||
"provenance": {
|
"provenance": {
|
||||||
"$ref": "#/definitions/Provenance"
|
"$ref": "#/definitions/Provenance"
|
||||||
},
|
},
|
||||||
|
"record": {
|
||||||
|
"$ref": "#/definitions/Record"
|
||||||
|
},
|
||||||
"ruleGroup": {
|
"ruleGroup": {
|
||||||
"example": "eval_group_1",
|
"example": "eval_group_1",
|
||||||
"maxLength": 190,
|
"maxLength": 190,
|
||||||
@ -3256,13 +3274,20 @@
|
|||||||
"Record": {
|
"Record": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"from": {
|
"from": {
|
||||||
|
"description": "Which expression node should be used as the input for the recorded metric.",
|
||||||
|
"example": "A",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"metric": {
|
"metric": {
|
||||||
|
"description": "Name of the recorded metric.",
|
||||||
|
"example": "grafana_alerts_ratio",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "Record defines how data produced by a recording rule is written.",
|
"required": [
|
||||||
|
"metric",
|
||||||
|
"from"
|
||||||
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"RelativeTimeRange": {
|
"RelativeTimeRange": {
|
||||||
@ -4435,7 +4460,6 @@
|
|||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"alertGroup": {
|
"alertGroup": {
|
||||||
"description": "AlertGroup alert group",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"alerts": {
|
"alerts": {
|
||||||
"description": "alerts",
|
"description": "alerts",
|
||||||
@ -4459,7 +4483,6 @@
|
|||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"alertGroups": {
|
"alertGroups": {
|
||||||
"description": "AlertGroups alert groups",
|
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/alertGroup"
|
"$ref": "#/definitions/alertGroup"
|
||||||
},
|
},
|
||||||
@ -4626,6 +4649,7 @@
|
|||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"gettableSilence": {
|
"gettableSilence": {
|
||||||
|
"description": "GettableSilence gettable silence",
|
||||||
"properties": {
|
"properties": {
|
||||||
"comment": {
|
"comment": {
|
||||||
"description": "comment",
|
"description": "comment",
|
||||||
@ -4681,6 +4705,7 @@
|
|||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"integration": {
|
"integration": {
|
||||||
|
"description": "Integration integration",
|
||||||
"properties": {
|
"properties": {
|
||||||
"lastNotifyAttempt": {
|
"lastNotifyAttempt": {
|
||||||
"description": "A timestamp indicating the last attempt to deliver a notification regardless of the outcome.\nFormat: date-time",
|
"description": "A timestamp indicating the last attempt to deliver a notification regardless of the outcome.\nFormat: date-time",
|
||||||
@ -4861,7 +4886,6 @@
|
|||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"receiver": {
|
"receiver": {
|
||||||
"description": "Receiver receiver",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"active": {
|
"active": {
|
||||||
"description": "active",
|
"description": "active",
|
||||||
|
@ -477,6 +477,18 @@ type AlertRuleNotificationSettings struct {
|
|||||||
MuteTimeIntervals []string `json:"mute_time_intervals,omitempty"`
|
MuteTimeIntervals []string `json:"mute_time_intervals,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// swagger:model
|
||||||
|
type Record struct {
|
||||||
|
// Name of the recorded metric.
|
||||||
|
// required: true
|
||||||
|
// example: grafana_alerts_ratio
|
||||||
|
Metric string `json:"metric" yaml:"metric"`
|
||||||
|
// Which expression node should be used as the input for the recorded metric.
|
||||||
|
// required: true
|
||||||
|
// example: A
|
||||||
|
From string `json:"from" yaml:"from"`
|
||||||
|
}
|
||||||
|
|
||||||
// swagger:model
|
// swagger:model
|
||||||
type PostableGrafanaRule struct {
|
type PostableGrafanaRule struct {
|
||||||
Title string `json:"title" yaml:"title"`
|
Title string `json:"title" yaml:"title"`
|
||||||
@ -578,12 +590,6 @@ func (d *Duration) UnmarshalYAML(unmarshal func(any) error) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record defines how data produced by a recording rule is written.
|
|
||||||
type Record struct {
|
|
||||||
Metric string `json:"metric" yaml:"metric"`
|
|
||||||
From string `json:"from" yaml:"from"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// swagger:model
|
// swagger:model
|
||||||
type UpdateRuleGroupResponse struct {
|
type UpdateRuleGroupResponse struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
|
@ -167,6 +167,8 @@ type ProvisionedAlertRule struct {
|
|||||||
IsPaused bool `json:"isPaused"`
|
IsPaused bool `json:"isPaused"`
|
||||||
// example: {"receiver":"email","group_by":["alertname","grafana_folder","cluster"],"group_wait":"30s","group_interval":"1m","repeat_interval":"4d","mute_time_intervals":["Weekends","Holidays"]}
|
// example: {"receiver":"email","group_by":["alertname","grafana_folder","cluster"],"group_wait":"30s","group_interval":"1m","repeat_interval":"4d","mute_time_intervals":["Weekends","Holidays"]}
|
||||||
NotificationSettings *AlertRuleNotificationSettings `json:"notification_settings"`
|
NotificationSettings *AlertRuleNotificationSettings `json:"notification_settings"`
|
||||||
|
//example: {"metric":"grafana_alerts_ratio", "from":"A"}
|
||||||
|
Record *Record `json:"record"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// swagger:route GET /v1/provisioning/folder/{FolderUID}/rule-groups/{Group} provisioning stable RouteGetAlertRuleGroup
|
// swagger:route GET /v1/provisioning/folder/{FolderUID}/rule-groups/{Group} provisioning stable RouteGetAlertRuleGroup
|
||||||
@ -273,6 +275,7 @@ type AlertRuleExport struct {
|
|||||||
Labels *map[string]string `json:"labels,omitempty" yaml:"labels,omitempty" hcl:"labels"`
|
Labels *map[string]string `json:"labels,omitempty" yaml:"labels,omitempty" hcl:"labels"`
|
||||||
IsPaused bool `json:"isPaused" yaml:"isPaused" hcl:"is_paused"`
|
IsPaused bool `json:"isPaused" yaml:"isPaused" hcl:"is_paused"`
|
||||||
NotificationSettings *AlertRuleNotificationSettingsExport `json:"notification_settings,omitempty" yaml:"notification_settings,omitempty" hcl:"notification_settings,block"`
|
NotificationSettings *AlertRuleNotificationSettingsExport `json:"notification_settings,omitempty" yaml:"notification_settings,omitempty" hcl:"notification_settings,block"`
|
||||||
|
Record *AlertRuleRecordExport `json:"record,omitempty" yaml:"record,omitempty" hcl:"record"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AlertQueryExport is the provisioned export of models.AlertQuery.
|
// AlertQueryExport is the provisioned export of models.AlertQuery.
|
||||||
@ -301,3 +304,9 @@ type AlertRuleNotificationSettingsExport struct {
|
|||||||
RepeatInterval *string `yaml:"repeat_interval,omitempty" json:"repeat_interval,omitempty" hcl:"repeat_interval,optional"`
|
RepeatInterval *string `yaml:"repeat_interval,omitempty" json:"repeat_interval,omitempty" hcl:"repeat_interval,optional"`
|
||||||
MuteTimeIntervals []string `yaml:"mute_time_intervals,omitempty" json:"mute_time_intervals,omitempty" hcl:"mute_timings"` // TF -> `mute_timings`
|
MuteTimeIntervals []string `yaml:"mute_time_intervals,omitempty" json:"mute_time_intervals,omitempty" hcl:"mute_timings"` // TF -> `mute_timings`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Record is the provisioned export of models.Record.
|
||||||
|
type AlertRuleRecordExport struct {
|
||||||
|
Metric string `json:"metric" yaml:"metric" hcl:"metric"`
|
||||||
|
From string `json:"from" yaml:"from" hcl:"from"`
|
||||||
|
}
|
||||||
|
@ -218,6 +218,9 @@
|
|||||||
"format": "int64",
|
"format": "int64",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
|
"record": {
|
||||||
|
"$ref": "#/definitions/AlertRuleRecordExport"
|
||||||
|
},
|
||||||
"title": {
|
"title": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -367,6 +370,18 @@
|
|||||||
"title": "AlertRuleNotificationSettingsExport is the provisioned export of models.NotificationSettings.",
|
"title": "AlertRuleNotificationSettingsExport is the provisioned export of models.NotificationSettings.",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"AlertRuleRecordExport": {
|
||||||
|
"properties": {
|
||||||
|
"from": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"metric": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Record is the provisioned export of models.Record.",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"AlertingFileExport": {
|
"AlertingFileExport": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"apiVersion": {
|
"apiVersion": {
|
||||||
@ -967,7 +982,9 @@
|
|||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"EvalQueriesResponse": {},
|
"EvalQueriesResponse": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"ExplorePanelsState": {
|
"ExplorePanelsState": {
|
||||||
"description": "This is an object constructed with the keys as the values of the enum VisType and the value being a bag of properties"
|
"description": "This is an object constructed with the keys as the values of the enum VisType and the value being a bag of properties"
|
||||||
},
|
},
|
||||||
@ -2922,6 +2939,9 @@
|
|||||||
"provenance": {
|
"provenance": {
|
||||||
"$ref": "#/definitions/Provenance"
|
"$ref": "#/definitions/Provenance"
|
||||||
},
|
},
|
||||||
|
"record": {
|
||||||
|
"$ref": "#/definitions/Record"
|
||||||
|
},
|
||||||
"ruleGroup": {
|
"ruleGroup": {
|
||||||
"example": "eval_group_1",
|
"example": "eval_group_1",
|
||||||
"maxLength": 190,
|
"maxLength": 190,
|
||||||
@ -3256,13 +3276,20 @@
|
|||||||
"Record": {
|
"Record": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"from": {
|
"from": {
|
||||||
|
"description": "Which expression node should be used as the input for the recorded metric.",
|
||||||
|
"example": "A",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"metric": {
|
"metric": {
|
||||||
|
"description": "Name of the recorded metric.",
|
||||||
|
"example": "grafana_alerts_ratio",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "Record defines how data produced by a recording rule is written.",
|
"required": [
|
||||||
|
"metric",
|
||||||
|
"from"
|
||||||
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"RelativeTimeRange": {
|
"RelativeTimeRange": {
|
||||||
@ -4461,7 +4488,8 @@
|
|||||||
},
|
},
|
||||||
"alertGroups": {
|
"alertGroups": {
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/alertGroup"
|
"$ref": "#/definitions/alertGroup",
|
||||||
|
"type": "object"
|
||||||
},
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
@ -4564,49 +4592,6 @@
|
|||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"gettableAlert": {
|
"gettableAlert": {
|
||||||
"description": "GettableAlert gettable alert",
|
|
||||||
"properties": {
|
|
||||||
"annotations": {
|
|
||||||
"$ref": "#/definitions/labelSet"
|
|
||||||
},
|
|
||||||
"endsAt": {
|
|
||||||
"description": "ends at",
|
|
||||||
"format": "date-time",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"fingerprint": {
|
|
||||||
"description": "fingerprint",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"generatorURL": {
|
|
||||||
"description": "generator URL\nFormat: uri",
|
|
||||||
"format": "uri",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"labels": {
|
|
||||||
"$ref": "#/definitions/labelSet"
|
|
||||||
},
|
|
||||||
"receivers": {
|
|
||||||
"description": "receivers",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/receiver"
|
|
||||||
},
|
|
||||||
"type": "array"
|
|
||||||
},
|
|
||||||
"startsAt": {
|
|
||||||
"description": "starts at",
|
|
||||||
"format": "date-time",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"$ref": "#/definitions/alertStatus"
|
|
||||||
},
|
|
||||||
"updatedAt": {
|
|
||||||
"description": "updated at",
|
|
||||||
"format": "date-time",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
"required": [
|
||||||
"labels",
|
"labels",
|
||||||
"annotations",
|
"annotations",
|
||||||
@ -4620,49 +4605,13 @@
|
|||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"gettableAlerts": {
|
"gettableAlerts": {
|
||||||
"description": "GettableAlerts gettable alerts",
|
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/gettableAlert"
|
"$ref": "#/definitions/gettableAlert",
|
||||||
|
"type": "object"
|
||||||
},
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"gettableSilence": {
|
"gettableSilence": {
|
||||||
"description": "GettableSilence gettable silence",
|
|
||||||
"properties": {
|
|
||||||
"comment": {
|
|
||||||
"description": "comment",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"createdBy": {
|
|
||||||
"description": "created by",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"endsAt": {
|
|
||||||
"description": "ends at",
|
|
||||||
"format": "date-time",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"description": "id",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"matchers": {
|
|
||||||
"$ref": "#/definitions/matchers"
|
|
||||||
},
|
|
||||||
"startsAt": {
|
|
||||||
"description": "starts at",
|
|
||||||
"format": "date-time",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"$ref": "#/definitions/silenceStatus"
|
|
||||||
},
|
|
||||||
"updatedAt": {
|
|
||||||
"description": "updated at",
|
|
||||||
"format": "date-time",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
"required": [
|
||||||
"comment",
|
"comment",
|
||||||
"createdBy",
|
"createdBy",
|
||||||
@ -4677,11 +4626,13 @@
|
|||||||
},
|
},
|
||||||
"gettableSilences": {
|
"gettableSilences": {
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/gettableSilence"
|
"$ref": "#/definitions/gettableSilence",
|
||||||
|
"type": "object"
|
||||||
},
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"integration": {
|
"integration": {
|
||||||
|
"description": "Integration integration",
|
||||||
"properties": {
|
"properties": {
|
||||||
"lastNotifyAttempt": {
|
"lastNotifyAttempt": {
|
||||||
"description": "A timestamp indicating the last attempt to deliver a notification regardless of the outcome.\nFormat: date-time",
|
"description": "A timestamp indicating the last attempt to deliver a notification regardless of the outcome.\nFormat: date-time",
|
||||||
@ -4825,6 +4776,7 @@
|
|||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"postableSilence": {
|
"postableSilence": {
|
||||||
|
"description": "PostableSilence postable silence",
|
||||||
"properties": {
|
"properties": {
|
||||||
"comment": {
|
"comment": {
|
||||||
"description": "comment",
|
"description": "comment",
|
||||||
@ -4862,6 +4814,7 @@
|
|||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"receiver": {
|
"receiver": {
|
||||||
|
"description": "Receiver receiver",
|
||||||
"properties": {
|
"properties": {
|
||||||
"active": {
|
"active": {
|
||||||
"description": "active",
|
"description": "active",
|
||||||
|
@ -3731,6 +3731,9 @@
|
|||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int64"
|
"format": "int64"
|
||||||
},
|
},
|
||||||
|
"record": {
|
||||||
|
"$ref": "#/definitions/AlertRuleRecordExport"
|
||||||
|
},
|
||||||
"title": {
|
"title": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -3878,6 +3881,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"AlertRuleRecordExport": {
|
||||||
|
"type": "object",
|
||||||
|
"title": "Record is the provisioned export of models.Record.",
|
||||||
|
"properties": {
|
||||||
|
"from": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"metric": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"AlertingFileExport": {
|
"AlertingFileExport": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "AlertingFileExport is the full provisioned file export.",
|
"title": "AlertingFileExport is the full provisioned file export.",
|
||||||
@ -4480,7 +4495,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"EvalQueriesResponse": {
|
"EvalQueriesResponse": {
|
||||||
"$ref": "#/definitions/EvalQueriesResponse"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"ExplorePanelsState": {
|
"ExplorePanelsState": {
|
||||||
"description": "This is an object constructed with the keys as the values of the enum VisType and the value being a bag of properties"
|
"description": "This is an object constructed with the keys as the values of the enum VisType and the value being a bag of properties"
|
||||||
@ -6449,6 +6464,9 @@
|
|||||||
"provenance": {
|
"provenance": {
|
||||||
"$ref": "#/definitions/Provenance"
|
"$ref": "#/definitions/Provenance"
|
||||||
},
|
},
|
||||||
|
"record": {
|
||||||
|
"$ref": "#/definitions/Record"
|
||||||
|
},
|
||||||
"ruleGroup": {
|
"ruleGroup": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"maxLength": 190,
|
"maxLength": 190,
|
||||||
@ -6770,13 +6788,20 @@
|
|||||||
},
|
},
|
||||||
"Record": {
|
"Record": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "Record defines how data produced by a recording rule is written.",
|
"required": [
|
||||||
|
"metric",
|
||||||
|
"from"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"from": {
|
"from": {
|
||||||
"type": "string"
|
"description": "Which expression node should be used as the input for the recorded metric.",
|
||||||
|
"type": "string",
|
||||||
|
"example": "A"
|
||||||
},
|
},
|
||||||
"metric": {
|
"metric": {
|
||||||
"type": "string"
|
"description": "Name of the recorded metric.",
|
||||||
|
"type": "string",
|
||||||
|
"example": "grafana_alerts_ratio"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -7972,15 +7997,15 @@
|
|||||||
"receiver": {
|
"receiver": {
|
||||||
"$ref": "#/definitions/receiver"
|
"$ref": "#/definitions/receiver"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"$ref": "#/definitions/alertGroup"
|
|
||||||
},
|
},
|
||||||
"alertGroups": {
|
"alertGroups": {
|
||||||
|
"description": "AlertGroups alert groups",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
|
"type": "object",
|
||||||
"$ref": "#/definitions/alertGroup"
|
"$ref": "#/definitions/alertGroup"
|
||||||
},
|
}
|
||||||
"$ref": "#/definitions/alertGroups"
|
|
||||||
},
|
},
|
||||||
"alertStatus": {
|
"alertStatus": {
|
||||||
"description": "AlertStatus alert status",
|
"description": "AlertStatus alert status",
|
||||||
@ -8081,7 +8106,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"gettableAlert": {
|
"gettableAlert": {
|
||||||
"description": "GettableAlert gettable alert",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"labels",
|
"labels",
|
||||||
@ -8092,58 +8116,14 @@
|
|||||||
"startsAt",
|
"startsAt",
|
||||||
"status",
|
"status",
|
||||||
"updatedAt"
|
"updatedAt"
|
||||||
],
|
]
|
||||||
"properties": {
|
|
||||||
"annotations": {
|
|
||||||
"$ref": "#/definitions/labelSet"
|
|
||||||
},
|
|
||||||
"endsAt": {
|
|
||||||
"description": "ends at",
|
|
||||||
"type": "string",
|
|
||||||
"format": "date-time"
|
|
||||||
},
|
|
||||||
"fingerprint": {
|
|
||||||
"description": "fingerprint",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"generatorURL": {
|
|
||||||
"description": "generator URL\nFormat: uri",
|
|
||||||
"type": "string",
|
|
||||||
"format": "uri"
|
|
||||||
},
|
|
||||||
"labels": {
|
|
||||||
"$ref": "#/definitions/labelSet"
|
|
||||||
},
|
|
||||||
"receivers": {
|
|
||||||
"description": "receivers",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/receiver"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"startsAt": {
|
|
||||||
"description": "starts at",
|
|
||||||
"type": "string",
|
|
||||||
"format": "date-time"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"$ref": "#/definitions/alertStatus"
|
|
||||||
},
|
|
||||||
"updatedAt": {
|
|
||||||
"description": "updated at",
|
|
||||||
"type": "string",
|
|
||||||
"format": "date-time"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"$ref": "#/definitions/gettableAlert"
|
|
||||||
},
|
},
|
||||||
"gettableAlerts": {
|
"gettableAlerts": {
|
||||||
"description": "GettableAlerts gettable alerts",
|
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
|
"type": "object",
|
||||||
"$ref": "#/definitions/gettableAlert"
|
"$ref": "#/definitions/gettableAlert"
|
||||||
},
|
}
|
||||||
"$ref": "#/definitions/gettableAlerts"
|
|
||||||
},
|
},
|
||||||
"gettableSilence": {
|
"gettableSilence": {
|
||||||
"description": "GettableSilence gettable silence",
|
"description": "GettableSilence gettable silence",
|
||||||
@ -8192,17 +8172,17 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "date-time"
|
"format": "date-time"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"$ref": "#/definitions/gettableSilence"
|
|
||||||
},
|
},
|
||||||
"gettableSilences": {
|
"gettableSilences": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
|
"type": "object",
|
||||||
"$ref": "#/definitions/gettableSilence"
|
"$ref": "#/definitions/gettableSilence"
|
||||||
},
|
}
|
||||||
"$ref": "#/definitions/gettableSilences"
|
|
||||||
},
|
},
|
||||||
"integration": {
|
"integration": {
|
||||||
|
"description": "Integration integration",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"name",
|
"name",
|
||||||
@ -8230,8 +8210,7 @@
|
|||||||
"description": "send resolved",
|
"description": "send resolved",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"$ref": "#/definitions/integration"
|
|
||||||
},
|
},
|
||||||
"labelSet": {
|
"labelSet": {
|
||||||
"description": "LabelSet label set",
|
"description": "LabelSet label set",
|
||||||
@ -8347,6 +8326,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"postableSilence": {
|
"postableSilence": {
|
||||||
|
"description": "PostableSilence postable silence",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"comment",
|
"comment",
|
||||||
@ -8381,8 +8361,7 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "date-time"
|
"format": "date-time"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"$ref": "#/definitions/postableSilence"
|
|
||||||
},
|
},
|
||||||
"receiver": {
|
"receiver": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@ -8390,25 +8369,7 @@
|
|||||||
"active",
|
"active",
|
||||||
"integrations",
|
"integrations",
|
||||||
"name"
|
"name"
|
||||||
],
|
]
|
||||||
"properties": {
|
|
||||||
"active": {
|
|
||||||
"description": "active",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"integrations": {
|
|
||||||
"description": "integrations",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/integration"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"description": "name",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"$ref": "#/definitions/receiver"
|
|
||||||
},
|
},
|
||||||
"silence": {
|
"silence": {
|
||||||
"description": "Silence silence",
|
"description": "Silence silence",
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
|
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||||
|
prommodels "github.com/prometheus/common/model"
|
||||||
|
|
||||||
alertingModels "github.com/grafana/alerting/models"
|
alertingModels "github.com/grafana/alerting/models"
|
||||||
|
|
||||||
@ -510,14 +511,14 @@ func (alertRule *AlertRule) ValidateAlertRule(cfg setting.UnifiedAlertingSetting
|
|||||||
return fmt.Errorf("%w: cannot have Panel ID without a Dashboard UID", ErrAlertRuleFailedValidation)
|
return fmt.Errorf("%w: cannot have Panel ID without a Dashboard UID", ErrAlertRuleFailedValidation)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !alertRule.IsRecordingRule() {
|
var err error
|
||||||
if _, err := ErrStateFromString(string(alertRule.ExecErrState)); err != nil {
|
if alertRule.IsRecordingRule() {
|
||||||
return err
|
err = validateRecordingRuleFields(alertRule)
|
||||||
}
|
} else {
|
||||||
|
err = validateAlertRuleFields(alertRule)
|
||||||
if _, err := NoDataStateFromString(string(alertRule.NoDataState)); err != nil {
|
}
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if alertRule.For < 0 {
|
if alertRule.For < 0 {
|
||||||
@ -543,6 +544,29 @@ func (alertRule *AlertRule) ValidateAlertRule(cfg setting.UnifiedAlertingSetting
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateAlertRuleFields(rule *AlertRule) error {
|
||||||
|
if _, err := ErrStateFromString(string(rule.ExecErrState)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := NoDataStateFromString(string(rule.NoDataState)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateRecordingRuleFields(rule *AlertRule) error {
|
||||||
|
metricName := prommodels.LabelValue(rule.Record.Metric)
|
||||||
|
if !metricName.IsValid() {
|
||||||
|
return fmt.Errorf("%w: %s", ErrAlertRuleFailedValidation, "metric name for recording rule must be a valid utf8 string")
|
||||||
|
}
|
||||||
|
if !prommodels.IsValidMetricName(metricName) {
|
||||||
|
return fmt.Errorf("%w: %s", ErrAlertRuleFailedValidation, "metric name for recording rule must be a valid Prometheus metric name")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (alertRule *AlertRule) ResourceType() string {
|
func (alertRule *AlertRule) ResourceType() string {
|
||||||
return "alertRule"
|
return "alertRule"
|
||||||
}
|
}
|
||||||
|
@ -603,6 +603,7 @@ func CopyRule(r *AlertRule, mutators ...AlertRuleMutator) *AlertRule {
|
|||||||
NoDataState: r.NoDataState,
|
NoDataState: r.NoDataState,
|
||||||
ExecErrState: r.ExecErrState,
|
ExecErrState: r.ExecErrState,
|
||||||
For: r.For,
|
For: r.For,
|
||||||
|
Record: r.Record,
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.DashboardUID != nil {
|
if r.DashboardUID != nil {
|
||||||
|
@ -77,7 +77,7 @@ func TestIntegrationUpdateAlertRules(t *testing.T) {
|
|||||||
t.Run("updating record field should increase version", func(t *testing.T) {
|
t.Run("updating record field should increase version", func(t *testing.T) {
|
||||||
rule := createRule(t, store, recordingRuleGen)
|
rule := createRule(t, store, recordingRuleGen)
|
||||||
newRule := models.CopyRule(rule)
|
newRule := models.CopyRule(rule)
|
||||||
newRule.Record.Metric = "new-metric"
|
newRule.Record.Metric = "new_metric"
|
||||||
|
|
||||||
err := store.UpdateAlertRules(context.Background(), []models.UpdateRule{{
|
err := store.UpdateAlertRules(context.Background(), []models.UpdateRule{{
|
||||||
Existing: rule,
|
Existing: rule,
|
||||||
@ -721,6 +721,26 @@ func TestIntegrationInsertAlertRules(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("inserted recording rules fail validation if metric name is invalid", func(t *testing.T) {
|
||||||
|
t.Run("invalid UTF-8", func(t *testing.T) {
|
||||||
|
invalidMetric := "my_metric\x80"
|
||||||
|
invalidRule := recordingRulesGen.Generate()
|
||||||
|
invalidRule.Record.Metric = invalidMetric
|
||||||
|
_, err := store.InsertAlertRules(context.Background(), []models.AlertRule{invalidRule})
|
||||||
|
require.ErrorIs(t, err, models.ErrAlertRuleFailedValidation)
|
||||||
|
require.ErrorContains(t, err, "metric name for recording rule must be a valid utf8 string")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid metric name", func(t *testing.T) {
|
||||||
|
invalidMetric := "with-dashes"
|
||||||
|
invalidRule := recordingRulesGen.Generate()
|
||||||
|
invalidRule.Record.Metric = invalidMetric
|
||||||
|
_, err := store.InsertAlertRules(context.Background(), []models.AlertRule{invalidRule})
|
||||||
|
require.ErrorIs(t, err, models.ErrAlertRuleFailedValidation)
|
||||||
|
require.ErrorContains(t, err, "metric name for recording rule must be a valid Prometheus metric name")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("fail to insert rules with same ID", func(t *testing.T) {
|
t.Run("fail to insert rules with same ID", func(t *testing.T) {
|
||||||
_, err = store.InsertAlertRules(context.Background(), []models.AlertRule{rules[0]})
|
_, err = store.InsertAlertRules(context.Background(), []models.AlertRule{rules[0]})
|
||||||
require.ErrorIs(t, err, models.ErrAlertRuleConflictBase)
|
require.ErrorIs(t, err, models.ErrAlertRuleConflictBase)
|
||||||
|
@ -75,6 +75,7 @@ type AlertRuleV1 struct {
|
|||||||
Labels values.StringMapValue `json:"labels" yaml:"labels"`
|
Labels values.StringMapValue `json:"labels" yaml:"labels"`
|
||||||
IsPaused values.BoolValue `json:"isPaused" yaml:"isPaused"`
|
IsPaused values.BoolValue `json:"isPaused" yaml:"isPaused"`
|
||||||
NotificationSettings *NotificationSettingsV1 `json:"notification_settings" yaml:"notification_settings"`
|
NotificationSettings *NotificationSettingsV1 `json:"notification_settings" yaml:"notification_settings"`
|
||||||
|
Record *RecordV1 `json:"record" yaml:"record"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rule *AlertRuleV1) mapToModel(orgID int64) (models.AlertRule, error) {
|
func (rule *AlertRuleV1) mapToModel(orgID int64) (models.AlertRule, error) {
|
||||||
@ -139,6 +140,13 @@ func (rule *AlertRuleV1) mapToModel(orgID int64) (models.AlertRule, error) {
|
|||||||
}
|
}
|
||||||
alertRule.NotificationSettings = append(alertRule.NotificationSettings, ns)
|
alertRule.NotificationSettings = append(alertRule.NotificationSettings, ns)
|
||||||
}
|
}
|
||||||
|
if rule.Record != nil {
|
||||||
|
record, err := rule.Record.mapToModel()
|
||||||
|
if err != nil {
|
||||||
|
return models.AlertRule{}, fmt.Errorf("rule '%s' failed to parse: %w", alertRule.Title, err)
|
||||||
|
}
|
||||||
|
alertRule.Record = &record
|
||||||
|
}
|
||||||
return alertRule, nil
|
return alertRule, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,3 +254,15 @@ func (nsV1 *NotificationSettingsV1) mapToModel() (models.NotificationSettings, e
|
|||||||
MuteTimeIntervals: mute,
|
MuteTimeIntervals: mute,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RecordV1 struct {
|
||||||
|
Metric values.StringValue `json:"metric" yaml:"metric"`
|
||||||
|
From values.StringValue `json:"from" yaml:"from"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record *RecordV1) mapToModel() (models.Record, error) {
|
||||||
|
return models.Record{
|
||||||
|
Metric: record.Metric.Value(),
|
||||||
|
From: record.From.Value(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
@ -12191,6 +12191,9 @@
|
|||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int64"
|
"format": "int64"
|
||||||
},
|
},
|
||||||
|
"record": {
|
||||||
|
"$ref": "#/definitions/AlertRuleRecordExport"
|
||||||
|
},
|
||||||
"title": {
|
"title": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -12338,6 +12341,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"AlertRuleRecordExport": {
|
||||||
|
"type": "object",
|
||||||
|
"title": "Record is the provisioned export of models.Record.",
|
||||||
|
"properties": {
|
||||||
|
"from": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"metric": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"AlertingFileExport": {
|
"AlertingFileExport": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "AlertingFileExport is the full provisioned file export.",
|
"title": "AlertingFileExport is the full provisioned file export.",
|
||||||
@ -17944,6 +17959,9 @@
|
|||||||
"provenance": {
|
"provenance": {
|
||||||
"$ref": "#/definitions/Provenance"
|
"$ref": "#/definitions/Provenance"
|
||||||
},
|
},
|
||||||
|
"record": {
|
||||||
|
"$ref": "#/definitions/Record"
|
||||||
|
},
|
||||||
"ruleGroup": {
|
"ruleGroup": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"maxLength": 190,
|
"maxLength": 190,
|
||||||
@ -18511,13 +18529,20 @@
|
|||||||
},
|
},
|
||||||
"Record": {
|
"Record": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "Record defines how data produced by a recording rule is written.",
|
"required": [
|
||||||
|
"metric",
|
||||||
|
"from"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"from": {
|
"from": {
|
||||||
"type": "string"
|
"description": "Which expression node should be used as the input for the recorded metric.",
|
||||||
|
"type": "string",
|
||||||
|
"example": "A"
|
||||||
},
|
},
|
||||||
"metric": {
|
"metric": {
|
||||||
"type": "string"
|
"description": "Name of the recorded metric.",
|
||||||
|
"type": "string",
|
||||||
|
"example": "grafana_alerts_ratio"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -21472,7 +21497,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"alertGroup": {
|
"alertGroup": {
|
||||||
"description": "AlertGroup alert group",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"alerts",
|
"alerts",
|
||||||
@ -21496,7 +21520,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"alertGroups": {
|
"alertGroups": {
|
||||||
"description": "AlertGroups alert groups",
|
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/alertGroup"
|
"$ref": "#/definitions/alertGroup"
|
||||||
@ -21691,6 +21714,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"gettableSilence": {
|
"gettableSilence": {
|
||||||
|
"description": "GettableSilence gettable silence",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"comment",
|
"comment",
|
||||||
@ -21746,6 +21770,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"integration": {
|
"integration": {
|
||||||
|
"description": "Integration integration",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"name",
|
"name",
|
||||||
@ -21954,7 +21979,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"receiver": {
|
"receiver": {
|
||||||
"description": "Receiver receiver",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"active",
|
"active",
|
||||||
|
@ -2585,6 +2585,9 @@
|
|||||||
"format": "int64",
|
"format": "int64",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
|
"record": {
|
||||||
|
"$ref": "#/components/schemas/AlertRuleRecordExport"
|
||||||
|
},
|
||||||
"title": {
|
"title": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -2734,6 +2737,18 @@
|
|||||||
"title": "AlertRuleNotificationSettingsExport is the provisioned export of models.NotificationSettings.",
|
"title": "AlertRuleNotificationSettingsExport is the provisioned export of models.NotificationSettings.",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"AlertRuleRecordExport": {
|
||||||
|
"properties": {
|
||||||
|
"from": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"metric": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Record is the provisioned export of models.Record.",
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"AlertingFileExport": {
|
"AlertingFileExport": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"apiVersion": {
|
"apiVersion": {
|
||||||
@ -8328,6 +8343,9 @@
|
|||||||
"provenance": {
|
"provenance": {
|
||||||
"$ref": "#/components/schemas/Provenance"
|
"$ref": "#/components/schemas/Provenance"
|
||||||
},
|
},
|
||||||
|
"record": {
|
||||||
|
"$ref": "#/components/schemas/Record"
|
||||||
|
},
|
||||||
"ruleGroup": {
|
"ruleGroup": {
|
||||||
"example": "eval_group_1",
|
"example": "eval_group_1",
|
||||||
"maxLength": 190,
|
"maxLength": 190,
|
||||||
@ -8908,13 +8926,20 @@
|
|||||||
"Record": {
|
"Record": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"from": {
|
"from": {
|
||||||
|
"description": "Which expression node should be used as the input for the recorded metric.",
|
||||||
|
"example": "A",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"metric": {
|
"metric": {
|
||||||
|
"description": "Name of the recorded metric.",
|
||||||
|
"example": "grafana_alerts_ratio",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "Record defines how data produced by a recording rule is written.",
|
"required": [
|
||||||
|
"metric",
|
||||||
|
"from"
|
||||||
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"RecordingRuleJSON": {
|
"RecordingRuleJSON": {
|
||||||
@ -11867,7 +11892,6 @@
|
|||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"alertGroup": {
|
"alertGroup": {
|
||||||
"description": "AlertGroup alert group",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"alerts": {
|
"alerts": {
|
||||||
"description": "alerts",
|
"description": "alerts",
|
||||||
@ -11891,7 +11915,6 @@
|
|||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"alertGroups": {
|
"alertGroups": {
|
||||||
"description": "AlertGroups alert groups",
|
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/components/schemas/alertGroup"
|
"$ref": "#/components/schemas/alertGroup"
|
||||||
},
|
},
|
||||||
@ -12086,6 +12109,7 @@
|
|||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"gettableSilence": {
|
"gettableSilence": {
|
||||||
|
"description": "GettableSilence gettable silence",
|
||||||
"properties": {
|
"properties": {
|
||||||
"comment": {
|
"comment": {
|
||||||
"description": "comment",
|
"description": "comment",
|
||||||
@ -12141,6 +12165,7 @@
|
|||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"integration": {
|
"integration": {
|
||||||
|
"description": "Integration integration",
|
||||||
"properties": {
|
"properties": {
|
||||||
"lastNotifyAttempt": {
|
"lastNotifyAttempt": {
|
||||||
"description": "A timestamp indicating the last attempt to deliver a notification regardless of the outcome.\nFormat: date-time",
|
"description": "A timestamp indicating the last attempt to deliver a notification regardless of the outcome.\nFormat: date-time",
|
||||||
@ -12349,7 +12374,6 @@
|
|||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"receiver": {
|
"receiver": {
|
||||||
"description": "Receiver receiver",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"active": {
|
"active": {
|
||||||
"description": "active",
|
"description": "active",
|
||||||
|
Loading…
Reference in New Issue
Block a user