Alerting: Allow specifying a folder for Prometheus rule import (#101406)
What is this feature?
Allows the creation of alert rules with mimirtool in a specified folder.
Why do we need this feature?
Currently, the APIs for mimirtool create namespaces and rule groups in the root folder without the ability to set a custom folder. For example, it could be a special "Imported" folder, etc.
This PR makes it possible with a special header: mimirtool ... --extra-headers="X-Grafana-Alerting-Folder-UID=123". If it's not present, the root folder is used, otherwise, the specified one is used.
mimirtool does not support nested folder structures, while Grafana allows folder nesting. To keep compatibility, we return only direct child folders of the working folder (as namespaces) with rule groups and rules that are directly in these child folders as if there are no nested folders.
For example, given this folder structure in Grafana:
```
grafana/
├── production/
│ ├── service1/
│ │ └── alerts/
│ └── service2/
└── testing/
└── service3/
```
If the working folder is "grafana":
Only namespaces "production" and "testing" are returned
Only rule groups directly within these folders are included
If the working folder is "production":
- Only namespaces "service1" and "service2" are returned
Only rule groups directly within these folders are included
This commit is contained in:
@@ -1102,14 +1102,28 @@ func (a apiClient) GetRuleByUID(t *testing.T, ruleUID string) apimodels.Gettable
|
||||
return rule
|
||||
}
|
||||
|
||||
func (a apiClient) ConvertPrometheusPostRuleGroup(t *testing.T, namespaceTitle, datasourceUID string, promGroup apimodels.PrometheusRuleGroup, headers map[string]string) (apimodels.ConvertPrometheusResponse, int, string) {
|
||||
func (a apiClient) ConvertPrometheusPostRuleGroup(t *testing.T, namespaceTitle, datasourceUID string, promGroup apimodels.PrometheusRuleGroup, headers map[string]string) apimodels.ConvertPrometheusResponse {
|
||||
t.Helper()
|
||||
|
||||
resp, status, body := a.RawConvertPrometheusPostRuleGroup(t, namespaceTitle, datasourceUID, promGroup, headers)
|
||||
requireStatusCode(t, http.StatusAccepted, status, body)
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
func (a apiClient) RawConvertPrometheusPostRuleGroup(t *testing.T, namespaceTitle, datasourceUID string, promGroup apimodels.PrometheusRuleGroup, headers map[string]string) (apimodels.ConvertPrometheusResponse, int, string) {
|
||||
t.Helper()
|
||||
|
||||
path := "%s/api/convert/prometheus/config/v1/rules/%s"
|
||||
if a.prometheusConversionUseLokiPaths {
|
||||
path = "%s/api/convert/api/prom/rules/%s"
|
||||
}
|
||||
|
||||
data, err := yaml.Marshal(promGroup)
|
||||
require.NoError(t, err)
|
||||
buf := bytes.NewReader(data)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/convert/prometheus/config/v1/rules/%s", a.url, namespaceTitle), buf)
|
||||
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf(path, a.url, namespaceTitle), buf)
|
||||
require.NoError(t, err)
|
||||
req.Header.Add("X-Grafana-Alerting-Datasource-UID", datasourceUID)
|
||||
|
||||
@@ -1120,7 +1134,16 @@ func (a apiClient) ConvertPrometheusPostRuleGroup(t *testing.T, namespaceTitle,
|
||||
return sendRequestJSON[apimodels.ConvertPrometheusResponse](t, req, http.StatusAccepted)
|
||||
}
|
||||
|
||||
func (a apiClient) ConvertPrometheusGetRuleGroupRules(t *testing.T, namespaceTitle, groupName string) apimodels.PrometheusRuleGroup {
|
||||
func (a apiClient) ConvertPrometheusGetRuleGroupRules(t *testing.T, namespaceTitle, groupName string, headers map[string]string) apimodels.PrometheusRuleGroup {
|
||||
t.Helper()
|
||||
|
||||
rule, status, raw := a.RawConvertPrometheusGetRuleGroupRules(t, namespaceTitle, groupName, headers)
|
||||
requireStatusCode(t, http.StatusOK, status, raw)
|
||||
|
||||
return rule
|
||||
}
|
||||
|
||||
func (a apiClient) RawConvertPrometheusGetRuleGroupRules(t *testing.T, namespaceTitle, groupName string, headers map[string]string) (apimodels.PrometheusRuleGroup, int, string) {
|
||||
t.Helper()
|
||||
|
||||
path := "%s/api/convert/prometheus/config/v1/rules/%s/%s"
|
||||
@@ -1130,12 +1153,17 @@ func (a apiClient) ConvertPrometheusGetRuleGroupRules(t *testing.T, namespaceTit
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf(path, a.url, namespaceTitle, groupName), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
for key, value := range headers {
|
||||
req.Header.Add(key, value)
|
||||
}
|
||||
|
||||
rule, status, raw := sendRequestYAML[apimodels.PrometheusRuleGroup](t, req, http.StatusOK)
|
||||
requireStatusCode(t, http.StatusOK, status, raw)
|
||||
return rule
|
||||
|
||||
return rule, status, raw
|
||||
}
|
||||
|
||||
func (a apiClient) ConvertPrometheusGetNamespaceRules(t *testing.T, namespaceTitle string) map[string][]apimodels.PrometheusRuleGroup {
|
||||
func (a apiClient) ConvertPrometheusGetNamespaceRules(t *testing.T, namespaceTitle string, headers map[string]string) map[string][]apimodels.PrometheusRuleGroup {
|
||||
t.Helper()
|
||||
|
||||
path := "%s/api/convert/prometheus/config/v1/rules/%s"
|
||||
@@ -1145,12 +1173,18 @@ func (a apiClient) ConvertPrometheusGetNamespaceRules(t *testing.T, namespaceTit
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf(path, a.url, namespaceTitle), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
for key, value := range headers {
|
||||
req.Header.Add(key, value)
|
||||
}
|
||||
|
||||
ns, status, raw := sendRequestYAML[map[string][]apimodels.PrometheusRuleGroup](t, req, http.StatusOK)
|
||||
requireStatusCode(t, http.StatusOK, status, raw)
|
||||
|
||||
return ns
|
||||
}
|
||||
|
||||
func (a apiClient) ConvertPrometheusGetAllRules(t *testing.T) map[string][]apimodels.PrometheusRuleGroup {
|
||||
func (a apiClient) ConvertPrometheusGetAllRules(t *testing.T, headers map[string]string) map[string][]apimodels.PrometheusRuleGroup {
|
||||
t.Helper()
|
||||
|
||||
path := "%s/api/convert/prometheus/config/v1/rules"
|
||||
@@ -1160,12 +1194,25 @@ func (a apiClient) ConvertPrometheusGetAllRules(t *testing.T) map[string][]apimo
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf(path, a.url), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
for key, value := range headers {
|
||||
req.Header.Add(key, value)
|
||||
}
|
||||
|
||||
result, status, raw := sendRequestYAML[map[string][]apimodels.PrometheusRuleGroup](t, req, http.StatusOK)
|
||||
requireStatusCode(t, http.StatusOK, status, raw)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (a apiClient) ConvertPrometheusDeleteRuleGroup(t *testing.T, namespaceTitle, groupName string) {
|
||||
func (a apiClient) ConvertPrometheusDeleteRuleGroup(t *testing.T, namespaceTitle, groupName string, headers map[string]string) {
|
||||
t.Helper()
|
||||
|
||||
_, status, raw := a.RawConvertPrometheusDeleteRuleGroup(t, namespaceTitle, groupName, headers)
|
||||
requireStatusCode(t, http.StatusAccepted, status, raw)
|
||||
}
|
||||
|
||||
func (a apiClient) RawConvertPrometheusDeleteRuleGroup(t *testing.T, namespaceTitle, groupName string, headers map[string]string) (apimodels.ConvertPrometheusResponse, int, string) {
|
||||
t.Helper()
|
||||
|
||||
path := "%s/api/convert/prometheus/config/v1/rules/%s/%s"
|
||||
@@ -1175,11 +1222,22 @@ func (a apiClient) ConvertPrometheusDeleteRuleGroup(t *testing.T, namespaceTitle
|
||||
|
||||
req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf(path, a.url, namespaceTitle, groupName), nil)
|
||||
require.NoError(t, err)
|
||||
_, status, raw := sendRequestJSON[apimodels.ConvertPrometheusResponse](t, req, http.StatusAccepted)
|
||||
|
||||
for key, value := range headers {
|
||||
req.Header.Add(key, value)
|
||||
}
|
||||
|
||||
return sendRequestJSON[apimodels.ConvertPrometheusResponse](t, req, http.StatusAccepted)
|
||||
}
|
||||
|
||||
func (a apiClient) ConvertPrometheusDeleteNamespace(t *testing.T, namespaceTitle string, headers map[string]string) {
|
||||
t.Helper()
|
||||
|
||||
_, status, raw := a.RawConvertPrometheusDeleteNamespace(t, namespaceTitle, headers)
|
||||
requireStatusCode(t, http.StatusAccepted, status, raw)
|
||||
}
|
||||
|
||||
func (a apiClient) ConvertPrometheusDeleteNamespace(t *testing.T, namespaceTitle string) {
|
||||
func (a apiClient) RawConvertPrometheusDeleteNamespace(t *testing.T, namespaceTitle string, headers map[string]string) (apimodels.ConvertPrometheusResponse, int, string) {
|
||||
t.Helper()
|
||||
|
||||
path := "%s/api/convert/prometheus/config/v1/rules/%s"
|
||||
@@ -1189,8 +1247,12 @@ func (a apiClient) ConvertPrometheusDeleteNamespace(t *testing.T, namespaceTitle
|
||||
|
||||
req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf(path, a.url, namespaceTitle), nil)
|
||||
require.NoError(t, err)
|
||||
_, status, raw := sendRequestJSON[apimodels.ConvertPrometheusResponse](t, req, http.StatusAccepted)
|
||||
requireStatusCode(t, http.StatusAccepted, status, raw)
|
||||
|
||||
for key, value := range headers {
|
||||
req.Header.Add(key, value)
|
||||
}
|
||||
|
||||
return sendRequestJSON[apimodels.ConvertPrometheusResponse](t, req, http.StatusAccepted)
|
||||
}
|
||||
|
||||
func sendRequestRaw(t *testing.T, req *http.Request) ([]byte, int, error) {
|
||||
|
||||
Reference in New Issue
Block a user