2024-01-10 04:26:24 +08:00
|
|
|
package datasource
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2025-02-11 01:07:51 +08:00
|
|
|
"errors"
|
2024-02-01 02:36:51 +08:00
|
|
|
"fmt"
|
2024-01-10 04:26:24 +08:00
|
|
|
"net/http"
|
|
|
|
|
2025-08-29 22:46:39 +08:00
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
|
|
"k8s.io/apiserver/pkg/registry/rest"
|
|
|
|
|
2024-01-10 04:26:24 +08:00
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
2024-03-09 00:12:59 +08:00
|
|
|
data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1"
|
2025-10-03 03:40:30 +08:00
|
|
|
"github.com/grafana/grafana/pkg/apimachinery/errutil"
|
2024-09-04 00:01:27 +08:00
|
|
|
query "github.com/grafana/grafana/pkg/apis/query/v0alpha1"
|
|
|
|
query_headers "github.com/grafana/grafana/pkg/registry/apis/query"
|
2025-02-11 01:07:51 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/datasources"
|
2024-02-01 02:36:51 +08:00
|
|
|
|
|
|
|
"github.com/grafana/grafana/pkg/web"
|
2024-01-10 04:26:24 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
type subQueryREST struct {
|
|
|
|
builder *DataSourceAPIBuilder
|
|
|
|
}
|
|
|
|
|
2024-03-09 00:12:59 +08:00
|
|
|
var (
|
|
|
|
_ rest.Storage = (*subQueryREST)(nil)
|
|
|
|
_ rest.Connecter = (*subQueryREST)(nil)
|
|
|
|
_ rest.StorageMetadata = (*subQueryREST)(nil)
|
2025-08-29 22:46:39 +08:00
|
|
|
_ rest.Scoper = (*subQueryREST)(nil)
|
2024-03-09 00:12:59 +08:00
|
|
|
)
|
2024-01-10 04:26:24 +08:00
|
|
|
|
|
|
|
func (r *subQueryREST) New() runtime.Object {
|
2024-03-09 00:12:59 +08:00
|
|
|
// This is added as the "ResponseType" regarless what ProducesObject() says :)
|
2024-03-02 06:26:04 +08:00
|
|
|
return &query.QueryDataResponse{}
|
2024-01-10 04:26:24 +08:00
|
|
|
}
|
|
|
|
|
2024-02-01 02:36:51 +08:00
|
|
|
func (r *subQueryREST) Destroy() {}
|
2024-01-10 04:26:24 +08:00
|
|
|
|
2025-08-29 22:46:39 +08:00
|
|
|
func (r *subQueryREST) NamespaceScoped() bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2024-03-09 00:12:59 +08:00
|
|
|
func (r *subQueryREST) ProducesMIMETypes(verb string) []string {
|
|
|
|
return []string{"application/json"} // and parquet!
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *subQueryREST) ProducesObject(verb string) interface{} {
|
|
|
|
return &query.QueryDataResponse{}
|
|
|
|
}
|
|
|
|
|
2024-01-10 04:26:24 +08:00
|
|
|
func (r *subQueryREST) ConnectMethods() []string {
|
2024-03-02 06:26:04 +08:00
|
|
|
return []string{"POST"}
|
2024-01-10 04:26:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *subQueryREST) NewConnectOptions() (runtime.Object, bool, string) {
|
2024-03-09 00:12:59 +08:00
|
|
|
return nil, false, "" // true means you can use the trailing path as a variable
|
2024-01-10 04:26:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *subQueryREST) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) {
|
2024-01-23 03:32:25 +08:00
|
|
|
pluginCtx, err := r.builder.getPluginContext(ctx, name)
|
2025-02-11 01:07:51 +08:00
|
|
|
|
2024-01-10 04:26:24 +08:00
|
|
|
if err != nil {
|
2025-02-11 01:07:51 +08:00
|
|
|
if errors.Is(err, datasources.ErrDataSourceNotFound) {
|
2025-08-29 22:46:39 +08:00
|
|
|
return nil, r.builder.datasourceResourceInfo.NewNotFound(name)
|
2025-02-11 01:07:51 +08:00
|
|
|
}
|
2024-01-10 04:26:24 +08:00
|
|
|
return nil, err
|
|
|
|
}
|
2025-02-11 01:07:51 +08:00
|
|
|
|
2024-01-10 04:26:24 +08:00
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
2024-03-09 00:12:59 +08:00
|
|
|
dqr := data.QueryDataRequest{}
|
|
|
|
err := web.Bind(req, &dqr)
|
2024-01-10 04:26:24 +08:00
|
|
|
if err != nil {
|
|
|
|
responder.Error(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-06-06 04:31:06 +08:00
|
|
|
queries, dsRef, err := data.ToDataSourceQueries(dqr)
|
2024-01-10 04:26:24 +08:00
|
|
|
if err != nil {
|
2024-02-01 02:36:51 +08:00
|
|
|
responder.Error(err)
|
2024-01-10 04:26:24 +08:00
|
|
|
return
|
|
|
|
}
|
2024-03-09 00:12:59 +08:00
|
|
|
if dsRef != nil && dsRef.UID != name {
|
|
|
|
responder.Error(fmt.Errorf("expected query body datasource and request to match"))
|
2024-03-19 16:02:52 +08:00
|
|
|
return
|
2024-02-01 02:36:51 +08:00
|
|
|
}
|
|
|
|
|
2024-03-09 00:12:59 +08:00
|
|
|
ctx = backend.WithGrafanaConfig(ctx, pluginCtx.GrafanaConfig)
|
2024-03-13 17:14:16 +08:00
|
|
|
ctx = contextualMiddlewares(ctx)
|
2024-08-29 23:06:25 +08:00
|
|
|
|
2024-03-09 00:12:59 +08:00
|
|
|
rsp, err := r.builder.client.QueryData(ctx, &backend.QueryDataRequest{
|
|
|
|
Queries: queries,
|
|
|
|
PluginContext: pluginCtx,
|
2024-09-04 00:01:27 +08:00
|
|
|
Headers: query_headers.ExtractKnownHeaders(req.Header),
|
2024-03-09 00:12:59 +08:00
|
|
|
})
|
2025-10-03 03:40:30 +08:00
|
|
|
|
|
|
|
// all errors get converted into k8 errors when sent in responder.Error and lose important context like downstream info
|
|
|
|
var e errutil.Error
|
|
|
|
if errors.As(err, &e) && e.Source == errutil.SourceDownstream {
|
|
|
|
responder.Object(int(backend.StatusBadRequest),
|
|
|
|
&query.QueryDataResponse{QueryDataResponse: backend.QueryDataResponse{Responses: map[string]backend.DataResponse{
|
|
|
|
"A": {
|
|
|
|
Error: errors.New(e.LogMessage),
|
|
|
|
ErrorSource: backend.ErrorSourceDownstream,
|
|
|
|
Status: backend.StatusBadRequest,
|
|
|
|
},
|
|
|
|
}}},
|
|
|
|
)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-01-10 04:26:24 +08:00
|
|
|
if err != nil {
|
|
|
|
responder.Error(err)
|
2024-03-09 00:12:59 +08:00
|
|
|
return
|
2024-01-10 04:26:24 +08:00
|
|
|
}
|
2024-03-09 00:12:59 +08:00
|
|
|
responder.Object(query.GetResponseCode(rsp),
|
|
|
|
&query.QueryDataResponse{QueryDataResponse: *rsp},
|
|
|
|
)
|
2024-01-10 04:26:24 +08:00
|
|
|
}), nil
|
|
|
|
}
|