From 36614b03f7970cc4938a194aa50ac94c56a74be9 Mon Sep 17 00:00:00 2001 From: Owen Diehl Date: Wed, 17 Mar 2021 13:10:40 -0400 Subject: [PATCH] Data Source Proxy: Migrate proxy to its own service and make more extensible (#31927) * datasource proxy extensions * revert normalresponse extensions * data proxy service impl * lint * api datasource proxy method * moves datasource proxy tests into correct new pkg --- pkg/api/dataproxy.go | 61 +------------ pkg/api/http_server.go | 52 ++++++------ .../datasourceproxy/datasourceproxy.go | 85 +++++++++++++++++++ .../datasourceproxy/datasourceproxy_test.go} | 2 +- 4 files changed, 115 insertions(+), 85 deletions(-) create mode 100644 pkg/services/datasourceproxy/datasourceproxy.go rename pkg/{api/dataproxy_test.go => services/datasourceproxy/datasourceproxy_test.go} (96%) diff --git a/pkg/api/dataproxy.go b/pkg/api/dataproxy.go index 2b024ffcd54..5a39872d20e 100644 --- a/pkg/api/dataproxy.go +++ b/pkg/api/dataproxy.go @@ -1,64 +1,7 @@ package api -import ( - "errors" - "fmt" - "net/http" - "regexp" +import "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/api/datasource" - "github.com/grafana/grafana/pkg/api/pluginproxy" - "github.com/grafana/grafana/pkg/infra/metrics" - "github.com/grafana/grafana/pkg/models" -) - -// ProxyDataSourceRequest proxies datasource requests func (hs *HTTPServer) ProxyDataSourceRequest(c *models.ReqContext) { - c.TimeRequest(metrics.MDataSourceProxyReqTimer) - - dsID := c.ParamsInt64(":id") - ds, err := hs.DatasourceCache.GetDatasource(dsID, c.SignedInUser, c.SkipCache) - if err != nil { - if errors.Is(err, models.ErrDataSourceAccessDenied) { - c.JsonApiErr(http.StatusForbidden, "Access denied to datasource", err) - return - } - c.JsonApiErr(http.StatusInternalServerError, "Unable to load datasource meta data", err) - return - } - - err = hs.PluginRequestValidator.Validate(ds.Url, c.Req.Request) - if err != nil { - c.JsonApiErr(http.StatusForbidden, "Access denied", err) - return - } - - // find plugin - plugin := hs.PluginManager.GetDataSource(ds.Type) - if plugin == nil { - c.JsonApiErr(http.StatusInternalServerError, "Unable to find datasource plugin", err) - return - } - - proxyPath := getProxyPath(c) - proxy, err := pluginproxy.NewDataSourceProxy(ds, plugin, c, proxyPath, hs.Cfg) - if err != nil { - if errors.Is(err, datasource.URLValidationError{}) { - c.JsonApiErr(http.StatusBadRequest, fmt.Sprintf("Invalid data source URL: %q", ds.Url), err) - } else { - c.JsonApiErr(http.StatusInternalServerError, "Failed creating data source proxy", err) - } - return - } - proxy.HandleRequest() -} - -var proxyPathRegexp = regexp.MustCompile(`^\/api\/datasources\/proxy\/[\d]+\/?`) - -func extractProxyPath(originalRawPath string) string { - return proxyPathRegexp.ReplaceAllString(originalRawPath, "") -} - -func getProxyPath(c *models.ReqContext) string { - return extractProxyPath(c.Req.URL.EscapedPath()) + hs.DataProxy.ProxyDataSourceRequest(c) } diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index 80776e26ac0..916deaf29a1 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -36,6 +36,7 @@ import ( "github.com/grafana/grafana/pkg/plugins/plugindashboards" "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/contexthandler" + "github.com/grafana/grafana/pkg/services/datasourceproxy" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/hooks" "github.com/grafana/grafana/pkg/services/librarypanels" @@ -65,31 +66,32 @@ type HTTPServer struct { httpSrv *http.Server middlewares []macaron.Handler - RouteRegister routing.RouteRegister `inject:""` - Bus bus.Bus `inject:""` - RenderService rendering.Service `inject:""` - Cfg *setting.Cfg `inject:""` - HooksService *hooks.HooksService `inject:""` - CacheService *localcache.CacheService `inject:""` - DatasourceCache datasources.CacheService `inject:""` - AuthTokenService models.UserTokenService `inject:""` - QuotaService *quota.QuotaService `inject:""` - RemoteCacheService *remotecache.RemoteCache `inject:""` - ProvisioningService provisioning.ProvisioningService `inject:""` - Login *login.LoginService `inject:""` - License models.Licensing `inject:""` - BackendPluginManager backendplugin.Manager `inject:""` - PluginRequestValidator models.PluginRequestValidator `inject:""` - PluginManager plugins.Manager `inject:""` - SearchService *search.SearchService `inject:""` - ShortURLService *shorturls.ShortURLService `inject:""` - Live *live.GrafanaLive `inject:""` - ContextHandler *contexthandler.ContextHandler `inject:""` - SQLStore *sqlstore.SQLStore `inject:""` - LibraryPanelService *librarypanels.LibraryPanelService `inject:""` - DataService *tsdb.Service `inject:""` - PluginDashboardService *plugindashboards.Service `inject:""` - AlertEngine *alerting.AlertEngine `inject:""` + RouteRegister routing.RouteRegister `inject:""` + Bus bus.Bus `inject:""` + RenderService rendering.Service `inject:""` + Cfg *setting.Cfg `inject:""` + HooksService *hooks.HooksService `inject:""` + CacheService *localcache.CacheService `inject:""` + DatasourceCache datasources.CacheService `inject:""` + AuthTokenService models.UserTokenService `inject:""` + QuotaService *quota.QuotaService `inject:""` + RemoteCacheService *remotecache.RemoteCache `inject:""` + ProvisioningService provisioning.ProvisioningService `inject:""` + Login *login.LoginService `inject:""` + License models.Licensing `inject:""` + BackendPluginManager backendplugin.Manager `inject:""` + DataProxy *datasourceproxy.DatasourceProxyService `inject:""` + PluginRequestValidator models.PluginRequestValidator `inject:""` + PluginManager plugins.Manager `inject:""` + SearchService *search.SearchService `inject:""` + ShortURLService *shorturls.ShortURLService `inject:""` + Live *live.GrafanaLive `inject:""` + ContextHandler *contexthandler.ContextHandler `inject:""` + SQLStore *sqlstore.SQLStore `inject:""` + LibraryPanelService *librarypanels.LibraryPanelService `inject:""` + DataService *tsdb.Service `inject:""` + PluginDashboardService *plugindashboards.Service `inject:""` + AlertEngine *alerting.AlertEngine `inject:""` Listener net.Listener } diff --git a/pkg/services/datasourceproxy/datasourceproxy.go b/pkg/services/datasourceproxy/datasourceproxy.go new file mode 100644 index 00000000000..87b0d3526fb --- /dev/null +++ b/pkg/services/datasourceproxy/datasourceproxy.go @@ -0,0 +1,85 @@ +package datasourceproxy + +import ( + "errors" + "fmt" + "net/http" + "regexp" + + "github.com/grafana/grafana/pkg/api/datasource" + "github.com/grafana/grafana/pkg/api/pluginproxy" + "github.com/grafana/grafana/pkg/infra/metrics" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/registry" + "github.com/grafana/grafana/pkg/services/datasources" + "github.com/grafana/grafana/pkg/setting" +) + +func init() { + registry.RegisterService(&DatasourceProxyService{}) +} + +type DatasourceProxyService struct { + DatasourceCache datasources.CacheService `inject:""` + PluginRequestValidator models.PluginRequestValidator `inject:""` + PluginManager plugins.Manager `inject:""` + Cfg *setting.Cfg `inject:""` +} + +func (p *DatasourceProxyService) Init() error { + return nil +} + +func (p *DatasourceProxyService) ProxyDataSourceRequest(c *models.ReqContext) { + p.ProxyDatasourceRequestWithID(c, c.ParamsInt64(":id")) +} + +func (p *DatasourceProxyService) ProxyDatasourceRequestWithID(c *models.ReqContext, dsID int64) { + c.TimeRequest(metrics.MDataSourceProxyReqTimer) + + ds, err := p.DatasourceCache.GetDatasource(dsID, c.SignedInUser, c.SkipCache) + if err != nil { + if errors.Is(err, models.ErrDataSourceAccessDenied) { + c.JsonApiErr(http.StatusForbidden, "Access denied to datasource", err) + return + } + c.JsonApiErr(http.StatusInternalServerError, "Unable to load datasource meta data", err) + return + } + + err = p.PluginRequestValidator.Validate(ds.Url, c.Req.Request) + if err != nil { + c.JsonApiErr(http.StatusForbidden, "Access denied", err) + return + } + + // find plugin + plugin := p.PluginManager.GetDataSource(ds.Type) + if plugin == nil { + c.JsonApiErr(http.StatusInternalServerError, "Unable to find datasource plugin", err) + return + } + + proxyPath := getProxyPath(c) + proxy, err := pluginproxy.NewDataSourceProxy(ds, plugin, c, proxyPath, p.Cfg) + if err != nil { + if errors.Is(err, datasource.URLValidationError{}) { + c.JsonApiErr(http.StatusBadRequest, fmt.Sprintf("Invalid data source URL: %q", ds.Url), err) + } else { + c.JsonApiErr(http.StatusInternalServerError, "Failed creating data source proxy", err) + } + return + } + proxy.HandleRequest() +} + +var proxyPathRegexp = regexp.MustCompile(`^\/api\/datasources\/proxy\/[\d]+\/?`) + +func extractProxyPath(originalRawPath string) string { + return proxyPathRegexp.ReplaceAllString(originalRawPath, "") +} + +func getProxyPath(c *models.ReqContext) string { + return extractProxyPath(c.Req.URL.EscapedPath()) +} diff --git a/pkg/api/dataproxy_test.go b/pkg/services/datasourceproxy/datasourceproxy_test.go similarity index 96% rename from pkg/api/dataproxy_test.go rename to pkg/services/datasourceproxy/datasourceproxy_test.go index be0f98cd77d..4533fbf74d9 100644 --- a/pkg/api/dataproxy_test.go +++ b/pkg/services/datasourceproxy/datasourceproxy_test.go @@ -1,4 +1,4 @@ -package api +package datasourceproxy import ( "testing"