diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index cbe09d02910..8e955663673 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -253,6 +253,7 @@ export interface FeatureToggles { exploreMetricsUseExternalAppPlugin?: boolean; datasourceConnectionsTab?: boolean; fetchRulesUsingPost?: boolean; + alertingConversionAPI?: boolean; alertingAlertmanagerExtraDedupStage?: boolean; alertingAlertmanagerExtraDedupStageStopPipeline?: boolean; newLogsPanel?: boolean; diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 81dda206467..879c3153378 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1760,6 +1760,14 @@ var ( HideFromAdminPage: true, HideFromDocs: true, }, + { + Name: "alertingConversionAPI", + Description: "Enable the alerting conversion API", + Stage: FeatureStageExperimental, + Owner: grafanaAlertingSquad, + HideFromAdminPage: true, + HideFromDocs: true, + }, { Name: "alertingAlertmanagerExtraDedupStage", Description: "enables extra deduplication stage in alertmanager that checks that timestamps of the pipeline and the current state are matching", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index d21b4c4fded..a3694fa49ae 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -234,6 +234,7 @@ elasticsearchImprovedParsing,experimental,@grafana/aws-datasources,false,false,f exploreMetricsUseExternalAppPlugin,experimental,@grafana/observability-metrics,false,true,true datasourceConnectionsTab,experimental,@grafana/plugins-platform-backend,false,false,true fetchRulesUsingPost,experimental,@grafana/alerting-squad,false,false,false +alertingConversionAPI,experimental,@grafana/alerting-squad,false,false,false alertingAlertmanagerExtraDedupStage,experimental,@grafana/alerting-squad,false,true,false alertingAlertmanagerExtraDedupStageStopPipeline,experimental,@grafana/alerting-squad,false,true,false newLogsPanel,experimental,@grafana/observability-logs,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index bedb497d4f5..d2f0868a839 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -947,6 +947,10 @@ const ( // Use a POST request to list rules by passing down the namespaces user has access to FlagFetchRulesUsingPost = "fetchRulesUsingPost" + // FlagAlertingConversionAPI + // Enable the alerting conversion API + FlagAlertingConversionAPI = "alertingConversionAPI" + // FlagAlertingAlertmanagerExtraDedupStage // enables extra deduplication stage in alertmanager that checks that timestamps of the pipeline and the current state are matching FlagAlertingAlertmanagerExtraDedupStage = "alertingAlertmanagerExtraDedupStage" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index db65ff4fdb8..0cdcc21124f 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -215,6 +215,20 @@ "frontend": true } }, + { + "metadata": { + "name": "alertingConversionAPI", + "resourceVersion": "1739207762746", + "creationTimestamp": "2025-02-10T17:16:02Z" + }, + "spec": { + "description": "Enable the alerting conversion API", + "stage": "experimental", + "codeowner": "@grafana/alerting-squad", + "hideFromAdminPage": true, + "hideFromDocs": true + } + }, { "metadata": { "name": "alertingDisableSendAlertsExternal", diff --git a/pkg/services/ngalert/api/api.go b/pkg/services/ngalert/api/api.go index 811edf6e893..8973ca6cce7 100644 --- a/pkg/services/ngalert/api/api.go +++ b/pkg/services/ngalert/api/api.go @@ -185,4 +185,10 @@ func (api *API) RegisterAPIEndpoints(m *metrics.API) { receiverService: api.ReceiverService, muteTimingService: api.MuteTimings, }), m) + + if api.FeatureManager.IsEnabledGlobally(featuremgmt.FlagAlertingConversionAPI) { + api.RegisterConvertPrometheusApiEndpoints(NewConvertPrometheusApi(&ConvertPrometheusSrv{ + logger: logger, + }), m) + } } diff --git a/pkg/services/ngalert/api/api_convert_prometheus.go b/pkg/services/ngalert/api/api_convert_prometheus.go new file mode 100644 index 00000000000..babe4f1bca4 --- /dev/null +++ b/pkg/services/ngalert/api/api_convert_prometheus.go @@ -0,0 +1,36 @@ +package api + +import ( + "github.com/grafana/grafana/pkg/api/response" + "github.com/grafana/grafana/pkg/infra/log" + contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" + apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" +) + +type ConvertPrometheusSrv struct { + logger log.Logger +} + +func (srv *ConvertPrometheusSrv) RouteConvertPrometheusGetRules(c *contextmodel.ReqContext) response.Response { + return response.Error(501, "Not implemented", nil) +} + +func (srv *ConvertPrometheusSrv) RouteConvertPrometheusDeleteNamespace(c *contextmodel.ReqContext, namespaceTitle string) response.Response { + return response.Error(501, "Not implemented", nil) +} + +func (srv *ConvertPrometheusSrv) RouteConvertPrometheusDeleteRuleGroup(c *contextmodel.ReqContext, namespaceTitle string, group string) response.Response { + return response.Error(501, "Not implemented", nil) +} + +func (srv *ConvertPrometheusSrv) RouteConvertPrometheusGetNamespace(c *contextmodel.ReqContext, namespaceTitle string) response.Response { + return response.Error(501, "Not implemented", nil) +} + +func (srv *ConvertPrometheusSrv) RouteConvertPrometheusGetRuleGroup(c *contextmodel.ReqContext, namespaceTitle string, group string) response.Response { + return response.Error(501, "Not implemented", nil) +} + +func (srv *ConvertPrometheusSrv) RouteConvertPrometheusPostRuleGroup(c *contextmodel.ReqContext, namespaceTitle string, prometheusGroup apimodels.PrometheusRuleGroup) response.Response { + return response.Error(501, "Not implemented", nil) +} diff --git a/pkg/services/ngalert/api/authorization.go b/pkg/services/ngalert/api/authorization.go index ac5aacfd93d..155f3e0131e 100644 --- a/pkg/services/ngalert/api/authorization.go +++ b/pkg/services/ngalert/api/authorization.go @@ -123,6 +123,34 @@ func (api *API) authorize(method, path string) web.Handler { case http.MethodPost + "/api/v1/rule/test/{DatasourceUID}": eval = ac.EvalPermission(ac.ActionAlertingRuleExternalRead, datasources.ScopeProvider.GetResourceScopeUID(ac.Parameter(":DatasourceUID"))) + // convert/prometheus API paths + case http.MethodGet + "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}/{Group}", + http.MethodGet + "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}": + eval = ac.EvalAll( + ac.EvalPermission(ac.ActionAlertingRuleRead), + ac.EvalPermission(dashboards.ActionFoldersRead), + ) + + case http.MethodGet + "/api/convert/prometheus/config/v1/rules": + eval = ac.EvalPermission(ac.ActionAlertingRuleRead) + + 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), + ) + + 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), + ) + // Alert Instances and Silences // Silences for Grafana paths. diff --git a/pkg/services/ngalert/api/authorization_test.go b/pkg/services/ngalert/api/authorization_test.go index 1f83e8750cf..13b0f24c36c 100644 --- a/pkg/services/ngalert/api/authorization_test.go +++ b/pkg/services/ngalert/api/authorization_test.go @@ -41,7 +41,7 @@ func TestAuthorize(t *testing.T) { } paths[p] = methods } - require.Len(t, paths, 60) + require.Len(t, paths, 63) ac := acmock.New() api := &API{AccessControl: ac, FeatureManager: featuremgmt.WithFeatures()} diff --git a/pkg/services/ngalert/api/generated_base_api_convert_prometheus.go b/pkg/services/ngalert/api/generated_base_api_convert_prometheus.go new file mode 100644 index 00000000000..3f2e0566295 --- /dev/null +++ b/pkg/services/ngalert/api/generated_base_api_convert_prometheus.go @@ -0,0 +1,136 @@ +/*Package api contains base API implementation of unified alerting + * + *Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + * + *Do not manually edit these files, please find ngalert/api/swagger-codegen/ for commands on how to generate them. + */ +package api + +import ( + "net/http" + + "github.com/grafana/grafana/pkg/api/response" + "github.com/grafana/grafana/pkg/api/routing" + "github.com/grafana/grafana/pkg/middleware" + "github.com/grafana/grafana/pkg/middleware/requestmeta" + contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" + "github.com/grafana/grafana/pkg/services/ngalert/metrics" + "github.com/grafana/grafana/pkg/web" +) + +type ConvertPrometheusApi interface { + RouteConvertPrometheusDeleteNamespace(*contextmodel.ReqContext) response.Response + RouteConvertPrometheusDeleteRuleGroup(*contextmodel.ReqContext) response.Response + RouteConvertPrometheusGetNamespace(*contextmodel.ReqContext) response.Response + RouteConvertPrometheusGetRuleGroup(*contextmodel.ReqContext) response.Response + RouteConvertPrometheusGetRules(*contextmodel.ReqContext) response.Response + RouteConvertPrometheusPostRuleGroup(*contextmodel.ReqContext) response.Response +} + +func (f *ConvertPrometheusApiHandler) RouteConvertPrometheusDeleteNamespace(ctx *contextmodel.ReqContext) response.Response { + // Parse Path Parameters + namespaceTitleParam := web.Params(ctx.Req)[":NamespaceTitle"] + return f.handleRouteConvertPrometheusDeleteNamespace(ctx, namespaceTitleParam) +} +func (f *ConvertPrometheusApiHandler) RouteConvertPrometheusDeleteRuleGroup(ctx *contextmodel.ReqContext) response.Response { + // Parse Path Parameters + namespaceTitleParam := web.Params(ctx.Req)[":NamespaceTitle"] + groupParam := web.Params(ctx.Req)[":Group"] + return f.handleRouteConvertPrometheusDeleteRuleGroup(ctx, namespaceTitleParam, groupParam) +} +func (f *ConvertPrometheusApiHandler) RouteConvertPrometheusGetNamespace(ctx *contextmodel.ReqContext) response.Response { + // Parse Path Parameters + namespaceTitleParam := web.Params(ctx.Req)[":NamespaceTitle"] + return f.handleRouteConvertPrometheusGetNamespace(ctx, namespaceTitleParam) +} +func (f *ConvertPrometheusApiHandler) RouteConvertPrometheusGetRuleGroup(ctx *contextmodel.ReqContext) response.Response { + // Parse Path Parameters + namespaceTitleParam := web.Params(ctx.Req)[":NamespaceTitle"] + groupParam := web.Params(ctx.Req)[":Group"] + return f.handleRouteConvertPrometheusGetRuleGroup(ctx, namespaceTitleParam, groupParam) +} +func (f *ConvertPrometheusApiHandler) RouteConvertPrometheusGetRules(ctx *contextmodel.ReqContext) response.Response { + return f.handleRouteConvertPrometheusGetRules(ctx) +} +func (f *ConvertPrometheusApiHandler) RouteConvertPrometheusPostRuleGroup(ctx *contextmodel.ReqContext) response.Response { + // Parse Path Parameters + namespaceTitleParam := web.Params(ctx.Req)[":NamespaceTitle"] + return f.handleRouteConvertPrometheusPostRuleGroup(ctx, namespaceTitleParam) +} + +func (api *API) RegisterConvertPrometheusApiEndpoints(srv ConvertPrometheusApi, m *metrics.API) { + api.RouteRegister.Group("", func(group routing.RouteRegister) { + group.Delete( + toMacaronPath("/api/convert/prometheus/config/v1/rules/{NamespaceTitle}"), + requestmeta.SetOwner(requestmeta.TeamAlerting), + requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow), + api.authorize(http.MethodDelete, "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}"), + metrics.Instrument( + http.MethodDelete, + "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}", + api.Hooks.Wrap(srv.RouteConvertPrometheusDeleteNamespace), + m, + ), + ) + group.Delete( + toMacaronPath("/api/convert/prometheus/config/v1/rules/{NamespaceTitle}/{Group}"), + requestmeta.SetOwner(requestmeta.TeamAlerting), + requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow), + api.authorize(http.MethodDelete, "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}/{Group}"), + metrics.Instrument( + http.MethodDelete, + "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}/{Group}", + api.Hooks.Wrap(srv.RouteConvertPrometheusDeleteRuleGroup), + m, + ), + ) + group.Get( + toMacaronPath("/api/convert/prometheus/config/v1/rules/{NamespaceTitle}"), + requestmeta.SetOwner(requestmeta.TeamAlerting), + requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow), + api.authorize(http.MethodGet, "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}"), + metrics.Instrument( + http.MethodGet, + "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}", + api.Hooks.Wrap(srv.RouteConvertPrometheusGetNamespace), + m, + ), + ) + group.Get( + toMacaronPath("/api/convert/prometheus/config/v1/rules/{NamespaceTitle}/{Group}"), + requestmeta.SetOwner(requestmeta.TeamAlerting), + requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow), + api.authorize(http.MethodGet, "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}/{Group}"), + metrics.Instrument( + http.MethodGet, + "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}/{Group}", + api.Hooks.Wrap(srv.RouteConvertPrometheusGetRuleGroup), + m, + ), + ) + group.Get( + toMacaronPath("/api/convert/prometheus/config/v1/rules"), + requestmeta.SetOwner(requestmeta.TeamAlerting), + requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow), + api.authorize(http.MethodGet, "/api/convert/prometheus/config/v1/rules"), + metrics.Instrument( + http.MethodGet, + "/api/convert/prometheus/config/v1/rules", + api.Hooks.Wrap(srv.RouteConvertPrometheusGetRules), + m, + ), + ) + group.Post( + toMacaronPath("/api/convert/prometheus/config/v1/rules/{NamespaceTitle}"), + requestmeta.SetOwner(requestmeta.TeamAlerting), + requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow), + api.authorize(http.MethodPost, "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}"), + metrics.Instrument( + http.MethodPost, + "/api/convert/prometheus/config/v1/rules/{NamespaceTitle}", + api.Hooks.Wrap(srv.RouteConvertPrometheusPostRuleGroup), + m, + ), + ) + }, middleware.ReqSignedIn) +} diff --git a/pkg/services/ngalert/api/prometheus_conversion.go b/pkg/services/ngalert/api/prometheus_conversion.go new file mode 100644 index 00000000000..354f6de2a38 --- /dev/null +++ b/pkg/services/ngalert/api/prometheus_conversion.go @@ -0,0 +1,56 @@ +package api + +import ( + "io" + + "gopkg.in/yaml.v3" + + "github.com/grafana/grafana/pkg/api/response" + contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" + apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" +) + +type ConvertPrometheusApiHandler struct { + svc *ConvertPrometheusSrv +} + +func NewConvertPrometheusApi(svc *ConvertPrometheusSrv) *ConvertPrometheusApiHandler { + return &ConvertPrometheusApiHandler{ + svc: svc, + } +} + +func (f *ConvertPrometheusApiHandler) handleRouteConvertPrometheusGetRules(ctx *contextmodel.ReqContext) response.Response { + return f.svc.RouteConvertPrometheusGetRules(ctx) +} + +func (f *ConvertPrometheusApiHandler) handleRouteConvertPrometheusDeleteNamespace(ctx *contextmodel.ReqContext, namespaceTitle string) response.Response { + return f.svc.RouteConvertPrometheusDeleteNamespace(ctx, namespaceTitle) +} + +func (f *ConvertPrometheusApiHandler) handleRouteConvertPrometheusDeleteRuleGroup(ctx *contextmodel.ReqContext, namespaceTitle string, group string) response.Response { + return f.svc.RouteConvertPrometheusDeleteRuleGroup(ctx, namespaceTitle, group) +} + +func (f *ConvertPrometheusApiHandler) handleRouteConvertPrometheusGetNamespace(ctx *contextmodel.ReqContext, namespaceTitle string) response.Response { + return f.svc.RouteConvertPrometheusGetNamespace(ctx, namespaceTitle) +} + +func (f *ConvertPrometheusApiHandler) handleRouteConvertPrometheusGetRuleGroup(ctx *contextmodel.ReqContext, namespaceTitle string, group string) response.Response { + return f.svc.RouteConvertPrometheusGetRuleGroup(ctx, namespaceTitle, group) +} + +func (f *ConvertPrometheusApiHandler) handleRouteConvertPrometheusPostRuleGroup(ctx *contextmodel.ReqContext, namespaceTitle string) response.Response { + body, err := io.ReadAll(ctx.Req.Body) + if err != nil { + return errorToResponse(err) + } + defer func() { _ = ctx.Req.Body.Close() }() + + var promGroup apimodels.PrometheusRuleGroup + if err := yaml.Unmarshal(body, &promGroup); err != nil { + return errorToResponse(err) + } + + return f.svc.RouteConvertPrometheusPostRuleGroup(ctx, namespaceTitle, promGroup) +} diff --git a/pkg/services/ngalert/api/tooling/api.json b/pkg/services/ngalert/api/tooling/api.json index 25f7b2ff8c3..7ed5ae04ba0 100644 --- a/pkg/services/ngalert/api/tooling/api.json +++ b/pkg/services/ngalert/api/tooling/api.json @@ -734,6 +734,20 @@ }, "type": "array" }, + "ConvertPrometheusResponse": { + "properties": { + "error": { + "type": "string" + }, + "errorType": { + "type": "string" + }, + "status": { + "type": "string" + } + }, + "type": "object" + }, "CounterResetHint": { "description": "or alternatively that we are dealing with a gauge histogram, where counter resets do not apply.", "format": "uint8", @@ -2940,6 +2954,70 @@ }, "type": "object" }, + "PrometheusNamespace": { + "properties": { + "Body": { + "additionalProperties": { + "items": { + "$ref": "#/definitions/PrometheusRuleGroup" + }, + "type": "array" + }, + "description": "in: body", + "type": "object" + } + }, + "type": "object" + }, + "PrometheusRule": { + "properties": { + "Alert": { + "type": "string" + }, + "Annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "Expr": { + "type": "string" + }, + "For": { + "type": "string" + }, + "KeepFiringFor": { + "type": "string" + }, + "Labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "Record": { + "type": "string" + } + }, + "type": "object" + }, + "PrometheusRuleGroup": { + "properties": { + "Interval": { + "$ref": "#/definitions/Duration" + }, + "Name": { + "type": "string" + }, + "Rules": { + "items": { + "$ref": "#/definitions/PrometheusRule" + }, + "type": "array" + } + }, + "type": "object" + }, "Provenance": { "type": "string" }, @@ -4692,7 +4770,6 @@ "type": "object" }, "alertGroups": { - "description": "AlertGroups alert groups", "items": { "$ref": "#/definitions/alertGroup", "type": "object" diff --git a/pkg/services/ngalert/api/tooling/definitions/convert_prometheus_api.go b/pkg/services/ngalert/api/tooling/definitions/convert_prometheus_api.go new file mode 100644 index 00000000000..b2bae42eacb --- /dev/null +++ b/pkg/services/ngalert/api/tooling/definitions/convert_prometheus_api.go @@ -0,0 +1,139 @@ +package definitions + +import ( + "github.com/prometheus/common/model" +) + +// swagger:route GET /convert/prometheus/config/v1/rules convert_prometheus RouteConvertPrometheusGetRules +// +// Gets all namespaces with their rule groups in Prometheus format. +// +// Produces: +// - application/json +// +// Responses: +// 200: PrometheusNamespace +// 403: ForbiddenError +// 404: NotFound + +// swagger:route GET /convert/prometheus/config/v1/rules/{NamespaceTitle} convert_prometheus RouteConvertPrometheusGetNamespace +// +// Gets rules in prometheus format for a given namespace. +// +// Produces: +// - application/json +// +// Responses: +// 200: PrometheusNamespace +// 403: ForbiddenError +// 404: NotFound + +// swagger:route GET /convert/prometheus/config/v1/rules/{NamespaceTitle}/{Group} convert_prometheus RouteConvertPrometheusGetRuleGroup +// +// Gets a rule group in Prometheus format. +// +// Produces: +// - application/json +// +// Responses: +// 200: PrometheusRuleGroup +// 403: ForbiddenError +// 404: NotFound + +// swagger:route POST /convert/prometheus/config/v1/rules/{NamespaceTitle} convert_prometheus RouteConvertPrometheusPostRuleGroup +// +// Creates or updates a rule group in Prometheus format. +// +// Consumes: +// - application/yaml +// +// Produces: +// - application/json +// +// Responses: +// 202: ConvertPrometheusResponse +// 403: ForbiddenError +// +// Extensions: +// x-raw-request: true + +// swagger:route DELETE /convert/prometheus/config/v1/rules/{NamespaceTitle} convert_prometheus RouteConvertPrometheusDeleteNamespace +// +// Deletes all rule groups in the given namespace. +// +// Produces: +// - application/json +// +// Responses: +// 202: ConvertPrometheusResponse +// 403: ForbiddenError + +// swagger:route DELETE /convert/prometheus/config/v1/rules/{NamespaceTitle}/{Group} convert_prometheus RouteConvertPrometheusDeleteRuleGroup +// +// Deletes a rule group in Prometheus format. +// +// Produces: +// - application/json +// +// Responses: +// 202: ConvertPrometheusResponse +// 403: ForbiddenError + +// swagger:parameters RouteConvertPrometheusPostRuleGroup +type RouteConvertPrometheusPostRuleGroupParams struct { + // in: path + NamespaceTitle string + // in: header + DatasourceUID string `json:"x-datasource-uid"` + // in: header + RecordingRulesPaused bool `json:"x-recording-rules-paused"` + // in: header + AlertRulesPaused bool `json:"x-alert-rules-paused"` + // in:body + Body PrometheusRuleGroup +} + +// swagger:model +type PrometheusNamespace struct { + // in: body + Body map[string][]PrometheusRuleGroup +} + +// swagger:model +type PrometheusRuleGroup struct { + Name string `yaml:"name"` + Interval model.Duration `yaml:"interval"` + Rules []PrometheusRule `yaml:"rules"` +} + +// swagger:model +type PrometheusRule struct { + Alert string `yaml:"alert,omitempty"` + Expr string `yaml:"expr"` + For *model.Duration `yaml:"for,omitempty"` + KeepFiringFor *model.Duration `yaml:"keep_firing_for,omitempty"` + Labels map[string]string `yaml:"labels,omitempty"` + Annotations map[string]string `yaml:"annotations,omitempty"` + Record string `yaml:"record,omitempty"` +} + +// swagger:parameters RouteConvertPrometheusDeleteRuleGroup RouteConvertPrometheusGetRuleGroup +type RouteConvertPrometheusDeleteRuleGroupParams struct { + // in: path + NamespaceTitle string + // in: path + Group string +} + +// swagger:parameters RouteConvertPrometheusDeleteNamespace RouteConvertPrometheusGetNamespace +type RouteConvertPrometheusDeleteNamespaceParams struct { + // in: path + NamespaceTitle string +} + +// swagger:model +type ConvertPrometheusResponse struct { + Status string `json:"status"` + ErrorType string `json:"errorType"` + Error string `json:"error"` +} diff --git a/pkg/services/ngalert/api/tooling/post.json b/pkg/services/ngalert/api/tooling/post.json index e8c4c20d008..26e062391cf 100644 --- a/pkg/services/ngalert/api/tooling/post.json +++ b/pkg/services/ngalert/api/tooling/post.json @@ -734,6 +734,20 @@ }, "type": "array" }, + "ConvertPrometheusResponse": { + "properties": { + "error": { + "type": "string" + }, + "errorType": { + "type": "string" + }, + "status": { + "type": "string" + } + }, + "type": "object" + }, "CounterResetHint": { "description": "or alternatively that we are dealing with a gauge histogram, where counter resets do not apply.", "format": "uint8", @@ -2940,6 +2954,70 @@ }, "type": "object" }, + "PrometheusNamespace": { + "properties": { + "Body": { + "additionalProperties": { + "items": { + "$ref": "#/definitions/PrometheusRuleGroup" + }, + "type": "array" + }, + "description": "in: body", + "type": "object" + } + }, + "type": "object" + }, + "PrometheusRule": { + "properties": { + "Alert": { + "type": "string" + }, + "Annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "Expr": { + "type": "string" + }, + "For": { + "type": "string" + }, + "KeepFiringFor": { + "type": "string" + }, + "Labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "Record": { + "type": "string" + } + }, + "type": "object" + }, + "PrometheusRuleGroup": { + "properties": { + "Interval": { + "$ref": "#/definitions/Duration" + }, + "Name": { + "type": "string" + }, + "Rules": { + "items": { + "$ref": "#/definitions/PrometheusRule" + }, + "type": "array" + } + }, + "type": "object" + }, "Provenance": { "type": "string" }, @@ -4854,7 +4932,6 @@ "type": "object" }, "gettableAlerts": { - "description": "GettableAlerts gettable alerts", "items": { "$ref": "#/definitions/gettableAlert", "type": "object" @@ -4979,7 +5056,6 @@ "type": "object" }, "gettableSilences": { - "description": "GettableSilences gettable silences", "items": { "$ref": "#/definitions/gettableSilence", "type": "object" @@ -6322,6 +6398,252 @@ ] } }, + "/convert/prometheus/config/v1/rules": { + "get": { + "operationId": "RouteConvertPrometheusGetRules", + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "PrometheusNamespace", + "schema": { + "$ref": "#/definitions/PrometheusNamespace" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + }, + "404": { + "description": "NotFound", + "schema": { + "$ref": "#/definitions/NotFound" + } + } + }, + "summary": "Gets all namespaces with their rule groups in Prometheus format.", + "tags": [ + "convert_prometheus" + ] + } + }, + "/convert/prometheus/config/v1/rules/{NamespaceTitle}": { + "delete": { + "operationId": "RouteConvertPrometheusDeleteNamespace", + "parameters": [ + { + "in": "path", + "name": "NamespaceTitle", + "required": true, + "type": "string" + } + ], + "produces": [ + "application/json" + ], + "responses": { + "202": { + "description": "ConvertPrometheusResponse", + "schema": { + "$ref": "#/definitions/ConvertPrometheusResponse" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + } + }, + "summary": "Deletes all rule groups in the given namespace.", + "tags": [ + "convert_prometheus" + ] + }, + "get": { + "operationId": "RouteConvertPrometheusGetNamespace", + "parameters": [ + { + "in": "path", + "name": "NamespaceTitle", + "required": true, + "type": "string" + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "PrometheusNamespace", + "schema": { + "$ref": "#/definitions/PrometheusNamespace" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + }, + "404": { + "description": "NotFound", + "schema": { + "$ref": "#/definitions/NotFound" + } + } + }, + "summary": "Gets rules in prometheus format for a given namespace.", + "tags": [ + "convert_prometheus" + ] + }, + "post": { + "consumes": [ + "application/yaml" + ], + "operationId": "RouteConvertPrometheusPostRuleGroup", + "parameters": [ + { + "in": "path", + "name": "NamespaceTitle", + "required": true, + "type": "string" + }, + { + "in": "header", + "name": "x-datasource-uid", + "type": "string" + }, + { + "in": "header", + "name": "x-recording-rules-paused", + "type": "boolean" + }, + { + "in": "header", + "name": "x-alert-rules-paused", + "type": "boolean" + }, + { + "in": "body", + "name": "Body", + "schema": { + "$ref": "#/definitions/PrometheusRuleGroup" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "202": { + "description": "ConvertPrometheusResponse", + "schema": { + "$ref": "#/definitions/ConvertPrometheusResponse" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + } + }, + "summary": "Creates or updates a rule group in Prometheus format.", + "tags": [ + "convert_prometheus" + ], + "x-raw-request": "true" + } + }, + "/convert/prometheus/config/v1/rules/{NamespaceTitle}/{Group}": { + "delete": { + "operationId": "RouteConvertPrometheusDeleteRuleGroup", + "parameters": [ + { + "in": "path", + "name": "NamespaceTitle", + "required": true, + "type": "string" + }, + { + "in": "path", + "name": "Group", + "required": true, + "type": "string" + } + ], + "produces": [ + "application/json" + ], + "responses": { + "202": { + "description": "ConvertPrometheusResponse", + "schema": { + "$ref": "#/definitions/ConvertPrometheusResponse" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + } + }, + "summary": "Deletes a rule group in Prometheus format.", + "tags": [ + "convert_prometheus" + ] + }, + "get": { + "operationId": "RouteConvertPrometheusGetRuleGroup", + "parameters": [ + { + "in": "path", + "name": "NamespaceTitle", + "required": true, + "type": "string" + }, + { + "in": "path", + "name": "Group", + "required": true, + "type": "string" + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "PrometheusRuleGroup", + "schema": { + "$ref": "#/definitions/PrometheusRuleGroup" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + }, + "404": { + "description": "NotFound", + "schema": { + "$ref": "#/definitions/NotFound" + } + } + }, + "summary": "Gets a rule group in Prometheus format.", + "tags": [ + "convert_prometheus" + ] + } + }, "/prometheus/grafana/api/v1/alerts": { "get": { "description": "gets the current alerts", @@ -6886,6 +7208,12 @@ "schema": { "$ref": "#/definitions/ForbiddenError" } + }, + "404": { + "description": "NotFound", + "schema": { + "$ref": "#/definitions/NotFound" + } } }, "tags": [ diff --git a/pkg/services/ngalert/api/tooling/spec.json b/pkg/services/ngalert/api/tooling/spec.json index abe3364c501..f42d1f7c408 100644 --- a/pkg/services/ngalert/api/tooling/spec.json +++ b/pkg/services/ngalert/api/tooling/spec.json @@ -1102,6 +1102,252 @@ } } }, + "/convert/prometheus/config/v1/rules": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "convert_prometheus" + ], + "summary": "Gets all namespaces with their rule groups in Prometheus format.", + "operationId": "RouteConvertPrometheusGetRules", + "responses": { + "200": { + "description": "PrometheusNamespace", + "schema": { + "$ref": "#/definitions/PrometheusNamespace" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + }, + "404": { + "description": "NotFound", + "schema": { + "$ref": "#/definitions/NotFound" + } + } + } + } + }, + "/convert/prometheus/config/v1/rules/{NamespaceTitle}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "convert_prometheus" + ], + "summary": "Gets rules in prometheus format for a given namespace.", + "operationId": "RouteConvertPrometheusGetNamespace", + "parameters": [ + { + "type": "string", + "name": "NamespaceTitle", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "PrometheusNamespace", + "schema": { + "$ref": "#/definitions/PrometheusNamespace" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + }, + "404": { + "description": "NotFound", + "schema": { + "$ref": "#/definitions/NotFound" + } + } + } + }, + "post": { + "consumes": [ + "application/yaml" + ], + "produces": [ + "application/json" + ], + "tags": [ + "convert_prometheus" + ], + "summary": "Creates or updates a rule group in Prometheus format.", + "operationId": "RouteConvertPrometheusPostRuleGroup", + "parameters": [ + { + "type": "string", + "name": "NamespaceTitle", + "in": "path", + "required": true + }, + { + "type": "string", + "name": "x-datasource-uid", + "in": "header" + }, + { + "type": "boolean", + "name": "x-recording-rules-paused", + "in": "header" + }, + { + "type": "boolean", + "name": "x-alert-rules-paused", + "in": "header" + }, + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/PrometheusRuleGroup" + } + } + ], + "responses": { + "202": { + "description": "ConvertPrometheusResponse", + "schema": { + "$ref": "#/definitions/ConvertPrometheusResponse" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + } + }, + "x-raw-request": "true" + }, + "delete": { + "produces": [ + "application/json" + ], + "tags": [ + "convert_prometheus" + ], + "summary": "Deletes all rule groups in the given namespace.", + "operationId": "RouteConvertPrometheusDeleteNamespace", + "parameters": [ + { + "type": "string", + "name": "NamespaceTitle", + "in": "path", + "required": true + } + ], + "responses": { + "202": { + "description": "ConvertPrometheusResponse", + "schema": { + "$ref": "#/definitions/ConvertPrometheusResponse" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + } + } + } + }, + "/convert/prometheus/config/v1/rules/{NamespaceTitle}/{Group}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "convert_prometheus" + ], + "summary": "Gets a rule group in Prometheus format.", + "operationId": "RouteConvertPrometheusGetRuleGroup", + "parameters": [ + { + "type": "string", + "name": "NamespaceTitle", + "in": "path", + "required": true + }, + { + "type": "string", + "name": "Group", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "PrometheusRuleGroup", + "schema": { + "$ref": "#/definitions/PrometheusRuleGroup" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + }, + "404": { + "description": "NotFound", + "schema": { + "$ref": "#/definitions/NotFound" + } + } + } + }, + "delete": { + "produces": [ + "application/json" + ], + "tags": [ + "convert_prometheus" + ], + "summary": "Deletes a rule group in Prometheus format.", + "operationId": "RouteConvertPrometheusDeleteRuleGroup", + "parameters": [ + { + "type": "string", + "name": "NamespaceTitle", + "in": "path", + "required": true + }, + { + "type": "string", + "name": "Group", + "in": "path", + "required": true + } + ], + "responses": { + "202": { + "description": "ConvertPrometheusResponse", + "schema": { + "$ref": "#/definitions/ConvertPrometheusResponse" + } + }, + "403": { + "description": "ForbiddenError", + "schema": { + "$ref": "#/definitions/ForbiddenError" + } + } + } + } + }, "/prometheus/grafana/api/v1/alerts": { "get": { "description": "gets the current alerts", @@ -1633,6 +1879,12 @@ "schema": { "$ref": "#/definitions/ForbiddenError" } + }, + "404": { + "description": "NotFound", + "schema": { + "$ref": "#/definitions/NotFound" + } } } }, @@ -4420,6 +4672,20 @@ "$ref": "#/definitions/EmbeddedContactPoint" } }, + "ConvertPrometheusResponse": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "errorType": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, "CounterResetHint": { "description": "or alternatively that we are dealing with a gauge histogram, where counter resets do not apply.", "type": "integer", @@ -6628,6 +6894,70 @@ } } }, + "PrometheusNamespace": { + "type": "object", + "properties": { + "Body": { + "description": "in: body", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/definitions/PrometheusRuleGroup" + } + } + } + } + }, + "PrometheusRule": { + "type": "object", + "properties": { + "Alert": { + "type": "string" + }, + "Annotations": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Expr": { + "type": "string" + }, + "For": { + "type": "string" + }, + "KeepFiringFor": { + "type": "string" + }, + "Labels": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Record": { + "type": "string" + } + } + }, + "PrometheusRuleGroup": { + "type": "object", + "properties": { + "Interval": { + "$ref": "#/definitions/Duration" + }, + "Name": { + "type": "string" + }, + "Rules": { + "type": "array", + "items": { + "$ref": "#/definitions/PrometheusRule" + } + } + } + }, "Provenance": { "type": "string" }, @@ -8542,7 +8872,6 @@ } }, "gettableAlerts": { - "description": "GettableAlerts gettable alerts", "type": "array", "items": { "type": "object", @@ -8667,7 +8996,6 @@ } }, "gettableSilences": { - "description": "GettableSilences gettable silences", "type": "array", "items": { "type": "object", diff --git a/pkg/services/ngalert/api/tooling/swagger-codegen/templates/controller-api.mustache b/pkg/services/ngalert/api/tooling/swagger-codegen/templates/controller-api.mustache index f9902d265c7..635bcc68287 100644 --- a/pkg/services/ngalert/api/tooling/swagger-codegen/templates/controller-api.mustache +++ b/pkg/services/ngalert/api/tooling/swagger-codegen/templates/controller-api.mustache @@ -20,6 +20,14 @@ type {{classname}} interface { {{#operation}} } {{#operations}}{{#operation}} +{{#vendorExtensions.x-raw-request}} +func (f *{{classname}}Handler) {{nickname}}(ctx *contextmodel.ReqContext) response.Response { {{#hasPathParams}} + // Parse Path Parameters{{/hasPathParams}}{{#pathParams}} + {{paramName}}Param := web.Params(ctx.Req)[":{{baseName}}"]{{/pathParams}} + return f.handle{{nickname}}(ctx{{#pathParams}}, {{paramName}}Param{{/pathParams}}) +} +{{/vendorExtensions.x-raw-request}} +{{^vendorExtensions.x-raw-request}} func (f *{{classname}}Handler) {{nickname}}(ctx *contextmodel.ReqContext) response.Response { {{#hasPathParams}} // Parse Path Parameters{{/hasPathParams}}{{#pathParams}} {{paramName}}Param := web.Params(ctx.Req)[":{{baseName}}"]{{/pathParams}} @@ -31,6 +39,7 @@ func (f *{{classname}}Handler) {{nickname}}(ctx *contextmodel.ReqContext) respon } {{/bodyParams}}return f.handle{{nickname}}(ctx{{#bodyParams}}, conf{{/bodyParams}}{{#pathParams}}, {{paramName}}Param{{/pathParams}}) } +{{/vendorExtensions.x-raw-request}} {{/operation}}{{/operations}} func (api *API) Register{{classname}}Endpoints(srv {{classname}}, m *metrics.API) { diff --git a/public/api-merged.json b/public/api-merged.json index 3e2239131d4..04ffb5ef505 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -13882,6 +13882,20 @@ "$ref": "#/definitions/EmbeddedContactPoint" } }, + "ConvertPrometheusResponse": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "errorType": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, "CookiePreferences": { "type": "object", "properties": { @@ -18520,6 +18534,21 @@ } } }, + "PrometheusNamespace": { + "type": "object", + "properties": { + "Body": { + "description": "in: body", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/definitions/PrometheusRuleGroup" + } + } + } + } + }, "PrometheusRemoteWriteTargetJSON": { "type": "object", "properties": { @@ -18534,6 +18563,55 @@ } } }, + "PrometheusRule": { + "type": "object", + "properties": { + "Alert": { + "type": "string" + }, + "Annotations": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Expr": { + "type": "string" + }, + "For": { + "type": "string" + }, + "KeepFiringFor": { + "type": "string" + }, + "Labels": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Record": { + "type": "string" + } + } + }, + "PrometheusRuleGroup": { + "type": "object", + "properties": { + "Interval": { + "$ref": "#/definitions/Duration" + }, + "Name": { + "type": "string" + }, + "Rules": { + "type": "array", + "items": { + "$ref": "#/definitions/PrometheusRule" + } + } + } + }, "Provenance": { "type": "string" }, @@ -22483,7 +22561,6 @@ } }, "alertGroups": { - "description": "AlertGroups alert groups", "type": "array", "items": { "type": "object", diff --git a/public/openapi3.json b/public/openapi3.json index 89612da9cf1..3aa7a775761 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -3957,6 +3957,20 @@ }, "type": "array" }, + "ConvertPrometheusResponse": { + "properties": { + "error": { + "type": "string" + }, + "errorType": { + "type": "string" + }, + "status": { + "type": "string" + } + }, + "type": "object" + }, "CookiePreferences": { "properties": { "analytics": {}, @@ -8595,6 +8609,21 @@ }, "type": "object" }, + "PrometheusNamespace": { + "properties": { + "Body": { + "additionalProperties": { + "items": { + "$ref": "#/components/schemas/PrometheusRuleGroup" + }, + "type": "array" + }, + "description": "in: body", + "type": "object" + } + }, + "type": "object" + }, "PrometheusRemoteWriteTargetJSON": { "properties": { "data_source_uid": { @@ -8609,6 +8638,55 @@ }, "type": "object" }, + "PrometheusRule": { + "properties": { + "Alert": { + "type": "string" + }, + "Annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "Expr": { + "type": "string" + }, + "For": { + "type": "string" + }, + "KeepFiringFor": { + "type": "string" + }, + "Labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "Record": { + "type": "string" + } + }, + "type": "object" + }, + "PrometheusRuleGroup": { + "properties": { + "Interval": { + "$ref": "#/components/schemas/Duration" + }, + "Name": { + "type": "string" + }, + "Rules": { + "items": { + "$ref": "#/components/schemas/PrometheusRule" + }, + "type": "array" + } + }, + "type": "object" + }, "Provenance": { "type": "string" }, @@ -12557,7 +12635,6 @@ "type": "object" }, "alertGroups": { - "description": "AlertGroups alert groups", "items": { "$ref": "#/components/schemas/alertGroup" },