Alerting: Start splitting apart ngalert/api package. (#102075)

This commit is contained in:
Steve Simpson
2025-03-13 09:28:35 +01:00
committed by GitHub
parent 3589baac68
commit 87638c0170
18 changed files with 127 additions and 76 deletions
@@ -9,7 +9,7 @@ import (
"github.com/prometheus/common/model"
"github.com/grafana/grafana/pkg/components/simplejson"
ngalertapi "github.com/grafana/grafana/pkg/services/ngalert/api"
ngalertapi "github.com/grafana/grafana/pkg/services/ngalert/api/compat"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
"github.com/grafana/grafana/pkg/services/user"
+3 -2
View File
@@ -14,6 +14,7 @@ import (
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
apiprometheus "github.com/grafana/grafana/pkg/services/ngalert/api/prometheus"
"github.com/grafana/grafana/pkg/services/ngalert/backtesting"
"github.com/grafana/grafana/pkg/services/ngalert/eval"
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
@@ -64,7 +65,7 @@ type API struct {
DataProxy *datasourceproxy.DataSourceProxyService
MultiOrgAlertmanager *notifier.MultiOrgAlertmanager
StateManager *state.Manager
Scheduler StatusReader
Scheduler apiprometheus.StatusReader
AccessControl ac.AccessControl
Policies *provisioning.NotificationPolicyService
ReceiverService *notifier.ReceiverService
@@ -119,7 +120,7 @@ func (api *API) RegisterAPIEndpoints(m *metrics.API) {
api.RegisterPrometheusApiEndpoints(NewForkingProm(
api.DatasourceCache,
NewLotexProm(proxy, logger),
&PrometheusSrv{log: logger, manager: api.StateManager, status: api.Scheduler, store: api.RuleStore, authz: ruleAuthzService},
apiprometheus.NewPrometheusSrv(logger, api.StateManager, api.Scheduler, api.RuleStore, ruleAuthzService),
), m)
// Register endpoints for proxying to Cortex Ruler-compatible backends.
api.RegisterRulerApiEndpoints(NewForkingRuler(
@@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
. "github.com/grafana/grafana/pkg/services/ngalert/api/compat"
"github.com/grafana/grafana/pkg/services/ngalert/models"
)
@@ -18,6 +18,7 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
ac "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
. "github.com/grafana/grafana/pkg/services/ngalert/api/compat"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
+42 -40
View File
@@ -32,6 +32,8 @@ import (
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/web"
. "github.com/grafana/grafana/pkg/services/ngalert/api/prometheus"
)
func Test_FormatValues(t *testing.T) {
@@ -79,7 +81,7 @@ func Test_FormatValues(t *testing.T) {
for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.expected, formatValues(tt.alertState))
require.Equal(t, tt.expected, FormatValues(tt.alertState))
})
}
}
@@ -503,13 +505,13 @@ func TestRouteGetRuleStatuses(t *testing.T) {
rules := gen.With(gen.WithGroupKey(groupKey), gen.WithUniqueGroupIndex()).GenerateManyRef(5, 10)
ruleStore.PutRule(context.Background(), rules...)
api := PrometheusSrv{
log: log.NewNopLogger(),
manager: fakeAIM,
status: fakeSch,
store: ruleStore,
authz: &fakeRuleAccessControlService{},
}
api := NewPrometheusSrv(
log.NewNopLogger(),
fakeAIM,
fakeSch,
ruleStore,
&fakeRuleAccessControlService{},
)
response := api.RouteGetRuleStatuses(c)
require.Equal(t, http.StatusOK, response.Status())
@@ -565,13 +567,13 @@ func TestRouteGetRuleStatuses(t *testing.T) {
ruleStore.PutRule(context.Background(), rulesInGroup2...)
ruleStore.PutRule(context.Background(), rulesInGroup3...)
api := PrometheusSrv{
log: log.NewNopLogger(),
manager: fakeAIM,
status: newFakeSchedulerReader(t).setupStates(fakeAIM),
store: ruleStore,
authz: accesscontrol.NewRuleService(acimpl.ProvideAccessControl(featuremgmt.WithFeatures())),
}
api := NewPrometheusSrv(
log.NewNopLogger(),
fakeAIM,
newFakeSchedulerReader(t).setupStates(fakeAIM),
ruleStore,
accesscontrol.NewRuleService(acimpl.ProvideAccessControl(featuremgmt.WithFeatures())),
)
permissions := createPermissionsForRules(slices.Concat(rulesInGroup1, rulesInGroup2, rulesInGroup3), orgID)
user := &user.SignedInUser{
@@ -663,7 +665,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
require.NoError(t, json.Unmarshal(resp.Body(), result))
require.Len(t, result.Data.RuleGroups, 1)
folder, err := api.store.GetNamespaceByUID(context.Background(), "folder-1", orgID, user)
folder, err := ruleStore.GetNamespaceByUID(context.Background(), "folder-1", orgID, user)
require.NoError(t, err)
require.Equal(t, folder.Fullpath, result.Data.RuleGroups[0].File)
require.Equal(t, "rule-group-3", result.Data.RuleGroups[0].Name)
@@ -691,13 +693,13 @@ func TestRouteGetRuleStatuses(t *testing.T) {
ruleStore.PutRule(context.Background(), rules...)
}
api := PrometheusSrv{
log: log.NewNopLogger(),
manager: fakeAIM,
status: newFakeSchedulerReader(t).setupStates(fakeAIM),
store: ruleStore,
authz: accesscontrol.NewRuleService(acimpl.ProvideAccessControl(featuremgmt.WithFeatures())),
}
api := NewPrometheusSrv(
log.NewNopLogger(),
fakeAIM,
newFakeSchedulerReader(t).setupStates(fakeAIM),
ruleStore,
accesscontrol.NewRuleService(acimpl.ProvideAccessControl(featuremgmt.WithFeatures())),
)
permissions := createPermissionsForRules(allRules, orgID)
user := &user.SignedInUser{
@@ -722,7 +724,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
require.Len(t, result.Data.RuleGroups, 9)
require.NotZero(t, len(result.Data.Totals))
for i := 0; i < 9; i++ {
folder, err := api.store.GetNamespaceByUID(context.Background(), fmt.Sprintf("namespace_%d", i/9), orgID, user)
folder, err := ruleStore.GetNamespaceByUID(context.Background(), fmt.Sprintf("namespace_%d", i/9), orgID, user)
require.NoError(t, err)
require.Equal(t, folder.Fullpath, result.Data.RuleGroups[i].File)
require.Equal(t, fmt.Sprintf("rule_group_%d", i), result.Data.RuleGroups[i].Name)
@@ -783,7 +785,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
require.Empty(t, result.Data.NextToken)
for i := 0; i < 9; i++ {
folder, err := api.store.GetNamespaceByUID(context.Background(), fmt.Sprintf("namespace_%d", i/9), orgID, user)
folder, err := ruleStore.GetNamespaceByUID(context.Background(), fmt.Sprintf("namespace_%d", i/9), orgID, user)
require.NoError(t, err)
require.Equal(t, folder.Fullpath, returnedGroups[i].File)
require.Equal(t, fmt.Sprintf("rule_group_%d", i), returnedGroups[i].Name)
@@ -805,7 +807,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
require.Len(t, result.Data.Totals, 0)
require.NotEmpty(t, result.Data.NextToken)
folder, err := api.store.GetNamespaceByUID(context.Background(), "namespace_0", orgID, user)
folder, err := ruleStore.GetNamespaceByUID(context.Background(), "namespace_0", orgID, user)
require.NoError(t, err)
require.Equal(t, folder.Fullpath, result.Data.RuleGroups[0].File)
require.Equal(t, "rule_group_0", result.Data.RuleGroups[0].Name)
@@ -835,13 +837,13 @@ func TestRouteGetRuleStatuses(t *testing.T) {
ruleStore.PutRule(context.Background(), rules...)
ruleStore.PutRule(context.Background(), gen.GenerateManyRef(2, 6)...)
api := PrometheusSrv{
log: log.NewNopLogger(),
manager: fakeAIM,
status: newFakeSchedulerReader(t).setupStates(fakeAIM),
store: ruleStore,
authz: accesscontrol.NewRuleService(acimpl.ProvideAccessControl(featuremgmt.WithFeatures())),
}
api := NewPrometheusSrv(
log.NewNopLogger(),
fakeAIM,
newFakeSchedulerReader(t).setupStates(fakeAIM),
ruleStore,
accesscontrol.NewRuleService(acimpl.ProvideAccessControl(featuremgmt.WithFeatures())),
)
c := &contextmodel.ReqContext{Context: &web.Context{Req: req}, SignedInUser: &user.SignedInUser{OrgID: orgID, Permissions: createPermissionsForRules(rules, orgID)}}
@@ -1523,13 +1525,13 @@ func setupAPI(t *testing.T) (*fakes.RuleStore, *fakeAlertInstanceManager, Promet
fakeSch := newFakeSchedulerReader(t).setupStates(fakeAIM)
fakeAuthz := &fakeRuleAccessControlService{}
api := PrometheusSrv{
log: log.NewNopLogger(),
manager: fakeAIM,
status: fakeSch,
store: fakeStore,
authz: fakeAuthz,
}
api := *NewPrometheusSrv(
log.NewNopLogger(),
fakeAIM,
fakeSch,
fakeStore,
fakeAuthz,
)
return fakeStore, fakeAIM, api
}
@@ -14,6 +14,7 @@ import (
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
. "github.com/grafana/grafana/pkg/services/ngalert/api/compat"
"github.com/grafana/grafana/pkg/services/ngalert/api/hcl"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
alerting_models "github.com/grafana/grafana/pkg/services/ngalert/models"
+12 -1
View File
@@ -5,8 +5,10 @@ import (
"errors"
"fmt"
"net/http"
"net/url"
"slices"
"sort"
"strconv"
"strings"
"time"
@@ -21,7 +23,9 @@ import (
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
authz "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
. "github.com/grafana/grafana/pkg/services/ngalert/api/compat"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
apivalidation "github.com/grafana/grafana/pkg/services/ngalert/api/validation"
"github.com/grafana/grafana/pkg/services/ngalert/eval"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
@@ -390,7 +394,7 @@ func (srv RulerSrv) RoutePostNameRulesConfig(c *contextmodel.ReqContext, ruleGro
return ErrResp(http.StatusBadRequest, err, "")
}
rules, err := ValidateRuleGroup(&ruleGroupConfig, c.SignedInUser.GetOrgID(), namespace.UID, RuleLimitsFromConfig(srv.cfg, srv.featureManager))
rules, err := apivalidation.ValidateRuleGroup(&ruleGroupConfig, c.SignedInUser.GetOrgID(), namespace.UID, apivalidation.RuleLimitsFromConfig(srv.cfg, srv.featureManager))
if err != nil {
return ErrResp(http.StatusBadRequest, err, "")
}
@@ -813,3 +817,10 @@ func (srv RulerSrv) resolveUserIdToNameFn(ctx context.Context) userIDToUserInfoF
return result
}
}
func getPanelIDFromQuery(v url.Values) (int64, error) {
if s := strings.TrimSpace(v.Get("panel_id")); s != "" {
return strconv.ParseInt(s, 10, 64)
}
return 0, nil
}
+3 -1
View File
@@ -9,7 +9,9 @@ import (
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
authz "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
. "github.com/grafana/grafana/pkg/services/ngalert/api/compat"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
apivalidation "github.com/grafana/grafana/pkg/services/ngalert/api/validation"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
)
@@ -21,7 +23,7 @@ func (srv RulerSrv) ExportFromPayload(c *contextmodel.ReqContext, ruleGroupConfi
return toNamespaceErrorResponse(err)
}
rulesWithOptionals, err := ValidateRuleGroup(&ruleGroupConfig, c.SignedInUser.GetOrgID(), namespace.UID, RuleLimitsFromConfig(srv.cfg, srv.featureManager))
rulesWithOptionals, err := apivalidation.ValidateRuleGroup(&ruleGroupConfig, c.SignedInUser.GetOrgID(), namespace.UID, apivalidation.RuleLimitsFromConfig(srv.cfg, srv.featureManager))
if err != nil {
return ErrResp(http.StatusBadRequest, err, "")
}
@@ -20,6 +20,7 @@ import (
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasources"
folder2 "github.com/grafana/grafana/pkg/services/folder"
. "github.com/grafana/grafana/pkg/services/ngalert/api/compat"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
@@ -14,11 +14,14 @@ import (
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
. "github.com/grafana/grafana/pkg/services/ngalert/api/compat"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
. "github.com/grafana/grafana/pkg/services/ngalert/api/validation"
)
var allNoData = []apimodels.NoDataState{
@@ -187,7 +190,7 @@ func TestValidateCondition(t *testing.T) {
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
err := validateCondition(tc.condition, tc.data, false)
err := ValidateCondition(tc.condition, tc.data, false)
if tc.errorMsg == "" {
require.NoError(t, err)
} else {
@@ -511,7 +514,7 @@ func TestValidateRuleNode_NoUID(t *testing.T) {
lim = *testCase.limits
}
alert, err := validateRuleNode(r, name, interval, orgId, folder.UID, lim)
alert, err := ValidateRuleNode(r, name, interval, orgId, folder.UID, lim)
require.NoError(t, err)
testCase.assert(t, r, alert)
})
@@ -519,7 +522,7 @@ func TestValidateRuleNode_NoUID(t *testing.T) {
t.Run("accepts empty group name", func(t *testing.T) {
r := validRule()
alert, err := validateRuleNode(&r, "", interval, orgId, folder.UID, limits)
alert, err := ValidateRuleNode(&r, "", interval, orgId, folder.UID, limits)
require.NoError(t, err)
require.Equal(t, "", alert.RuleGroup)
})
@@ -739,7 +742,7 @@ func TestValidateRuleNodeFailures_NoUID(t *testing.T) {
lim = *testCase.limits
}
_, err := validateRuleNode(r, "", interval, orgId, folder.UID, lim)
_, err := ValidateRuleNode(r, "", interval, orgId, folder.UID, lim)
require.Error(t, err)
if testCase.expErr != "" {
require.ErrorContains(t, err, testCase.expErr)
@@ -835,7 +838,7 @@ func TestValidateRuleNode_UID(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
r := testCase.rule()
alert, err := validateRuleNode(r, name, interval, orgId, folder.UID, limits)
alert, err := ValidateRuleNode(r, name, interval, orgId, folder.UID, limits)
require.NoError(t, err)
testCase.assert(t, r, alert)
})
@@ -843,7 +846,7 @@ func TestValidateRuleNode_UID(t *testing.T) {
t.Run("accepts empty group name", func(t *testing.T) {
r := validRule()
alert, err := validateRuleNode(&r, "", interval, orgId, folder.UID, limits)
alert, err := ValidateRuleNode(&r, "", interval, orgId, folder.UID, limits)
require.NoError(t, err)
require.Equal(t, "", alert.RuleGroup)
})
@@ -939,7 +942,7 @@ func TestValidateRuleNodeFailures_UID(t *testing.T) {
interval = *testCase.interval
}
_, err := validateRuleNode(r, "", interval, orgId, folder.UID, limits)
_, err := ValidateRuleNode(r, "", interval, orgId, folder.UID, limits)
require.Error(t, err)
if testCase.assert != nil {
testCase.assert(t, r, err)
@@ -973,7 +976,7 @@ func TestValidateRuleNodeIntervalFailures(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
r := validRule()
_, err := validateRuleNode(&r, util.GenerateShortUID(), testCase.interval, rand.Int63(), randFolder().UID, limits)
_, err := ValidateRuleNode(&r, util.GenerateShortUID(), testCase.interval, rand.Int63(), randFolder().UID, limits)
require.Error(t, err)
})
}
@@ -1064,7 +1067,7 @@ func TestValidateRuleNodeNotificationSettings(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
r := validRule()
r.GrafanaManagedAlert.NotificationSettings = AlertRuleNotificationSettingsFromNotificationSettings([]models.NotificationSettings{tt.notificationSettings})
_, err := validateRuleNode(&r, util.GenerateShortUID(), cfg.BaseInterval*time.Duration(rand.Int63n(10)+1), rand.Int63(), randFolder().UID, limits)
_, err := ValidateRuleNode(&r, util.GenerateShortUID(), cfg.BaseInterval*time.Duration(rand.Int63n(10)+1), rand.Int63(), randFolder().UID, limits)
if tt.expErrorContains != "" {
require.Error(t, err)
@@ -1099,7 +1102,7 @@ func TestValidateRuleNodeEditorSettings(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
r := validRule()
r.GrafanaManagedAlert.Metadata = AlertRuleMetadataFromModelMetadata(models.AlertRuleMetadata{EditorSettings: tt.editorSettings})
newRule, err := validateRuleNode(&r, util.GenerateShortUID(), cfg.BaseInterval*time.Duration(rand.Int63n(10)+1), rand.Int63(), randFolder().UID, limits)
newRule, err := ValidateRuleNode(&r, util.GenerateShortUID(), cfg.BaseInterval*time.Duration(rand.Int63n(10)+1), rand.Int63(), randFolder().UID, limits)
require.NoError(t, err)
require.Equal(t, tt.editorSettings, newRule.Metadata.EditorSettings)
})
@@ -1116,7 +1119,7 @@ func TestValidateRuleNodeReservedLabels(t *testing.T) {
r.ApiRuleNode.Labels = map[string]string{
label: "true",
}
_, err := validateRuleNode(&r, util.GenerateShortUID(), cfg.BaseInterval*time.Duration(rand.Int63n(10)+1), rand.Int63(), randFolder().UID, limits)
_, err := ValidateRuleNode(&r, util.GenerateShortUID(), cfg.BaseInterval*time.Duration(rand.Int63n(10)+1), rand.Int63(), randFolder().UID, limits)
require.Error(t, err)
require.ErrorContains(t, err, label)
})
+5 -3
View File
@@ -25,7 +25,9 @@ import (
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
. "github.com/grafana/grafana/pkg/services/ngalert/api/compat"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
apivalidation "github.com/grafana/grafana/pkg/services/ngalert/api/validation"
"github.com/grafana/grafana/pkg/services/ngalert/backtesting"
"github.com/grafana/grafana/pkg/services/ngalert/eval"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
@@ -61,13 +63,13 @@ func (srv TestingApiSrv) RouteTestGrafanaRuleConfig(c *contextmodel.ReqContext,
if err != nil {
return toNamespaceErrorResponse(dashboards.ErrFolderAccessDenied)
}
rule, err := validateRuleNode(
rule, err := apivalidation.ValidateRuleNode(
&body.Rule,
body.RuleGroup,
srv.cfg.BaseInterval,
c.SignedInUser.GetOrgID(),
folder.UID,
RuleLimitsFromConfig(srv.cfg, srv.featureManager),
apivalidation.RuleLimitsFromConfig(srv.cfg, srv.featureManager),
)
if err != nil {
return ErrResp(http.StatusBadRequest, err, "")
@@ -239,7 +241,7 @@ func (srv TestingApiSrv) BacktestAlertRule(c *contextmodel.ReqContext, cmd apimo
return ErrResp(400, nil, "Bad For interval")
}
intervalSeconds, err := validateInterval(time.Duration(cmd.Interval), srv.cfg.BaseInterval)
intervalSeconds, err := apivalidation.ValidateInterval(time.Duration(cmd.Interval), srv.cfg.BaseInterval)
if err != nil {
return ErrResp(400, err, "")
}
@@ -22,6 +22,7 @@ import (
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
. "github.com/grafana/grafana/pkg/services/ngalert/api/compat"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/eval"
"github.com/grafana/grafana/pkg/services/ngalert/eval/eval_mocks"
@@ -11,6 +11,7 @@ import (
receiversTesting "github.com/grafana/alerting/receivers/testing"
"github.com/stretchr/testify/require"
apicompat "github.com/grafana/grafana/pkg/services/ngalert/api/compat"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
@@ -38,7 +39,7 @@ func TestContactPointFromContactPointExports(t *testing.T) {
return string(d)
})
require.NoError(t, err)
ex, err := ReceiverExportFromEmbeddedContactPoint(emb)
ex, err := apicompat.ReceiverExportFromEmbeddedContactPoint(emb)
require.NoError(t, err)
export = append(export, ex)
}
@@ -5,16 +5,18 @@ import (
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/datasources"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
apiprometheus "github.com/grafana/grafana/pkg/services/ngalert/api/prometheus"
)
type PrometheusApiHandler struct {
ProxySvc *LotexProm
GrafanaSvc *PrometheusSrv
GrafanaSvc *apiprometheus.PrometheusSrv
DatasourceCache datasources.CacheService
}
// NewForkingProm implements a set of routes that proxy to various Prometheus-compatible backends.
func NewForkingProm(datasourceCache datasources.CacheService, proxy *LotexProm, grafana *PrometheusSrv) *PrometheusApiHandler {
func NewForkingProm(datasourceCache datasources.CacheService, proxy *LotexProm, grafana *apiprometheus.PrometheusSrv) *PrometheusApiHandler {
return &PrometheusApiHandler{
ProxySvc: proxy,
GrafanaSvc: grafana,
@@ -16,8 +16,10 @@ import (
apiv1 "github.com/prometheus/client_golang/api/prometheus/v1"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/folder"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/eval"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
@@ -25,6 +27,15 @@ import (
"github.com/grafana/grafana/pkg/util"
)
type RuleStoreReader interface {
GetUserVisibleNamespaces(context.Context, int64, identity.Requester) (map[string]*folder.Folder, error)
ListAlertRules(ctx context.Context, query *ngmodels.ListAlertRulesQuery) (ngmodels.RulesGroup, error)
}
type RuleGroupAccessControlService interface {
HasAccessToRuleGroup(ctx context.Context, user identity.Requester, rules ngmodels.RulesGroup) (bool, error)
}
type StatusReader interface {
Status(key ngmodels.AlertRuleKey) (ngmodels.RuleStatus, bool)
}
@@ -33,8 +44,18 @@ type PrometheusSrv struct {
log log.Logger
manager state.AlertInstanceManager
status StatusReader
store RuleStore
authz RuleAccessControlService
store RuleStoreReader
authz RuleGroupAccessControlService
}
func NewPrometheusSrv(log log.Logger, manager state.AlertInstanceManager, status StatusReader, store RuleStoreReader, authz RuleGroupAccessControlService) *PrometheusSrv {
return &PrometheusSrv{
log: log,
manager: manager,
status: status,
store: store,
authz: authz,
}
}
const queryIncludeInternalLabels = "includeInternalLabels"
@@ -99,7 +120,7 @@ func PrepareAlertStatuses(manager state.AlertInstanceManager, opts AlertStatuses
valString := ""
if alertState.State == eval.Alerting || alertState.State == eval.Pending {
valString = formatValues(alertState)
valString = FormatValues(alertState)
}
alertResponse.Data.Alerts = append(alertResponse.Data.Alerts, &apimodels.Alert{
@@ -117,7 +138,7 @@ func PrepareAlertStatuses(manager state.AlertInstanceManager, opts AlertStatuses
return alertResponse
}
func formatValues(alertState *state.State) string {
func FormatValues(alertState *state.State) string {
var fv string
values := alertState.GetLastEvaluationValuesForCondition()
@@ -546,7 +567,7 @@ func toRuleGroup(log log.Logger, manager state.AlertInstanceManager, sr StatusRe
activeAt := alertState.StartsAt
valString := ""
if alertState.State == eval.Alerting || alertState.State == eval.Pending {
valString = formatValues(alertState)
valString = FormatValues(alertState)
}
stateKey := strings.ToLower(alertState.State.String())
totals[stateKey] += 1
@@ -1,4 +1,4 @@
package api
package validation
import (
"errors"
@@ -8,6 +8,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/services/featuremgmt"
. "github.com/grafana/grafana/pkg/services/ngalert/api/compat"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/store"
@@ -32,15 +33,15 @@ func RuleLimitsFromConfig(cfg *setting.UnifiedAlertingSettings, toggles featurem
}
}
// validateRuleNode validates API model (definitions.PostableExtendedRuleNode) and converts it to models.AlertRule
func validateRuleNode(
// ValidateRuleNode validates API model (definitions.PostableExtendedRuleNode) and converts it to models.AlertRule
func ValidateRuleNode(
ruleNode *apimodels.PostableExtendedRuleNode,
groupName string,
interval time.Duration,
orgId int64,
namespaceUID string,
limits RuleLimits) (*ngmodels.AlertRule, error) {
intervalSeconds, err := validateInterval(interval, limits.BaseInterval)
intervalSeconds, err := ValidateInterval(interval, limits.BaseInterval)
if err != nil {
return nil, err
}
@@ -132,7 +133,7 @@ func validateAlertingRuleFields(in *apimodels.PostableExtendedRuleNode, newRule
}
newRule.ExecErrState = errorState
err = validateCondition(in.GrafanaManagedAlert.Condition, in.GrafanaManagedAlert.Data, canPatch)
err = ValidateCondition(in.GrafanaManagedAlert.Condition, in.GrafanaManagedAlert.Data, canPatch)
if err != nil {
return ngmodels.AlertRule{}, err
}
@@ -166,7 +167,7 @@ func validateRecordingRuleFields(in *apimodels.PostableExtendedRuleNode, newRule
return ngmodels.AlertRule{}, fmt.Errorf("%w: recording rules cannot be created on this instance", ngmodels.ErrAlertRuleFailedValidation)
}
err := validateCondition(in.GrafanaManagedAlert.Record.From, in.GrafanaManagedAlert.Data, canPatch)
err := ValidateCondition(in.GrafanaManagedAlert.Record.From, in.GrafanaManagedAlert.Data, canPatch)
if err != nil {
return ngmodels.AlertRule{}, fmt.Errorf("%w: %s", ngmodels.ErrAlertRuleFailedValidation, err.Error())
}
@@ -198,7 +199,7 @@ func validateLabels(l map[string]string) error {
return nil
}
func validateCondition(condition string, queries []apimodels.AlertQuery, canPatch bool) error {
func ValidateCondition(condition string, queries []apimodels.AlertQuery, canPatch bool) error {
if canPatch {
// Patch requests may leave both query and condition blank. If a request supplies one, it must supply the other.
if len(queries) == 0 && condition == "" {
@@ -240,7 +241,7 @@ func validateCondition(condition string, queries []apimodels.AlertQuery, canPatc
return nil
}
func validateInterval(interval, baseInterval time.Duration) (int64, error) {
func ValidateInterval(interval, baseInterval time.Duration) (int64, error) {
intervalSeconds := int64(interval.Seconds())
baseIntervalSeconds := int64(baseInterval.Seconds())
@@ -302,7 +303,7 @@ func ValidateRuleGroup(
result := make([]*ngmodels.AlertRuleWithOptionals, 0, len(ruleGroupConfig.Rules))
uids := make(map[string]int, cap(result))
for idx := range ruleGroupConfig.Rules {
rule, err := validateRuleNode(&ruleGroupConfig.Rules[idx], ruleGroupConfig.Name, interval, orgId, namespaceUID, limits)
rule, err := ValidateRuleNode(&ruleGroupConfig.Rules[idx], ruleGroupConfig.Name, interval, orgId, namespaceUID, limits)
// TODO do not stop on the first failure but return all failures
if err != nil {
return nil, fmt.Errorf("invalid rule specification at index [%d]: %w", idx, err)