diff --git a/pkg/api/api.go b/pkg/api/api.go index b08f051539d..eaae0045682 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -25,13 +25,16 @@ func Register(m *macaron.Macaron) { m.Post("/api/account/using/:id", auth, SetUsingAccount) m.Get("/api/account/others", auth, GetOtherAccounts) - // datasources + // data sources m.Get("/admin/datasources/", auth, Index) m.Get("/api/admin/datasources/list", auth, GetDataSources) m.Put("/api/admin/datasources", auth, AddDataSource) m.Post("/api/admin/datasources", auth, UpdateDataSource) m.Delete("/api/admin/datasources/:id", auth, DeleteDataSource) + // data source proxy + m.Any("/api/datasources/proxy/:name/*", auth, ProxyDataSourceRequest) + // user register m.Get("/register/*_", Index) m.Post("/api/account", CreateAccount) diff --git a/pkg/api/api_config.go b/pkg/api/api_config.go index 17894b269fa..a88e3de27dd 100644 --- a/pkg/api/api_config.go +++ b/pkg/api/api_config.go @@ -25,9 +25,13 @@ func renderConfig(data *configJsTmplModel) string { datasources := make(map[string]interface{}) for _, ds := range data.DataSources { + url := ds.Url + if ds.Access == m.DS_ACCESS_PROXY { + url = "/api/datasources/proxy/" + ds.Name + } datasources[ds.Name] = map[string]interface{}{ "type": ds.Type, - "url": ds.Url, + "url": url, } } diff --git a/pkg/api/api_dataproxy.go b/pkg/api/api_dataproxy.go new file mode 100644 index 00000000000..c97341b3f4c --- /dev/null +++ b/pkg/api/api_dataproxy.go @@ -0,0 +1,63 @@ +package api + +import ( + "net/http" + "net/http/httputil" + "net/url" + "strings" + + "github.com/torkelo/grafana-pro/pkg/bus" + "github.com/torkelo/grafana-pro/pkg/log" + "github.com/torkelo/grafana-pro/pkg/middleware" + m "github.com/torkelo/grafana-pro/pkg/models" +) + +func singleJoiningSlash(a, b string) string { + aslash := strings.HasSuffix(a, "/") + bslash := strings.HasPrefix(b, "/") + switch { + case aslash && bslash: + return a + b[1:] + case !aslash && !bslash: + return a + "/" + b + } + return a + b +} + +func NewReverseProxy(target *url.URL, proxyPath string) *httputil.ReverseProxy { + targetQuery := target.RawQuery + + director := func(req *http.Request) { + req.URL.Scheme = target.Scheme + req.URL.Host = target.Host + req.URL.Path = singleJoiningSlash(target.Path, proxyPath) + if targetQuery == "" || req.URL.RawQuery == "" { + req.URL.RawQuery = targetQuery + req.URL.RawQuery + } else { + req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery + } + + log.Info("Proxy: %v", req.URL.Path) + } + return &httputil.ReverseProxy{Director: director} +} + +// TODO: need to cache datasources +func ProxyDataSourceRequest(c *middleware.Context) { + name := c.Params(":name") + + query := m.GetDataSourceByNameQuery{ + Name: name, + AccountId: c.GetAccountId(), + } + + err := bus.Dispatch(&query) + if err != nil { + c.JsonApiErr(500, "Unable to load datasource meta data", err) + } + + proxyPath := c.Params("*") + url, _ := url.Parse(query.Result.Url) + proxy := NewReverseProxy(url, proxyPath) + proxy.ServeHTTP(c.RW(), c.Req.Request) +} diff --git a/pkg/models/datasource.go b/pkg/models/datasource.go index fbacfb09f24..3baf81af8ba 100644 --- a/pkg/models/datasource.go +++ b/pkg/models/datasource.go @@ -1,6 +1,9 @@ package models -import "time" +import ( + "errors" + "time" +) const ( DS_GRAPHITE = "graphite" @@ -10,6 +13,11 @@ const ( DS_ACCESS_PROXY = "proxy" ) +// Typed errors +var ( + ErrDataSourceNotFound = errors.New("Data source not found") +) + type DsType string type DsAccess string @@ -34,6 +42,12 @@ type GetDataSourcesQuery struct { Result []*DataSource } +type GetDataSourceByNameQuery struct { + Name string + AccountId int64 + Result DataSource +} + type AddDataSourceCommand struct { AccountId int64 Name string diff --git a/pkg/stores/sqlstore/sqlstore_datasource.go b/pkg/stores/sqlstore/sqlstore_datasource.go index cd8be560ac9..adea310366a 100644 --- a/pkg/stores/sqlstore/sqlstore_datasource.go +++ b/pkg/stores/sqlstore/sqlstore_datasource.go @@ -14,6 +14,17 @@ func init() { bus.AddHandler("sql", AddDataSource) bus.AddHandler("sql", DeleteDataSource) bus.AddHandler("sql", UpdateDataSource) + bus.AddHandler("sql", GetDataSourceByName) +} + +func GetDataSourceByName(query *m.GetDataSourceByNameQuery) error { + sess := x.Limit(100, 0).Where("account_id=? AND name=?", query.AccountId, query.Name) + has, err := sess.Get(&query.Result) + + if !has { + return m.ErrDataSourceNotFound + } + return err } func GetDataSources(query *m.GetDataSourcesQuery) error {