From 6176bcff6977bda71f4fde10a52dde6d7d7ceb9a Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Mon, 27 Nov 2017 17:23:35 -0500 Subject: PLT-8131 (part2) Add plugin key value store support (#7902) * Add plugin key value store support * Add localization strings * Updates per feedback --- plugin/api.go | 16 ++++++++- plugin/plugintest/api.go | 38 ++++++++++++++++++++ plugin/rpcplugin/api.go | 85 +++++++++++++++++++++++++++++++++++++++++--- plugin/rpcplugin/api_test.go | 18 +++++++++- 4 files changed, 150 insertions(+), 7 deletions(-) (limited to 'plugin') diff --git a/plugin/api.go b/plugin/api.go index 8d27bc794..4bcfd112b 100644 --- a/plugin/api.go +++ b/plugin/api.go @@ -79,6 +79,20 @@ type API interface { // GetPost gets a post. GetPost(postId string) (*model.Post, *model.AppError) - // Update post updates a post. + // UpdatePost updates a post. UpdatePost(post *model.Post) (*model.Post, *model.AppError) + + // KeyValueStore returns an object for accessing the persistent key value storage. + KeyValueStore() KeyValueStore +} + +type KeyValueStore interface { + // Set will store a key-value pair, unique per plugin. + Set(key string, value []byte) *model.AppError + + // Get will retrieve a value based on the key. Returns nil for non-existent keys. + Get(key string) ([]byte, *model.AppError) + + // Delete will remove a key-value pair. Returns nil for non-existent keys. + Delete(key string) *model.AppError } diff --git a/plugin/plugintest/api.go b/plugin/plugintest/api.go index c0e77648b..37052b4cf 100644 --- a/plugin/plugintest/api.go +++ b/plugin/plugintest/api.go @@ -12,9 +12,15 @@ import ( type API struct { mock.Mock + Store *KeyValueStore +} + +type KeyValueStore struct { + mock.Mock } var _ plugin.API = (*API)(nil) +var _ plugin.KeyValueStore = (*KeyValueStore)(nil) func (m *API) LoadPluginConfiguration(dest interface{}) error { return m.Called(dest).Error(0) @@ -235,3 +241,35 @@ func (m *API) UpdatePost(post *model.Post) (*model.Post, *model.AppError) { err, _ := ret.Get(1).(*model.AppError) return postOut, err } + +func (m *API) KeyValueStore() plugin.KeyValueStore { + return m.Store +} + +func (m *KeyValueStore) Set(key string, value []byte) *model.AppError { + ret := m.Called(key, value) + if f, ok := ret.Get(0).(func(string, []byte) *model.AppError); ok { + return f(key, value) + } + err, _ := ret.Get(0).(*model.AppError) + return err +} + +func (m *KeyValueStore) Get(key string) ([]byte, *model.AppError) { + ret := m.Called(key) + if f, ok := ret.Get(0).(func(string) ([]byte, *model.AppError)); ok { + return f(key) + } + psv, _ := ret.Get(0).([]byte) + err, _ := ret.Get(1).(*model.AppError) + return psv, err +} + +func (m *KeyValueStore) Delete(key string) *model.AppError { + ret := m.Called(key) + if f, ok := ret.Get(0).(func(string) *model.AppError); ok { + return f(key) + } + err, _ := ret.Get(0).(*model.AppError) + return err +} diff --git a/plugin/rpcplugin/api.go b/plugin/rpcplugin/api.go index 98333b1d9..f2068e815 100644 --- a/plugin/rpcplugin/api.go +++ b/plugin/rpcplugin/api.go @@ -259,6 +259,41 @@ func (api *LocalAPI) UpdatePost(args *model.Post, reply *APIPostReply) error { return nil } +type APIKeyValueStoreReply struct { + Value []byte + Error *model.AppError +} + +type APIKeyValueStoreSetArgs struct { + Key string + Value []byte +} + +func (api *LocalAPI) KeyValueStoreSet(args *APIKeyValueStoreSetArgs, reply *APIErrorReply) error { + err := api.api.KeyValueStore().Set(args.Key, args.Value) + *reply = APIErrorReply{ + Error: err, + } + return nil +} + +func (api *LocalAPI) KeyValueStoreGet(args string, reply *APIKeyValueStoreReply) error { + v, err := api.api.KeyValueStore().Get(args) + *reply = APIKeyValueStoreReply{ + Value: v, + Error: err, + } + return nil +} + +func (api *LocalAPI) KeyValueStoreDelete(args string, reply *APIErrorReply) error { + err := api.api.KeyValueStore().Delete(args) + *reply = APIErrorReply{ + Error: err, + } + return nil +} + func ServeAPI(api plugin.API, conn io.ReadWriteCloser, muxer *Muxer) { server := rpc.NewServer() server.Register(&LocalAPI{ @@ -269,11 +304,17 @@ func ServeAPI(api plugin.API, conn io.ReadWriteCloser, muxer *Muxer) { } type RemoteAPI struct { - client *rpc.Client - muxer *Muxer + client *rpc.Client + muxer *Muxer + keyValueStore *RemoteKeyValueStore +} + +type RemoteKeyValueStore struct { + api *RemoteAPI } var _ plugin.API = (*RemoteAPI)(nil) +var _ plugin.KeyValueStore = (*RemoteKeyValueStore)(nil) func (api *RemoteAPI) LoadPluginConfiguration(dest interface{}) error { var config []byte @@ -467,13 +508,47 @@ func (api *RemoteAPI) UpdatePost(post *model.Post) (*model.Post, *model.AppError return reply.Post, reply.Error } +func (api *RemoteAPI) KeyValueStore() plugin.KeyValueStore { + return api.keyValueStore +} + +func (s *RemoteKeyValueStore) Set(key string, value []byte) *model.AppError { + var reply APIErrorReply + if err := s.api.client.Call("LocalAPI.KeyValueStoreSet", &APIKeyValueStoreSetArgs{Key: key, Value: value}, &reply); err != nil { + return model.NewAppError("RemoteAPI.KeyValueStoreSet", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError) + } + return reply.Error +} + +func (s *RemoteKeyValueStore) Get(key string) ([]byte, *model.AppError) { + var reply APIKeyValueStoreReply + if err := s.api.client.Call("LocalAPI.KeyValueStoreGet", key, &reply); err != nil { + return nil, model.NewAppError("RemoteAPI.KeyValueStoreGet", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError) + } + return reply.Value, reply.Error +} + +func (s *RemoteKeyValueStore) Delete(key string) *model.AppError { + var reply APIErrorReply + if err := s.api.client.Call("LocalAPI.KeyValueStoreDelete", key, &reply); err != nil { + return model.NewAppError("RemoteAPI.KeyValueStoreDelete", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError) + } + return reply.Error +} + func (h *RemoteAPI) Close() error { return h.client.Close() } func ConnectAPI(conn io.ReadWriteCloser, muxer *Muxer) *RemoteAPI { - return &RemoteAPI{ - client: rpc.NewClient(conn), - muxer: muxer, + remoteKeyValueStore := &RemoteKeyValueStore{} + remoteApi := &RemoteAPI{ + client: rpc.NewClient(conn), + muxer: muxer, + keyValueStore: remoteKeyValueStore, } + + remoteKeyValueStore.api = remoteApi + + return remoteApi } diff --git a/plugin/rpcplugin/api_test.go b/plugin/rpcplugin/api_test.go index 080f2825f..0c7321162 100644 --- a/plugin/rpcplugin/api_test.go +++ b/plugin/rpcplugin/api_test.go @@ -34,7 +34,8 @@ func testAPIRPC(api plugin.API, f func(plugin.API)) { } func TestAPI(t *testing.T) { - var api plugintest.API + keyValueStore := &plugintest.KeyValueStore{} + api := plugintest.API{Store: keyValueStore} defer api.AssertExpectations(t) type Config struct { @@ -199,5 +200,20 @@ func TestAPI(t *testing.T) { post, err = remote.UpdatePost(testPost) assert.Equal(t, testPost, post) assert.Nil(t, err) + + api.KeyValueStore().(*plugintest.KeyValueStore).On("Set", "thekey", []byte("thevalue")).Return(nil).Once() + err = remote.KeyValueStore().Set("thekey", []byte("thevalue")) + assert.Nil(t, err) + + api.KeyValueStore().(*plugintest.KeyValueStore).On("Get", "thekey").Return(func(key string) ([]byte, *model.AppError) { + return []byte("thevalue"), nil + }).Once() + ret, err := remote.KeyValueStore().Get("thekey") + assert.Nil(t, err) + assert.Equal(t, []byte("thevalue"), ret) + + api.KeyValueStore().(*plugintest.KeyValueStore).On("Delete", "thekey").Return(nil).Once() + err = remote.KeyValueStore().Delete("thekey") + assert.Nil(t, err) }) } -- cgit v1.2.3-1-g7c22