github.com/xzl8028/xenia-server@v0.0.0-20190809101854-18450a97da63/app/plugin.go (about) 1 // Copyright (c) 2017-present Xenia, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package app 5 6 import ( 7 "net/http" 8 "os" 9 "path/filepath" 10 "strings" 11 12 "github.com/xzl8028/xenia-server/mlog" 13 "github.com/xzl8028/xenia-server/model" 14 "github.com/xzl8028/xenia-server/plugin" 15 "github.com/xzl8028/xenia-server/utils/fileutils" 16 ) 17 18 // GetPluginsEnvironment returns the plugin environment for use if plugins are enabled and 19 // initialized. 20 // 21 // To get the plugins environment when the plugins are disabled, manually acquire the plugins 22 // lock instead. 23 func (a *App) GetPluginsEnvironment() *plugin.Environment { 24 if !*a.Config().PluginSettings.Enable { 25 return nil 26 } 27 28 a.Srv.PluginsLock.RLock() 29 defer a.Srv.PluginsLock.RUnlock() 30 31 return a.Srv.PluginsEnvironment 32 } 33 34 func (a *App) SetPluginsEnvironment(pluginsEnvironment *plugin.Environment) { 35 a.Srv.PluginsLock.Lock() 36 defer a.Srv.PluginsLock.Unlock() 37 38 a.Srv.PluginsEnvironment = pluginsEnvironment 39 } 40 41 func (a *App) SyncPluginsActiveState() { 42 a.Srv.PluginsLock.RLock() 43 pluginsEnvironment := a.Srv.PluginsEnvironment 44 a.Srv.PluginsLock.RUnlock() 45 46 if pluginsEnvironment == nil { 47 return 48 } 49 50 config := a.Config().PluginSettings 51 52 if *config.Enable { 53 availablePlugins, err := pluginsEnvironment.Available() 54 if err != nil { 55 a.Log.Error("Unable to get available plugins", mlog.Err(err)) 56 return 57 } 58 59 // Deactivate any plugins that have been disabled. 60 for _, plugin := range availablePlugins { 61 // Determine if plugin is enabled 62 pluginId := plugin.Manifest.Id 63 pluginEnabled := false 64 if state, ok := config.PluginStates[pluginId]; ok { 65 pluginEnabled = state.Enable 66 } 67 68 // If it's not enabled we need to deactivate it 69 if !pluginEnabled { 70 deactivated := pluginsEnvironment.Deactivate(pluginId) 71 if deactivated && plugin.Manifest.HasClient() { 72 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PLUGIN_DISABLED, "", "", "", nil) 73 message.Add("manifest", plugin.Manifest.ClientManifest()) 74 a.Publish(message) 75 } 76 } 77 } 78 79 // Activate any plugins that have been enabled 80 for _, plugin := range availablePlugins { 81 if plugin.Manifest == nil { 82 plugin.WrapLogger(a.Log).Error("Plugin manifest could not be loaded", mlog.Err(plugin.ManifestError)) 83 continue 84 } 85 86 // Determine if plugin is enabled 87 pluginId := plugin.Manifest.Id 88 pluginEnabled := false 89 if state, ok := config.PluginStates[pluginId]; ok { 90 pluginEnabled = state.Enable 91 } 92 93 // Activate plugin if enabled 94 if pluginEnabled { 95 updatedManifest, activated, err := pluginsEnvironment.Activate(pluginId) 96 if err != nil { 97 plugin.WrapLogger(a.Log).Error("Unable to activate plugin", mlog.Err(err)) 98 continue 99 } 100 101 if activated && updatedManifest.HasClient() { 102 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PLUGIN_ENABLED, "", "", "", nil) 103 message.Add("manifest", updatedManifest.ClientManifest()) 104 a.Publish(message) 105 } 106 } 107 } 108 } else { // If plugins are disabled, shutdown plugins. 109 pluginsEnvironment.Shutdown() 110 } 111 112 if err := a.notifyPluginStatusesChanged(); err != nil { 113 mlog.Error("failed to notify plugin status changed", mlog.Err(err)) 114 } 115 } 116 117 func (a *App) NewPluginAPI(manifest *model.Manifest) plugin.API { 118 return NewPluginAPI(a, manifest) 119 } 120 121 func (a *App) InitPlugins(pluginDir, webappPluginDir string) { 122 a.Srv.PluginsLock.RLock() 123 pluginsEnvironment := a.Srv.PluginsEnvironment 124 a.Srv.PluginsLock.RUnlock() 125 if pluginsEnvironment != nil || !*a.Config().PluginSettings.Enable { 126 a.SyncPluginsActiveState() 127 return 128 } 129 130 a.Log.Info("Starting up plugins") 131 132 if err := os.Mkdir(pluginDir, 0744); err != nil && !os.IsExist(err) { 133 mlog.Error("Failed to start up plugins", mlog.Err(err)) 134 return 135 } 136 137 if err := os.Mkdir(webappPluginDir, 0744); err != nil && !os.IsExist(err) { 138 mlog.Error("Failed to start up plugins", mlog.Err(err)) 139 return 140 } 141 142 env, err := plugin.NewEnvironment(a.NewPluginAPI, pluginDir, webappPluginDir, a.Log) 143 if err != nil { 144 mlog.Error("Failed to start up plugins", mlog.Err(err)) 145 return 146 } 147 a.SetPluginsEnvironment(env) 148 149 prepackagedPluginsDir, found := fileutils.FindDir("prepackaged_plugins") 150 if found { 151 if err := filepath.Walk(prepackagedPluginsDir, func(walkPath string, info os.FileInfo, err error) error { 152 if !strings.HasSuffix(walkPath, ".tar.gz") { 153 return nil 154 } 155 156 if fileReader, err := os.Open(walkPath); err != nil { 157 mlog.Error("Failed to open prepackaged plugin", mlog.Err(err), mlog.String("path", walkPath)) 158 } else if _, err := a.InstallPlugin(fileReader, true); err != nil { 159 mlog.Error("Failed to unpack prepackaged plugin", mlog.Err(err), mlog.String("path", walkPath)) 160 } 161 162 return nil 163 }); err != nil { 164 mlog.Error("Failed to complete unpacking prepackaged plugins", mlog.Err(err)) 165 } 166 } 167 168 // Sync plugin active state when config changes. Also notify plugins. 169 a.Srv.PluginsLock.Lock() 170 a.RemoveConfigListener(a.Srv.PluginConfigListenerId) 171 a.Srv.PluginConfigListenerId = a.AddConfigListener(func(*model.Config, *model.Config) { 172 a.SyncPluginsActiveState() 173 if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil { 174 pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool { 175 hooks.OnConfigurationChange() 176 return true 177 }, plugin.OnConfigurationChangeId) 178 } 179 }) 180 a.Srv.PluginsLock.Unlock() 181 182 a.SyncPluginsActiveState() 183 } 184 185 func (a *App) ShutDownPlugins() { 186 a.Srv.PluginsLock.Lock() 187 pluginsEnvironment := a.Srv.PluginsEnvironment 188 defer a.Srv.PluginsLock.Unlock() 189 if pluginsEnvironment == nil { 190 return 191 } 192 193 mlog.Info("Shutting down plugins") 194 195 pluginsEnvironment.Shutdown() 196 197 a.RemoveConfigListener(a.Srv.PluginConfigListenerId) 198 a.Srv.PluginConfigListenerId = "" 199 a.Srv.PluginsEnvironment = nil 200 } 201 202 func (a *App) GetActivePluginManifests() ([]*model.Manifest, *model.AppError) { 203 pluginsEnvironment := a.GetPluginsEnvironment() 204 if pluginsEnvironment == nil { 205 return nil, model.NewAppError("GetActivePluginManifests", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) 206 } 207 208 plugins := pluginsEnvironment.Active() 209 210 manifests := make([]*model.Manifest, len(plugins)) 211 for i, plugin := range plugins { 212 manifests[i] = plugin.Manifest 213 } 214 215 return manifests, nil 216 } 217 218 // EnablePlugin will set the config for an installed plugin to enabled, triggering asynchronous 219 // activation if inactive anywhere in the cluster. 220 func (a *App) EnablePlugin(id string) *model.AppError { 221 pluginsEnvironment := a.GetPluginsEnvironment() 222 if pluginsEnvironment == nil { 223 return model.NewAppError("EnablePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) 224 } 225 226 plugins, err := pluginsEnvironment.Available() 227 if err != nil { 228 return model.NewAppError("EnablePlugin", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError) 229 } 230 231 id = strings.ToLower(id) 232 233 var manifest *model.Manifest 234 for _, p := range plugins { 235 if p.Manifest.Id == id { 236 manifest = p.Manifest 237 break 238 } 239 } 240 241 if manifest == nil { 242 return model.NewAppError("EnablePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusBadRequest) 243 } 244 245 a.UpdateConfig(func(cfg *model.Config) { 246 cfg.PluginSettings.PluginStates[id] = &model.PluginState{Enable: true} 247 }) 248 249 // This call will cause SyncPluginsActiveState to be called and the plugin to be activated 250 if err := a.SaveConfig(a.Config(), true); err != nil { 251 if err.Id == "ent.cluster.save_config.error" { 252 return model.NewAppError("EnablePlugin", "app.plugin.cluster.save_config.app_error", nil, "", http.StatusInternalServerError) 253 } 254 return model.NewAppError("EnablePlugin", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError) 255 } 256 257 return nil 258 } 259 260 // DisablePlugin will set the config for an installed plugin to disabled, triggering deactivation if active. 261 func (a *App) DisablePlugin(id string) *model.AppError { 262 pluginsEnvironment := a.GetPluginsEnvironment() 263 if pluginsEnvironment == nil { 264 return model.NewAppError("DisablePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) 265 } 266 267 plugins, err := pluginsEnvironment.Available() 268 if err != nil { 269 return model.NewAppError("DisablePlugin", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError) 270 } 271 272 id = strings.ToLower(id) 273 274 var manifest *model.Manifest 275 for _, p := range plugins { 276 if p.Manifest.Id == id { 277 manifest = p.Manifest 278 break 279 } 280 } 281 282 if manifest == nil { 283 return model.NewAppError("DisablePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusBadRequest) 284 } 285 286 a.UpdateConfig(func(cfg *model.Config) { 287 cfg.PluginSettings.PluginStates[id] = &model.PluginState{Enable: false} 288 }) 289 a.UnregisterPluginCommands(id) 290 291 if err := a.SaveConfig(a.Config(), true); err != nil { 292 return model.NewAppError("DisablePlugin", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError) 293 } 294 295 return nil 296 } 297 298 func (a *App) GetPlugins() (*model.PluginsResponse, *model.AppError) { 299 pluginsEnvironment := a.GetPluginsEnvironment() 300 if pluginsEnvironment == nil { 301 return nil, model.NewAppError("GetPlugins", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) 302 } 303 304 availablePlugins, err := pluginsEnvironment.Available() 305 if err != nil { 306 return nil, model.NewAppError("GetPlugins", "app.plugin.get_plugins.app_error", nil, err.Error(), http.StatusInternalServerError) 307 } 308 resp := &model.PluginsResponse{Active: []*model.PluginInfo{}, Inactive: []*model.PluginInfo{}} 309 for _, plugin := range availablePlugins { 310 if plugin.Manifest == nil { 311 continue 312 } 313 314 info := &model.PluginInfo{ 315 Manifest: *plugin.Manifest, 316 } 317 318 if pluginsEnvironment.IsActive(plugin.Manifest.Id) { 319 resp.Active = append(resp.Active, info) 320 } else { 321 resp.Inactive = append(resp.Inactive, info) 322 } 323 } 324 325 return resp, nil 326 }