From 1e5c432e1029601a664454388ae366ef69618d62 Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Mon, 25 Jun 2018 12:33:13 -0700 Subject: MM-10702 Moving plugins to use hashicorp go-plugin. (#8978) * Moving plugins to use hashicorp go-plugin. * Tweaks from feedback. --- app/plugin.go | 813 ++++++++-------------------------------------------------- 1 file changed, 102 insertions(+), 711 deletions(-) (limited to 'app/plugin.go') diff --git a/app/plugin.go b/app/plugin.go index dda09596a..af4bae649 100644 --- a/app/plugin.go +++ b/app/plugin.go @@ -4,356 +4,135 @@ package app import ( - "bytes" - "context" - "crypto/sha256" - "encoding/base64" - "fmt" - "io" - "io/ioutil" "net/http" "os" - "path/filepath" - "strings" - "github.com/gorilla/mux" "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" - - builtinplugin "github.com/mattermost/mattermost-server/app/plugin" - "github.com/mattermost/mattermost-server/app/plugin/jira" - "github.com/mattermost/mattermost-server/app/plugin/ldapextras" - "github.com/mattermost/mattermost-server/app/plugin/zoom" - "github.com/mattermost/mattermost-server/plugin" - "github.com/mattermost/mattermost-server/plugin/pluginenv" - "github.com/mattermost/mattermost-server/plugin/rpcplugin" - "github.com/mattermost/mattermost-server/plugin/rpcplugin/sandbox" ) -var prepackagedPlugins map[string]func(string) ([]byte, error) = map[string]func(string) ([]byte, error){ - "jira": jira.Asset, - "zoom": zoom.Asset, -} - -func (a *App) notifyPluginStatusesChanged() error { - pluginStatuses, err := a.GetClusterPluginStatuses() - if err != nil { - return err - } - - // Notify any system admins. - message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PLUGIN_STATUSES_CHANGED, "", "", "", nil) - message.Add("plugin_statuses", pluginStatuses) - message.Broadcast.ContainsSensitiveData = true - a.Publish(message) - - return nil -} - -func (a *App) setPluginStatusState(id string, state int) error { - if _, ok := a.pluginStatuses[id]; !ok { - return nil - } - - a.pluginStatuses[id].State = state - - return a.notifyPluginStatusesChanged() -} - -func (a *App) initBuiltInPlugins() { - plugins := map[string]builtinplugin.Plugin{ - "ldapextras": &ldapextras.Plugin{}, - } - for id, p := range plugins { - mlog.Debug("Initializing built-in plugin", mlog.String("plugin_id", id)) - api := &BuiltInPluginAPI{ - id: id, - router: a.Srv.Router.PathPrefix("/plugins/" + id).Subrouter(), - app: a, - } - p.Initialize(api) - } - a.AddConfigListener(func(before, after *model.Config) { - for _, p := range plugins { - p.OnConfigurationChange() - } - }) - for _, p := range plugins { - p.OnConfigurationChange() - } -} - -func (a *App) setPluginsActive(activate bool) { - if a.PluginEnv == nil { - mlog.Error(fmt.Sprintf("Cannot setPluginsActive(%t): plugin env not initialized", activate)) +func (a *App) SyncPluginsActiveState() { + if a.Plugins == nil { return } - plugins, err := a.PluginEnv.Plugins() - if err != nil { - mlog.Error(fmt.Sprintf("Cannot setPluginsActive(%t)", activate), mlog.Err(err)) - return - } - - for _, plugin := range plugins { - if plugin.Manifest == nil { - continue - } - - enabled := false - if state, ok := a.Config().PluginSettings.PluginStates[plugin.Manifest.Id]; ok { - enabled = state.Enable - } - - a.pluginStatuses[plugin.Manifest.Id] = &model.PluginStatus{ - ClusterId: a.GetClusterId(), - PluginId: plugin.Manifest.Id, - PluginPath: filepath.Dir(plugin.ManifestPath), - IsSandboxed: a.IsPluginSandboxSupported, - Name: plugin.Manifest.Name, - Description: plugin.Manifest.Description, - Version: plugin.Manifest.Version, - } - - if activate && enabled { - a.setPluginActive(plugin, activate) - } else if !activate { - a.setPluginActive(plugin, activate) - } - } - - if err := a.notifyPluginStatusesChanged(); err != nil { - mlog.Error("failed to notify plugin status changed", mlog.Err(err)) - } -} + config := a.Config().PluginSettings -func (a *App) setPluginActiveById(id string, activate bool) { - plugins, err := a.PluginEnv.Plugins() - if err != nil { - mlog.Error(fmt.Sprintf("Cannot setPluginActiveById(%t)", activate), mlog.String("plugin_id", id), mlog.Err(err)) - return - } - - for _, plugin := range plugins { - if plugin.Manifest != nil && plugin.Manifest.Id == id { - a.setPluginActive(plugin, activate) + if *config.Enable { + availablePlugins, err := a.Plugins.Available() + if err != nil { + a.Log.Error("Unable to get available plugins", mlog.Err(err)) + return } - } -} - -func (a *App) setPluginActive(plugin *model.BundleInfo, activate bool) { - if plugin.Manifest == nil { - return - } - id := plugin.Manifest.Id - - active := a.PluginEnv.IsPluginActive(id) - - if activate { - if !active { - if err := a.activatePlugin(plugin.Manifest); err != nil { - mlog.Error("Plugin failed to activate", mlog.String("plugin_id", plugin.Manifest.Id), mlog.String("err", err.DetailedError)) + // Deactivate any plugins that have been disabled. + for _, plugin := range a.Plugins.Active() { + // Determine if plugin is enabled + pluginId := plugin.Manifest.Id + pluginEnabled := false + if state, ok := config.PluginStates[pluginId]; ok { + pluginEnabled = state.Enable } - } - } else if !activate { - if active { - if err := a.deactivatePlugin(plugin.Manifest); err != nil { - mlog.Error("Plugin failed to deactivate", mlog.String("plugin_id", plugin.Manifest.Id), mlog.String("err", err.DetailedError)) - } - } else { - if err := a.setPluginStatusState(plugin.Manifest.Id, model.PluginStateNotRunning); err != nil { - mlog.Error("Plugin status state failed to update", mlog.String("plugin_id", plugin.Manifest.Id), mlog.String("err", err.Error())) + // If it's not enabled we need to deactivate it + if !pluginEnabled { + a.Plugins.Deactivate(pluginId) } } - } -} - -func (a *App) activatePlugin(manifest *model.Manifest) *model.AppError { - mlog.Debug("Activating plugin", mlog.String("plugin_id", manifest.Id)) - if err := a.setPluginStatusState(manifest.Id, model.PluginStateStarting); err != nil { - return model.NewAppError("activatePlugin", "app.plugin.set_plugin_status_state.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - onError := func(err error) { - mlog.Debug("Plugin failed to stay running", mlog.String("plugin_id", manifest.Id), mlog.Err(err)) + // Activate any plugins that have been enabled + for _, plugin := range availablePlugins { + if plugin.Manifest == nil { + plugin.WrapLogger(a.Log).Error("Plugin manifest could not be loaded", mlog.Err(plugin.ManifestError)) + continue + } - if err := a.setPluginStatusState(manifest.Id, model.PluginStateFailedToStayRunning); err != nil { - mlog.Error("Failed to record plugin status", mlog.String("plugin_id", manifest.Id), mlog.Err(err)) - } - } + // Determine if plugin is enabled + pluginId := plugin.Manifest.Id + pluginEnabled := false + if state, ok := config.PluginStates[pluginId]; ok { + pluginEnabled = state.Enable + } - if err := a.PluginEnv.ActivatePlugin(manifest.Id, onError); err != nil { - if err := a.setPluginStatusState(manifest.Id, model.PluginStateFailedToStart); err != nil { - return model.NewAppError("activatePlugin", "app.plugin.activate.app_error", nil, err.Error(), http.StatusInternalServerError) + // Activate plugin if enabled + if pluginEnabled { + if err := a.Plugins.Activate(pluginId); err != nil { + plugin.WrapLogger(a.Log).Error("Unable to activate plugin", mlog.Err(err)) + } + } } - - return model.NewAppError("activatePlugin", "app.plugin.activate.app_error", nil, err.Error(), http.StatusBadRequest) - } - - if err := a.setPluginStatusState(manifest.Id, model.PluginStateRunning); err != nil { - return model.NewAppError("activatePlugin", "app.plugin.activate.app_error", nil, err.Error(), http.StatusBadRequest) - } - - if manifest.HasClient() { - message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PLUGIN_ACTIVATED, "", "", "", nil) - message.Add("manifest", manifest.ClientManifest()) - a.Publish(message) - } - - mlog.Info("Activated plugin", mlog.String("plugin_id", manifest.Id)) - return nil -} - -func (a *App) deactivatePlugin(manifest *model.Manifest) *model.AppError { - mlog.Debug("Deactivating plugin", mlog.String("plugin_id", manifest.Id)) - - if err := a.setPluginStatusState(manifest.Id, model.PluginStateStopping); err != nil { - return model.NewAppError("EnablePlugin", "app.plugin.deactivate.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - if err := a.PluginEnv.DeactivatePlugin(manifest.Id); err != nil { - return model.NewAppError("deactivatePlugin", "app.plugin.deactivate.app_error", nil, err.Error(), http.StatusBadRequest) - } - - a.UnregisterPluginCommands(manifest.Id) - - if manifest.HasClient() { - message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PLUGIN_DEACTIVATED, "", "", "", nil) - message.Add("manifest", manifest.ClientManifest()) - a.Publish(message) + } else { // If plugins are disabled, shutdown plugins. + a.Plugins.Shutdown() } - if err := a.setPluginStatusState(manifest.Id, model.PluginStateNotRunning); err != nil { - return model.NewAppError("deactivatePlugin", "app.plugin.deactivate.app_error", nil, err.Error(), http.StatusBadRequest) + if err := a.notifyPluginStatusesChanged(); err != nil { + mlog.Error("failed to notify plugin status changed", mlog.Err(err)) } - - mlog.Info("Deactivated plugin", mlog.String("plugin_id", manifest.Id)) - return nil } -// InstallPlugin unpacks and installs a plugin but does not activate it. -func (a *App) InstallPlugin(pluginFile io.Reader) (*model.Manifest, *model.AppError) { - return a.installPlugin(pluginFile, false) +func (a *App) NewPluginAPI(manifest *model.Manifest) plugin.API { + return NewPluginAPI(a, manifest) } -func (a *App) installPlugin(pluginFile io.Reader, allowPrepackaged bool) (*model.Manifest, *model.AppError) { - if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable { - return nil, model.NewAppError("installPlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) - } - - tmpDir, err := ioutil.TempDir("", "plugintmp") - if err != nil { - return nil, model.NewAppError("installPlugin", "app.plugin.filesystem.app_error", nil, err.Error(), http.StatusInternalServerError) - } - defer os.RemoveAll(tmpDir) - - if err := utils.ExtractTarGz(pluginFile, tmpDir); err != nil { - return nil, model.NewAppError("installPlugin", "app.plugin.extract.app_error", nil, err.Error(), http.StatusBadRequest) - } - - tmpPluginDir := tmpDir - dir, err := ioutil.ReadDir(tmpDir) - if err != nil { - return nil, model.NewAppError("installPlugin", "app.plugin.filesystem.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - if len(dir) == 1 && dir[0].IsDir() { - tmpPluginDir = filepath.Join(tmpPluginDir, dir[0].Name()) - } - - manifest, _, err := model.FindManifest(tmpPluginDir) - if err != nil { - return nil, model.NewAppError("installPlugin", "app.plugin.manifest.app_error", nil, err.Error(), http.StatusBadRequest) +func (a *App) InitPlugins(pluginDir, webappPluginDir string) { + if a.Plugins != nil || !*a.Config().PluginSettings.Enable { + a.SyncPluginsActiveState() + return } - _, isPrepackaged := prepackagedPlugins[manifest.Id] - if isPrepackaged && !allowPrepackaged { - return nil, model.NewAppError("installPlugin", "app.plugin.prepackaged.app_error", nil, "", http.StatusBadRequest) - } + a.Log.Info("Starting up plugins") - if !plugin.IsValidId(manifest.Id) { - return nil, model.NewAppError("installPlugin", "app.plugin.invalid_id.app_error", map[string]interface{}{"Min": plugin.MinIdLength, "Max": plugin.MaxIdLength, "Regex": plugin.ValidId.String()}, "", http.StatusBadRequest) - } - - bundles, err := a.PluginEnv.Plugins() - if err != nil { - return nil, model.NewAppError("installPlugin", "app.plugin.install.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - for _, bundle := range bundles { - if bundle.Manifest != nil && bundle.Manifest.Id == manifest.Id { - return nil, model.NewAppError("installPlugin", "app.plugin.install_id.app_error", nil, "", http.StatusBadRequest) - } + if err := os.Mkdir(pluginDir, 0744); err != nil && !os.IsExist(err) { + mlog.Error("Failed to start up plugins", mlog.Err(err)) + return } - pluginPath := filepath.Join(a.PluginEnv.SearchPath(), manifest.Id) - err = utils.CopyDir(tmpPluginDir, pluginPath) - if err != nil { - return nil, model.NewAppError("installPlugin", "app.plugin.mvdir.app_error", nil, err.Error(), http.StatusInternalServerError) + if err := os.Mkdir(webappPluginDir, 0744); err != nil && !os.IsExist(err) { + mlog.Error("Failed to start up plugins", mlog.Err(err)) + return } - a.pluginStatuses[manifest.Id] = &model.PluginStatus{ - ClusterId: a.GetClusterId(), - PluginId: manifest.Id, - PluginPath: pluginPath, - State: model.PluginStateNotRunning, - IsSandboxed: a.IsPluginSandboxSupported, - IsPrepackaged: isPrepackaged, - Name: manifest.Name, - Description: manifest.Description, - Version: manifest.Version, + if env, err := plugin.NewEnvironment(a.NewPluginAPI, pluginDir, webappPluginDir, a.Log); err != nil { + mlog.Error("Failed to start up plugins", mlog.Err(err)) + return + } else { + a.Plugins = env } - if err := a.notifyPluginStatusesChanged(); err != nil { - mlog.Error("failed to notify plugin status changed", mlog.Err(err)) - } + // Sync plugin active state when config changes. Also notify plugins. + a.RemoveConfigListener(a.PluginConfigListenerId) + a.PluginConfigListenerId = a.AddConfigListener(func(*model.Config, *model.Config) { + a.SyncPluginsActiveState() + a.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool { + hooks.OnConfigurationChange() + return true + }, plugin.OnConfigurationChangeId) + }) - return manifest, nil + a.SyncPluginsActiveState() } -// GetPlugins returned the plugins installed on this server, including the manifests needed to -// enable plugins with web functionality. -func (a *App) GetPlugins() (*model.PluginsResponse, *model.AppError) { - if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable { - return nil, model.NewAppError("GetPlugins", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) - } - - plugins, err := a.PluginEnv.Plugins() - if err != nil { - return nil, model.NewAppError("GetPlugins", "app.plugin.get_plugins.app_error", nil, err.Error(), http.StatusInternalServerError) +func (a *App) ShutDownPlugins() { + if a.Plugins == nil { + return } - resp := &model.PluginsResponse{Active: []*model.PluginInfo{}, Inactive: []*model.PluginInfo{}} - for _, plugin := range plugins { - if plugin.Manifest == nil { - continue - } + mlog.Info("Shutting down plugins") - info := &model.PluginInfo{ - Manifest: *plugin.Manifest, - } - _, info.Prepackaged = prepackagedPlugins[plugin.Manifest.Id] - if a.PluginEnv.IsPluginActive(plugin.Manifest.Id) { - resp.Active = append(resp.Active, info) - } else { - resp.Inactive = append(resp.Inactive, info) - } - } + a.Plugins.Shutdown() - return resp, nil + a.RemoveConfigListener(a.PluginConfigListenerId) + a.PluginConfigListenerId = "" + a.Plugins = nil } func (a *App) GetActivePluginManifests() ([]*model.Manifest, *model.AppError) { - if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable { + if a.Plugins == nil || !*a.Config().PluginSettings.Enable { return nil, model.NewAppError("GetActivePluginManifests", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } - plugins := a.PluginEnv.ActivePlugins() + plugins := a.Plugins.Active() manifests := make([]*model.Manifest, len(plugins)) for i, plugin := range plugins { @@ -363,99 +142,14 @@ func (a *App) GetActivePluginManifests() ([]*model.Manifest, *model.AppError) { return manifests, nil } -// GetPluginStatuses returns the status for plugins installed on this server. -func (a *App) GetPluginStatuses() (model.PluginStatuses, *model.AppError) { - if !*a.Config().PluginSettings.Enable { - return nil, model.NewAppError("GetPluginStatuses", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) - } - - pluginStatuses := make([]*model.PluginStatus, 0, len(a.pluginStatuses)) - for _, pluginStatus := range a.pluginStatuses { - pluginStatuses = append(pluginStatuses, pluginStatus) - } - - return pluginStatuses, nil -} - -// GetClusterPluginStatuses returns the status for plugins installed anywhere in the cluster. -func (a *App) GetClusterPluginStatuses() (model.PluginStatuses, *model.AppError) { - pluginStatuses, err := a.GetPluginStatuses() - if err != nil { - return nil, err - } - - if a.Cluster != nil && *a.Config().ClusterSettings.Enable { - clusterPluginStatuses, err := a.Cluster.GetPluginStatuses() - if err != nil { - return nil, model.NewAppError("GetClusterPluginStatuses", "app.plugin.get_cluster_plugin_statuses.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - pluginStatuses = append(pluginStatuses, clusterPluginStatuses...) - } - - return pluginStatuses, nil -} - -func (a *App) RemovePlugin(id string) *model.AppError { - return a.removePlugin(id, false) -} - -func (a *App) removePlugin(id string, allowPrepackaged bool) *model.AppError { - if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable { - return model.NewAppError("removePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) - } - - if _, ok := prepackagedPlugins[id]; ok && !allowPrepackaged { - return model.NewAppError("removePlugin", "app.plugin.prepackaged.app_error", nil, "", http.StatusBadRequest) - } - - plugins, err := a.PluginEnv.Plugins() - if err != nil { - return model.NewAppError("removePlugin", "app.plugin.deactivate.app_error", nil, err.Error(), http.StatusBadRequest) - } - - var manifest *model.Manifest - var pluginPath string - for _, p := range plugins { - if p.Manifest != nil && p.Manifest.Id == id { - manifest = p.Manifest - pluginPath = filepath.Dir(p.ManifestPath) - break - } - } - - if manifest == nil { - return model.NewAppError("removePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusBadRequest) - } - - if a.PluginEnv.IsPluginActive(id) { - err := a.deactivatePlugin(manifest) - if err != nil { - return err - } - } - - err = os.RemoveAll(pluginPath) - if err != nil { - return model.NewAppError("removePlugin", "app.plugin.remove.app_error", nil, err.Error(), http.StatusInternalServerError) - } - - delete(a.pluginStatuses, manifest.Id) - if err := a.notifyPluginStatusesChanged(); err != nil { - mlog.Error("failed to notify plugin status changed", mlog.Err(err)) - } - - return nil -} - // EnablePlugin will set the config for an installed plugin to enabled, triggering asynchronous // activation if inactive anywhere in the cluster. func (a *App) EnablePlugin(id string) *model.AppError { - if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable { + if a.Plugins == nil || !*a.Config().PluginSettings.Enable { return model.NewAppError("EnablePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } - plugins, err := a.PluginEnv.Plugins() + plugins, err := a.Plugins.Available() if err != nil { return model.NewAppError("EnablePlugin", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError) } @@ -472,14 +166,11 @@ func (a *App) EnablePlugin(id string) *model.AppError { return model.NewAppError("EnablePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusBadRequest) } - if err := a.setPluginStatusState(manifest.Id, model.PluginStateStarting); err != nil { - return model.NewAppError("EnablePlugin", "app.plugin.set_plugin_status_state.app_error", nil, err.Error(), http.StatusInternalServerError) - } - a.UpdateConfig(func(cfg *model.Config) { cfg.PluginSettings.PluginStates[id] = &model.PluginState{Enable: true} }) + // This call will cause SyncPluginsActiveState to be called and the plugin to be activated if err := a.SaveConfig(a.Config(), true); err != nil { if err.Id == "ent.cluster.save_config.error" { return model.NewAppError("EnablePlugin", "app.plugin.cluster.save_config.app_error", nil, "", http.StatusInternalServerError) @@ -492,11 +183,11 @@ func (a *App) EnablePlugin(id string) *model.AppError { // DisablePlugin will set the config for an installed plugin to disabled, triggering deactivation if active. func (a *App) DisablePlugin(id string) *model.AppError { - if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable { + if a.Plugins == nil || !*a.Config().PluginSettings.Enable { return model.NewAppError("DisablePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } - plugins, err := a.PluginEnv.Plugins() + plugins, err := a.Plugins.Available() if err != nil { return model.NewAppError("DisablePlugin", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError) } @@ -513,10 +204,6 @@ func (a *App) DisablePlugin(id string) *model.AppError { return model.NewAppError("DisablePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusBadRequest) } - if err := a.setPluginStatusState(manifest.Id, model.PluginStateStopping); err != nil { - return model.NewAppError("EnablePlugin", "app.plugin.set_plugin_status_state.app_error", nil, err.Error(), http.StatusInternalServerError) - } - a.UpdateConfig(func(cfg *model.Config) { cfg.PluginSettings.PluginStates[id] = &model.PluginState{Enable: false} }) @@ -528,331 +215,35 @@ func (a *App) DisablePlugin(id string) *model.AppError { return nil } -func (a *App) InitPlugins(pluginPath, webappPath string, supervisorOverride pluginenv.SupervisorProviderFunc) { - if a.PluginEnv != nil { - return - } - - if !*a.Config().PluginSettings.Enable { - return - } - - mlog.Info("Starting up plugins") - - a.pluginStatuses = make(map[string]*model.PluginStatus) - - if err := os.Mkdir(pluginPath, 0744); err != nil && !os.IsExist(err) { - mlog.Error("Failed to start up plugins", mlog.Err(err)) - return - } - - if err := os.Mkdir(webappPath, 0744); err != nil && !os.IsExist(err) { - mlog.Error("Failed to start up plugins", mlog.Err(err)) - return - } - - options := []pluginenv.Option{ - pluginenv.SearchPath(pluginPath), - pluginenv.WebappPath(webappPath), - pluginenv.APIProvider(func(m *model.Manifest) (plugin.API, error) { - return &PluginAPI{ - id: m.Id, - app: a, - keyValueStore: &PluginKeyValueStore{ - id: m.Id, - app: a, - }, - }, nil - }), - } - - a.IsPluginSandboxSupported = sandbox.CheckSupport() == nil - - if supervisorOverride != nil { - options = append(options, pluginenv.SupervisorProvider(supervisorOverride)) - } else if a.IsPluginSandboxSupported { - options = append(options, pluginenv.SupervisorProvider(sandbox.SupervisorProvider)) - } else { - options = append(options, pluginenv.SupervisorProvider(rpcplugin.SupervisorProvider)) - } - - if env, err := pluginenv.New(options...); err != nil { - mlog.Error("Failed to start up plugins", mlog.Err(err)) - return - } else { - a.PluginEnv = env - } - - for id, asset := range prepackagedPlugins { - if tarball, err := asset("plugin.tar.gz"); err != nil { - mlog.Error("Failed to install prepackaged plugin", mlog.Err(err)) - } else if tarball != nil { - a.removePlugin(id, true) - if _, err := a.installPlugin(bytes.NewReader(tarball), true); err != nil { - mlog.Error("Failed to install prepackaged plugin", mlog.Err(err)) - } - if _, ok := a.Config().PluginSettings.PluginStates[id]; !ok && id != "zoom" { - if err := a.EnablePlugin(id); err != nil { - mlog.Error("Failed to enable prepackaged plugin", mlog.Err(err)) - } - } - } - } - - a.RemoveConfigListener(a.PluginConfigListenerId) - a.PluginConfigListenerId = a.AddConfigListener(func(oldCfg *model.Config, cfg *model.Config) { - if a.PluginEnv == nil { - return - } - - if *oldCfg.PluginSettings.Enable != *cfg.PluginSettings.Enable { - a.setPluginsActive(*cfg.PluginSettings.Enable) - } else { - plugins := map[string]bool{} - for id := range oldCfg.PluginSettings.PluginStates { - plugins[id] = true - } - for id := range cfg.PluginSettings.PluginStates { - plugins[id] = true - } - - for id := range plugins { - oldPluginState := oldCfg.PluginSettings.PluginStates[id] - pluginState := cfg.PluginSettings.PluginStates[id] - - wasEnabled := oldPluginState != nil && oldPluginState.Enable - isEnabled := pluginState != nil && pluginState.Enable - - if wasEnabled != isEnabled { - a.setPluginActiveById(id, isEnabled) - } - } - } - - for _, err := range a.PluginEnv.Hooks().OnConfigurationChange() { - mlog.Error(err.Error()) - } - }) - - a.setPluginsActive(true) -} - -func (a *App) ServePluginRequest(w http.ResponseWriter, r *http.Request) { - if a.PluginEnv == nil || !*a.Config().PluginSettings.Enable { - err := model.NewAppError("ServePluginRequest", "app.plugin.disabled.app_error", nil, "Enable plugins to serve plugin requests", http.StatusNotImplemented) - mlog.Error(err.Error()) - w.WriteHeader(err.StatusCode) - w.Header().Set("Content-Type", "application/json") - w.Write([]byte(err.ToJson())) - return - } - - a.servePluginRequest(w, r, a.PluginEnv.Hooks().ServeHTTP) -} - -func (a *App) servePluginRequest(w http.ResponseWriter, r *http.Request, handler http.HandlerFunc) { - token := "" - - authHeader := r.Header.Get(model.HEADER_AUTH) - if strings.HasPrefix(strings.ToUpper(authHeader), model.HEADER_BEARER+" ") { - token = authHeader[len(model.HEADER_BEARER)+1:] - } else if strings.HasPrefix(strings.ToLower(authHeader), model.HEADER_TOKEN+" ") { - token = authHeader[len(model.HEADER_TOKEN)+1:] - } else if cookie, _ := r.Cookie(model.SESSION_COOKIE_TOKEN); cookie != nil && (r.Method == "GET" || r.Header.Get(model.HEADER_REQUESTED_WITH) == model.HEADER_REQUESTED_WITH_XML) { - token = cookie.Value - } else { - token = r.URL.Query().Get("access_token") - } - - r.Header.Del("Mattermost-User-Id") - if token != "" { - if session, err := a.GetSession(token); session != nil && err == nil { - r.Header.Set("Mattermost-User-Id", session.UserId) - } - } - - cookies := r.Cookies() - r.Header.Del("Cookie") - for _, c := range cookies { - if c.Name != model.SESSION_COOKIE_TOKEN { - r.AddCookie(c) - } - } - r.Header.Del(model.HEADER_AUTH) - r.Header.Del("Referer") - - params := mux.Vars(r) - - newQuery := r.URL.Query() - newQuery.Del("access_token") - r.URL.RawQuery = newQuery.Encode() - r.URL.Path = strings.TrimPrefix(r.URL.Path, "/plugins/"+params["plugin_id"]) - - handler(w, r.WithContext(context.WithValue(r.Context(), "plugin_id", params["plugin_id"]))) -} - -func (a *App) ShutDownPlugins() { - if a.PluginEnv == nil { - return - } - - mlog.Info("Shutting down plugins") - - for _, err := range a.PluginEnv.Shutdown() { - mlog.Error(err.Error()) - } - a.RemoveConfigListener(a.PluginConfigListenerId) - a.PluginConfigListenerId = "" - a.PluginEnv = nil -} - -func getKeyHash(key string) string { - hash := sha256.New() - hash.Write([]byte(key)) - return base64.StdEncoding.EncodeToString(hash.Sum(nil)) -} - -func (a *App) SetPluginKey(pluginId string, key string, value []byte) *model.AppError { - kv := &model.PluginKeyValue{ - PluginId: pluginId, - Key: getKeyHash(key), - Value: value, - } - - result := <-a.Srv.Store.Plugin().SaveOrUpdate(kv) - - if result.Err != nil { - mlog.Error(result.Err.Error()) - } - - return result.Err -} - -func (a *App) GetPluginKey(pluginId string, key string) ([]byte, *model.AppError) { - result := <-a.Srv.Store.Plugin().Get(pluginId, getKeyHash(key)) - - if result.Err != nil { - if result.Err.StatusCode == http.StatusNotFound { - return nil, nil - } - mlog.Error(result.Err.Error()) - return nil, result.Err - } - - kv := result.Data.(*model.PluginKeyValue) - - return kv.Value, nil -} - -func (a *App) DeletePluginKey(pluginId string, key string) *model.AppError { - result := <-a.Srv.Store.Plugin().Delete(pluginId, getKeyHash(key)) - - if result.Err != nil { - mlog.Error(result.Err.Error()) - } - - return result.Err -} - -type PluginCommand struct { - Command *model.Command - PluginId string +func (a *App) PluginsReady() bool { + return a.Plugins != nil && *a.Config().PluginSettings.Enable } -func (a *App) RegisterPluginCommand(pluginId string, command *model.Command) error { - if command.Trigger == "" { - return fmt.Errorf("invalid command") - } - - command = &model.Command{ - Trigger: strings.ToLower(command.Trigger), - TeamId: command.TeamId, - AutoComplete: command.AutoComplete, - AutoCompleteDesc: command.AutoCompleteDesc, - AutoCompleteHint: command.AutoCompleteHint, - DisplayName: command.DisplayName, - } - - a.pluginCommandsLock.Lock() - defer a.pluginCommandsLock.Unlock() - - for _, pc := range a.pluginCommands { - if pc.Command.Trigger == command.Trigger && pc.Command.TeamId == command.TeamId { - if pc.PluginId == pluginId { - pc.Command = command - return nil - } - } +func (a *App) GetPlugins() (*model.PluginsResponse, *model.AppError) { + if !a.PluginsReady() { + return nil, model.NewAppError("GetPlugins", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } - a.pluginCommands = append(a.pluginCommands, &PluginCommand{ - Command: command, - PluginId: pluginId, - }) - return nil -} - -func (a *App) UnregisterPluginCommand(pluginId, teamId, trigger string) { - trigger = strings.ToLower(trigger) - - a.pluginCommandsLock.Lock() - defer a.pluginCommandsLock.Unlock() - - var remaining []*PluginCommand - for _, pc := range a.pluginCommands { - if pc.Command.TeamId != teamId || pc.Command.Trigger != trigger { - remaining = append(remaining, pc) - } + availablePlugins, err := a.Plugins.Available() + if err != nil { + return nil, model.NewAppError("GetPlugins", "app.plugin.get_plugins.app_error", nil, err.Error(), http.StatusInternalServerError) } - a.pluginCommands = remaining -} - -func (a *App) UnregisterPluginCommands(pluginId string) { - a.pluginCommandsLock.Lock() - defer a.pluginCommandsLock.Unlock() - - var remaining []*PluginCommand - for _, pc := range a.pluginCommands { - if pc.PluginId != pluginId { - remaining = append(remaining, pc) + resp := &model.PluginsResponse{Active: []*model.PluginInfo{}, Inactive: []*model.PluginInfo{}} + for _, plugin := range availablePlugins { + if plugin.Manifest == nil { + continue } - } - a.pluginCommands = remaining -} - -func (a *App) PluginCommandsForTeam(teamId string) []*model.Command { - a.pluginCommandsLock.RLock() - defer a.pluginCommandsLock.RUnlock() - var commands []*model.Command - for _, pc := range a.pluginCommands { - if pc.Command.TeamId == "" || pc.Command.TeamId == teamId { - commands = append(commands, pc.Command) + info := &model.PluginInfo{ + Manifest: *plugin.Manifest, } - } - return commands -} - -func (a *App) ExecutePluginCommand(args *model.CommandArgs) (*model.Command, *model.CommandResponse, *model.AppError) { - parts := strings.Split(args.Command, " ") - trigger := parts[0][1:] - trigger = strings.ToLower(trigger) - - a.pluginCommandsLock.RLock() - defer a.pluginCommandsLock.RUnlock() - for _, pc := range a.pluginCommands { - if (pc.Command.TeamId == "" || pc.Command.TeamId == args.TeamId) && pc.Command.Trigger == trigger { - response, appErr, err := a.PluginEnv.HooksForPlugin(pc.PluginId).ExecuteCommand(args) - if err != nil { - return pc.Command, nil, model.NewAppError("ExecutePluginCommand", "model.plugin_command.error.app_error", nil, "err="+err.Error(), http.StatusInternalServerError) - } - return pc.Command, response, appErr + if a.Plugins.IsActive(plugin.Manifest.Id) { + resp.Active = append(resp.Active, info) + } else { + resp.Inactive = append(resp.Inactive, info) } } - return nil, nil, nil -} -func (a *App) PluginsReady() bool { - return a.PluginEnv != nil && *a.Config().PluginSettings.Enable + return resp, nil } -- cgit v1.2.3-1-g7c22 From 7bfb5aec26c6bb8c49fa19e8347bc91acc86fe92 Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Thu, 5 Jul 2018 10:23:55 -0400 Subject: Add back enable/disable WS events (#9052) --- app/plugin.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'app/plugin.go') diff --git a/app/plugin.go b/app/plugin.go index af4bae649..d68579311 100644 --- a/app/plugin.go +++ b/app/plugin.go @@ -170,6 +170,12 @@ func (a *App) EnablePlugin(id string) *model.AppError { cfg.PluginSettings.PluginStates[id] = &model.PluginState{Enable: true} }) + if manifest.HasClient() { + message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PLUGIN_ENABLED, "", "", "", nil) + message.Add("manifest", manifest.ClientManifest()) + a.Publish(message) + } + // This call will cause SyncPluginsActiveState to be called and the plugin to be activated if err := a.SaveConfig(a.Config(), true); err != nil { if err.Id == "ent.cluster.save_config.error" { @@ -208,6 +214,12 @@ func (a *App) DisablePlugin(id string) *model.AppError { cfg.PluginSettings.PluginStates[id] = &model.PluginState{Enable: false} }) + if manifest.HasClient() { + message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PLUGIN_DISABLED, "", "", "", nil) + message.Add("manifest", manifest.ClientManifest()) + a.Publish(message) + } + if err := a.SaveConfig(a.Config(), true); err != nil { return model.NewAppError("DisablePlugin", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError) } -- cgit v1.2.3-1-g7c22