github.com/psyb0t/mattermost-server@v4.6.1-0.20180125161845-5503a1351abf+incompatible/plugin/pluginenv/environment.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  // Package pluginenv provides high level functionality for discovering and launching plugins.
     5  package pluginenv
     6  
     7  import (
     8  	"fmt"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"sync"
    12  
    13  	"github.com/pkg/errors"
    14  
    15  	"github.com/mattermost/mattermost-server/model"
    16  	"github.com/mattermost/mattermost-server/plugin"
    17  )
    18  
    19  type APIProviderFunc func(*model.Manifest) (plugin.API, error)
    20  type SupervisorProviderFunc func(*model.BundleInfo) (plugin.Supervisor, error)
    21  
    22  type ActivePlugin struct {
    23  	BundleInfo *model.BundleInfo
    24  	Supervisor plugin.Supervisor
    25  }
    26  
    27  // Environment represents an environment that plugins are discovered and launched in.
    28  type Environment struct {
    29  	searchPath         string
    30  	webappPath         string
    31  	apiProvider        APIProviderFunc
    32  	supervisorProvider SupervisorProviderFunc
    33  	activePlugins      map[string]ActivePlugin
    34  	mutex              sync.RWMutex
    35  }
    36  
    37  type Option func(*Environment)
    38  
    39  // Creates a new environment. At a minimum, the APIProvider and SearchPath options are required.
    40  func New(options ...Option) (*Environment, error) {
    41  	env := &Environment{
    42  		activePlugins: make(map[string]ActivePlugin),
    43  	}
    44  	for _, opt := range options {
    45  		opt(env)
    46  	}
    47  	if env.supervisorProvider == nil {
    48  		env.supervisorProvider = DefaultSupervisorProvider
    49  	}
    50  	if env.searchPath == "" {
    51  		return nil, fmt.Errorf("a search path must be provided")
    52  	}
    53  	return env, nil
    54  }
    55  
    56  // Returns the configured webapp path.
    57  func (env *Environment) WebappPath() string {
    58  	return env.webappPath
    59  }
    60  
    61  // Returns the configured search path.
    62  func (env *Environment) SearchPath() string {
    63  	return env.searchPath
    64  }
    65  
    66  // Returns a list of all plugins found within the environment.
    67  func (env *Environment) Plugins() ([]*model.BundleInfo, error) {
    68  	return ScanSearchPath(env.searchPath)
    69  }
    70  
    71  // Returns a list of all currently active plugins within the environment.
    72  func (env *Environment) ActivePlugins() []*model.BundleInfo {
    73  	env.mutex.RLock()
    74  	defer env.mutex.RUnlock()
    75  
    76  	activePlugins := []*model.BundleInfo{}
    77  	for _, p := range env.activePlugins {
    78  		activePlugins = append(activePlugins, p.BundleInfo)
    79  	}
    80  
    81  	return activePlugins
    82  }
    83  
    84  // Returns the ids of the currently active plugins.
    85  func (env *Environment) ActivePluginIds() (ids []string) {
    86  	env.mutex.RLock()
    87  	defer env.mutex.RUnlock()
    88  
    89  	for id := range env.activePlugins {
    90  		ids = append(ids, id)
    91  	}
    92  	return
    93  }
    94  
    95  // Returns true if the plugin is active, false otherwise.
    96  func (env *Environment) IsPluginActive(pluginId string) bool {
    97  	env.mutex.RLock()
    98  	defer env.mutex.RUnlock()
    99  
   100  	for id := range env.activePlugins {
   101  		if id == pluginId {
   102  			return true
   103  		}
   104  	}
   105  
   106  	return false
   107  }
   108  
   109  // Activates the plugin with the given id.
   110  func (env *Environment) ActivatePlugin(id string) error {
   111  	env.mutex.Lock()
   112  	defer env.mutex.Unlock()
   113  
   114  	if _, ok := env.activePlugins[id]; ok {
   115  		return fmt.Errorf("plugin already active: %v", id)
   116  	}
   117  	plugins, err := ScanSearchPath(env.searchPath)
   118  	if err != nil {
   119  		return err
   120  	}
   121  	var bundle *model.BundleInfo
   122  	for _, p := range plugins {
   123  		if p.Manifest != nil && p.Manifest.Id == id {
   124  			if bundle != nil {
   125  				return fmt.Errorf("multiple plugins found: %v", id)
   126  			}
   127  			bundle = p
   128  		}
   129  	}
   130  	if bundle == nil {
   131  		return fmt.Errorf("plugin not found: %v", id)
   132  	}
   133  
   134  	activePlugin := ActivePlugin{BundleInfo: bundle}
   135  
   136  	var supervisor plugin.Supervisor
   137  
   138  	if bundle.Manifest.Backend != nil {
   139  		if env.apiProvider == nil {
   140  			return fmt.Errorf("env missing api provider, cannot activate plugin: %v", id)
   141  		}
   142  
   143  		supervisor, err = env.supervisorProvider(bundle)
   144  		if err != nil {
   145  			return errors.Wrapf(err, "unable to create supervisor for plugin: %v", id)
   146  		}
   147  		api, err := env.apiProvider(bundle.Manifest)
   148  		if err != nil {
   149  			return errors.Wrapf(err, "unable to get api for plugin: %v", id)
   150  		}
   151  		if err := supervisor.Start(api); err != nil {
   152  			return errors.Wrapf(err, "unable to start plugin: %v", id)
   153  		}
   154  
   155  		activePlugin.Supervisor = supervisor
   156  	}
   157  
   158  	if bundle.Manifest.Webapp != nil {
   159  		if env.webappPath == "" {
   160  			if supervisor != nil {
   161  				supervisor.Stop()
   162  			}
   163  			return fmt.Errorf("env missing webapp path, cannot activate plugin: %v", id)
   164  		}
   165  
   166  		webappBundle, err := ioutil.ReadFile(fmt.Sprintf("%s/%s/webapp/%s_bundle.js", env.searchPath, id, id))
   167  		if err != nil {
   168  			if supervisor != nil {
   169  				supervisor.Stop()
   170  			}
   171  			return errors.Wrapf(err, "unable to read webapp bundle: %v", id)
   172  		}
   173  
   174  		err = ioutil.WriteFile(fmt.Sprintf("%s/%s_bundle.js", env.webappPath, id), webappBundle, 0644)
   175  		if err != nil {
   176  			if supervisor != nil {
   177  				supervisor.Stop()
   178  			}
   179  			return errors.Wrapf(err, "unable to write webapp bundle: %v", id)
   180  		}
   181  	}
   182  
   183  	env.activePlugins[id] = activePlugin
   184  	return nil
   185  }
   186  
   187  // Deactivates the plugin with the given id.
   188  func (env *Environment) DeactivatePlugin(id string) error {
   189  	env.mutex.Lock()
   190  	defer env.mutex.Unlock()
   191  
   192  	if activePlugin, ok := env.activePlugins[id]; !ok {
   193  		return fmt.Errorf("plugin not active: %v", id)
   194  	} else {
   195  		delete(env.activePlugins, id)
   196  		var err error
   197  		if activePlugin.Supervisor != nil {
   198  			err = activePlugin.Supervisor.Hooks().OnDeactivate()
   199  			if serr := activePlugin.Supervisor.Stop(); err == nil {
   200  				err = serr
   201  			}
   202  		}
   203  		return err
   204  	}
   205  }
   206  
   207  // Deactivates all plugins and gracefully shuts down the environment.
   208  func (env *Environment) Shutdown() (errs []error) {
   209  	env.mutex.Lock()
   210  	defer env.mutex.Unlock()
   211  
   212  	for _, activePlugin := range env.activePlugins {
   213  		if activePlugin.Supervisor != nil {
   214  			if err := activePlugin.Supervisor.Hooks().OnDeactivate(); err != nil {
   215  				errs = append(errs, errors.Wrapf(err, "OnDeactivate() error for %v", activePlugin.BundleInfo.Manifest.Id))
   216  			}
   217  			if err := activePlugin.Supervisor.Stop(); err != nil {
   218  				errs = append(errs, errors.Wrapf(err, "error stopping supervisor for %v", activePlugin.BundleInfo.Manifest.Id))
   219  			}
   220  		}
   221  	}
   222  	env.activePlugins = make(map[string]ActivePlugin)
   223  	return
   224  }
   225  
   226  type MultiPluginHooks struct {
   227  	env *Environment
   228  }
   229  
   230  type SinglePluginHooks struct {
   231  	env      *Environment
   232  	pluginId string
   233  }
   234  
   235  func (env *Environment) Hooks() *MultiPluginHooks {
   236  	return &MultiPluginHooks{
   237  		env: env,
   238  	}
   239  }
   240  
   241  func (env *Environment) HooksForPlugin(id string) *SinglePluginHooks {
   242  	return &SinglePluginHooks{
   243  		env:      env,
   244  		pluginId: id,
   245  	}
   246  }
   247  
   248  func (h *MultiPluginHooks) invoke(f func(plugin.Hooks) error) (errs []error) {
   249  	h.env.mutex.RLock()
   250  	defer h.env.mutex.RUnlock()
   251  
   252  	for _, activePlugin := range h.env.activePlugins {
   253  		if activePlugin.Supervisor == nil {
   254  			continue
   255  		}
   256  		if err := f(activePlugin.Supervisor.Hooks()); err != nil {
   257  			errs = append(errs, errors.Wrapf(err, "hook error for %v", activePlugin.BundleInfo.Manifest.Id))
   258  		}
   259  	}
   260  	return
   261  }
   262  
   263  // OnConfigurationChange invokes the OnConfigurationChange hook for all plugins. Any errors
   264  // encountered will be returned.
   265  func (h *MultiPluginHooks) OnConfigurationChange() []error {
   266  	return h.invoke(func(hooks plugin.Hooks) error {
   267  		if err := hooks.OnConfigurationChange(); err != nil {
   268  			return errors.Wrapf(err, "error calling OnConfigurationChange hook")
   269  		}
   270  		return nil
   271  	})
   272  }
   273  
   274  // ServeHTTP invokes the ServeHTTP hook for the plugin identified by the request or responds with a
   275  // 404 not found.
   276  //
   277  // It expects the request's context to have a plugin_id set.
   278  func (h *MultiPluginHooks) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   279  	if id := r.Context().Value("plugin_id"); id != nil {
   280  		if idstr, ok := id.(string); ok {
   281  			h.env.mutex.RLock()
   282  			defer h.env.mutex.RUnlock()
   283  			if plugin, ok := h.env.activePlugins[idstr]; ok && plugin.Supervisor != nil {
   284  				plugin.Supervisor.Hooks().ServeHTTP(w, r)
   285  				return
   286  			}
   287  		}
   288  	}
   289  	http.NotFound(w, r)
   290  }
   291  
   292  func (h *SinglePluginHooks) invoke(f func(plugin.Hooks) error) error {
   293  	h.env.mutex.RLock()
   294  	defer h.env.mutex.RUnlock()
   295  
   296  	if activePlugin, ok := h.env.activePlugins[h.pluginId]; ok && activePlugin.Supervisor != nil {
   297  		if err := f(activePlugin.Supervisor.Hooks()); err != nil {
   298  			return errors.Wrapf(err, "hook error for plugin: %v", activePlugin.BundleInfo.Manifest.Id)
   299  		}
   300  		return nil
   301  	}
   302  	return fmt.Errorf("unable to invoke hook for plugin: %v", h.pluginId)
   303  }
   304  
   305  // ExecuteCommand invokes the ExecuteCommand hook for the plugin.
   306  func (h *SinglePluginHooks) ExecuteCommand(args *model.CommandArgs) (resp *model.CommandResponse, appErr *model.AppError, err error) {
   307  	err = h.invoke(func(hooks plugin.Hooks) error {
   308  		resp, appErr = hooks.ExecuteCommand(args)
   309  		return nil
   310  	})
   311  	return
   312  }