mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
[Alerting]: Several modifications in alert rules (#32983)
* [Alerting]: Use common properties for all rules * Add Labels in rules * Fix update ruleGroup API Return 400 Bad Request response when the request contains a UID that does not exist * Check permissions and return namespace id * Apply suggestions from code review Co-authored-by: gotjosh <josue@grafana.com>
This commit is contained in:
parent
34b4f7c717
commit
6bbb2fd4ba
2
go.mod
2
go.mod
@ -41,7 +41,7 @@ require (
|
|||||||
github.com/google/go-cmp v0.5.5
|
github.com/google/go-cmp v0.5.5
|
||||||
github.com/google/uuid v1.2.0
|
github.com/google/uuid v1.2.0
|
||||||
github.com/gosimple/slug v1.9.0
|
github.com/gosimple/slug v1.9.0
|
||||||
github.com/grafana/alerting-api v0.0.0-20210412090350-fcb11bfbb6a4
|
github.com/grafana/alerting-api v0.0.0-20210414165752-6625e7a4f9a9
|
||||||
github.com/grafana/grafana-aws-sdk v0.4.0
|
github.com/grafana/grafana-aws-sdk v0.4.0
|
||||||
github.com/grafana/grafana-live-sdk v0.0.4
|
github.com/grafana/grafana-live-sdk v0.0.4
|
||||||
github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4
|
github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4
|
||||||
|
2
go.sum
2
go.sum
@ -820,6 +820,8 @@ github.com/gosimple/slug v1.9.0 h1:r5vDcYrFz9BmfIAMC829un9hq7hKM4cHUrsv36LbEqs=
|
|||||||
github.com/gosimple/slug v1.9.0/go.mod h1:AMZ+sOVe65uByN3kgEyf9WEBKBCSS+dJjMX9x4vDJbg=
|
github.com/gosimple/slug v1.9.0/go.mod h1:AMZ+sOVe65uByN3kgEyf9WEBKBCSS+dJjMX9x4vDJbg=
|
||||||
github.com/grafana/alerting-api v0.0.0-20210412090350-fcb11bfbb6a4 h1:S4nnWhH40AIWCkk3F7pUYVr67rqqangwm8a8cskYGyc=
|
github.com/grafana/alerting-api v0.0.0-20210412090350-fcb11bfbb6a4 h1:S4nnWhH40AIWCkk3F7pUYVr67rqqangwm8a8cskYGyc=
|
||||||
github.com/grafana/alerting-api v0.0.0-20210412090350-fcb11bfbb6a4/go.mod h1:Ce2PwraBlFMa+P0ArBzubfB/BXZV35mfYWQjM8C/BSE=
|
github.com/grafana/alerting-api v0.0.0-20210412090350-fcb11bfbb6a4/go.mod h1:Ce2PwraBlFMa+P0ArBzubfB/BXZV35mfYWQjM8C/BSE=
|
||||||
|
github.com/grafana/alerting-api v0.0.0-20210414165752-6625e7a4f9a9 h1:kPlrt7kss4NDk2w5G4pbvmdkQCiiJNmuORabWi3F2Ko=
|
||||||
|
github.com/grafana/alerting-api v0.0.0-20210414165752-6625e7a4f9a9/go.mod h1:Ce2PwraBlFMa+P0ArBzubfB/BXZV35mfYWQjM8C/BSE=
|
||||||
github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036 h1:GplhUk6Xes5JIhUUrggPcPBhOn+eT8+WsHiebvq7GgA=
|
github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036 h1:GplhUk6Xes5JIhUUrggPcPBhOn+eT8+WsHiebvq7GgA=
|
||||||
github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||||
github.com/grafana/grafana v1.9.2-0.20210308201921-4ce0a49eac03/go.mod h1:AHRRvd4utJGY25J5nW8aL7wZzn/LcJ0z2za9oOp14j4=
|
github.com/grafana/grafana v1.9.2-0.20210308201921-4ce0a49eac03/go.mod h1:AHRRvd4utJGY25J5nW8aL7wZzn/LcJ0z2za9oOp14j4=
|
||||||
|
@ -19,7 +19,7 @@ func (hs *HTTPServer) GetFolders(c *models.ReqContext) response.Response {
|
|||||||
folders, err := s.GetFolders(c.QueryInt64("limit"))
|
folders, err := s.GetFolders(c.QueryInt64("limit"))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toFolderError(err)
|
return ToFolderErrorResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([]dtos.FolderSearchHit, 0)
|
result := make([]dtos.FolderSearchHit, 0)
|
||||||
@ -39,7 +39,7 @@ func (hs *HTTPServer) GetFolderByUID(c *models.ReqContext) response.Response {
|
|||||||
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
|
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
|
||||||
folder, err := s.GetFolderByUID(c.Params(":uid"))
|
folder, err := s.GetFolderByUID(c.Params(":uid"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toFolderError(err)
|
return ToFolderErrorResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
g := guardian.New(folder.Id, c.OrgId, c.SignedInUser)
|
g := guardian.New(folder.Id, c.OrgId, c.SignedInUser)
|
||||||
@ -50,7 +50,7 @@ func (hs *HTTPServer) GetFolderByID(c *models.ReqContext) response.Response {
|
|||||||
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
|
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
|
||||||
folder, err := s.GetFolderByID(c.ParamsInt64(":id"))
|
folder, err := s.GetFolderByID(c.ParamsInt64(":id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toFolderError(err)
|
return ToFolderErrorResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
g := guardian.New(folder.Id, c.OrgId, c.SignedInUser)
|
g := guardian.New(folder.Id, c.OrgId, c.SignedInUser)
|
||||||
@ -61,7 +61,7 @@ func (hs *HTTPServer) CreateFolder(c *models.ReqContext, cmd models.CreateFolder
|
|||||||
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
|
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
|
||||||
folder, err := s.CreateFolder(cmd.Title, cmd.Uid)
|
folder, err := s.CreateFolder(cmd.Title, cmd.Uid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toFolderError(err)
|
return ToFolderErrorResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if hs.Cfg.EditorsCanAdmin {
|
if hs.Cfg.EditorsCanAdmin {
|
||||||
@ -79,7 +79,7 @@ func (hs *HTTPServer) UpdateFolder(c *models.ReqContext, cmd models.UpdateFolder
|
|||||||
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
|
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
|
||||||
err := s.UpdateFolder(c.Params(":uid"), &cmd)
|
err := s.UpdateFolder(c.Params(":uid"), &cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toFolderError(err)
|
return ToFolderErrorResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
g := guardian.New(cmd.Result.Id, c.OrgId, c.SignedInUser)
|
g := guardian.New(cmd.Result.Id, c.OrgId, c.SignedInUser)
|
||||||
@ -94,13 +94,13 @@ func (hs *HTTPServer) DeleteFolder(c *models.ReqContext) response.Response { //
|
|||||||
if errors.Is(err, librarypanels.ErrFolderHasConnectedLibraryPanels) {
|
if errors.Is(err, librarypanels.ErrFolderHasConnectedLibraryPanels) {
|
||||||
return response.Error(403, "Folder could not be deleted because it contains linked library panels", err)
|
return response.Error(403, "Folder could not be deleted because it contains linked library panels", err)
|
||||||
}
|
}
|
||||||
return toFolderError(err)
|
return ToFolderErrorResponse(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := s.DeleteFolder(c.Params(":uid"))
|
f, err := s.DeleteFolder(c.Params(":uid"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toFolderError(err)
|
return ToFolderErrorResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.JSON(200, util.DynMap{
|
return response.JSON(200, util.DynMap{
|
||||||
@ -141,7 +141,8 @@ func toFolderDto(g guardian.DashboardGuardian, folder *models.Folder) dtos.Folde
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toFolderError(err error) response.Response {
|
// ToFolderErrorResponse returns a different response status according to the folder error type
|
||||||
|
func ToFolderErrorResponse(err error) response.Response {
|
||||||
var dashboardErr models.DashboardErr
|
var dashboardErr models.DashboardErr
|
||||||
if ok := errors.As(err, &dashboardErr); ok {
|
if ok := errors.As(err, &dashboardErr); ok {
|
||||||
return response.Error(dashboardErr.StatusCode, err.Error(), err)
|
return response.Error(dashboardErr.StatusCode, err.Error(), err)
|
||||||
|
@ -17,13 +17,13 @@ func (hs *HTTPServer) GetFolderPermissionList(c *models.ReqContext) response.Res
|
|||||||
folder, err := s.GetFolderByUID(c.Params(":uid"))
|
folder, err := s.GetFolderByUID(c.Params(":uid"))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toFolderError(err)
|
return ToFolderErrorResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
g := guardian.New(folder.Id, c.OrgId, c.SignedInUser)
|
g := guardian.New(folder.Id, c.OrgId, c.SignedInUser)
|
||||||
|
|
||||||
if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin {
|
if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin {
|
||||||
return toFolderError(models.ErrFolderAccessDenied)
|
return ToFolderErrorResponse(models.ErrFolderAccessDenied)
|
||||||
}
|
}
|
||||||
|
|
||||||
acl, err := g.GetAcl()
|
acl, err := g.GetAcl()
|
||||||
@ -64,17 +64,17 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext, apiCmd dtos.
|
|||||||
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
|
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
|
||||||
folder, err := s.GetFolderByUID(c.Params(":uid"))
|
folder, err := s.GetFolderByUID(c.Params(":uid"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toFolderError(err)
|
return ToFolderErrorResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
g := guardian.New(folder.Id, c.OrgId, c.SignedInUser)
|
g := guardian.New(folder.Id, c.OrgId, c.SignedInUser)
|
||||||
canAdmin, err := g.CanAdmin()
|
canAdmin, err := g.CanAdmin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toFolderError(err)
|
return ToFolderErrorResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !canAdmin {
|
if !canAdmin {
|
||||||
return toFolderError(models.ErrFolderAccessDenied)
|
return ToFolderErrorResponse(models.ErrFolderAccessDenied)
|
||||||
}
|
}
|
||||||
|
|
||||||
var items []*models.DashboardAcl
|
var items []*models.DashboardAcl
|
||||||
|
@ -100,7 +100,7 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *models.ReqContext) response.Res
|
|||||||
State: "inactive",
|
State: "inactive",
|
||||||
Name: rule.Title,
|
Name: rule.Title,
|
||||||
Query: "", // TODO: get this from parsing AlertRule.Data
|
Query: "", // TODO: get this from parsing AlertRule.Data
|
||||||
Duration: time.Duration(rule.For).Seconds(),
|
Duration: rule.For.Seconds(),
|
||||||
Annotations: rule.Annotations,
|
Annotations: rule.Annotations,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||||
|
|
||||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||||
|
coreapi "github.com/grafana/grafana/pkg/api"
|
||||||
"github.com/grafana/grafana/pkg/api/response"
|
"github.com/grafana/grafana/pkg/api/response"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
@ -22,40 +23,41 @@ type RulerSrv struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (srv RulerSrv) RouteDeleteNamespaceRulesConfig(c *models.ReqContext) response.Response {
|
func (srv RulerSrv) RouteDeleteNamespaceRulesConfig(c *models.ReqContext) response.Response {
|
||||||
namespace := c.Params(":Namespace")
|
namespaceTitle := c.Params(":Namespace")
|
||||||
namespaceUID, err := srv.store.GetNamespaceUIDByTitle(namespace, c.SignedInUser.OrgId, c.SignedInUser)
|
namespace, err := srv.store.GetNamespaceByTitle(namespaceTitle, c.SignedInUser.OrgId, c.SignedInUser, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(http.StatusInternalServerError, fmt.Sprintf("failed to get namespace: %s", namespace), err)
|
return toNamespaceErrorResponse(err)
|
||||||
}
|
}
|
||||||
if err := srv.store.DeleteNamespaceAlertRules(c.SignedInUser.OrgId, namespaceUID); err != nil {
|
|
||||||
|
if err := srv.store.DeleteNamespaceAlertRules(c.SignedInUser.OrgId, namespace.Uid); err != nil {
|
||||||
return response.Error(http.StatusInternalServerError, "failed to delete namespace alert rules", err)
|
return response.Error(http.StatusInternalServerError, "failed to delete namespace alert rules", err)
|
||||||
}
|
}
|
||||||
return response.JSON(http.StatusAccepted, util.DynMap{"message": "namespace rules deleted"})
|
return response.JSON(http.StatusAccepted, util.DynMap{"message": "namespace rules deleted"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv RulerSrv) RouteDeleteRuleGroupConfig(c *models.ReqContext) response.Response {
|
func (srv RulerSrv) RouteDeleteRuleGroupConfig(c *models.ReqContext) response.Response {
|
||||||
namespace := c.Params(":Namespace")
|
namespaceTitle := c.Params(":Namespace")
|
||||||
namespaceUID, err := srv.store.GetNamespaceUIDByTitle(namespace, c.SignedInUser.OrgId, c.SignedInUser)
|
namespace, err := srv.store.GetNamespaceByTitle(namespaceTitle, c.SignedInUser.OrgId, c.SignedInUser, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(http.StatusInternalServerError, fmt.Sprintf("failed to get namespace: %s", namespace), err)
|
return toNamespaceErrorResponse(err)
|
||||||
}
|
}
|
||||||
ruleGroup := c.Params(":Groupname")
|
ruleGroup := c.Params(":Groupname")
|
||||||
if err := srv.store.DeleteRuleGroupAlertRules(c.SignedInUser.OrgId, namespaceUID, ruleGroup); err != nil {
|
if err := srv.store.DeleteRuleGroupAlertRules(c.SignedInUser.OrgId, namespace.Uid, ruleGroup); err != nil {
|
||||||
return response.Error(http.StatusInternalServerError, "failed to delete group alert rules", err)
|
return response.Error(http.StatusInternalServerError, "failed to delete group alert rules", err)
|
||||||
}
|
}
|
||||||
return response.JSON(http.StatusAccepted, util.DynMap{"message": "rule group deleted"})
|
return response.JSON(http.StatusAccepted, util.DynMap{"message": "rule group deleted"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *models.ReqContext) response.Response {
|
func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *models.ReqContext) response.Response {
|
||||||
namespace := c.Params(":Namespace")
|
namespaceTitle := c.Params(":Namespace")
|
||||||
namespaceUID, err := srv.store.GetNamespaceUIDByTitle(namespace, c.SignedInUser.OrgId, c.SignedInUser)
|
namespace, err := srv.store.GetNamespaceByTitle(namespaceTitle, c.SignedInUser.OrgId, c.SignedInUser, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(http.StatusInternalServerError, fmt.Sprintf("failed to get namespace: %s", namespace), err)
|
return toNamespaceErrorResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
q := ngmodels.ListNamespaceAlertRulesQuery{
|
q := ngmodels.ListNamespaceAlertRulesQuery{
|
||||||
OrgID: c.SignedInUser.OrgId,
|
OrgID: c.SignedInUser.OrgId,
|
||||||
NamespaceUID: namespaceUID,
|
NamespaceUID: namespace.Uid,
|
||||||
}
|
}
|
||||||
if err := srv.store.GetNamespaceAlertRules(&q); err != nil {
|
if err := srv.store.GetNamespaceAlertRules(&q); err != nil {
|
||||||
return response.Error(http.StatusInternalServerError, "failed to update rule group", err)
|
return response.Error(http.StatusInternalServerError, "failed to update rule group", err)
|
||||||
@ -71,33 +73,33 @@ func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *models.ReqContext) response.
|
|||||||
Name: r.RuleGroup,
|
Name: r.RuleGroup,
|
||||||
Interval: ruleGroupInterval,
|
Interval: ruleGroupInterval,
|
||||||
Rules: []apimodels.GettableExtendedRuleNode{
|
Rules: []apimodels.GettableExtendedRuleNode{
|
||||||
toGettableExtendedRuleNode(*r),
|
toGettableExtendedRuleNode(*r, namespace.Id),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ruleGroupConfig.Rules = append(ruleGroupConfig.Rules, toGettableExtendedRuleNode(*r))
|
ruleGroupConfig.Rules = append(ruleGroupConfig.Rules, toGettableExtendedRuleNode(*r, namespace.Id))
|
||||||
ruleGroupConfigs[r.RuleGroup] = ruleGroupConfig
|
ruleGroupConfigs[r.RuleGroup] = ruleGroupConfig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ruleGroupConfig := range ruleGroupConfigs {
|
for _, ruleGroupConfig := range ruleGroupConfigs {
|
||||||
result[namespace] = append(result[namespace], ruleGroupConfig)
|
result[namespaceTitle] = append(result[namespaceTitle], ruleGroupConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.JSON(http.StatusAccepted, result)
|
return response.JSON(http.StatusAccepted, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv RulerSrv) RouteGetRulegGroupConfig(c *models.ReqContext) response.Response {
|
func (srv RulerSrv) RouteGetRulegGroupConfig(c *models.ReqContext) response.Response {
|
||||||
namespace := c.Params(":Namespace")
|
namespaceTitle := c.Params(":Namespace")
|
||||||
namespaceUID, err := srv.store.GetNamespaceUIDByTitle(namespace, c.SignedInUser.OrgId, c.SignedInUser)
|
namespace, err := srv.store.GetNamespaceByTitle(namespaceTitle, c.SignedInUser.OrgId, c.SignedInUser, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(http.StatusInternalServerError, fmt.Sprintf("failed to get namespace: %s", namespace), err)
|
return toNamespaceErrorResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ruleGroup := c.Params(":Groupname")
|
ruleGroup := c.Params(":Groupname")
|
||||||
q := ngmodels.ListRuleGroupAlertRulesQuery{
|
q := ngmodels.ListRuleGroupAlertRulesQuery{
|
||||||
OrgID: c.SignedInUser.OrgId,
|
OrgID: c.SignedInUser.OrgId,
|
||||||
NamespaceUID: namespaceUID,
|
NamespaceUID: namespace.Uid,
|
||||||
RuleGroup: ruleGroup,
|
RuleGroup: ruleGroup,
|
||||||
}
|
}
|
||||||
if err := srv.store.GetRuleGroupAlertRules(&q); err != nil {
|
if err := srv.store.GetRuleGroupAlertRules(&q); err != nil {
|
||||||
@ -108,7 +110,7 @@ func (srv RulerSrv) RouteGetRulegGroupConfig(c *models.ReqContext) response.Resp
|
|||||||
ruleNodes := make([]apimodels.GettableExtendedRuleNode, 0, len(q.Result))
|
ruleNodes := make([]apimodels.GettableExtendedRuleNode, 0, len(q.Result))
|
||||||
for _, r := range q.Result {
|
for _, r := range q.Result {
|
||||||
ruleGroupInterval = model.Duration(time.Duration(r.IntervalSeconds) * time.Second)
|
ruleGroupInterval = model.Duration(time.Duration(r.IntervalSeconds) * time.Second)
|
||||||
ruleNodes = append(ruleNodes, toGettableExtendedRuleNode(*r))
|
ruleNodes = append(ruleNodes, toGettableExtendedRuleNode(*r, namespace.Id))
|
||||||
}
|
}
|
||||||
|
|
||||||
result := apimodels.RuleGroupConfigResponse{
|
result := apimodels.RuleGroupConfigResponse{
|
||||||
@ -131,10 +133,11 @@ func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response
|
|||||||
|
|
||||||
configs := make(map[string]map[string]apimodels.GettableRuleGroupConfig)
|
configs := make(map[string]map[string]apimodels.GettableRuleGroupConfig)
|
||||||
for _, r := range q.Result {
|
for _, r := range q.Result {
|
||||||
namespace, err := srv.store.GetNamespaceByUID(r.NamespaceUID, c.SignedInUser.OrgId, c.SignedInUser)
|
folder, err := srv.store.GetNamespaceByUID(r.NamespaceUID, c.SignedInUser.OrgId, c.SignedInUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(http.StatusInternalServerError, fmt.Sprintf("failed to get namespace: %s", r.NamespaceUID), err)
|
return toNamespaceErrorResponse(err)
|
||||||
}
|
}
|
||||||
|
namespace := folder.Title
|
||||||
_, ok := configs[namespace]
|
_, ok := configs[namespace]
|
||||||
if !ok {
|
if !ok {
|
||||||
ruleGroupInterval := model.Duration(time.Duration(r.IntervalSeconds) * time.Second)
|
ruleGroupInterval := model.Duration(time.Duration(r.IntervalSeconds) * time.Second)
|
||||||
@ -143,7 +146,7 @@ func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response
|
|||||||
Name: r.RuleGroup,
|
Name: r.RuleGroup,
|
||||||
Interval: ruleGroupInterval,
|
Interval: ruleGroupInterval,
|
||||||
Rules: []apimodels.GettableExtendedRuleNode{
|
Rules: []apimodels.GettableExtendedRuleNode{
|
||||||
toGettableExtendedRuleNode(*r),
|
toGettableExtendedRuleNode(*r, folder.Id),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -154,11 +157,11 @@ func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response
|
|||||||
Name: r.RuleGroup,
|
Name: r.RuleGroup,
|
||||||
Interval: ruleGroupInterval,
|
Interval: ruleGroupInterval,
|
||||||
Rules: []apimodels.GettableExtendedRuleNode{
|
Rules: []apimodels.GettableExtendedRuleNode{
|
||||||
toGettableExtendedRuleNode(*r),
|
toGettableExtendedRuleNode(*r, folder.Id),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ruleGroupConfig.Rules = append(ruleGroupConfig.Rules, toGettableExtendedRuleNode(*r))
|
ruleGroupConfig.Rules = append(ruleGroupConfig.Rules, toGettableExtendedRuleNode(*r, folder.Id))
|
||||||
configs[namespace][r.RuleGroup] = ruleGroupConfig
|
configs[namespace][r.RuleGroup] = ruleGroupConfig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -174,10 +177,10 @@ func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (srv RulerSrv) RoutePostNameRulesConfig(c *models.ReqContext, ruleGroupConfig apimodels.PostableRuleGroupConfig) response.Response {
|
func (srv RulerSrv) RoutePostNameRulesConfig(c *models.ReqContext, ruleGroupConfig apimodels.PostableRuleGroupConfig) response.Response {
|
||||||
namespace := c.Params(":Namespace")
|
namespaceTitle := c.Params(":Namespace")
|
||||||
namespaceUID, err := srv.store.GetNamespaceUIDByTitle(namespace, c.SignedInUser.OrgId, c.SignedInUser)
|
namespace, err := srv.store.GetNamespaceByTitle(namespaceTitle, c.SignedInUser.OrgId, c.SignedInUser, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(http.StatusInternalServerError, fmt.Sprintf("failed to get namespace: %s", namespace), err)
|
return toNamespaceErrorResponse(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO check permissions
|
// TODO check permissions
|
||||||
@ -186,17 +189,20 @@ func (srv RulerSrv) RoutePostNameRulesConfig(c *models.ReqContext, ruleGroupConf
|
|||||||
|
|
||||||
if err := srv.store.UpdateRuleGroup(store.UpdateRuleGroupCmd{
|
if err := srv.store.UpdateRuleGroup(store.UpdateRuleGroupCmd{
|
||||||
OrgID: c.SignedInUser.OrgId,
|
OrgID: c.SignedInUser.OrgId,
|
||||||
NamespaceUID: namespaceUID,
|
NamespaceUID: namespace.Uid,
|
||||||
RuleGroupConfig: ruleGroupConfig,
|
RuleGroupConfig: ruleGroupConfig,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
if errors.Is(err, ngmodels.ErrAlertRuleNotFound) {
|
||||||
|
return response.Error(http.StatusNotFound, "failed to update rule group", err)
|
||||||
|
}
|
||||||
return response.Error(http.StatusInternalServerError, "failed to update rule group", err)
|
return response.Error(http.StatusInternalServerError, "failed to update rule group", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.JSON(http.StatusAccepted, util.DynMap{"message": "rule group updated successfully"})
|
return response.JSON(http.StatusAccepted, util.DynMap{"message": "rule group updated successfully"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func toGettableExtendedRuleNode(r ngmodels.AlertRule) apimodels.GettableExtendedRuleNode {
|
func toGettableExtendedRuleNode(r ngmodels.AlertRule, namespaceID int64) apimodels.GettableExtendedRuleNode {
|
||||||
return apimodels.GettableExtendedRuleNode{
|
gettableExtendedRuleNode := apimodels.GettableExtendedRuleNode{
|
||||||
GrafanaManagedAlert: &apimodels.GettableGrafanaRule{
|
GrafanaManagedAlert: &apimodels.GettableGrafanaRule{
|
||||||
ID: r.ID,
|
ID: r.ID,
|
||||||
OrgID: r.OrgID,
|
OrgID: r.OrgID,
|
||||||
@ -208,17 +214,22 @@ func toGettableExtendedRuleNode(r ngmodels.AlertRule) apimodels.GettableExtended
|
|||||||
Version: r.Version,
|
Version: r.Version,
|
||||||
UID: r.UID,
|
UID: r.UID,
|
||||||
NamespaceUID: r.NamespaceUID,
|
NamespaceUID: r.NamespaceUID,
|
||||||
|
NamespaceID: namespaceID,
|
||||||
RuleGroup: r.RuleGroup,
|
RuleGroup: r.RuleGroup,
|
||||||
NoDataState: apimodels.NoDataState(r.NoDataState),
|
NoDataState: apimodels.NoDataState(r.NoDataState),
|
||||||
ExecErrState: apimodels.ExecutionErrorState(r.ExecErrState),
|
ExecErrState: apimodels.ExecutionErrorState(r.ExecErrState),
|
||||||
For: r.For,
|
|
||||||
Annotations: r.Annotations,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
gettableExtendedRuleNode.ApiRuleNode = &apimodels.ApiRuleNode{
|
||||||
|
For: model.Duration(r.For),
|
||||||
|
Annotations: r.Annotations,
|
||||||
|
Labels: r.Labels,
|
||||||
|
}
|
||||||
|
return gettableExtendedRuleNode
|
||||||
}
|
}
|
||||||
|
|
||||||
func toPostableExtendedRuleNode(r ngmodels.AlertRule) apimodels.PostableExtendedRuleNode {
|
func toPostableExtendedRuleNode(r ngmodels.AlertRule) apimodels.PostableExtendedRuleNode {
|
||||||
return apimodels.PostableExtendedRuleNode{
|
postableExtendedRuleNode := apimodels.PostableExtendedRuleNode{
|
||||||
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
||||||
OrgID: r.OrgID,
|
OrgID: r.OrgID,
|
||||||
Title: r.Title,
|
Title: r.Title,
|
||||||
@ -227,8 +238,22 @@ func toPostableExtendedRuleNode(r ngmodels.AlertRule) apimodels.PostableExtended
|
|||||||
UID: r.UID,
|
UID: r.UID,
|
||||||
NoDataState: apimodels.NoDataState(r.NoDataState),
|
NoDataState: apimodels.NoDataState(r.NoDataState),
|
||||||
ExecErrState: apimodels.ExecutionErrorState(r.ExecErrState),
|
ExecErrState: apimodels.ExecutionErrorState(r.ExecErrState),
|
||||||
For: r.For,
|
|
||||||
Annotations: r.Annotations,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
postableExtendedRuleNode.ApiRuleNode = &apimodels.ApiRuleNode{
|
||||||
|
For: model.Duration(r.For),
|
||||||
|
Annotations: r.Annotations,
|
||||||
|
Labels: r.Labels,
|
||||||
|
}
|
||||||
|
return postableExtendedRuleNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func toNamespaceErrorResponse(err error) response.Response {
|
||||||
|
if errors.Is(err, ngmodels.ErrCannotEditNamespace) {
|
||||||
|
return response.Error(http.StatusForbidden, err.Error(), err)
|
||||||
|
}
|
||||||
|
if errors.Is(err, models.ErrDashboardIdentifierNotSet) {
|
||||||
|
return response.Error(http.StatusBadRequest, err.Error(), err)
|
||||||
|
}
|
||||||
|
return coreapi.ToFolderErrorResponse(err)
|
||||||
}
|
}
|
||||||
|
@ -178,7 +178,7 @@ func (api *API) ruleGroupByOldID(c *models.ReqContext) response.Response {
|
|||||||
Condition: sseCond.Condition,
|
Condition: sseCond.Condition,
|
||||||
NoDataState: *noDataSetting,
|
NoDataState: *noDataSetting,
|
||||||
ExecErrState: *execErrSetting,
|
ExecErrState: *execErrSetting,
|
||||||
For: ngmodels.Duration(oldAlert.For),
|
For: oldAlert.For,
|
||||||
Annotations: ruleTags,
|
Annotations: ruleTags,
|
||||||
}
|
}
|
||||||
rgc := apimodels.PostableRuleGroupConfig{
|
rgc := apimodels.PostableRuleGroupConfig{
|
||||||
|
@ -3,13 +3,16 @@
|
|||||||
"interval": "10s",
|
"interval": "10s",
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
|
"for": "1m",
|
||||||
|
"annotations": {
|
||||||
|
"foo": "bar"
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"label1": "val1"
|
||||||
|
},
|
||||||
"grafana_alert": {
|
"grafana_alert": {
|
||||||
"title": "prom query with SSE",
|
"title": "prom query with SSE",
|
||||||
"condition": "condition",
|
"condition": "condition",
|
||||||
"for": 5,
|
|
||||||
"annotations": {
|
|
||||||
"foo": "bar"
|
|
||||||
},
|
|
||||||
"data": [
|
"data": [
|
||||||
{
|
{
|
||||||
"refId": "query",
|
"refId": "query",
|
||||||
|
7
pkg/services/ngalert/api/test-data/prom.http
Normal file
7
pkg/services/ngalert/api/test-data/prom.http
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
@grafanaRecipient = grafana
|
||||||
|
|
||||||
|
GET http://admin:admin@localhost:3000/api/prometheus/{{grafanaRecipient}}/api/v1/rules
|
||||||
|
|
||||||
|
###
|
||||||
|
GET http://admin:admin@localhost:3000/api/prometheus/{{grafanaRecipient}}/api/v1/alerts
|
@ -31,6 +31,10 @@ GET http://admin:admin@localhost:3000/api/ruler/{{grafanaRecipient}}/api/v1/rule
|
|||||||
// get group101 rules
|
// get group101 rules
|
||||||
GET http://admin:admin@localhost:3000/api/ruler/{{grafanaRecipient}}/api/v1/rules/{{namespace1}}/group101
|
GET http://admin:admin@localhost:3000/api/ruler/{{grafanaRecipient}}/api/v1/rules/{{namespace1}}/group101
|
||||||
|
|
||||||
|
###
|
||||||
|
// get group101 rules - empty namespace
|
||||||
|
GET http://admin:admin@localhost:3000/api/ruler/{{grafanaRecipient}}/api/v1/rules//group101
|
||||||
|
|
||||||
###
|
###
|
||||||
// get namespace rules
|
// get namespace rules
|
||||||
GET http://admin:admin@localhost:3000/api/ruler/{{grafanaRecipient}}/api/v1/rules/{{namespace1}}
|
GET http://admin:admin@localhost:3000/api/ruler/{{grafanaRecipient}}/api/v1/rules/{{namespace1}}
|
||||||
@ -244,3 +248,82 @@ GET http://admin:admin@localhost:3000/api/ruler/{{grafanaRecipient}}/api/v1/rule
|
|||||||
###
|
###
|
||||||
// get namespace rules
|
// get namespace rules
|
||||||
GET http://admin:admin@localhost:3000/api/ruler/{{grafanaRecipient}}/api/v1/rules/{{namespace1}}
|
GET http://admin:admin@localhost:3000/api/ruler/{{grafanaRecipient}}/api/v1/rules/{{namespace1}}
|
||||||
|
|
||||||
|
###
|
||||||
|
// update rulegroup; Bad Request; not existing UID
|
||||||
|
POST http://admin:admin@localhost:3000/api/ruler/grafana/api/v1/rules/{{namespace1}}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "group42",
|
||||||
|
"interval": "20s",
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"grafana_alert": {
|
||||||
|
"title": "prom query with SSE",
|
||||||
|
"condition": "condition",
|
||||||
|
"uid": "unknown",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"refId": "query",
|
||||||
|
"queryType": "",
|
||||||
|
"relativeTimeRange": {
|
||||||
|
"from": 18000,
|
||||||
|
"to": 10800
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"datasource": "gdev-prometheus",
|
||||||
|
"datasourceUid": "000000002",
|
||||||
|
"expr": "http_request_duration_microseconds_count",
|
||||||
|
"hide": false,
|
||||||
|
"interval": "",
|
||||||
|
"intervalMs": 1000,
|
||||||
|
"legendFormat": "",
|
||||||
|
"maxDataPoints": 100,
|
||||||
|
"refId": "query"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"refId": "reduced",
|
||||||
|
"queryType": "",
|
||||||
|
"relativeTimeRange": {
|
||||||
|
"from": 18000,
|
||||||
|
"to": 10800
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"datasource": "__expr__",
|
||||||
|
"datasourceUid": "-100",
|
||||||
|
"expression": "query",
|
||||||
|
"hide": false,
|
||||||
|
"intervalMs": 1000,
|
||||||
|
"maxDataPoints": 100,
|
||||||
|
"reducer": "mean",
|
||||||
|
"refId": "reduced",
|
||||||
|
"type": "reduce"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"refId": "condition",
|
||||||
|
"queryType": "",
|
||||||
|
"relativeTimeRange": {
|
||||||
|
"from": 18000,
|
||||||
|
"to": 10800
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"datasource": "__expr__",
|
||||||
|
"datasourceUid": "-100",
|
||||||
|
"expression": "$reduced > 42",
|
||||||
|
"hide": false,
|
||||||
|
"intervalMs": 1000,
|
||||||
|
"maxDataPoints": 100,
|
||||||
|
"refId": "condition",
|
||||||
|
"type": "math"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no_data_state": "NoData",
|
||||||
|
"exec_err_state": "Alerting"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -11,6 +11,8 @@ var (
|
|||||||
ErrAlertRuleNotFound = fmt.Errorf("could not find alert rule")
|
ErrAlertRuleNotFound = fmt.Errorf("could not find alert rule")
|
||||||
// ErrAlertRuleFailedGenerateUniqueUID is an error for failure to generate alert rule UID
|
// ErrAlertRuleFailedGenerateUniqueUID is an error for failure to generate alert rule UID
|
||||||
ErrAlertRuleFailedGenerateUniqueUID = errors.New("failed to generate alert rule UID")
|
ErrAlertRuleFailedGenerateUniqueUID = errors.New("failed to generate alert rule UID")
|
||||||
|
// ErrCannotEditNamespace is an error returned if the user does not have permissions to edit the namespace
|
||||||
|
ErrCannotEditNamespace = errors.New("user does not have permissions to edit the namespace")
|
||||||
)
|
)
|
||||||
|
|
||||||
type NoDataState string
|
type NoDataState string
|
||||||
@ -52,8 +54,11 @@ type AlertRule struct {
|
|||||||
RuleGroup string
|
RuleGroup string
|
||||||
NoDataState NoDataState
|
NoDataState NoDataState
|
||||||
ExecErrState ExecutionErrorState
|
ExecErrState ExecutionErrorState
|
||||||
For Duration
|
// ideally this field should have been apimodels.ApiDuration
|
||||||
Annotations map[string]string
|
// but this is currently not possible because of circular dependencies
|
||||||
|
For time.Duration
|
||||||
|
Annotations map[string]string
|
||||||
|
Labels map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// AlertRuleKey is the alert definition identifier
|
// AlertRuleKey is the alert definition identifier
|
||||||
@ -102,8 +107,11 @@ type AlertRuleVersion struct {
|
|||||||
IntervalSeconds int64
|
IntervalSeconds int64
|
||||||
NoDataState NoDataState
|
NoDataState NoDataState
|
||||||
ExecErrState ExecutionErrorState
|
ExecErrState ExecutionErrorState
|
||||||
For Duration
|
// ideally this field should have been apimodels.ApiDuration
|
||||||
Annotations map[string]string
|
// but this is currently not possible because of circular dependencies
|
||||||
|
For time.Duration
|
||||||
|
Annotations map[string]string
|
||||||
|
Labels map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAlertRuleByUIDQuery is the query for retrieving/deleting an alert rule by UID and organisation ID.
|
// GetAlertRuleByUIDQuery is the query for retrieving/deleting an alert rule by UID and organisation ID.
|
||||||
|
@ -6,6 +6,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/guardian"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
@ -43,8 +45,8 @@ type RuleStore interface {
|
|||||||
GetOrgAlertRules(query *ngmodels.ListAlertRulesQuery) error
|
GetOrgAlertRules(query *ngmodels.ListAlertRulesQuery) error
|
||||||
GetNamespaceAlertRules(query *ngmodels.ListNamespaceAlertRulesQuery) error
|
GetNamespaceAlertRules(query *ngmodels.ListNamespaceAlertRulesQuery) error
|
||||||
GetRuleGroupAlertRules(query *ngmodels.ListRuleGroupAlertRulesQuery) error
|
GetRuleGroupAlertRules(query *ngmodels.ListRuleGroupAlertRulesQuery) error
|
||||||
GetNamespaceUIDByTitle(string, int64, *models.SignedInUser) (string, error)
|
GetNamespaceByTitle(string, int64, *models.SignedInUser, bool) (*models.Folder, error)
|
||||||
GetNamespaceByUID(string, int64, *models.SignedInUser) (string, error)
|
GetNamespaceByUID(string, int64, *models.SignedInUser) (*models.Folder, error)
|
||||||
GetOrgRuleGroups(query *ngmodels.ListOrgRuleGroupsQuery) error
|
GetOrgRuleGroups(query *ngmodels.ListOrgRuleGroupsQuery) error
|
||||||
UpsertAlertRules([]UpsertRule) error
|
UpsertAlertRules([]UpsertRule) error
|
||||||
UpdateRuleGroup(UpdateRuleGroupCmd) error
|
UpdateRuleGroup(UpdateRuleGroupCmd) error
|
||||||
@ -68,7 +70,6 @@ func getAlertRuleByUID(sess *sqlstore.DBSession, alertRuleUID string, orgID int6
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeleteAlertRuleByUID is a handler for deleting an alert rule.
|
// DeleteAlertRuleByUID is a handler for deleting an alert rule.
|
||||||
// It returns ngmodels.ErrAlertRuleNotFound if no alert rule is found for the provided ID.
|
|
||||||
func (st DBstore) DeleteAlertRuleByUID(orgID int64, ruleUID string) error {
|
func (st DBstore) DeleteAlertRuleByUID(orgID int64, ruleUID string) error {
|
||||||
return st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
return st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||||
_, err := sess.Exec("DELETE FROM alert_rule WHERE org_id = ? AND uid = ?", orgID, ruleUID)
|
_, err := sess.Exec("DELETE FROM alert_rule WHERE org_id = ? AND uid = ?", orgID, ruleUID)
|
||||||
@ -156,7 +157,7 @@ func (st DBstore) UpsertAlertRules(rules []UpsertRule) error {
|
|||||||
existingAlertRule, err := getAlertRuleByUID(sess, r.New.UID, r.New.OrgID)
|
existingAlertRule, err := getAlertRuleByUID(sess, r.New.UID, r.New.OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ngmodels.ErrAlertRuleNotFound) {
|
if errors.Is(err, ngmodels.ErrAlertRuleNotFound) {
|
||||||
return nil
|
return fmt.Errorf("failed to get alert rule %s: %w", r.New.UID, err)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -214,6 +215,7 @@ func (st DBstore) UpsertAlertRules(rules []UpsertRule) error {
|
|||||||
|
|
||||||
r.New.For = r.Existing.For
|
r.New.For = r.Existing.For
|
||||||
r.New.Annotations = r.Existing.Annotations
|
r.New.Annotations = r.Existing.Annotations
|
||||||
|
r.New.Labels = r.Existing.Labels
|
||||||
|
|
||||||
if err := st.ValidateAlertRule(r.New, true); err != nil {
|
if err := st.ValidateAlertRule(r.New, true); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -247,6 +249,7 @@ func (st DBstore) UpsertAlertRules(rules []UpsertRule) error {
|
|||||||
ExecErrState: r.New.ExecErrState,
|
ExecErrState: r.New.ExecErrState,
|
||||||
For: r.New.For,
|
For: r.New.For,
|
||||||
Annotations: r.New.Annotations,
|
Annotations: r.New.Annotations,
|
||||||
|
Labels: r.New.Labels,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,24 +313,33 @@ func (st DBstore) GetRuleGroupAlertRules(query *ngmodels.ListRuleGroupAlertRules
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNamespaceUIDByTitle is a handler for retrieving a namespace UID by its title.
|
// GetNamespaceByTitle is a handler for retrieving a namespace by its title. Alerting rules follow a Grafana folder-like structure which we call namespaces.
|
||||||
func (st DBstore) GetNamespaceUIDByTitle(namespace string, orgID int64, user *models.SignedInUser) (string, error) {
|
func (st DBstore) GetNamespaceByTitle(namespace string, orgID int64, user *models.SignedInUser, withEdit bool) (*models.Folder, error) {
|
||||||
s := dashboards.NewFolderService(orgID, user, st.SQLStore)
|
s := dashboards.NewFolderService(orgID, user, st.SQLStore)
|
||||||
folder, err := s.GetFolderByTitle(namespace)
|
folder, err := s.GetFolderByTitle(namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
return folder.Uid, nil
|
|
||||||
|
if withEdit {
|
||||||
|
g := guardian.New(folder.Id, orgID, user)
|
||||||
|
if canAdmin, err := g.CanEdit(); err != nil || !canAdmin {
|
||||||
|
return nil, ngmodels.ErrCannotEditNamespace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return folder, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNamespaceByUID is a handler for retrieving namespace by its UID.
|
// GetNamespaceByUID is a handler for retrieving namespace by its UID.
|
||||||
func (st DBstore) GetNamespaceByUID(UID string, orgID int64, user *models.SignedInUser) (string, error) {
|
func (st DBstore) GetNamespaceByUID(UID string, orgID int64, user *models.SignedInUser) (*models.Folder, error) {
|
||||||
s := dashboards.NewFolderService(orgID, user, st.SQLStore)
|
s := dashboards.NewFolderService(orgID, user, st.SQLStore)
|
||||||
folder, err := s.GetFolderByUID(UID)
|
folder, err := s.GetFolderByUID(UID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
return folder.Title, nil
|
|
||||||
|
return folder, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAlertRulesForScheduling returns alert rule info (identifier, interval, version state)
|
// GetAlertRulesForScheduling returns alert rule info (identifier, interval, version state)
|
||||||
@ -419,21 +431,27 @@ func (st DBstore) UpdateRuleGroup(cmd UpdateRuleGroupCmd) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
new := ngmodels.AlertRule{
|
||||||
|
OrgID: cmd.OrgID,
|
||||||
|
Title: r.GrafanaManagedAlert.Title,
|
||||||
|
Condition: r.GrafanaManagedAlert.Condition,
|
||||||
|
Data: r.GrafanaManagedAlert.Data,
|
||||||
|
UID: r.GrafanaManagedAlert.UID,
|
||||||
|
IntervalSeconds: int64(time.Duration(cmd.RuleGroupConfig.Interval).Seconds()),
|
||||||
|
NamespaceUID: cmd.NamespaceUID,
|
||||||
|
RuleGroup: ruleGroup,
|
||||||
|
NoDataState: ngmodels.NoDataState(r.GrafanaManagedAlert.NoDataState),
|
||||||
|
ExecErrState: ngmodels.ExecutionErrorState(r.GrafanaManagedAlert.ExecErrState),
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.ApiRuleNode != nil {
|
||||||
|
new.For = time.Duration(r.ApiRuleNode.For)
|
||||||
|
new.Annotations = r.ApiRuleNode.Annotations
|
||||||
|
new.Labels = r.ApiRuleNode.Labels
|
||||||
|
}
|
||||||
|
|
||||||
upsertRule := UpsertRule{
|
upsertRule := UpsertRule{
|
||||||
New: ngmodels.AlertRule{
|
New: new,
|
||||||
OrgID: cmd.OrgID,
|
|
||||||
Title: r.GrafanaManagedAlert.Title,
|
|
||||||
Condition: r.GrafanaManagedAlert.Condition,
|
|
||||||
Data: r.GrafanaManagedAlert.Data,
|
|
||||||
UID: r.GrafanaManagedAlert.UID,
|
|
||||||
IntervalSeconds: int64(time.Duration(cmd.RuleGroupConfig.Interval).Seconds()),
|
|
||||||
NamespaceUID: cmd.NamespaceUID,
|
|
||||||
RuleGroup: ruleGroup,
|
|
||||||
For: r.GrafanaManagedAlert.For,
|
|
||||||
Annotations: r.GrafanaManagedAlert.Annotations,
|
|
||||||
NoDataState: ngmodels.NoDataState(r.GrafanaManagedAlert.NoDataState),
|
|
||||||
ExecErrState: ngmodels.ExecutionErrorState(r.GrafanaManagedAlert.ExecErrState),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if existingGroupRule, ok := existingGroupRulesUIDs[r.GrafanaManagedAlert.UID]; ok {
|
if existingGroupRule, ok := existingGroupRulesUIDs[r.GrafanaManagedAlert.UID]; ok {
|
||||||
|
@ -153,6 +153,9 @@ func AddAlertRuleMigrations(mg *migrator.Migrator, defaultIntervalSeconds int64)
|
|||||||
|
|
||||||
// add annotations column
|
// add annotations column
|
||||||
mg.AddMigration("add column annotations to alert_rule", migrator.NewAddColumnMigration(alertRule, &migrator.Column{Name: "annotations", Type: migrator.DB_Text, Nullable: true}))
|
mg.AddMigration("add column annotations to alert_rule", migrator.NewAddColumnMigration(alertRule, &migrator.Column{Name: "annotations", Type: migrator.DB_Text, Nullable: true}))
|
||||||
|
|
||||||
|
// add labels column
|
||||||
|
mg.AddMigration("add column labels to alert_rule", migrator.NewAddColumnMigration(alertRule, &migrator.Column{Name: "labels", Type: migrator.DB_Text, Nullable: true}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddAlertRuleVersionMigrations(mg *migrator.Migrator) {
|
func AddAlertRuleVersionMigrations(mg *migrator.Migrator) {
|
||||||
@ -193,4 +196,7 @@ func AddAlertRuleVersionMigrations(mg *migrator.Migrator) {
|
|||||||
|
|
||||||
// add annotations column
|
// add annotations column
|
||||||
mg.AddMigration("add column annotations to alert_rule_version", migrator.NewAddColumnMigration(alertRuleVersion, &migrator.Column{Name: "annotations", Type: migrator.DB_Text, Nullable: true}))
|
mg.AddMigration("add column annotations to alert_rule_version", migrator.NewAddColumnMigration(alertRuleVersion, &migrator.Column{Name: "annotations", Type: migrator.DB_Text, Nullable: true}))
|
||||||
|
|
||||||
|
// add labels column
|
||||||
|
mg.AddMigration("add column labels to alert_rule_version", migrator.NewAddColumnMigration(alertRuleVersion, &migrator.Column{Name: "labels", Type: migrator.DB_Text, Nullable: true}))
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user