DataSources: Add datasource fetching + querying interface (#80749)

* first pass

* separate oss + enterprise

* tidy things up

* add ctx

* fix tests

* use standalone svcs

* mv plugin context provide

* fix wire

* fix import
pull/80820/head
Will Browne 1 year ago committed by GitHub
parent bb0fa4f99a
commit 3f30cbf91c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 8
      pkg/api/metrics_test.go
  2. 4
      pkg/api/plugin_resource_test.go
  3. 3
      pkg/expr/dataplane_test.go
  4. 4
      pkg/expr/service_test.go
  5. 50
      pkg/registry/apis/datasource/connections.go
  6. 11
      pkg/registry/apis/datasource/plugincontext.go
  7. 169
      pkg/registry/apis/datasource/querier.go
  8. 125
      pkg/registry/apis/datasource/register.go
  9. 41
      pkg/registry/apis/datasource/standalone.go
  10. 24
      pkg/registry/apis/datasource/standalone_services.go
  11. 6
      pkg/registry/apis/datasource/sub_health.go
  12. 13
      pkg/registry/apis/datasource/sub_proxy.go
  13. 6
      pkg/registry/apis/datasource/sub_query.go
  14. 6
      pkg/registry/apis/datasource/sub_resource.go
  15. 6
      pkg/registry/apis/wireset.go
  16. 49
      pkg/services/pluginsintegration/plugincontext/plugincontext.go
  17. 2
      pkg/services/pluginsintegration/plugincontext/plugincontext_test.go
  18. 2
      pkg/services/pluginsintegration/pluginsintegration.go
  19. 3
      pkg/services/publicdashboards/api/common_test.go
  20. 2
      pkg/services/query/query_test.go
  21. 4
      pkg/tsdb/legacydata/service/service_test.go

@ -77,8 +77,8 @@ func TestAPIEndpoint_Metrics_QueryMetricsV2(t *testing.T) {
},
},
},
}, &fakeDatasources.FakeDataSourceService{}, pluginSettings.ProvideService(dbtest.NewFakeDB(),
secretstest.NewFakeSecretsService()), pluginFakes.NewFakeLicensingService(), &config.Cfg{}),
}, &fakeDatasources.FakeCacheService{}, &fakeDatasources.FakeDataSourceService{},
pluginSettings.ProvideService(dbtest.NewFakeDB(), secretstest.NewFakeSecretsService()), pluginFakes.NewFakeLicensingService(), &config.Cfg{}),
)
serverFeatureEnabled := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.queryDataService = qds
@ -124,6 +124,7 @@ func TestAPIEndpoint_Metrics_PluginDecryptionFailure(t *testing.T) {
},
},
},
&fakeDatasources.FakeCacheService{},
ds, pluginSettings.ProvideService(db, secretstest.NewFakeSecretsService()), pluginFakes.NewFakeLicensingService(), &config.Cfg{},
)
qds := query.ProvideService(
@ -300,7 +301,8 @@ func TestDataSourceQueryError(t *testing.T) {
plugincontext.ProvideService(cfg, localcache.ProvideService(), &pluginstore.FakePluginStore{
PluginList: []pluginstore.Plugin{pluginstore.ToGrafanaDTO(p)},
},
ds, pluginSettings.ProvideService(dbtest.NewFakeDB(),
&fakeDatasources.FakeCacheService{}, ds,
pluginSettings.ProvideService(dbtest.NewFakeDB(),
secretstest.NewFakeSecretsService()), pluginFakes.NewFakeLicensingService(), &config.Cfg{}),
)
hs.QuotaService = quotatest.New(false, nil)

@ -55,8 +55,8 @@ func TestCallResource(t *testing.T) {
textCtx := pluginsintegration.CreateIntegrationTestCtx(t, cfg, coreRegistry)
pcp := plugincontext.ProvideService(cfg, localcache.ProvideService(), textCtx.PluginStore, &datasources.FakeDataSourceService{},
pluginSettings.ProvideService(db.InitTestDB(t), fakeSecrets.NewFakeSecretsService()), nil, &pCfg)
pcp := plugincontext.ProvideService(cfg, localcache.ProvideService(), textCtx.PluginStore, &datasources.FakeCacheService{},
&datasources.FakeDataSourceService{}, pluginSettings.ProvideService(db.InitTestDB(t), fakeSecrets.NewFakeSecretsService()), nil, &pCfg)
srv := SetupAPITestServer(t, func(hs *HTTPServer) {
hs.Cfg = cfg

@ -61,7 +61,8 @@ func framesPassThroughService(t *testing.T, frames data.Frames) (data.Frames, er
PluginList: []pluginstore.Plugin{
{JSONData: plugins.JSONData{ID: "test"}},
}},
&datafakes.FakeDataSourceService{}, nil, pluginFakes.NewFakeLicensingService(), &config.Cfg{}),
&datafakes.FakeCacheService{}, &datafakes.FakeDataSourceService{},
nil, pluginFakes.NewFakeLicensingService(), &config.Cfg{}),
tracer: tracing.InitializeTracerForTest(),
metrics: newMetrics(nil),
}

@ -42,7 +42,7 @@ func TestService(t *testing.T) {
PluginList: []pluginstore.Plugin{
{JSONData: plugins.JSONData{ID: "test"}},
},
}, &datafakes.FakeDataSourceService{}, nil, fakes.NewFakeLicensingService(), &config.Cfg{})
}, &datafakes.FakeCacheService{}, &datafakes.FakeDataSourceService{}, nil, fakes.NewFakeLicensingService(), &config.Cfg{})
s := Service{
cfg: setting.NewCfg(),
@ -128,7 +128,7 @@ func TestDSQueryError(t *testing.T) {
PluginList: []pluginstore.Plugin{
{JSONData: plugins.JSONData{ID: "test"}},
},
}, &datafakes.FakeDataSourceService{}, nil, nil, &config.Cfg{})
}, &datafakes.FakeCacheService{}, &datafakes.FakeDataSourceService{}, nil, nil, &config.Cfg{})
s := Service{
cfg: setting.NewCfg(),

@ -2,18 +2,13 @@ package datasource
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
common "github.com/grafana/grafana/pkg/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/utils"
)
var (
@ -27,7 +22,7 @@ var (
type connectionAccess struct {
resourceInfo common.ResourceInfo
tableConverter rest.TableConvertor
builder *DataSourceAPIBuilder
builder Querier
}
func (s *connectionAccess) New() runtime.Object {
@ -57,48 +52,9 @@ func (s *connectionAccess) ConvertToTable(ctx context.Context, object runtime.Ob
}
func (s *connectionAccess) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
ns := request.NamespaceValue(ctx)
ds, err := s.builder.getDataSource(ctx, name)
if err != nil {
return nil, err
}
return s.asConnection(ds, ns)
return s.builder.Datasource(ctx, name)
}
func (s *connectionAccess) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
ns := request.NamespaceValue(ctx)
if ns == "" {
// require a namespace so we do not need to support reverse mappings (yet)
return nil, fmt.Errorf("missing namespace in request URL")
}
result := &v0alpha1.DataSourceConnectionList{
Items: []v0alpha1.DataSourceConnection{},
}
vals, err := s.builder.getDataSources(ctx)
if err == nil {
for _, ds := range vals {
v, _ := s.asConnection(ds, ns)
result.Items = append(result.Items, *v)
}
}
return result, err
}
func (s *connectionAccess) asConnection(ds *datasources.DataSource, ns string) (*v0alpha1.DataSourceConnection, error) {
v := &v0alpha1.DataSourceConnection{
TypeMeta: s.resourceInfo.TypeMeta(),
ObjectMeta: metav1.ObjectMeta{
Name: ds.UID,
Namespace: ns,
CreationTimestamp: metav1.NewTime(ds.Created),
ResourceVersion: fmt.Sprintf("%d", ds.Updated.UnixMilli()),
},
Title: ds.Name,
}
v.UID = utils.CalculateClusterWideUID(v) // indicates if the value changed on the server
meta, err := utils.MetaAccessor(v)
if err != nil {
meta.SetUpdatedTimestamp(&ds.Updated)
}
return v, err
return s.builder.Datasources(ctx)
}

@ -0,0 +1,11 @@
package datasource
import (
"context"
"github.com/grafana/grafana-plugin-sdk-go/backend"
)
type PluginContextProvider interface {
PluginContextForDataSource(ctx context.Context, pluginID, name string) (backend.PluginContext, error)
}

@ -0,0 +1,169 @@
package datasource
import (
"context"
"fmt"
"github.com/grafana/grafana-plugin-sdk-go/backend"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
common "github.com/grafana/grafana/pkg/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/utils"
)
type QuerierFactoryFunc func(ctx context.Context, ri common.ResourceInfo, pj plugins.JSONData) (Querier, error)
type QuerierProvider interface {
Querier(ctx context.Context, ri common.ResourceInfo, pj plugins.JSONData) (Querier, error)
}
type DefaultQuerierProvider struct {
factory QuerierFactoryFunc
}
func ProvideDefaultQuerierProvider(pluginClient plugins.Client, dsService datasources.DataSourceService,
dsCache datasources.CacheService) *DefaultQuerierProvider {
return NewQuerierProvider(func(ctx context.Context, ri common.ResourceInfo, pj plugins.JSONData) (Querier, error) {
return NewDefaultQuerier(ri, pj, pluginClient, dsService, dsCache), nil
})
}
func NewQuerierProvider(factory QuerierFactoryFunc) *DefaultQuerierProvider {
return &DefaultQuerierProvider{
factory: factory,
}
}
func (p *DefaultQuerierProvider) Querier(ctx context.Context, ri common.ResourceInfo, pj plugins.JSONData) (Querier, error) {
return p.factory(ctx, ri, pj)
}
// Querier is the interface that wraps the Query method.
type Querier interface {
// Query runs the query on behalf of the user in context.
Query(ctx context.Context, query *backend.QueryDataRequest) (*backend.QueryDataResponse, error)
// Health checks the health of the plugin.
Health(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error)
// Resource gets a resource plugin.
Resource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error
// Datasource gets all data source plugins (with elevated permissions).
Datasource(ctx context.Context, name string) (*v0alpha1.DataSourceConnection, error)
// Datasources lists all data sources (with elevated permissions).
Datasources(ctx context.Context) (*v0alpha1.DataSourceConnectionList, error)
}
type DefaultQuerier struct {
connectionResourceInfo common.ResourceInfo
pluginJSON plugins.JSONData
pluginClient plugins.Client
dsService datasources.DataSourceService
dsCache datasources.CacheService
}
func NewDefaultQuerier(
connectionResourceInfo common.ResourceInfo,
pluginJSON plugins.JSONData,
pluginClient plugins.Client,
dsService datasources.DataSourceService,
dsCache datasources.CacheService,
) *DefaultQuerier {
return &DefaultQuerier{
connectionResourceInfo: connectionResourceInfo,
pluginJSON: pluginJSON,
pluginClient: pluginClient,
dsService: dsService,
dsCache: dsCache,
}
}
func (q *DefaultQuerier) Query(ctx context.Context, query *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
_, err := request.NamespaceInfoFrom(ctx, true)
if err != nil {
return nil, err
}
return q.pluginClient.QueryData(ctx, query)
}
func (q *DefaultQuerier) Resource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
_, err := request.NamespaceInfoFrom(ctx, true)
if err != nil {
return err
}
return q.pluginClient.CallResource(ctx, req, sender)
}
func (q *DefaultQuerier) Health(ctx context.Context, query *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
_, err := request.NamespaceInfoFrom(ctx, true)
if err != nil {
return nil, err
}
return q.pluginClient.CheckHealth(ctx, query)
}
func (q *DefaultQuerier) Datasource(ctx context.Context, name string) (*v0alpha1.DataSourceConnection, error) {
info, err := request.NamespaceInfoFrom(ctx, true)
if err != nil {
return nil, err
}
user, err := appcontext.User(ctx)
if err != nil {
return nil, err
}
ds, err := q.dsCache.GetDatasourceByUID(ctx, name, user, false)
if err != nil {
return nil, err
}
return asConnection(q.connectionResourceInfo.TypeMeta(), ds, info.Value)
}
func (q *DefaultQuerier) Datasources(ctx context.Context) (*v0alpha1.DataSourceConnectionList, error) {
info, err := request.NamespaceInfoFrom(ctx, true)
if err != nil {
return nil, err
}
ds, err := q.dsService.GetDataSourcesByType(ctx, &datasources.GetDataSourcesByTypeQuery{
OrgID: info.OrgID,
Type: q.pluginJSON.ID,
})
if err != nil {
return nil, err
}
return asConnectionList(q.connectionResourceInfo.TypeMeta(), ds, info.Value)
}
func asConnection(typeMeta metav1.TypeMeta, ds *datasources.DataSource, ns string) (*v0alpha1.DataSourceConnection, error) {
v := &v0alpha1.DataSourceConnection{
TypeMeta: typeMeta,
ObjectMeta: metav1.ObjectMeta{
Name: ds.UID,
Namespace: ns,
CreationTimestamp: metav1.NewTime(ds.Created),
ResourceVersion: fmt.Sprintf("%d", ds.Updated.UnixMilli()),
},
Title: ds.Name,
}
v.UID = utils.CalculateClusterWideUID(v) // indicates if the value changed on the server
meta, err := utils.MetaAccessor(v)
if err != nil {
meta.SetUpdatedTimestamp(&ds.Updated)
}
return v, err
}
func asConnectionList(typeMeta metav1.TypeMeta, dss []*datasources.DataSource, ns string) (*v0alpha1.DataSourceConnectionList, error) {
result := &v0alpha1.DataSourceConnectionList{
Items: []v0alpha1.DataSourceConnection{},
}
for _, ds := range dss {
v, _ := asConnection(typeMeta, ds, ns)
result.Items = append(result.Items, *v)
}
return result, nil
}

