Plugins: Refactor creation of plugin context to dedicated service (#66451)

* first pass

* fix tests

* return errs

* change signature

* tidy

* delete unnecessary fields from test

* tidy

* fix tests

* simplify

* separate error check in API

* apply nits
This commit is contained in:
Will Browne
2023-06-08 13:59:51 +02:00
committed by GitHub
parent 49ad802ca6
commit 624777258b
25 changed files with 308 additions and 261 deletions

View File

@@ -2,6 +2,7 @@ package features
import (
"context"
"errors"
"github.com/centrifugal/centrifuge"
"github.com/grafana/grafana-plugin-sdk-go/backend"
@@ -9,13 +10,14 @@ import (
"github.com/grafana/grafana/pkg/services/live/model"
"github.com/grafana/grafana/pkg/services/live/orgchannel"
"github.com/grafana/grafana/pkg/services/live/runstream"
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
"github.com/grafana/grafana/pkg/services/user"
)
//go:generate mockgen -destination=plugin_mock.go -package=features github.com/grafana/grafana/pkg/services/live/features PluginContextGetter
type PluginContextGetter interface {
GetPluginContext(ctx context.Context, user *user.SignedInUser, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error)
GetPluginContext(ctx context.Context, user *user.SignedInUser, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, error)
}
// PluginRunner can handle streaming operations for channels belonging to plugins.
@@ -62,15 +64,15 @@ type PluginPathRunner struct {
// OnSubscribe passes control to a plugin.
func (r *PluginPathRunner) OnSubscribe(ctx context.Context, user *user.SignedInUser, e model.SubscribeEvent) (model.SubscribeReply, backend.SubscribeStreamStatus, error) {
pCtx, found, err := r.pluginContextGetter.GetPluginContext(ctx, user, r.pluginID, r.datasourceUID, false)
pCtx, err := r.pluginContextGetter.GetPluginContext(ctx, user, r.pluginID, r.datasourceUID, false)
if err != nil {
if errors.Is(err, plugincontext.ErrPluginNotFound) {
logger.Error("Plugin context not found", "path", r.path)
return model.SubscribeReply{}, 0, centrifuge.ErrorInternal
}
logger.Error("Get plugin context error", "error", err, "path", r.path)
return model.SubscribeReply{}, 0, err
}
if !found {
logger.Error("Plugin context not found", "path", r.path)
return model.SubscribeReply{}, 0, centrifuge.ErrorInternal
}
resp, err := r.handler.SubscribeStream(ctx, &backend.SubscribeStreamRequest{
PluginContext: pCtx,
Path: r.path,
@@ -106,15 +108,15 @@ func (r *PluginPathRunner) OnSubscribe(ctx context.Context, user *user.SignedInU
// OnPublish passes control to a plugin.
func (r *PluginPathRunner) OnPublish(ctx context.Context, user *user.SignedInUser, e model.PublishEvent) (model.PublishReply, backend.PublishStreamStatus, error) {
pCtx, found, err := r.pluginContextGetter.GetPluginContext(ctx, user, r.pluginID, r.datasourceUID, false)
pCtx, err := r.pluginContextGetter.GetPluginContext(ctx, user, r.pluginID, r.datasourceUID, false)
if err != nil {
if errors.Is(err, plugincontext.ErrPluginNotFound) {
logger.Error("Plugin context not found", "path", r.path)
return model.PublishReply{}, 0, centrifuge.ErrorInternal
}
logger.Error("Get plugin context error", "error", err, "path", r.path)
return model.PublishReply{}, 0, err
}
if !found {
logger.Error("Plugin context not found", "path", r.path)
return model.PublishReply{}, 0, centrifuge.ErrorInternal
}
resp, err := r.handler.PublishStream(ctx, &backend.PublishStreamRequest{
PluginContext: pCtx,
Path: r.path,

View File

@@ -5,13 +5,12 @@
package features
import (
"context"
context "context"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
backend "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/services/user"
user "github.com/grafana/grafana/pkg/services/user"
)
// MockPluginContextGetter is a mock of PluginContextGetter interface.
@@ -38,17 +37,16 @@ func (m *MockPluginContextGetter) EXPECT() *MockPluginContextGetterMockRecorder
}
// GetPluginContext mocks base method.
func (m *MockPluginContextGetter) GetPluginContext(ctx context.Context, arg0 *user.SignedInUser, arg1, arg2 string) (backend.PluginContext, bool, error) {
func (m *MockPluginContextGetter) GetPluginContext(arg0 context.Context, arg1 *user.SignedInUser, arg2, arg3 string, arg4 bool) (backend.PluginContext, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPluginContext", arg0, arg1, arg2)
ret := m.ctrl.Call(m, "GetPluginContext", arg0, arg1, arg2, arg3, arg4)
ret0, _ := ret[0].(backend.PluginContext)
ret1, _ := ret[1].(bool)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetPluginContext indicates an expected call of GetPluginContext.
func (mr *MockPluginContextGetterMockRecorder) GetPluginContext(ctx context.Context, arg0, arg1, arg2 interface{}) *gomock.Call {
func (mr *MockPluginContextGetterMockRecorder) GetPluginContext(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPluginContext", reflect.TypeOf((*MockPluginContextGetter)(nil).GetPluginContext), arg0, arg1, arg2)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPluginContext", reflect.TypeOf((*MockPluginContextGetter)(nil).GetPluginContext), arg0, arg1, arg2, arg3, arg4)
}

View File

@@ -72,14 +72,14 @@ func NewContextGetter(pluginContextProvider *plugincontext.Provider, dataSourceC
}
}
func (g *ContextGetter) GetPluginContext(ctx context.Context, user *user.SignedInUser, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error) {
func (g *ContextGetter) GetPluginContext(ctx context.Context, user *user.SignedInUser, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, error) {
if datasourceUID == "" {
return g.pluginContextProvider.Get(ctx, pluginID, user)
}
ds, err := g.dataSourceCache.GetDatasourceByUID(ctx, datasourceUID, user, skipCache)
if err != nil {
return backend.PluginContext{}, false, fmt.Errorf("%v: %w", "Failed to get datasource", err)
return backend.PluginContext{}, fmt.Errorf("%v: %w", "Failed to get datasource", err)
}
return g.pluginContextProvider.GetWithDataSource(ctx, pluginID, user, ds)
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
"github.com/grafana/grafana/pkg/services/user"
)
@@ -25,7 +26,7 @@ type ChannelLocalPublisher interface {
}
type PluginContextGetter interface {
GetPluginContext(ctx context.Context, user *user.SignedInUser, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, bool, error)
GetPluginContext(ctx context.Context, user *user.SignedInUser, pluginID string, datasourceUID string, skipCache bool) (backend.PluginContext, error)
}
type NumLocalSubscribersGetter interface {
@@ -182,15 +183,15 @@ func (s *Manager) watchStream(ctx context.Context, cancelFn func(), sr streamReq
case <-datasourceTicker.C:
if sr.PluginContext.DataSourceInstanceSettings != nil {
dsUID := sr.PluginContext.DataSourceInstanceSettings.UID
pCtx, ok, err := s.pluginContextGetter.GetPluginContext(ctx, sr.user, sr.PluginContext.PluginID, dsUID, false)
pCtx, err := s.pluginContextGetter.GetPluginContext(ctx, sr.user, sr.PluginContext.PluginID, dsUID, false)
if err != nil {
if errors.Is(err, plugincontext.ErrPluginNotFound) {
logger.Debug("Datasource not found, stop stream", "channel", sr.Channel, "path", sr.Path)
return
}
logger.Error("Error getting datasource context", "channel", sr.Channel, "path", sr.Path, "error", err)
continue
}
if !ok {
logger.Debug("Datasource not found, stop stream", "channel", sr.Channel, "path", sr.Path)
return
}
if pCtx.DataSourceInstanceSettings.Updated != sr.PluginContext.DataSourceInstanceSettings.Updated {
logger.Debug("Datasource changed, re-establish stream", "channel", sr.Channel, "path", sr.Path)
err := s.HandleDatasourceUpdate(pCtx.OrgID, dsUID)
@@ -283,16 +284,16 @@ func (s *Manager) runStream(ctx context.Context, cancelFn func(), sr streamReque
if pluginCtx.DataSourceInstanceSettings != nil {
datasourceUID = pluginCtx.DataSourceInstanceSettings.UID
}
newPluginCtx, ok, err := s.pluginContextGetter.GetPluginContext(ctx, sr.user, pluginCtx.PluginID, datasourceUID, false)
newPluginCtx, err := s.pluginContextGetter.GetPluginContext(ctx, sr.user, pluginCtx.PluginID, datasourceUID, false)
if err != nil {
if errors.Is(err, plugincontext.ErrPluginNotFound) {
logger.Info("No plugin context found, stopping stream", "path", sr.Path)
return
}
logger.Error("Error getting plugin context", "path", sr.Path, "error", err)
isReconnect = true
continue
}
if !ok {
logger.Info("No plugin context found, stopping stream", "path", sr.Path)
return
}
pluginCtx = newPluginCtx
}
@@ -407,13 +408,13 @@ func (s *Manager) SubmitStream(ctx context.Context, user *user.SignedInUser, cha
if pCtx.DataSourceInstanceSettings != nil {
datasourceUID = pCtx.DataSourceInstanceSettings.UID
}
newPluginCtx, ok, err := s.pluginContextGetter.GetPluginContext(ctx, user, pCtx.PluginID, datasourceUID, false)
newPluginCtx, err := s.pluginContextGetter.GetPluginContext(ctx, user, pCtx.PluginID, datasourceUID, false)
if err != nil {
if errors.Is(err, plugincontext.ErrPluginNotFound) {
return nil, errDatasourceNotFound
}
return nil, err
}
if !ok {
return nil, errDatasourceNotFound
}
pCtx = newPluginCtx
}

View File

@@ -83,7 +83,7 @@ func TestStreamManager_SubmitStream_Send(t *testing.T) {
mockStreamRunner := NewMockStreamRunner(mockCtrl)
mockStreamRunner.EXPECT().RunStream(
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
gomock.Any(), gomock.Any(), gomock.Any(),
).DoAndReturn(func(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
require.Equal(t, "test", req.Path)
close(startedCh)
@@ -139,7 +139,7 @@ func TestStreamManager_SubmitStream_DifferentOrgID(t *testing.T) {
mockStreamRunner1 := NewMockStreamRunner(mockCtrl)
mockStreamRunner1.EXPECT().RunStream(
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
gomock.Any(), gomock.Any(), gomock.Any(),
).DoAndReturn(func(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
require.Equal(t, "test", req.Path)
close(startedCh1)
@@ -152,7 +152,7 @@ func TestStreamManager_SubmitStream_DifferentOrgID(t *testing.T) {
mockStreamRunner2 := NewMockStreamRunner(mockCtrl)
mockStreamRunner2.EXPECT().RunStream(
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
gomock.Any(), gomock.Any(), gomock.Any(),
).DoAndReturn(func(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
require.Equal(t, "test", req.Path)
close(startedCh2)
@@ -212,7 +212,7 @@ func TestStreamManager_SubmitStream_CloseNoSubscribers(t *testing.T) {
mockNumSubscribersGetter.EXPECT().GetNumLocalSubscribers("1/test").Return(0, nil).Times(3)
mockStreamRunner := NewMockStreamRunner(mockCtrl)
mockStreamRunner.EXPECT().RunStream(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
mockStreamRunner.EXPECT().RunStream(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
close(startedCh)
<-ctx.Done()
close(doneCh)
@@ -264,7 +264,7 @@ func TestStreamManager_SubmitStream_ErrorRestartsRunStream(t *testing.T) {
mockStreamRunner := NewMockStreamRunner(mockCtrl)
mockStreamRunner.EXPECT().RunStream(
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
gomock.Any(), gomock.Any(), gomock.Any(),
).DoAndReturn(func(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
if currentErrors >= numErrors {
return nil
@@ -302,7 +302,7 @@ func TestStreamManager_SubmitStream_NilErrorStopsRunStream(t *testing.T) {
mockStreamRunner := NewMockStreamRunner(mockCtrl)
mockStreamRunner.EXPECT().RunStream(
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
gomock.Any(), gomock.Any(), gomock.Any(),
).DoAndReturn(func(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
return nil
}).Times(1)
@@ -352,7 +352,7 @@ func TestStreamManager_HandleDatasourceUpdate(t *testing.T) {
mockStreamRunner := NewMockStreamRunner(mockCtrl)
mockStreamRunner.EXPECT().RunStream(
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
gomock.Any(), gomock.Any(), gomock.Any(),
).DoAndReturn(func(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
if isFirstCall {
// first RunStream will wait till context done.
@@ -415,7 +415,7 @@ func TestStreamManager_HandleDatasourceDelete(t *testing.T) {
mockStreamRunner := NewMockStreamRunner(mockCtrl)
mockStreamRunner.EXPECT().RunStream(
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
gomock.Any(), gomock.Any(), gomock.Any(),
).DoAndReturn(func(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
close(doneCh)
<-ctx.Done()

View File

@@ -10,8 +10,7 @@ import (
gomock "github.com/golang/mock/gomock"
backend "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/services/user"
user "github.com/grafana/grafana/pkg/services/user"
)
// MockChannelLocalPublisher is a mock of ChannelLocalPublisher interface.
@@ -121,7 +120,7 @@ func (m *MockStreamRunner) RunStream(arg0 context.Context, arg1 *backend.RunStre
}
// RunStream indicates an expected call of RunStream.
func (mr *MockStreamRunnerMockRecorder) RunStream(ctx, arg0, arg1, arg2 interface{}) *gomock.Call {
func (mr *MockStreamRunnerMockRecorder) RunStream(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RunStream", reflect.TypeOf((*MockStreamRunner)(nil).RunStream), arg0, arg1, arg2)
}
@@ -150,17 +149,16 @@ func (m *MockPluginContextGetter) EXPECT() *MockPluginContextGetterMockRecorder
}
// GetPluginContext mocks base method.
func (m *MockPluginContextGetter) GetPluginContext(ctx context.Context, arg0 *user.SignedInUser, arg1, arg2 string, arg3 bool) (backend.PluginContext, bool, error) {
func (m *MockPluginContextGetter) GetPluginContext(arg0 context.Context, arg1 *user.SignedInUser, arg2, arg3 string, arg4 bool) (backend.PluginContext, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPluginContext", ctx, arg0, arg1, arg2, arg3)
ret := m.ctrl.Call(m, "GetPluginContext", arg0, arg1, arg2, arg3, arg4)
ret0, _ := ret[0].(backend.PluginContext)
ret1, _ := ret[1].(bool)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetPluginContext indicates an expected call of GetPluginContext.
func (mr *MockPluginContextGetterMockRecorder) GetPluginContext(ctx, arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
func (mr *MockPluginContextGetterMockRecorder) GetPluginContext(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPluginContext", reflect.TypeOf((*MockPluginContextGetter)(nil).GetPluginContext), ctx, arg0, arg1, arg2, arg3)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPluginContext", reflect.TypeOf((*MockPluginContextGetter)(nil).GetPluginContext), arg0, arg1, arg2, arg3, arg4)
}

View File

@@ -247,6 +247,7 @@ func getExprRequest(ctx EvaluationContext, data []models.AlertQuery, dsCacheServ
req := &expr.Request{
OrgId: ctx.User.OrgID,
Headers: buildDatasourceHeaders(ctx.Ctx),
User: ctx.User,
}
datasources := make(map[string]*datasources.DataSource, len(data))

View File

@@ -11,68 +11,78 @@ import (
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/log"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/pluginsintegration/adapters"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
"github.com/grafana/grafana/pkg/services/user"
)
var ErrPluginNotFound = errors.New("plugin not found")
func ProvideService(cacheService *localcache.CacheService, pluginStore plugins.Store,
dataSourceCache datasources.CacheService, dataSourceService datasources.DataSourceService,
pluginSettingsService pluginsettings.Service) *Provider {
dataSourceService datasources.DataSourceService, pluginSettingsService pluginsettings.Service) *Provider {
return &Provider{
cacheService: cacheService,
pluginStore: pluginStore,
dataSourceCache: dataSourceCache,
dataSourceService: dataSourceService,
pluginSettingsService: pluginSettingsService,
logger: log.New("plugincontext"),
}
}
type Provider struct {
cacheService *localcache.CacheService
pluginStore plugins.Store
dataSourceCache datasources.CacheService
dataSourceService datasources.DataSourceService
pluginSettingsService pluginsettings.Service
logger log.Logger
}
// Get allows getting plugin context by its ID. If datasourceUID is not empty string
// then PluginContext.DataSourceInstanceSettings will be resolved and appended to
// returned context.
func (p *Provider) Get(ctx context.Context, pluginID string, user *user.SignedInUser) (backend.PluginContext, bool, error) {
return p.pluginContext(ctx, pluginID, user)
func (p *Provider) Get(ctx context.Context, pluginID string, user *user.SignedInUser) (backend.PluginContext, error) {
plugin, exists := p.pluginStore.Plugin(ctx, pluginID)
if !exists {
return backend.PluginContext{}, ErrPluginNotFound
}
pCtx := backend.PluginContext{
OrgID: user.OrgID,
PluginID: pluginID,
User: adapters.BackendUserFromSignedInUser(user),
}
if plugin.IsApp() {
appSettings, err := p.appInstanceSettings(ctx, pluginID, user)
if err != nil {
return backend.PluginContext{}, err
}
pCtx.AppInstanceSettings = appSettings
}
return pCtx, nil
}
// GetWithDataSource allows getting plugin context by its ID and PluginContext.DataSourceInstanceSettings will be
// resolved and appended to the returned context.
func (p *Provider) GetWithDataSource(ctx context.Context, pluginID string, user *user.SignedInUser, ds *datasources.DataSource) (backend.PluginContext, bool, error) {
pCtx, exists, err := p.pluginContext(ctx, pluginID, user)
func (p *Provider) GetWithDataSource(ctx context.Context, pluginID string, user *user.SignedInUser, ds *datasources.DataSource) (backend.PluginContext, error) {
pCtx, err := p.Get(ctx, pluginID, user)
if err != nil {
return pCtx, exists, err
return backend.PluginContext{}, err
}
datasourceSettings, err := adapters.ModelToInstanceSettings(ds, p.decryptSecureJsonDataFn(ctx))
if err != nil {
return pCtx, exists, fmt.Errorf("%v: %w", "Failed to convert datasource", err)
return pCtx, err
}
pCtx.DataSourceInstanceSettings = datasourceSettings
return pCtx, true, nil
return pCtx, nil
}
const pluginSettingsCacheTTL = 5 * time.Second
const pluginSettingsCachePrefix = "plugin-setting-"
func (p *Provider) pluginContext(ctx context.Context, pluginID string, user *user.SignedInUser) (backend.PluginContext, bool, error) {
plugin, exists := p.pluginStore.Plugin(ctx, pluginID)
if !exists {
return backend.PluginContext{}, false, nil
}
func (p *Provider) appInstanceSettings(ctx context.Context, pluginID string, user *user.SignedInUser) (*backend.AppInstanceSettings, error) {
jsonData := json.RawMessage{}
decryptedSecureJSONData := map[string]string{}
var updated time.Time
@@ -80,29 +90,28 @@ func (p *Provider) pluginContext(ctx context.Context, pluginID string, user *use
ps, err := p.getCachedPluginSettings(ctx, pluginID, user)
if err != nil {
// pluginsettings.ErrPluginSettingNotFound is expected if there's no row found for plugin setting in database (if non-app plugin).
// If it's not this expected error something is wrong with cache or database and we return the error to the client.
// Otherwise, something is wrong with cache or database, and we return the error to the client.
if !errors.Is(err, pluginsettings.ErrPluginSettingNotFound) {
return backend.PluginContext{}, false, fmt.Errorf("%v: %w", "Failed to get plugin settings", err)
return nil, fmt.Errorf("%v: %w", "Failed to get plugin settings", err)
}
} else {
jsonData, err = json.Marshal(ps.JSONData)
if err != nil {
return backend.PluginContext{}, false, fmt.Errorf("%v: %w", "Failed to unmarshal plugin json data", err)
return nil, fmt.Errorf("%v: %w", "Failed to unmarshal plugin json data", err)
}
decryptedSecureJSONData = p.pluginSettingsService.DecryptedValues(ps)
updated = ps.Updated
}
return backend.PluginContext{
OrgID: user.OrgID,
PluginID: plugin.ID,
User: adapters.BackendUserFromSignedInUser(user),
AppInstanceSettings: &backend.AppInstanceSettings{
JSONData: jsonData,
DecryptedSecureJSONData: decryptedSecureJSONData,
Updated: updated,
},
}, true, nil
return &backend.AppInstanceSettings{
JSONData: jsonData,
DecryptedSecureJSONData: decryptedSecureJSONData,
Updated: updated,
}, nil
}
func (p *Provider) InvalidateSettingsCache(_ context.Context, pluginID string) {
p.cacheService.Delete(getCacheKey(pluginID))
}
func (p *Provider) getCachedPluginSettings(ctx context.Context, pluginID string, user *user.SignedInUser) (*pluginsettings.DTO, error) {
@@ -127,10 +136,6 @@ func (p *Provider) getCachedPluginSettings(ctx context.Context, pluginID string,
return ps, nil
}
func (p *Provider) InvalidateSettingsCache(_ context.Context, pluginID string) {
p.cacheService.Delete(getCacheKey(pluginID))
}
func (p *Provider) decryptSecureJsonDataFn(ctx context.Context) func(ds *datasources.DataSource) (map[string]string, error) {
return func(ds *datasources.DataSource) (map[string]string, error) {
return p.dataSourceService.DecryptedValues(ctx, ds)

View File

@@ -25,8 +25,11 @@ import (
fakeDatasources "github.com/grafana/grafana/pkg/services/datasources/fakes"
datasourceService "github.com/grafana/grafana/pkg/services/datasources/service"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
pluginSettings "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings/service"
"github.com/grafana/grafana/pkg/services/publicdashboards"
"github.com/grafana/grafana/pkg/services/query"
fakeSecrets "github.com/grafana/grafana/pkg/services/secrets/fakes"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web"
@@ -129,13 +132,24 @@ func buildQueryDataService(t *testing.T, cs datasources.CacheService, fpc *fakeP
}
}
ds := &fakeDatasources.FakeDataSourceService{}
pCtxProvider := plugincontext.ProvideService(localcache.ProvideService(), &plugins.FakePluginStore{
PluginList: []plugins.PluginDTO{
{
JSONData: plugins.JSONData{
ID: "mysql",
},
},
},
}, ds, pluginSettings.ProvideService(store, fakeSecrets.NewFakeSecretsService()))
return query.ProvideService(
setting.NewCfg(),
cs,
nil,
&fakePluginRequestValidator{},
&fakeDatasources.FakeDataSourceService{},
fpc,
pCtxProvider,
)
}

View File

@@ -14,7 +14,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/pluginsintegration/adapters"
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/services/validations"
"github.com/grafana/grafana/pkg/setting"
@@ -37,16 +37,16 @@ func ProvideService(
dataSourceCache datasources.CacheService,
expressionService *expr.Service,
pluginRequestValidator validations.PluginRequestValidator,
dataSourceService datasources.DataSourceService,
pluginClient plugins.Client,
pCtxProvider *plugincontext.Provider,
) *ServiceImpl {
g := &ServiceImpl{
cfg: cfg,
dataSourceCache: dataSourceCache,
expressionService: expressionService,
pluginRequestValidator: pluginRequestValidator,
dataSourceService: dataSourceService,
pluginClient: pluginClient,
pCtxProvider: pCtxProvider,
log: log.New("query_data"),
}
g.log.Info("Query Service initialization")
@@ -67,8 +67,8 @@ type ServiceImpl struct {
dataSourceCache datasources.CacheService
expressionService *expr.Service
pluginRequestValidator validations.PluginRequestValidator
dataSourceService datasources.DataSourceService
pluginClient plugins.Client
pCtxProvider *plugincontext.Provider
log log.Logger
}
@@ -178,7 +178,7 @@ func (s *ServiceImpl) handleExpressions(ctx context.Context, user *user.SignedIn
}
if user != nil { // for passthrough authentication, SSE does not authenticate
exprReq.User = adapters.BackendUserFromSignedInUser(user)
exprReq.User = user
exprReq.OrgId = user.OrgID
}
@@ -227,20 +227,14 @@ func (s *ServiceImpl) handleQuerySingleDatasource(ctx context.Context, user *use
}
}
instanceSettings, err := adapters.ModelToInstanceSettings(ds, s.decryptSecureJsonDataFn(ctx))
pCtx, err := s.pCtxProvider.GetWithDataSource(ctx, ds.Type, user, ds)
if err != nil {
return nil, err
}
req := &backend.QueryDataRequest{
PluginContext: backend.PluginContext{
OrgID: ds.OrgID,
PluginID: ds.Type,
User: adapters.BackendUserFromSignedInUser(user),
DataSourceInstanceSettings: instanceSettings,
},
Headers: map[string]string{},
Queries: []backend.DataQuery{},
PluginContext: pCtx,
Headers: map[string]string{},
Queries: []backend.DataQuery{},
}
for _, q := range queries {
@@ -355,9 +349,3 @@ func (s *ServiceImpl) getDataSourceFromQuery(ctx context.Context, user *user.Sig
return nil, ErrInvalidDatasourceID
}
func (s *ServiceImpl) decryptSecureJsonDataFn(ctx context.Context) func(ds *datasources.DataSource) (map[string]string, error) {
return func(ds *datasources.DataSource) (map[string]string, error) {
return s.dataSourceService.DecryptedValues(ctx, ds)
}
}

View File

@@ -16,18 +16,18 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/expr"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/models/roletype"
"github.com/grafana/grafana/pkg/plugins"
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/contexthandler/ctxkey"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/datasources"
fakeDatasources "github.com/grafana/grafana/pkg/services/datasources/fakes"
dsSvc "github.com/grafana/grafana/pkg/services/datasources/service"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
pluginSettings "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings/service"
"github.com/grafana/grafana/pkg/services/secrets/fakes"
secretskvs "github.com/grafana/grafana/pkg/services/secrets/kvstore"
secretsmng "github.com/grafana/grafana/pkg/services/secrets/manager"
@@ -431,28 +431,42 @@ func TestQueryDataMultipleSources(t *testing.T) {
}
func setup(t *testing.T) *testContext {
dss := []*datasources.DataSource{
{UID: "gIEkMvIVz", Type: "postgres"},
{UID: "sEx6ZvSVk", Type: "testdata"},
{UID: "ds1", Type: "mysql"},
{UID: "ds2", Type: "mysql"},
}
t.Helper()
pc := &fakePluginClient{}
dc := &fakeDataSourceCache{ds: &datasources.DataSource{}}
dc := &fakeDataSourceCache{cache: dss}
rv := &fakePluginRequestValidator{}
sqlStore := db.InitTestDB(t)
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
ss := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
ssvc := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
quotaService := quotatest.New(false, nil)
ds, err := dsSvc.ProvideService(nil, ssvc, ss, nil, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService)
require.NoError(t, err)
fakeDatasourceService := &fakeDatasources.FakeDataSourceService{
DataSources: nil,
DataSources: dss,
SimulatePluginFailure: false,
}
exprService := expr.ProvideService(&setting.Cfg{ExpressionsEnabled: true}, pc, fakeDatasourceService, &featuremgmt.FeatureManager{}, nil, tracing.InitializeTracerForTest())
queryService := ProvideService(setting.NewCfg(), dc, exprService, rv, ds, pc) // provider belonging to this package
pCtxProvider := plugincontext.ProvideService(
localcache.ProvideService(), &plugins.FakePluginStore{
PluginList: []plugins.PluginDTO{
{JSONData: plugins.JSONData{ID: "postgres"}},
{JSONData: plugins.JSONData{ID: "testdata"}},
{JSONData: plugins.JSONData{ID: "mysql"}},
},
}, fakeDatasourceService,
pluginSettings.ProvideService(sqlStore, secretsService),
)
exprService := expr.ProvideService(&setting.Cfg{ExpressionsEnabled: true}, pc, pCtxProvider,
&featuremgmt.FeatureManager{}, nil, tracing.InitializeTracerForTest())
queryService := ProvideService(setting.NewCfg(), dc, exprService, rv, pc, pCtxProvider) // provider belonging to this package
return &testContext{
pluginContext: pc,
secretStore: ss,
dataSourceCache: dc,
pluginRequestValidator: rv,
queryService: queryService,
signedInUser: &user.SignedInUser{OrgID: 1, Login: "login", Name: "name", Email: "email", OrgRole: roletype.RoleAdmin},
@@ -462,7 +476,6 @@ func setup(t *testing.T) *testContext {
type testContext struct {
pluginContext *fakePluginClient
secretStore secretskvs.SecretsKVStore
dataSourceCache *fakeDataSourceCache
pluginRequestValidator *fakePluginRequestValidator
queryService *ServiceImpl // implementation belonging to this package
signedInUser *user.SignedInUser
@@ -493,7 +506,7 @@ func (rv *fakePluginRequestValidator) Validate(dsURL string, req *http.Request)
}
type fakeDataSourceCache struct {
ds *datasources.DataSource
cache []*datasources.DataSource
}
func (c *fakeDataSourceCache) GetDatasource(ctx context.Context, datasourceID int64, user *user.SignedInUser, skipCache bool) (*datasources.DataSource, error) {
@@ -502,9 +515,13 @@ func (c *fakeDataSourceCache) GetDatasource(ctx context.Context, datasourceID in
}
func (c *fakeDataSourceCache) GetDatasourceByUID(ctx context.Context, datasourceUID string, user *user.SignedInUser, skipCache bool) (*datasources.DataSource, error) {
return &datasources.DataSource{
UID: datasourceUID,
}, nil
for _, ds := range c.cache {
if ds.UID == datasourceUID {
return ds, nil
}
}
return nil, fmt.Errorf("not found")
}
type fakePluginClient struct {