From 05e12e787bc002eccb30a3085aaa397f4fc11e6f Mon Sep 17 00:00:00 2001 From: George Robinson Date: Wed, 11 Oct 2023 14:51:20 +0100 Subject: [PATCH] Alerting: Add provenance field to /api/v1/provisioning/alert-rules (#76252) This commit adds the missing Provenance field to responses for /api/v1/provisioning/alert-rules. --- pkg/services/ngalert/api/api_provisioning.go | 6 ++-- pkg/services/ngalert/api/compat.go | 4 +-- .../ngalert/provisioning/alert_rules.go | 21 +++++++---- .../api/alerting/api_provisioning_test.go | 36 +++++++++++++++++++ 4 files changed, 55 insertions(+), 12 deletions(-) diff --git a/pkg/services/ngalert/api/api_provisioning.go b/pkg/services/ngalert/api/api_provisioning.go index 14733cbefd5..bd825349c28 100644 --- a/pkg/services/ngalert/api/api_provisioning.go +++ b/pkg/services/ngalert/api/api_provisioning.go @@ -57,7 +57,7 @@ type MuteTimingService interface { } type AlertRuleService interface { - GetAlertRules(ctx context.Context, orgID int64) ([]*alerting_models.AlertRule, error) + GetAlertRules(ctx context.Context, orgID int64) ([]*alerting_models.AlertRule, map[string]alerting_models.Provenance, error) GetAlertRule(ctx context.Context, orgID int64, ruleUID string) (alerting_models.AlertRule, alerting_models.Provenance, error) CreateAlertRule(ctx context.Context, rule alerting_models.AlertRule, provenance alerting_models.Provenance, userID int64) (alerting_models.AlertRule, error) UpdateAlertRule(ctx context.Context, rule alerting_models.AlertRule, provenance alerting_models.Provenance) (alerting_models.AlertRule, error) @@ -300,11 +300,11 @@ func (srv *ProvisioningSrv) RouteDeleteMuteTiming(c *contextmodel.ReqContext, na } func (srv *ProvisioningSrv) RouteGetAlertRules(c *contextmodel.ReqContext) response.Response { - rules, err := srv.alertRules.GetAlertRules(c.Req.Context(), c.SignedInUser.GetOrgID()) + rules, provenances, err := srv.alertRules.GetAlertRules(c.Req.Context(), c.SignedInUser.GetOrgID()) if err != nil { return ErrResp(http.StatusInternalServerError, err, "") } - return response.JSON(http.StatusOK, ProvisionedAlertRuleFromAlertRules(rules)) + return response.JSON(http.StatusOK, ProvisionedAlertRuleFromAlertRules(rules, provenances)) } func (srv *ProvisioningSrv) RouteRouteGetAlertRule(c *contextmodel.ReqContext, UID string) response.Response { diff --git a/pkg/services/ngalert/api/compat.go b/pkg/services/ngalert/api/compat.go index fc5a01ba083..899609a7dc3 100644 --- a/pkg/services/ngalert/api/compat.go +++ b/pkg/services/ngalert/api/compat.go @@ -55,10 +55,10 @@ func ProvisionedAlertRuleFromAlertRule(rule models.AlertRule, provenance models. } // ProvisionedAlertRuleFromAlertRules converts a collection of models.AlertRule to definitions.ProvisionedAlertRules with provenance status models.ProvenanceNone -func ProvisionedAlertRuleFromAlertRules(rules []*models.AlertRule) definitions.ProvisionedAlertRules { +func ProvisionedAlertRuleFromAlertRules(rules []*models.AlertRule, provenances map[string]models.Provenance) definitions.ProvisionedAlertRules { result := make([]definitions.ProvisionedAlertRule, 0, len(rules)) for _, r := range rules { - result = append(result, ProvisionedAlertRuleFromAlertRule(*r, models.ProvenanceNone)) + result = append(result, ProvisionedAlertRuleFromAlertRule(*r, provenances[r.UID])) } return result } diff --git a/pkg/services/ngalert/provisioning/alert_rules.go b/pkg/services/ngalert/provisioning/alert_rules.go index 0c354d0d08c..043f7d8d989 100644 --- a/pkg/services/ngalert/provisioning/alert_rules.go +++ b/pkg/services/ngalert/provisioning/alert_rules.go @@ -45,16 +45,23 @@ func NewAlertRuleService(ruleStore RuleStore, } } -func (service *AlertRuleService) GetAlertRules(ctx context.Context, orgID int64) ([]*models.AlertRule, error) { +func (service *AlertRuleService) GetAlertRules(ctx context.Context, orgID int64) ([]*models.AlertRule, map[string]models.Provenance, error) { q := models.ListAlertRulesQuery{ OrgID: orgID, } rules, err := service.ruleStore.ListAlertRules(ctx, &q) if err != nil { - return nil, err + return nil, nil, err } - // TODO: GET provenance - return rules, nil + provenances := make(map[string]models.Provenance) + if len(rules) > 0 { + resourceType := rules[0].ResourceType() + provenances, err = service.provenanceStore.GetProvenances(ctx, orgID, resourceType) + if err != nil { + return nil, nil, err + } + } + return rules, provenances, nil } func (service *AlertRuleService) GetAlertRule(ctx context.Context, orgID int64, ruleUID string) (models.AlertRule, models.Provenance, error) { @@ -62,15 +69,15 @@ func (service *AlertRuleService) GetAlertRule(ctx context.Context, orgID int64, OrgID: orgID, UID: ruleUID, } - rules, err := service.ruleStore.GetAlertRuleByUID(ctx, query) + rule, err := service.ruleStore.GetAlertRuleByUID(ctx, query) if err != nil { return models.AlertRule{}, models.ProvenanceNone, err } - provenance, err := service.provenanceStore.GetProvenance(ctx, rules, orgID) + provenance, err := service.provenanceStore.GetProvenance(ctx, rule, orgID) if err != nil { return models.AlertRule{}, models.ProvenanceNone, err } - return *rules, provenance, nil + return *rule, provenance, nil } type AlertRuleWithFolderTitle struct { diff --git a/pkg/tests/api/alerting/api_provisioning_test.go b/pkg/tests/api/alerting/api_provisioning_test.go index 4bef914bb2a..9a8c424abe5 100644 --- a/pkg/tests/api/alerting/api_provisioning_test.go +++ b/pkg/tests/api/alerting/api_provisioning_test.go @@ -2,13 +2,16 @@ package alerting import ( "bytes" + "encoding/json" "fmt" "io" "net/http" + "sort" "testing" "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/tests/testinfra" @@ -43,6 +46,11 @@ func TestIntegrationProvisioning(t *testing.T) { Login: "admin", }) + apiClient := newAlertingApiClient(grafanaListedAddr, "editor", "editor") + // Create the namespace we'll save our alerts to. + namespaceUID := "default" + apiClient.CreateFolder(t, namespaceUID, namespaceUID) + t.Run("when provisioning notification policies", func(t *testing.T) { url := fmt.Sprintf("http://%s/api/v1/provisioning/policies", grafanaListedAddr) body := ` @@ -333,6 +341,34 @@ func TestIntegrationProvisioning(t *testing.T) { require.Equal(t, 200, resp.StatusCode) }) }) + + t.Run("when provisioning alert rules", func(t *testing.T) { + url := fmt.Sprintf("http://%s/api/v1/provisioning/alert-rules", grafanaListedAddr) + body := `{"orgID":1,"folderUID":"default","ruleGroup":"Test Group","title":"Provisioned","condition":"A","data":[{"refId":"A","queryType":"","relativeTimeRange":{"from":600,"to":0},"datasourceUid":"f558c85f-66ad-4fd1-b31d-7979e6c93db4","model":{"editorMode":"code","exemplar":false,"expr":"sum(rate(low_card[5m])) \u003e 0","format":"time_series","instant":true,"intervalMs":1000,"legendFormat":"__auto","maxDataPoints":43200,"range":false,"refId":"A"}}],"noDataState":"NoData","execErrState":"Error","for":"0s"}` + req := createTestRequest("POST", url, "admin", body) + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + require.NoError(t, resp.Body.Close()) + require.Equal(t, 201, resp.StatusCode) + + // We want to check the provenances of both provisioned and non-provisioned rules + createRule(t, apiClient, namespaceUID) + + req = createTestRequest("GET", url, "admin", "") + resp, err = http.DefaultClient.Do(req) + require.NoError(t, err) + + var rules definitions.ProvisionedAlertRules + require.NoError(t, json.NewDecoder(resp.Body).Decode(&rules)) + require.NoError(t, resp.Body.Close()) + + require.Len(t, rules, 2) + sort.Slice(rules, func(i, j int) bool { + return rules[i].ID < rules[j].ID + }) + require.Equal(t, definitions.Provenance("api"), rules[0].Provenance) + require.Equal(t, definitions.Provenance(""), rules[1].Provenance) + }) } func createTestRequest(method string, url string, user string, body string) *http.Request {