From e67d89b9a8715c8bd6ef9cf05e7edf729b75deca Mon Sep 17 00:00:00 2001 From: Hanzei <16541325+hanzei@users.noreply.github.com> Date: Thu, 8 Nov 2018 19:17:07 +0100 Subject: [PATCH] Add plugin methods to plugin API (#9744) --- app/plugin_api.go | 35 ++++++++ app/plugin_api_test.go | 64 ++++++++++++++- app/plugin_statuses.go | 21 +++++ model/client4.go | 6 +- plugin/api.go | 29 +++++++ plugin/client_rpc_generated.go | 141 +++++++++++++++++++++++++++++++++ plugin/plugintest/api.go | 98 +++++++++++++++++++++++ 7 files changed, 390 insertions(+), 4 deletions(-) diff --git a/app/plugin_api.go b/app/plugin_api.go index a0c449b874..6aae2e618c 100644 --- a/app/plugin_api.go +++ b/app/plugin_api.go @@ -438,6 +438,41 @@ func (api *PluginAPI) GetEmojiImage(emojiId string) ([]byte, string, *model.AppE return api.app.GetEmojiImage(emojiId) } +// Plugin Section + +func (api *PluginAPI) GetPlugins() ([]*model.Manifest, *model.AppError) { + plugins, err := api.app.GetPlugins() + if err != nil { + return nil, err + } + var manifests []*model.Manifest + for _, manifest := range plugins.Active { + manifests = append(manifests, &manifest.Manifest) + } + for _, manifest := range plugins.Inactive { + manifests = append(manifests, &manifest.Manifest) + } + return manifests, nil +} + +func (api *PluginAPI) EnablePlugin(id string) *model.AppError { + return api.app.EnablePlugin(id) +} + +func (api *PluginAPI) DisablePlugin(id string) *model.AppError { + return api.app.DisablePlugin(id) +} + +func (api *PluginAPI) RemovePlugin(id string) *model.AppError { + return api.app.RemovePlugin(id) +} + +func (api *PluginAPI) GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) { + return api.app.GetPluginStatus(id) +} + +// KV Store Section + func (api *PluginAPI) KVSet(key string, value []byte) *model.AppError { return api.app.SetPluginKey(api.id, key, value) } diff --git a/app/plugin_api_test.go b/app/plugin_api_test.go index 6c51189dee..7311348990 100644 --- a/app/plugin_api_test.go +++ b/app/plugin_api_test.go @@ -5,6 +5,7 @@ package app import ( "encoding/json" + "fmt" "io/ioutil" "os" "path/filepath" @@ -31,7 +32,10 @@ func setupPluginApiTest(t *testing.T, pluginCode string, pluginManifest string, compileGo(t, pluginCode, backend) ioutil.WriteFile(filepath.Join(pluginDir, pluginId, "plugin.json"), []byte(pluginManifest), 0600) - env.Activate(pluginId) + manifest, activated, reterr := env.Activate(pluginId) + require.Nil(t, reterr) + require.NotNil(t, manifest) + require.True(t, activated) app.Srv.Plugins = env } @@ -215,3 +219,61 @@ func TestPluginAPIGetProfileImage(t *testing.T) { require.NotNil(t, err) require.Nil(t, data) } + +func TestPluginAPIGetPlugins(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + api := th.SetupPluginAPI() + + pluginCode := ` + package main + + import ( + "github.com/mattermost/mattermost-server/plugin" + ) + + type MyPlugin struct { + plugin.MattermostPlugin + } + + func main() { + plugin.ClientMain(&MyPlugin{}) + } + ` + + pluginDir, err := ioutil.TempDir("", "") + require.NoError(t, err) + webappPluginDir, err := ioutil.TempDir("", "") + require.NoError(t, err) + defer os.RemoveAll(pluginDir) + defer os.RemoveAll(webappPluginDir) + + env, err := plugin.NewEnvironment(th.App.NewPluginAPI, pluginDir, webappPluginDir, th.App.Log) + require.NoError(t, err) + + pluginIDs := []string{"pluginid1", "pluginid2", "pluginid3"} + var pluginManifests []*model.Manifest + for _, pluginID := range pluginIDs { + backend := filepath.Join(pluginDir, pluginID, "backend.exe") + compileGo(t, pluginCode, backend) + + ioutil.WriteFile(filepath.Join(pluginDir, pluginID, "plugin.json"), []byte(fmt.Sprintf(`{"id": "%s", "server": {"executable": "backend.exe"}}`, pluginID)), 0600) + manifest, activated, reterr := env.Activate(pluginID) + + require.Nil(t, reterr) + require.NotNil(t, manifest) + require.True(t, activated) + pluginManifests = append(pluginManifests, manifest) + } + th.App.Srv.Plugins = env + + // Decative the last one for testing + sucess := env.Deactivate(pluginIDs[len(pluginIDs)-1]) + require.True(t, sucess) + + // check existing user first + plugins, err := api.GetPlugins() + assert.Nil(t, err) + assert.NotEmpty(t, plugins) + assert.Equal(t, pluginManifests, plugins) +} diff --git a/app/plugin_statuses.go b/app/plugin_statuses.go index df6477f89b..fc5561bcfc 100644 --- a/app/plugin_statuses.go +++ b/app/plugin_statuses.go @@ -9,6 +9,27 @@ import ( "github.com/mattermost/mattermost-server/model" ) +// GetPluginStatus returns the status for a plugin installed on this server. +func (a *App) GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) { + if a.Srv.Plugins == nil || !*a.Config().PluginSettings.Enable { + return nil, model.NewAppError("GetPluginStatus", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) + } + + pluginStatuses, err := a.Srv.Plugins.Statuses() + if err != nil { + return nil, model.NewAppError("GetPluginStatus", "app.plugin.get_statuses.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + // Add our cluster ID + for _, status := range pluginStatuses { + if status.PluginId == id { + status.ClusterId = a.GetClusterId() + return status, nil + } + } + return nil, model.NewAppError("GetPluginStatus", "app.plugin.not_installed.app_error", nil, "", http.StatusBadRequest) +} + // GetPluginStatuses returns the status for plugins installed on this server. func (a *App) GetPluginStatuses() (model.PluginStatuses, *model.AppError) { if a.Srv.Plugins == nil || !*a.Config().PluginSettings.Enable { diff --git a/model/client4.go b/model/client4.go index cce3b62ef9..484694416b 100644 --- a/model/client4.go +++ b/model/client4.go @@ -3771,7 +3771,7 @@ func (c *Client4) GetPluginStatuses() (PluginStatuses, *Response) { } } -// RemovePlugin will deactivate and delete a plugin. +// RemovePlugin will disable and delete a plugin. // WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE. func (c *Client4) RemovePlugin(id string) (bool, *Response) { if r, err := c.DoApiDelete(c.GetPluginRoute(id)); err != nil { @@ -3793,7 +3793,7 @@ func (c *Client4) GetWebappPlugins() ([]*Manifest, *Response) { } } -// ActivatePlugin will activate an plugin installed. +// EnablePlugin will enable an plugin installed. // WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE. func (c *Client4) EnablePlugin(id string) (bool, *Response) { if r, err := c.DoApiPost(c.GetPluginRoute(id)+"/enable", ""); err != nil { @@ -3804,7 +3804,7 @@ func (c *Client4) EnablePlugin(id string) (bool, *Response) { } } -// DeactivatePlugin will deactivate an active plugin. +// DisablePlugin will disable an enabled plugin. // WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE. func (c *Client4) DisablePlugin(id string) (bool, *Response) { if r, err := c.DoApiPost(c.GetPluginRoute(id)+"/disable", ""); err != nil { diff --git a/plugin/api.go b/plugin/api.go index 2589fa9e01..33d07c40ac 100644 --- a/plugin/api.go +++ b/plugin/api.go @@ -314,6 +314,35 @@ type API interface { // Minimum server version: 5.6 UploadFile(data []byte, channelId string, filename string) (*model.FileInfo, *model.AppError) + // Plugin Section + + // GetPlugins will return a list of plugin manifests for currently active plugins. + // + // Minimum server version: 5.6 + GetPlugins() ([]*model.Manifest, *model.AppError) + + // EnablePlugin will enable an plugin installed. + // + // Minimum server version: 5.6 + EnablePlugin(id string) *model.AppError + + // DisablePlugin will disable an enabled plugin. + // + // Minimum server version: 5.6 + DisablePlugin(id string) *model.AppError + + // RemovePlugin will disable and delete a plugin. + // + // Minimum server version: 5.6 + RemovePlugin(id string) *model.AppError + + // GetPluginStatus will return the status of a plugin. + // + // Minimum server version: 5.6 + GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) + + // KV Store Section + // KVSet will store a key-value pair, unique per plugin. KVSet(key string, value []byte) *model.AppError diff --git a/plugin/client_rpc_generated.go b/plugin/client_rpc_generated.go index 6b20dd8679..2e18934715 100644 --- a/plugin/client_rpc_generated.go +++ b/plugin/client_rpc_generated.go @@ -2728,6 +2728,147 @@ func (s *apiRPCServer) UploadFile(args *Z_UploadFileArgs, returns *Z_UploadFileR return nil } +type Z_GetPluginsArgs struct { +} + +type Z_GetPluginsReturns struct { + A []*model.Manifest + B *model.AppError +} + +func (g *apiRPCClient) GetPlugins() ([]*model.Manifest, *model.AppError) { + _args := &Z_GetPluginsArgs{} + _returns := &Z_GetPluginsReturns{} + if err := g.client.Call("Plugin.GetPlugins", _args, _returns); err != nil { + log.Printf("RPC call to GetPlugins API failed: %s", err.Error()) + } + return _returns.A, _returns.B +} + +func (s *apiRPCServer) GetPlugins(args *Z_GetPluginsArgs, returns *Z_GetPluginsReturns) error { + if hook, ok := s.impl.(interface { + GetPlugins() ([]*model.Manifest, *model.AppError) + }); ok { + returns.A, returns.B = hook.GetPlugins() + } else { + return encodableError(fmt.Errorf("API GetPlugins called but not implemented.")) + } + return nil +} + +type Z_GetPluginStatusArgs struct { + A string +} + +type Z_GetPluginStatusReturns struct { + A *model.PluginStatus + B *model.AppError +} + +func (g *apiRPCClient) GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) { + _args := &Z_GetPluginStatusArgs{id} + _returns := &Z_GetPluginStatusReturns{} + if err := g.client.Call("Plugin.GetPluginStatus", _args, _returns); err != nil { + log.Printf("RPC call to GetPluginStatus API failed: %s", err.Error()) + } + return _returns.A, _returns.B +} + +func (s *apiRPCServer) GetPluginStatus(args *Z_GetPluginStatusArgs, returns *Z_GetPluginStatusReturns) error { + if hook, ok := s.impl.(interface { + GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) + }); ok { + returns.A, returns.B = hook.GetPluginStatus(args.A) + } else { + return encodableError(fmt.Errorf("API GetPluginStatus called but not implemented.")) + } + return nil +} + +type Z_EnablePluginArgs struct { + A string +} + +type Z_EnablePluginReturns struct { + A *model.AppError +} + +func (g *apiRPCClient) EnablePlugin(id string) *model.AppError { + _args := &Z_EnablePluginArgs{id} + _returns := &Z_EnablePluginReturns{} + if err := g.client.Call("Plugin.EnablePlugin", _args, _returns); err != nil { + log.Printf("RPC call to EnablePlugin API failed: %s", err.Error()) + } + return _returns.A +} + +func (s *apiRPCServer) EnablePlugin(args *Z_EnablePluginArgs, returns *Z_EnablePluginReturns) error { + if hook, ok := s.impl.(interface { + EnablePlugin(id string) *model.AppError + }); ok { + returns.A = hook.EnablePlugin(args.A) + } else { + return encodableError(fmt.Errorf("API EnablePlugin called but not implemented.")) + } + return nil +} + +type Z_DisablePluginArgs struct { + A string +} + +type Z_DisablePluginReturns struct { + A *model.AppError +} + +func (g *apiRPCClient) DisablePlugin(id string) *model.AppError { + _args := &Z_DisablePluginArgs{id} + _returns := &Z_DisablePluginReturns{} + if err := g.client.Call("Plugin.DisablePlugin", _args, _returns); err != nil { + log.Printf("RPC call to DisablePlugin API failed: %s", err.Error()) + } + return _returns.A +} + +func (s *apiRPCServer) DisablePlugin(args *Z_DisablePluginArgs, returns *Z_DisablePluginReturns) error { + if hook, ok := s.impl.(interface { + DisablePlugin(id string) *model.AppError + }); ok { + returns.A = hook.DisablePlugin(args.A) + } else { + return encodableError(fmt.Errorf("API DisablePlugin called but not implemented.")) + } + return nil +} + +type Z_RemovePluginArgs struct { + A string +} + +type Z_RemovePluginReturns struct { + A *model.AppError +} + +func (g *apiRPCClient) RemovePlugin(id string) *model.AppError { + _args := &Z_RemovePluginArgs{id} + _returns := &Z_RemovePluginReturns{} + if err := g.client.Call("Plugin.RemovePlugin", _args, _returns); err != nil { + log.Printf("RPC call to RemovePlugin API failed: %s", err.Error()) + } + return _returns.A +} + +func (s *apiRPCServer) RemovePlugin(args *Z_RemovePluginArgs, returns *Z_RemovePluginReturns) error { + if hook, ok := s.impl.(interface { + RemovePlugin(id string) *model.AppError + }); ok { + returns.A = hook.RemovePlugin(args.A) + } else { + return encodableError(fmt.Errorf("API RemovePlugin called but not implemented.")) + } + return nil +} + type Z_KVSetArgs struct { A string B []byte diff --git a/plugin/plugintest/api.go b/plugin/plugintest/api.go index 14628095d2..d192fe713f 100644 --- a/plugin/plugintest/api.go +++ b/plugin/plugintest/api.go @@ -333,6 +333,38 @@ func (_m *API) DeleteUser(userId string) *model.AppError { return r0 } +// DisablePlugin provides a mock function with given fields: id +func (_m *API) DisablePlugin(id string) *model.AppError { + ret := _m.Called(id) + + var r0 *model.AppError + if rf, ok := ret.Get(0).(func(string) *model.AppError); ok { + r0 = rf(id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.AppError) + } + } + + return r0 +} + +// EnablePlugin provides a mock function with given fields: id +func (_m *API) EnablePlugin(id string) *model.AppError { + ret := _m.Called(id) + + var r0 *model.AppError + if rf, ok := ret.Get(0).(func(string) *model.AppError); ok { + r0 = rf(id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.AppError) + } + } + + return r0 +} + // GetChannel provides a mock function with given fields: channelId func (_m *API) GetChannel(channelId string) (*model.Channel, *model.AppError) { ret := _m.Called(channelId) @@ -779,6 +811,56 @@ func (_m *API) GetLDAPUserAttributes(userId string, attributes []string) (map[st return r0, r1 } +// GetPluginStatus provides a mock function with given fields: id +func (_m *API) GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) { + ret := _m.Called(id) + + var r0 *model.PluginStatus + if rf, ok := ret.Get(0).(func(string) *model.PluginStatus); ok { + r0 = rf(id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.PluginStatus) + } + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func(string) *model.AppError); ok { + r1 = rf(id) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + +// GetPlugins provides a mock function with given fields: +func (_m *API) GetPlugins() ([]*model.Manifest, *model.AppError) { + ret := _m.Called() + + var r0 []*model.Manifest + if rf, ok := ret.Get(0).(func() []*model.Manifest); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.Manifest) + } + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func() *model.AppError); ok { + r1 = rf() + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + // GetPost provides a mock function with given fields: postId func (_m *API) GetPost(postId string) (*model.Post, *model.AppError) { ret := _m.Called(postId) @@ -1664,6 +1746,22 @@ func (_m *API) RegisterCommand(command *model.Command) error { return r0 } +// RemovePlugin provides a mock function with given fields: id +func (_m *API) RemovePlugin(id string) *model.AppError { + ret := _m.Called(id) + + var r0 *model.AppError + if rf, ok := ret.Get(0).(func(string) *model.AppError); ok { + r0 = rf(id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.AppError) + } + } + + return r0 +} + // RemoveReaction provides a mock function with given fields: reaction func (_m *API) RemoveReaction(reaction *model.Reaction) *model.AppError { ret := _m.Called(reaction)