mirror of https://github.com/grafana/grafana.git
				
				
				
			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) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -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 { | ||||||
|  |  | ||||||
|  | @ -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