mirror of
https://github.com/grafana/grafana.git
synced 2025-07-31 07:42:31 +08:00

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
1333 lines
39 KiB
Go
1333 lines
39 KiB
Go
package alerting
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
|
"github.com/prometheus/common/model"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gopkg.in/yaml.v3"
|
|
|
|
"github.com/grafana/grafana/pkg/api"
|
|
"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/accesscontrol"
|
|
"github.com/grafana/grafana/pkg/services/folder"
|
|
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
|
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
"github.com/grafana/grafana/pkg/services/org/orgimpl"
|
|
"github.com/grafana/grafana/pkg/services/quota"
|
|
"github.com/grafana/grafana/pkg/services/quota/quotaimpl"
|
|
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
|
|
"github.com/grafana/grafana/pkg/services/user"
|
|
"github.com/grafana/grafana/pkg/services/user/userimpl"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
"github.com/grafana/grafana/pkg/util"
|
|
)
|
|
|
|
const defaultAlertmanagerConfigJSON = `
|
|
{
|
|
"template_files": null,
|
|
"alertmanager_config": {
|
|
"route": {
|
|
"receiver": "grafana-default-email",
|
|
"group_by": ["grafana_folder", "alertname"]
|
|
},
|
|
"receivers": [{
|
|
"name": "grafana-default-email",
|
|
"grafana_managed_receiver_configs": [{
|
|
"uid": "",
|
|
"name": "email receiver",
|
|
"type": "email",
|
|
"disableResolveMessage": false,
|
|
"settings": {
|
|
"addresses": "\u003cexample@email.com\u003e"
|
|
},
|
|
"secureFields": {}
|
|
}]
|
|
}]
|
|
}
|
|
}
|
|
`
|
|
|
|
type Response struct {
|
|
Message string `json:"message"`
|
|
TraceID string `json:"traceID"`
|
|
}
|
|
|
|
func getRequest(t *testing.T, url string, expStatusCode int) *http.Response {
|
|
t.Helper()
|
|
// nolint:gosec
|
|
resp, err := http.Get(url)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() {
|
|
require.NoError(t, resp.Body.Close())
|
|
})
|
|
if expStatusCode != resp.StatusCode {
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
t.Fatal(string(b))
|
|
}
|
|
return resp
|
|
}
|
|
|
|
func postRequest(t *testing.T, url string, body string, expStatusCode int) *http.Response {
|
|
t.Helper()
|
|
buf := bytes.NewReader([]byte(body))
|
|
// nolint:gosec
|
|
resp, err := http.Post(url, "application/json", buf)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() {
|
|
require.NoError(t, resp.Body.Close())
|
|
})
|
|
if expStatusCode != resp.StatusCode {
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
t.Log(string(b))
|
|
require.Equal(t, expStatusCode, resp.StatusCode)
|
|
}
|
|
return resp
|
|
}
|
|
|
|
func getBody(t *testing.T, body io.ReadCloser) string {
|
|
t.Helper()
|
|
b, err := io.ReadAll(body)
|
|
require.NoError(t, err)
|
|
return string(b)
|
|
}
|
|
|
|
type ruleMutator func(r *apimodels.PostableExtendedRuleNode)
|
|
|
|
func alertRuleGen(mutators ...ruleMutator) func() apimodels.PostableExtendedRuleNode {
|
|
return func() apimodels.PostableExtendedRuleNode {
|
|
forDuration := model.Duration(10 * time.Second)
|
|
rule := apimodels.PostableExtendedRuleNode{
|
|
ApiRuleNode: &apimodels.ApiRuleNode{
|
|
For: &forDuration,
|
|
Labels: map[string]string{"label1": "val1"},
|
|
Annotations: map[string]string{"annotation1": "val1"},
|
|
},
|
|
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
|
Title: fmt.Sprintf("rule-%s", util.GenerateShortUID()),
|
|
Condition: "A",
|
|
Data: []apimodels.AlertQuery{
|
|
{
|
|
RefID: "A",
|
|
RelativeTimeRange: apimodels.RelativeTimeRange{
|
|
From: apimodels.Duration(time.Duration(5) * time.Hour),
|
|
To: apimodels.Duration(time.Duration(3) * time.Hour),
|
|
},
|
|
DatasourceUID: expr.DatasourceUID,
|
|
Model: json.RawMessage(`{
|
|
"type": "math",
|
|
"expression": "2 + 3 > 1"
|
|
}`),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, mutator := range mutators {
|
|
mutator(&rule)
|
|
}
|
|
return rule
|
|
}
|
|
}
|
|
|
|
func withDatasourceQuery(uid string) func(r *apimodels.PostableExtendedRuleNode) {
|
|
data := []apimodels.AlertQuery{
|
|
{
|
|
RefID: "A",
|
|
RelativeTimeRange: apimodels.RelativeTimeRange{
|
|
From: apimodels.Duration(600 * time.Second),
|
|
To: 0,
|
|
},
|
|
DatasourceUID: uid,
|
|
Model: json.RawMessage(fmt.Sprintf(`{
|
|
"refId": "A",
|
|
"hide": false,
|
|
"datasource": {
|
|
"type": "testdata",
|
|
"uid": "%s"
|
|
},
|
|
"scenarioId": "random_walk",
|
|
"seriesCount": 5,
|
|
"labels": "series=series-$seriesIndex"
|
|
}`, uid)),
|
|
},
|
|
{
|
|
RefID: "B",
|
|
DatasourceUID: expr.DatasourceType,
|
|
Model: json.RawMessage(`{
|
|
"type": "reduce",
|
|
"reducer": "last",
|
|
"expression": "A"
|
|
}`),
|
|
},
|
|
{
|
|
RefID: "C",
|
|
DatasourceUID: expr.DatasourceType,
|
|
Model: json.RawMessage(`{
|
|
"refId": "C",
|
|
"type": "threshold",
|
|
"conditions": [
|
|
{
|
|
"type": "query",
|
|
"evaluator": {
|
|
"params": [
|
|
0
|
|
],
|
|
"type": "gt"
|
|
}
|
|
}
|
|
],
|
|
"expression": "B"
|
|
}`),
|
|
},
|
|
}
|
|
|
|
return func(r *apimodels.PostableExtendedRuleNode) {
|
|
r.GrafanaManagedAlert.Data = data
|
|
r.GrafanaManagedAlert.Condition = "C"
|
|
}
|
|
}
|
|
|
|
func generateAlertRuleGroup(rulesCount int, gen func() apimodels.PostableExtendedRuleNode) apimodels.PostableRuleGroupConfig {
|
|
rules := make([]apimodels.PostableExtendedRuleNode, 0, rulesCount)
|
|
for i := 0; i < rulesCount; i++ {
|
|
rules = append(rules, gen())
|
|
}
|
|
return apimodels.PostableRuleGroupConfig{
|
|
Name: "arulegroup-" + uuid.NewString(),
|
|
Interval: model.Duration(10 * time.Second),
|
|
Rules: rules,
|
|
}
|
|
}
|
|
|
|
func convertGettableRuleGroupToPostable(gettable apimodels.GettableRuleGroupConfig) apimodels.PostableRuleGroupConfig {
|
|
rules := make([]apimodels.PostableExtendedRuleNode, 0, len(gettable.Rules))
|
|
for _, rule := range gettable.Rules {
|
|
rules = append(rules, convertGettableRuleToPostable(rule))
|
|
}
|
|
return apimodels.PostableRuleGroupConfig{
|
|
Name: gettable.Name,
|
|
Interval: gettable.Interval,
|
|
Rules: rules,
|
|
}
|
|
}
|
|
|
|
func convertGettableRuleToPostable(gettable apimodels.GettableExtendedRuleNode) apimodels.PostableExtendedRuleNode {
|
|
return apimodels.PostableExtendedRuleNode{
|
|
ApiRuleNode: gettable.ApiRuleNode,
|
|
GrafanaManagedAlert: convertGettableGrafanaRuleToPostable(gettable.GrafanaManagedAlert),
|
|
}
|
|
}
|
|
|
|
func convertGettableGrafanaRuleToPostable(gettable *apimodels.GettableGrafanaRule) *apimodels.PostableGrafanaRule {
|
|
if gettable == nil {
|
|
return nil
|
|
}
|
|
return &apimodels.PostableGrafanaRule{
|
|
Title: gettable.Title,
|
|
Condition: gettable.Condition,
|
|
Data: gettable.Data,
|
|
UID: gettable.UID,
|
|
NoDataState: gettable.NoDataState,
|
|
ExecErrState: gettable.ExecErrState,
|
|
IsPaused: &gettable.IsPaused,
|
|
NotificationSettings: gettable.NotificationSettings,
|
|
Metadata: gettable.Metadata,
|
|
}
|
|
}
|
|
|
|
type apiClient struct {
|
|
url string
|
|
prometheusConversionUseLokiPaths bool
|
|
}
|
|
|
|
type LegacyApiClient struct {
|
|
apiClient
|
|
}
|
|
|
|
func NewAlertingLegacyAPIClient(host, user, pass string) LegacyApiClient {
|
|
cli := newAlertingApiClient(host, user, pass)
|
|
return LegacyApiClient{
|
|
apiClient: cli,
|
|
}
|
|
}
|
|
|
|
func newAlertingApiClient(host, user, pass string) apiClient {
|
|
if len(user) == 0 && len(pass) == 0 {
|
|
return apiClient{url: fmt.Sprintf("http://%s", host)}
|
|
}
|
|
return apiClient{url: fmt.Sprintf("http://%s:%s@%s", user, pass, host)}
|
|
}
|
|
|
|
// ReloadCachedPermissions sends a request to access control API to refresh cached user permissions
|
|
func (a apiClient) ReloadCachedPermissions(t *testing.T) {
|
|
t.Helper()
|
|
|
|
u := fmt.Sprintf("%s/api/access-control/user/permissions?reloadcache=true", a.url)
|
|
// nolint:gosec
|
|
resp, err := http.Get(u)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
require.NoErrorf(t, err, "failed to reload permissions cache")
|
|
require.Equalf(t, http.StatusOK, resp.StatusCode, "failed to reload permissions cache")
|
|
}
|
|
|
|
// AssignReceiverPermission sends a request to access control API to assign permissions to a user, role, or team on a receiver.
|
|
func (a apiClient) AssignReceiverPermission(t *testing.T, receiverUID string, cmd accesscontrol.SetResourcePermissionCommand) (int, string) {
|
|
t.Helper()
|
|
|
|
var assignment string
|
|
var assignTo string
|
|
if cmd.UserID != 0 {
|
|
assignment = "users"
|
|
assignTo = fmt.Sprintf("%d", cmd.UserID)
|
|
} else if cmd.TeamID != 0 {
|
|
assignment = "teams"
|
|
assignTo = fmt.Sprintf("%d", cmd.TeamID)
|
|
} else {
|
|
assignment = "builtInRoles"
|
|
assignTo = cmd.BuiltinRole
|
|
}
|
|
|
|
body := strings.NewReader(fmt.Sprintf(`{"permission": "%s"}`, cmd.Permission))
|
|
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/access-control/receivers/%s/%s/%s", a.url, receiverUID, assignment, assignTo), body)
|
|
require.NoError(t, err)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
return resp.StatusCode, string(b)
|
|
}
|
|
|
|
// CreateFolder creates a folder for storing our alerts, and then refreshes the permission cache to make sure that following requests will be accepted
|
|
func (a apiClient) CreateFolder(t *testing.T, uID string, title string, parentUID ...string) {
|
|
t.Helper()
|
|
cmd := folder.CreateFolderCommand{
|
|
UID: uID,
|
|
Title: title,
|
|
}
|
|
if len(parentUID) > 0 {
|
|
cmd.ParentUID = parentUID[0]
|
|
}
|
|
|
|
blob, err := json.Marshal(cmd)
|
|
require.NoError(t, err)
|
|
|
|
payload := string(blob)
|
|
u := fmt.Sprintf("%s/api/folders", a.url)
|
|
r := strings.NewReader(payload)
|
|
// nolint:gosec
|
|
resp, err := http.Post(u, "application/json", r)
|
|
defer func() {
|
|
require.NoError(t, resp.Body.Close())
|
|
}()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
a.ReloadCachedPermissions(t)
|
|
}
|
|
|
|
func (a apiClient) ReloadAlertingFileProvisioning(t *testing.T) {
|
|
t.Helper()
|
|
|
|
u := fmt.Sprintf("%s/api/admin/provisioning/alerting/reload", a.url)
|
|
// nolint:gosec
|
|
resp, err := http.Post(u, "application/json", nil)
|
|
defer func() {
|
|
require.NoError(t, resp.Body.Close())
|
|
}()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
}
|
|
|
|
func (a apiClient) GetOrgQuotaLimits(t *testing.T, orgID int64) (int64, int64) {
|
|
t.Helper()
|
|
|
|
u := fmt.Sprintf("%s/api/orgs/%d/quotas", a.url, orgID)
|
|
// nolint:gosec
|
|
resp, err := http.Get(u)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
|
|
|
results := []quota.QuotaDTO{}
|
|
require.NoError(t, json.Unmarshal(b, &results))
|
|
|
|
var limit int64 = 0
|
|
var used int64 = 0
|
|
for _, q := range results {
|
|
if q.Target != string(ngmodels.QuotaTargetSrv) {
|
|
continue
|
|
}
|
|
limit = q.Limit
|
|
used = q.Used
|
|
}
|
|
return limit, used
|
|
}
|
|
|
|
func (a apiClient) UpdateAlertRuleOrgQuota(t *testing.T, orgID int64, limit int64) {
|
|
t.Helper()
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode("a.UpdateQuotaCmd{
|
|
Target: "alert_rule",
|
|
Limit: limit,
|
|
OrgID: orgID,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
u := fmt.Sprintf("%s/api/orgs/%d/quotas/alert_rule", a.url, orgID)
|
|
// nolint:gosec
|
|
client := &http.Client{}
|
|
req, err := http.NewRequest(http.MethodPut, u, &buf)
|
|
require.NoError(t, err)
|
|
req.Header.Add("Content-Type", "application/json")
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
}
|
|
|
|
func (a apiClient) PostConfiguration(t *testing.T, c apimodels.PostableUserConfig) (bool, error) {
|
|
t.Helper()
|
|
|
|
b, err := json.Marshal(c)
|
|
require.NoError(t, err)
|
|
|
|
u := fmt.Sprintf("%s/api/alertmanager/grafana/config/api/v1/alerts", a.url)
|
|
req, err := http.NewRequest(http.MethodPost, u, bytes.NewReader(b))
|
|
require.NoError(t, err)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err = io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
data := struct {
|
|
Message string `json:"message"`
|
|
}{}
|
|
require.NoError(t, json.Unmarshal(b, &data))
|
|
|
|
if resp.StatusCode == http.StatusAccepted {
|
|
return true, nil
|
|
}
|
|
|
|
return false, errors.New(data.Message)
|
|
}
|
|
|
|
func (a apiClient) PostRulesGroupWithStatus(t *testing.T, folder string, group *apimodels.PostableRuleGroupConfig) (apimodels.UpdateRuleGroupResponse, int, string) {
|
|
t.Helper()
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(group)
|
|
require.NoError(t, err)
|
|
|
|
u := fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules/%s", a.url, folder)
|
|
// nolint:gosec
|
|
resp, err := http.Post(u, "application/json", &buf)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
var m apimodels.UpdateRuleGroupResponse
|
|
if resp.StatusCode == http.StatusAccepted {
|
|
require.NoError(t, json.Unmarshal(b, &m))
|
|
}
|
|
return m, resp.StatusCode, string(b)
|
|
}
|
|
|
|
func (a apiClient) PostRulesGroup(t *testing.T, folder string, group *apimodels.PostableRuleGroupConfig) apimodels.UpdateRuleGroupResponse {
|
|
t.Helper()
|
|
m, status, raw := a.PostRulesGroupWithStatus(t, folder, group)
|
|
requireStatusCode(t, http.StatusAccepted, status, raw)
|
|
return m
|
|
}
|
|
|
|
func (a apiClient) PostRulesExportWithStatus(t *testing.T, folder string, group *apimodels.PostableRuleGroupConfig, params *apimodels.ExportQueryParams) (int, string) {
|
|
t.Helper()
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(group)
|
|
require.NoError(t, err)
|
|
|
|
u, err := url.Parse(fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules/%s/export", a.url, folder))
|
|
require.NoError(t, err)
|
|
|
|
if params != nil {
|
|
q := url.Values{}
|
|
if params.Format != "" {
|
|
q.Set("format", params.Format)
|
|
}
|
|
if params.Download {
|
|
q.Set("download", "true")
|
|
}
|
|
u.RawQuery = q.Encode()
|
|
}
|
|
|
|
req, err := http.NewRequest(http.MethodPost, u.String(), &buf)
|
|
req.Header.Add("Content-Type", "application/json")
|
|
require.NoError(t, err)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
return resp.StatusCode, string(b)
|
|
}
|
|
|
|
func (a apiClient) DeleteRulesGroup(t *testing.T, folder string, group string) (int, string) {
|
|
t.Helper()
|
|
|
|
u := fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules/%s/%s", a.url, folder, group)
|
|
req, err := http.NewRequest(http.MethodDelete, u, nil)
|
|
require.NoError(t, err)
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
return resp.StatusCode, string(b)
|
|
}
|
|
|
|
func (a apiClient) PostSilence(t *testing.T, s apimodels.PostableSilence) (apimodels.PostSilencesOKBody, int, string) {
|
|
t.Helper()
|
|
|
|
b, err := json.Marshal(s)
|
|
require.NoError(t, err)
|
|
|
|
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/alertmanager/grafana/api/v2/silences", a.url), bytes.NewReader(b))
|
|
require.NoError(t, err)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
return sendRequestJSON[apimodels.PostSilencesOKBody](t, req, http.StatusAccepted)
|
|
}
|
|
|
|
func (a apiClient) GetSilence(t *testing.T, id string) (apimodels.GettableSilence, int, string) {
|
|
t.Helper()
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/alertmanager/grafana/api/v2/silence/%s", a.url, id), nil)
|
|
require.NoError(t, err)
|
|
return sendRequestJSON[apimodels.GettableSilence](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) GetSilences(t *testing.T, filters ...string) (apimodels.GettableSilences, int, string) {
|
|
t.Helper()
|
|
|
|
u, err := url.Parse(fmt.Sprintf("%s/api/alertmanager/grafana/api/v2/silences", a.url))
|
|
require.NoError(t, err)
|
|
if len(filters) > 0 {
|
|
u.RawQuery = url.Values{"filter": filters}.Encode()
|
|
}
|
|
|
|
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequestJSON[apimodels.GettableSilences](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) DeleteSilence(t *testing.T, id string) (any, int, string) {
|
|
t.Helper()
|
|
req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/api/alertmanager/grafana/api/v2/silence/%s", a.url, id), nil)
|
|
require.NoError(t, err)
|
|
|
|
type dynamic struct {
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
return sendRequestJSON[dynamic](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) GetRulesGroup(t *testing.T, folder string, group string) (apimodels.RuleGroupConfigResponse, int) {
|
|
result, status, _ := a.GetRulesGroupWithStatus(t, folder, group)
|
|
return result, status
|
|
}
|
|
|
|
func (a apiClient) GetRulesGroupWithStatus(t *testing.T, folder string, group string) (apimodels.RuleGroupConfigResponse, int, []byte) {
|
|
t.Helper()
|
|
u := fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules/%s/%s", a.url, folder, group)
|
|
// nolint:gosec
|
|
resp, err := http.Get(u)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
result := apimodels.RuleGroupConfigResponse{}
|
|
|
|
if http.StatusAccepted == resp.StatusCode {
|
|
require.NoError(t, json.Unmarshal(b, &result))
|
|
}
|
|
return result, resp.StatusCode, b
|
|
}
|
|
|
|
func (a apiClient) GetAllRulesGroupInFolderWithStatus(t *testing.T, folder string) (apimodels.NamespaceConfigResponse, int, []byte) {
|
|
t.Helper()
|
|
u := fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules/%s", a.url, folder)
|
|
// nolint:gosec
|
|
resp, err := http.Get(u)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
result := apimodels.NamespaceConfigResponse{}
|
|
if http.StatusAccepted == resp.StatusCode {
|
|
require.NoError(t, json.Unmarshal(b, &result))
|
|
}
|
|
return result, resp.StatusCode, b
|
|
}
|
|
|
|
func (a apiClient) GetAllRulesWithStatus(t *testing.T) (apimodels.NamespaceConfigResponse, int, []byte) {
|
|
t.Helper()
|
|
u := fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules", a.url)
|
|
// nolint:gosec
|
|
resp, err := http.Get(u)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
result := apimodels.NamespaceConfigResponse{}
|
|
if http.StatusOK == resp.StatusCode {
|
|
require.NoError(t, json.Unmarshal(b, &result))
|
|
}
|
|
return result, resp.StatusCode, b
|
|
}
|
|
|
|
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))
|
|
require.NoError(t, err)
|
|
if params != nil {
|
|
q := url.Values{}
|
|
if params.Format != "" {
|
|
q.Set("format", params.Format)
|
|
}
|
|
if params.Download {
|
|
q.Set("download", "true")
|
|
}
|
|
if len(params.FolderUID) > 0 {
|
|
for _, s := range params.FolderUID {
|
|
q.Add("folderUid", s)
|
|
}
|
|
}
|
|
if params.GroupName != "" {
|
|
q.Set("group", params.GroupName)
|
|
}
|
|
if params.RuleUID != "" {
|
|
q.Set("ruleUid", params.RuleUID)
|
|
}
|
|
u.RawQuery = q.Encode()
|
|
}
|
|
|
|
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
|
require.NoError(t, err)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
return resp.StatusCode, string(b)
|
|
}
|
|
|
|
func (a apiClient) GetRuleGroupProvisioning(t *testing.T, folderUID string, groupName string) (apimodels.AlertRuleGroup, int, string) {
|
|
t.Helper()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/provisioning/folder/%s/rule-groups/%s", a.url, folderUID, groupName), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequestJSON[apimodels.AlertRuleGroup](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) CreateOrUpdateRuleGroupProvisioning(t *testing.T, group apimodels.AlertRuleGroup) (apimodels.AlertRuleGroup, int, string) {
|
|
t.Helper()
|
|
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(group)
|
|
require.NoError(t, err)
|
|
|
|
req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("%s/api/v1/provisioning/folder/%s/rule-groups/%s", a.url, group.FolderUID, group.Title), &buf)
|
|
require.NoError(t, err)
|
|
req.Header.Add("Content-Type", "application/json")
|
|
|
|
return sendRequestJSON[apimodels.AlertRuleGroup](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) SubmitRuleForBacktesting(t *testing.T, config apimodels.BacktestConfig) (int, string) {
|
|
t.Helper()
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(config)
|
|
require.NoError(t, err)
|
|
|
|
u := fmt.Sprintf("%s/api/v1/rule/backtest", a.url)
|
|
// nolint:gosec
|
|
resp, err := http.Post(u, "application/json", &buf)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
return resp.StatusCode, string(b)
|
|
}
|
|
|
|
func (a apiClient) SubmitRuleForTesting(t *testing.T, config apimodels.PostableExtendedRuleNodeExtended) (int, string) {
|
|
t.Helper()
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(config)
|
|
require.NoError(t, err)
|
|
|
|
u := fmt.Sprintf("%s/api/v1/rule/test/grafana", a.url)
|
|
// nolint:gosec
|
|
resp, err := http.Post(u, "application/json", &buf)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
return resp.StatusCode, string(b)
|
|
}
|
|
|
|
func (a apiClient) CreateTestDatasource(t *testing.T) (result api.CreateOrUpdateDatasourceResponse) {
|
|
t.Helper()
|
|
|
|
return a.CreateDatasource(t, "testdata")
|
|
}
|
|
|
|
func (a apiClient) CreateDatasource(t *testing.T, dsType string) (result api.CreateOrUpdateDatasourceResponse) {
|
|
t.Helper()
|
|
|
|
payload := fmt.Sprintf(`{"name":"TestDatasource-%s","type":"%s","access":"proxy","isDefault":false}`, uuid.NewString(), dsType)
|
|
buf := bytes.Buffer{}
|
|
buf.Write([]byte(payload))
|
|
|
|
u := fmt.Sprintf("%s/api/datasources", a.url)
|
|
|
|
// nolint:gosec
|
|
resp, err := http.Post(u, "application/json", &buf)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
if resp.StatusCode != 200 {
|
|
require.Failf(t, "failed to create data source", "API request to create a datasource failed. Status code: %d, response: %s", resp.StatusCode, string(b))
|
|
}
|
|
require.NoError(t, json.Unmarshal([]byte(fmt.Sprintf(`{ "body": %s }`, string(b))), &result))
|
|
return result
|
|
}
|
|
|
|
func (a apiClient) DeleteDatasource(t *testing.T, uid string) {
|
|
t.Helper()
|
|
|
|
u := fmt.Sprintf("%s/api/datasources/uid/%s", a.url, uid)
|
|
|
|
req, err := http.NewRequest(http.MethodDelete, u, nil)
|
|
require.NoError(t, err)
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
if resp.StatusCode != 200 {
|
|
require.Failf(t, "failed to create data source", "API request to create a datasource failed. Status code: %d, response: %s", resp.StatusCode, string(b))
|
|
}
|
|
}
|
|
|
|
func (a apiClient) GetAllMuteTimingsWithStatus(t *testing.T) (apimodels.MuteTimings, int, string) {
|
|
t.Helper()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/provisioning/mute-timings", a.url), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequestJSON[apimodels.MuteTimings](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) GetMuteTimingByNameWithStatus(t *testing.T, name string) (apimodels.MuteTimeInterval, int, string) {
|
|
t.Helper()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/provisioning/mute-timings/%s", a.url, name), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequestJSON[apimodels.MuteTimeInterval](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) CreateMuteTimingWithStatus(t *testing.T, interval apimodels.MuteTimeInterval) (apimodels.MuteTimeInterval, int, string) {
|
|
t.Helper()
|
|
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(interval)
|
|
require.NoError(t, err)
|
|
|
|
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/v1/provisioning/mute-timings", a.url), &buf)
|
|
req.Header.Add("Content-Type", "application/json")
|
|
require.NoError(t, err)
|
|
|
|
return sendRequestJSON[apimodels.MuteTimeInterval](t, req, http.StatusCreated)
|
|
}
|
|
|
|
func (a apiClient) EnsureMuteTiming(t *testing.T, interval apimodels.MuteTimeInterval) {
|
|
t.Helper()
|
|
|
|
_, status, body := a.CreateMuteTimingWithStatus(t, interval)
|
|
require.Equalf(t, http.StatusCreated, status, body)
|
|
}
|
|
|
|
func (a apiClient) UpdateMuteTimingWithStatus(t *testing.T, interval apimodels.MuteTimeInterval) (apimodels.MuteTimeInterval, int, string) {
|
|
t.Helper()
|
|
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(interval)
|
|
require.NoError(t, err)
|
|
|
|
req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("%s/api/v1/provisioning/mute-timings/%s", a.url, interval.Name), &buf)
|
|
req.Header.Add("Content-Type", "application/json")
|
|
require.NoError(t, err)
|
|
|
|
return sendRequestJSON[apimodels.MuteTimeInterval](t, req, http.StatusAccepted)
|
|
}
|
|
|
|
func (a apiClient) DeleteMuteTimingWithStatus(t *testing.T, name string) (int, string) {
|
|
t.Helper()
|
|
|
|
req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/api/v1/provisioning/mute-timings/%s", a.url, name), nil)
|
|
req.Header.Add("Content-Type", "application/json")
|
|
require.NoError(t, err)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
body, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
return resp.StatusCode, string(body)
|
|
}
|
|
|
|
func (a apiClient) ExportMuteTiming(t *testing.T, name string, format string) string {
|
|
t.Helper()
|
|
|
|
u, err := url.Parse(fmt.Sprintf("%s/api/v1/provisioning/mute-timings/%s/export", a.url, name))
|
|
require.NoError(t, err)
|
|
q := url.Values{}
|
|
q.Set("format", format)
|
|
u.RawQuery = q.Encode()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
|
require.NoError(t, err)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
body, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
requireStatusCode(t, http.StatusOK, resp.StatusCode, string(body))
|
|
return string(body)
|
|
}
|
|
|
|
func (a apiClient) GetRouteWithStatus(t *testing.T) (apimodels.Route, int, string) {
|
|
t.Helper()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/provisioning/policies", a.url), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequestJSON[apimodels.Route](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) GetRoute(t *testing.T) apimodels.Route {
|
|
t.Helper()
|
|
|
|
route, status, data := a.GetRouteWithStatus(t)
|
|
requireStatusCode(t, http.StatusOK, status, data)
|
|
return route
|
|
}
|
|
|
|
func (a apiClient) UpdateRouteWithStatus(t *testing.T, route apimodels.Route, noProvenance bool) (int, string) {
|
|
t.Helper()
|
|
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(route)
|
|
require.NoError(t, err)
|
|
|
|
req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("%s/api/v1/provisioning/policies", a.url), &buf)
|
|
req.Header.Add("Content-Type", "application/json")
|
|
if noProvenance {
|
|
req.Header.Add("X-Disable-Provenance", "true")
|
|
}
|
|
require.NoError(t, err)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
body, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
return resp.StatusCode, string(body)
|
|
}
|
|
|
|
func (a apiClient) ExportNotificationPolicy(t *testing.T, format string) string {
|
|
t.Helper()
|
|
|
|
u, err := url.Parse(fmt.Sprintf("%s/api/v1/provisioning/policies/export", a.url))
|
|
require.NoError(t, err)
|
|
q := url.Values{}
|
|
q.Set("format", format)
|
|
u.RawQuery = q.Encode()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
|
require.NoError(t, err)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
body, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
requireStatusCode(t, http.StatusOK, resp.StatusCode, string(body))
|
|
return string(body)
|
|
}
|
|
|
|
func (a apiClient) UpdateRoute(t *testing.T, route apimodels.Route, noProvenance bool) {
|
|
t.Helper()
|
|
status, data := a.UpdateRouteWithStatus(t, route, noProvenance)
|
|
requireStatusCode(t, http.StatusAccepted, status, data)
|
|
}
|
|
|
|
func (a apiClient) GetRuleHistoryWithStatus(t *testing.T, ruleUID string) (data.Frame, int, string) {
|
|
t.Helper()
|
|
u, err := url.Parse(fmt.Sprintf("%s/api/v1/rules/history", a.url))
|
|
require.NoError(t, err)
|
|
q := url.Values{}
|
|
q.Set("ruleUID", ruleUID)
|
|
u.RawQuery = q.Encode()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequestJSON[data.Frame](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) GetAllTimeIntervalsWithStatus(t *testing.T) ([]apimodels.GettableTimeIntervals, int, string) {
|
|
t.Helper()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/notifications/time-intervals", a.url), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequestJSON[[]apimodels.GettableTimeIntervals](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) GetTimeIntervalByNameWithStatus(t *testing.T, name string) (apimodels.GettableTimeIntervals, int, string) {
|
|
t.Helper()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/notifications/time-intervals/%s", a.url, name), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequestJSON[apimodels.GettableTimeIntervals](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) CreateReceiverWithStatus(t *testing.T, receiver apimodels.EmbeddedContactPoint) (apimodels.EmbeddedContactPoint, int, string) {
|
|
t.Helper()
|
|
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(receiver)
|
|
require.NoError(t, err)
|
|
|
|
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/v1/provisioning/contact-points", a.url), &buf)
|
|
req.Header.Add("Content-Type", "application/json")
|
|
require.NoError(t, err)
|
|
|
|
return sendRequestJSON[apimodels.EmbeddedContactPoint](t, req, http.StatusAccepted)
|
|
}
|
|
|
|
func (a apiClient) EnsureReceiver(t *testing.T, receiver apimodels.EmbeddedContactPoint) {
|
|
t.Helper()
|
|
|
|
_, status, body := a.CreateReceiverWithStatus(t, receiver)
|
|
require.Equalf(t, http.StatusAccepted, status, body)
|
|
}
|
|
|
|
func (a apiClient) ExportReceiver(t *testing.T, name string, format string, decrypt bool) string {
|
|
t.Helper()
|
|
u, err := url.Parse(fmt.Sprintf("%s/api/v1/provisioning/contact-points/export", a.url))
|
|
require.NoError(t, err)
|
|
q := url.Values{}
|
|
q.Set("name", name)
|
|
q.Set("format", format)
|
|
q.Set("decrypt", fmt.Sprintf("%v", decrypt))
|
|
u.RawQuery = q.Encode()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
|
require.NoError(t, err)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
body, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
requireStatusCode(t, http.StatusOK, resp.StatusCode, string(body))
|
|
return string(body)
|
|
}
|
|
|
|
func (a apiClient) ExportReceiverTyped(t *testing.T, name string, decrypt bool) apimodels.ContactPointExport {
|
|
t.Helper()
|
|
|
|
response := a.ExportReceiver(t, name, "json", decrypt)
|
|
|
|
var export apimodels.AlertingFileExport
|
|
require.NoError(t, json.Unmarshal([]byte(response), &export))
|
|
require.Len(t, export.ContactPoints, 1)
|
|
return export.ContactPoints[0]
|
|
}
|
|
|
|
func (a apiClient) GetAlertmanagerConfigWithStatus(t *testing.T) (apimodels.GettableUserConfig, int, string) {
|
|
t.Helper()
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/alertmanager/grafana/config/api/v1/alerts", a.url), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequestJSON[apimodels.GettableUserConfig](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) GetActiveAlertsWithStatus(t *testing.T) (apimodels.AlertGroups, int, string) {
|
|
t.Helper()
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/alertmanager/grafana/api/v2/alerts/groups", a.url), nil)
|
|
require.NoError(t, err)
|
|
return sendRequestJSON[apimodels.AlertGroups](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) GetRuleVersionsWithStatus(t *testing.T, ruleUID string) (apimodels.GettableRuleVersions, int, string) {
|
|
t.Helper()
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/ruler/grafana/api/v1/rule/%s/versions", a.url, ruleUID), nil)
|
|
require.NoError(t, err)
|
|
return sendRequestJSON[apimodels.GettableRuleVersions](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) GetRuleByUID(t *testing.T, ruleUID string) apimodels.GettableExtendedRuleNode {
|
|
t.Helper()
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/ruler/grafana/api/v1/rule/%s", a.url, ruleUID), nil)
|
|
require.NoError(t, err)
|
|
rule, status, raw := sendRequestJSON[apimodels.GettableExtendedRuleNode](t, req, http.StatusOK)
|
|
requireStatusCode(t, http.StatusOK, status, raw)
|
|
return rule
|
|
}
|
|
|
|
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(path, a.url, namespaceTitle), buf)
|
|
require.NoError(t, err)
|
|
req.Header.Add("X-Grafana-Alerting-Datasource-UID", datasourceUID)
|
|
|
|
for key, value := range headers {
|
|
req.Header.Add(key, value)
|
|
}
|
|
|
|
return sendRequestJSON[apimodels.ConvertPrometheusResponse](t, req, http.StatusAccepted)
|
|
}
|
|
|
|
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"
|
|
if a.prometheusConversionUseLokiPaths {
|
|
path = "%s/api/convert/api/prom/rules/%s/%s"
|
|
}
|
|
|
|
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)
|
|
|
|
return rule, status, raw
|
|
}
|
|
|
|
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"
|
|
if a.prometheusConversionUseLokiPaths {
|
|
path = "%s/api/convert/api/prom/rules/%s"
|
|
}
|
|
|
|
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, headers map[string]string) map[string][]apimodels.PrometheusRuleGroup {
|
|
t.Helper()
|
|
|
|
path := "%s/api/convert/prometheus/config/v1/rules"
|
|
if a.prometheusConversionUseLokiPaths {
|
|
path = "%s/api/convert/api/prom/rules"
|
|
}
|
|
|
|
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, 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"
|
|
if a.prometheusConversionUseLokiPaths {
|
|
path = "%s/api/convert/api/prom/rules/%s/%s"
|
|
}
|
|
|
|
req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf(path, a.url, namespaceTitle, groupName), nil)
|
|
require.NoError(t, err)
|
|
|
|
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) 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"
|
|
if a.prometheusConversionUseLokiPaths {
|
|
path = "%s/api/convert/api/prom/rules/%s"
|
|
}
|
|
|
|
req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf(path, a.url, namespaceTitle), nil)
|
|
require.NoError(t, err)
|
|
|
|
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) {
|
|
t.Helper()
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
return body, resp.StatusCode, nil
|
|
}
|
|
|
|
func sendRequestJSON[T any](t *testing.T, req *http.Request, successStatusCode int) (T, int, string) {
|
|
t.Helper()
|
|
var result T
|
|
|
|
body, statusCode, err := sendRequestRaw(t, req)
|
|
require.NoError(t, err)
|
|
|
|
if statusCode != successStatusCode {
|
|
return result, statusCode, string(body)
|
|
}
|
|
|
|
err = json.Unmarshal(body, &result)
|
|
require.NoError(t, err)
|
|
return result, statusCode, string(body)
|
|
}
|
|
|
|
func sendRequestYAML[T any](t *testing.T, req *http.Request, successStatusCode int) (T, int, string) {
|
|
t.Helper()
|
|
var result T
|
|
|
|
body, statusCode, err := sendRequestRaw(t, req)
|
|
require.NoError(t, err)
|
|
|
|
if statusCode != successStatusCode {
|
|
return result, statusCode, string(body)
|
|
}
|
|
|
|
err = yaml.Unmarshal(body, &result)
|
|
require.NoError(t, err)
|
|
return result, statusCode, string(body)
|
|
}
|
|
|
|
func requireStatusCode(t *testing.T, expected, actual int, response string) {
|
|
t.Helper()
|
|
require.Equalf(t, expected, actual, "Unexpected status. Response: %s", response)
|
|
}
|
|
|
|
func createUser(t *testing.T, db db.DB, cfg *setting.Cfg, cmd user.CreateUserCommand) int64 {
|
|
t.Helper()
|
|
|
|
cfg.AutoAssignOrg = true
|
|
cfg.AutoAssignOrgId = 1
|
|
|
|
quotaService := quotaimpl.ProvideService(db, cfg)
|
|
orgService, err := orgimpl.ProvideService(db, cfg, quotaService)
|
|
require.NoError(t, err)
|
|
usrSvc, err := userimpl.ProvideService(
|
|
db, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(),
|
|
quotaService, supportbundlestest.NewFakeBundleService(),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
u, err := usrSvc.Create(context.Background(), &cmd)
|
|
require.NoError(t, err)
|
|
return u.ID
|
|
}
|