mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Refactor API endpoints for fetching alert rules (#37055)
* Refactor ruler API endpoint for listing rules * Refactor prometheus API endpoint for listing rules * Update HTTP API docs
This commit is contained in:
parent
9cd8e11c30
commit
7815ed511f
@ -24,7 +24,7 @@ that you cannot use this API for retrieving information about the General folder
|
|||||||
|
|
||||||
`GET /api/folders`
|
`GET /api/folders`
|
||||||
|
|
||||||
Returns all folders that the authenticated user has permission to view. You can control the maximum number of folders returned through the `limit` query parameter, the default is 1000.
|
Returns all folders that the authenticated user has permission to view. You can control the maximum number of folders returned through the `limit` query parameter, the default is 1000. You can also pass the `page` query parameter for fetching folders from a page other than the first one.
|
||||||
|
|
||||||
**Example Request**:
|
**Example Request**:
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ import (
|
|||||||
|
|
||||||
func (hs *HTTPServer) GetFolders(c *models.ReqContext) response.Response {
|
func (hs *HTTPServer) GetFolders(c *models.ReqContext) response.Response {
|
||||||
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
|
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
|
||||||
folders, err := s.GetFolders(c.QueryInt64("limit"))
|
folders, err := s.GetFolders(c.QueryInt64("limit"), c.QueryInt64("page"))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ToFolderErrorResponse(err)
|
return ToFolderErrorResponse(err)
|
||||||
|
@ -217,7 +217,7 @@ type fakeFolderService struct {
|
|||||||
DeletedFolderUids []string
|
DeletedFolderUids []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *fakeFolderService) GetFolders(limit int64) ([]*models.Folder, error) {
|
func (s *fakeFolderService) GetFolders(limit int64, page int64) ([]*models.Folder, error) {
|
||||||
return s.GetFoldersResult, s.GetFoldersError
|
return s.GetFoldersResult, s.GetFoldersError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
// FolderService is a service for operating on folders.
|
// FolderService is a service for operating on folders.
|
||||||
type FolderService interface {
|
type FolderService interface {
|
||||||
GetFolders(limit int64) ([]*models.Folder, error)
|
GetFolders(limit int64, page int64) ([]*models.Folder, error)
|
||||||
GetFolderByID(id int64) (*models.Folder, error)
|
GetFolderByID(id int64) (*models.Folder, error)
|
||||||
GetFolderByUID(uid string) (*models.Folder, error)
|
GetFolderByUID(uid string) (*models.Folder, error)
|
||||||
GetFolderByTitle(title string) (*models.Folder, error)
|
GetFolderByTitle(title string) (*models.Folder, error)
|
||||||
@ -32,7 +32,7 @@ var NewFolderService = func(orgID int64, user *models.SignedInUser, store dashbo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dr *dashboardServiceImpl) GetFolders(limit int64) ([]*models.Folder, error) {
|
func (dr *dashboardServiceImpl) GetFolders(limit int64, page int64) ([]*models.Folder, error) {
|
||||||
searchQuery := search.Query{
|
searchQuery := search.Query{
|
||||||
SignedInUser: dr.user,
|
SignedInUser: dr.user,
|
||||||
DashboardIds: make([]int64, 0),
|
DashboardIds: make([]int64, 0),
|
||||||
@ -41,6 +41,7 @@ func (dr *dashboardServiceImpl) GetFolders(limit int64) ([]*models.Folder, error
|
|||||||
OrgId: dr.orgId,
|
OrgId: dr.orgId,
|
||||||
Type: "dash-folder",
|
Type: "dash-folder",
|
||||||
Permission: models.PERMISSION_VIEW,
|
Permission: models.PERMISSION_VIEW,
|
||||||
|
Page: page,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := bus.Dispatch(&searchQuery); err != nil {
|
if err := bus.Dispatch(&searchQuery); err != nil {
|
||||||
|
@ -2,7 +2,6 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
@ -62,8 +61,19 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *models.ReqContext) response.Res
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespaceMap, err := srv.store.GetNamespaces(c.OrgId, c.SignedInUser)
|
||||||
|
if err != nil {
|
||||||
|
return ErrResp(http.StatusInternalServerError, err, "failed to get namespaces visible to the user")
|
||||||
|
}
|
||||||
|
|
||||||
|
namespaceUIDs := make([]string, len(namespaceMap))
|
||||||
|
for k := range namespaceMap {
|
||||||
|
namespaceUIDs = append(namespaceUIDs, k)
|
||||||
|
}
|
||||||
|
|
||||||
ruleGroupQuery := ngmodels.ListOrgRuleGroupsQuery{
|
ruleGroupQuery := ngmodels.ListOrgRuleGroupsQuery{
|
||||||
OrgID: c.SignedInUser.OrgId,
|
OrgID: c.SignedInUser.OrgId,
|
||||||
|
NamespaceUIDs: namespaceUIDs,
|
||||||
}
|
}
|
||||||
if err := srv.store.GetOrgRuleGroups(&ruleGroupQuery); err != nil {
|
if err := srv.store.GetOrgRuleGroups(&ruleGroupQuery); err != nil {
|
||||||
ruleResponse.DiscoveryBase.Status = "error"
|
ruleResponse.DiscoveryBase.Status = "error"
|
||||||
@ -77,13 +87,6 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *models.ReqContext) response.Res
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
groupId, namespaceUID, namespace := r[0], r[1], r[2]
|
groupId, namespaceUID, namespace := r[0], r[1], r[2]
|
||||||
if _, err := srv.store.GetNamespaceByUID(namespaceUID, c.SignedInUser.OrgId, c.SignedInUser); err != nil {
|
|
||||||
if errors.Is(err, models.ErrFolderAccessDenied) {
|
|
||||||
// do not include it in the response
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return toNamespaceErrorResponse(err)
|
|
||||||
}
|
|
||||||
alertRuleQuery := ngmodels.ListRuleGroupAlertRulesQuery{OrgID: c.SignedInUser.OrgId, NamespaceUID: namespaceUID, RuleGroup: groupId}
|
alertRuleQuery := ngmodels.ListRuleGroupAlertRulesQuery{OrgID: c.SignedInUser.OrgId, NamespaceUID: namespaceUID, RuleGroup: groupId}
|
||||||
if err := srv.store.GetRuleGroupAlertRules(&alertRuleQuery); err != nil {
|
if err := srv.store.GetRuleGroupAlertRules(&alertRuleQuery); err != nil {
|
||||||
ruleResponse.DiscoveryBase.Status = "error"
|
ruleResponse.DiscoveryBase.Status = "error"
|
||||||
|
@ -146,26 +146,34 @@ func (srv RulerSrv) RouteGetRulegGroupConfig(c *models.ReqContext) response.Resp
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response {
|
func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response {
|
||||||
q := ngmodels.ListAlertRulesQuery{
|
namespaceMap, err := srv.store.GetNamespaces(c.OrgId, c.SignedInUser)
|
||||||
OrgID: c.SignedInUser.OrgId,
|
if err != nil {
|
||||||
|
return ErrResp(http.StatusInternalServerError, err, "failed to get namespaces visible to the user")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespaceUIDs := make([]string, len(namespaceMap))
|
||||||
|
for k := range namespaceMap {
|
||||||
|
namespaceUIDs = append(namespaceUIDs, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
q := ngmodels.ListAlertRulesQuery{
|
||||||
|
OrgID: c.SignedInUser.OrgId,
|
||||||
|
NamespaceUIDs: namespaceUIDs,
|
||||||
|
}
|
||||||
|
|
||||||
if err := srv.store.GetOrgAlertRules(&q); err != nil {
|
if err := srv.store.GetOrgAlertRules(&q); err != nil {
|
||||||
return ErrResp(http.StatusInternalServerError, err, "failed to get alert rules")
|
return ErrResp(http.StatusInternalServerError, err, "failed to get alert rules")
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
folder, err := srv.store.GetNamespaceByUID(r.NamespaceUID, c.SignedInUser.OrgId, c.SignedInUser)
|
folder, ok := namespaceMap[r.NamespaceUID]
|
||||||
if err != nil {
|
if !ok {
|
||||||
if errors.Is(err, models.ErrFolderAccessDenied) {
|
srv.log.Error("namespace not visible to the user", "user", c.SignedInUser.UserId, "namespace", r.NamespaceUID, "rule", r.UID)
|
||||||
// do not fail if used does not have access to a specific namespace
|
continue
|
||||||
// just do not include it in the response
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return toNamespaceErrorResponse(err)
|
|
||||||
}
|
}
|
||||||
namespace := folder.Title
|
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)
|
||||||
configs[namespace] = make(map[string]apimodels.GettableRuleGroupConfig)
|
configs[namespace] = make(map[string]apimodels.GettableRuleGroupConfig)
|
||||||
|
@ -133,7 +133,8 @@ type GetAlertRuleByUIDQuery struct {
|
|||||||
|
|
||||||
// ListAlertRulesQuery is the query for listing alert rules
|
// ListAlertRulesQuery is the query for listing alert rules
|
||||||
type ListAlertRulesQuery struct {
|
type ListAlertRulesQuery struct {
|
||||||
OrgID int64
|
OrgID int64
|
||||||
|
NamespaceUIDs []string
|
||||||
|
|
||||||
Result []*AlertRule
|
Result []*AlertRule
|
||||||
}
|
}
|
||||||
@ -159,7 +160,8 @@ type ListRuleGroupAlertRulesQuery struct {
|
|||||||
|
|
||||||
// ListOrgRuleGroupsQuery is the query for listing unique rule groups
|
// ListOrgRuleGroupsQuery is the query for listing unique rule groups
|
||||||
type ListOrgRuleGroupsQuery struct {
|
type ListOrgRuleGroupsQuery struct {
|
||||||
OrgID int64
|
OrgID int64
|
||||||
|
NamespaceUIDs []string
|
||||||
|
|
||||||
Result [][]string
|
Result [][]string
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/guardian"
|
"github.com/grafana/grafana/pkg/services/guardian"
|
||||||
@ -46,8 +47,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
|
||||||
|
GetNamespaces(int64, *models.SignedInUser) (map[string]*models.Folder, error)
|
||||||
GetNamespaceByTitle(string, int64, *models.SignedInUser, bool) (*models.Folder, error)
|
GetNamespaceByTitle(string, int64, *models.SignedInUser, bool) (*models.Folder, 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
|
||||||
@ -320,7 +321,18 @@ func (st DBstore) GetOrgAlertRules(query *ngmodels.ListAlertRulesQuery) error {
|
|||||||
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||||
alertRules := make([]*ngmodels.AlertRule, 0)
|
alertRules := make([]*ngmodels.AlertRule, 0)
|
||||||
q := "SELECT * FROM alert_rule WHERE org_id = ?"
|
q := "SELECT * FROM alert_rule WHERE org_id = ?"
|
||||||
if err := sess.SQL(q, query.OrgID).Find(&alertRules); err != nil {
|
params := []interface{}{query.OrgID}
|
||||||
|
|
||||||
|
if len(query.NamespaceUIDs) > 0 {
|
||||||
|
placeholders := make([]string, 0, len(query.NamespaceUIDs))
|
||||||
|
for _, folderUID := range query.NamespaceUIDs {
|
||||||
|
params = append(params, folderUID)
|
||||||
|
placeholders = append(placeholders, "?")
|
||||||
|
}
|
||||||
|
q = fmt.Sprintf("%s AND namespace_uid IN (%s)", q, strings.Join(placeholders, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sess.SQL(q, params...).Find(&alertRules); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -359,6 +371,30 @@ func (st DBstore) GetRuleGroupAlertRules(query *ngmodels.ListRuleGroupAlertRules
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetNamespaces returns the folders that are visible to the user
|
||||||
|
func (st DBstore) GetNamespaces(orgID int64, user *models.SignedInUser) (map[string]*models.Folder, error) {
|
||||||
|
s := dashboards.NewFolderService(orgID, user, st.SQLStore)
|
||||||
|
namespaceMap := make(map[string]*models.Folder)
|
||||||
|
var page int64 = 1
|
||||||
|
for {
|
||||||
|
// if limit is negative; it fetches at most 1000
|
||||||
|
folders, err := s.GetFolders(-1, page)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(folders) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range folders {
|
||||||
|
namespaceMap[f.Uid] = f
|
||||||
|
}
|
||||||
|
page += 1
|
||||||
|
}
|
||||||
|
return namespaceMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetNamespaceByTitle is a handler for retrieving a namespace by its title. Alerting rules follow a Grafana folder-like structure which we call namespaces.
|
// 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) GetNamespaceByTitle(namespace string, orgID int64, user *models.SignedInUser, withCanSave bool) (*models.Folder, error) {
|
func (st DBstore) GetNamespaceByTitle(namespace string, orgID int64, user *models.SignedInUser, withCanSave bool) (*models.Folder, error) {
|
||||||
s := dashboards.NewFolderService(orgID, user, st.SQLStore)
|
s := dashboards.NewFolderService(orgID, user, st.SQLStore)
|
||||||
@ -380,17 +416,6 @@ func (st DBstore) GetNamespaceByTitle(namespace string, orgID int64, user *model
|
|||||||
return folder, nil
|
return folder, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNamespaceByUID is a handler for retrieving namespace by its UID.
|
|
||||||
func (st DBstore) GetNamespaceByUID(UID string, orgID int64, user *models.SignedInUser) (*models.Folder, error) {
|
|
||||||
s := dashboards.NewFolderService(orgID, user, st.SQLStore)
|
|
||||||
folder, err := s.GetFolderByUID(UID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return folder, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAlertRulesForScheduling returns alert rule info (identifier, interval, version state)
|
// GetAlertRulesForScheduling returns alert rule info (identifier, interval, version state)
|
||||||
// that is useful for it's scheduling.
|
// that is useful for it's scheduling.
|
||||||
func (st DBstore) GetAlertRulesForScheduling(query *ngmodels.ListAlertRulesQuery) error {
|
func (st DBstore) GetAlertRulesForScheduling(query *ngmodels.ListAlertRulesQuery) error {
|
||||||
@ -542,8 +567,20 @@ func (st DBstore) UpdateRuleGroup(cmd UpdateRuleGroupCmd) error {
|
|||||||
func (st DBstore) GetOrgRuleGroups(query *ngmodels.ListOrgRuleGroupsQuery) error {
|
func (st DBstore) GetOrgRuleGroups(query *ngmodels.ListOrgRuleGroupsQuery) error {
|
||||||
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||||
var ruleGroups [][]string
|
var ruleGroups [][]string
|
||||||
q := "SELECT DISTINCT rule_group, namespace_uid, (select title from dashboard where org_id = alert_rule.org_id and uid = alert_rule.namespace_uid) AS namespace_title FROM alert_rule WHERE org_id = ? ORDER BY namespace_title"
|
q := "SELECT DISTINCT rule_group, namespace_uid, (select title from dashboard where org_id = alert_rule.org_id and uid = alert_rule.namespace_uid) AS namespace_title FROM alert_rule WHERE org_id = ?"
|
||||||
if err := sess.SQL(q, query.OrgID).Find(&ruleGroups); err != nil {
|
params := []interface{}{query.OrgID}
|
||||||
|
|
||||||
|
if len(query.NamespaceUIDs) > 0 {
|
||||||
|
placeholders := make([]string, 0, len(query.NamespaceUIDs))
|
||||||
|
for _, folderUID := range query.NamespaceUIDs {
|
||||||
|
params = append(params, folderUID)
|
||||||
|
placeholders = append(placeholders, "?")
|
||||||
|
}
|
||||||
|
q = fmt.Sprintf(" %s AND namespace_uid IN (%s)", q, strings.Join(placeholders, ","))
|
||||||
|
}
|
||||||
|
q = fmt.Sprintf(" %s ORDER BY namespace_title", q)
|
||||||
|
|
||||||
|
if err := sess.SQL(q, params...).Find(&ruleGroups); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user