Alerting: API to create rule groups using mimirtool (#100558)
What is this feature?
Adds an API endpoint to create alert rules with mimirtool:
- POST /convert/prometheus/config/v1/rules/{NamespaceTitle} - Accepts a single rule group in a Prometheus YAML format and creates or updates a Grafana rule group from it.
The endpoint uses the conversion package from #100224.
Key parts
The API works similarly to the provisioning API. If the rule does not exist, it will be created, otherwise updated. Any rules not present in the new group will be deleted, ensuring the group is fully synchronized with the provided configuration.
Since the API works with namespace titles (folders), the handler automatically creates a folder in the root based on the provided title if it does not exist. It also requires a special header, X-Grafana-Alerting-Datasource-UID. This header specifies which datasource to use for the new rules.
If the rule group's evaluation interval is not specified, it uses the DefaultRuleEvaluationInterval from settings.
This commit is contained in:
@@ -187,8 +187,8 @@ func (api *API) RegisterAPIEndpoints(m *metrics.API) {
|
||||
}), m)
|
||||
|
||||
if api.FeatureManager.IsEnabledGlobally(featuremgmt.FlagAlertingConversionAPI) {
|
||||
api.RegisterConvertPrometheusApiEndpoints(NewConvertPrometheusApi(&ConvertPrometheusSrv{
|
||||
logger: logger,
|
||||
}), m)
|
||||
api.RegisterConvertPrometheusApiEndpoints(NewConvertPrometheusApi(
|
||||
NewConvertPrometheusSrv(&api.Cfg.UnifiedAlerting, logger, api.RuleStore, api.DatasourceCache, api.AlertRules),
|
||||
), m)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,60 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/errutil"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/prom"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
datasourceUIDHeader = "X-Grafana-Alerting-Datasource-UID"
|
||||
recordingRulesPausedHeader = "X-Grafana-Alerting-Recording-Rules-Paused"
|
||||
alertRulesPausedHeader = "X-Grafana-Alerting-Alert-Rules-Paused"
|
||||
)
|
||||
|
||||
var (
|
||||
errDatasourceUIDHeaderMissing = errutil.ValidationFailed(
|
||||
"alerting.datasourceUIDHeaderMissing",
|
||||
errutil.WithPublicMessage(fmt.Sprintf("Missing datasource UID header: %s", datasourceUIDHeader)),
|
||||
).Errorf("missing datasource UID header")
|
||||
|
||||
errInvalidHeaderValueMsg = "Invalid value for header {{.Public.Header}}: must be 'true' or 'false'"
|
||||
errInvalidHeaderValueBase = errutil.ValidationFailed("aleting.invalidHeaderValue").MustTemplate(errInvalidHeaderValueMsg, errutil.WithPublic(errInvalidHeaderValueMsg))
|
||||
)
|
||||
|
||||
func errInvalidHeaderValue(header string) error {
|
||||
return errInvalidHeaderValueBase.Build(errutil.TemplateData{Public: map[string]any{"Header": header}})
|
||||
}
|
||||
|
||||
type ConvertPrometheusSrv struct {
|
||||
logger log.Logger
|
||||
cfg *setting.UnifiedAlertingSettings
|
||||
logger log.Logger
|
||||
ruleStore RuleStore
|
||||
datasourceCache datasources.CacheService
|
||||
alertRuleService *provisioning.AlertRuleService
|
||||
}
|
||||
|
||||
func NewConvertPrometheusSrv(cfg *setting.UnifiedAlertingSettings, logger log.Logger, ruleStore RuleStore, datasourceCache datasources.CacheService, alertRuleService *provisioning.AlertRuleService) *ConvertPrometheusSrv {
|
||||
return &ConvertPrometheusSrv{
|
||||
cfg: cfg,
|
||||
logger: logger,
|
||||
ruleStore: ruleStore,
|
||||
datasourceCache: datasourceCache,
|
||||
alertRuleService: alertRuleService,
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *ConvertPrometheusSrv) RouteConvertPrometheusGetRules(c *contextmodel.ReqContext) response.Response {
|
||||
@@ -28,9 +74,131 @@ func (srv *ConvertPrometheusSrv) RouteConvertPrometheusGetNamespace(c *contextmo
|
||||
}
|
||||
|
||||
func (srv *ConvertPrometheusSrv) RouteConvertPrometheusGetRuleGroup(c *contextmodel.ReqContext, namespaceTitle string, group string) response.Response {
|
||||
return response.Error(501, "Not implemented", nil)
|
||||
// Just to make the mimirtool rules load work. It first checks if the group exists, and if the endpoint returns 501 it fails.
|
||||
return response.YAML(http.StatusOK, apimodels.PrometheusRuleGroup{})
|
||||
}
|
||||
|
||||
func (srv *ConvertPrometheusSrv) RouteConvertPrometheusPostRuleGroup(c *contextmodel.ReqContext, namespaceTitle string, prometheusGroup apimodels.PrometheusRuleGroup) response.Response {
|
||||
return response.Error(501, "Not implemented", nil)
|
||||
func (srv *ConvertPrometheusSrv) RouteConvertPrometheusPostRuleGroup(c *contextmodel.ReqContext, namespaceTitle string, promGroup apimodels.PrometheusRuleGroup) response.Response {
|
||||
logger := srv.logger.FromContext(c.Req.Context())
|
||||
logger = logger.New("folder_title", namespaceTitle, "group", promGroup.Name)
|
||||
|
||||
logger.Info("Converting Prometheus rule group", "rules", len(promGroup.Rules))
|
||||
|
||||
ns, errResp := srv.getOrCreateNamespace(c, namespaceTitle, logger)
|
||||
if errResp != nil {
|
||||
return errResp
|
||||
}
|
||||
|
||||
datasourceUID := strings.TrimSpace(c.Req.Header.Get(datasourceUIDHeader))
|
||||
if datasourceUID == "" {
|
||||
return response.Err(errDatasourceUIDHeaderMissing)
|
||||
}
|
||||
ds, err := srv.datasourceCache.GetDatasourceByUID(c.Req.Context(), datasourceUID, c.SignedInUser, c.SkipDSCache)
|
||||
if err != nil {
|
||||
logger.Error("Failed to get datasource", "datasource_uid", datasourceUID, "error", err)
|
||||
return errorToResponse(err)
|
||||
}
|
||||
|
||||
group, err := srv.convertToGrafanaRuleGroup(c, ds, ns.UID, promGroup, logger)
|
||||
if err != nil {
|
||||
return errorToResponse(err)
|
||||
}
|
||||
|
||||
err = srv.alertRuleService.ReplaceRuleGroup(c.Req.Context(), c.SignedInUser, *group, models.ProvenanceConvertedPrometheus)
|
||||
if err != nil {
|
||||
logger.Error("Failed to replace rule group", "error", err)
|
||||
return errorToResponse(err)
|
||||
}
|
||||
|
||||
return response.JSON(http.StatusAccepted, map[string]string{"status": "success"})
|
||||
}
|
||||
|
||||
func (srv *ConvertPrometheusSrv) getOrCreateNamespace(c *contextmodel.ReqContext, title string, logger log.Logger) (*folder.Folder, response.Response) {
|
||||
logger.Debug("Getting or creating a new folder")
|
||||
|
||||
ns, err := srv.ruleStore.GetOrCreateNamespaceInRootByTitle(
|
||||
c.Req.Context(),
|
||||
title,
|
||||
c.SignedInUser.GetOrgID(),
|
||||
c.SignedInUser,
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error("Failed to get or create a new folder", "error", err)
|
||||
return nil, toNamespaceErrorResponse(err)
|
||||
}
|
||||
|
||||
logger.Debug("Using folder for the converted rules", "folder_uid", ns.UID)
|
||||
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
func (srv *ConvertPrometheusSrv) convertToGrafanaRuleGroup(c *contextmodel.ReqContext, ds *datasources.DataSource, namespaceUID string, promGroup apimodels.PrometheusRuleGroup, logger log.Logger) (*models.AlertRuleGroup, error) {
|
||||
logger.Info("Converting Prometheus rules to Grafana rules", "rules", len(promGroup.Rules), "folder_uid", namespaceUID, "datasource_uid", ds.UID, "datasource_type", ds.Type)
|
||||
|
||||
rules := make([]prom.PrometheusRule, len(promGroup.Rules))
|
||||
for i, r := range promGroup.Rules {
|
||||
rules[i] = prom.PrometheusRule{
|
||||
Alert: r.Alert,
|
||||
Expr: r.Expr,
|
||||
For: r.For,
|
||||
KeepFiringFor: r.KeepFiringFor,
|
||||
Labels: r.Labels,
|
||||
Annotations: r.Annotations,
|
||||
Record: r.Record,
|
||||
}
|
||||
}
|
||||
group := prom.PrometheusRuleGroup{
|
||||
Name: promGroup.Name,
|
||||
Interval: promGroup.Interval,
|
||||
Rules: rules,
|
||||
}
|
||||
|
||||
pauseRecordingRules, err := parseBooleanHeader(c.Req.Header.Get(recordingRulesPausedHeader), recordingRulesPausedHeader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pauseAlertRules, err := parseBooleanHeader(c.Req.Header.Get(alertRulesPausedHeader), alertRulesPausedHeader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
converter, err := prom.NewConverter(
|
||||
prom.Config{
|
||||
DatasourceUID: ds.UID,
|
||||
DatasourceType: ds.Type,
|
||||
DefaultInterval: srv.cfg.DefaultRuleEvaluationInterval,
|
||||
RecordingRules: prom.RulesConfig{
|
||||
IsPaused: pauseRecordingRules,
|
||||
},
|
||||
AlertRules: prom.RulesConfig{
|
||||
IsPaused: pauseAlertRules,
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error("Failed to create Prometheus converter", "datasource_uid", ds.UID, "datasource_type", ds.Type, "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
grafanaGroup, err := converter.PrometheusRulesToGrafana(c.SignedInUser.GetOrgID(), namespaceUID, group)
|
||||
if err != nil {
|
||||
logger.Error("Failed to convert Prometheus rules to Grafana rules", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return grafanaGroup, nil
|
||||
}
|
||||
|
||||
// parseBooleanHeader parses a boolean header value, returning an error if the header
|
||||
// is present but invalid. If the header is not present, returns (false, nil).
|
||||
func parseBooleanHeader(header string, headerName string) (bool, error) {
|
||||
if header == "" {
|
||||
return false, nil
|
||||
}
|
||||
val, err := strconv.ParseBool(header)
|
||||
if err != nil {
|
||||
return false, errInvalidHeaderValue(headerName)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
prommodel "github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
dsfakes "github.com/grafana/grafana/pkg/services/datasources/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
||||
acfakes "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol/fakes"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
const (
|
||||
existingDSUID = "test-ds"
|
||||
)
|
||||
|
||||
func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) {
|
||||
simpleGroup := apimodels.PrometheusRuleGroup{
|
||||
Name: "Test Group",
|
||||
Interval: prommodel.Duration(1 * time.Minute),
|
||||
Rules: []apimodels.PrometheusRule{
|
||||
{
|
||||
Alert: "TestAlert",
|
||||
Expr: "up == 0",
|
||||
For: util.Pointer(prommodel.Duration(5 * time.Minute)),
|
||||
Labels: map[string]string{
|
||||
"severity": "critical",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("without datasource UID header should return 400", func(t *testing.T) {
|
||||
srv, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(datasourceUIDHeader, "")
|
||||
|
||||
response := srv.RouteConvertPrometheusPostRuleGroup(rc, "test", apimodels.PrometheusRuleGroup{})
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, response.Status())
|
||||
require.Contains(t, string(response.Body()), "Missing datasource UID header")
|
||||
})
|
||||
|
||||
t.Run("with invalid datasource should return error", func(t *testing.T) {
|
||||
srv, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(datasourceUIDHeader, "non-existing-ds")
|
||||
|
||||
response := srv.RouteConvertPrometheusPostRuleGroup(rc, "test", apimodels.PrometheusRuleGroup{})
|
||||
|
||||
require.Equal(t, http.StatusNotFound, response.Status())
|
||||
})
|
||||
|
||||
t.Run("with rule group without evaluation interval should return 202", func(t *testing.T) {
|
||||
srv, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
|
||||
response := srv.RouteConvertPrometheusPostRuleGroup(rc, "test", simpleGroup)
|
||||
require.Equal(t, http.StatusAccepted, response.Status())
|
||||
})
|
||||
|
||||
t.Run("with valid pause header values should return 202", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
headerName string
|
||||
headerValue string
|
||||
}{
|
||||
{
|
||||
name: "true recording rules pause value",
|
||||
headerName: recordingRulesPausedHeader,
|
||||
headerValue: "true",
|
||||
},
|
||||
{
|
||||
name: "false recording rules pause value",
|
||||
headerName: recordingRulesPausedHeader,
|
||||
headerValue: "false",
|
||||
},
|
||||
{
|
||||
name: "true alert rules pause value",
|
||||
headerName: alertRulesPausedHeader,
|
||||
headerValue: "true",
|
||||
},
|
||||
{
|
||||
name: "false alert rules pause value",
|
||||
headerName: alertRulesPausedHeader,
|
||||
headerValue: "false",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
srv, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(tc.headerName, tc.headerValue)
|
||||
|
||||
response := srv.RouteConvertPrometheusPostRuleGroup(rc, "test", simpleGroup)
|
||||
require.Equal(t, http.StatusAccepted, response.Status())
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("with invalid pause header values should return 400", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
headerName string
|
||||
headerValue string
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "invalid recording rules pause value",
|
||||
headerName: recordingRulesPausedHeader,
|
||||
headerValue: "invalid",
|
||||
expectedError: "Invalid value for header X-Grafana-Alerting-Recording-Rules-Paused: must be 'true' or 'false'",
|
||||
},
|
||||
{
|
||||
name: "invalid alert rules pause value",
|
||||
headerName: alertRulesPausedHeader,
|
||||
headerValue: "invalid",
|
||||
expectedError: "Invalid value for header X-Grafana-Alerting-Alert-Rules-Paused: must be 'true' or 'false'",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
srv, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
rc.Req.Header.Set(tc.headerName, tc.headerValue)
|
||||
|
||||
response := srv.RouteConvertPrometheusPostRuleGroup(rc, "test", simpleGroup)
|
||||
require.Equal(t, http.StatusBadRequest, response.Status())
|
||||
require.Contains(t, string(response.Body()), tc.expectedError)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("with valid request should return 202", func(t *testing.T) {
|
||||
srv, _ := createConvertPrometheusSrv(t)
|
||||
rc := createRequestCtx()
|
||||
|
||||
response := srv.RouteConvertPrometheusPostRuleGroup(rc, "test", simpleGroup)
|
||||
require.Equal(t, http.StatusAccepted, response.Status())
|
||||
})
|
||||
}
|
||||
|
||||
func createConvertPrometheusSrv(t *testing.T) (*ConvertPrometheusSrv, datasources.CacheService) {
|
||||
t.Helper()
|
||||
|
||||
ruleStore := fakes.NewRuleStore(t)
|
||||
folder := randFolder()
|
||||
ruleStore.Folders[1] = append(ruleStore.Folders[1], folder)
|
||||
|
||||
dsCache := &dsfakes.FakeCacheService{}
|
||||
ds := &datasources.DataSource{
|
||||
UID: existingDSUID,
|
||||
Type: datasources.DS_PROMETHEUS,
|
||||
}
|
||||
dsCache.DataSources = append(dsCache.DataSources, ds)
|
||||
|
||||
quotas := &provisioning.MockQuotaChecker{}
|
||||
quotas.EXPECT().LimitOK()
|
||||
|
||||
folderService := foldertest.NewFakeService()
|
||||
|
||||
alertRuleService := provisioning.NewAlertRuleService(
|
||||
ruleStore,
|
||||
fakes.NewFakeProvisioningStore(),
|
||||
folderService,
|
||||
quotas,
|
||||
&provisioning.NopTransactionManager{},
|
||||
60,
|
||||
10,
|
||||
100,
|
||||
log.New("test"),
|
||||
&provisioning.NotificationSettingsValidatorProviderFake{},
|
||||
&acfakes.FakeRuleService{},
|
||||
)
|
||||
|
||||
cfg := &setting.UnifiedAlertingSettings{
|
||||
DefaultRuleEvaluationInterval: 1 * time.Minute,
|
||||
}
|
||||
|
||||
srv := NewConvertPrometheusSrv(cfg, log.NewNopLogger(), ruleStore, dsCache, alertRuleService)
|
||||
|
||||
return srv, dsCache
|
||||
}
|
||||
|
||||
func createRequestCtx() *contextmodel.ReqContext {
|
||||
req := httptest.NewRequest("GET", "http://localhost", nil)
|
||||
req.Header.Set(datasourceUIDHeader, existingDSUID)
|
||||
|
||||
return &contextmodel.ReqContext{
|
||||
Context: &web.Context{
|
||||
Req: req,
|
||||
Resp: web.NewResponseWriter("GET", httptest.NewRecorder()),
|
||||
},
|
||||
SignedInUser: &user.SignedInUser{OrgID: 1},
|
||||
}
|
||||
}
|
||||
@@ -132,23 +132,26 @@ func (api *API) authorize(method, path string) web.Handler {
|
||||
)
|
||||
|
||||
case http.MethodGet + "/api/convert/prometheus/config/v1/rules":
|
||||
eval = ac.EvalPermission(ac.ActionAlertingRuleRead)
|
||||
eval = ac.EvalAll(
|
||||
ac.EvalPermission(ac.ActionAlertingRuleRead),
|
||||
ac.EvalPermission(dashboards.ActionFoldersRead),
|
||||
)
|
||||
|
||||
case http.MethodPost + "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}":
|
||||
eval = ac.EvalAll(
|
||||
ac.EvalPermission(dashboards.ActionFoldersWrite),
|
||||
ac.EvalPermission(ac.ActionAlertingRuleRead),
|
||||
ac.EvalPermission(ac.ActionAlertingRuleUpdate),
|
||||
ac.EvalPermission(ac.ActionAlertingRuleCreate),
|
||||
ac.EvalPermission(ac.ActionAlertingRuleDelete),
|
||||
ac.EvalPermission(ac.ActionAlertingProvisioningSetStatus),
|
||||
)
|
||||
|
||||
case http.MethodDelete + "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}/{Group}",
|
||||
http.MethodDelete + "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}":
|
||||
eval = ac.EvalAll(
|
||||
ac.EvalPermission(ac.ActionAlertingRuleDelete),
|
||||
ac.EvalPermission(ac.ActionAlertingRuleRead),
|
||||
ac.EvalPermission(dashboards.ActionFoldersRead),
|
||||
eval = ac.EvalAny(
|
||||
ac.EvalAll(
|
||||
ac.EvalPermission(ac.ActionAlertingRuleRead),
|
||||
ac.EvalPermission(dashboards.ActionFoldersRead),
|
||||
ac.EvalPermission(ac.ActionAlertingRuleDelete),
|
||||
ac.EvalPermission(ac.ActionAlertingProvisioningSetStatus),
|
||||
),
|
||||
)
|
||||
|
||||
// Alert Instances and Silences
|
||||
|
||||
@@ -15,6 +15,8 @@ type RuleStore interface {
|
||||
// by returning map[string]struct{} instead of map[string]*folder.Folder
|
||||
GetUserVisibleNamespaces(context.Context, int64, identity.Requester) (map[string]*folder.Folder, error)
|
||||
GetNamespaceByUID(ctx context.Context, uid string, orgID int64, user identity.Requester) (*folder.Folder, error)
|
||||
GetNamespaceInRootByTitle(ctx context.Context, fullpath string, orgID int64, user identity.Requester) (*folder.Folder, error)
|
||||
GetOrCreateNamespaceInRootByTitle(ctx context.Context, title string, orgID int64, user identity.Requester) (*folder.Folder, error)
|
||||
|
||||
GetAlertRuleByUID(ctx context.Context, query *ngmodels.GetAlertRuleByUIDQuery) (*ngmodels.AlertRule, error)
|
||||
GetAlertRulesGroupByRuleUID(ctx context.Context, query *ngmodels.GetAlertRulesGroupByRuleUIDQuery) ([]*ngmodels.AlertRule, error)
|
||||
|
||||
@@ -4493,6 +4493,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"URL": {
|
||||
"description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nThe Host field contains the host and port subcomponents of the URL.\nWhen the port is present, it is separated from the host with a colon.\nWhen the host is an IPv6 address, it must be enclosed in square brackets:\n\"[fe80::1]:80\". The [net.JoinHostPort] function combines a host and port\ninto a string suitable for the Host field, adding square brackets to\nthe host when necessary.\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use the [URL.EscapedPath] method, which preserves\nthe original encoding of Path.\n\nThe RawPath field is an optional field which is only set when the default\nencoding of Path is different from the escaped path. See the EscapedPath method\nfor more details.\n\nURL's String method uses the EscapedPath method to obtain the path.",
|
||||
"properties": {
|
||||
"ForceQuery": {
|
||||
"type": "boolean"
|
||||
@@ -4528,7 +4529,7 @@
|
||||
"$ref": "#/definitions/Userinfo"
|
||||
}
|
||||
},
|
||||
"title": "URL is a custom URL type that allows validation at configuration load time.",
|
||||
"title": "A URL represents a parsed URL (technically, a URI reference).",
|
||||
"type": "object"
|
||||
},
|
||||
"UpdateRuleGroupResponse": {
|
||||
@@ -4931,7 +4932,6 @@
|
||||
"type": "object"
|
||||
},
|
||||
"gettableAlerts": {
|
||||
"description": "GettableAlerts gettable alerts",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gettableAlert",
|
||||
"type": "object"
|
||||
@@ -5056,7 +5056,6 @@
|
||||
"type": "object"
|
||||
},
|
||||
"gettableSilences": {
|
||||
"description": "GettableSilences gettable silences",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gettableSilence",
|
||||
"type": "object"
|
||||
|
||||
@@ -84,11 +84,11 @@ type RouteConvertPrometheusPostRuleGroupParams struct {
|
||||
// in: path
|
||||
NamespaceTitle string
|
||||
// in: header
|
||||
DatasourceUID string `json:"x-datasource-uid"`
|
||||
DatasourceUID string `json:"x-grafana-alerting-datasource-uid"`
|
||||
// in: header
|
||||
RecordingRulesPaused bool `json:"x-recording-rules-paused"`
|
||||
RecordingRulesPaused bool `json:"x-grafana-alerting-recording-rules-paused"`
|
||||
// in: header
|
||||
AlertRulesPaused bool `json:"x-alert-rules-paused"`
|
||||
AlertRulesPaused bool `json:"x-grafana-alerting-alert-rules-paused"`
|
||||
// in:body
|
||||
Body PrometheusRuleGroup
|
||||
}
|
||||
|
||||
@@ -4932,6 +4932,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"gettableAlerts": {
|
||||
"description": "GettableAlerts gettable alerts",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gettableAlert",
|
||||
"type": "object"
|
||||
@@ -6515,17 +6516,17 @@
|
||||
},
|
||||
{
|
||||
"in": "header",
|
||||
"name": "x-datasource-uid",
|
||||
"name": "x-grafana-alerting-datasource-uid",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "header",
|
||||
"name": "x-recording-rules-paused",
|
||||
"name": "x-grafana-alerting-recording-rules-paused",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"in": "header",
|
||||
"name": "x-alert-rules-paused",
|
||||
"name": "x-grafana-alerting-alert-rules-paused",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1194,17 +1194,17 @@
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "x-datasource-uid",
|
||||
"name": "x-grafana-alerting-datasource-uid",
|
||||
"in": "header"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"name": "x-recording-rules-paused",
|
||||
"name": "x-grafana-alerting-recording-rules-paused",
|
||||
"in": "header"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"name": "x-alert-rules-paused",
|
||||
"name": "x-grafana-alerting-alert-rules-paused",
|
||||
"in": "header"
|
||||
},
|
||||
{
|
||||
@@ -8872,6 +8872,7 @@
|
||||
}
|
||||
},
|
||||
"gettableAlerts": {
|
||||
"description": "GettableAlerts gettable alerts",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
|
||||
Reference in New Issue
Block a user