diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go index c75d8a35df0..886070aee09 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go @@ -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" diff --git a/pkg/services/ngalert/api/api.go b/pkg/services/ngalert/api/api.go index 03431b0d7ca..31c9211ad6a 100644 --- a/pkg/services/ngalert/api/api.go +++ b/pkg/services/ngalert/api/api.go @@ -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( diff --git a/pkg/services/ngalert/api/api_notifications.go b/pkg/services/ngalert/api/api_notifications.go index c828895c1bf..c29a9d14c16 100644 --- a/pkg/services/ngalert/api/api_notifications.go +++ b/pkg/services/ngalert/api/api_notifications.go @@ -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" ) diff --git a/pkg/services/ngalert/api/api_notifications_test.go b/pkg/services/ngalert/api/api_notifications_test.go index d4e97b3c6f3..aec7c0072c1 100644 --- a/pkg/services/ngalert/api/api_notifications_test.go +++ b/pkg/services/ngalert/api/api_notifications_test.go @@ -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" diff --git a/pkg/services/ngalert/api/api_prometheus_test.go b/pkg/services/ngalert/api/api_prometheus_test.go index 8ac41c3606e..d37dea79d2a 100644 --- a/pkg/services/ngalert/api/api_prometheus_test.go +++ b/pkg/services/ngalert/api/api_prometheus_test.go @@ -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 } diff --git a/pkg/services/ngalert/api/api_provisioning.go b/pkg/services/ngalert/api/api_provisioning.go index b5c7f5ab1c2..44e2fd9a935 100644 --- a/pkg/services/ngalert/api/api_provisioning.go +++ b/pkg/services/ngalert/api/api_provisioning.go @@ -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" diff --git a/pkg/services/ngalert/api/api_ruler.go b/pkg/services/ngalert/api/api_ruler.go index e5ece7154e3..f3f9573be66 100644 --- a/pkg/services/ngalert/api/api_ruler.go +++ b/pkg/services/ngalert/api/api_ruler.go @@ -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 +} diff --git a/pkg/services/ngalert/api/api_ruler_export.go b/pkg/services/ngalert/api/api_ruler_export.go index bd940a35b45..38af5aa9e4f 100644 --- a/pkg/services/ngalert/api/api_ruler_export.go +++ b/pkg/services/ngalert/api/api_ruler_export.go @@ -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, "") } diff --git a/pkg/services/ngalert/api/api_ruler_export_test.go b/pkg/services/ngalert/api/api_ruler_export_test.go index 866e31dbae0..f976bcf0e4e 100644 --- a/pkg/services/ngalert/api/api_ruler_export_test.go +++ b/pkg/services/ngalert/api/api_ruler_export_test.go @@ -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" diff --git a/pkg/services/ngalert/api/api_ruler_validation_test.go b/pkg/services/ngalert/api/api_ruler_validation_test.go index b56705f625f..7f47630a56b 100644 --- a/pkg/services/ngalert/api/api_ruler_validation_test.go +++ b/pkg/services/ngalert/api/api_ruler_validation_test.go @@ -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) }) diff --git a/pkg/services/ngalert/api/api_testing.go b/pkg/services/ngalert/api/api_testing.go index ce1d941f000..a8eaec2223e 100644 --- a/pkg/services/ngalert/api/api_testing.go +++ b/pkg/services/ngalert/api/api_testing.go @@ -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, "") } diff --git a/pkg/services/ngalert/api/api_testing_test.go b/pkg/services/ngalert/api/api_testing_test.go index a8bee7838dd..4a1862240df 100644 --- a/pkg/services/ngalert/api/api_testing_test.go +++ b/pkg/services/ngalert/api/api_testing_test.go @@ -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" diff --git a/pkg/services/ngalert/api/compat.go b/pkg/services/ngalert/api/compat/compat.go similarity index 100% rename from pkg/services/ngalert/api/compat.go rename to pkg/services/ngalert/api/compat/compat.go diff --git a/pkg/services/ngalert/api/compat_test.go b/pkg/services/ngalert/api/compat/compat_test.go similarity index 100% rename from pkg/services/ngalert/api/compat_test.go rename to pkg/services/ngalert/api/compat/compat_test.go diff --git a/pkg/services/ngalert/api/compat_contact_points_test.go b/pkg/services/ngalert/api/compat_contact_points_test.go index 58698c8a65a..b8fa71fbee9 100644 --- a/pkg/services/ngalert/api/compat_contact_points_test.go +++ b/pkg/services/ngalert/api/compat_contact_points_test.go @@ -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) } diff --git a/pkg/services/ngalert/api/forking_prometheus.go b/pkg/services/ngalert/api/forking_prometheus.go index c0fa78b9f74..e5286d4db48 100644 --- a/pkg/services/ngalert/api/forking_prometheus.go +++ b/pkg/services/ngalert/api/forking_prometheus.go @@ -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, diff --git a/pkg/services/ngalert/api/api_prometheus.go b/pkg/services/ngalert/api/prometheus/api_prometheus.go similarity index 95% rename from pkg/services/ngalert/api/api_prometheus.go rename to pkg/services/ngalert/api/prometheus/api_prometheus.go index 364a7027253..635756e3a88 100644 --- a/pkg/services/ngalert/api/api_prometheus.go +++ b/pkg/services/ngalert/api/prometheus/api_prometheus.go @@ -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 diff --git a/pkg/services/ngalert/api/api_ruler_validation.go b/pkg/services/ngalert/api/validation/api_ruler_validation.go similarity index 95% rename from pkg/services/ngalert/api/api_ruler_validation.go rename to pkg/services/ngalert/api/validation/api_ruler_validation.go index 672873fa040..1635658fcd1 100644 --- a/pkg/services/ngalert/api/api_ruler_validation.go +++ b/pkg/services/ngalert/api/validation/api_ruler_validation.go @@ -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)