Alerting: indicate whether an alertrule is provisioned (#48458)

This commit is contained in:
Jean-Philippe Quéméner 2022-04-28 21:27:34 +02:00 committed by GitHub
parent 7048bf9f0d
commit 9e21e4d1c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 79 additions and 9 deletions

View File

@ -52,6 +52,7 @@ Scopes must have an order to ensure consistency and ease of search, this helps u
- `grafana_alerting_ticker_interval_seconds`
- [FEATURE] Indicate whether routes are provisioned when GETting Alertmanager configuration #47857
- [FEATURE] Indicate whether contact point is provisioned when GETting Alertmanager configuration #48323
- [FEATURE] Indicate whether alert rule is provisioned when GETting the rule #48458
## 8.5.1

View File

@ -67,6 +67,7 @@ type API struct {
QuotaService *quota.QuotaService
Schedule schedule.ScheduleService
TransactionManager provisioning.TransactionManager
ProvenanceStore provisioning.ProvisioningStore
RuleStore store.RuleStore
InstanceStore store.InstanceStore
AlertingStore AlertingStore
@ -109,6 +110,7 @@ func (api *API) RegisterAPIEndpoints(m *metrics.API) {
QuotaService: api.QuotaService,
scheduleService: api.Schedule,
store: api.RuleStore,
provenanceStore: api.ProvenanceStore,
xactManager: api.TransactionManager,
log: logger,
cfg: &api.Cfg.UnifiedAlerting,

View File

@ -30,6 +30,7 @@ import (
type RulerSrv struct {
xactManager provisioning.TransactionManager
provenanceStore provisioning.ProvisioningStore
store store.RuleStore
DatasourceCache datasources.CacheService
QuotaService *quota.QuotaService
@ -143,6 +144,11 @@ func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *models.ReqContext) response.
return accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqSignedIn, evaluator)
}
provenanceRecords, err := srv.provenanceStore.GetProvenances(c.Req.Context(), c.SignedInUser.OrgId, (&ngmodels.AlertRule{}).ResourceType())
if err != nil {
return ErrResp(http.StatusInternalServerError, err, "failed to get provenance for rule group")
}
for _, r := range q.Result {
if !authorizeDatasourceAccessForRule(r, hasAccess) {
continue
@ -154,11 +160,11 @@ func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *models.ReqContext) response.
Name: r.RuleGroup,
Interval: ruleGroupInterval,
Rules: []apimodels.GettableExtendedRuleNode{
toGettableExtendedRuleNode(*r, namespace.Id),
toGettableExtendedRuleNode(*r, namespace.Id, provenanceRecords),
},
}
} else {
ruleGroupConfig.Rules = append(ruleGroupConfig.Rules, toGettableExtendedRuleNode(*r, namespace.Id))
ruleGroupConfig.Rules = append(ruleGroupConfig.Rules, toGettableExtendedRuleNode(*r, namespace.Id, provenanceRecords))
ruleGroupConfigs[r.RuleGroup] = ruleGroupConfig
}
}
@ -170,7 +176,7 @@ func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *models.ReqContext) response.
return response.JSON(http.StatusAccepted, result)
}
func (srv RulerSrv) RouteGetRulegGroupConfig(c *models.ReqContext) response.Response {
func (srv RulerSrv) RouteGetRulesGroupConfig(c *models.ReqContext) response.Response {
namespaceTitle := web.Params(c.Req)[":Namespace"]
namespace, err := srv.store.GetNamespaceByTitle(c.Req.Context(), namespaceTitle, c.SignedInUser.OrgId, c.SignedInUser, false)
if err != nil {
@ -194,12 +200,17 @@ func (srv RulerSrv) RouteGetRulegGroupConfig(c *models.ReqContext) response.Resp
return accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqSignedIn, evaluator)
}
provenanceRecords, err := srv.provenanceStore.GetProvenances(c.Req.Context(), c.SignedInUser.OrgId, (&ngmodels.AlertRule{}).ResourceType())
if err != nil {
return ErrResp(http.StatusInternalServerError, err, "failed to get group alert rules")
}
for _, r := range q.Result {
if !authorizeDatasourceAccessForRule(r, hasAccess) {
continue
}
ruleGroupInterval = model.Duration(time.Duration(r.IntervalSeconds) * time.Second)
ruleNodes = append(ruleNodes, toGettableExtendedRuleNode(*r, namespace.Id))
ruleNodes = append(ruleNodes, toGettableExtendedRuleNode(*r, namespace.Id, provenanceRecords))
}
result := apimodels.RuleGroupConfigResponse{
@ -255,6 +266,11 @@ func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response
return accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqSignedIn, evaluator)
}
provenanceRecords, err := srv.provenanceStore.GetProvenances(c.Req.Context(), c.SignedInUser.OrgId, (&ngmodels.AlertRule{}).ResourceType())
if err != nil {
return ErrResp(http.StatusInternalServerError, err, "failed to get alert rules")
}
for _, r := range q.Result {
if !authorizeDatasourceAccessForRule(r, hasAccess) {
continue
@ -273,7 +289,7 @@ func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response
Name: r.RuleGroup,
Interval: ruleGroupInterval,
Rules: []apimodels.GettableExtendedRuleNode{
toGettableExtendedRuleNode(*r, folder.Id),
toGettableExtendedRuleNode(*r, folder.Id, provenanceRecords),
},
}
} else {
@ -284,11 +300,11 @@ func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response
Name: r.RuleGroup,
Interval: ruleGroupInterval,
Rules: []apimodels.GettableExtendedRuleNode{
toGettableExtendedRuleNode(*r, folder.Id),
toGettableExtendedRuleNode(*r, folder.Id, provenanceRecords),
},
}
} else {
ruleGroupConfig.Rules = append(ruleGroupConfig.Rules, toGettableExtendedRuleNode(*r, folder.Id))
ruleGroupConfig.Rules = append(ruleGroupConfig.Rules, toGettableExtendedRuleNode(*r, folder.Id, provenanceRecords))
configs[namespace][r.RuleGroup] = ruleGroupConfig
}
}
@ -437,7 +453,11 @@ func (srv RulerSrv) updateAlertRulesInGroup(c *models.ReqContext, namespace *mod
return response.JSON(http.StatusAccepted, util.DynMap{"message": "rule group updated successfully"})
}
func toGettableExtendedRuleNode(r ngmodels.AlertRule, namespaceID int64) apimodels.GettableExtendedRuleNode {
func toGettableExtendedRuleNode(r ngmodels.AlertRule, namespaceID int64, provenanceRecords map[string]ngmodels.Provenance) apimodels.GettableExtendedRuleNode {
provenance := ngmodels.ProvenanceNone
if prov, exists := provenanceRecords[r.ResourceID()]; exists {
provenance = prov
}
gettableExtendedRuleNode := apimodels.GettableExtendedRuleNode{
GrafanaManagedAlert: &apimodels.GettableGrafanaRule{
ID: r.ID,
@ -454,6 +474,7 @@ func toGettableExtendedRuleNode(r ngmodels.AlertRule, namespaceID int64) apimode
RuleGroup: r.RuleGroup,
NoDataState: apimodels.NoDataState(r.NoDataState),
ExecErrState: apimodels.ExecutionErrorState(r.ExecErrState),
Provenance: provenance,
},
}
gettableExtendedRuleNode.ApiRuleNode = &apimodels.ApiRuleNode{

View File

@ -20,6 +20,7 @@ import (
"github.com/grafana/grafana/pkg/services/datasources"
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/provisioning"
"github.com/grafana/grafana/pkg/services/ngalert/schedule"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/util"
@ -550,6 +551,48 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) {
assert.Emptyf(t, expectedRules, "not all expected rules were returned")
})
})
t.Run("should return the provenance of the alert rules", func(t *testing.T) {
orgID := rand.Int63()
folder := randFolder()
ruleStore := store.NewFakeRuleStore(t)
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
expectedRules := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))
ruleStore.PutRule(context.Background(), expectedRules...)
ac := acMock.New().WithDisabled()
svc := createService(ac, ruleStore, nil)
// add provenance to the first generated rule
rule := &models.AlertRule{
UID: expectedRules[0].UID,
}
err := svc.provenanceStore.SetProvenance(context.Background(), rule, orgID, models.ProvenanceAPI)
require.NoError(t, err)
response := svc.RouteGetNamespaceRulesConfig(createRequestContext(orgID, "", map[string]string{
":Namespace": folder.Title,
}))
require.Equal(t, http.StatusAccepted, response.Status())
result := &apimodels.NamespaceConfigResponse{}
require.NoError(t, json.Unmarshal(response.Body(), result))
require.NotNil(t, result)
found := false
for namespace, groups := range *result {
require.Equal(t, folder.Title, namespace)
for _, group := range groups {
for _, actualRule := range group.Rules {
if actualRule.GrafanaManagedAlert.UID == expectedRules[0].UID {
require.Equal(t, models.ProvenanceAPI, actualRule.GrafanaManagedAlert.Provenance)
found = true
} else {
require.Equal(t, models.ProvenanceNone, actualRule.GrafanaManagedAlert.Provenance)
}
}
}
}
require.True(t, found)
})
}
func createService(ac *acMock.Mock, store *store.FakeRuleStore, scheduler schedule.ScheduleService) *RulerSrv {
@ -558,6 +601,7 @@ func createService(ac *acMock.Mock, store *store.FakeRuleStore, scheduler schedu
store: store,
DatasourceCache: nil,
QuotaService: nil,
provenanceStore: provisioning.NewFakeProvisioningStore(),
scheduleService: scheduler,
log: log.New("test"),
cfg: nil,

View File

@ -122,7 +122,7 @@ func (f *ForkedRulerApi) forkRouteGetNamespaceGrafanaRulesConfig(ctx *models.Req
}
func (f *ForkedRulerApi) forkRouteGetGrafanaRuleGroupConfig(ctx *models.ReqContext) response.Response {
return f.GrafanaRuler.RouteGetRulegGroupConfig(ctx)
return f.GrafanaRuler.RouteGetRulesGroupConfig(ctx)
}
func (f *ForkedRulerApi) forkRouteGetGrafanaRulesConfig(ctx *models.ReqContext) response.Response {

View File

@ -386,4 +386,5 @@ type GettableGrafanaRule struct {
RuleGroup string `json:"rule_group" yaml:"rule_group"`
NoDataState NoDataState `json:"no_data_state" yaml:"no_data_state"`
ExecErrState ExecutionErrorState `json:"exec_err_state" yaml:"exec_err_state"`
Provenance models.Provenance `json:"provenance,omitempty" yaml:"provenance,omitempty"`
}

View File

@ -156,6 +156,7 @@ func (ng *AlertNG) init() error {
RuleStore: store,
AlertingStore: store,
AdminConfigStore: store,
ProvenanceStore: store,
MultiOrgAlertmanager: ng.MultiOrgAlertmanager,
StateManager: ng.stateManager,
AccessControl: ng.accesscontrol,