diff --git a/pkg/api/metrics_test.go b/pkg/api/metrics_test.go index 7eeeb890891..370596435c0 100644 --- a/pkg/api/metrics_test.go +++ b/pkg/api/metrics_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) diff --git a/pkg/api/plugin_resource_test.go b/pkg/api/plugin_resource_test.go index df94ea6d2b8..0d7d7fbe96b 100644 --- a/pkg/api/plugin_resource_test.go +++ b/pkg/api/plugin_resource_test.go @@ -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 diff --git a/pkg/expr/dataplane_test.go b/pkg/expr/dataplane_test.go index 3973dd8c86e..c302064c1f0 100644 --- a/pkg/expr/dataplane_test.go +++ b/pkg/expr/dataplane_test.go @@ -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), } diff --git a/pkg/expr/service_test.go b/pkg/expr/service_test.go index 9b0c417a09e..7983be6951e 100644 --- a/pkg/expr/service_test.go +++ b/pkg/expr/service_test.go @@ -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(), diff --git a/pkg/registry/apis/datasource/connections.go b/pkg/registry/apis/datasource/connections.go index 56cf94bd333..5d3d8fd1acc 100644 --- a/pkg/registry/apis/datasource/connections.go +++ b/pkg/registry/apis/datasource/connections.go @@ -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) } diff --git a/pkg/registry/apis/datasource/plugincontext.go b/pkg/registry/apis/datasource/plugincontext.go new file mode 100644 index 00000000000..3908e4b5d04 --- /dev/null +++ b/pkg/registry/apis/datasource/plugincontext.go @@ -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) +} diff --git a/pkg/registry/apis/datasource/querier.go b/pkg/registry/apis/datasource/querier.go new file mode 100644 index 00000000000..0bf6c26cafa --- /dev/null +++ b/pkg/registry/apis/datasource/querier.go @@ -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 +} diff --git a/pkg/registry/apis/datasource/register.go b/pkg/registry/apis/datasource/register.go index 34c4dc31cc7..ebd5d196205 100644 --- a/pkg/registry/apis/datasource/register.go +++ b/pkg/registry/apis/datasource/register.go @@ -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, - }) -} diff --git a/pkg/registry/apis/datasource/standalone.go b/pkg/registry/apis/datasource/standalone.go index 46e6059ac01..b4a1a1f3f17 100644 --- a/pkg/registry/apis/datasource/standalone.go +++ b/pkg/registry/apis/datasource/standalone.go @@ -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 +} diff --git a/pkg/registry/apis/datasource/standalone_services.go b/pkg/registry/apis/datasource/standalone_services.go index 6087d8c30c7..f8028263fc2 100644 --- a/pkg/registry/apis/datasource/standalone_services.go +++ b/pkg/registry/apis/datasource/standalone_services.go @@ -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) diff --git a/pkg/registry/apis/datasource/sub_health.go b/pkg/registry/apis/datasource/sub_health.go index ec57c771c17..48199be0c4e 100644 --- a/pkg/registry/apis/datasource/sub_health.go +++ b/pkg/registry/apis/datasource/sub_health.go @@ -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) diff --git a/pkg/registry/apis/datasource/sub_proxy.go b/pkg/registry/apis/datasource/sub_proxy.go index cb2bb895a39..5c082fc4a92 100644 --- a/pkg/registry/apis/datasource/sub_proxy.go +++ b/pkg/registry/apis/datasource/sub_proxy.go @@ -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 } diff --git a/pkg/registry/apis/datasource/sub_query.go b/pkg/registry/apis/datasource/sub_query.go index 5181e141b57..b46f1043f3c 100644 --- a/pkg/registry/apis/datasource/sub_query.go +++ b/pkg/registry/apis/datasource/sub_query.go @@ -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 }) diff --git a/pkg/registry/apis/datasource/sub_resource.go b/pkg/registry/apis/datasource/sub_resource.go index b6af786c574..3feb4022564 100644 --- a/pkg/registry/apis/datasource/sub_resource.go +++ b/pkg/registry/apis/datasource/sub_resource.go @@ -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, diff --git a/pkg/registry/apis/wireset.go b/pkg/registry/apis/wireset.go index f6324594311..9d484cea29f 100644 --- a/pkg/registry/apis/wireset.go +++ b/pkg/registry/apis/wireset.go @@ -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, diff --git a/pkg/services/pluginsintegration/plugincontext/plugincontext.go b/pkg/services/pluginsintegration/plugincontext/plugincontext.go index f96026c6614..54de3592e32 100644 --- a/pkg/services/pluginsintegration/plugincontext/plugincontext.go +++ b/pkg/services/pluginsintegration/plugincontext/plugincontext.go @@ -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{} diff --git a/pkg/services/pluginsintegration/plugincontext/plugincontext_test.go b/pkg/services/pluginsintegration/plugincontext/plugincontext_test.go index 9d4c750ff2c..7622f3d29d3 100644 --- a/pkg/services/pluginsintegration/plugincontext/plugincontext_test.go +++ b/pkg/services/pluginsintegration/plugincontext/plugincontext_test.go @@ -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"} diff --git a/pkg/services/pluginsintegration/pluginsintegration.go b/pkg/services/pluginsintegration/pluginsintegration.go index ae43c16369b..6edee03042d 100644 --- a/pkg/services/pluginsintegration/pluginsintegration.go +++ b/pkg/services/pluginsintegration/pluginsintegration.go @@ -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)), diff --git a/pkg/services/publicdashboards/api/common_test.go b/pkg/services/publicdashboards/api/common_test.go index b8dc715b71c..3f016a34e46 100644 --- a/pkg/services/publicdashboards/api/common_test.go +++ b/pkg/services/publicdashboards/api/common_test.go @@ -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( diff --git a/pkg/services/query/query_test.go b/pkg/services/query/query_test.go index c1cd5166b6f..3db35179074 100644 --- a/pkg/services/query/query_test.go +++ b/pkg/services/query/query_test.go @@ -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, diff --git a/pkg/tsdb/legacydata/service/service_test.go b/pkg/tsdb/legacydata/service/service_test.go index ebf5f98d488..89d8b7c0c74 100644 --- a/pkg/tsdb/legacydata/service/service_test.go +++ b/pkg/tsdb/legacydata/service/service_test.go @@ -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()}