@ -5,7 +5,6 @@ import (
"fmt"
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
@ -19,39 +18,32 @@ import (
common "github.com/grafana/grafana/pkg/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt"
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/utils"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
"github.com/grafana/grafana/pkg/setting"
)
var _ grafanaapiserver.APIGroupBuilder = (*DataSourceAPIBuilder)(nil)
// This is used just so wire has something unique to return
// DataSourceAPIBuilder is used just so wire has something unique to return
type DataSourceAPIBuilder struct {
connectionResourceInfo common.ResourceInfo
plugin plugins.JSONData
client plugins.Client
dsService datasources.DataSourceService
dsCache datasources.CacheService
pluginJSON plugins.JSONData
querier Querier
pluginContext PluginContextProvider
accessControl accesscontrol.AccessControl
}
func RegisterAPIService(
cfg *setting.Cfg,
querierProvider QuerierProvider,
features featuremgmt.FeatureToggles,
apiregistration grafanaapiserver.APIRegistrar,
pluginClient plugins.Client,
apiRegistrar grafanaapiserver.APIRegistrar,
pluginContext PluginContextProvider,
pluginStore pluginstore.Store,
dsService datasources.DataSourceService,
dsCache datasources.CacheService,
accessControl accesscontrol.AccessControl,
) (*DataSourceAPIBuilder, error) {
// This requires devmode!
@ -71,31 +63,35 @@ func RegisterAPIService(
continue // skip this one
}
builder, err = NewDataSourceAPIBuilder(ds.JSONData, pluginClient, dsService, dsCache, accessControl)
builder, err = NewDataSourceAPIBuilder(ds.JSONData, querierProvider, pluginContext, accessControl)
if err != nil {
return nil, err
}
apiregistration.RegisterAPI(builder)
apiRegistrar.RegisterAPI(builder)
}
return builder, nil // only used for wire
}
func NewDataSourceAPIBuilder(
plugin plugins.JSONData,
client plugins.Client,
dsService datasources.DataSourceService,
dsCache datasources.CacheService,
querierProvider QuerierProvider,
pluginContext PluginContextProvider,
accessControl accesscontrol.AccessControl) (*DataSourceAPIBuilder, error) {
group, err := getDatasourceGroupNameFromPluginID(plugin.ID)
ri, err := resourceFromPluginID(plugin.ID)
if err != nil {
return nil, err
}
querier, err := querierProvider.Querier(context.Background(), ri, plugin)
if err != nil {
return nil, err
}
return &DataSourceAPIBuilder{
connectionResourceInfo: v0alpha1.GenericConnectionResourceInfo.WithGroupAndShortName(group, plugin.ID+"-connection"),
plugin: plugin,
client: client,
dsService: dsService,
dsCache: dsCache,
connectionResourceInfo: ri,
pluginJSON: plugin,
querier: querier,
pluginContext: pluginContext,
accessControl: accessControl,
}, nil
}
@ -135,16 +131,24 @@ func (b *DataSourceAPIBuilder) InstallSchema(scheme *runtime.Scheme) error {
return scheme.SetVersionPriority(gv)
}
func resourceFromPluginID(pluginID string) (common.ResourceInfo, error) {
group, err := getDatasourceGroupNameFromPluginID(pluginID)
if err != nil {
return common.ResourceInfo{}, err
}
return v0alpha1.GenericConnectionResourceInfo.WithGroupAndShortName(group, pluginID+"-connection"), nil
}
func (b *DataSourceAPIBuilder) GetAPIGroupInfo(
scheme *runtime.Scheme,
codecs serializer.CodecFactory, // pointer?
optsGetter generic.RESTOptionsGetter,
_ generic.RESTOptionsGetter,
) (*genericapiserver.APIGroupInfo, error) {
storage := map[string]rest.Storage{}
conn := b.connectionResourceInfo
storage[conn.StoragePath()] = &connectionAccess{
builder: b,
builder: b.querier,
resourceInfo: conn,
tableConverter: utils.NewTableConverter(
conn.GroupResource(),
@ -175,8 +179,8 @@ func (b *DataSourceAPIBuilder) GetAPIGroupInfo(
storage[conn.StoragePath("resource")] = &subResourceREST{builder: b}
// Frontend proxy
if len(b.plugin.Routes) > 0 {
storage[conn.StoragePath("proxy")] = &subProxyREST{builder: b}
if len(b.pluginJSON.Routes) > 0 {
storage[conn.StoragePath("proxy")] = &subProxyREST{pluginJSON: b.pluginJSON}
}
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(
@ -195,64 +199,3 @@ func (b *DataSourceAPIBuilder) GetOpenAPIDefinitions() openapi.GetOpenAPIDefinit
func (b *DataSourceAPIBuilder) GetAPIRoutes() *grafanaapiserver.APIRoutes {
return nil
}
func (b *DataSourceAPIBuilder) getDataSourcePluginContext(ctx context.Context, name string) (*backend.PluginContext, error) {
info, err := request.NamespaceInfoFrom(ctx, true)
if err != nil {
return nil, err
}
user, err := appcontext.User(ctx)
if err != nil {
return nil, err
}
ds, err := b.dsCache.GetDatasourceByUID(ctx, name, user, false)
if err != nil {
return nil, err
}
settings := backend.DataSourceInstanceSettings{}
settings.ID = ds.ID
settings.UID = ds.UID
settings.Name = ds.Name
settings.URL = ds.URL
settings.Updated = ds.Updated
settings.User = ds.User
settings.JSONData, err = ds.JsonData.ToDB()
if err != nil {
return nil, err
}
settings.DecryptedSecureJSONData, err = b.dsService.DecryptedValues(ctx, ds)
if err != nil {
return nil, err
}
return &backend.PluginContext{
OrgID: info.OrgID,
PluginID: b.plugin.ID,
PluginVersion: b.plugin.Info.Version,
User: &backend.User{},
AppInstanceSettings: &backend.AppInstanceSettings{},
DataSourceInstanceSettings: &settings,
}, nil
}
func (b *DataSourceAPIBuilder) getDataSource(ctx context.Context, name string) (*datasources.DataSource, error) {
user, err := appcontext.User(ctx)
if err != nil {
return nil, err
}
return b.dsCache.GetDatasourceByUID(ctx, name, user, false)
}
func (b *DataSourceAPIBuilder) getDataSources(ctx context.Context) ([]*datasources.DataSource, error) {
orgId, err := request.OrgIDForList(ctx)
if err != nil {
return nil, err
}
return b.dsService.GetDataSourcesByType(ctx, &datasources.GetDataSourcesByTypeQuery{
OrgID: orgId,
Type: b.plugin.ID,
})
}

@ -4,19 +4,25 @@ import (
"context"
"fmt"
"github.com/grafana/grafana-plugin-sdk-go/backend"
common "github.com/grafana/grafana/pkg/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/setting"
testdatasource "github.com/grafana/grafana/pkg/tsdb/grafana-testdata-datasource"
)
// This is a helper function to create a new datasource API server for a group
// NewStandaloneDatasource is a helper function to create a new datasource API server for a group.
// This currently has no dependencies and only works for testdata. In future iterations
// this will include here (or elsewhere) versions that can load config from HG api or
// the remote SQL directly
// the remote SQL directly.
func NewStandaloneDatasource(group string) (*DataSourceAPIBuilder, error) {
pluginID := "grafana-testdata-datasource"
if group != "testdata.datasource.grafana.app" {
return nil, fmt.Errorf("only testadata is currently supported")
return nil, fmt.Errorf("only %s is currently supported", pluginID)
}
pluginId := "grafana-testdata-datasource"
cfg, err := setting.NewCfgFromArgs(setting.CommandLineArgs{
// TODO: Add support for args?
@ -25,21 +31,30 @@ func NewStandaloneDatasource(group string) (*DataSourceAPIBuilder, error) {
return nil, err
}
accessControl, pluginstoreService, dsService, cacheServiceImpl, err := apiBuilderServices(cfg, pluginId)
_, pluginStore, dsService, dsCache, err := apiBuilderServices(cfg, pluginID)
if err != nil {
return nil, err
}
testdataPlugin, found := pluginstoreService.Plugin(context.Background(), pluginId)
if !found {
return nil, fmt.Errorf("plugin %s not found", pluginId)
td, exists := pluginStore.Plugin(context.Background(), pluginID)
if !exists {
return nil, fmt.Errorf("plugin %s not found", pluginID)
}
var testsDataQuerierFactory QuerierFactoryFunc = func(ctx context.Context, ri common.ResourceInfo, pj plugins.JSONData) (Querier, error) {
return NewDefaultQuerier(ri, td.JSONData, testdatasource.ProvideService(), dsService, dsCache), nil
}
return NewDataSourceAPIBuilder(
testdataPlugin.JSONData,
testdatasource.ProvideService(),
dsService,
cacheServiceImpl,
accessControl,
td.JSONData,
NewQuerierProvider(testsDataQuerierFactory),
&TestDataPluginContextProvider{},
acimpl.ProvideAccessControl(cfg),
)
}
type TestDataPluginContextProvider struct{}
func (p *TestDataPluginContextProvider) PluginContextForDataSource(_ context.Context, _, _ string) (backend.PluginContext, error) {
return backend.PluginContext{}, nil
}

@ -77,8 +77,8 @@ func apiBuilderServices(cfg *setting.Cfg, pluginID string) (
if err != nil {
return nil, nil, nil, nil, err
}
bundleregistryService := bundleregistry.ProvideService()
usageStats, err := service.ProvideService(cfg, kvStore, routeRegisterImpl, tracingService, accessControl, acimplService, bundleregistryService)
bundleRegistry := bundleregistry.ProvideService()
usageStats, err := service.ProvideService(cfg, kvStore, routeRegisterImpl, tracingService, accessControl, acimplService, bundleRegistry)
if err != nil {
return nil, nil, nil, nil, err
}
@ -88,32 +88,32 @@ func apiBuilderServices(cfg *setting.Cfg, pluginID string) (
if err != nil {
return nil, nil, nil, nil, err
}
osskmsprovidersService := osskmsproviders.ProvideService(serviceService, cfg, featureToggles)
secretsService, err := manager.ProvideSecretsService(secretsStoreImpl, osskmsprovidersService, serviceService, cfg, featureToggles, usageStats)
kmsProviders := osskmsproviders.ProvideService(serviceService, cfg, featureToggles)
secretsService, err := manager.ProvideSecretsService(secretsStoreImpl, kmsProviders, serviceService, cfg, featureToggles, usageStats)
if err != nil {
return nil, nil, nil, nil, err
}
ossImpl := setting.ProvideProvider(cfg)
configCfg, err := config.ProvideConfig(ossImpl, cfg, featureToggles)
pluginCfg, err := config.ProvideConfig(ossImpl, cfg, featureToggles)
if err != nil {
return nil, nil, nil, nil, err
}
inMemory := registry.ProvideService()
pluginRegistry := registry.ProvideService()
quotaService := quotaimpl.ProvideService(sqlStore, cfg)
loaderLoader, err := createLoader(configCfg, inMemory)
pluginLoader, err := createLoader(pluginCfg, pluginRegistry)
if err != nil {
return nil, nil, nil, nil, err
}
pluginstoreService, err := pluginstore.ProvideService(inMemory, newPluginSource(cfg, pluginID), loaderLoader)
pluginStore, err := pluginstore.ProvideService(pluginRegistry, newPluginSource(cfg, pluginID), pluginLoader)
if err != nil {
return nil, nil, nil, nil, err
}
secretsKVStore, err := kvstoreService.ProvideService(sqlStore, secretsService, pluginstoreService, kvStore, featureToggles, cfg)
secretsKVStore, err := kvstoreService.ProvideService(sqlStore, secretsService, pluginStore, kvStore, featureToggles, cfg)
if err != nil {
return nil, nil, nil, nil, err
}
datasourcePermissionsService := ossaccesscontrol.ProvideDatasourcePermissionsService()
service13, err := datasourceService.ProvideService(sqlStore, secretsService, secretsKVStore, cfg, featureToggles, accessControl, datasourcePermissionsService, quotaService, pluginstoreService)
dsPermissionsService := ossaccesscontrol.ProvideDatasourcePermissionsService()
dsService, err := datasourceService.ProvideService(sqlStore, secretsService, secretsKVStore, cfg, featureToggles, accessControl, dsPermissionsService, quotaService, pluginStore)
if err != nil {
return nil, nil, nil, nil, err
}
@ -121,7 +121,7 @@ func apiBuilderServices(cfg *setting.Cfg, pluginID string) (
ossProvider := guardian.ProvideGuardian()
cacheServiceImpl := datasourceService.ProvideCacheService(cacheService, sqlStore, ossProvider)
return accessControl, pluginstoreService, service13, cacheServiceImpl, nil
return accessControl, pluginStore, dsService, cacheServiceImpl, nil
}
var _ sources.Registry = (*pluginSource)(nil)

@ -35,14 +35,14 @@ func (r *subHealthREST) NewConnectOptions() (runtime.Object, bool, string) {
func (r *subHealthREST) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
pluginCtx, err := r.builder.getDataSourcePluginContext(ctx, name)
pluginCtx, err := r.builder.pluginContext.PluginContextForDataSource(ctx, r.builder.pluginJSON.ID, name)
if err != nil {
responder.Error(err)
return
}
healthResponse, err := r.builder.client.CheckHealth(ctx, &backend.CheckHealthRequest{
PluginContext: *pluginCtx,
healthResponse, err := r.builder.querier.Health(ctx, &backend.CheckHealthRequest{
PluginContext: pluginCtx,
})
if err != nil {
responder.Error(err)

@ -8,10 +8,12 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/rest"
"github.com/grafana/grafana/pkg/plugins"
)
type subProxyREST struct {
builder *DataSourceAPIBuilder
pluginJSON plugins.JSONData
}
var _ = rest.Connecter(&subProxyREST{})
@ -25,7 +27,7 @@ func (r *subProxyREST) Destroy() {}
func (r *subProxyREST) ConnectMethods() []string {
unique := map[string]bool{}
methods := []string{}
for _, r := range r.builder.plugin.Routes {
for _, r := range r.pluginJSON.Routes {
if unique[r.Method] {
continue
}
@ -40,12 +42,7 @@ func (r *subProxyREST) NewConnectOptions() (runtime.Object, bool, string) {
}
func (r *subProxyREST) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) {
pluginCtx, err := r.builder.getDataSourcePluginContext(ctx, name)
if err != nil {
return nil, err
}
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
responder.Error(fmt.Errorf("TODO, proxy: " + pluginCtx.PluginID))
responder.Error(fmt.Errorf("TODO, proxy: " + r.pluginJSON.ID))
}), nil
}

@ -71,7 +71,7 @@ func (r *subQueryREST) readQueries(req *http.Request) ([]backend.DataQuery, erro
}
func (r *subQueryREST) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) {
pluginCtx, err := r.builder.getDataSourcePluginContext(ctx, name)
pluginCtx, err := r.builder.pluginContext.PluginContextForDataSource(ctx, r.builder.pluginJSON.ID, name)
if err != nil {
return nil, err
}
@ -83,8 +83,8 @@ func (r *subQueryREST) Connect(ctx context.Context, name string, opts runtime.Ob
return
}
queryResponse, err := r.builder.client.QueryData(ctx, &backend.QueryDataRequest{
PluginContext: *pluginCtx,
queryResponse, err := r.builder.querier.Query(ctx, &backend.QueryDataRequest{
PluginContext: pluginCtx,
Queries: queries,
// Headers: // from context
})

@ -46,7 +46,7 @@ func (r *subResourceREST) NewConnectOptions() (runtime.Object, bool, string) {
}
func (r *subResourceREST) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) {
pluginCtx, err := r.builder.getDataSourcePluginContext(ctx, name)
pluginCtx, err := r.builder.pluginContext.PluginContextForDataSource(ctx, r.builder.pluginJSON.ID, name)
if err != nil {
return nil, err
}
@ -65,8 +65,8 @@ func (r *subResourceREST) Connect(ctx context.Context, name string, opts runtime
}
path := req.URL.Path[idx+len("/resource"):]
err = r.builder.client.CallResource(ctx, &backend.CallResourceRequest{
PluginContext: *pluginCtx,
err = r.builder.querier.Resource(ctx, &backend.CallResourceRequest{
PluginContext: pluginCtx,
Path: path,
Method: req.Method,
Body: body,

@ -9,11 +9,17 @@ import (
"github.com/grafana/grafana/pkg/registry/apis/featuretoggle"
"github.com/grafana/grafana/pkg/registry/apis/folders"
"github.com/grafana/grafana/pkg/registry/apis/playlist"
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
)
var WireSet = wire.NewSet(
ProvideRegistryServiceSink, // dummy background service that forces registration
wire.Bind(new(datasource.QuerierProvider), new(*datasource.DefaultQuerierProvider)),
datasource.ProvideDefaultQuerierProvider,
plugincontext.ProvideService,
wire.Bind(new(datasource.PluginContextProvider), new(*plugincontext.Provider)),
// Each must be added here *and* in the ServiceSink above
playlist.RegisterAPIService,
dashboard.RegisterAPIService,

@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/useragent"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/plugins"
@ -30,12 +31,13 @@ const (
)
func ProvideService(cfg *setting.Cfg, cacheService *localcache.CacheService, pluginStore pluginstore.Store,
dataSourceService datasources.DataSourceService, pluginSettingsService pluginsettings.Service,
licensing plugins.Licensing, pCfg *config.Cfg) *Provider {
dataSourceCache datasources.CacheService, dataSourceService datasources.DataSourceService,
pluginSettingsService pluginsettings.Service, licensing plugins.Licensing, pCfg *config.Cfg) *Provider {
return &Provider{
cfg: cfg,
cacheService: cacheService,
pluginStore: pluginStore,
dataSourceCache: dataSourceCache,
dataSourceService: dataSourceService,
pluginSettingsService: pluginSettingsService,
pluginEnvVars: envvars.NewProvider(pCfg, licensing),
@ -48,6 +50,7 @@ type Provider struct {
pluginEnvVars *envvars.Service
cacheService *localcache.CacheService
pluginStore pluginstore.Store
dataSourceCache datasources.CacheService
dataSourceService datasources.DataSourceService
pluginSettingsService pluginsettings.Service
logger log.Logger
@ -128,6 +131,48 @@ func (p *Provider) GetWithDataSource(ctx context.Context, pluginID string, user
return pCtx, nil
}
func (p *Provider) PluginContextForDataSource(ctx context.Context, pluginID, name string) (backend.PluginContext, error) {
plugin, exists := p.pluginStore.Plugin(ctx, pluginID)
if !exists {
return backend.PluginContext{}, plugins.ErrPluginNotRegistered
}
user, err := appcontext.User(ctx)
if err != nil {
return backend.PluginContext{}, err
}
ds, err := p.dataSourceCache.GetDatasourceByUID(ctx, name, user, false)
if err != nil {
return backend.PluginContext{}, err
}
pCtx := backend.PluginContext{
PluginID: plugin.ID,
PluginVersion: plugin.Info.Version,
}
if user != nil && !user.IsNil() {
pCtx.OrgID = user.GetOrgID()
pCtx.User = adapters.BackendUserFromSignedInUser(user)
}
datasourceSettings, err := adapters.ModelToInstanceSettings(ds, p.decryptSecureJsonDataFn(ctx))
if err != nil {
return pCtx, err
}
pCtx.DataSourceInstanceSettings = datasourceSettings
settings := p.pluginEnvVars.GetConfigMap(ctx, pluginID, plugin.ExternalService)
pCtx.GrafanaConfig = backend.NewGrafanaCfg(settings)
ua, err := useragent.New(p.cfg.BuildVersion, runtime.GOOS, runtime.GOARCH)
if err != nil {
p.logger.Warn("Could not create user agent", "error", err)
}
pCtx.UserAgent = ua
return pCtx, nil
}
func (p *Provider) appInstanceSettings(ctx context.Context, pluginID string, orgID int64) (*backend.AppInstanceSettings, error) {
jsonData := json.RawMessage{}
decryptedSecureJSONData := map[string]string{}

@ -42,7 +42,7 @@ func TestGet(t *testing.T) {
ds := &fakeDatasources.FakeDataSourceService{}
db := &dbtest.FakeDB{ExpectedError: pluginsettings.ErrPluginSettingNotFound}
pcp := plugincontext.ProvideService(cfg, localcache.ProvideService(),
pluginstore.New(preg, &pluginFakes.FakeLoader{}),
pluginstore.New(preg, &pluginFakes.FakeLoader{}), &fakeDatasources.FakeCacheService{},
ds, pluginSettings.ProvideService(db, secretstest.NewFakeSecretsService()), pluginFakes.NewFakeLicensingService(), &config.Cfg{},
)
identity := &user.SignedInUser{OrgID: int64(1), Login: "admin"}

@ -42,7 +42,6 @@ import (
"github.com/grafana/grafana/pkg/services/pluginsintegration/licensing"
"github.com/grafana/grafana/pkg/services/pluginsintegration/loader"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pipeline"
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginerrs"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
pluginSettings "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings/service"
@ -94,7 +93,6 @@ var WireSet = wire.NewSet(
wire.Bind(new(registry.Service), new(*registry.InMemory)),
repo.ProvideService,
wire.Bind(new(repo.Service), new(*repo.Manager)),
plugincontext.ProvideService,
licensing.ProvideLicensing,
wire.Bind(new(plugins.Licensing), new(*licensing.Service)),
wire.Bind(new(sources.Registry), new(*sources.Service)),

@ -140,7 +140,8 @@ func buildQueryDataService(t *testing.T, cs datasources.CacheService, fpc *fakeP
},
},
},
}, ds, pluginSettings.ProvideService(store, fakeSecrets.NewFakeSecretsService()), fakes.NewFakeLicensingService(),
}, &fakeDatasources.FakeCacheService{}, ds,
pluginSettings.ProvideService(store, fakeSecrets.NewFakeSecretsService()), fakes.NewFakeLicensingService(),
&config.Cfg{})
return query.ProvideService(

@ -476,7 +476,7 @@ func setup(t *testing.T) *testContext {
{JSONData: plugins.JSONData{ID: "testdata"}},
{JSONData: plugins.JSONData{ID: "mysql"}},
},
}, fakeDatasourceService,
}, &fakeDatasources.FakeCacheService{}, fakeDatasourceService,
pluginSettings.ProvideService(sqlStore, secretsService), pluginFakes.NewFakeLicensingService(), &config.Cfg{},
)
exprService := expr.ProvideService(&setting.Cfg{ExpressionsEnabled: true}, pc, pCtxProvider,

@ -16,6 +16,7 @@ import (
pluginFakes "github.com/grafana/grafana/pkg/plugins/manager/fakes"
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/datasources/guardian"
datasourceservice "github.com/grafana/grafana/pkg/services/datasources/service"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
@ -42,12 +43,13 @@ func TestHandleRequest(t *testing.T) {
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
datasourcePermissions := acmock.NewMockedPermissionsService()
quotaService := quotatest.New(false, nil)
dsCache := datasourceservice.ProvideCacheService(localcache.ProvideService(), sqlStore, guardian.ProvideGuardian())
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, sqlStore.Cfg, featuremgmt.WithFeatures(), acmock.New(), datasourcePermissions, quotaService, &pluginstore.FakePluginStore{})
require.NoError(t, err)
pCtxProvider := plugincontext.ProvideService(sqlStore.Cfg, localcache.ProvideService(), &pluginstore.FakePluginStore{
PluginList: []pluginstore.Plugin{{JSONData: plugins.JSONData{ID: "test"}}},
}, dsService, pluginSettings.ProvideService(sqlStore, secretsService), pluginFakes.NewFakeLicensingService(), &config.Cfg{})
}, dsCache, dsService, pluginSettings.ProvideService(sqlStore, secretsService), pluginFakes.NewFakeLicensingService(), &config.Cfg{})
s := ProvideService(client, nil, dsService, pCtxProvider)
ds := &datasources.DataSource{ID: 12, Type: "test", JsonData: simplejson.New()}

Loading…
Cancel
Save