Alerting: Provisioning API - Alert rules (#47930)

This commit is contained in:
Jean-Philippe Quéméner 2022-06-02 14:48:53 +02:00 committed by GitHub
parent 5dbea9996b
commit 81d360529b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1295 additions and 126 deletions

View File

@ -81,6 +81,7 @@ type API struct {
ContactPointService *provisioning.ContactPointService
Templates *provisioning.TemplateService
MuteTimings *provisioning.MuteTimingService
AlertRules *provisioning.AlertRuleService
}
// RegisterAPIEndpoints registers API handlers
@ -142,6 +143,7 @@ func (api *API) RegisterAPIEndpoints(m *metrics.API) {
contactPointService: api.ContactPointService,
templates: api.Templates,
muteTimings: api.MuteTimings,
alertRules: api.AlertRules,
}), m)
}
}

View File

@ -16,8 +16,13 @@ import (
"github.com/grafana/grafana/pkg/web"
)
const namePathParam = ":name"
const idPathParam = ":ID"
const (
namePathParam = ":name"
idPathParam = ":ID"
uidPathParam = ":UID"
groupPathParam = ":Group"
folderUIDPathParam = ":FolderUID"
)
type ProvisioningSrv struct {
log log.Logger
@ -25,6 +30,7 @@ type ProvisioningSrv struct {
contactPointService ContactPointService
templates TemplateService
muteTimings MuteTimingService
alertRules AlertRuleService
}
type ContactPointService interface {
@ -52,6 +58,14 @@ type MuteTimingService interface {
DeleteMuteTiming(ctx context.Context, name string, orgID int64) error
}
type AlertRuleService interface {
GetAlertRule(ctx context.Context, orgID int64, ruleUID string) (alerting_models.AlertRule, alerting_models.Provenance, error)
CreateAlertRule(ctx context.Context, rule alerting_models.AlertRule, provenance alerting_models.Provenance) (alerting_models.AlertRule, error)
UpdateAlertRule(ctx context.Context, rule alerting_models.AlertRule, provenance alerting_models.Provenance) (alerting_models.AlertRule, error)
DeleteAlertRule(ctx context.Context, orgID int64, ruleUID string, provenance alerting_models.Provenance) error
UpdateAlertGroup(ctx context.Context, orgID int64, folderUID, rulegroup string, interval int64) error
}
func (srv *ProvisioningSrv) RouteGetPolicyTree(c *models.ReqContext) response.Response {
policies, err := srv.policies.GetPolicyTree(c.Req.Context(), c.OrgId)
if errors.Is(err, store.ErrNoAlertmanagerConfiguration) {
@ -223,6 +237,54 @@ func (srv *ProvisioningSrv) RouteDeleteMuteTiming(c *models.ReqContext) response
return response.JSON(http.StatusNoContent, nil)
}
func (srv *ProvisioningSrv) RouteRouteGetAlertRule(c *models.ReqContext) response.Response {
uid := pathParam(c, uidPathParam)
rule, provenace, err := srv.alertRules.GetAlertRule(c.Req.Context(), c.OrgId, uid)
if err != nil {
return ErrResp(http.StatusInternalServerError, err, "")
}
return response.JSON(http.StatusOK, apimodels.NewAlertRule(rule, provenace))
}
func (srv *ProvisioningSrv) RoutePostAlertRule(c *models.ReqContext, ar apimodels.AlertRule) response.Response {
createdAlertRule, err := srv.alertRules.CreateAlertRule(c.Req.Context(), ar.UpstreamModel(), alerting_models.ProvenanceAPI)
if err != nil {
return ErrResp(http.StatusInternalServerError, err, "")
}
ar.ID = createdAlertRule.ID
ar.UID = createdAlertRule.UID
ar.Updated = createdAlertRule.Updated
return response.JSON(http.StatusCreated, ar)
}
func (srv *ProvisioningSrv) RoutePutAlertRule(c *models.ReqContext, ar apimodels.AlertRule) response.Response {
updatedAlertRule, err := srv.alertRules.UpdateAlertRule(c.Req.Context(), ar.UpstreamModel(), alerting_models.ProvenanceAPI)
if err != nil {
return ErrResp(http.StatusInternalServerError, err, "")
}
ar.Updated = updatedAlertRule.Updated
return response.JSON(http.StatusOK, ar)
}
func (srv *ProvisioningSrv) RouteDeleteAlertRule(c *models.ReqContext) response.Response {
uid := pathParam(c, uidPathParam)
err := srv.alertRules.DeleteAlertRule(c.Req.Context(), c.OrgId, uid, alerting_models.ProvenanceAPI)
if err != nil {
return ErrResp(http.StatusInternalServerError, err, "")
}
return response.JSON(http.StatusNoContent, "")
}
func (srv *ProvisioningSrv) RoutePutAlertRuleGroup(c *models.ReqContext, ag apimodels.AlertRuleGroup) response.Response {
rulegroup := pathParam(c, groupPathParam)
folderUID := pathParam(c, folderUIDPathParam)
err := srv.alertRules.UpdateAlertGroup(c.Req.Context(), c.OrgId, folderUID, rulegroup, ag.Interval)
if err != nil {
return ErrResp(http.StatusInternalServerError, err, "")
}
return response.JSON(http.StatusOK, ag)
}
func pathParam(c *models.ReqContext, param string) string {
return web.Params(c.Req)[param]
}

View File

@ -422,7 +422,7 @@ func (srv RulerSrv) updateAlertRulesInGroup(c *models.ReqContext, groupKey ngmod
for _, rule := range finalChanges.New {
inserts = append(inserts, *rule)
}
err = srv.store.InsertAlertRules(tranCtx, inserts)
_, err = srv.store.InsertAlertRules(tranCtx, inserts)
if err != nil {
return fmt.Errorf("failed to add rules: %w", err)
}

View File

@ -184,7 +184,8 @@ func (api *API) authorize(method, path string) web.Handler {
http.MethodGet + "/api/provisioning/templates",
http.MethodGet + "/api/provisioning/templates/{name}",
http.MethodGet + "/api/provisioning/mute-timings",
http.MethodGet + "/api/provisioning/mute-timings/{name}":
http.MethodGet + "/api/provisioning/mute-timings/{name}",
http.MethodGet + "/api/provisioning/alert-rules/{UID}":
return middleware.ReqSignedIn
case http.MethodPut + "/api/provisioning/policies",
@ -195,7 +196,11 @@ func (api *API) authorize(method, path string) web.Handler {
http.MethodDelete + "/api/provisioning/templates/{name}",
http.MethodPost + "/api/provisioning/mute-timings",
http.MethodPut + "/api/provisioning/mute-timings/{name}",
http.MethodDelete + "/api/provisioning/mute-timings/{name}":
http.MethodDelete + "/api/provisioning/mute-timings/{name}",
http.MethodPost + "/api/provisioning/alert-rules",
http.MethodPut + "/api/provisioning/alert-rules/{UID}",
http.MethodDelete + "/api/provisioning/alert-rules/{UID}",
http.MethodPut + "/api/provisioning/folder/{FolderUID}/rule-groups/{Group}":
return middleware.ReqEditorRole
}

View File

@ -48,7 +48,7 @@ func TestAuthorize(t *testing.T) {
}
paths[p] = methods
}
require.Len(t, paths, 36)
require.Len(t, paths, 39)
ac := acmock.New()
api := &API{AccessControl: ac}

View File

@ -78,3 +78,23 @@ func (f *ForkedProvisioningApi) forkRoutePutMuteTiming(ctx *models.ReqContext, m
func (f *ForkedProvisioningApi) forkRouteDeleteMuteTiming(ctx *models.ReqContext) response.Response {
return f.svc.RouteDeleteMuteTiming(ctx)
}
func (f *ForkedProvisioningApi) forkRouteGetAlertRule(ctx *models.ReqContext) response.Response {
return f.svc.RouteRouteGetAlertRule(ctx)
}
func (f *ForkedProvisioningApi) forkRoutePostAlertRule(ctx *models.ReqContext, ar apimodels.AlertRule) response.Response {
return f.svc.RoutePostAlertRule(ctx, ar)
}
func (f *ForkedProvisioningApi) forkRoutePutAlertRule(ctx *models.ReqContext, ar apimodels.AlertRule) response.Response {
return f.svc.RoutePutAlertRule(ctx, ar)
}
func (f *ForkedProvisioningApi) forkRouteDeleteAlertRule(ctx *models.ReqContext) response.Response {
return f.svc.RouteDeleteAlertRule(ctx)
}
func (f *ForkedProvisioningApi) forkRoutePutAlertRuleGroup(ctx *models.ReqContext, ag apimodels.AlertRuleGroup) response.Response {
return f.svc.RoutePutAlertRuleGroup(ctx, ag)
}

View File

@ -4,7 +4,6 @@
*
*Do not manually edit these files, please find ngalert/api/swagger-codegen/ for commands on how to generate them.
*/
package api
import (
@ -53,7 +52,6 @@ func (f *ForkedAlertmanagerApi) RouteCreateGrafanaSilence(ctx *models.ReqContext
}
return f.forkRouteCreateGrafanaSilence(ctx, conf)
}
func (f *ForkedAlertmanagerApi) RouteCreateSilence(ctx *models.ReqContext) response.Response {
conf := apimodels.PostableSilence{}
if err := web.Bind(ctx.Req, &conf); err != nil {
@ -61,71 +59,54 @@ func (f *ForkedAlertmanagerApi) RouteCreateSilence(ctx *models.ReqContext) respo
}
return f.forkRouteCreateSilence(ctx, conf)
}
func (f *ForkedAlertmanagerApi) RouteDeleteAlertingConfig(ctx *models.ReqContext) response.Response {
return f.forkRouteDeleteAlertingConfig(ctx)
}
func (f *ForkedAlertmanagerApi) RouteDeleteGrafanaAlertingConfig(ctx *models.ReqContext) response.Response {
return f.forkRouteDeleteGrafanaAlertingConfig(ctx)
}
func (f *ForkedAlertmanagerApi) RouteDeleteGrafanaSilence(ctx *models.ReqContext) response.Response {
return f.forkRouteDeleteGrafanaSilence(ctx)
}
func (f *ForkedAlertmanagerApi) RouteDeleteSilence(ctx *models.ReqContext) response.Response {
return f.forkRouteDeleteSilence(ctx)
}
func (f *ForkedAlertmanagerApi) RouteGetAMAlertGroups(ctx *models.ReqContext) response.Response {
return f.forkRouteGetAMAlertGroups(ctx)
}
func (f *ForkedAlertmanagerApi) RouteGetAMAlerts(ctx *models.ReqContext) response.Response {
return f.forkRouteGetAMAlerts(ctx)
}
func (f *ForkedAlertmanagerApi) RouteGetAMStatus(ctx *models.ReqContext) response.Response {
return f.forkRouteGetAMStatus(ctx)
}
func (f *ForkedAlertmanagerApi) RouteGetAlertingConfig(ctx *models.ReqContext) response.Response {
return f.forkRouteGetAlertingConfig(ctx)
}
func (f *ForkedAlertmanagerApi) RouteGetGrafanaAMAlertGroups(ctx *models.ReqContext) response.Response {
return f.forkRouteGetGrafanaAMAlertGroups(ctx)
}
func (f *ForkedAlertmanagerApi) RouteGetGrafanaAMAlerts(ctx *models.ReqContext) response.Response {
return f.forkRouteGetGrafanaAMAlerts(ctx)
}
func (f *ForkedAlertmanagerApi) RouteGetGrafanaAMStatus(ctx *models.ReqContext) response.Response {
return f.forkRouteGetGrafanaAMStatus(ctx)
}
func (f *ForkedAlertmanagerApi) RouteGetGrafanaAlertingConfig(ctx *models.ReqContext) response.Response {
return f.forkRouteGetGrafanaAlertingConfig(ctx)
}
func (f *ForkedAlertmanagerApi) RouteGetGrafanaSilence(ctx *models.ReqContext) response.Response {
return f.forkRouteGetGrafanaSilence(ctx)
}
func (f *ForkedAlertmanagerApi) RouteGetGrafanaSilences(ctx *models.ReqContext) response.Response {
return f.forkRouteGetGrafanaSilences(ctx)
}
func (f *ForkedAlertmanagerApi) RouteGetSilence(ctx *models.ReqContext) response.Response {
return f.forkRouteGetSilence(ctx)
}
func (f *ForkedAlertmanagerApi) RouteGetSilences(ctx *models.ReqContext) response.Response {
return f.forkRouteGetSilences(ctx)
}
func (f *ForkedAlertmanagerApi) RoutePostAMAlerts(ctx *models.ReqContext) response.Response {
conf := apimodels.PostableAlerts{}
if err := web.Bind(ctx.Req, &conf); err != nil {
@ -133,7 +114,6 @@ func (f *ForkedAlertmanagerApi) RoutePostAMAlerts(ctx *models.ReqContext) respon
}
return f.forkRoutePostAMAlerts(ctx, conf)
}
func (f *ForkedAlertmanagerApi) RoutePostAlertingConfig(ctx *models.ReqContext) response.Response {
conf := apimodels.PostableUserConfig{}
if err := web.Bind(ctx.Req, &conf); err != nil {
@ -141,7 +121,6 @@ func (f *ForkedAlertmanagerApi) RoutePostAlertingConfig(ctx *models.ReqContext)
}
return f.forkRoutePostAlertingConfig(ctx, conf)
}
func (f *ForkedAlertmanagerApi) RoutePostGrafanaAMAlerts(ctx *models.ReqContext) response.Response {
conf := apimodels.PostableAlerts{}
if err := web.Bind(ctx.Req, &conf); err != nil {
@ -149,7 +128,6 @@ func (f *ForkedAlertmanagerApi) RoutePostGrafanaAMAlerts(ctx *models.ReqContext)
}
return f.forkRoutePostGrafanaAMAlerts(ctx, conf)
}
func (f *ForkedAlertmanagerApi) RoutePostGrafanaAlertingConfig(ctx *models.ReqContext) response.Response {
conf := apimodels.PostableUserConfig{}
if err := web.Bind(ctx.Req, &conf); err != nil {
@ -157,7 +135,6 @@ func (f *ForkedAlertmanagerApi) RoutePostGrafanaAlertingConfig(ctx *models.ReqCo
}
return f.forkRoutePostGrafanaAlertingConfig(ctx, conf)
}
func (f *ForkedAlertmanagerApi) RoutePostTestGrafanaReceivers(ctx *models.ReqContext) response.Response {
conf := apimodels.TestReceiversConfigBodyParams{}
if err := web.Bind(ctx.Req, &conf); err != nil {
@ -165,7 +142,6 @@ func (f *ForkedAlertmanagerApi) RoutePostTestGrafanaReceivers(ctx *models.ReqCon
}
return f.forkRoutePostTestGrafanaReceivers(ctx, conf)
}
func (f *ForkedAlertmanagerApi) RoutePostTestReceivers(ctx *models.ReqContext) response.Response {
conf := apimodels.TestReceiversConfigBodyParams{}
if err := web.Bind(ctx.Req, &conf); err != nil {

View File

@ -4,7 +4,6 @@
*
*Do not manually edit these files, please find ngalert/api/swagger-codegen/ for commands on how to generate them.
*/
package api
import (
@ -29,15 +28,12 @@ type ConfigurationApiForkingService interface {
func (f *ForkedConfigurationApi) RouteDeleteNGalertConfig(ctx *models.ReqContext) response.Response {
return f.forkRouteDeleteNGalertConfig(ctx)
}
func (f *ForkedConfigurationApi) RouteGetAlertmanagers(ctx *models.ReqContext) response.Response {
return f.forkRouteGetAlertmanagers(ctx)
}
func (f *ForkedConfigurationApi) RouteGetNGalertConfig(ctx *models.ReqContext) response.Response {
return f.forkRouteGetNGalertConfig(ctx)
}
func (f *ForkedConfigurationApi) RoutePostNGalertConfig(ctx *models.ReqContext) response.Response {
conf := apimodels.PostableNGalertConfig{}
if err := web.Bind(ctx.Req, &conf); err != nil {

View File

@ -4,7 +4,6 @@
*
*Do not manually edit these files, please find ngalert/api/swagger-codegen/ for commands on how to generate them.
*/
package api
import (
@ -27,15 +26,12 @@ type PrometheusApiForkingService interface {
func (f *ForkedPrometheusApi) RouteGetAlertStatuses(ctx *models.ReqContext) response.Response {
return f.forkRouteGetAlertStatuses(ctx)
}
func (f *ForkedPrometheusApi) RouteGetGrafanaAlertStatuses(ctx *models.ReqContext) response.Response {
return f.forkRouteGetGrafanaAlertStatuses(ctx)
}
func (f *ForkedPrometheusApi) RouteGetGrafanaRuleStatuses(ctx *models.ReqContext) response.Response {
return f.forkRouteGetGrafanaRuleStatuses(ctx)
}
func (f *ForkedPrometheusApi) RouteGetRuleStatuses(ctx *models.ReqContext) response.Response {
return f.forkRouteGetRuleStatuses(ctx)
}

View File

@ -4,7 +4,6 @@
*
*Do not manually edit these files, please find ngalert/api/swagger-codegen/ for commands on how to generate them.
*/
package api
import (
@ -20,59 +19,68 @@ import (
)
type ProvisioningApiForkingService interface {
RouteDeleteAlertRule(*models.ReqContext) response.Response
RouteDeleteContactpoints(*models.ReqContext) response.Response
RouteDeleteMuteTiming(*models.ReqContext) response.Response
RouteDeleteTemplate(*models.ReqContext) response.Response
RouteGetAlertRule(*models.ReqContext) response.Response
RouteGetContactpoints(*models.ReqContext) response.Response
RouteGetMuteTiming(*models.ReqContext) response.Response
RouteGetMuteTimings(*models.ReqContext) response.Response
RouteGetPolicyTree(*models.ReqContext) response.Response
RouteGetTemplate(*models.ReqContext) response.Response
RouteGetTemplates(*models.ReqContext) response.Response
RoutePostAlertRule(*models.ReqContext) response.Response
RoutePostContactpoints(*models.ReqContext) response.Response
RoutePostMuteTiming(*models.ReqContext) response.Response
RoutePutAlertRule(*models.ReqContext) response.Response
RoutePutAlertRuleGroup(*models.ReqContext) response.Response
RoutePutContactpoint(*models.ReqContext) response.Response
RoutePutMuteTiming(*models.ReqContext) response.Response
RoutePutPolicyTree(*models.ReqContext) response.Response
RoutePutTemplate(*models.ReqContext) response.Response
}
func (f *ForkedProvisioningApi) RouteDeleteAlertRule(ctx *models.ReqContext) response.Response {
return f.forkRouteDeleteAlertRule(ctx)
}
func (f *ForkedProvisioningApi) RouteDeleteContactpoints(ctx *models.ReqContext) response.Response {
return f.forkRouteDeleteContactpoints(ctx)
}
func (f *ForkedProvisioningApi) RouteDeleteMuteTiming(ctx *models.ReqContext) response.Response {
return f.forkRouteDeleteMuteTiming(ctx)
}
func (f *ForkedProvisioningApi) RouteDeleteTemplate(ctx *models.ReqContext) response.Response {
return f.forkRouteDeleteTemplate(ctx)
}
func (f *ForkedProvisioningApi) RouteGetAlertRule(ctx *models.ReqContext) response.Response {
return f.forkRouteGetAlertRule(ctx)
}
func (f *ForkedProvisioningApi) RouteGetContactpoints(ctx *models.ReqContext) response.Response {
return f.forkRouteGetContactpoints(ctx)
}
func (f *ForkedProvisioningApi) RouteGetMuteTiming(ctx *models.ReqContext) response.Response {
return f.forkRouteGetMuteTiming(ctx)
}
func (f *ForkedProvisioningApi) RouteGetMuteTimings(ctx *models.ReqContext) response.Response {
return f.forkRouteGetMuteTimings(ctx)
}
func (f *ForkedProvisioningApi) RouteGetPolicyTree(ctx *models.ReqContext) response.Response {
return f.forkRouteGetPolicyTree(ctx)
}
func (f *ForkedProvisioningApi) RouteGetTemplate(ctx *models.ReqContext) response.Response {
return f.forkRouteGetTemplate(ctx)
}
func (f *ForkedProvisioningApi) RouteGetTemplates(ctx *models.ReqContext) response.Response {
return f.forkRouteGetTemplates(ctx)
}
func (f *ForkedProvisioningApi) RoutePostAlertRule(ctx *models.ReqContext) response.Response {
conf := apimodels.AlertRule{}
if err := web.Bind(ctx.Req, &conf); err != nil {
return response.Error(http.StatusBadRequest, "bad request data", err)
}
return f.forkRoutePostAlertRule(ctx, conf)
}
func (f *ForkedProvisioningApi) RoutePostContactpoints(ctx *models.ReqContext) response.Response {
conf := apimodels.EmbeddedContactPoint{}
if err := web.Bind(ctx.Req, &conf); err != nil {
@ -80,7 +88,6 @@ func (f *ForkedProvisioningApi) RoutePostContactpoints(ctx *models.ReqContext) r
}
return f.forkRoutePostContactpoints(ctx, conf)
}
func (f *ForkedProvisioningApi) RoutePostMuteTiming(ctx *models.ReqContext) response.Response {
conf := apimodels.MuteTimeInterval{}
if err := web.Bind(ctx.Req, &conf); err != nil {
@ -88,7 +95,20 @@ func (f *ForkedProvisioningApi) RoutePostMuteTiming(ctx *models.ReqContext) resp
}
return f.forkRoutePostMuteTiming(ctx, conf)
}
func (f *ForkedProvisioningApi) RoutePutAlertRule(ctx *models.ReqContext) response.Response {
conf := apimodels.AlertRule{}
if err := web.Bind(ctx.Req, &conf); err != nil {
return response.Error(http.StatusBadRequest, "bad request data", err)
}
return f.forkRoutePutAlertRule(ctx, conf)
}
func (f *ForkedProvisioningApi) RoutePutAlertRuleGroup(ctx *models.ReqContext) response.Response {
conf := apimodels.AlertRuleGroup{}
if err := web.Bind(ctx.Req, &conf); err != nil {
return response.Error(http.StatusBadRequest, "bad request data", err)
}
return f.forkRoutePutAlertRuleGroup(ctx, conf)
}
func (f *ForkedProvisioningApi) RoutePutContactpoint(ctx *models.ReqContext) response.Response {
conf := apimodels.EmbeddedContactPoint{}
if err := web.Bind(ctx.Req, &conf); err != nil {
@ -96,7 +116,6 @@ func (f *ForkedProvisioningApi) RoutePutContactpoint(ctx *models.ReqContext) res
}
return f.forkRoutePutContactpoint(ctx, conf)
}
func (f *ForkedProvisioningApi) RoutePutMuteTiming(ctx *models.ReqContext) response.Response {
conf := apimodels.MuteTimeInterval{}
if err := web.Bind(ctx.Req, &conf); err != nil {
@ -104,7 +123,6 @@ func (f *ForkedProvisioningApi) RoutePutMuteTiming(ctx *models.ReqContext) respo
}
return f.forkRoutePutMuteTiming(ctx, conf)
}
func (f *ForkedProvisioningApi) RoutePutPolicyTree(ctx *models.ReqContext) response.Response {
conf := apimodels.Route{}
if err := web.Bind(ctx.Req, &conf); err != nil {
@ -112,7 +130,6 @@ func (f *ForkedProvisioningApi) RoutePutPolicyTree(ctx *models.ReqContext) respo
}
return f.forkRoutePutPolicyTree(ctx, conf)
}
func (f *ForkedProvisioningApi) RoutePutTemplate(ctx *models.ReqContext) response.Response {
conf := apimodels.MessageTemplateContent{}
if err := web.Bind(ctx.Req, &conf); err != nil {
@ -123,6 +140,16 @@ func (f *ForkedProvisioningApi) RoutePutTemplate(ctx *models.ReqContext) respons
func (api *API) RegisterProvisioningApiEndpoints(srv ProvisioningApiForkingService, m *metrics.API) {
api.RouteRegister.Group("", func(group routing.RouteRegister) {
group.Delete(
toMacaronPath("/api/provisioning/alert-rules/{UID}"),
api.authorize(http.MethodDelete, "/api/provisioning/alert-rules/{UID}"),
metrics.Instrument(
http.MethodDelete,
"/api/provisioning/alert-rules/{UID}",
srv.RouteDeleteAlertRule,
m,
),
)
group.Delete(
toMacaronPath("/api/provisioning/contact-points/{ID}"),
api.authorize(http.MethodDelete, "/api/provisioning/contact-points/{ID}"),
@ -153,6 +180,16 @@ func (api *API) RegisterProvisioningApiEndpoints(srv ProvisioningApiForkingServi
m,
),
)
group.Get(
toMacaronPath("/api/provisioning/alert-rules/{UID}"),
api.authorize(http.MethodGet, "/api/provisioning/alert-rules/{UID}"),
metrics.Instrument(
http.MethodGet,
"/api/provisioning/alert-rules/{UID}",
srv.RouteGetAlertRule,
m,
),
)
group.Get(
toMacaronPath("/api/provisioning/contact-points"),
api.authorize(http.MethodGet, "/api/provisioning/contact-points"),
@ -213,6 +250,16 @@ func (api *API) RegisterProvisioningApiEndpoints(srv ProvisioningApiForkingServi
m,
),
)
group.Post(
toMacaronPath("/api/provisioning/alert-rules"),
api.authorize(http.MethodPost, "/api/provisioning/alert-rules"),
metrics.Instrument(
http.MethodPost,
"/api/provisioning/alert-rules",
srv.RoutePostAlertRule,
m,
),
)
group.Post(
toMacaronPath("/api/provisioning/contact-points"),
api.authorize(http.MethodPost, "/api/provisioning/contact-points"),
@ -233,6 +280,26 @@ func (api *API) RegisterProvisioningApiEndpoints(srv ProvisioningApiForkingServi
m,
),
)
group.Put(
toMacaronPath("/api/provisioning/alert-rules/{UID}"),
api.authorize(http.MethodPut, "/api/provisioning/alert-rules/{UID}"),
metrics.Instrument(
http.MethodPut,
"/api/provisioning/alert-rules/{UID}",
srv.RoutePutAlertRule,
m,
),
)
group.Put(
toMacaronPath("/api/provisioning/folder/{FolderUID}/rule-groups/{Group}"),
api.authorize(http.MethodPut, "/api/provisioning/folder/{FolderUID}/rule-groups/{Group}"),
metrics.Instrument(
http.MethodPut,
"/api/provisioning/folder/{FolderUID}/rule-groups/{Group}",
srv.RoutePutAlertRuleGroup,
m,
),
)
group.Put(
toMacaronPath("/api/provisioning/contact-points/{ID}"),
api.authorize(http.MethodPut, "/api/provisioning/contact-points/{ID}"),

View File

@ -4,7 +4,6 @@
*
*Do not manually edit these files, please find ngalert/api/swagger-codegen/ for commands on how to generate them.
*/
package api
import (
@ -37,43 +36,33 @@ type RulerApiForkingService interface {
func (f *ForkedRulerApi) RouteDeleteGrafanaRuleGroupConfig(ctx *models.ReqContext) response.Response {
return f.forkRouteDeleteGrafanaRuleGroupConfig(ctx)
}
func (f *ForkedRulerApi) RouteDeleteNamespaceGrafanaRulesConfig(ctx *models.ReqContext) response.Response {
return f.forkRouteDeleteNamespaceGrafanaRulesConfig(ctx)
}
func (f *ForkedRulerApi) RouteDeleteNamespaceRulesConfig(ctx *models.ReqContext) response.Response {
return f.forkRouteDeleteNamespaceRulesConfig(ctx)
}
func (f *ForkedRulerApi) RouteDeleteRuleGroupConfig(ctx *models.ReqContext) response.Response {
return f.forkRouteDeleteRuleGroupConfig(ctx)
}
func (f *ForkedRulerApi) RouteGetGrafanaRuleGroupConfig(ctx *models.ReqContext) response.Response {
return f.forkRouteGetGrafanaRuleGroupConfig(ctx)
}
func (f *ForkedRulerApi) RouteGetGrafanaRulesConfig(ctx *models.ReqContext) response.Response {
return f.forkRouteGetGrafanaRulesConfig(ctx)
}
func (f *ForkedRulerApi) RouteGetNamespaceGrafanaRulesConfig(ctx *models.ReqContext) response.Response {
return f.forkRouteGetNamespaceGrafanaRulesConfig(ctx)
}
func (f *ForkedRulerApi) RouteGetNamespaceRulesConfig(ctx *models.ReqContext) response.Response {
return f.forkRouteGetNamespaceRulesConfig(ctx)
}
func (f *ForkedRulerApi) RouteGetRulegGroupConfig(ctx *models.ReqContext) response.Response {
return f.forkRouteGetRulegGroupConfig(ctx)
}
func (f *ForkedRulerApi) RouteGetRulesConfig(ctx *models.ReqContext) response.Response {
return f.forkRouteGetRulesConfig(ctx)
}
func (f *ForkedRulerApi) RoutePostNameGrafanaRulesConfig(ctx *models.ReqContext) response.Response {
conf := apimodels.PostableRuleGroupConfig{}
if err := web.Bind(ctx.Req, &conf); err != nil {
@ -81,7 +70,6 @@ func (f *ForkedRulerApi) RoutePostNameGrafanaRulesConfig(ctx *models.ReqContext)
}
return f.forkRoutePostNameGrafanaRulesConfig(ctx, conf)
}
func (f *ForkedRulerApi) RoutePostNameRulesConfig(ctx *models.ReqContext) response.Response {
conf := apimodels.PostableRuleGroupConfig{}
if err := web.Bind(ctx.Req, &conf); err != nil {

View File

@ -4,7 +4,6 @@
*
*Do not manually edit these files, please find ngalert/api/swagger-codegen/ for commands on how to generate them.
*/
package api
import (
@ -32,7 +31,6 @@ func (f *ForkedTestingApi) RouteEvalQueries(ctx *models.ReqContext) response.Res
}
return f.forkRouteEvalQueries(ctx, conf)
}
func (f *ForkedTestingApi) RouteTestRuleConfig(ctx *models.ReqContext) response.Response {
conf := apimodels.TestRulePayload{}
if err := web.Bind(ctx.Req, &conf); err != nil {
@ -40,7 +38,6 @@ func (f *ForkedTestingApi) RouteTestRuleConfig(ctx *models.ReqContext) response.
}
return f.forkRouteTestRuleConfig(ctx, conf)
}
func (f *ForkedTestingApi) RouteTestRuleGrafanaConfig(ctx *models.ReqContext) response.Response {
conf := apimodels.TestRulePayload{}
if err := web.Bind(ctx.Req, &conf); err != nil {

View File

@ -35,7 +35,7 @@ api.json: spec-stable.json
go run cmd/clean-swagger/main.go -if $(<) -of $@
swagger-codegen-api:
docker run --rm -v $$(pwd):/local --user $$(id -u):$$(id -g) swaggerapi/swagger-codegen-cli generate \
docker run --rm -v $$(pwd):/local --user $$(id -u):$$(id -g) parsertongue/swagger-codegen-cli:3.0.32 generate \
-i /local/post.json \
-l go-server \
-Dapis \
@ -49,9 +49,9 @@ copy-files:
ls -1 go | xargs -n 1 -I {} mv go/{} ../generated_base_{}
fix:
sed -i -e 's/apimodels\.\[\]PostableAlert/apimodels.PostableAlerts/' $(GENERATED_GO_MATCHERS)
sed -i -e 's/apimodels\.\[\]UpdateDashboardAclCommand/apimodels.Permissions/' $(GENERATED_GO_MATCHERS)
sed -i -e 's/apimodels\.\[\]PostableApiReceiver/apimodels.TestReceiversConfigParams/' $(GENERATED_GO_MATCHERS)
sed -i '' -e 's/apimodels\.\[\]PostableAlert/apimodels.PostableAlerts/' $(GENERATED_GO_MATCHERS)
sed -i '' -e 's/apimodels\.\[\]UpdateDashboardAclCommand/apimodels.Permissions/' $(GENERATED_GO_MATCHERS)
sed -i '' -e 's/apimodels\.\[\]PostableApiReceiver/apimodels.TestReceiversConfigParams/' $(GENERATED_GO_MATCHERS)
goimports -w -v $(GENERATED_GO_MATCHERS)
clean:

View File

@ -0,0 +1,144 @@
package definitions
import (
"time"
"github.com/grafana/grafana/pkg/services/ngalert/models"
)
// swagger:route GET /api/provisioning/alert-rules/{UID} provisioning RouteGetAlertRule
//
// Get a specific alert rule by UID.
//
// Responses:
// 200: AlertRule
// 400: ValidationError
// swagger:route POST /api/provisioning/alert-rules provisioning RoutePostAlertRule
//
// Create a new alert rule.
//
// Responses:
// 201: AlertRule
// 400: ValidationError
// swagger:route PUT /api/provisioning/alert-rules/{UID} provisioning RoutePutAlertRule
//
// Update an existing alert rule.
//
// Consumes:
// - application/json
//
// Responses:
// 200: AlertRule
// 400: ValidationError
// swagger:route DELETE /api/provisioning/alert-rules/{UID} provisioning RouteDeleteAlertRule
//
// Delete a specific alert rule by UID.
//
// Responses:
// 204: description: The alert rule was deleted successfully.
// 400: ValidationError
// swagger:parameters RouteGetAlertRule RoutePutAlertRule RouteDeleteAlertRule
type AlertRuleUIDReference struct {
// in:path
UID string
}
// swagger:parameters RoutePostAlertRule RoutePutAlertRule
type AlertRulePayload struct {
// in:body
Body AlertRule
}
type AlertRule struct {
ID int64 `json:"id"`
UID string `json:"uid"`
OrgID int64 `json:"orgID"`
FolderUID string `json:"folderUID"`
RuleGroup string `json:"ruleGroup"`
Title string `json:"title"`
Condition string `json:"condition"`
Data []models.AlertQuery `json:"data"`
Updated time.Time `json:"updated,omitempty"`
NoDataState models.NoDataState `json:"noDataState"`
ExecErrState models.ExecutionErrorState `json:"execErrState"`
For time.Duration `json:"for"`
Annotations map[string]string `json:"annotations,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
Provenance models.Provenance `json:"provenance,omitempty"`
}
func (a *AlertRule) UpstreamModel() models.AlertRule {
return models.AlertRule{
ID: a.ID,
UID: a.UID,
OrgID: a.OrgID,
NamespaceUID: a.FolderUID,
RuleGroup: a.RuleGroup,
Title: a.Title,
Condition: a.Condition,
Data: a.Data,
Updated: a.Updated,
NoDataState: a.NoDataState,
ExecErrState: a.ExecErrState,
For: a.For,
Annotations: a.Annotations,
Labels: a.Labels,
}
}
func NewAlertRule(rule models.AlertRule, provenance models.Provenance) AlertRule {
return AlertRule{
ID: rule.ID,
UID: rule.UID,
OrgID: rule.OrgID,
FolderUID: rule.NamespaceUID,
RuleGroup: rule.RuleGroup,
Title: rule.Title,
For: rule.For,
Condition: rule.Condition,
Data: rule.Data,
Updated: rule.Updated,
NoDataState: rule.NoDataState,
ExecErrState: rule.ExecErrState,
Annotations: rule.Annotations,
Labels: rule.Labels,
Provenance: provenance,
}
}
// swagger:route PUT /api/provisioning/folder/{FolderUID}/rule-groups/{Group} provisioning RoutePutAlertRuleGroup
//
// Update the interval of a rule group.
//
// Consumes:
// - application/json
//
// Responses:
// 200: AlertRuleGroup
// 400: ValidationError
// swagger:parameters RoutePutAlertRuleGroup
type FolderUIDPathParam struct {
// in:path
FolderUID string `json:"FolderUID"`
}
// swagger:parameters RoutePutAlertRuleGroup
type RuleGroupPathParam struct {
// in:path
Group string `json:"Group"`
}
// swagger:parameters RoutePutAlertRuleGroup
type AlertRuleGroupPayload struct {
// in:body
Body AlertRuleGroup
}
type AlertRuleGroup struct {
Interval int64 `json:"interval"`
}

View File

@ -194,6 +194,91 @@
"type": "object",
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
},
"AlertRule": {
"properties": {
"annotations": {
"additionalProperties": {
"type": "string"
},
"type": "object",
"x-go-name": "Annotations"
},
"condition": {
"type": "string",
"x-go-name": "Condition"
},
"data": {
"items": {
"$ref": "#/definitions/AlertQuery"
},
"type": "array",
"x-go-name": "Data"
},
"execErrState": {
"$ref": "#/definitions/ExecutionErrorState"
},
"folderUID": {
"type": "string",
"x-go-name": "FolderUID"
},
"for": {
"$ref": "#/definitions/Duration"
},
"id": {
"format": "int64",
"type": "integer",
"x-go-name": "ID"
},
"labels": {
"additionalProperties": {
"type": "string"
},
"type": "object",
"x-go-name": "Labels"
},
"noDataState": {
"$ref": "#/definitions/NoDataState"
},
"orgID": {
"format": "int64",
"type": "integer",
"x-go-name": "OrgID"
},
"provenance": {
"$ref": "#/definitions/Provenance"
},
"ruleGroup": {
"type": "string",
"x-go-name": "RuleGroup"
},
"title": {
"type": "string",
"x-go-name": "Title"
},
"uid": {
"type": "string",
"x-go-name": "UID"
},
"updated": {
"format": "date-time",
"type": "string",
"x-go-name": "Updated"
}
},
"type": "object",
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
},
"AlertRuleGroup": {
"properties": {
"interval": {
"format": "int64",
"type": "integer",
"x-go-name": "Interval"
}
},
"type": "object",
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
},
"AlertingRule": {
"description": "adapted from cortex",
"properties": {
@ -663,6 +748,10 @@
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
},
"EvalQueriesResponse": {},
"ExecutionErrorState": {
"type": "string",
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/models"
},
"ExtendedReceiver": {
"properties": {
"email_configs": {
@ -1456,6 +1545,10 @@
"type": "object",
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
},
"NoDataState": {
"type": "string",
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/models"
},
"NotFound": {
"type": "object",
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
@ -2923,6 +3016,7 @@
"x-go-package": "github.com/prometheus/alertmanager/timeinterval"
},
"URL": {
"description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use RawPath, an optional field which only gets\nset if the default encoding is different from Path.\n\nURL's String method uses the EscapedPath method to obtain the path. See the\nEscapedPath method for more details.",
"properties": {
"ForceQuery": {
"type": "boolean"
@ -2955,9 +3049,9 @@
"$ref": "#/definitions/Userinfo"
}
},
"title": "URL is a custom URL type that allows validation at configuration load time.",
"title": "A URL represents a parsed URL (technically, a URI reference).",
"type": "object",
"x-go-package": "github.com/prometheus/common/config"
"x-go-package": "net/url"
},
"Userinfo": {
"description": "The Userinfo type is an immutable encapsulation of username and\npassword details for a URL. An existing Userinfo value is guaranteed\nto have a username set (potentially empty, as allowed by RFC 2396),\nand optionally a password.",
@ -3179,12 +3273,11 @@
"type": "object"
},
"alertGroups": {
"description": "AlertGroups alert groups",
"items": {
"$ref": "#/definitions/alertGroup"
},
"type": "array",
"x-go-name": "AlertGroups",
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
"type": "array"
},
"alertStatus": {
"description": "AlertStatus alert status",
@ -3304,6 +3397,7 @@
"$ref": "#/definitions/Duration"
},
"gettableAlert": {
"description": "GettableAlert gettable alert",
"properties": {
"annotations": {
"$ref": "#/definitions/labelSet"
@ -3362,9 +3456,7 @@
"status",
"updatedAt"
],
"type": "object",
"x-go-name": "GettableAlert",
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
"type": "object"
},
"gettableAlerts": {
"description": "GettableAlerts gettable alerts",
@ -3374,7 +3466,6 @@
"type": "array"
},
"gettableSilence": {
"description": "GettableSilence gettable silence",
"properties": {
"comment": {
"description": "comment",
@ -3426,7 +3517,9 @@
"status",
"updatedAt"
],
"type": "object"
"type": "object",
"x-go-name": "GettableSilence",
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
},
"gettableSilences": {
"items": {
@ -3563,6 +3656,7 @@
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
},
"postableSilence": {
"description": "PostableSilence postable silence",
"properties": {
"comment": {
"description": "comment",
@ -3602,11 +3696,10 @@
"matchers",
"startsAt"
],
"type": "object",
"x-go-name": "PostableSilence",
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
"type": "object"
},
"receiver": {
"description": "Receiver receiver",
"properties": {
"name": {
"description": "name",
@ -3617,9 +3710,7 @@
"required": [
"name"
],
"type": "object",
"x-go-name": "Receiver",
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
"type": "object"
},
"silence": {
"description": "Silence silence",
@ -4838,6 +4929,134 @@
]
}
},
"/api/provisioning/alert-rules": {
"post": {
"operationId": "RoutePostAlertRule",
"parameters": [
{
"in": "body",
"name": "Body",
"schema": {
"$ref": "#/definitions/AlertRule"
}
}
],
"responses": {
"201": {
"description": "AlertRule",
"schema": {
"$ref": "#/definitions/AlertRule"
}
},
"400": {
"description": "ValidationError",
"schema": {
"$ref": "#/definitions/ValidationError"
}
}
},
"summary": "Create a new alert rule.",
"tags": [
"provisioning"
]
}
},
"/api/provisioning/alert-rules/{UID}": {
"delete": {
"operationId": "RouteDeleteAlertRule",
"parameters": [
{
"in": "path",
"name": "UID",
"required": true,
"type": "string"
}
],
"responses": {
"204": {
"description": " The alert rule was deleted successfully."
},
"400": {
"description": "ValidationError",
"schema": {
"$ref": "#/definitions/ValidationError"
}
}
},
"summary": "Delete a specific alert rule by UID.",
"tags": [
"provisioning"
]
},
"get": {
"operationId": "RouteGetAlertRule",
"parameters": [
{
"in": "path",
"name": "UID",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "AlertRule",
"schema": {
"$ref": "#/definitions/AlertRule"
}
},
"400": {
"description": "ValidationError",
"schema": {
"$ref": "#/definitions/ValidationError"
}
}
},
"summary": "Get a specific alert rule by UID.",
"tags": [
"provisioning"
]
},
"put": {
"consumes": [
"application/json"
],
"operationId": "RoutePutAlertRule",
"parameters": [
{
"in": "path",
"name": "UID",
"required": true,
"type": "string"
},
{
"in": "body",
"name": "Body",
"schema": {
"$ref": "#/definitions/AlertRule"
}
}
],
"responses": {
"200": {
"description": "AlertRule",
"schema": {
"$ref": "#/definitions/AlertRule"
}
},
"400": {
"description": "ValidationError",
"schema": {
"$ref": "#/definitions/ValidationError"
}
}
},
"summary": "Update an existing alert rule.",
"tags": [
"provisioning"
]
}
},
"/api/provisioning/contact-points": {
"get": {
"operationId": "RouteGetContactpoints",
@ -4969,6 +5188,53 @@
]
}
},
"/api/provisioning/folder/{FolderUID}/rule-groups/{Group}": {
"put": {
"consumes": [
"application/json"
],
"operationId": "RoutePutAlertRuleGroup",
"parameters": [
{
"in": "path",
"name": "FolderUID",
"required": true,
"type": "string"
},
{
"in": "path",
"name": "Group",
"required": true,
"type": "string"
},
{
"in": "body",
"name": "Body",
"schema": {
"$ref": "#/definitions/AlertRuleGroup"
}
}
],
"responses": {
"200": {
"description": "AlertRuleGroup",
"schema": {
"$ref": "#/definitions/AlertRuleGroup"
}
},
"400": {
"description": "ValidationError",
"schema": {
"$ref": "#/definitions/ValidationError"
}
}
},
"summary": "Update the interval of an rule group.",
"tags": [
"provisioning"
]
}
},
"/api/provisioning/mute-timings": {
"get": {
"operationId": "RouteGetMuteTimings",

View File

@ -1122,6 +1122,134 @@
}
}
},
"/api/provisioning/alert-rules": {
"post": {
"tags": [
"provisioning"
],
"summary": "Create a new alert rule.",
"operationId": "RoutePostAlertRule",
"parameters": [
{
"name": "Body",
"in": "body",
"schema": {
"$ref": "#/definitions/AlertRule"
}
}
],
"responses": {
"201": {
"description": "AlertRule",
"schema": {
"$ref": "#/definitions/AlertRule"
}
},
"400": {
"description": "ValidationError",
"schema": {
"$ref": "#/definitions/ValidationError"
}
}
}
}
},
"/api/provisioning/alert-rules/{UID}": {
"get": {
"tags": [
"provisioning"
],
"summary": "Get a specific alert rule by UID.",
"operationId": "RouteGetAlertRule",
"parameters": [
{
"type": "string",
"name": "UID",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "AlertRule",
"schema": {
"$ref": "#/definitions/AlertRule"
}
},
"400": {
"description": "ValidationError",
"schema": {
"$ref": "#/definitions/ValidationError"
}
}
}
},
"put": {
"consumes": [
"application/json"
],
"tags": [
"provisioning"
],
"summary": "Update an existing alert rule.",
"operationId": "RoutePutAlertRule",
"parameters": [
{
"type": "string",
"name": "UID",
"in": "path",
"required": true
},
{
"name": "Body",
"in": "body",
"schema": {
"$ref": "#/definitions/AlertRule"
}
}
],
"responses": {
"200": {
"description": "AlertRule",
"schema": {
"$ref": "#/definitions/AlertRule"
}
},
"400": {
"description": "ValidationError",
"schema": {
"$ref": "#/definitions/ValidationError"
}
}
}
},
"delete": {
"tags": [
"provisioning"
],
"summary": "Delete a specific alert rule by UID.",
"operationId": "RouteDeleteAlertRule",
"parameters": [
{
"type": "string",
"name": "UID",
"in": "path",
"required": true
}
],
"responses": {
"204": {
"description": " The alert rule was deleted successfully."
},
"400": {
"description": "ValidationError",
"schema": {
"$ref": "#/definitions/ValidationError"
}
}
}
}
},
"/api/provisioning/contact-points": {
"get": {
"tags": [
@ -1253,6 +1381,53 @@
}
}
},
"/api/provisioning/folder/{FolderUID}/rule-groups/{Group}": {
"put": {
"consumes": [
"application/json"
],
"tags": [
"provisioning"
],
"summary": "Update the interval of an rule group.",
"operationId": "RoutePutAlertRuleGroup",
"parameters": [
{
"type": "string",
"name": "FolderUID",
"in": "path",
"required": true
},
{
"type": "string",
"name": "Group",
"in": "path",
"required": true
},
{
"name": "Body",
"in": "body",
"schema": {
"$ref": "#/definitions/AlertRuleGroup"
}
}
],
"responses": {
"200": {
"description": "AlertRuleGroup",
"schema": {
"$ref": "#/definitions/AlertRuleGroup"
}
},
"400": {
"description": "ValidationError",
"schema": {
"$ref": "#/definitions/ValidationError"
}
}
}
}
},
"/api/provisioning/mute-timings": {
"get": {
"tags": [
@ -2394,6 +2569,91 @@
},
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
},
"AlertRule": {
"type": "object",
"properties": {
"annotations": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"x-go-name": "Annotations"
},
"condition": {
"type": "string",
"x-go-name": "Condition"
},
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/AlertQuery"
},
"x-go-name": "Data"
},
"execErrState": {
"$ref": "#/definitions/ExecutionErrorState"
},
"folderUID": {
"type": "string",
"x-go-name": "FolderUID"
},
"for": {
"$ref": "#/definitions/Duration"
},
"id": {
"type": "integer",
"format": "int64",
"x-go-name": "ID"
},
"labels": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"x-go-name": "Labels"
},
"noDataState": {
"$ref": "#/definitions/NoDataState"
},
"orgID": {
"type": "integer",
"format": "int64",
"x-go-name": "OrgID"
},
"provenance": {
"$ref": "#/definitions/Provenance"
},
"ruleGroup": {
"type": "string",
"x-go-name": "RuleGroup"
},
"title": {
"type": "string",
"x-go-name": "Title"
},
"uid": {
"type": "string",
"x-go-name": "UID"
},
"updated": {
"type": "string",
"format": "date-time",
"x-go-name": "Updated"
}
},
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
},
"AlertRuleGroup": {
"type": "object",
"properties": {
"interval": {
"type": "integer",
"format": "int64",
"x-go-name": "Interval"
}
},
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
},
"AlertingRule": {
"description": "adapted from cortex",
"type": "object",
@ -2866,6 +3126,10 @@
"EvalQueriesResponse": {
"$ref": "#/definitions/EvalQueriesResponse"
},
"ExecutionErrorState": {
"type": "string",
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/models"
},
"ExtendedReceiver": {
"type": "object",
"properties": {
@ -3660,6 +3924,10 @@
},
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
},
"NoDataState": {
"type": "string",
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/models"
},
"NotFound": {
"type": "object",
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
@ -5127,8 +5395,9 @@
"x-go-package": "github.com/prometheus/alertmanager/timeinterval"
},
"URL": {
"description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use RawPath, an optional field which only gets\nset if the default encoding is different from Path.\n\nURL's String method uses the EscapedPath method to obtain the path. See the\nEscapedPath method for more details.",
"type": "object",
"title": "URL is a custom URL type that allows validation at configuration load time.",
"title": "A URL represents a parsed URL (technically, a URI reference).",
"properties": {
"ForceQuery": {
"type": "boolean"
@ -5161,7 +5430,7 @@
"$ref": "#/definitions/Userinfo"
}
},
"x-go-package": "github.com/prometheus/common/config"
"x-go-package": "net/url"
},
"Userinfo": {
"description": "The Userinfo type is an immutable encapsulation of username and\npassword details for a URL. An existing Userinfo value is guaranteed\nto have a username set (potentially empty, as allowed by RFC 2396),\nand optionally a password.",
@ -5384,12 +5653,11 @@
"$ref": "#/definitions/alertGroup"
},
"alertGroups": {
"description": "AlertGroups alert groups",
"type": "array",
"items": {
"$ref": "#/definitions/alertGroup"
},
"x-go-name": "AlertGroups",
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models",
"$ref": "#/definitions/alertGroups"
},
"alertStatus": {
@ -5510,6 +5778,7 @@
"$ref": "#/definitions/Duration"
},
"gettableAlert": {
"description": "GettableAlert gettable alert",
"type": "object",
"required": [
"labels",
@ -5569,8 +5838,6 @@
"x-go-name": "UpdatedAt"
}
},
"x-go-name": "GettableAlert",
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models",
"$ref": "#/definitions/gettableAlert"
},
"gettableAlerts": {
@ -5582,7 +5849,6 @@
"$ref": "#/definitions/gettableAlerts"
},
"gettableSilence": {
"description": "GettableSilence gettable silence",
"type": "object",
"required": [
"comment",
@ -5635,6 +5901,8 @@
"x-go-name": "UpdatedAt"
}
},
"x-go-name": "GettableSilence",
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models",
"$ref": "#/definitions/gettableSilence"
},
"gettableSilences": {
@ -5773,6 +6041,7 @@
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
},
"postableSilence": {
"description": "PostableSilence postable silence",
"type": "object",
"required": [
"comment",
@ -5813,11 +6082,10 @@
"x-go-name": "StartsAt"
}
},
"x-go-name": "PostableSilence",
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models",
"$ref": "#/definitions/postableSilence"
},
"receiver": {
"description": "Receiver receiver",
"type": "object",
"required": [
"name"
@ -5829,8 +6097,6 @@
"x-go-name": "Name"
}
},
"x-go-name": "Receiver",
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models",
"$ref": "#/definitions/receiver"
},
"silence": {

View File

@ -157,6 +157,7 @@ func (ng *AlertNG) init() error {
contactPointService := provisioning.NewContactPointService(store, ng.SecretsService, store, store, ng.Log)
templateService := provisioning.NewTemplateService(store, store, store, ng.Log)
muteTimingService := provisioning.NewMuteTimingService(store, store, store, ng.Log)
alertRuleService := provisioning.NewAlertRuleService(store, store, store, int64(ng.Cfg.UnifiedAlerting.DefaultRuleEvaluationInterval.Seconds()), ng.Log)
api := api.API{
Cfg: ng.Cfg,
@ -180,6 +181,7 @@ func (ng *AlertNG) init() error {
ContactPointService: contactPointService,
Templates: templateService,
MuteTimings: muteTimingService,
AlertRules: alertRuleService,
}
api.RegisterAPIEndpoints(ng.Metrics.GetAPIMetrics())

View File

@ -0,0 +1,151 @@
package provisioning
import (
"context"
"errors"
"fmt"
"time"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/util"
)
type AlertRuleService struct {
defaultInterval int64
ruleStore store.RuleStore
provenanceStore ProvisioningStore
xact TransactionManager
log log.Logger
}
func NewAlertRuleService(ruleStore store.RuleStore,
provenanceStore ProvisioningStore,
xact TransactionManager,
defaultInterval int64,
log log.Logger) *AlertRuleService {
return &AlertRuleService{
defaultInterval: defaultInterval,
ruleStore: ruleStore,
provenanceStore: provenanceStore,
xact: xact,
log: log,
}
}
func (service *AlertRuleService) GetAlertRule(ctx context.Context, orgID int64, ruleUID string) (models.AlertRule, models.Provenance, error) {
query := &models.GetAlertRuleByUIDQuery{
OrgID: orgID,
UID: ruleUID,
}
err := service.ruleStore.GetAlertRuleByUID(ctx, query)
if err != nil {
return models.AlertRule{}, models.ProvenanceNone, err
}
provenance, err := service.provenanceStore.GetProvenance(ctx, query.Result, orgID)
if err != nil {
return models.AlertRule{}, models.ProvenanceNone, err
}
return *query.Result, provenance, nil
}
func (service *AlertRuleService) CreateAlertRule(ctx context.Context, rule models.AlertRule, provenance models.Provenance) (models.AlertRule, error) {
if rule.UID == "" {
rule.UID = util.GenerateShortUID()
}
interval, err := service.ruleStore.GetRuleGroupInterval(ctx, rule.OrgID, rule.NamespaceUID, rule.RuleGroup)
// if the alert group does not exists we just use the default interval
if err != nil && errors.Is(err, store.ErrAlertRuleGroupNotFound) {
interval = service.defaultInterval
} else if err != nil {
return models.AlertRule{}, err
}
rule.IntervalSeconds = interval
rule.Updated = time.Now()
err = service.xact.InTransaction(ctx, func(ctx context.Context) error {
ids, err := service.ruleStore.InsertAlertRules(ctx, []models.AlertRule{
rule,
})
if err != nil {
return err
}
if id, ok := ids[rule.UID]; ok {
rule.ID = id
} else {
return errors.New("couldn't find newly created id")
}
err = service.ruleStore.UpdateRuleGroup(ctx, rule.OrgID, rule.NamespaceUID, rule.RuleGroup, rule.IntervalSeconds)
if err != nil {
return err
}
return service.provenanceStore.SetProvenance(ctx, &rule, rule.OrgID, provenance)
})
if err != nil {
return models.AlertRule{}, err
}
return rule, nil
}
func (service *AlertRuleService) UpdateAlertRule(ctx context.Context, rule models.AlertRule, provenance models.Provenance) (models.AlertRule, error) {
storedRule, storedProvenance, err := service.GetAlertRule(ctx, rule.OrgID, rule.UID)
if err != nil {
return models.AlertRule{}, err
}
if storedProvenance != provenance && storedProvenance != models.ProvenanceNone {
return models.AlertRule{}, fmt.Errorf("cannot changed provenance from '%s' to '%s'", storedProvenance, provenance)
}
rule.Updated = time.Now()
rule.ID = storedRule.ID
rule.IntervalSeconds, err = service.ruleStore.GetRuleGroupInterval(ctx, rule.OrgID, rule.NamespaceUID, rule.RuleGroup)
if err != nil {
return models.AlertRule{}, err
}
service.log.Info("update rule", "ID", storedRule.ID, "labels", fmt.Sprintf("%+v", rule.Labels))
err = service.xact.InTransaction(ctx, func(ctx context.Context) error {
err := service.ruleStore.UpdateAlertRules(ctx, []store.UpdateRule{
{
Existing: &storedRule,
New: rule,
},
})
if err != nil {
return err
}
err = service.ruleStore.UpdateRuleGroup(ctx, rule.OrgID, rule.NamespaceUID, rule.RuleGroup, rule.IntervalSeconds)
if err != nil {
return err
}
return service.provenanceStore.SetProvenance(ctx, &rule, rule.OrgID, provenance)
})
if err != nil {
return models.AlertRule{}, err
}
return rule, err
}
func (service *AlertRuleService) DeleteAlertRule(ctx context.Context, orgID int64, ruleUID string, provenance models.Provenance) error {
rule := &models.AlertRule{
OrgID: orgID,
UID: ruleUID,
}
// check that provenance is not changed in a invalid way
storedProvenance, err := service.provenanceStore.GetProvenance(ctx, rule, rule.OrgID)
if err != nil {
return err
}
if storedProvenance != provenance && storedProvenance != models.ProvenanceNone {
return fmt.Errorf("cannot delete with provided provenance '%s', needs '%s'", provenance, storedProvenance)
}
return service.xact.InTransaction(ctx, func(ctx context.Context) error {
err := service.ruleStore.DeleteAlertRulesByUID(ctx, orgID, ruleUID)
if err != nil {
return err
}
return service.provenanceStore.DeleteProvenance(ctx, rule, rule.OrgID)
})
}
func (service *AlertRuleService) UpdateAlertGroup(ctx context.Context, orgID int64, folderUID, roulegroup string, interval int64) error {
return service.ruleStore.UpdateRuleGroup(ctx, orgID, folderUID, roulegroup, interval)
}

View File

@ -0,0 +1,166 @@
package provisioning
import (
"context"
"encoding/json"
"testing"
"time"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/stretchr/testify/require"
)
func TestAlertRuleService(t *testing.T) {
ruleService := createAlertRuleService(t)
t.Run("alert rule creation should return the created id", func(t *testing.T) {
var orgID int64 = 1
rule, err := ruleService.CreateAlertRule(context.Background(), dummyRule("test#1", orgID), models.ProvenanceNone)
require.NoError(t, err)
require.NotEqual(t, 0, rule.ID, "expected to get the created id and not the zero value")
})
t.Run("alert rule creation should set the right provenance", func(t *testing.T) {
var orgID int64 = 1
rule, err := ruleService.CreateAlertRule(context.Background(), dummyRule("test#2", orgID), models.ProvenanceAPI)
require.NoError(t, err)
_, provenance, err := ruleService.GetAlertRule(context.Background(), orgID, rule.UID)
require.NoError(t, err)
require.Equal(t, models.ProvenanceAPI, provenance)
})
t.Run("alert rule group should be updated correctly", func(t *testing.T) {
var orgID int64 = 1
rule := dummyRule("test#3", orgID)
rule.RuleGroup = "a"
rule, err := ruleService.CreateAlertRule(context.Background(), rule, models.ProvenanceNone)
require.NoError(t, err)
require.Equal(t, int64(60), rule.IntervalSeconds)
var interval int64 = 120
err = ruleService.UpdateAlertGroup(context.Background(), orgID, rule.NamespaceUID, rule.RuleGroup, 120)
require.NoError(t, err)
rule, _, err = ruleService.GetAlertRule(context.Background(), orgID, rule.UID)
require.NoError(t, err)
require.Equal(t, interval, rule.IntervalSeconds)
})
t.Run("alert rule should get interval from existing rule group", func(t *testing.T) {
var orgID int64 = 1
rule := dummyRule("test#4", orgID)
rule.RuleGroup = "b"
rule, err := ruleService.CreateAlertRule(context.Background(), rule, models.ProvenanceNone)
require.NoError(t, err)
var interval int64 = 120
err = ruleService.UpdateAlertGroup(context.Background(), orgID, rule.NamespaceUID, rule.RuleGroup, 120)
require.NoError(t, err)
rule = dummyRule("test#4-1", orgID)
rule.RuleGroup = "b"
rule, err = ruleService.CreateAlertRule(context.Background(), rule, models.ProvenanceNone)
require.NoError(t, err)
require.Equal(t, interval, rule.IntervalSeconds)
})
t.Run("alert rule provenace should be correctly checked", func(t *testing.T) {
tests := []struct {
name string
from models.Provenance
to models.Provenance
errNil bool
}{
{
name: "should be able to update from provenance none to api",
from: models.ProvenanceNone,
to: models.ProvenanceAPI,
errNil: true,
},
{
name: "should be able to update from provenance none to file",
from: models.ProvenanceNone,
to: models.ProvenanceFile,
errNil: true,
},
{
name: "should not be able to update from provenance api to file",
from: models.ProvenanceAPI,
to: models.ProvenanceFile,
errNil: false,
},
{
name: "should not be able to update from provenance api to none",
from: models.ProvenanceAPI,
to: models.ProvenanceNone,
errNil: false,
},
{
name: "should not be able to update from provenance file to api",
from: models.ProvenanceFile,
to: models.ProvenanceAPI,
errNil: false,
},
{
name: "should not be able to update from provenance file to none",
from: models.ProvenanceFile,
to: models.ProvenanceNone,
errNil: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var orgID int64 = 1
rule := dummyRule(t.Name(), orgID)
rule, err := ruleService.CreateAlertRule(context.Background(), rule, test.from)
require.NoError(t, err)
_, err = ruleService.UpdateAlertRule(context.Background(), rule, test.to)
if test.errNil {
require.NoError(t, err)
} else {
require.Error(t, err)
}
})
}
})
}
func createAlertRuleService(t *testing.T) AlertRuleService {
t.Helper()
sqlStore := sqlstore.InitTestDB(t)
store := store.DBstore{
SQLStore: sqlStore,
BaseInterval: time.Second * 10,
}
return AlertRuleService{
ruleStore: store,
provenanceStore: store,
xact: sqlStore,
log: log.New("testing"),
defaultInterval: 60,
}
}
func dummyRule(title string, orgID int64) models.AlertRule {
return models.AlertRule{
OrgID: orgID,
Title: title,
Condition: "A",
Version: 1,
IntervalSeconds: 60,
Data: []models.AlertQuery{
{
RefID: "A",
Model: json.RawMessage("{}"),
RelativeTimeRange: models.RelativeTimeRange{
From: models.Duration(60),
To: models.Duration(0),
},
},
},
RuleGroup: "my-cool-group",
For: time.Second * 60,
NoDataState: models.OK,
ExecErrState: models.OkErrState,
}
}

View File

@ -2,6 +2,7 @@ package store
import (
"context"
"errors"
"fmt"
"strings"
"time"
@ -32,6 +33,10 @@ type UpdateRule struct {
New ngmodels.AlertRule
}
var (
ErrAlertRuleGroupNotFound = errors.New("rulegroup not found")
)
// RuleStore is the interface for persisting alert rules and instances
type RuleStore interface {
DeleteAlertRulesByUID(ctx context.Context, orgID int64, ruleUID ...string) error
@ -42,9 +47,14 @@ type RuleStore interface {
ListAlertRules(ctx context.Context, query *ngmodels.ListAlertRulesQuery) error
// GetRuleGroups returns the unique rule groups across all organizations.
GetRuleGroups(ctx context.Context, query *ngmodels.ListRuleGroupsQuery) error
GetRuleGroupInterval(ctx context.Context, orgID int64, namespaceUID string, ruleGroup string) (int64, error)
// UpdateRuleGroup will update the interval for all rules in the group.
UpdateRuleGroup(ctx context.Context, orgID int64, namespaceUID string, ruleGroup string, interval int64) error
GetUserVisibleNamespaces(context.Context, int64, *models.SignedInUser) (map[string]*models.Folder, error)
GetNamespaceByTitle(context.Context, string, int64, *models.SignedInUser, bool) (*models.Folder, error)
InsertAlertRules(ctx context.Context, rule []ngmodels.AlertRule) error
// InsertAlertRules will insert all alert rules passed into the function
// and return the map of uuid to id.
InsertAlertRules(ctx context.Context, rule []ngmodels.AlertRule) (map[string]int64, error)
UpdateAlertRules(ctx context.Context, rule []UpdateRule) error
}
@ -127,17 +137,20 @@ func (st DBstore) GetAlertRulesGroupByRuleUID(ctx context.Context, query *ngmode
}
// InsertAlertRules is a handler for creating/updating alert rules.
func (st DBstore) InsertAlertRules(ctx context.Context, rules []ngmodels.AlertRule) error {
return st.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
func (st DBstore) InsertAlertRules(ctx context.Context, rules []ngmodels.AlertRule) (map[string]int64, error) {
ids := make(map[string]int64, len(rules))
return ids, st.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
newRules := make([]ngmodels.AlertRule, 0, len(rules))
ruleVersions := make([]ngmodels.AlertRuleVersion, 0, len(rules))
for i := range rules {
r := rules[i]
if r.UID == "" {
uid, err := GenerateNewAlertRuleUID(sess, r.OrgID, r.Title)
if err != nil {
return fmt.Errorf("failed to generate UID for alert rule %q: %w", r.Title, err)
}
r.UID = uid
}
r.Version = 1
if err := st.validateAlertRule(r); err != nil {
return err
@ -147,8 +160,8 @@ func (st DBstore) InsertAlertRules(ctx context.Context, rules []ngmodels.AlertRu
}
newRules = append(newRules, r)
ruleVersions = append(ruleVersions, ngmodels.AlertRuleVersion{
RuleOrgID: r.OrgID,
RuleUID: r.UID,
RuleOrgID: r.OrgID,
RuleNamespaceUID: r.NamespaceUID,
RuleGroup: r.RuleGroup,
ParentVersion: 0,
@ -166,12 +179,17 @@ func (st DBstore) InsertAlertRules(ctx context.Context, rules []ngmodels.AlertRu
})
}
if len(newRules) > 0 {
if _, err := sess.Insert(&newRules); err != nil {
// we have to insert the rules one by one as otherwise we are
// not able to fetch the inserted id as it's not supported by xorm
for i := range newRules {
if _, err := sess.Insert(&newRules[i]); err != nil {
if st.SQLStore.Dialect.IsUniqueConstraintViolation(err) {
return ngmodels.ErrAlertRuleUniqueConstraintViolation
}
return fmt.Errorf("failed to create new rules: %w", err)
}
ids[newRules[i].UID] = newRules[i].ID
}
}
if len(ruleVersions) > 0 {
@ -179,15 +197,13 @@ func (st DBstore) InsertAlertRules(ctx context.Context, rules []ngmodels.AlertRu
return fmt.Errorf("failed to create new rule versions: %w", err)
}
}
return nil
})
}
// UpdateAlertRules is a handler for creating/updating alert rules.
// UpdateAlertRules is a handler for updating alert rules.
func (st DBstore) UpdateAlertRules(ctx context.Context, rules []UpdateRule) error {
return st.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
newRules := make([]ngmodels.AlertRule, 0, len(rules))
ruleVersions := make([]ngmodels.AlertRuleVersion, 0, len(rules))
for _, r := range rules {
var parentVersion int64
@ -226,14 +242,6 @@ func (st DBstore) UpdateAlertRules(ctx context.Context, rules []UpdateRule) erro
Labels: r.New.Labels,
})
}
if len(newRules) > 0 {
if _, err := sess.Insert(&newRules); err != nil {
if st.SQLStore.Dialect.IsUniqueConstraintViolation(err) {
return ngmodels.ErrAlertRuleUniqueConstraintViolation
}
return fmt.Errorf("failed to create new rules: %w", err)
}
}
if len(ruleVersions) > 0 {
if _, err := sess.Insert(&ruleVersions); err != nil {
return fmt.Errorf("failed to create new rule versions: %w", err)
@ -296,6 +304,32 @@ func (st DBstore) GetRuleGroups(ctx context.Context, query *ngmodels.ListRuleGro
})
}
func (st DBstore) GetRuleGroupInterval(ctx context.Context, orgID int64, namespaceUID string, ruleGroup string) (int64, error) {
var interval int64 = 0
return interval, st.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
ruleGroups := make([]ngmodels.AlertRule, 0)
err := sess.Find(
&ruleGroups,
ngmodels.AlertRule{OrgID: orgID, RuleGroup: ruleGroup, NamespaceUID: namespaceUID},
)
if len(ruleGroups) == 0 {
return ErrAlertRuleGroupNotFound
}
interval = ruleGroups[0].IntervalSeconds
return err
})
}
func (st DBstore) UpdateRuleGroup(ctx context.Context, orgID int64, namespaceUID string, ruleGroup string, interval int64) error {
return st.SQLStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
_, err := sess.Update(
ngmodels.AlertRule{IntervalSeconds: interval},
ngmodels.AlertRule{OrgID: orgID, RuleGroup: ruleGroup, NamespaceUID: namespaceUID},
)
return err
})
}
// GetNamespaces returns the folders that are visible to the user and have at least one alert in it
func (st DBstore) GetUserVisibleNamespaces(ctx context.Context, orgID int64, user *models.SignedInUser) (map[string]*models.Folder, error) {
namespaceMap := make(map[string]*models.Folder)
@ -433,5 +467,13 @@ func (st DBstore) validateAlertRule(alertRule ngmodels.AlertRule) error {
return fmt.Errorf("%w: cannot have Panel ID without a Dashboard UID", ngmodels.ErrAlertRuleFailedValidation)
}
if _, err := ngmodels.ErrStateFromString(string(alertRule.ExecErrState)); err != nil {
return err
}
if _, err := ngmodels.NoDataStateFromString(string(alertRule.NoDataState)); err != nil {
return err
}
return nil
}

View File

@ -315,20 +315,43 @@ func (f *FakeRuleStore) UpdateAlertRules(_ context.Context, q []UpdateRule) erro
return nil
}
func (f *FakeRuleStore) InsertAlertRules(_ context.Context, q []models.AlertRule) error {
func (f *FakeRuleStore) InsertAlertRules(_ context.Context, q []models.AlertRule) (map[string]int64, error) {
f.mtx.Lock()
defer f.mtx.Unlock()
f.RecordedOps = append(f.RecordedOps, q)
ids := make(map[string]int64, len(q))
if err := f.Hook(q); err != nil {
return err
return ids, err
}
return nil
return ids, nil
}
func (f *FakeRuleStore) InTransaction(ctx context.Context, fn func(c context.Context) error) error {
return fn(ctx)
}
func (f *FakeRuleStore) GetRuleGroupInterval(ctx context.Context, orgID int64, namespaceUID string, ruleGroup string) (int64, error) {
f.mtx.Lock()
defer f.mtx.Unlock()
for _, rule := range f.Rules[orgID] {
if rule.RuleGroup == ruleGroup && rule.NamespaceUID == namespaceUID {
return rule.IntervalSeconds, nil
}
}
return 0, ErrAlertRuleGroupNotFound
}
func (f *FakeRuleStore) UpdateRuleGroup(ctx context.Context, orgID int64, namespaceUID string, ruleGroup string, interval int64) error {
f.mtx.Lock()
defer f.mtx.Unlock()
for _, rule := range f.Rules[orgID] {
if rule.RuleGroup == ruleGroup && rule.NamespaceUID == namespaceUID {
rule.IntervalSeconds = interval
}
}
return nil
}
type FakeInstanceStore struct {
mtx sync.Mutex
RecordedOps []interface{}

View File

@ -84,7 +84,7 @@ func CreateTestAlertRule(t *testing.T, ctx context.Context, dbstore *store.DBsto
func CreateTestAlertRuleWithLabels(t *testing.T, ctx context.Context, dbstore *store.DBstore, intervalSeconds int64, orgID int64, labels map[string]string) *models.AlertRule {
ruleGroup := fmt.Sprintf("ruleGroup-%s", util.GenerateShortUID())
err := dbstore.InsertAlertRules(ctx, []models.AlertRule{
_, err := dbstore.InsertAlertRules(ctx, []models.AlertRule{
{
ID: 0,