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 }