Zipkin: Run resource calls through backend with feature toggle enabled (#96139)
* Zipkin: Run resource calls througgh backend with feature toggle enabled * Update * Don't return early in createZipkinURL and add tests * Update pkg/tsdb/zipkin/client.go Co-authored-by: Sriram <153843+yesoreyeram@users.noreply.github.com> * Update pkg/tsdb/zipkin/client.go Co-authored-by: Sriram <153843+yesoreyeram@users.noreply.github.com> * Update pkg/tsdb/zipkin/client.go Co-authored-by: Sriram <153843+yesoreyeram@users.noreply.github.com> * Update pkg/tsdb/zipkin/client_test.go Co-authored-by: Sriram <153843+yesoreyeram@users.noreply.github.com> * Update pkg/tsdb/zipkin/client_test.go Co-authored-by: Sriram <153843+yesoreyeram@users.noreply.github.com> * Update pkg/tsdb/zipkin/client_test.go Co-authored-by: Sriram <153843+yesoreyeram@users.noreply.github.com> * Update pkg/tsdb/zipkin/client_test.go Co-authored-by: Sriram <153843+yesoreyeram@users.noreply.github.com> * Fix lint * Fix tests --------- Co-authored-by: Sriram <153843+yesoreyeram@users.noreply.github.com>
This commit is contained in:
@@ -2,12 +2,14 @@ package zipkin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||
"github.com/openzipkin/zipkin-go/model"
|
||||
)
|
||||
|
||||
type ZipkinClient struct {
|
||||
@@ -48,3 +50,119 @@ func (z *ZipkinClient) Services() ([]string, error) {
|
||||
}
|
||||
return services, err
|
||||
}
|
||||
|
||||
// Spans returns list of spans for the given service
|
||||
// https://zipkin.io/zipkin-api/#/default/get_spans
|
||||
func (z *ZipkinClient) Spans(serviceName string) ([]string, error) {
|
||||
spans := []string{}
|
||||
if serviceName == "" {
|
||||
return spans, errors.New("invalid/empty serviceName")
|
||||
}
|
||||
|
||||
spansUrl, err := createZipkinURL(z.url, "/api/v2/spans", map[string]string{"serviceName": serviceName})
|
||||
if err != nil {
|
||||
return spans, backend.DownstreamError(fmt.Errorf("failed to compose url: %w", err))
|
||||
}
|
||||
|
||||
res, err := z.httpClient.Get(spansUrl)
|
||||
defer func() {
|
||||
if res != nil {
|
||||
if err = res.Body.Close(); err != nil {
|
||||
z.logger.Error("Failed to close response body", "error", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
return spans, err
|
||||
}
|
||||
if err := json.NewDecoder(res.Body).Decode(&spans); err != nil {
|
||||
return spans, err
|
||||
}
|
||||
return spans, err
|
||||
}
|
||||
|
||||
// Traces returns list of traces for the given service and span
|
||||
// https://zipkin.io/zipkin-api/#/default/get_traces
|
||||
func (z *ZipkinClient) Traces(serviceName string, spanName string) ([][]model.SpanModel, error) {
|
||||
traces := [][]model.SpanModel{}
|
||||
if serviceName == "" {
|
||||
return traces, errors.New("invalid/empty serviceName")
|
||||
}
|
||||
if spanName == "" {
|
||||
return traces, errors.New("invalid/empty spanName")
|
||||
}
|
||||
tracesUrl, err := createZipkinURL(z.url, "/api/v2/traces", map[string]string{"serviceName": serviceName, "spanName": spanName})
|
||||
if err != nil {
|
||||
return traces, backend.DownstreamError(fmt.Errorf("failed to compose url: %w", err))
|
||||
}
|
||||
|
||||
res, err := z.httpClient.Get(tracesUrl)
|
||||
defer func() {
|
||||
if res != nil {
|
||||
if err = res.Body.Close(); err != nil {
|
||||
z.logger.Error("Failed to close response body", "error", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
return traces, err
|
||||
}
|
||||
if err := json.NewDecoder(res.Body).Decode(&traces); err != nil {
|
||||
return traces, err
|
||||
}
|
||||
return traces, err
|
||||
}
|
||||
|
||||
// Trace returns trace for the given traceId
|
||||
// https://zipkin.io/zipkin-api/#/default/get_trace__traceId_
|
||||
func (z *ZipkinClient) Trace(traceId string) ([]model.SpanModel, error) {
|
||||
trace := []model.SpanModel{}
|
||||
if traceId == "" {
|
||||
return trace, errors.New("invalid/empty traceId")
|
||||
}
|
||||
|
||||
traceUrl, err := url.JoinPath(z.url, "/api/v2/trace", url.QueryEscape(traceId))
|
||||
if err != nil {
|
||||
return trace, backend.DownstreamError(fmt.Errorf("failed to join url: %w", err))
|
||||
}
|
||||
|
||||
res, err := z.httpClient.Get(traceUrl)
|
||||
defer func() {
|
||||
if res != nil {
|
||||
if err = res.Body.Close(); err != nil {
|
||||
z.logger.Error("Failed to close response body", "error", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
return trace, err
|
||||
}
|
||||
if err := json.NewDecoder(res.Body).Decode(&trace); err != nil {
|
||||
return trace, err
|
||||
}
|
||||
return trace, err
|
||||
}
|
||||
|
||||
func createZipkinURL(baseURL string, path string, params map[string]string) (string, error) {
|
||||
// Parse the base URL
|
||||
finalUrl, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Add the path
|
||||
urlPath, err := url.JoinPath(finalUrl.Path, path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
finalUrl.Path = urlPath
|
||||
// If there are query parameters, add them
|
||||
if len(params) > 0 {
|
||||
queryParams := finalUrl.Query()
|
||||
for k, v := range params {
|
||||
queryParams.Set(k, v)
|
||||
}
|
||||
finalUrl.RawQuery = queryParams.Encode()
|
||||
}
|
||||
// Return the composed URL as a string
|
||||
return finalUrl.String(), nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,357 @@
|
||||
package zipkin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||
"github.com/openzipkin/zipkin-go/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestZipkinClient_Services(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mockResponse string
|
||||
mockStatusCode int
|
||||
expectedResult []string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "Successful response",
|
||||
mockResponse: `["service1", "service2"]`,
|
||||
mockStatusCode: http.StatusOK,
|
||||
expectedResult: []string{"service1", "service2"},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Non-200 response",
|
||||
mockResponse: "",
|
||||
mockStatusCode: http.StatusInternalServerError,
|
||||
expectedResult: []string{},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid JSON response",
|
||||
mockResponse: `{invalid json`,
|
||||
mockStatusCode: http.StatusOK,
|
||||
expectedResult: []string{},
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/api/v2/services", r.URL.Path)
|
||||
w.WriteHeader(tt.mockStatusCode)
|
||||
_, _ = w.Write([]byte(tt.mockResponse))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client, _ := New(server.URL, server.Client(), log.New())
|
||||
services, err := client.Services()
|
||||
|
||||
if tt.expectError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.expectedResult, services)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestZipkinClient_Spans(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
serviceName string
|
||||
mockResponse string
|
||||
mockStatusCode int
|
||||
expectedResult []string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "Successful response",
|
||||
serviceName: "service1",
|
||||
mockResponse: `["span1", "span2"]`,
|
||||
mockStatusCode: http.StatusOK,
|
||||
expectedResult: []string{"span1", "span2"},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Non-200 response",
|
||||
serviceName: "service1",
|
||||
mockResponse: "",
|
||||
mockStatusCode: http.StatusNotFound,
|
||||
expectedResult: []string{},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid JSON response",
|
||||
serviceName: "service1",
|
||||
mockResponse: `{invalid json`,
|
||||
mockStatusCode: http.StatusOK,
|
||||
expectedResult: []string{},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "Empty serviceName",
|
||||
serviceName: "",
|
||||
mockResponse: "",
|
||||
mockStatusCode: http.StatusOK,
|
||||
expectedResult: []string{},
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/api/v2/spans", r.URL.Path)
|
||||
w.WriteHeader(tt.mockStatusCode)
|
||||
_, _ = w.Write([]byte(tt.mockResponse))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client, _ := New(server.URL, server.Client(), log.New())
|
||||
spans, err := client.Spans(tt.serviceName)
|
||||
|
||||
if tt.expectError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.expectedResult, spans)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestZipkinClient_Traces(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
serviceName string
|
||||
spanName string
|
||||
mockResponse interface{}
|
||||
mockStatusCode int
|
||||
expectedResult [][]model.SpanModel
|
||||
expectError bool
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "Successful response",
|
||||
serviceName: "service1",
|
||||
spanName: "span1",
|
||||
mockResponse: [][]model.SpanModel{{{SpanContext: model.SpanContext{TraceID: model.TraceID{Low: 1234}, ID: 1}, Name: "operation1", Tags: map[string]string{"key1": "value1"}}}},
|
||||
mockStatusCode: http.StatusOK,
|
||||
expectedResult: [][]model.SpanModel{{{SpanContext: model.SpanContext{TraceID: model.TraceID{Low: 1234}, ID: 1}, Name: "operation1", Tags: map[string]string{"key1": "value1"}}}},
|
||||
expectError: false,
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "Non-200 response",
|
||||
serviceName: "service1",
|
||||
spanName: "span1",
|
||||
mockResponse: nil,
|
||||
mockStatusCode: http.StatusForbidden,
|
||||
expectedResult: [][]model.SpanModel{},
|
||||
expectError: true,
|
||||
expectedError: "EOF",
|
||||
},
|
||||
{
|
||||
name: "Empty serviceName",
|
||||
serviceName: "",
|
||||
spanName: "span1",
|
||||
mockResponse: nil,
|
||||
mockStatusCode: http.StatusOK,
|
||||
expectedResult: [][]model.SpanModel{},
|
||||
expectError: true,
|
||||
expectedError: "invalid/empty serviceName",
|
||||
},
|
||||
{
|
||||
name: "Empty spanName",
|
||||
serviceName: "service1",
|
||||
spanName: "",
|
||||
mockResponse: nil,
|
||||
mockStatusCode: http.StatusOK,
|
||||
expectedResult: [][]model.SpanModel{},
|
||||
expectError: true,
|
||||
expectedError: "invalid/empty spanName",
|
||||
},
|
||||
{
|
||||
name: "Valid response with empty trace list",
|
||||
serviceName: "service1",
|
||||
spanName: "span1",
|
||||
mockResponse: [][]model.SpanModel{},
|
||||
mockStatusCode: http.StatusOK,
|
||||
expectedResult: [][]model.SpanModel{},
|
||||
expectError: false,
|
||||
expectedError: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var response []byte
|
||||
if mockData, ok := tt.mockResponse.([][]model.SpanModel); ok {
|
||||
response, _ = json.Marshal(mockData)
|
||||
} else if str, ok := tt.mockResponse.(string); ok {
|
||||
response = []byte(str)
|
||||
}
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/api/v2/traces", r.URL.Path)
|
||||
w.WriteHeader(tt.mockStatusCode)
|
||||
_, _ = w.Write(response)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client, _ := New(server.URL, server.Client(), log.New())
|
||||
traces, err := client.Traces(tt.serviceName, tt.spanName)
|
||||
|
||||
if tt.expectError {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, err.Error(), tt.expectedError)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.expectedResult, traces)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestZipkinClient_Trace(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
traceID string
|
||||
mockResponse string
|
||||
mockStatusCode int
|
||||
expectedResult []model.SpanModel
|
||||
expectError bool
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "Successful response",
|
||||
traceID: "trace-id",
|
||||
mockResponse: `[{"traceId":"00000000000004d2","id":"0000000000000001","name":"operation1","tags":{"key1":"value1"}}]`,
|
||||
mockStatusCode: http.StatusOK,
|
||||
expectedResult: []model.SpanModel{
|
||||
{
|
||||
SpanContext: model.SpanContext{
|
||||
TraceID: model.TraceID{Low: 1234},
|
||||
ID: model.ID(1),
|
||||
},
|
||||
Name: "operation1",
|
||||
Tags: map[string]string{"key1": "value1"},
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid traceID",
|
||||
traceID: "",
|
||||
mockResponse: "",
|
||||
mockStatusCode: http.StatusOK,
|
||||
expectedResult: []model.SpanModel{},
|
||||
expectError: true,
|
||||
expectedError: "invalid/empty traceId",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var client ZipkinClient
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "/api/v2/trace/"+tt.traceID, r.URL.Path)
|
||||
w.WriteHeader(tt.mockStatusCode)
|
||||
_, _ = w.Write([]byte(tt.mockResponse))
|
||||
}))
|
||||
defer server.Close()
|
||||
client, _ = New(server.URL, server.Client(), log.New())
|
||||
|
||||
trace, err := client.Trace(tt.traceID)
|
||||
|
||||
if tt.expectError {
|
||||
assert.Error(t, err)
|
||||
assert.Empty(t, trace)
|
||||
assert.Equal(t, tt.expectedError, err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedResult, trace)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateZipkinURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
baseURL string
|
||||
path string
|
||||
params map[string]string
|
||||
expected string
|
||||
shouldErr bool
|
||||
}{
|
||||
{
|
||||
name: "WithPathAndParams",
|
||||
baseURL: "http://example.com",
|
||||
path: "api/v1/trace",
|
||||
params: map[string]string{"key1": "value1", "key2": "value2"},
|
||||
expected: "http://example.com/api/v1/trace?key1=value1&key2=value2",
|
||||
},
|
||||
{
|
||||
name: "OnlyParams",
|
||||
baseURL: "http://example.com",
|
||||
path: "",
|
||||
params: map[string]string{"key1": "value1"},
|
||||
expected: "http://example.com?key1=value1",
|
||||
},
|
||||
{
|
||||
name: "NoParams",
|
||||
baseURL: "http://example.com",
|
||||
path: "api/v1/trace",
|
||||
params: map[string]string{},
|
||||
expected: "http://example.com/api/v1/trace",
|
||||
},
|
||||
{
|
||||
name: "InvalidBaseURL",
|
||||
baseURL: "http://example .com",
|
||||
path: "api/v1/trace",
|
||||
params: map[string]string{},
|
||||
shouldErr: true,
|
||||
},
|
||||
{
|
||||
name: "BaseURLWithPath",
|
||||
baseURL: "http://example.com/base",
|
||||
path: "api/v1/trace",
|
||||
params: map[string]string{"key1": "value1"},
|
||||
expected: "http://example.com/base/api/v1/trace?key1=value1",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result, err := createZipkinURL(tc.baseURL, tc.path, tc.params)
|
||||
|
||||
if tc.shouldErr {
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error, but got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if result != tc.expected {
|
||||
t.Errorf("Expected %s, got %s", tc.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package zipkin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||
)
|
||||
|
||||
func (s *Service) registerResourceRoutes() *http.ServeMux {
|
||||
router := http.NewServeMux()
|
||||
router.HandleFunc("GET /services", s.withDatasourceHandlerFunc(getServicesHandler))
|
||||
router.HandleFunc("GET /spans", s.withDatasourceHandlerFunc(getSpansHandler))
|
||||
router.HandleFunc("GET /traces", s.withDatasourceHandlerFunc(getTracesHandler))
|
||||
router.HandleFunc("GET /trace/{traceId}", s.withDatasourceHandlerFunc(getTraceHandler))
|
||||
return router
|
||||
}
|
||||
|
||||
func (s *Service) withDatasourceHandlerFunc(getHandler func(d *datasourceInfo) http.HandlerFunc) func(rw http.ResponseWriter, r *http.Request) {
|
||||
return func(rw http.ResponseWriter, r *http.Request) {
|
||||
client, err := s.getDSInfo(r.Context(), backend.PluginConfigFromContext(r.Context()))
|
||||
if err != nil {
|
||||
writeResponse(nil, errors.New("error getting data source information from context"), rw, client.ZipkinClient.logger)
|
||||
return
|
||||
}
|
||||
h := getHandler(client)
|
||||
h.ServeHTTP(rw, r)
|
||||
}
|
||||
}
|
||||
|
||||
func getServicesHandler(ds *datasourceInfo) http.HandlerFunc {
|
||||
return func(rw http.ResponseWriter, r *http.Request) {
|
||||
services, err := ds.ZipkinClient.Services()
|
||||
writeResponse(services, err, rw, ds.ZipkinClient.logger)
|
||||
}
|
||||
}
|
||||
|
||||
func getSpansHandler(ds *datasourceInfo) http.HandlerFunc {
|
||||
return func(rw http.ResponseWriter, r *http.Request) {
|
||||
serviceName := strings.TrimSpace(r.URL.Query().Get("serviceName"))
|
||||
spans, err := ds.ZipkinClient.Spans(serviceName)
|
||||
writeResponse(spans, err, rw, ds.ZipkinClient.logger)
|
||||
}
|
||||
}
|
||||
|
||||
func getTracesHandler(ds *datasourceInfo) http.HandlerFunc {
|
||||
return func(rw http.ResponseWriter, r *http.Request) {
|
||||
serviceName := strings.TrimSpace(r.URL.Query().Get("serviceName"))
|
||||
spanName := strings.TrimSpace(r.URL.Query().Get("spanName"))
|
||||
traces, err := ds.ZipkinClient.Traces(serviceName, spanName)
|
||||
writeResponse(traces, err, rw, ds.ZipkinClient.logger)
|
||||
}
|
||||
}
|
||||
|
||||
func getTraceHandler(ds *datasourceInfo) http.HandlerFunc {
|
||||
return func(rw http.ResponseWriter, r *http.Request) {
|
||||
traceId := strings.TrimSpace(r.PathValue("traceId"))
|
||||
trace, err := ds.ZipkinClient.Trace(traceId)
|
||||
writeResponse(trace, err, rw, ds.ZipkinClient.logger)
|
||||
}
|
||||
}
|
||||
|
||||
func writeResponse(res interface{}, err error, rw http.ResponseWriter, logger log.Logger) {
|
||||
if err != nil {
|
||||
// This is used for resource calls, we don't need to add actual error message, but we should log it
|
||||
logger.Warn("An error occurred while doing a resource call", "error", err)
|
||||
http.Error(rw, "An error occurred within the plugin", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// Response should not be string, but just in case, handle it
|
||||
if str, ok := res.(string); ok {
|
||||
rw.Header().Set("Content-Type", "text/plain")
|
||||
_, _ = rw.Write([]byte(str))
|
||||
return
|
||||
}
|
||||
b, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
// This is used for resource calls, we don't need to add actual error message, but we should log it
|
||||
logger.Warn("An error occurred while processing response from resource call", "error", err)
|
||||
http.Error(rw, "An error occurred within the plugin", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
_, _ = rw.Write(b)
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/httpclient"
|
||||
)
|
||||
@@ -81,3 +82,8 @@ func (s *Service) CheckHealth(ctx context.Context, req *backend.CheckHealthReque
|
||||
Message: "Data source is working",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
||||
handler := httpadapter.New(s.registerResourceRoutes())
|
||||
return handler.CallResource(ctx, req, sender)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user