mirror of https://github.com/grafana/grafana.git
Alerting: Empty endpoint to load alertmanager config with mimirtool (#106266)
This commit is contained in:
parent
0da0fb5af1
commit
a4fa8ab891
|
|
@ -513,6 +513,10 @@ func (srv *ConvertPrometheusSrv) convertToGrafanaRuleGroup(
|
|||
return grafanaGroup, nil
|
||||
}
|
||||
|
||||
func (srv *ConvertPrometheusSrv) RouteConvertPrometheusPostAlertmanagerConfig(c *contextmodel.ReqContext, config apimodels.AlertmanagerUserConfig) response.Response {
|
||||
return response.Error(501, "Not implemented", 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) {
|
||||
|
|
|
|||
|
|
@ -147,6 +147,9 @@ func (api *API) authorize(method, path string) web.Handler {
|
|||
ac.EvalPermission(ac.ActionAlertingProvisioningSetStatus),
|
||||
)
|
||||
|
||||
case http.MethodPost + "/api/convert/api/v1/alerts":
|
||||
eval = ac.EvalPermission(ac.ActionAlertingNotificationsWrite)
|
||||
|
||||
// Alert Instances and Silences
|
||||
|
||||
// Silences for Grafana paths.
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ func TestAuthorize(t *testing.T) {
|
|||
}
|
||||
paths[p] = methods
|
||||
}
|
||||
require.Len(t, paths, 63)
|
||||
require.Len(t, paths, 64)
|
||||
|
||||
ac := acmock.New()
|
||||
api := &API{AccessControl: ac, FeatureManager: featuremgmt.WithFeatures()}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ type ConvertPrometheusApi interface {
|
|||
RouteConvertPrometheusGetNamespace(*contextmodel.ReqContext) response.Response
|
||||
RouteConvertPrometheusGetRuleGroup(*contextmodel.ReqContext) response.Response
|
||||
RouteConvertPrometheusGetRules(*contextmodel.ReqContext) response.Response
|
||||
RouteConvertPrometheusPostAlertmanagerConfig(*contextmodel.ReqContext) response.Response
|
||||
RouteConvertPrometheusPostRuleGroup(*contextmodel.ReqContext) response.Response
|
||||
RouteConvertPrometheusPostRuleGroups(*contextmodel.ReqContext) response.Response
|
||||
}
|
||||
|
|
@ -93,6 +94,9 @@ func (f *ConvertPrometheusApiHandler) RouteConvertPrometheusGetRuleGroup(ctx *co
|
|||
func (f *ConvertPrometheusApiHandler) RouteConvertPrometheusGetRules(ctx *contextmodel.ReqContext) response.Response {
|
||||
return f.handleRouteConvertPrometheusGetRules(ctx)
|
||||
}
|
||||
func (f *ConvertPrometheusApiHandler) RouteConvertPrometheusPostAlertmanagerConfig(ctx *contextmodel.ReqContext) response.Response {
|
||||
return f.handleRouteConvertPrometheusPostAlertmanagerConfig(ctx)
|
||||
}
|
||||
func (f *ConvertPrometheusApiHandler) RouteConvertPrometheusPostRuleGroup(ctx *contextmodel.ReqContext) response.Response {
|
||||
// Parse Path Parameters
|
||||
namespaceTitleParam := web.Params(ctx.Req)[":NamespaceTitle"]
|
||||
|
|
@ -248,6 +252,18 @@ func (api *API) RegisterConvertPrometheusApiEndpoints(srv ConvertPrometheusApi,
|
|||
m,
|
||||
),
|
||||
)
|
||||
group.Post(
|
||||
toMacaronPath("/api/convert/api/v1/alerts"),
|
||||
requestmeta.SetOwner(requestmeta.TeamAlerting),
|
||||
requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow),
|
||||
api.authorize(http.MethodPost, "/api/convert/api/v1/alerts"),
|
||||
metrics.Instrument(
|
||||
http.MethodPost,
|
||||
"/api/convert/api/v1/alerts",
|
||||
api.Hooks.Wrap(srv.RouteConvertPrometheusPostAlertmanagerConfig),
|
||||
m,
|
||||
),
|
||||
)
|
||||
group.Post(
|
||||
toMacaronPath("/api/convert/prometheus/config/v1/rules/{NamespaceTitle}"),
|
||||
requestmeta.SetOwner(requestmeta.TeamAlerting),
|
||||
|
|
|
|||
|
|
@ -15,6 +15,38 @@ import (
|
|||
|
||||
var errorUnsupportedMediaType = errutil.UnsupportedMediaType("alerting.unsupportedMediaType")
|
||||
|
||||
// parseJSONOrYAML unmarshals body into target based on content-type, defaulting to YAML
|
||||
func parseJSONOrYAML(ctx *contextmodel.ReqContext, target interface{}) error {
|
||||
var m string
|
||||
|
||||
body, err := io.ReadAll(ctx.Req.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = ctx.Req.Body.Close() }()
|
||||
|
||||
contentType := ctx.Req.Header.Get("content-type")
|
||||
|
||||
// Parse content-type only if it's not empty,
|
||||
// otherwise we'll assume it's yaml
|
||||
if contentType != "" {
|
||||
m, _, err = mime.ParseMediaType(contentType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
switch m {
|
||||
case "application/yaml", "":
|
||||
// mimirtool does not send content-type, so if it's empty, we assume it's yaml
|
||||
return yaml.Unmarshal(body, target)
|
||||
case "application/json":
|
||||
return json.Unmarshal(body, target)
|
||||
default:
|
||||
return errorUnsupportedMediaType.Errorf("unsupported media type: %s, only application/yaml and application/json are supported", m)
|
||||
}
|
||||
}
|
||||
|
||||
type ConvertPrometheusApiHandler struct {
|
||||
svc *ConvertPrometheusSrv
|
||||
}
|
||||
|
|
@ -47,75 +79,18 @@ func (f *ConvertPrometheusApiHandler) handleRouteConvertPrometheusGetRuleGroup(c
|
|||
}
|
||||
|
||||
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
|
||||
var m string
|
||||
|
||||
// Parse content-type only if it's not empty,
|
||||
// otherwise we'll assume it's yaml
|
||||
contentType := ctx.Req.Header.Get("content-type")
|
||||
if contentType != "" {
|
||||
m, _, err = mime.ParseMediaType(contentType)
|
||||
if err != nil {
|
||||
return errorToResponse(err)
|
||||
}
|
||||
}
|
||||
|
||||
switch m {
|
||||
case "application/yaml", "":
|
||||
// mimirtool does not send content-type, so if it's empty, we assume it's yaml
|
||||
if err := yaml.Unmarshal(body, &promGroup); err != nil {
|
||||
return errorToResponse(err)
|
||||
}
|
||||
case "application/json":
|
||||
if err := json.Unmarshal(body, &promGroup); err != nil {
|
||||
return errorToResponse(err)
|
||||
}
|
||||
default:
|
||||
return errorToResponse(errorUnsupportedMediaType.Errorf("unsupported media type: %s, only application/yaml and application/json are supported", m))
|
||||
if err := parseJSONOrYAML(ctx, &promGroup); err != nil {
|
||||
return errorToResponse(err)
|
||||
}
|
||||
|
||||
return f.svc.RouteConvertPrometheusPostRuleGroup(ctx, namespaceTitle, promGroup)
|
||||
}
|
||||
|
||||
func (f *ConvertPrometheusApiHandler) handleRouteConvertPrometheusPostRuleGroups(ctx *contextmodel.ReqContext) response.Response {
|
||||
body, err := io.ReadAll(ctx.Req.Body)
|
||||
if err != nil {
|
||||
return errorToResponse(err)
|
||||
}
|
||||
defer func() { _ = ctx.Req.Body.Close() }()
|
||||
|
||||
var m string
|
||||
|
||||
// Parse content-type only if it's not empty,
|
||||
// otherwise we'll assume it's yaml
|
||||
contentType := ctx.Req.Header.Get("content-type")
|
||||
if contentType != "" {
|
||||
m, _, err = mime.ParseMediaType(contentType)
|
||||
if err != nil {
|
||||
return errorToResponse(err)
|
||||
}
|
||||
}
|
||||
|
||||
var promNamespaces map[string][]apimodels.PrometheusRuleGroup
|
||||
|
||||
switch m {
|
||||
case "application/yaml", "":
|
||||
// mimirtool does not send content-type, so if it's empty, we assume it's yaml
|
||||
if err := yaml.Unmarshal(body, &promNamespaces); err != nil {
|
||||
return errorToResponse(err)
|
||||
}
|
||||
case "application/json":
|
||||
if err := json.Unmarshal(body, &promNamespaces); err != nil {
|
||||
return errorToResponse(err)
|
||||
}
|
||||
default:
|
||||
return errorToResponse(errorUnsupportedMediaType.Errorf("unsupported media type: %s, only application/yaml and application/json are supported", m))
|
||||
if err := parseJSONOrYAML(ctx, &promNamespaces); err != nil {
|
||||
return errorToResponse(err)
|
||||
}
|
||||
|
||||
return f.svc.RouteConvertPrometheusPostRuleGroups(ctx, promNamespaces)
|
||||
|
|
@ -149,3 +124,13 @@ func (f *ConvertPrometheusApiHandler) handleRouteConvertPrometheusCortexPostRule
|
|||
func (f *ConvertPrometheusApiHandler) handleRouteConvertPrometheusCortexPostRuleGroups(ctx *contextmodel.ReqContext) response.Response {
|
||||
return f.handleRouteConvertPrometheusPostRuleGroups(ctx)
|
||||
}
|
||||
|
||||
// alertmanager
|
||||
func (f *ConvertPrometheusApiHandler) handleRouteConvertPrometheusPostAlertmanagerConfig(ctx *contextmodel.ReqContext) response.Response {
|
||||
var config apimodels.AlertmanagerUserConfig
|
||||
if err := parseJSONOrYAML(ctx, &config); err != nil {
|
||||
return errorToResponse(err)
|
||||
}
|
||||
|
||||
return f.svc.RouteConvertPrometheusPostAlertmanagerConfig(ctx, config)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -576,6 +576,14 @@
|
|||
},
|
||||
"type": "object"
|
||||
},
|
||||
"AlertmanagerUserConfig": {
|
||||
"properties": {
|
||||
"alertmanager_config": {
|
||||
"$ref": "#/definitions/Config"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ApiRuleNode": {
|
||||
"properties": {
|
||||
"alert": {
|
||||
|
|
@ -3666,7 +3674,6 @@
|
|||
"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": {
|
||||
|
|
@ -3708,12 +3715,6 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"object_matchers": {
|
||||
"$ref": "#/definitions/ObjectMatchers"
|
||||
},
|
||||
"provenance": {
|
||||
"$ref": "#/definitions/Provenance"
|
||||
},
|
||||
"receiver": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -3727,6 +3728,7 @@
|
|||
"type": "array"
|
||||
}
|
||||
},
|
||||
"title": "A Route is a node that contains definitions of how to handle alerts.",
|
||||
"type": "object"
|
||||
},
|
||||
"RouteExport": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package definitions
|
||||
|
||||
import (
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
|
|
@ -200,6 +201,21 @@ import (
|
|||
// 202: ConvertPrometheusResponse
|
||||
// 403: ForbiddenError
|
||||
|
||||
// Route for `mimirtool alertmanager load`
|
||||
// swagger:route POST /convert/api/v1/alerts convert_prometheus RouteConvertPrometheusPostAlertmanagerConfig
|
||||
//
|
||||
// Load Alertmanager configuration to Grafana and merge it with the existing configuration.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Responses:
|
||||
// 202: ConvertPrometheusResponse
|
||||
// 403: ForbiddenError
|
||||
//
|
||||
// Extensions:
|
||||
// x-raw-request: true
|
||||
|
||||
// swagger:parameters RouteConvertPrometheusPostRuleGroup RouteConvertPrometheusCortexPostRuleGroup
|
||||
type RouteConvertPrometheusPostRuleGroupParams struct {
|
||||
// in: path
|
||||
|
|
@ -267,3 +283,14 @@ type ConvertPrometheusResponse struct {
|
|||
ErrorType string `json:"errorType"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// swagger:parameters RouteConvertPrometheusPostAlertmanagerConfig
|
||||
type RouteConvertPrometheusPostAlertmanagerConfigParams struct {
|
||||
// in:body
|
||||
Body AlertmanagerUserConfig
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type AlertmanagerUserConfig struct {
|
||||
AlertmanagerConfig config.Config `yaml:"alertmanager_config" json:"alertmanager_config"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -576,6 +576,14 @@
|
|||
},
|
||||
"type": "object"
|
||||
},
|
||||
"AlertmanagerUserConfig": {
|
||||
"properties": {
|
||||
"alertmanager_config": {
|
||||
"$ref": "#/definitions/Config"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ApiRuleNode": {
|
||||
"properties": {
|
||||
"alert": {
|
||||
|
|
@ -3666,7 +3674,6 @@
|
|||
"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": {
|
||||
|
|
@ -3708,12 +3715,6 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"object_matchers": {
|
||||
"$ref": "#/definitions/ObjectMatchers"
|
||||
},
|
||||
"provenance": {
|
||||
"$ref": "#/definitions/Provenance"
|
||||
},
|
||||
"receiver": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -3727,6 +3728,7 @@
|
|||
"type": "array"
|
||||
}
|
||||
},
|
||||
"title": "A Route is a node that contains definitions of how to handle alerts.",
|
||||
"type": "object"
|
||||
},
|
||||
"RouteExport": {
|
||||
|
|
@ -6820,6 +6822,42 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"/convert/api/v1/alerts": {
|
||||
"post": {
|
||||
"operationId": "RouteConvertPrometheusPostAlertmanagerConfig",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "body",
|
||||
"name": "Body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/AlertmanagerUserConfig"
|
||||
}
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "ConvertPrometheusResponse",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ConvertPrometheusResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": "Load Alertmanager configuration to Grafana and merge it with the existing configuration.",
|
||||
"tags": [
|
||||
"convert_prometheus"
|
||||
],
|
||||
"x-raw-request": "true"
|
||||
}
|
||||
},
|
||||
"/convert/prometheus/config/v1/rules": {
|
||||
"get": {
|
||||
"operationId": "RouteConvertPrometheusGetRules",
|
||||
|
|
|
|||
|
|
@ -1360,6 +1360,42 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/convert/api/v1/alerts": {
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"convert_prometheus"
|
||||
],
|
||||
"summary": "Load Alertmanager configuration to Grafana and merge it with the existing configuration.",
|
||||
"operationId": "RouteConvertPrometheusPostAlertmanagerConfig",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/AlertmanagerUserConfig"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "ConvertPrometheusResponse",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ConvertPrometheusResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-raw-request": "true"
|
||||
}
|
||||
},
|
||||
"/convert/prometheus/config/v1/rules": {
|
||||
"get": {
|
||||
"produces": [
|
||||
|
|
@ -4747,6 +4783,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"AlertmanagerUserConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"alertmanager_config": {
|
||||
"$ref": "#/definitions/Config"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ApiRuleNode": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -7839,8 +7883,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",
|
||||
|
|
@ -7882,12 +7926,6 @@
|
|||
"type": "string"
|
||||
}
|
||||
},
|
||||
"object_matchers": {
|
||||
"$ref": "#/definitions/ObjectMatchers"
|
||||
},
|
||||
"provenance": {
|
||||
"$ref": "#/definitions/Provenance"
|
||||
},
|
||||
"receiver": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -12994,6 +12994,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"AlertmanagerUserConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"alertmanager_config": {
|
||||
"$ref": "#/definitions/Config"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Annotation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -20019,8 +20027,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",
|
||||
|
|
@ -20062,12 +20070,6 @@
|
|||
"type": "string"
|
||||
}
|
||||
},
|
||||
"object_matchers": {
|
||||
"$ref": "#/definitions/ObjectMatchers"
|
||||
},
|
||||
"provenance": {
|
||||
"$ref": "#/definitions/Provenance"
|
||||
},
|
||||
"receiver": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3044,6 +3044,14 @@
|
|||
},
|
||||
"type": "object"
|
||||
},
|
||||
"AlertmanagerUserConfig": {
|
||||
"properties": {
|
||||
"alertmanager_config": {
|
||||
"$ref": "#/components/schemas/Config"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Annotation": {
|
||||
"properties": {
|
||||
"alertId": {
|
||||
|
|
@ -10069,7 +10077,6 @@
|
|||
"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": {
|
||||
|
|
@ -10111,12 +10118,6 @@
|
|||
},
|
||||
"type": "array"
|
||||
},
|
||||
"object_matchers": {
|
||||
"$ref": "#/components/schemas/ObjectMatchers"
|
||||
},
|
||||
"provenance": {
|
||||
"$ref": "#/components/schemas/Provenance"
|
||||
},
|
||||
"receiver": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
@ -10130,6 +10131,7 @@
|
|||
"type": "array"
|
||||
}
|
||||
},
|
||||
"title": "A Route is a node that contains definitions of how to handle alerts.",
|
||||
"type": "object"
|
||||
},
|
||||
"RouteExport": {
|
||||
|
|
|
|||
Loading…
Reference in New Issue