Alerting: Add support for retrieving imported Prometheus Alertmanager configurations (#106864)
This commit is contained in:
@@ -189,6 +189,7 @@ func (api *API) RegisterAPIEndpoints(m *metrics.API) {
|
||||
api.DatasourceCache,
|
||||
api.AlertRules,
|
||||
api.FeatureManager,
|
||||
api.MultiOrgAlertmanager,
|
||||
),
|
||||
), m)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -10,6 +11,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
amconfig "github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/pkg/labels"
|
||||
prommodel "github.com/prometheus/common/model"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
@@ -47,6 +50,13 @@ const (
|
||||
// notificationSettingsHeader is the header that specifies the notification settings to be used for the rules.
|
||||
// The value should be a JSON-encoded AlertRuleNotificationSettings object.
|
||||
notificationSettingsHeader = "X-Grafana-Alerting-Notification-Settings"
|
||||
|
||||
// mergeMatchersHeader is the header that specifies the merge matchers for imported Alertmanager config.
|
||||
// The value should be comma-separated key=value pairs, e.g., "environment=production,team=alerting".
|
||||
mergeMatchersHeader = "X-Grafana-Alerting-Merge-Matchers"
|
||||
|
||||
// configIdentifierHeader is the header that specifies the identifier for imported Alertmanager config.
|
||||
configIdentifierHeader = "X-Grafana-Alerting-Config-Identifier"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -113,6 +123,11 @@ type ConvertPrometheusSrv struct {
|
||||
datasourceCache datasources.CacheService
|
||||
alertRuleService *provisioning.AlertRuleService
|
||||
featureToggles featuremgmt.FeatureToggles
|
||||
am Alertmanager
|
||||
}
|
||||
|
||||
type Alertmanager interface {
|
||||
GetAlertmanagerConfiguration(ctx context.Context, org int64, withAutogen bool) (apimodels.GettableUserConfig, error)
|
||||
}
|
||||
|
||||
func NewConvertPrometheusSrv(
|
||||
@@ -122,6 +137,7 @@ func NewConvertPrometheusSrv(
|
||||
datasourceCache datasources.CacheService,
|
||||
alertRuleService *provisioning.AlertRuleService,
|
||||
featureToggles featuremgmt.FeatureToggles,
|
||||
am Alertmanager,
|
||||
) *ConvertPrometheusSrv {
|
||||
return &ConvertPrometheusSrv{
|
||||
cfg: cfg,
|
||||
@@ -130,6 +146,7 @@ func NewConvertPrometheusSrv(
|
||||
datasourceCache: datasourceCache,
|
||||
alertRuleService: alertRuleService,
|
||||
featureToggles: featureToggles,
|
||||
am: am,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -518,7 +535,47 @@ func (srv *ConvertPrometheusSrv) RouteConvertPrometheusPostAlertmanagerConfig(c
|
||||
}
|
||||
|
||||
func (srv *ConvertPrometheusSrv) RouteConvertPrometheusGetAlertmanagerConfig(c *contextmodel.ReqContext) response.Response {
|
||||
return response.Error(http.StatusNotImplemented, "Not Implemented", nil)
|
||||
if !srv.featureToggles.IsEnabledGlobally(featuremgmt.FlagAlertingImportAlertmanagerAPI) {
|
||||
return response.Error(http.StatusNotImplemented, "Not Implemented", nil)
|
||||
}
|
||||
|
||||
logger := srv.logger.FromContext(c.Req.Context())
|
||||
ctx := c.Req.Context()
|
||||
|
||||
identifier, err := parseConfigIdentifierHeader(c)
|
||||
if err != nil {
|
||||
logger.Error("failed to parse config identifier header", "err", err)
|
||||
return errorToResponse(err)
|
||||
}
|
||||
|
||||
cfg, err := srv.am.GetAlertmanagerConfiguration(ctx, c.GetOrgID(), false)
|
||||
if err != nil {
|
||||
logger.Error("failed to get alertmanager configuration", "err", err)
|
||||
return errorToResponse(err)
|
||||
}
|
||||
|
||||
var extraCfg *apimodels.ExtraConfiguration
|
||||
for i := range cfg.ExtraConfigs {
|
||||
if cfg.ExtraConfigs[i].Identifier == identifier {
|
||||
extraCfg = &cfg.ExtraConfigs[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if extraCfg == nil {
|
||||
return response.Error(http.StatusNotFound, "Alertmanager configuration not found", nil)
|
||||
}
|
||||
|
||||
respBody := apimodels.AlertmanagerUserConfig{
|
||||
AlertmanagerConfig: extraCfg.AlertmanagerConfig,
|
||||
TemplateFiles: extraCfg.TemplateFiles,
|
||||
}
|
||||
|
||||
resp := response.YAML(http.StatusOK, respBody)
|
||||
resp.SetHeader(configIdentifierHeader, extraCfg.Identifier)
|
||||
resp.SetHeader(mergeMatchersHeader, formatMergeMatchers(extraCfg.MergeMatchers))
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
func (srv *ConvertPrometheusSrv) RouteConvertPrometheusDeleteAlertmanagerConfig(c *contextmodel.ReqContext) response.Response {
|
||||
@@ -647,3 +704,55 @@ func parseNotificationSettingsHeader(ctx *contextmodel.ReqContext) ([]models.Not
|
||||
|
||||
return notificationSettings, nil
|
||||
}
|
||||
|
||||
// parseMergeMatchersHeader parses the merge matchers header value.
|
||||
// Expected format: "key1=value1,key2=value2"
|
||||
func parseMergeMatchersHeader(c *contextmodel.ReqContext) (amconfig.Matchers, error) {
|
||||
matchersStr := strings.TrimSpace(c.Req.Header.Get(mergeMatchersHeader))
|
||||
|
||||
if matchersStr == "" {
|
||||
return amconfig.Matchers{}, errInvalidHeaderValue(mergeMatchersHeader, errors.New("value cannot be empty"))
|
||||
}
|
||||
|
||||
matchers := amconfig.Matchers{}
|
||||
|
||||
for pair := range strings.SplitSeq(matchersStr, ",") {
|
||||
parts := strings.SplitN(strings.TrimSpace(pair), "=", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, errInvalidHeaderValue(mergeMatchersHeader, errors.New("format should be 'key=value,key2=value2'"))
|
||||
}
|
||||
|
||||
key := strings.TrimSpace(parts[0])
|
||||
value := strings.TrimSpace(parts[1])
|
||||
|
||||
if key == "" || value == "" {
|
||||
return nil, errInvalidHeaderValue(mergeMatchersHeader, errors.New("keys and values cannot be empty"))
|
||||
}
|
||||
|
||||
matchers = append(matchers, &labels.Matcher{
|
||||
Type: labels.MatchEqual,
|
||||
Name: key,
|
||||
Value: value,
|
||||
})
|
||||
}
|
||||
|
||||
return matchers, nil
|
||||
}
|
||||
|
||||
func formatMergeMatchers(matchers amconfig.Matchers) string {
|
||||
var pairs []string
|
||||
for _, matcher := range matchers {
|
||||
if matcher.Type == labels.MatchEqual {
|
||||
pairs = append(pairs, fmt.Sprintf("%s=%s", matcher.Name, matcher.Value))
|
||||
}
|
||||
}
|
||||
return strings.Join(pairs, ",")
|
||||
}
|
||||
|
||||
func parseConfigIdentifierHeader(c *contextmodel.ReqContext) (string, error) {
|
||||
identifier := strings.TrimSpace(c.Req.Header.Get(configIdentifierHeader))
|
||||
if identifier == "" {
|
||||
return "", errInvalidHeaderValue(configIdentifierHeader, errors.New("identifier cannot be empty"))
|
||||
}
|
||||
return identifier, nil
|
||||
}
|
||||
|
||||
@@ -3,12 +3,16 @@ package api
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
amconfig "github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/pkg/labels"
|
||||
prommodel "github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
@@ -60,7 +64,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("without datasource UID header should return 400", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(datasourceUIDHeader, "")
|
||||
|
||||
@@ -71,7 +75,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("with invalid datasource should return error", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(datasourceUIDHeader, "non-existing-ds")
|
||||
|
||||
@@ -81,7 +85,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("with rule group without evaluation interval should return 202", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
|
||||
response := srv.RouteConvertPrometheusPostRuleGroup(rc, "test", simpleGroup)
|
||||
@@ -90,7 +94,8 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
|
||||
t.Run("should replace an existing rule group", func(t *testing.T) {
|
||||
provenanceStore := fakes.NewFakeProvisioningStore()
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t, withProvenanceStore(provenanceStore))
|
||||
folderService := foldertest.NewFakeService()
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t, withProvenanceStore(provenanceStore), withFolderService(folderService))
|
||||
|
||||
// Create a folder in the root
|
||||
fldr := randFolder()
|
||||
@@ -152,7 +157,8 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
|
||||
t.Run("should fail to replace a provisioned rule group", func(t *testing.T) {
|
||||
provenanceStore := fakes.NewFakeProvisioningStore()
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t, withProvenanceStore(provenanceStore))
|
||||
folderService := foldertest.NewFakeService()
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t, withProvenanceStore(provenanceStore), withFolderService(folderService))
|
||||
|
||||
// Create a folder in the root
|
||||
fldr := randFolder()
|
||||
@@ -187,7 +193,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
|
||||
t.Run("with no access to the datasource should return 403", func(t *testing.T) {
|
||||
acFake := &acfakes.FakeRuleService{}
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t, withFakeAccessControlRuleService(acFake))
|
||||
srv, _, _ := createConvertPrometheusSrv(t, withFakeAccessControlRuleService(acFake))
|
||||
|
||||
acFake.AuthorizeRuleChangesFunc = func(context.Context, identity.Requester, *store.GroupDelta) error {
|
||||
return datasources.ErrDataSourceAccessDenied
|
||||
@@ -203,7 +209,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
quotas := &provisioning.MockQuotaChecker{}
|
||||
quotas.EXPECT().LimitExceeded()
|
||||
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t, withQuotaChecker(quotas))
|
||||
srv, _, _ := createConvertPrometheusSrv(t, withQuotaChecker(quotas))
|
||||
|
||||
rc := createRequestCtx()
|
||||
response := srv.RouteConvertPrometheusPostRuleGroup(rc, "folder", simpleGroup)
|
||||
@@ -241,7 +247,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(tc.headerName, tc.headerValue)
|
||||
|
||||
@@ -274,7 +280,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(tc.headerName, tc.headerValue)
|
||||
|
||||
@@ -286,7 +292,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("with valid request should return 202", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
|
||||
response := srv.RouteConvertPrometheusPostRuleGroup(rc, "test", simpleGroup)
|
||||
@@ -315,7 +321,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
features := featuremgmt.WithFeatures()
|
||||
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t, withFeatureToggles(features))
|
||||
srv, _, _ := createConvertPrometheusSrv(t, withFeatureToggles(features))
|
||||
srv.cfg.RecordingRules.Enabled = tc.recordingRules
|
||||
rc := createRequestCtx()
|
||||
|
||||
@@ -327,7 +333,8 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
|
||||
t.Run("with disable provenance header should use ProvenanceNone", func(t *testing.T) {
|
||||
provenanceStore := fakes.NewFakeProvisioningStore()
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t, withProvenanceStore(provenanceStore))
|
||||
folderService := foldertest.NewFakeService()
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t, withProvenanceStore(provenanceStore), withFolderService(folderService))
|
||||
|
||||
// Create a folder in the root
|
||||
fldr := randFolder()
|
||||
@@ -361,7 +368,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("returns error when target datasource does not exist", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(targetDatasourceUIDHeader, "some-data-source")
|
||||
|
||||
@@ -371,7 +378,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("uses target datasource for recording rules", func(t *testing.T) {
|
||||
srv, dsCache, ruleStore, _ := createConvertPrometheusSrv(t)
|
||||
srv, dsCache, ruleStore := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
targetDSUID := util.GenerateShortUID()
|
||||
ds := &datasources.DataSource{
|
||||
@@ -408,7 +415,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("sets notification settings for rules if specified", func(t *testing.T) {
|
||||
srv, _, ruleStore, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
|
||||
receiver := "test-receiver"
|
||||
@@ -450,7 +457,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("returns error when notification settings header contains invalid JSON", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
|
||||
rc.Req.Header.Set(notificationSettingsHeader, "{invalid json")
|
||||
@@ -472,7 +479,7 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("returns error when notification settings contain invalid values", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
|
||||
settings := apimodels.AlertRuleNotificationSettings{
|
||||
@@ -515,7 +522,7 @@ func TestRouteConvertPrometheusGetRuleGroup(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("with non-existent folder should return 404", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
|
||||
response := srv.RouteConvertPrometheusGetRuleGroup(rc, "non-existent", "test")
|
||||
@@ -523,7 +530,7 @@ func TestRouteConvertPrometheusGetRuleGroup(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("with non-existent group should return 404", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
|
||||
response := srv.RouteConvertPrometheusGetRuleGroup(rc, "test", "non-existent")
|
||||
@@ -531,7 +538,8 @@ func TestRouteConvertPrometheusGetRuleGroup(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("with valid request should return 200", func(t *testing.T) {
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t)
|
||||
folderService := foldertest.NewFakeService()
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t, withFolderService(folderService))
|
||||
rc := createRequestCtx()
|
||||
|
||||
// Create two folders in the root folder
|
||||
@@ -619,7 +627,7 @@ func TestRouteConvertPrometheusGetNamespace(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("with non-existent folder should return 404", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
|
||||
response := srv.RouteConvertPrometheusGetNamespace(rc, "non-existent")
|
||||
@@ -627,7 +635,8 @@ func TestRouteConvertPrometheusGetNamespace(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("with valid request should return 200", func(t *testing.T) {
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t)
|
||||
folderService := foldertest.NewFakeService()
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t, withFolderService(folderService))
|
||||
rc := createRequestCtx()
|
||||
|
||||
// Create two folders in the root folder
|
||||
@@ -731,12 +740,13 @@ func TestRouteConvertPrometheusGetRules(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("for non-existent folder should return empty response", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
assertEmptyResponse(t, srv, rc)
|
||||
})
|
||||
|
||||
t.Run("for existing folder with no children should return empty response", func(t *testing.T) {
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t)
|
||||
folderService := foldertest.NewFakeService()
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t, withFolderService(folderService))
|
||||
|
||||
fldr := randFolder()
|
||||
fldr.UID = unknownFolderUID
|
||||
@@ -757,7 +767,8 @@ func TestRouteConvertPrometheusGetRules(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("with rules should return 200 with rules", func(t *testing.T) {
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t)
|
||||
folderService := foldertest.NewFakeService()
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t, withFolderService(folderService))
|
||||
rc := createRequestCtx()
|
||||
|
||||
// Create a folder in the root
|
||||
@@ -797,7 +808,7 @@ func TestRouteConvertPrometheusGetRules(t *testing.T) {
|
||||
|
||||
func TestRouteConvertPrometheusDeleteNamespace(t *testing.T) {
|
||||
t.Run("for non-existent folder should return 404", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
|
||||
response := srv.RouteConvertPrometheusDeleteNamespace(rc, "non-existent")
|
||||
@@ -805,7 +816,8 @@ func TestRouteConvertPrometheusDeleteNamespace(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("for existing folder with no groups should return 404", func(t *testing.T) {
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t)
|
||||
folderService := foldertest.NewFakeService()
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t, withFolderService(folderService))
|
||||
rc := createRequestCtx()
|
||||
|
||||
fldr := randFolder()
|
||||
@@ -820,7 +832,8 @@ func TestRouteConvertPrometheusDeleteNamespace(t *testing.T) {
|
||||
|
||||
t.Run("valid request should delete rules", func(t *testing.T) {
|
||||
initNamespace := func(promDefinition string, opts ...convertPrometheusSrvOptionsFunc) (*ConvertPrometheusSrv, *fakes.RuleStore, *folder.Folder, *models.AlertRule) {
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t, opts...)
|
||||
folderService := foldertest.NewFakeService()
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t, append(opts, withFolderService(folderService))...)
|
||||
|
||||
// Create a folder in the root
|
||||
fldr := randFolder()
|
||||
@@ -927,7 +940,7 @@ func TestRouteConvertPrometheusDeleteNamespace(t *testing.T) {
|
||||
|
||||
func TestRouteConvertPrometheusDeleteRuleGroup(t *testing.T) {
|
||||
t.Run("for non-existent folder should return 404", func(t *testing.T) {
|
||||
srv, _, _, _ := createConvertPrometheusSrv(t)
|
||||
srv, _, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
|
||||
response := srv.RouteConvertPrometheusDeleteRuleGroup(rc, "non-existent", "test-group")
|
||||
@@ -935,7 +948,8 @@ func TestRouteConvertPrometheusDeleteRuleGroup(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("for existing folder with no group should return 404", func(t *testing.T) {
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t)
|
||||
folderService := foldertest.NewFakeService()
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t, withFolderService(folderService))
|
||||
rc := createRequestCtx()
|
||||
|
||||
fldr := randFolder()
|
||||
@@ -952,7 +966,8 @@ func TestRouteConvertPrometheusDeleteRuleGroup(t *testing.T) {
|
||||
|
||||
t.Run("valid request should delete rules", func(t *testing.T) {
|
||||
initGroup := func(promDefinition string, groupName string, opts ...convertPrometheusSrvOptionsFunc) (*ConvertPrometheusSrv, *fakes.RuleStore, *folder.Folder, *models.AlertRule) {
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t, opts...)
|
||||
folderService := foldertest.NewFakeService()
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t, append(opts, withFolderService(folderService))...)
|
||||
|
||||
// Create a folder in the root
|
||||
fldr := randFolder()
|
||||
@@ -1060,7 +1075,8 @@ func TestRouteConvertPrometheusDeleteRuleGroup(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRouteConvertPrometheusPostRuleGroups(t *testing.T) {
|
||||
srv, _, ruleStore, folderService := createConvertPrometheusSrv(t)
|
||||
folderService := foldertest.NewFakeService()
|
||||
srv, _, ruleStore := createConvertPrometheusSrv(t, withFolderService(folderService))
|
||||
|
||||
req := createRequestCtx()
|
||||
req.Req.Header.Set(datasourceUIDHeader, existingDSUID)
|
||||
@@ -1265,6 +1281,8 @@ type convertPrometheusSrvOptions struct {
|
||||
fakeAccessControlRuleService *acfakes.FakeRuleService
|
||||
quotaChecker *provisioning.MockQuotaChecker
|
||||
featureToggles featuremgmt.FeatureToggles
|
||||
alertmanager Alertmanager
|
||||
folderService folder.Service
|
||||
}
|
||||
|
||||
type convertPrometheusSrvOptionsFunc func(*convertPrometheusSrvOptions)
|
||||
@@ -1293,7 +1311,19 @@ func withFeatureToggles(toggles featuremgmt.FeatureToggles) convertPrometheusSrv
|
||||
}
|
||||
}
|
||||
|
||||
func createConvertPrometheusSrv(t *testing.T, opts ...convertPrometheusSrvOptionsFunc) (*ConvertPrometheusSrv, *dsfakes.FakeCacheService, *fakes.RuleStore, *foldertest.FakeService) {
|
||||
func withAlertmanager(am Alertmanager) convertPrometheusSrvOptionsFunc {
|
||||
return func(opts *convertPrometheusSrvOptions) {
|
||||
opts.alertmanager = am
|
||||
}
|
||||
}
|
||||
|
||||
func withFolderService(f folder.Service) convertPrometheusSrvOptionsFunc {
|
||||
return func(opts *convertPrometheusSrvOptions) {
|
||||
opts.folderService = f
|
||||
}
|
||||
}
|
||||
|
||||
func createConvertPrometheusSrv(t *testing.T, opts ...convertPrometheusSrvOptionsFunc) (*ConvertPrometheusSrv, *dsfakes.FakeCacheService, *fakes.RuleStore) {
|
||||
t.Helper()
|
||||
|
||||
// By default the quota checker will allow the operation
|
||||
@@ -1304,6 +1334,7 @@ func createConvertPrometheusSrv(t *testing.T, opts ...convertPrometheusSrvOption
|
||||
provenanceStore: fakes.NewFakeProvisioningStore(),
|
||||
fakeAccessControlRuleService: &acfakes.FakeRuleService{},
|
||||
quotaChecker: quotas,
|
||||
folderService: foldertest.NewFakeService(),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
@@ -1321,12 +1352,10 @@ func createConvertPrometheusSrv(t *testing.T, opts ...convertPrometheusSrvOption
|
||||
}
|
||||
dsCache.DataSources = append(dsCache.DataSources, ds)
|
||||
|
||||
folderService := foldertest.NewFakeService()
|
||||
|
||||
alertRuleService := provisioning.NewAlertRuleService(
|
||||
ruleStore,
|
||||
options.provenanceStore,
|
||||
folderService,
|
||||
options.folderService,
|
||||
options.quotaChecker,
|
||||
&provisioning.NopTransactionManager{},
|
||||
60,
|
||||
@@ -1344,9 +1373,9 @@ func createConvertPrometheusSrv(t *testing.T, opts ...convertPrometheusSrvOption
|
||||
},
|
||||
}
|
||||
|
||||
srv := NewConvertPrometheusSrv(cfg, log.NewNopLogger(), ruleStore, dsCache, alertRuleService, options.featureToggles)
|
||||
srv := NewConvertPrometheusSrv(cfg, log.NewNopLogger(), ruleStore, dsCache, alertRuleService, options.featureToggles, options.alertmanager)
|
||||
|
||||
return srv, dsCache, ruleStore, folderService
|
||||
return srv, dsCache, ruleStore
|
||||
}
|
||||
|
||||
func createRequestCtx() *contextmodel.ReqContext {
|
||||
@@ -1466,3 +1495,307 @@ func TestGetProvenance(t *testing.T) {
|
||||
require.Equal(t, models.ProvenanceNone, provenance)
|
||||
})
|
||||
}
|
||||
|
||||
type mockAlertmanager struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *mockAlertmanager) SaveAndApplyExtraConfiguration(ctx context.Context, org int64, extraConfig apimodels.ExtraConfiguration) error {
|
||||
args := m.Called(ctx, org, extraConfig)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *mockAlertmanager) GetAlertmanagerConfiguration(ctx context.Context, org int64, withAutogen bool) (apimodels.GettableUserConfig, error) {
|
||||
args := m.Called(ctx, org, withAutogen)
|
||||
return args.Get(0).(apimodels.GettableUserConfig), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *mockAlertmanager) DeleteAndApplyExtraConfiguration(ctx context.Context, org int64, identifier string) error {
|
||||
args := m.Called(ctx, org, identifier)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func TestRouteConvertPrometheusGetAlertmanagerConfig(t *testing.T) {
|
||||
const identifier = "test-config"
|
||||
const orgID = int64(1)
|
||||
|
||||
t.Run("without feature flag should return 501", func(t *testing.T) {
|
||||
ft := featuremgmt.WithFeatures()
|
||||
srv, _, _ := createConvertPrometheusSrv(t, withFeatureToggles(ft))
|
||||
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(configIdentifierHeader, identifier)
|
||||
response := srv.RouteConvertPrometheusGetAlertmanagerConfig(rc)
|
||||
|
||||
require.Equal(t, http.StatusNotImplemented, response.Status())
|
||||
})
|
||||
|
||||
t.Run("without config identifier header should return 400", func(t *testing.T) {
|
||||
mockAM := &mockAlertmanager{}
|
||||
ft := featuremgmt.WithFeatures(featuremgmt.FlagAlertingImportAlertmanagerAPI)
|
||||
srv, _, _ := createConvertPrometheusSrv(t, withAlertmanager(mockAM), withFeatureToggles(ft))
|
||||
|
||||
rc := createRequestCtx()
|
||||
response := srv.RouteConvertPrometheusGetAlertmanagerConfig(rc)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, response.Status())
|
||||
})
|
||||
|
||||
t.Run("with empty config identifier header should return 400", func(t *testing.T) {
|
||||
mockAM := &mockAlertmanager{}
|
||||
ft := featuremgmt.WithFeatures(featuremgmt.FlagAlertingImportAlertmanagerAPI)
|
||||
srv, _, _ := createConvertPrometheusSrv(t, withAlertmanager(mockAM), withFeatureToggles(ft))
|
||||
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(configIdentifierHeader, "")
|
||||
response := srv.RouteConvertPrometheusGetAlertmanagerConfig(rc)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, response.Status())
|
||||
})
|
||||
|
||||
t.Run("should return config when it is found", func(t *testing.T) {
|
||||
mockAM := &mockAlertmanager{}
|
||||
ft := featuremgmt.WithFeatures(featuremgmt.FlagAlertingImportAlertmanagerAPI)
|
||||
srv, _, _ := createConvertPrometheusSrv(t, withAlertmanager(mockAM), withFeatureToggles(ft))
|
||||
|
||||
expectedConfig := apimodels.GettableUserConfig{
|
||||
ExtraConfigs: []apimodels.ExtraConfiguration{
|
||||
{
|
||||
Identifier: identifier,
|
||||
TemplateFiles: map[string]string{
|
||||
"test.tmpl": "{{ define \"test\" }}Hello{{ end }}",
|
||||
},
|
||||
AlertmanagerConfig: `route:
|
||||
receiver: default
|
||||
receivers:
|
||||
- name: default`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mockAM.On("GetAlertmanagerConfiguration", mock.Anything, int64(1), false).Return(expectedConfig, nil).Once()
|
||||
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(configIdentifierHeader, identifier)
|
||||
response := srv.RouteConvertPrometheusGetAlertmanagerConfig(rc)
|
||||
|
||||
require.Equal(t, http.StatusOK, response.Status())
|
||||
|
||||
expectedResponse := `alertmanager_config: |-
|
||||
route:
|
||||
receiver: default
|
||||
receivers:
|
||||
- name: default
|
||||
template_files:
|
||||
test.tmpl: '{{ define "test" }}Hello{{ end }}'`
|
||||
|
||||
require.YAMLEq(t, expectedResponse, string(response.Body()))
|
||||
mockAM.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("when config not found should return 404", func(t *testing.T) {
|
||||
mockAM := &mockAlertmanager{}
|
||||
ft := featuremgmt.WithFeatures(featuremgmt.FlagAlertingImportAlertmanagerAPI)
|
||||
srv, _, _ := createConvertPrometheusSrv(t, withAlertmanager(mockAM), withFeatureToggles(ft))
|
||||
|
||||
expectedConfig := apimodels.GettableUserConfig{
|
||||
ExtraConfigs: []apimodels.ExtraConfiguration{
|
||||
{
|
||||
Identifier: "other-config",
|
||||
TemplateFiles: map[string]string{
|
||||
"test.tmpl": "{{ define \"test\" }}Hello{{ end }}",
|
||||
},
|
||||
AlertmanagerConfig: `route:
|
||||
receiver: default
|
||||
receivers:
|
||||
- name: default`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mockAM.On("GetAlertmanagerConfiguration", mock.Anything, orgID, false).Return(expectedConfig, nil).Once()
|
||||
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(configIdentifierHeader, identifier)
|
||||
response := srv.RouteConvertPrometheusGetAlertmanagerConfig(rc)
|
||||
|
||||
require.Equal(t, http.StatusNotFound, response.Status())
|
||||
mockAM.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("should return error when GetAlertmanagerConfiguration fails", func(t *testing.T) {
|
||||
mockAM := &mockAlertmanager{}
|
||||
ft := featuremgmt.WithFeatures(featuremgmt.FlagAlertingImportAlertmanagerAPI)
|
||||
srv, _, _ := createConvertPrometheusSrv(t, withAlertmanager(mockAM), withFeatureToggles(ft))
|
||||
|
||||
mockAM.On("GetAlertmanagerConfiguration", mock.Anything, orgID, false).Return(apimodels.GettableUserConfig{}, errors.New("config error")).Once()
|
||||
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(configIdentifierHeader, identifier)
|
||||
response := srv.RouteConvertPrometheusGetAlertmanagerConfig(rc)
|
||||
|
||||
require.Equal(t, http.StatusInternalServerError, response.Status())
|
||||
mockAM.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseMergeMatchersHeader(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
headerValue string
|
||||
expectedError bool
|
||||
expectedMatchers amconfig.Matchers
|
||||
}{
|
||||
{
|
||||
name: "empty header should return error",
|
||||
headerValue: "",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "single matcher should parse correctly",
|
||||
headerValue: "env=prod",
|
||||
expectedError: false,
|
||||
expectedMatchers: amconfig.Matchers{
|
||||
{Type: labels.MatchEqual, Name: "env", Value: "prod"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple matchers should be parsed correctly",
|
||||
headerValue: "env=prod,team=alerting",
|
||||
expectedError: false,
|
||||
expectedMatchers: amconfig.Matchers{
|
||||
{Type: labels.MatchEqual, Name: "env", Value: "prod"},
|
||||
{Type: labels.MatchEqual, Name: "team", Value: "alerting"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "matchers with spaces should be parsed correctly",
|
||||
headerValue: " env = prod , team = alerting ",
|
||||
expectedError: false,
|
||||
expectedMatchers: amconfig.Matchers{
|
||||
{Type: labels.MatchEqual, Name: "env", Value: "prod"},
|
||||
{Type: labels.MatchEqual, Name: "team", Value: "alerting"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid format without equals should return error",
|
||||
headerValue: "env:prod",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "empty key should return error",
|
||||
headerValue: "=prod",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "empty value should return error",
|
||||
headerValue: "env=",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "missing value should return error",
|
||||
headerValue: "env",
|
||||
expectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(mergeMatchersHeader, tc.headerValue)
|
||||
|
||||
matchers, err := parseMergeMatchersHeader(rc)
|
||||
|
||||
if tc.expectedError {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, tc.expectedMatchers, matchers)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseConfigIdentifierHeader(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
headerValue string
|
||||
expectedValue string
|
||||
expectedError bool
|
||||
}{
|
||||
{
|
||||
name: "valid identifier should parse correctly",
|
||||
headerValue: "test-config",
|
||||
expectedValue: "test-config",
|
||||
expectedError: false,
|
||||
},
|
||||
{
|
||||
name: "identifier with spaces should be trimmed",
|
||||
headerValue: " test-config ",
|
||||
expectedValue: "test-config",
|
||||
expectedError: false,
|
||||
},
|
||||
{
|
||||
name: "empty identifier should return error",
|
||||
headerValue: "",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "whitespace only identifier should return error",
|
||||
headerValue: " ",
|
||||
expectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(configIdentifierHeader, tc.headerValue)
|
||||
|
||||
identifier, err := parseConfigIdentifierHeader(rc)
|
||||
|
||||
if tc.expectedError {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expectedValue, identifier)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatMergeMatchers(t *testing.T) {
|
||||
t.Run("empty matchers should return empty string", func(t *testing.T) {
|
||||
result := formatMergeMatchers(nil)
|
||||
require.Equal(t, "", result)
|
||||
})
|
||||
|
||||
t.Run("single matcher should format correctly", func(t *testing.T) {
|
||||
matchers := amconfig.Matchers{
|
||||
&labels.Matcher{
|
||||
Type: labels.MatchEqual,
|
||||
Name: "env",
|
||||
Value: "prod",
|
||||
},
|
||||
}
|
||||
result := formatMergeMatchers(matchers)
|
||||
require.Equal(t, "env=prod", result)
|
||||
})
|
||||
|
||||
t.Run("multiple matchers should format correctly", func(t *testing.T) {
|
||||
matchers := amconfig.Matchers{
|
||||
&labels.Matcher{
|
||||
Type: labels.MatchEqual,
|
||||
Name: "env",
|
||||
Value: "prod",
|
||||
},
|
||||
&labels.Matcher{
|
||||
Type: labels.MatchEqual,
|
||||
Name: "team",
|
||||
Value: "backend",
|
||||
},
|
||||
}
|
||||
result := formatMergeMatchers(matchers)
|
||||
require.Equal(t, "env=prod,team=backend", result)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -514,6 +514,9 @@
|
||||
"notificationSettings": {
|
||||
"$ref": "#/definitions/AlertRuleNotificationSettings"
|
||||
},
|
||||
"provenance": {
|
||||
"$ref": "#/definitions/Provenance"
|
||||
},
|
||||
"queriedDatasourceUIDs": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
@@ -579,7 +582,8 @@
|
||||
"AlertmanagerUserConfig": {
|
||||
"properties": {
|
||||
"alertmanager_config": {
|
||||
"$ref": "#/definitions/Config"
|
||||
"description": "Configuration for Alertmanager in YAML format.\nin: body",
|
||||
"type": "string"
|
||||
},
|
||||
"template_files": {
|
||||
"additionalProperties": {
|
||||
@@ -1434,20 +1438,6 @@
|
||||
"title": "Frames is a slice of Frame pointers.",
|
||||
"type": "array"
|
||||
},
|
||||
"GettableAlertmanagerUserConfig": {
|
||||
"properties": {
|
||||
"alertmanager_config": {
|
||||
"type": "string"
|
||||
},
|
||||
"template_files": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"GettableAlertmanagers": {
|
||||
"properties": {
|
||||
"data": {
|
||||
@@ -3719,6 +3709,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"Route": {
|
||||
"description": "A Route is a node that contains definitions of how to handle alerts. This is modified\nfrom the upstream alertmanager in that it adds the ObjectMatchers property.",
|
||||
"properties": {
|
||||
"active_time_intervals": {
|
||||
"items": {
|
||||
@@ -3760,6 +3751,12 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"object_matchers": {
|
||||
"$ref": "#/definitions/ObjectMatchers"
|
||||
},
|
||||
"provenance": {
|
||||
"$ref": "#/definitions/Provenance"
|
||||
},
|
||||
"receiver": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -3773,7 +3770,6 @@
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"title": "A Route is a node that contains definitions of how to handle alerts.",
|
||||
"type": "object"
|
||||
},
|
||||
"RouteExport": {
|
||||
@@ -3869,6 +3865,9 @@
|
||||
"notificationSettings": {
|
||||
"$ref": "#/definitions/AlertRuleNotificationSettings"
|
||||
},
|
||||
"provenance": {
|
||||
"$ref": "#/definitions/Provenance"
|
||||
},
|
||||
"query": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package definitions
|
||||
|
||||
import (
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/common/model"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Route for mimirtool
|
||||
@@ -230,7 +228,7 @@ import (
|
||||
// - application/yaml
|
||||
//
|
||||
// Responses:
|
||||
// 200: GettableAlertmanagerUserConfig
|
||||
// 200: AlertmanagerUserConfig
|
||||
// 403: ForbiddenError
|
||||
|
||||
// Route for `mimirtool alertmanager delete`
|
||||
@@ -347,33 +345,8 @@ type RouteConvertPrometheusDeleteAlertmanagerConfigParams struct {
|
||||
|
||||
// swagger:model
|
||||
type AlertmanagerUserConfig struct {
|
||||
AlertmanagerConfig config.Config `yaml:"alertmanager_config" json:"alertmanager_config"`
|
||||
TemplateFiles map[string]string `yaml:"template_files" json:"template_files"`
|
||||
}
|
||||
|
||||
func (c *AlertmanagerUserConfig) UnmarshalYAML(value *yaml.Node) error {
|
||||
// mimirtool sends alertmanager_config as a string
|
||||
type cortexAlertmanagerUserConfig struct {
|
||||
TemplateFiles map[string]string `yaml:"template_files" json:"template_files"`
|
||||
AlertmanagerConfig string `yaml:"alertmanager_config" json:"alertmanager_config"`
|
||||
}
|
||||
|
||||
var tmp cortexAlertmanagerUserConfig
|
||||
|
||||
if err := value.Decode(&tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal([]byte(tmp.AlertmanagerConfig), &c.AlertmanagerConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
c.TemplateFiles = tmp.TemplateFiles
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type GettableAlertmanagerUserConfig struct {
|
||||
// Configuration for Alertmanager in YAML format.
|
||||
// in: body
|
||||
AlertmanagerConfig string `yaml:"alertmanager_config" json:"alertmanager_config"`
|
||||
TemplateFiles map[string]string `yaml:"template_files" json:"template_files"`
|
||||
}
|
||||
|
||||
@@ -514,6 +514,9 @@
|
||||
"notificationSettings": {
|
||||
"$ref": "#/definitions/AlertRuleNotificationSettings"
|
||||
},
|
||||
"provenance": {
|
||||
"$ref": "#/definitions/Provenance"
|
||||
},
|
||||
"queriedDatasourceUIDs": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
@@ -579,7 +582,8 @@
|
||||
"AlertmanagerUserConfig": {
|
||||
"properties": {
|
||||
"alertmanager_config": {
|
||||
"$ref": "#/definitions/Config"
|
||||
"description": "Configuration for Alertmanager in YAML format.\nin: body",
|
||||
"type": "string"
|
||||
},
|
||||
"template_files": {
|
||||
"additionalProperties": {
|
||||
@@ -1434,20 +1438,6 @@
|
||||
"title": "Frames is a slice of Frame pointers.",
|
||||
"type": "array"
|
||||
},
|
||||
"GettableAlertmanagerUserConfig": {
|
||||
"properties": {
|
||||
"alertmanager_config": {
|
||||
"type": "string"
|
||||
},
|
||||
"template_files": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"GettableAlertmanagers": {
|
||||
"properties": {
|
||||
"data": {
|
||||
@@ -3875,6 +3865,9 @@
|
||||
"notificationSettings": {
|
||||
"$ref": "#/definitions/AlertRuleNotificationSettings"
|
||||
},
|
||||
"provenance": {
|
||||
"$ref": "#/definitions/Provenance"
|
||||
},
|
||||
"query": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -6923,9 +6916,9 @@
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "GettableAlertmanagerUserConfig",
|
||||
"description": "AlertmanagerUserConfig",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/GettableAlertmanagerUserConfig"
|
||||
"$ref": "#/definitions/AlertmanagerUserConfig"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
|
||||
@@ -1381,9 +1381,9 @@
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "GettableAlertmanagerUserConfig",
|
||||
"description": "AlertmanagerUserConfig",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/GettableAlertmanagerUserConfig"
|
||||
"$ref": "#/definitions/AlertmanagerUserConfig"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
@@ -4812,6 +4812,9 @@
|
||||
"notificationSettings": {
|
||||
"$ref": "#/definitions/AlertRuleNotificationSettings"
|
||||
},
|
||||
"provenance": {
|
||||
"$ref": "#/definitions/Provenance"
|
||||
},
|
||||
"queriedDatasourceUIDs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@@ -4868,7 +4871,8 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"alertmanager_config": {
|
||||
"$ref": "#/definitions/Config"
|
||||
"description": "Configuration for Alertmanager in YAML format.\nin: body",
|
||||
"type": "string"
|
||||
},
|
||||
"template_files": {
|
||||
"type": "object",
|
||||
@@ -5723,20 +5727,6 @@
|
||||
"$ref": "#/definitions/Frame"
|
||||
}
|
||||
},
|
||||
"GettableAlertmanagerUserConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"alertmanager_config": {
|
||||
"type": "string"
|
||||
},
|
||||
"template_files": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"GettableAlertmanagers": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -8172,6 +8162,9 @@
|
||||
"notificationSettings": {
|
||||
"$ref": "#/definitions/AlertRuleNotificationSettings"
|
||||
},
|
||||
"provenance": {
|
||||
"$ref": "#/definitions/Provenance"
|
||||
},
|
||||
"query": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
+15
-16
@@ -12941,6 +12941,9 @@
|
||||
"notificationSettings": {
|
||||
"$ref": "#/definitions/AlertRuleNotificationSettings"
|
||||
},
|
||||
"provenance": {
|
||||
"$ref": "#/definitions/Provenance"
|
||||
},
|
||||
"queriedDatasourceUIDs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@@ -12997,7 +13000,8 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"alertmanager_config": {
|
||||
"$ref": "#/definitions/Config"
|
||||
"description": "Configuration for Alertmanager in YAML format.\nin: body",
|
||||
"type": "string"
|
||||
},
|
||||
"template_files": {
|
||||
"type": "object",
|
||||
@@ -15883,20 +15887,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"GettableAlertmanagerUserConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"alertmanager_config": {
|
||||
"type": "string"
|
||||
},
|
||||
"template_files": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"GettableAlertmanagers": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -20073,8 +20063,8 @@
|
||||
}
|
||||
},
|
||||
"Route": {
|
||||
"description": "A Route is a node that contains definitions of how to handle alerts. This is modified\nfrom the upstream alertmanager in that it adds the ObjectMatchers property.",
|
||||
"type": "object",
|
||||
"title": "A Route is a node that contains definitions of how to handle alerts.",
|
||||
"properties": {
|
||||
"active_time_intervals": {
|
||||
"type": "array",
|
||||
@@ -20116,6 +20106,12 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"object_matchers": {
|
||||
"$ref": "#/definitions/ObjectMatchers"
|
||||
},
|
||||
"provenance": {
|
||||
"$ref": "#/definitions/Provenance"
|
||||
},
|
||||
"receiver": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -20230,6 +20226,9 @@
|
||||
"notificationSettings": {
|
||||
"$ref": "#/definitions/AlertRuleNotificationSettings"
|
||||
},
|
||||
"provenance": {
|
||||
"$ref": "#/definitions/Provenance"
|
||||
},
|
||||
"query": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
+15
-16
@@ -2982,6 +2982,9 @@
|
||||
"notificationSettings": {
|
||||
"$ref": "#/components/schemas/AlertRuleNotificationSettings"
|
||||
},
|
||||
"provenance": {
|
||||
"$ref": "#/components/schemas/Provenance"
|
||||
},
|
||||
"queriedDatasourceUIDs": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
@@ -3047,7 +3050,8 @@
|
||||
"AlertmanagerUserConfig": {
|
||||
"properties": {
|
||||
"alertmanager_config": {
|
||||
"$ref": "#/components/schemas/Config"
|
||||
"description": "Configuration for Alertmanager in YAML format.\nin: body",
|
||||
"type": "string"
|
||||
},
|
||||
"template_files": {
|
||||
"additionalProperties": {
|
||||
@@ -5934,20 +5938,6 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"GettableAlertmanagerUserConfig": {
|
||||
"properties": {
|
||||
"alertmanager_config": {
|
||||
"type": "string"
|
||||
},
|
||||
"template_files": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"GettableAlertmanagers": {
|
||||
"properties": {
|
||||
"data": {
|
||||
@@ -10124,6 +10114,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"Route": {
|
||||
"description": "A Route is a node that contains definitions of how to handle alerts. This is modified\nfrom the upstream alertmanager in that it adds the ObjectMatchers property.",
|
||||
"properties": {
|
||||
"active_time_intervals": {
|
||||
"items": {
|
||||
@@ -10165,6 +10156,12 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"object_matchers": {
|
||||
"$ref": "#/components/schemas/ObjectMatchers"
|
||||
},
|
||||
"provenance": {
|
||||
"$ref": "#/components/schemas/Provenance"
|
||||
},
|
||||
"receiver": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -10178,7 +10175,6 @@
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"title": "A Route is a node that contains definitions of how to handle alerts.",
|
||||
"type": "object"
|
||||
},
|
||||
"RouteExport": {
|
||||
@@ -10274,6 +10270,9 @@
|
||||
"notificationSettings": {
|
||||
"$ref": "#/components/schemas/AlertRuleNotificationSettings"
|
||||
},
|
||||
"provenance": {
|
||||
"$ref": "#/components/schemas/Provenance"
|
||||
},
|
||||
"query": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user