summaryrefslogtreecommitdiffstats
path: root/plugin/pluginenv
diff options
context:
space:
mode:
authorChristopher Speller <crspeller@gmail.com>2018-06-25 12:33:13 -0700
committerGitHub <noreply@github.com>2018-06-25 12:33:13 -0700
commit1e5c432e1029601a664454388ae366ef69618d62 (patch)
treecb9e8bfb66640ac3b29c934bb2c3202d25aeb368 /plugin/pluginenv
parentecefa6cdd1e7376046bbec82c1b47f7756fea646 (diff)
downloadchat-1e5c432e1029601a664454388ae366ef69618d62.tar.gz
chat-1e5c432e1029601a664454388ae366ef69618d62.tar.bz2
chat-1e5c432e1029601a664454388ae366ef69618d62.zip
MM-10702 Moving plugins to use hashicorp go-plugin. (#8978)
* Moving plugins to use hashicorp go-plugin. * Tweaks from feedback.
Diffstat (limited to 'plugin/pluginenv')
-rw-r--r--plugin/pluginenv/environment.go396
-rw-r--r--plugin/pluginenv/environment_test.go409
-rw-r--r--plugin/pluginenv/options.go50
-rw-r--r--plugin/pluginenv/options_test.go32
-rw-r--r--plugin/pluginenv/search_path.go35
-rw-r--r--plugin/pluginenv/search_path_test.go62
6 files changed, 0 insertions, 984 deletions
diff --git a/plugin/pluginenv/environment.go b/plugin/pluginenv/environment.go
deleted file mode 100644
index f704aa5bb..000000000
--- a/plugin/pluginenv/environment.go
+++ /dev/null
@@ -1,396 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-// Package pluginenv provides high level functionality for discovering and launching plugins.
-package pluginenv
-
-import (
- "fmt"
- "io/ioutil"
- "net/http"
- "path/filepath"
- "sync"
-
- "github.com/pkg/errors"
-
- "github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/plugin"
-)
-
-type APIProviderFunc func(*model.Manifest) (plugin.API, error)
-type SupervisorProviderFunc func(*model.BundleInfo) (plugin.Supervisor, error)
-
-type ActivePlugin struct {
- BundleInfo *model.BundleInfo
- Supervisor plugin.Supervisor
-}
-
-// Environment represents an environment that plugins are discovered and launched in.
-type Environment struct {
- searchPath string
- webappPath string
- apiProvider APIProviderFunc
- supervisorProvider SupervisorProviderFunc
- activePlugins map[string]ActivePlugin
- mutex sync.RWMutex
-}
-
-type Option func(*Environment)
-
-// Creates a new environment. At a minimum, the APIProvider and SearchPath options are required.
-func New(options ...Option) (*Environment, error) {
- env := &Environment{
- activePlugins: make(map[string]ActivePlugin),
- }
- for _, opt := range options {
- opt(env)
- }
- if env.supervisorProvider == nil {
- env.supervisorProvider = DefaultSupervisorProvider
- }
- if env.searchPath == "" {
- return nil, fmt.Errorf("a search path must be provided")
- }
- return env, nil
-}
-
-// Returns the configured webapp path.
-func (env *Environment) WebappPath() string {
- return env.webappPath
-}
-
-// Returns the configured search path.
-func (env *Environment) SearchPath() string {
- return env.searchPath
-}
-
-// Returns a list of all plugins found within the environment.
-func (env *Environment) Plugins() ([]*model.BundleInfo, error) {
- return ScanSearchPath(env.searchPath)
-}
-
-// Returns a list of all currently active plugins within the environment.
-func (env *Environment) ActivePlugins() []*model.BundleInfo {
- env.mutex.RLock()
- defer env.mutex.RUnlock()
-
- activePlugins := []*model.BundleInfo{}
- for _, p := range env.activePlugins {
- activePlugins = append(activePlugins, p.BundleInfo)
- }
-
- return activePlugins
-}
-
-// Returns the ids of the currently active plugins.
-func (env *Environment) ActivePluginIds() (ids []string) {
- env.mutex.RLock()
- defer env.mutex.RUnlock()
-
- for id := range env.activePlugins {
- ids = append(ids, id)
- }
- return
-}
-
-// Returns true if the plugin is active, false otherwise.
-func (env *Environment) IsPluginActive(pluginId string) bool {
- env.mutex.RLock()
- defer env.mutex.RUnlock()
-
- for id := range env.activePlugins {
- if id == pluginId {
- return true
- }
- }
-
- return false
-}
-
-// Activates the plugin with the given id.
-func (env *Environment) ActivatePlugin(id string, onError func(error)) error {
- env.mutex.Lock()
- defer env.mutex.Unlock()
-
- if !plugin.IsValidId(id) {
- return fmt.Errorf("invalid plugin id: %s", id)
- }
-
- if _, ok := env.activePlugins[id]; ok {
- return fmt.Errorf("plugin already active: %v", id)
- }
- plugins, err := ScanSearchPath(env.searchPath)
- if err != nil {
- return err
- }
- var bundle *model.BundleInfo
- for _, p := range plugins {
- if p.Manifest != nil && p.Manifest.Id == id {
- if bundle != nil {
- return fmt.Errorf("multiple plugins found: %v", id)
- }
- bundle = p
- }
- }
- if bundle == nil {
- return fmt.Errorf("plugin not found: %v", id)
- }
-
- activePlugin := ActivePlugin{BundleInfo: bundle}
-
- var supervisor plugin.Supervisor
-
- if bundle.Manifest.Backend != nil {
- if env.apiProvider == nil {
- return fmt.Errorf("env missing api provider, cannot activate plugin: %v", id)
- }
-
- supervisor, err = env.supervisorProvider(bundle)
- if err != nil {
- return errors.Wrapf(err, "unable to create supervisor for plugin: %v", id)
- }
- api, err := env.apiProvider(bundle.Manifest)
- if err != nil {
- return errors.Wrapf(err, "unable to get api for plugin: %v", id)
- }
- if err := supervisor.Start(api); err != nil {
- return errors.Wrapf(err, "unable to start plugin: %v", id)
- }
- if onError != nil {
- go func() {
- err := supervisor.Wait()
- if err != nil {
- onError(err)
- }
- }()
- }
-
- activePlugin.Supervisor = supervisor
- }
-
- if bundle.Manifest.Webapp != nil {
- if env.webappPath == "" {
- if supervisor != nil {
- supervisor.Stop()
- }
- return fmt.Errorf("env missing webapp path, cannot activate plugin: %v", id)
- }
-
- bundlePath := filepath.Clean(bundle.Manifest.Webapp.BundlePath)
- if bundlePath == "" || bundlePath[0] == '.' {
- return fmt.Errorf("invalid webapp bundle path")
- }
- bundlePath = filepath.Join(env.searchPath, id, bundlePath)
-
- webappBundle, err := ioutil.ReadFile(bundlePath)
- if err != nil {
- // Backwards compatibility for plugins where webapp.bundle_path was ignored. This should
- // be removed eventually.
- if webappBundle2, err2 := ioutil.ReadFile(fmt.Sprintf("%s/%s/webapp/%s_bundle.js", env.searchPath, id, id)); err2 == nil {
- webappBundle = webappBundle2
- } else {
- if supervisor != nil {
- supervisor.Stop()
- }
- return errors.Wrapf(err, "unable to read webapp bundle: %v", id)
- }
- }
-
- err = ioutil.WriteFile(fmt.Sprintf("%s/%s_bundle.js", env.webappPath, id), webappBundle, 0644)
- if err != nil {
- if supervisor != nil {
- supervisor.Stop()
- }
- return errors.Wrapf(err, "unable to write webapp bundle: %v", id)
- }
- }
-
- env.activePlugins[id] = activePlugin
- return nil
-}
-
-// Deactivates the plugin with the given id.
-func (env *Environment) DeactivatePlugin(id string) error {
- env.mutex.Lock()
- defer env.mutex.Unlock()
-
- if activePlugin, ok := env.activePlugins[id]; !ok {
- return fmt.Errorf("plugin not active: %v", id)
- } else {
- delete(env.activePlugins, id)
- var err error
- if activePlugin.Supervisor != nil {
- err = activePlugin.Supervisor.Hooks().OnDeactivate()
- if serr := activePlugin.Supervisor.Stop(); err == nil {
- err = serr
- }
- }
- return err
- }
-}
-
-// Deactivates all plugins and gracefully shuts down the environment.
-func (env *Environment) Shutdown() (errs []error) {
- env.mutex.Lock()
- defer env.mutex.Unlock()
-
- for _, activePlugin := range env.activePlugins {
- if activePlugin.Supervisor != nil {
- if err := activePlugin.Supervisor.Hooks().OnDeactivate(); err != nil {
- errs = append(errs, errors.Wrapf(err, "OnDeactivate() error for %v", activePlugin.BundleInfo.Manifest.Id))
- }
- if err := activePlugin.Supervisor.Stop(); err != nil {
- errs = append(errs, errors.Wrapf(err, "error stopping supervisor for %v", activePlugin.BundleInfo.Manifest.Id))
- }
- }
- }
- env.activePlugins = make(map[string]ActivePlugin)
- return
-}
-
-type MultiPluginHooks struct {
- env *Environment
-}
-
-type SinglePluginHooks struct {
- env *Environment
- pluginId string
-}
-
-func (env *Environment) Hooks() *MultiPluginHooks {
- return &MultiPluginHooks{
- env: env,
- }
-}
-
-func (env *Environment) HooksForPlugin(id string) *SinglePluginHooks {
- return &SinglePluginHooks{
- env: env,
- pluginId: id,
- }
-}
-
-func (h *MultiPluginHooks) invoke(f func(plugin.Hooks) error) (errs []error) {
- h.env.mutex.RLock()
- defer h.env.mutex.RUnlock()
-
- for _, activePlugin := range h.env.activePlugins {
- if activePlugin.Supervisor == nil {
- continue
- }
- if err := f(activePlugin.Supervisor.Hooks()); err != nil {
- errs = append(errs, errors.Wrapf(err, "hook error for %v", activePlugin.BundleInfo.Manifest.Id))
- }
- }
- return
-}
-
-// OnConfigurationChange invokes the OnConfigurationChange hook for all plugins. Any errors
-// encountered will be returned.
-func (h *MultiPluginHooks) OnConfigurationChange() []error {
- return h.invoke(func(hooks plugin.Hooks) error {
- if err := hooks.OnConfigurationChange(); err != nil {
- return errors.Wrapf(err, "error calling OnConfigurationChange hook")
- }
- return nil
- })
-}
-
-// ServeHTTP invokes the ServeHTTP hook for the plugin identified by the request or responds with a
-// 404 not found.
-//
-// It expects the request's context to have a plugin_id set.
-func (h *MultiPluginHooks) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- if id := r.Context().Value("plugin_id"); id != nil {
- if idstr, ok := id.(string); ok {
- h.env.mutex.RLock()
- defer h.env.mutex.RUnlock()
- if plugin, ok := h.env.activePlugins[idstr]; ok && plugin.Supervisor != nil {
- plugin.Supervisor.Hooks().ServeHTTP(w, r)
- return
- }
- }
- }
- http.NotFound(w, r)
-}
-
-// MessageWillBePosted invokes the MessageWillBePosted hook for all plugins. Ordering
-// is not guaranteed and the next plugin will get the previous one's modifications.
-// if a plugin rejects a post, the rest of the plugins will not know that an attempt was made.
-// Returns the final result post, or nil if the post was rejected and a string with a reason
-// for the user the message was rejected.
-func (h *MultiPluginHooks) MessageWillBePosted(post *model.Post) (*model.Post, string) {
- h.env.mutex.RLock()
- defer h.env.mutex.RUnlock()
-
- for _, activePlugin := range h.env.activePlugins {
- if activePlugin.Supervisor == nil {
- continue
- }
- var rejectionReason string
- post, rejectionReason = activePlugin.Supervisor.Hooks().MessageWillBePosted(post)
- if post == nil {
- return nil, rejectionReason
- }
- }
- return post, ""
-}
-
-// MessageWillBeUpdated invokes the MessageWillBeUpdated hook for all plugins. Ordering
-// is not guaranteed and the next plugin will get the previous one's modifications.
-// if a plugin rejects a post, the rest of the plugins will not know that an attempt was made.
-// Returns the final result post, or nil if the post was rejected and a string with a reason
-// for the user the message was rejected.
-func (h *MultiPluginHooks) MessageWillBeUpdated(newPost, oldPost *model.Post) (*model.Post, string) {
- h.env.mutex.RLock()
- defer h.env.mutex.RUnlock()
-
- post := newPost
- for _, activePlugin := range h.env.activePlugins {
- if activePlugin.Supervisor == nil {
- continue
- }
- var rejectionReason string
- post, rejectionReason = activePlugin.Supervisor.Hooks().MessageWillBeUpdated(post, oldPost)
- if post == nil {
- return nil, rejectionReason
- }
- }
- return post, ""
-}
-
-func (h *MultiPluginHooks) MessageHasBeenPosted(post *model.Post) {
- h.invoke(func(hooks plugin.Hooks) error {
- hooks.MessageHasBeenPosted(post)
- return nil
- })
-}
-
-func (h *MultiPluginHooks) MessageHasBeenUpdated(newPost, oldPost *model.Post) {
- h.invoke(func(hooks plugin.Hooks) error {
- hooks.MessageHasBeenUpdated(newPost, oldPost)
- return nil
- })
-}
-
-func (h *SinglePluginHooks) invoke(f func(plugin.Hooks) error) error {
- h.env.mutex.RLock()
- defer h.env.mutex.RUnlock()
-
- if activePlugin, ok := h.env.activePlugins[h.pluginId]; ok && activePlugin.Supervisor != nil {
- if err := f(activePlugin.Supervisor.Hooks()); err != nil {
- return errors.Wrapf(err, "hook error for plugin: %v", activePlugin.BundleInfo.Manifest.Id)
- }
- return nil
- }
- return fmt.Errorf("unable to invoke hook for plugin: %v", h.pluginId)
-}
-
-// ExecuteCommand invokes the ExecuteCommand hook for the plugin.
-func (h *SinglePluginHooks) ExecuteCommand(args *model.CommandArgs) (resp *model.CommandResponse, appErr *model.AppError, err error) {
- err = h.invoke(func(hooks plugin.Hooks) error {
- resp, appErr = hooks.ExecuteCommand(args)
- return nil
- })
- return
-}
diff --git a/plugin/pluginenv/environment_test.go b/plugin/pluginenv/environment_test.go
deleted file mode 100644
index 8c1397799..000000000
--- a/plugin/pluginenv/environment_test.go
+++ /dev/null
@@ -1,409 +0,0 @@
-package pluginenv
-
-import (
- "context"
- "fmt"
- "io/ioutil"
- "net/http"
- "net/http/httptest"
- "os"
- "path/filepath"
- "sync"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
- "github.com/stretchr/testify/require"
-
- "github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/plugin"
- "github.com/mattermost/mattermost-server/plugin/plugintest"
-)
-
-type MockProvider struct {
- mock.Mock
-}
-
-func (m *MockProvider) API(manifest *model.Manifest) (plugin.API, error) {
- ret := m.Called()
- if ret.Get(0) == nil {
- return nil, ret.Error(1)
- }
- return ret.Get(0).(plugin.API), ret.Error(1)
-}
-
-func (m *MockProvider) Supervisor(bundle *model.BundleInfo) (plugin.Supervisor, error) {
- ret := m.Called()
- if ret.Get(0) == nil {
- return nil, ret.Error(1)
- }
- return ret.Get(0).(plugin.Supervisor), ret.Error(1)
-}
-
-type MockSupervisor struct {
- mock.Mock
-}
-
-func (m *MockSupervisor) Start(api plugin.API) error {
- return m.Called(api).Error(0)
-}
-
-func (m *MockSupervisor) Stop() error {
- return m.Called().Error(0)
-}
-
-func (m *MockSupervisor) Hooks() plugin.Hooks {
- return m.Called().Get(0).(plugin.Hooks)
-}
-
-func (m *MockSupervisor) Wait() error {
- return m.Called().Get(0).(error)
-}
-
-func initTmpDir(t *testing.T, files map[string]string) string {
- success := false
- dir, err := ioutil.TempDir("", "mm-plugin-test")
- require.NoError(t, err)
- defer func() {
- if !success {
- os.RemoveAll(dir)
- }
- }()
-
- for name, contents := range files {
- path := filepath.Join(dir, name)
- parent := filepath.Dir(path)
- require.NoError(t, os.MkdirAll(parent, 0700))
- f, err := os.Create(path)
- require.NoError(t, err)
- _, err = f.WriteString(contents)
- f.Close()
- require.NoError(t, err)
- }
-
- success = true
- return dir
-}
-
-func TestNew_MissingOptions(t *testing.T) {
- dir := initTmpDir(t, map[string]string{
- "foo/plugin.json": `{"id": "foo"}`,
- })
- defer os.RemoveAll(dir)
-
- var provider MockProvider
- defer provider.AssertExpectations(t)
-
- env, err := New(
- APIProvider(provider.API),
- )
- assert.Nil(t, env)
- assert.Error(t, err)
-}
-
-func TestEnvironment(t *testing.T) {
- dir := initTmpDir(t, map[string]string{
- ".foo/plugin.json": `{"id": "foo"}`,
- "foo/bar": "asdf",
- "foo/plugin.json": `{"id": "foo", "backend": {}}`,
- "bar/zxc": "qwer",
- "baz/plugin.yaml": "id: baz",
- "bad/plugin.json": "asd",
- "qwe": "asd",
- })
- defer os.RemoveAll(dir)
-
- webappDir := "notarealdirectory"
-
- var provider MockProvider
- defer provider.AssertExpectations(t)
-
- env, err := New(
- SearchPath(dir),
- WebappPath(webappDir),
- APIProvider(provider.API),
- SupervisorProvider(provider.Supervisor),
- )
- require.NoError(t, err)
- defer env.Shutdown()
-
- plugins, err := env.Plugins()
- assert.NoError(t, err)
- assert.Len(t, plugins, 3)
-
- activePlugins := env.ActivePlugins()
- assert.Len(t, activePlugins, 0)
-
- assert.Error(t, env.ActivatePlugin("x", nil))
-
- var api struct{ plugin.API }
- var supervisor MockSupervisor
- defer supervisor.AssertExpectations(t)
- var hooks plugintest.Hooks
- defer hooks.AssertExpectations(t)
-
- provider.On("API").Return(&api, nil)
- provider.On("Supervisor").Return(&supervisor, nil)
-
- supervisor.On("Start", &api).Return(nil)
- supervisor.On("Stop").Return(nil)
- supervisor.On("Hooks").Return(&hooks)
-
- assert.NoError(t, env.ActivatePlugin("foo", nil))
- assert.Equal(t, env.ActivePluginIds(), []string{"foo"})
- activePlugins = env.ActivePlugins()
- assert.Len(t, activePlugins, 1)
- assert.Error(t, env.ActivatePlugin("foo", nil))
- assert.True(t, env.IsPluginActive("foo"))
-
- hooks.On("OnDeactivate").Return(nil)
- assert.NoError(t, env.DeactivatePlugin("foo"))
- assert.Error(t, env.DeactivatePlugin("foo"))
- assert.False(t, env.IsPluginActive("foo"))
-
- assert.NoError(t, env.ActivatePlugin("foo", nil))
- assert.Equal(t, env.ActivePluginIds(), []string{"foo"})
-
- assert.Equal(t, env.SearchPath(), dir)
- assert.Equal(t, env.WebappPath(), webappDir)
-
- assert.Empty(t, env.Shutdown())
-}
-
-func TestEnvironment_DuplicatePluginError(t *testing.T) {
- dir := initTmpDir(t, map[string]string{
- "foo/plugin.json": `{"id": "foo"}`,
- "foo2/plugin.json": `{"id": "foo"}`,
- })
- defer os.RemoveAll(dir)
-
- var provider MockProvider
- defer provider.AssertExpectations(t)
-
- env, err := New(
- SearchPath(dir),
- APIProvider(provider.API),
- SupervisorProvider(provider.Supervisor),
- )
- require.NoError(t, err)
- defer env.Shutdown()
-
- assert.Error(t, env.ActivatePlugin("foo", nil))
- assert.Empty(t, env.ActivePluginIds())
-}
-
-func TestEnvironment_BadSearchPathError(t *testing.T) {
- var provider MockProvider
- defer provider.AssertExpectations(t)
-
- env, err := New(
- SearchPath("thissearchpathshouldnotexist!"),
- APIProvider(provider.API),
- SupervisorProvider(provider.Supervisor),
- )
- require.NoError(t, err)
- defer env.Shutdown()
-
- assert.Error(t, env.ActivatePlugin("foo", nil))
- assert.Empty(t, env.ActivePluginIds())
-}
-
-func TestEnvironment_ActivatePluginErrors(t *testing.T) {
- dir := initTmpDir(t, map[string]string{
- "foo/plugin.json": `{"id": "foo", "backend": {}}`,
- })
- defer os.RemoveAll(dir)
-
- var provider MockProvider
-
- env, err := New(
- SearchPath(dir),
- APIProvider(provider.API),
- SupervisorProvider(provider.Supervisor),
- )
- require.NoError(t, err)
- defer env.Shutdown()
-
- var api struct{ plugin.API }
- var supervisor MockSupervisor
- var hooks plugintest.Hooks
-
- for name, setup := range map[string]func(){
- "SupervisorProviderError": func() {
- provider.On("Supervisor").Return(nil, fmt.Errorf("test error"))
- },
- "APIProviderError": func() {
- provider.On("API").Return(plugin.API(nil), fmt.Errorf("test error"))
- provider.On("Supervisor").Return(&supervisor, nil)
- },
- "SupervisorError": func() {
- provider.On("API").Return(&api, nil)
- provider.On("Supervisor").Return(&supervisor, nil)
-
- supervisor.On("Start", &api).Return(fmt.Errorf("test error"))
- },
- } {
- t.Run(name, func(t *testing.T) {
- supervisor.Mock = mock.Mock{}
- hooks.Mock = mock.Mock{}
- provider.Mock = mock.Mock{}
- setup()
- assert.Error(t, env.ActivatePlugin("foo", nil))
- assert.Empty(t, env.ActivePluginIds())
- supervisor.AssertExpectations(t)
- hooks.AssertExpectations(t)
- provider.AssertExpectations(t)
- })
- }
-}
-
-func TestEnvironment_ShutdownError(t *testing.T) {
- dir := initTmpDir(t, map[string]string{
- "foo/plugin.json": `{"id": "foo", "backend": {}}`,
- })
- defer os.RemoveAll(dir)
-
- var provider MockProvider
- defer provider.AssertExpectations(t)
-
- env, err := New(
- SearchPath(dir),
- APIProvider(provider.API),
- SupervisorProvider(provider.Supervisor),
- )
- require.NoError(t, err)
- defer env.Shutdown()
-
- var api struct{ plugin.API }
- var supervisor MockSupervisor
- defer supervisor.AssertExpectations(t)
- var hooks plugintest.Hooks
- defer hooks.AssertExpectations(t)
-
- provider.On("API").Return(&api, nil)
- provider.On("Supervisor").Return(&supervisor, nil)
-
- supervisor.On("Start", &api).Return(nil)
- supervisor.On("Stop").Return(fmt.Errorf("test error"))
- supervisor.On("Hooks").Return(&hooks)
-
- hooks.On("OnDeactivate").Return(fmt.Errorf("test error"))
-
- assert.NoError(t, env.ActivatePlugin("foo", nil))
- assert.Equal(t, env.ActivePluginIds(), []string{"foo"})
- assert.Len(t, env.Shutdown(), 2)
-}
-
-func TestEnvironment_ConcurrentHookInvocations(t *testing.T) {
- dir := initTmpDir(t, map[string]string{
- "foo/plugin.json": `{"id": "foo", "backend": {}}`,
- })
- defer os.RemoveAll(dir)
-
- var provider MockProvider
- defer provider.AssertExpectations(t)
-
- var api struct{ plugin.API }
- var supervisor MockSupervisor
- defer supervisor.AssertExpectations(t)
- var hooks plugintest.Hooks
- defer hooks.AssertExpectations(t)
-
- env, err := New(
- SearchPath(dir),
- APIProvider(provider.API),
- SupervisorProvider(provider.Supervisor),
- )
- require.NoError(t, err)
- defer env.Shutdown()
-
- provider.On("API").Return(&api, nil)
- provider.On("Supervisor").Return(&supervisor, nil)
-
- supervisor.On("Start", &api).Return(nil)
- supervisor.On("Stop").Return(nil)
- supervisor.On("Hooks").Return(&hooks)
-
- ch := make(chan bool)
-
- hooks.On("OnDeactivate").Return(nil)
- hooks.On("ServeHTTP", mock.AnythingOfType("*httptest.ResponseRecorder"), mock.AnythingOfType("*http.Request")).Run(func(args mock.Arguments) {
- r := args.Get(1).(*http.Request)
- if r.URL.Path == "/1" {
- <-ch
- } else {
- ch <- true
- }
- })
-
- assert.NoError(t, env.ActivatePlugin("foo", nil))
-
- rec := httptest.NewRecorder()
-
- wg := sync.WaitGroup{}
- wg.Add(2)
-
- go func() {
- req, err := http.NewRequest("GET", "/1", nil)
- require.NoError(t, err)
- env.Hooks().ServeHTTP(rec, req.WithContext(context.WithValue(context.Background(), "plugin_id", "foo")))
- wg.Done()
- }()
-
- go func() {
- req, err := http.NewRequest("GET", "/2", nil)
- require.NoError(t, err)
- env.Hooks().ServeHTTP(rec, req.WithContext(context.WithValue(context.Background(), "plugin_id", "foo")))
- wg.Done()
- }()
-
- wg.Wait()
-}
-
-func TestEnvironment_HooksForPlugins(t *testing.T) {
- dir := initTmpDir(t, map[string]string{
- "foo/plugin.json": `{"id": "foo", "backend": {}}`,
- })
- defer os.RemoveAll(dir)
-
- var provider MockProvider
- defer provider.AssertExpectations(t)
-
- env, err := New(
- SearchPath(dir),
- APIProvider(provider.API),
- SupervisorProvider(provider.Supervisor),
- )
- require.NoError(t, err)
- defer env.Shutdown()
-
- var api struct{ plugin.API }
- var supervisor MockSupervisor
- defer supervisor.AssertExpectations(t)
- var hooks plugintest.Hooks
- defer hooks.AssertExpectations(t)
-
- provider.On("API").Return(&api, nil)
- provider.On("Supervisor").Return(&supervisor, nil)
-
- supervisor.On("Start", &api).Return(nil)
- supervisor.On("Stop").Return(nil)
- supervisor.On("Hooks").Return(&hooks)
-
- hooks.On("OnDeactivate").Return(nil)
- hooks.On("ExecuteCommand", mock.AnythingOfType("*model.CommandArgs")).Return(&model.CommandResponse{
- Text: "bar",
- }, nil)
-
- assert.NoError(t, env.ActivatePlugin("foo", nil))
- assert.Equal(t, env.ActivePluginIds(), []string{"foo"})
-
- resp, appErr, err := env.HooksForPlugin("foo").ExecuteCommand(&model.CommandArgs{
- Command: "/foo",
- })
- assert.Equal(t, "bar", resp.Text)
- assert.Nil(t, appErr)
- assert.NoError(t, err)
-
- assert.Empty(t, env.Shutdown())
-}
diff --git a/plugin/pluginenv/options.go b/plugin/pluginenv/options.go
deleted file mode 100644
index 43cbdac68..000000000
--- a/plugin/pluginenv/options.go
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package pluginenv
-
-import (
- "github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/plugin"
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
- "github.com/mattermost/mattermost-server/plugin/rpcplugin/sandbox"
-)
-
-// APIProvider specifies a function that provides an API implementation to each plugin.
-func APIProvider(provider APIProviderFunc) Option {
- return func(env *Environment) {
- env.apiProvider = provider
- }
-}
-
-// SupervisorProvider specifies a function that provides a Supervisor implementation to each plugin.
-// If unspecified, DefaultSupervisorProvider is used.
-func SupervisorProvider(provider SupervisorProviderFunc) Option {
- return func(env *Environment) {
- env.supervisorProvider = provider
- }
-}
-
-// SearchPath specifies a directory that contains the plugins to launch.
-func SearchPath(path string) Option {
- return func(env *Environment) {
- env.searchPath = path
- }
-}
-
-// WebappPath specifies the static directory serving the webapp.
-func WebappPath(path string) Option {
- return func(env *Environment) {
- env.webappPath = path
- }
-}
-
-// DefaultSupervisorProvider chooses a supervisor based on the system and the plugin's manifest
-// contents. E.g. if the manifest specifies a backend executable, it will be given an
-// rpcplugin.Supervisor.
-func DefaultSupervisorProvider(bundle *model.BundleInfo) (plugin.Supervisor, error) {
- if err := sandbox.CheckSupport(); err == nil {
- return sandbox.SupervisorProvider(bundle)
- }
- return rpcplugin.SupervisorProvider(bundle)
-}
diff --git a/plugin/pluginenv/options_test.go b/plugin/pluginenv/options_test.go
deleted file mode 100644
index 01fa206d0..000000000
--- a/plugin/pluginenv/options_test.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package pluginenv
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
-)
-
-func TestDefaultSupervisorProvider(t *testing.T) {
- _, err := DefaultSupervisorProvider(&model.BundleInfo{})
- assert.Error(t, err)
-
- _, err = DefaultSupervisorProvider(&model.BundleInfo{
- Manifest: &model.Manifest{},
- })
- assert.Error(t, err)
-
- supervisor, err := DefaultSupervisorProvider(&model.BundleInfo{
- Manifest: &model.Manifest{
- Backend: &model.ManifestBackend{
- Executable: "foo",
- },
- },
- })
- require.NoError(t, err)
- _, ok := supervisor.(*rpcplugin.Supervisor)
- assert.True(t, ok)
-}
diff --git a/plugin/pluginenv/search_path.go b/plugin/pluginenv/search_path.go
deleted file mode 100644
index 698424332..000000000
--- a/plugin/pluginenv/search_path.go
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package pluginenv
-
-import (
- "io/ioutil"
- "path/filepath"
-
- "github.com/mattermost/mattermost-server/model"
-)
-
-// Performs a full scan of the given path.
-//
-// This function will return info for all subdirectories that appear to be plugins (i.e. all
-// subdirectories containing plugin manifest files, regardless of whether they could actually be
-// parsed).
-//
-// Plugins are found non-recursively and paths beginning with a dot are always ignored.
-func ScanSearchPath(path string) ([]*model.BundleInfo, error) {
- files, err := ioutil.ReadDir(path)
- if err != nil {
- return nil, err
- }
- var ret []*model.BundleInfo
- for _, file := range files {
- if !file.IsDir() || file.Name()[0] == '.' {
- continue
- }
- if info := model.BundleInfoForPath(filepath.Join(path, file.Name())); info.ManifestPath != "" {
- ret = append(ret, info)
- }
- }
- return ret, nil
-}
diff --git a/plugin/pluginenv/search_path_test.go b/plugin/pluginenv/search_path_test.go
deleted file mode 100644
index dd9ff9a6b..000000000
--- a/plugin/pluginenv/search_path_test.go
+++ /dev/null
@@ -1,62 +0,0 @@
-package pluginenv
-
-import (
- "encoding/json"
- "os"
- "path/filepath"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/mattermost/mattermost-server/model"
-)
-
-func TestScanSearchPath(t *testing.T) {
- dir := initTmpDir(t, map[string]string{
- ".foo/plugin.json": `{"id": "foo"}`,
- "foo/bar": "asdf",
- "foo/plugin.json": `{"id": "foo"}`,
- "bar/zxc": "qwer",
- "baz/plugin.yaml": "id: baz",
- "bad/plugin.json": "asd",
- "qwe": "asd",
- })
- defer os.RemoveAll(dir)
-
- plugins, err := ScanSearchPath(dir)
- require.NoError(t, err)
- assert.Len(t, plugins, 3)
- assert.Contains(t, plugins, &model.BundleInfo{
- Path: filepath.Join(dir, "foo"),
- ManifestPath: filepath.Join(dir, "foo", "plugin.json"),
- Manifest: &model.Manifest{
- Id: "foo",
- },
- })
- assert.Contains(t, plugins, &model.BundleInfo{
- Path: filepath.Join(dir, "baz"),
- ManifestPath: filepath.Join(dir, "baz", "plugin.yaml"),
- Manifest: &model.Manifest{
- Id: "baz",
- },
- })
- foundError := false
- for _, x := range plugins {
- if x.ManifestError != nil {
- assert.Equal(t, x.Path, filepath.Join(dir, "bad"))
- assert.Equal(t, x.ManifestPath, filepath.Join(dir, "bad", "plugin.json"))
- syntexError, ok := x.ManifestError.(*json.SyntaxError)
- assert.True(t, ok)
- assert.EqualValues(t, 1, syntexError.Offset)
- foundError = true
- }
- }
- assert.True(t, foundError)
-}
-
-func TestScanSearchPath_Error(t *testing.T) {
- plugins, err := ScanSearchPath("not a valid path!")
- assert.Nil(t, plugins)
- assert.Error(t, err)
-}