2024-01-10 04:26:24 +08:00
|
|
|
package datasource
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
2024-02-01 02:36:51 +08:00
|
|
|
"fmt"
|
2024-01-10 04:26:24 +08:00
|
|
|
"net/http"
|
|
|
|
|
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
|
|
"k8s.io/apiserver/pkg/registry/rest"
|
2024-02-01 02:36:51 +08:00
|
|
|
|
2024-03-02 06:26:04 +08:00
|
|
|
query "github.com/grafana/grafana/pkg/apis/query/v0alpha1"
|
2024-02-01 02:36:51 +08:00
|
|
|
"github.com/grafana/grafana/pkg/middleware/requestmeta"
|
|
|
|
"github.com/grafana/grafana/pkg/tsdb/legacydata"
|
|
|
|
"github.com/grafana/grafana/pkg/web"
|
2024-01-10 04:26:24 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
type subQueryREST struct {
|
|
|
|
builder *DataSourceAPIBuilder
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ = rest.Connecter(&subQueryREST{})
|
|
|
|
|
|
|
|
func (r *subQueryREST) New() runtime.Object {
|
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
|
|
|
|
|
|
|
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) {
|
|
|
|
return nil, false, ""
|
|
|
|
}
|
|
|
|
|
2024-03-02 06:26:04 +08:00
|
|
|
func (r *subQueryREST) readQueries(req *http.Request) ([]backend.DataQuery, *query.DataSourceRef, error) {
|
|
|
|
reqDTO := query.GenericQueryRequest{}
|
|
|
|
if err := web.Bind(req, &reqDTO); err != nil {
|
2024-02-01 02:36:51 +08:00
|
|
|
return nil, nil, err
|
2024-01-10 04:26:24 +08:00
|
|
|
}
|
2024-02-01 02:36:51 +08:00
|
|
|
return legacydata.ToDataSourceQueries(reqDTO)
|
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)
|
2024-01-10 04:26:24 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-02-26 20:02:55 +08:00
|
|
|
ctx = backend.WithGrafanaConfig(ctx, pluginCtx.GrafanaConfig)
|
2024-01-10 04:26:24 +08:00
|
|
|
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
2024-02-01 02:36:51 +08:00
|
|
|
queries, dsRef, err := r.readQueries(req)
|
2024-01-10 04:26:24 +08:00
|
|
|
if err != nil {
|
|
|
|
responder.Error(err)
|
|
|
|
return
|
|
|
|
}
|
2024-02-01 02:36:51 +08:00
|
|
|
if dsRef != nil && dsRef.UID != name {
|
|
|
|
responder.Error(fmt.Errorf("expected the datasource in the request url and body to match"))
|
|
|
|
return
|
|
|
|
}
|
2024-01-10 04:26:24 +08:00
|
|
|
|
2024-02-01 02:36:51 +08:00
|
|
|
qdr, err := r.builder.client.QueryData(ctx, &backend.QueryDataRequest{
|
2024-01-19 22:56:52 +08:00
|
|
|
PluginContext: pluginCtx,
|
2024-01-10 04:26:24 +08:00
|
|
|
Queries: queries,
|
|
|
|
})
|
|
|
|
if err != nil {
|
2024-02-01 02:36:51 +08:00
|
|
|
responder.Error(err)
|
2024-01-10 04:26:24 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-02-01 02:36:51 +08:00
|
|
|
statusCode := http.StatusOK
|
|
|
|
for _, res := range qdr.Responses {
|
|
|
|
if res.Error != nil {
|
|
|
|
statusCode = http.StatusMultiStatus
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if statusCode != http.StatusOK {
|
|
|
|
requestmeta.WithDownstreamStatusSource(ctx)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO... someday :) can return protobuf for machine-machine communication
|
|
|
|
// will avoid some hops the current response workflow (for external plugins)
|
|
|
|
// 1. Plugin:
|
|
|
|
// creates: golang structs
|
|
|
|
// returns: arrow + protobuf |
|
|
|
|
// 2. Client: | direct when local/non grpc
|
|
|
|
// reads: protobuf+arrow V
|
|
|
|
// returns: golang structs
|
|
|
|
// 3. Datasource Server (eg right here):
|
|
|
|
// reads: golang structs
|
|
|
|
// returns: JSON
|
|
|
|
// 4. Query service (alerting etc):
|
|
|
|
// reads: JSON? (TODO! raw output from 1???)
|
|
|
|
// returns: JSON (after more operations)
|
|
|
|
// 5. Browser
|
|
|
|
// reads: JSON
|
|
|
|
w.WriteHeader(statusCode)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
err = json.NewEncoder(w).Encode(qdr)
|
2024-01-10 04:26:24 +08:00
|
|
|
if err != nil {
|
|
|
|
responder.Error(err)
|
|
|
|
}
|
|
|
|
}), nil
|
|
|
|
}
|