From 8f9e5a839fe4d5cfdc759737105aabe9cf3cc3c1 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Thu, 12 Nov 2020 17:16:04 +0100 Subject: [PATCH] Plugins: Bring back coreplugin package (#29064) Signed-off-by: Arve Knudsen --- .../backendplugin/coreplugin/core_plugin.go | 83 +++++++++++++ .../coreplugin/core_plugin_test.go | 67 +++++++++++ .../coreplugin/query_endpoint_adapter.go | 113 ++++++++++++++++++ 3 files changed, 263 insertions(+) create mode 100644 pkg/plugins/backendplugin/coreplugin/core_plugin.go create mode 100644 pkg/plugins/backendplugin/coreplugin/core_plugin_test.go create mode 100644 pkg/plugins/backendplugin/coreplugin/query_endpoint_adapter.go diff --git a/pkg/plugins/backendplugin/coreplugin/core_plugin.go b/pkg/plugins/backendplugin/coreplugin/core_plugin.go new file mode 100644 index 00000000000..d746e2b46bd --- /dev/null +++ b/pkg/plugins/backendplugin/coreplugin/core_plugin.go @@ -0,0 +1,83 @@ +package coreplugin + +import ( + "context" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/plugins/backendplugin" + "github.com/grafana/grafana/pkg/tsdb" +) + +// corePlugin represents a plugin that's part of Grafana core. +// nolint:unused +type corePlugin struct { + pluginID string + logger log.Logger + backend.CheckHealthHandler + backend.CallResourceHandler + backend.QueryDataHandler +} + +// New returns a new backendplugin.PluginFactoryFunc for creating a core (built-in) backendplugin.Plugin. +func New(opts backend.ServeOpts) backendplugin.PluginFactoryFunc { + return backendplugin.PluginFactoryFunc(func(pluginID string, logger log.Logger, env []string) (backendplugin.Plugin, error) { + return &corePlugin{ + pluginID: pluginID, + logger: logger, + CheckHealthHandler: opts.CheckHealthHandler, + CallResourceHandler: opts.CallResourceHandler, + QueryDataHandler: opts.QueryDataHandler, + }, nil + }) +} + +func (cp *corePlugin) PluginID() string { + return cp.pluginID +} + +func (cp *corePlugin) Logger() log.Logger { + return cp.logger +} + +func (cp *corePlugin) Start(ctx context.Context) error { + if cp.QueryDataHandler != nil { + tsdb.RegisterTsdbQueryEndpoint(cp.pluginID, func(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint, error) { + return newQueryEndpointAdapter(cp.pluginID, cp.logger, backendplugin.InstrumentQueryDataHandler(cp.QueryDataHandler)), nil + }) + } + return nil +} + +func (cp *corePlugin) Stop(ctx context.Context) error { + return nil +} + +func (cp *corePlugin) IsManaged() bool { + return false +} + +func (cp *corePlugin) Exited() bool { + return false +} + +func (cp *corePlugin) CollectMetrics(ctx context.Context) (*backend.CollectMetricsResult, error) { + return nil, backendplugin.ErrMethodNotImplemented +} + +func (cp *corePlugin) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { + if cp.CheckHealthHandler != nil { + return cp.CheckHealthHandler.CheckHealth(ctx, req) + } + + return nil, backendplugin.ErrMethodNotImplemented +} + +func (cp *corePlugin) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error { + if cp.CallResourceHandler != nil { + return cp.CallResourceHandler.CallResource(ctx, req, sender) + } + + return backendplugin.ErrMethodNotImplemented +} diff --git a/pkg/plugins/backendplugin/coreplugin/core_plugin_test.go b/pkg/plugins/backendplugin/coreplugin/core_plugin_test.go new file mode 100644 index 00000000000..d9fa8be7f2a --- /dev/null +++ b/pkg/plugins/backendplugin/coreplugin/core_plugin_test.go @@ -0,0 +1,67 @@ +package coreplugin_test + +import ( + "context" + "testing" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/plugins/backendplugin" + "github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin" + "github.com/stretchr/testify/require" +) + +func TestCorePlugin(t *testing.T) { + t.Run("New core plugin with empty opts should return expected values", func(t *testing.T) { + factory := coreplugin.New(backend.ServeOpts{}) + p, err := factory("plugin", log.New("test"), nil) + require.NoError(t, err) + require.NotNil(t, p) + require.NoError(t, p.Start(context.Background())) + require.NoError(t, p.Stop(context.Background())) + require.False(t, p.IsManaged()) + require.False(t, p.Exited()) + + _, err = p.CollectMetrics(context.Background()) + require.Equal(t, backendplugin.ErrMethodNotImplemented, err) + + _, err = p.CheckHealth(context.Background(), nil) + require.Equal(t, backendplugin.ErrMethodNotImplemented, err) + + err = p.CallResource(context.Background(), nil, nil) + require.Equal(t, backendplugin.ErrMethodNotImplemented, err) + }) + + t.Run("New core plugin with handlers set in opts should return expected values", func(t *testing.T) { + checkHealthCalled := false + callResourceCalled := false + factory := coreplugin.New(backend.ServeOpts{ + CheckHealthHandler: backend.CheckHealthHandlerFunc(func(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { + checkHealthCalled = true + return nil, nil + }), + CallResourceHandler: backend.CallResourceHandlerFunc(func(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error { + callResourceCalled = true + return nil + }), + }) + p, err := factory("plugin", log.New("test"), nil) + require.NoError(t, err) + require.NotNil(t, p) + require.NoError(t, p.Start(context.Background())) + require.NoError(t, p.Stop(context.Background())) + require.False(t, p.IsManaged()) + require.False(t, p.Exited()) + + _, err = p.CollectMetrics(context.Background()) + require.Equal(t, backendplugin.ErrMethodNotImplemented, err) + + _, err = p.CheckHealth(context.Background(), &backend.CheckHealthRequest{}) + require.NoError(t, err) + require.True(t, checkHealthCalled) + + err = p.CallResource(context.Background(), &backend.CallResourceRequest{}, nil) + require.NoError(t, err) + require.True(t, callResourceCalled) + }) +} diff --git a/pkg/plugins/backendplugin/coreplugin/query_endpoint_adapter.go b/pkg/plugins/backendplugin/coreplugin/query_endpoint_adapter.go new file mode 100644 index 00000000000..311d06cbf4b --- /dev/null +++ b/pkg/plugins/backendplugin/coreplugin/query_endpoint_adapter.go @@ -0,0 +1,113 @@ +package coreplugin + +import ( + "context" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/plugins/datasource/wrapper" + "github.com/grafana/grafana/pkg/tsdb" +) + +func newQueryEndpointAdapter(pluginID string, logger log.Logger, handler backend.QueryDataHandler) tsdb.TsdbQueryEndpoint { + return &queryEndpointAdapter{ + pluginID: pluginID, + logger: logger, + handler: handler, + } +} + +type queryEndpointAdapter struct { + pluginID string + logger log.Logger + handler backend.QueryDataHandler +} + +func modelToInstanceSettings(ds *models.DataSource) (*backend.DataSourceInstanceSettings, error) { + jsonDataBytes, err := ds.JsonData.MarshalJSON() + if err != nil { + return nil, err + } + + return &backend.DataSourceInstanceSettings{ + ID: ds.Id, + Name: ds.Name, + URL: ds.Url, + Database: ds.Database, + User: ds.User, + BasicAuthEnabled: ds.BasicAuth, + BasicAuthUser: ds.BasicAuthUser, + JSONData: jsonDataBytes, + DecryptedSecureJSONData: ds.DecryptedValues(), + Updated: ds.Updated, + }, nil +} + +func (a *queryEndpointAdapter) Query(ctx context.Context, ds *models.DataSource, query *tsdb.TsdbQuery) (*tsdb.Response, error) { + instanceSettings, err := modelToInstanceSettings(ds) + if err != nil { + return nil, err + } + + req := &backend.QueryDataRequest{ + PluginContext: backend.PluginContext{ + OrgID: ds.OrgId, + PluginID: a.pluginID, + User: wrapper.BackendUserFromSignedInUser(query.User), + DataSourceInstanceSettings: instanceSettings, + }, + Queries: []backend.DataQuery{}, + Headers: query.Headers, + } + + for _, q := range query.Queries { + modelJSON, err := q.Model.MarshalJSON() + if err != nil { + return nil, err + } + req.Queries = append(req.Queries, backend.DataQuery{ + RefID: q.RefId, + Interval: time.Duration(q.IntervalMs) * time.Millisecond, + MaxDataPoints: q.MaxDataPoints, + TimeRange: backend.TimeRange{ + From: query.TimeRange.GetFromAsTimeUTC(), + To: query.TimeRange.GetToAsTimeUTC(), + }, + QueryType: q.QueryType, + JSON: modelJSON, + }) + } + + resp, err := a.handler.QueryData(ctx, req) + if err != nil { + return nil, err + } + + tR := &tsdb.Response{ + Results: make(map[string]*tsdb.QueryResult, len(resp.Responses)), + } + + for refID, r := range resp.Responses { + qr := &tsdb.QueryResult{ + RefId: refID, + } + + for _, f := range r.Frames { + if f.RefID == "" { + f.RefID = refID + } + } + + qr.Dataframes = tsdb.NewDecodedDataFrames(r.Frames) + + if r.Error != nil { + qr.Error = r.Error + } + + tR.Results[refID] = qr + } + + return tR, nil +}