mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Extricates reusable utilities for different alerting proxy types (#32268)
* backendtype helper * abstracts alertingproxy * updates alerting api dep * prom endpoints
This commit is contained in:
parent
376ed8a381
commit
2179a2658e
2
go.mod
2
go.mod
@ -39,7 +39,7 @@ require (
|
|||||||
github.com/google/go-cmp v0.5.5
|
github.com/google/go-cmp v0.5.5
|
||||||
github.com/google/uuid v1.2.0
|
github.com/google/uuid v1.2.0
|
||||||
github.com/gosimple/slug v1.9.0
|
github.com/gosimple/slug v1.9.0
|
||||||
github.com/grafana/alerting-api v0.0.0-20210323142651-d6515052e2f0
|
github.com/grafana/alerting-api v0.0.0-20210323194814-03a29a4c4c27
|
||||||
github.com/grafana/grafana-aws-sdk v0.2.0
|
github.com/grafana/grafana-aws-sdk v0.2.0
|
||||||
github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4
|
github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4
|
||||||
github.com/grafana/grafana-plugin-sdk-go v0.89.0
|
github.com/grafana/grafana-plugin-sdk-go v0.89.0
|
||||||
|
8
go.sum
8
go.sum
@ -797,12 +797,8 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U
|
|||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/gosimple/slug v1.9.0 h1:r5vDcYrFz9BmfIAMC829un9hq7hKM4cHUrsv36LbEqs=
|
github.com/gosimple/slug v1.9.0 h1:r5vDcYrFz9BmfIAMC829un9hq7hKM4cHUrsv36LbEqs=
|
||||||
github.com/gosimple/slug v1.9.0/go.mod h1:AMZ+sOVe65uByN3kgEyf9WEBKBCSS+dJjMX9x4vDJbg=
|
github.com/gosimple/slug v1.9.0/go.mod h1:AMZ+sOVe65uByN3kgEyf9WEBKBCSS+dJjMX9x4vDJbg=
|
||||||
github.com/grafana/alerting-api v0.0.0-20210318231719-9499804fc548 h1:KjyaZJhPJ15Ul/+OQr8mbO7kDpU5i7G3r5FGVZKClTQ=
|
github.com/grafana/alerting-api v0.0.0-20210323194814-03a29a4c4c27 h1:DuyuEAHJeI+CMxIyzCVhmHcIeK+sjqberhDUfrgd3PY=
|
||||||
github.com/grafana/alerting-api v0.0.0-20210318231719-9499804fc548/go.mod h1:5IppnPguSHcCbVLGCVzVjBvuQZNbYgVJ4KyXXjhCyWY=
|
github.com/grafana/alerting-api v0.0.0-20210323194814-03a29a4c4c27/go.mod h1:5IppnPguSHcCbVLGCVzVjBvuQZNbYgVJ4KyXXjhCyWY=
|
||||||
github.com/grafana/alerting-api v0.0.0-20210323141138-8873de5bf07a h1:OGKDRdmQSXKFJelrJUf9O8Xh0C8u+OQG1NSurcBYpOI=
|
|
||||||
github.com/grafana/alerting-api v0.0.0-20210323141138-8873de5bf07a/go.mod h1:5IppnPguSHcCbVLGCVzVjBvuQZNbYgVJ4KyXXjhCyWY=
|
|
||||||
github.com/grafana/alerting-api v0.0.0-20210323142651-d6515052e2f0 h1:bMYGd71RigZvkLmcdedGdMDJXKJ20luqEQLbqjgAAjI=
|
|
||||||
github.com/grafana/alerting-api v0.0.0-20210323142651-d6515052e2f0/go.mod h1:5IppnPguSHcCbVLGCVzVjBvuQZNbYgVJ4KyXXjhCyWY=
|
|
||||||
github.com/grafana/grafana v1.9.2-0.20210308201921-4ce0a49eac03/go.mod h1:AHRRvd4utJGY25J5nW8aL7wZzn/LcJ0z2za9oOp14j4=
|
github.com/grafana/grafana v1.9.2-0.20210308201921-4ce0a49eac03/go.mod h1:AHRRvd4utJGY25J5nW8aL7wZzn/LcJ0z2za9oOp14j4=
|
||||||
github.com/grafana/grafana-aws-sdk v0.1.0/go.mod h1:+pPo5U+pX0zWimR7YBc7ASeSQfbRkcTyQYqMiAj7G5U=
|
github.com/grafana/grafana-aws-sdk v0.1.0/go.mod h1:+pPo5U+pX0zWimR7YBc7ASeSQfbRkcTyQYqMiAj7G5U=
|
||||||
github.com/grafana/grafana-aws-sdk v0.2.0 h1:UTBBYwye+ad5YUIlwN7TGxLdz1wXN3Ezhl0pseDGRVA=
|
github.com/grafana/grafana-aws-sdk v0.2.0 h1:UTBBYwye+ad5YUIlwN7TGxLdz1wXN3Ezhl0pseDGRVA=
|
||||||
|
@ -45,11 +45,18 @@ type API struct {
|
|||||||
// RegisterAPIEndpoints registers API handlers
|
// RegisterAPIEndpoints registers API handlers
|
||||||
func (api *API) RegisterAPIEndpoints() {
|
func (api *API) RegisterAPIEndpoints() {
|
||||||
logger := log.New("ngalert.api")
|
logger := log.New("ngalert.api")
|
||||||
|
proxy := &AlertingProxy{
|
||||||
|
DataProxy: api.DataProxy,
|
||||||
|
}
|
||||||
api.RegisterAlertmanagerApiEndpoints(AlertmanagerApiMock{log: logger})
|
api.RegisterAlertmanagerApiEndpoints(AlertmanagerApiMock{log: logger})
|
||||||
api.RegisterPrometheusApiEndpoints(PrometheusApiMock{log: logger})
|
api.RegisterPrometheusApiEndpoints(NewForkedProm(
|
||||||
|
api.DatasourceCache,
|
||||||
|
NewLotexProm(proxy, logger),
|
||||||
|
PrometheusApiMock{log: logger},
|
||||||
|
))
|
||||||
api.RegisterRulerApiEndpoints(NewForkedRuler(
|
api.RegisterRulerApiEndpoints(NewForkedRuler(
|
||||||
api.DatasourceCache,
|
api.DatasourceCache,
|
||||||
&LotexRuler{DataProxy: api.DataProxy, log: logger},
|
NewLotexRuler(proxy, logger),
|
||||||
RulerApiMock{log: logger},
|
RulerApiMock{log: logger},
|
||||||
))
|
))
|
||||||
api.RegisterTestingApiEndpoints(TestingApiMock{log: logger})
|
api.RegisterTestingApiEndpoints(TestingApiMock{log: logger})
|
||||||
|
@ -2,7 +2,6 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||||
"github.com/grafana/grafana/pkg/api/response"
|
"github.com/grafana/grafana/pkg/api/response"
|
||||||
@ -24,26 +23,8 @@ func NewForkedRuler(datasourceCache datasources.CacheService, lotex, grafana Rul
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ForkedRuler) backendType(ctx *models.ReqContext) (apimodels.Backend, error) {
|
|
||||||
recipient := ctx.Params("Recipient")
|
|
||||||
if recipient == apimodels.GrafanaBackend.String() {
|
|
||||||
return apimodels.GrafanaBackend, nil
|
|
||||||
}
|
|
||||||
if datasourceID, err := strconv.ParseInt(recipient, 10, 64); err == nil {
|
|
||||||
if ds, err := r.DatasourceCache.GetDatasource(datasourceID, ctx.SignedInUser, ctx.SkipCache); err == nil {
|
|
||||||
switch ds.Type {
|
|
||||||
case "loki", "prometheus":
|
|
||||||
return apimodels.LoTexRulerBackend, nil
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("unexpected backend type (%v)", ds.Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, fmt.Errorf("unexpected backend type (%v)", recipient)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ForkedRuler) RouteDeleteNamespaceRulesConfig(ctx *models.ReqContext) response.Response {
|
func (r *ForkedRuler) RouteDeleteNamespaceRulesConfig(ctx *models.ReqContext) response.Response {
|
||||||
t, err := r.backendType(ctx)
|
t, err := backendType(ctx, r.DatasourceCache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(400, err.Error(), nil)
|
return response.Error(400, err.Error(), nil)
|
||||||
}
|
}
|
||||||
@ -58,7 +39,7 @@ func (r *ForkedRuler) RouteDeleteNamespaceRulesConfig(ctx *models.ReqContext) re
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *ForkedRuler) RouteDeleteRuleGroupConfig(ctx *models.ReqContext) response.Response {
|
func (r *ForkedRuler) RouteDeleteRuleGroupConfig(ctx *models.ReqContext) response.Response {
|
||||||
t, err := r.backendType(ctx)
|
t, err := backendType(ctx, r.DatasourceCache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(400, err.Error(), nil)
|
return response.Error(400, err.Error(), nil)
|
||||||
}
|
}
|
||||||
@ -73,7 +54,7 @@ func (r *ForkedRuler) RouteDeleteRuleGroupConfig(ctx *models.ReqContext) respons
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *ForkedRuler) RouteGetNamespaceRulesConfig(ctx *models.ReqContext) response.Response {
|
func (r *ForkedRuler) RouteGetNamespaceRulesConfig(ctx *models.ReqContext) response.Response {
|
||||||
t, err := r.backendType(ctx)
|
t, err := backendType(ctx, r.DatasourceCache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(400, err.Error(), nil)
|
return response.Error(400, err.Error(), nil)
|
||||||
}
|
}
|
||||||
@ -88,7 +69,7 @@ func (r *ForkedRuler) RouteGetNamespaceRulesConfig(ctx *models.ReqContext) respo
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *ForkedRuler) RouteGetRulegGroupConfig(ctx *models.ReqContext) response.Response {
|
func (r *ForkedRuler) RouteGetRulegGroupConfig(ctx *models.ReqContext) response.Response {
|
||||||
t, err := r.backendType(ctx)
|
t, err := backendType(ctx, r.DatasourceCache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(400, err.Error(), nil)
|
return response.Error(400, err.Error(), nil)
|
||||||
}
|
}
|
||||||
@ -103,7 +84,7 @@ func (r *ForkedRuler) RouteGetRulegGroupConfig(ctx *models.ReqContext) response.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *ForkedRuler) RouteGetRulesConfig(ctx *models.ReqContext) response.Response {
|
func (r *ForkedRuler) RouteGetRulesConfig(ctx *models.ReqContext) response.Response {
|
||||||
t, err := r.backendType(ctx)
|
t, err := backendType(ctx, r.DatasourceCache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(400, err.Error(), nil)
|
return response.Error(400, err.Error(), nil)
|
||||||
}
|
}
|
||||||
@ -118,7 +99,7 @@ func (r *ForkedRuler) RouteGetRulesConfig(ctx *models.ReqContext) response.Respo
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *ForkedRuler) RoutePostNameRulesConfig(ctx *models.ReqContext, conf apimodels.RuleGroupConfig) response.Response {
|
func (r *ForkedRuler) RoutePostNameRulesConfig(ctx *models.ReqContext, conf apimodels.RuleGroupConfig) response.Response {
|
||||||
backendType, err := r.backendType(ctx)
|
backendType, err := backendType(ctx, r.DatasourceCache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(400, err.Error(), nil)
|
return response.Error(400, err.Error(), nil)
|
||||||
}
|
}
|
||||||
|
55
pkg/services/ngalert/api/forked_prom.go
Normal file
55
pkg/services/ngalert/api/forked_prom.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||||
|
"github.com/grafana/grafana/pkg/api/response"
|
||||||
|
"github.com/grafana/grafana/pkg/models"
|
||||||
|
"github.com/grafana/grafana/pkg/services/datasources"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ForkedPromSvc struct {
|
||||||
|
ProxySvc, GrafanaSvc PrometheusApiService
|
||||||
|
DatasourceCache datasources.CacheService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewForkedProm(datasourceCache datasources.CacheService, proxy, grafana PrometheusApiService) *ForkedPromSvc {
|
||||||
|
return &ForkedPromSvc{
|
||||||
|
ProxySvc: proxy,
|
||||||
|
GrafanaSvc: grafana,
|
||||||
|
DatasourceCache: datasourceCache,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ForkedPromSvc) RouteGetAlertStatuses(ctx *models.ReqContext) response.Response {
|
||||||
|
t, err := backendType(ctx, p.DatasourceCache)
|
||||||
|
if err != nil {
|
||||||
|
return response.Error(400, err.Error(), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t {
|
||||||
|
case apimodels.GrafanaBackend:
|
||||||
|
return p.GrafanaSvc.RouteGetAlertStatuses(ctx)
|
||||||
|
case apimodels.LoTexRulerBackend:
|
||||||
|
return p.ProxySvc.RouteGetAlertStatuses(ctx)
|
||||||
|
default:
|
||||||
|
return response.Error(400, fmt.Sprintf("unexpected backend type (%v)", t), nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ForkedPromSvc) RouteGetRuleStatuses(ctx *models.ReqContext) response.Response {
|
||||||
|
t, err := backendType(ctx, p.DatasourceCache)
|
||||||
|
if err != nil {
|
||||||
|
return response.Error(400, err.Error(), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t {
|
||||||
|
case apimodels.GrafanaBackend:
|
||||||
|
return p.GrafanaSvc.RouteGetRuleStatuses(ctx)
|
||||||
|
case apimodels.LoTexRulerBackend:
|
||||||
|
return p.ProxySvc.RouteGetRuleStatuses(ctx)
|
||||||
|
default:
|
||||||
|
return response.Error(400, fmt.Sprintf("unexpected backend type (%v)", t), nil)
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,6 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -10,96 +9,25 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||||
"gopkg.in/macaron.v1"
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/api/response"
|
"github.com/grafana/grafana/pkg/api/response"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/services/datasourceproxy"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const legacyRulerPrefix = "/api/prom/rules"
|
const legacyRulerPrefix = "/api/prom/rules"
|
||||||
|
|
||||||
type LotexRuler struct {
|
type LotexRuler struct {
|
||||||
DataProxy *datasourceproxy.DatasourceProxyService
|
|
||||||
log log.Logger
|
log log.Logger
|
||||||
|
*AlertingProxy
|
||||||
}
|
}
|
||||||
|
|
||||||
// macaron unsafely asserts the http.ResponseWriter is an http.CloseNotifier, which will panic.
|
func NewLotexRuler(proxy *AlertingProxy, log log.Logger) *LotexRuler {
|
||||||
// Here we impl it, which will ensure this no longer happens, but neither will we take
|
return &LotexRuler{
|
||||||
// advantage cancelling upstream requests when the downstream has closed.
|
log: log,
|
||||||
// NB: http.CloseNotifier is a deprecated ifc from before the context pkg.
|
AlertingProxy: proxy,
|
||||||
type safeMacaronWrapper struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *safeMacaronWrapper) CloseNotify() <-chan bool {
|
|
||||||
return make(chan bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
// replacedResponseWriter overwrites the underlying responsewriter used by a *models.ReqContext.
|
|
||||||
// It's ugly because it needs to replace a value behind a few nested pointers.
|
|
||||||
func replacedResponseWriter(ctx *models.ReqContext) (*models.ReqContext, *response.NormalResponse) {
|
|
||||||
resp := response.CreateNormalResponse(make(http.Header), nil, 0)
|
|
||||||
cpy := *ctx
|
|
||||||
cpyMCtx := *cpy.Context
|
|
||||||
cpyMCtx.Resp = macaron.NewResponseWriter(ctx.Req.Method, &safeMacaronWrapper{resp})
|
|
||||||
cpy.Context = &cpyMCtx
|
|
||||||
return &cpy, resp
|
|
||||||
}
|
|
||||||
|
|
||||||
// withReq proxies a different request
|
|
||||||
func (r *LotexRuler) withReq(
|
|
||||||
ctx *models.ReqContext,
|
|
||||||
req *http.Request,
|
|
||||||
extractor func([]byte) (interface{}, error),
|
|
||||||
) response.Response {
|
|
||||||
newCtx, resp := replacedResponseWriter(ctx)
|
|
||||||
newCtx.Req.Request = req
|
|
||||||
r.DataProxy.ProxyDatasourceRequestWithID(newCtx, ctx.ParamsInt64("Recipient"))
|
|
||||||
|
|
||||||
status := resp.Status()
|
|
||||||
if status >= 400 {
|
|
||||||
return response.Error(status, string(resp.Body()), nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
t, err := extractor(resp.Body())
|
|
||||||
if err != nil {
|
|
||||||
return response.Error(500, err.Error(), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := json.Marshal(t)
|
|
||||||
if err != nil {
|
|
||||||
return response.Error(500, err.Error(), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.JSON(status, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func yamlExtractor(v interface{}) func([]byte) (interface{}, error) {
|
|
||||||
return func(b []byte) (interface{}, error) {
|
|
||||||
decoder := yaml.NewDecoder(bytes.NewReader(b))
|
|
||||||
decoder.KnownFields(true)
|
|
||||||
|
|
||||||
err := decoder.Decode(v)
|
|
||||||
|
|
||||||
return v, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func jsonExtractor(v interface{}) func([]byte) (interface{}, error) {
|
|
||||||
if v == nil {
|
|
||||||
// json unmarshal expects a pointer
|
|
||||||
v = &map[string]interface{}{}
|
|
||||||
}
|
|
||||||
return func(b []byte) (interface{}, error) {
|
|
||||||
return v, json.Unmarshal(b, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func messageExtractor(b []byte) (interface{}, error) {
|
|
||||||
return map[string]string{"message": string(b)}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LotexRuler) RouteDeleteNamespaceRulesConfig(ctx *models.ReqContext) response.Response {
|
func (r *LotexRuler) RouteDeleteNamespaceRulesConfig(ctx *models.ReqContext) response.Response {
|
||||||
|
51
pkg/services/ngalert/api/lotex_prom.go
Normal file
51
pkg/services/ngalert/api/lotex_prom.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||||
|
"github.com/grafana/grafana/pkg/api/response"
|
||||||
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
|
"github.com/grafana/grafana/pkg/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
promRulesPath = "/prometheus/api/v1/rules"
|
||||||
|
promAlertsPath = "/prometheus/api/v1/alerts"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LotexProm struct {
|
||||||
|
log log.Logger
|
||||||
|
*AlertingProxy
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLotexProm(proxy *AlertingProxy, log log.Logger) *LotexProm {
|
||||||
|
return &LotexProm{
|
||||||
|
log: log,
|
||||||
|
AlertingProxy: proxy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LotexProm) RouteGetAlertStatuses(ctx *models.ReqContext) response.Response {
|
||||||
|
return p.withReq(
|
||||||
|
ctx, &http.Request{
|
||||||
|
URL: withPath(
|
||||||
|
*ctx.Req.URL,
|
||||||
|
promAlertsPath,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
jsonExtractor(&apimodels.AlertResponse{}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *LotexProm) RouteGetRuleStatuses(ctx *models.ReqContext) response.Response {
|
||||||
|
return p.withReq(
|
||||||
|
ctx, &http.Request{
|
||||||
|
URL: withPath(
|
||||||
|
*ctx.Req.URL,
|
||||||
|
promRulesPath,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
jsonExtractor(&apimodels.RuleResponse{}),
|
||||||
|
)
|
||||||
|
}
|
@ -1,10 +1,21 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/go-openapi/strfmt"
|
"github.com/go-openapi/strfmt"
|
||||||
|
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||||
|
"github.com/grafana/grafana/pkg/api/response"
|
||||||
|
"github.com/grafana/grafana/pkg/models"
|
||||||
|
"github.com/grafana/grafana/pkg/services/datasourceproxy"
|
||||||
|
"github.com/grafana/grafana/pkg/services/datasources"
|
||||||
|
"gopkg.in/macaron.v1"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var searchRegex = regexp.MustCompile(`\{(\w+)\}`)
|
var searchRegex = regexp.MustCompile(`\{(\w+)\}`)
|
||||||
@ -27,3 +38,101 @@ func stringPtr(s string) *string {
|
|||||||
func boolPtr(b bool) *bool {
|
func boolPtr(b bool) *bool {
|
||||||
return &b
|
return &b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func backendType(ctx *models.ReqContext, cache datasources.CacheService) (apimodels.Backend, error) {
|
||||||
|
recipient := ctx.Params("Recipient")
|
||||||
|
if recipient == apimodels.GrafanaBackend.String() {
|
||||||
|
return apimodels.GrafanaBackend, nil
|
||||||
|
}
|
||||||
|
if datasourceID, err := strconv.ParseInt(recipient, 10, 64); err == nil {
|
||||||
|
if ds, err := cache.GetDatasource(datasourceID, ctx.SignedInUser, ctx.SkipCache); err == nil {
|
||||||
|
switch ds.Type {
|
||||||
|
case "loki", "prometheus":
|
||||||
|
return apimodels.LoTexRulerBackend, nil
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("unexpected backend type (%v)", ds.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("unexpected backend type (%v)", recipient)
|
||||||
|
}
|
||||||
|
|
||||||
|
// macaron unsafely asserts the http.ResponseWriter is an http.CloseNotifier, which will panic.
|
||||||
|
// Here we impl it, which will ensure this no longer happens, but neither will we take
|
||||||
|
// advantage cancelling upstream requests when the downstream has closed.
|
||||||
|
// NB: http.CloseNotifier is a deprecated ifc from before the context pkg.
|
||||||
|
type safeMacaronWrapper struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *safeMacaronWrapper) CloseNotify() <-chan bool {
|
||||||
|
return make(chan bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// replacedResponseWriter overwrites the underlying responsewriter used by a *models.ReqContext.
|
||||||
|
// It's ugly because it needs to replace a value behind a few nested pointers.
|
||||||
|
func replacedResponseWriter(ctx *models.ReqContext) (*models.ReqContext, *response.NormalResponse) {
|
||||||
|
resp := response.CreateNormalResponse(make(http.Header), nil, 0)
|
||||||
|
cpy := *ctx
|
||||||
|
cpyMCtx := *cpy.Context
|
||||||
|
cpyMCtx.Resp = macaron.NewResponseWriter(ctx.Req.Method, &safeMacaronWrapper{resp})
|
||||||
|
cpy.Context = &cpyMCtx
|
||||||
|
return &cpy, resp
|
||||||
|
}
|
||||||
|
|
||||||
|
type AlertingProxy struct {
|
||||||
|
DataProxy *datasourceproxy.DatasourceProxyService
|
||||||
|
}
|
||||||
|
|
||||||
|
// withReq proxies a different request
|
||||||
|
func (p *AlertingProxy) withReq(
|
||||||
|
ctx *models.ReqContext,
|
||||||
|
req *http.Request,
|
||||||
|
extractor func([]byte) (interface{}, error),
|
||||||
|
) response.Response {
|
||||||
|
newCtx, resp := replacedResponseWriter(ctx)
|
||||||
|
newCtx.Req.Request = req
|
||||||
|
p.DataProxy.ProxyDatasourceRequestWithID(newCtx, ctx.ParamsInt64("Recipient"))
|
||||||
|
|
||||||
|
status := resp.Status()
|
||||||
|
if status >= 400 {
|
||||||
|
return response.Error(status, string(resp.Body()), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := extractor(resp.Body())
|
||||||
|
if err != nil {
|
||||||
|
return response.Error(500, err.Error(), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(t)
|
||||||
|
if err != nil {
|
||||||
|
return response.Error(500, err.Error(), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.JSON(status, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func yamlExtractor(v interface{}) func([]byte) (interface{}, error) {
|
||||||
|
return func(b []byte) (interface{}, error) {
|
||||||
|
decoder := yaml.NewDecoder(bytes.NewReader(b))
|
||||||
|
decoder.KnownFields(true)
|
||||||
|
|
||||||
|
err := decoder.Decode(v)
|
||||||
|
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsonExtractor(v interface{}) func([]byte) (interface{}, error) {
|
||||||
|
if v == nil {
|
||||||
|
// json unmarshal expects a pointer
|
||||||
|
v = &map[string]interface{}{}
|
||||||
|
}
|
||||||
|
return func(b []byte) (interface{}, error) {
|
||||||
|
return v, json.Unmarshal(b, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func messageExtractor(b []byte) (interface{}, error) {
|
||||||
|
return map[string]string{"message": string(b)}, nil
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user