Support delete endpoint for folders (#96573)

* Support delete endpoint for folders

* Include authorizer

* Add test for delete verb

* Add delete command to delete options

* Pass query string to context to admission

* Dont support nested folder deletion for now

* Skip test if feature flag is present

* Add test case

* Remove comment

* Only rely on the storage type config to run alerting tests

* Dont change legacy subpath

* Remove unised function

* Add test case when an editor can delete alert rules

* Lint
This commit is contained in:
Leonor Oliveira
2024-11-22 16:51:53 +01:00
committed by GitHub
parent 3c876f0208
commit 2a74778776
5 changed files with 106 additions and 80 deletions
+69 -71
View File
@@ -22,6 +22,7 @@ import (
"github.com/grafana/grafana/pkg/expr"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/apiserver/options"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
ngstore "github.com/grafana/grafana/pkg/services/ngalert/store"
@@ -694,15 +695,18 @@ func TestIntegrationRulerAccess(t *testing.T) {
func TestIntegrationDeleteFolderWithRules(t *testing.T) {
testinfra.SQLiteIntegrationTest(t)
// Setup Grafana and its Database
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
opts := testinfra.GrafanaOpts{
DisableLegacyAlerting: true,
EnableUnifiedAlerting: true,
EnableQuota: true,
DisableAnonymous: true,
ViewersCanEdit: true,
AppModeProduction: true,
})
APIServerStorageType: options.StorageTypeLegacy,
}
// Setup Grafana and its Database
dir, path := testinfra.CreateGrafDir(t, opts)
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
@@ -725,8 +729,7 @@ func TestIntegrationDeleteFolderWithRules(t *testing.T) {
createRule(t, apiClient, "default")
// First, let's have an editor create a rule within the folder/namespace.
{
t.Run("editor create a rule within the folder/namespace", func(t *testing.T) {
u := fmt.Sprintf("http://editor:editor@%s/api/ruler/grafana/api/v1/rules", grafanaListedAddr)
// nolint:gosec
resp, err := http.Get(u)
@@ -746,68 +749,66 @@ func TestIntegrationDeleteFolderWithRules(t *testing.T) {
b = re.ReplaceAll(b, []byte(`"updated":"2021-05-19T19:47:55Z"`))
expectedGetRulesResponseBody := fmt.Sprintf(`{
"default": [
{
"name": "arulegroup",
"interval": "1m",
"rules": [
{
"expr": "",
"for": "2m",
"labels": {
"label1": "val1"
},
"annotations": {
"annotation1": "val1"
},
"grafana_alert": {
"id": 1,
"orgId": 1,
"title": "rule under folder default",
"condition": "A",
"data": [
{
"refId": "A",
"queryType": "",
"relativeTimeRange": {
"from": 18000,
"to": 10800
},
"datasourceUid": "__expr__",
"model": {
"expression": "2 + 3 > 1",
"intervalMs": 1000,
"maxDataPoints": 43200,
"type": "math"
"default": [
{
"name": "arulegroup",
"interval": "1m",
"rules": [
{
"expr": "",
"for": "2m",
"labels": {
"label1": "val1"
},
"annotations": {
"annotation1": "val1"
},
"grafana_alert": {
"id": 1,
"orgId": 1,
"title": "rule under folder default",
"condition": "A",
"data": [
{
"refId": "A",
"queryType": "",
"relativeTimeRange": {
"from": 18000,
"to": 10800
},
"datasourceUid": "__expr__",
"model": {
"expression": "2 + 3 > 1",
"intervalMs": 1000,
"maxDataPoints": 43200,
"type": "math"
}
}
],
"updated": "2021-05-19T19:47:55Z",
"intervalSeconds": 60,
"is_paused": false,
"version": 1,
"uid": "",
"namespace_uid": %q,
"rule_group": "arulegroup",
"no_data_state": "NoData",
"exec_err_state": "Alerting",
"metadata": {
"editor_settings": {
"simplified_query_and_expressions_section": false,
"simplified_notifications_section": false
}
}
],
"updated": "2021-05-19T19:47:55Z",
"intervalSeconds": 60,
"is_paused": false,
"version": 1,
"uid": "",
"namespace_uid": %q,
"rule_group": "arulegroup",
"no_data_state": "NoData",
"exec_err_state": "Alerting",
"metadata": {
"editor_settings": {
"simplified_query_and_expressions_section": false,
"simplified_notifications_section": false
}
}
}
}
]
}
]
}`, namespaceUID)
]
}
]
}`, namespaceUID)
assert.JSONEq(t, expectedGetRulesResponseBody, string(b))
}
// Next, the editor can not delete the folder because it contains Grafana 8 alerts.
{
})
t.Run("editor can not delete the folder because it contains Grafana 8 alerts", func(t *testing.T) {
u := fmt.Sprintf("http://editor:editor@%s/api/folders/%s", grafanaListedAddr, namespaceUID)
req, err := http.NewRequest(http.MethodDelete, u, nil)
require.NoError(t, err)
@@ -825,10 +826,8 @@ func TestIntegrationDeleteFolderWithRules(t *testing.T) {
err = json.Unmarshal(b, &errutilErr)
require.NoError(t, err)
assert.Equal(t, "Folder cannot be deleted: folder is not empty", errutilErr.Message)
}
// Next, the editor can delete the folder if forceDeleteRules is true.
{
})
t.Run("editor can delete the folder if forceDeleteRules is true", func(t *testing.T) {
u := fmt.Sprintf("http://editor:editor@%s/api/folders/%s?forceDeleteRules=true", grafanaListedAddr, namespaceUID)
req, err := http.NewRequest(http.MethodDelete, u, nil)
require.NoError(t, err)
@@ -842,10 +841,8 @@ func TestIntegrationDeleteFolderWithRules(t *testing.T) {
_, err = io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, 200, resp.StatusCode)
}
// Finally, we ensure the rules were deleted.
{
})
t.Run("editor can delete rules", func(t *testing.T) {
u := fmt.Sprintf("http://editor:editor@%s/api/ruler/grafana/api/v1/rules", grafanaListedAddr)
// nolint:gosec
resp, err := http.Get(u)
@@ -859,7 +856,8 @@ func TestIntegrationDeleteFolderWithRules(t *testing.T) {
assert.Equal(t, 200, resp.StatusCode)
assert.JSONEq(t, "{}", string(b))
}
})
// TODO(@leonorfmartins): write tests for uni store when we are able to support it
}
func TestIntegrationAlertRuleCRUD(t *testing.T) {