Alerting: Allow specifying uid for new rules added to groups (#99858)

When modifying rule groups the `uid` can be specified but only if the rule already existed in the DB. If the rule is new the update would be rejected.

This updates the RuleGroup provisioning apis to allow specifying the `uid` when creating/updating rule groups. 

Additionally, the RuleGroupIdx was not being updated when rules were reordered in the group.

Context: https://github.com/grafana/terraform-provider-grafana/pull/1971#issuecomment-2599223897
Relates to: https://github.com/grafana/terraform-provider-grafana/issues/1928

Fixes: #98283
This commit is contained in:
Moustafa Baiou
2025-02-10 10:28:34 -05:00
committed by GitHub
parent 55e7c4ae6d
commit 7dee4d1808
8 changed files with 467 additions and 42 deletions
+171 -1
View File
@@ -508,7 +508,34 @@ func TestIntegrationProvisioning(t *testing.T) {
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"}`
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)
@@ -535,6 +562,149 @@ func TestIntegrationProvisioning(t *testing.T) {
})
}
func TestIntegrationProvisioningRules(t *testing.T) {
testinfra.SQLiteIntegrationTest(t)
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableLegacyAlerting: true,
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
})
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
// Create a users to make authenticated requests
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
DefaultOrgRole: string(org.RoleViewer),
Password: "viewer",
Login: "viewer",
})
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
DefaultOrgRole: string(org.RoleEditor),
Password: "editor",
Login: "editor",
})
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
DefaultOrgRole: string(org.RoleAdmin),
Password: "admin",
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 alert rules", func(t *testing.T) {
originalRuleGroup := definitions.AlertRuleGroup{
Title: "TestGroup",
Interval: 60,
FolderUID: "default",
Rules: []definitions.ProvisionedAlertRule{
{
UID: "rule1",
Title: "Rule1",
OrgID: 1,
RuleGroup: "TestGroup",
Condition: "A",
NoDataState: definitions.Alerting,
ExecErrState: definitions.AlertingErrState,
For: model.Duration(time.Duration(60) * time.Second),
Data: []definitions.AlertQuery{
{
RefID: "A",
RelativeTimeRange: definitions.RelativeTimeRange{
From: definitions.Duration(time.Duration(5) * time.Hour),
To: definitions.Duration(time.Duration(3) * time.Hour),
},
DatasourceUID: expr.DatasourceUID,
Model: json.RawMessage([]byte(`{"type":"math","expression":"2 + 3 \u003e 1"}`)),
},
},
},
{
UID: "rule2",
Title: "Rule2",
OrgID: 1,
RuleGroup: "TestGroup",
Condition: "A",
NoDataState: definitions.Alerting,
ExecErrState: definitions.AlertingErrState,
For: model.Duration(time.Duration(60) * time.Second),
Data: []definitions.AlertQuery{
{
RefID: "A",
RelativeTimeRange: definitions.RelativeTimeRange{
From: definitions.Duration(time.Duration(5) * time.Hour),
To: definitions.Duration(time.Duration(3) * time.Hour),
},
DatasourceUID: expr.DatasourceUID,
Model: json.RawMessage([]byte(`{"type":"math","expression":"2 + 3 \u003e 1"}`)),
},
},
},
{
UID: "rule3",
Title: "Rule3",
OrgID: 1,
RuleGroup: "TestGroup",
Condition: "A",
NoDataState: definitions.Alerting,
ExecErrState: definitions.AlertingErrState,
For: model.Duration(time.Duration(60) * time.Second),
Data: []definitions.AlertQuery{
{
RefID: "A",
RelativeTimeRange: definitions.RelativeTimeRange{
From: definitions.Duration(time.Duration(5) * time.Hour),
To: definitions.Duration(time.Duration(3) * time.Hour),
},
DatasourceUID: expr.DatasourceUID,
Model: json.RawMessage([]byte(`{"type":"math","expression":"2 + 3 \u003e 1"}`)),
},
},
},
},
}
result, status, raw := apiClient.CreateOrUpdateRuleGroupProvisioning(t, originalRuleGroup)
t.Run("should create a new rule group with UIDs specified", func(t *testing.T) {
requireStatusCode(t, http.StatusOK, status, raw)
require.Equal(t, originalRuleGroup, result)
})
t.Run("should remove a rule when updating group with a rule removed", func(t *testing.T) {
existingRuleGroup, status, raw := apiClient.GetRuleGroupProvisioning(t, "default", "TestGroup")
requireStatusCode(t, http.StatusOK, status, raw)
require.Len(t, existingRuleGroup.Rules, 3)
updatedRuleGroup := existingRuleGroup
updatedRuleGroup.Rules = updatedRuleGroup.Rules[:2]
result, status, raw := apiClient.CreateOrUpdateRuleGroupProvisioning(t, updatedRuleGroup)
requireStatusCode(t, http.StatusOK, status, raw)
require.Equal(t, updatedRuleGroup, result)
// Check that the rule was removed
rules, status, raw := apiClient.GetRuleGroupProvisioning(t, existingRuleGroup.FolderUID, existingRuleGroup.Title)
requireStatusCode(t, http.StatusOK, status, raw)
require.Len(t, rules.Rules, 2)
})
t.Run("should recreate a rule when updating group with the rule added back", func(t *testing.T) {
result, status, raw := apiClient.CreateOrUpdateRuleGroupProvisioning(t, originalRuleGroup)
requireStatusCode(t, http.StatusOK, status, raw)
require.Equal(t, originalRuleGroup, result)
require.Len(t, result.Rules, 3)
// Check that the rule was re-added
rules, status, raw := apiClient.GetRuleGroupProvisioning(t, originalRuleGroup.FolderUID, originalRuleGroup.Title)
requireStatusCode(t, http.StatusOK, status, raw)
require.Len(t, rules.Rules, 3)
})
})
}
func TestMuteTimings(t *testing.T) {
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableLegacyAlerting: true,