diff --git a/pkg/tsdb/loki/api.go b/pkg/tsdb/loki/api.go index 87e340dfc8c..d1d1eb6f813 100644 --- a/pkg/tsdb/loki/api.go +++ b/pkg/tsdb/loki/api.go @@ -25,7 +25,7 @@ func newLokiAPI(client *http.Client, url string, log log.Logger) *LokiAPI { return &LokiAPI{client: client, url: url, log: log} } -func makeRequest(ctx context.Context, lokiDsUrl string, query lokiQuery) (*http.Request, error) { +func makeDataRequest(ctx context.Context, lokiDsUrl string, query lokiQuery) (*http.Request, error) { qs := url.Values{} qs.Set("query", query.Expr) @@ -135,8 +135,8 @@ func makeLokiError(body io.ReadCloser) error { return fmt.Errorf("%v", errorMessage) } -func (api *LokiAPI) Query(ctx context.Context, query lokiQuery) (*loghttp.QueryResponse, error) { - req, err := makeRequest(ctx, api.url, query) +func (api *LokiAPI) DataQuery(ctx context.Context, query lokiQuery) (*loghttp.QueryResponse, error) { + req, err := makeDataRequest(ctx, api.url, query) if err != nil { return nil, err } @@ -164,3 +164,41 @@ func (api *LokiAPI) Query(ctx context.Context, query lokiQuery) (*loghttp.QueryR return &response, nil } + +func makeRawRequest(ctx context.Context, lokiDsUrl string, resourceURL string) (*http.Request, error) { + lokiUrl, err := url.Parse(lokiDsUrl) + if err != nil { + return nil, err + } + + url, err := lokiUrl.Parse(resourceURL) + if err != nil { + return nil, err + } + + return http.NewRequestWithContext(ctx, "GET", url.String(), nil) +} + +func (api *LokiAPI) RawQuery(ctx context.Context, resourceURL string) ([]byte, error) { + req, err := makeRawRequest(ctx, api.url, resourceURL) + if err != nil { + return nil, err + } + + resp, err := api.client.Do(req) + if err != nil { + return nil, err + } + + defer func() { + if err := resp.Body.Close(); err != nil { + api.log.Warn("Failed to close response body", "err", err) + } + }() + + if resp.StatusCode/100 != 2 { + return nil, makeLokiError(resp.Body) + } + + return io.ReadAll(resp.Body) +} diff --git a/pkg/tsdb/loki/loki.go b/pkg/tsdb/loki/loki.go index e2112d33623..976c1db464d 100644 --- a/pkg/tsdb/loki/loki.go +++ b/pkg/tsdb/loki/loki.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "regexp" + "strings" "sync" "github.com/grafana/grafana-plugin-sdk-go/backend" @@ -25,8 +26,9 @@ type Service struct { } var ( - _ backend.QueryDataHandler = (*Service)(nil) - _ backend.StreamHandler = (*Service)(nil) + _ backend.QueryDataHandler = (*Service)(nil) + _ backend.StreamHandler = (*Service)(nil) + _ backend.CallResourceHandler = (*Service)(nil) ) func ProvideService(httpClientProvider httpclient.Provider, tracer tracing.Tracer) *Service { @@ -89,6 +91,40 @@ func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.Inst } } +func (s *Service) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error { + url := req.URL + + // a very basic is-this-url-valid check + if req.Method != "GET" { + return fmt.Errorf("invalid resource method: %s", req.Method) + } + if (!strings.HasPrefix(url, "/loki/api/v1/label?")) && + (!strings.HasPrefix(url, "/loki/api/v1/label/")) && // the `/label/$label_name/values` form + (!strings.HasPrefix(url, "/loki/api/v1/series?")) { + return fmt.Errorf("invalid resource URL: %s", url) + } + + dsInfo, err := s.getDSInfo(req.PluginContext) + if err != nil { + return err + } + + api := newLokiAPI(dsInfo.HTTPClient, dsInfo.URL, s.plog) + bytes, err := api.RawQuery(ctx, url) + + if err != nil { + return err + } + + return sender.Send(&backend.CallResourceResponse{ + Status: http.StatusOK, + Headers: map[string][]string{ + "content-type": {"application/json"}, + }, + Body: bytes, + }) +} + func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { result := backend.NewQueryDataResponse() @@ -129,7 +165,7 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) // we extracted this part of the functionality to make it easy to unit-test it func runQuery(ctx context.Context, api *LokiAPI, query *lokiQuery) (data.Frames, error) { - value, err := api.Query(ctx, *query) + value, err := api.DataQuery(ctx, *query) if err != nil { return data.Frames{}, err } diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts index 9897ae2cc15..79b808543aa 100644 --- a/public/app/plugins/datasource/loki/datasource.ts +++ b/public/app/plugins/datasource/loki/datasource.ts @@ -439,8 +439,13 @@ export class LokiDatasource } async metadataRequest(url: string, params?: Record) { - const res = await lastValueFrom(this._request(url, params, { hideFromInspector: true })); - return res.data.data || res.data.values || []; + if (config.featureToggles.lokiBackendMode) { + const res = await this.getResource(url, params); + return res.data || res.values || []; + } else { + const res = await lastValueFrom(this._request(url, params, { hideFromInspector: true })); + return res.data.data || res.data.values || []; + } } async metricFindQuery(query: string) {