Alerting: API to return deleted rules (#101429)

This commit is contained in:
Yuri Tseretyan
2025-03-11 12:40:44 -04:00
committed by GitHub
parent 59d87fe3f1
commit 7e4beb2074
7 changed files with 233 additions and 0 deletions
+98
View File
@@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"io"
"maps"
"math/rand"
"net/http"
"path"
@@ -15,6 +16,7 @@ import (
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/prometheus/alertmanager/pkg/labels"
@@ -4645,6 +4647,102 @@ func TestIntegrationRuleVersions(t *testing.T) {
})
}
func TestIntegrationRuleSoftDelete(t *testing.T) {
testinfra.SQLiteIntegrationTest(t)
// Setup Grafana and its Database
dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableLegacyAlerting: true,
EnableUnifiedAlerting: true,
EnableQuota: true,
DisableAnonymous: true,
AppModeProduction: true,
EnableFeatureToggles: []string{featuremgmt.FlagAlertRuleRestore},
})
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p)
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
DefaultOrgRole: string(org.RoleAdmin),
Password: "admin",
Login: "admin",
})
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
DefaultOrgRole: string(org.RoleEditor),
Password: "password",
Login: "editor",
})
adminClient := newAlertingApiClient(grafanaListedAddr, "admin", "admin")
editorClient := newAlertingApiClient(grafanaListedAddr, "editor", "password")
deleted, status, data := adminClient.GetDeletedRulesWithStatus(t)
requireStatusCode(t, http.StatusOK, status, data)
require.Emptyf(t, deleted, "Expected empty list of deleted rules, got %v", deleted)
// Create the namespace we'll save our alerts to.
adminClient.CreateFolder(t, "folder1", "folder1")
var group apimodels.RuleGroupConfigResponse
{ // create rules and some history
postGroupRaw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1-post.json"))
require.NoError(t, err)
var group1 apimodels.PostableRuleGroupConfig
require.NoError(t, json.Unmarshal(postGroupRaw, &group1))
// Create rule under folder1
response := adminClient.PostRulesGroup(t, "folder1", &group1)
require.NotEmptyf(t, response.Created, "Expected created to be set")
// create some versions of the rule
for i := 0; i < 3; i++ {
groups, status := adminClient.GetRulesGroup(t, "folder1", group1.Name)
require.Equal(t, http.StatusAccepted, status)
group1 = convertGettableRuleGroupToPostable(groups.GettableRuleGroupConfig)
group1.Rules[0].Annotations[util.GenerateShortUID()] = util.GenerateShortUID()
_ = adminClient.PostRulesGroup(t, "folder1", &group1)
}
group, status = adminClient.GetRulesGroup(t, "folder1", group1.Name)
require.Equal(t, http.StatusAccepted, status)
}
// deleting group by using editor user
status, body := editorClient.DeleteRulesGroup(t, "folder1", group.Name)
require.Equalf(t, http.StatusAccepted, status, "failed to delete group. Response: %s", body)
t.Run("should see deleted rules", func(t *testing.T) {
rules, status, raw := adminClient.GetDeletedRulesWithStatus(t)
requireStatusCode(t, http.StatusOK, status, raw)
require.Containsf(t, rules, "", "All rules should be in empty folder but got %v", slices.Collect(maps.Keys(rules)))
require.Lenf(t, rules[""], 1, "All deleted rules should be in single group but got %d", len(rules[""]))
require.Equalf(t, "", rules[""][0].Name, "All deleted rules should be in empty group but got %v", rules[""][0].Name)
require.Len(t, rules[""][0].Rules, len(group.Rules))
require.Empty(t, cmp.Diff(group.Rules, rules[""][0].Rules, cmpopts.IgnoreFields(apimodels.GettableGrafanaRule{}, "UID", "Version", "Updated", "UpdatedBy")))
rule := rules[""][0].Rules[0]
require.Equalf(t, "editor", rule.GrafanaManagedAlert.UpdatedBy.Name, "Field 'UpdatedBy' should be set by editor but got %v ", rule.GrafanaManagedAlert.UpdatedBy)
})
t.Run("only admin should be able to see deleted rules", func(t *testing.T) {
t.Run("editor", func(t *testing.T) {
_, status, raw := editorClient.GetDeletedRulesWithStatus(t)
requireStatusCode(t, http.StatusForbidden, status, raw)
})
t.Run("viewer", func(t *testing.T) {
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
DefaultOrgRole: string(org.RoleViewer),
Password: "password",
Login: "viewer",
})
client := newAlertingApiClient(grafanaListedAddr, "viewer", "password")
_, status, raw := client.GetDeletedRulesWithStatus(t)
requireStatusCode(t, http.StatusForbidden, status, raw)
})
})
}
func newTestingRuleConfig(t *testing.T) apimodels.PostableRuleGroupConfig {
interval, err := model.ParseDuration("1m")
require.NoError(t, err)
+10
View File
@@ -647,6 +647,16 @@ func (a apiClient) GetAllRulesWithStatus(t *testing.T) (apimodels.NamespaceConfi
return result, resp.StatusCode, b
}
func (a apiClient) GetDeletedRulesWithStatus(t *testing.T) (apimodels.NamespaceConfigResponse, int, string) {
t.Helper()
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules", a.url), nil)
require.NoError(t, err)
q := req.URL.Query()
q.Add("deleted", "true")
req.URL.RawQuery = q.Encode()
return sendRequestJSON[apimodels.NamespaceConfigResponse](t, req, http.StatusOK)
}
func (a apiClient) ExportRulesWithStatus(t *testing.T, params *apimodels.AlertRulesExportParameters) (int, string) {
t.Helper()
u, err := url.Parse(fmt.Sprintf("%s/api/ruler/grafana/api/v1/export/rules", a.url))