From 899ab31fff9b34bc125faf75b79a89e390deb2cf Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Fri, 1 Sep 2017 09:00:27 -0400 Subject: Implement experimental REST API endpoints for plugins (#7279) * Implement experimental REST API endpoints for plugins * Updates per feedback and rebase * Update tests * Further updates * Update extraction of plugins * Use OS temp dir for plugins instead of search path * Fail extraction on paths that attempt to traverse upward * Update pluginenv ActivePlugins() --- app/plugins.go | 144 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ app/server.go | 32 +++++++++++++ 2 files changed, 176 insertions(+) (limited to 'app') diff --git a/app/plugins.go b/app/plugins.go index 82eda067c..51f6414a3 100644 --- a/app/plugins.go +++ b/app/plugins.go @@ -5,7 +5,12 @@ package app import ( "encoding/json" + "io" + "io/ioutil" "net/http" + "os" + "path/filepath" + "strings" l4g "github.com/alecthomas/log4go" @@ -84,3 +89,142 @@ func InitPlugins() { p.OnConfigurationChange() } } + +func ActivatePlugins() { + if Srv.PluginEnv == nil { + l4g.Error("plugin env not initialized") + return + } + + plugins, err := Srv.PluginEnv.Plugins() + if err != nil { + l4g.Error("failed to start up plugins: " + err.Error()) + return + } + + for _, plugin := range plugins { + err := Srv.PluginEnv.ActivatePlugin(plugin.Manifest.Id) + if err != nil { + l4g.Error(err.Error()) + } + l4g.Info("Activated %v plugin", plugin.Manifest.Id) + } +} + +func UnpackAndActivatePlugin(pluginFile io.Reader) (*model.Manifest, *model.AppError) { + if Srv.PluginEnv == nil || !*utils.Cfg.PluginSettings.Enable { + return nil, model.NewAppError("UnpackAndActivatePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) + } + + tmpDir, err := ioutil.TempDir("", "plugintmp") + if err != nil { + return nil, model.NewAppError("UnpackAndActivatePlugin", "app.plugin.temp_dir.app_error", nil, err.Error(), http.StatusInternalServerError) + } + defer func() { + os.RemoveAll(tmpDir) + }() + + filenames, err := utils.ExtractTarGz(pluginFile, tmpDir) + if err != nil { + return nil, model.NewAppError("UnpackAndActivatePlugin", "app.plugin.extract.app_error", nil, err.Error(), http.StatusBadRequest) + } + + if len(filenames) == 0 { + return nil, model.NewAppError("UnpackAndActivatePlugin", "app.plugin.no_files.app_error", nil, err.Error(), http.StatusBadRequest) + } + + splitPath := strings.Split(filenames[0], string(os.PathSeparator)) + + if len(splitPath) == 0 { + return nil, model.NewAppError("UnpackAndActivatePlugin", "app.plugin.bad_path.app_error", nil, err.Error(), http.StatusBadRequest) + } + + manifestDir := filepath.Join(tmpDir, splitPath[0]) + + manifest, _, err := model.FindManifest(manifestDir) + if err != nil { + return nil, model.NewAppError("UnpackAndActivatePlugin", "app.plugin.manifest.app_error", nil, err.Error(), http.StatusBadRequest) + } + + os.Rename(manifestDir, filepath.Join(Srv.PluginEnv.SearchPath(), manifest.Id)) + if err != nil { + return nil, model.NewAppError("UnpackAndActivatePlugin", "app.plugin.mvdir.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + // Should add manifest validation and error handling here + + err = Srv.PluginEnv.ActivatePlugin(manifest.Id) + if err != nil { + return nil, model.NewAppError("UnpackAndActivatePlugin", "app.plugin.activate.app_error", nil, err.Error(), http.StatusBadRequest) + } + + return manifest, nil +} + +func GetActivePluginManifests() ([]*model.Manifest, *model.AppError) { + if Srv.PluginEnv == nil || !*utils.Cfg.PluginSettings.Enable { + return nil, model.NewAppError("GetActivePluginManifests", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) + } + + plugins, err := Srv.PluginEnv.ActivePlugins() + if err != nil { + return nil, model.NewAppError("GetActivePluginManifests", "app.plugin.get_plugins.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + manifests := make([]*model.Manifest, len(plugins)) + for i, plugin := range plugins { + manifests[i] = plugin.Manifest + } + + return manifests, nil +} + +func RemovePlugin(id string) *model.AppError { + if Srv.PluginEnv == nil || !*utils.Cfg.PluginSettings.Enable { + return model.NewAppError("RemovePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) + } + + err := Srv.PluginEnv.DeactivatePlugin(id) + if err != nil { + return model.NewAppError("RemovePlugin", "app.plugin.deactivate.app_error", nil, err.Error(), http.StatusBadRequest) + } + + err = os.RemoveAll(filepath.Join(Srv.PluginEnv.SearchPath(), id)) + if err != nil { + return model.NewAppError("RemovePlugin", "app.plugin.remove.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + return nil +} + +// Temporary WIP function/type for experimental webapp plugins +type ClientConfigPlugin struct { + Id string `json:"id"` + BundlePath string `json:"bundle_path"` +} + +func GetPluginsForClientConfig() string { + if Srv.PluginEnv == nil || !*utils.Cfg.PluginSettings.Enable { + return "" + } + + plugins, err := Srv.PluginEnv.ActivePlugins() + if err != nil { + return "" + } + + pluginsConfig := []ClientConfigPlugin{} + for _, plugin := range plugins { + if plugin.Manifest.Webapp == nil { + continue + } + pluginsConfig = append(pluginsConfig, ClientConfigPlugin{Id: plugin.Manifest.Id, BundlePath: plugin.Manifest.Webapp.BundlePath}) + } + + b, err := json.Marshal(pluginsConfig) + if err != nil { + return "" + } + + return string(b) +} diff --git a/app/server.go b/app/server.go index b83aa9506..c3bcd562d 100644 --- a/app/server.go +++ b/app/server.go @@ -7,6 +7,7 @@ import ( "crypto/tls" "net" "net/http" + "os" "strings" "time" @@ -19,6 +20,7 @@ import ( "gopkg.in/throttled/throttled.v2/store/memstore" "github.com/mattermost/platform/model" + "github.com/mattermost/platform/plugin/pluginenv" "github.com/mattermost/platform/store" "github.com/mattermost/platform/utils" ) @@ -28,6 +30,7 @@ type Server struct { WebSocketRouter *WebSocketRouter Router *mux.Router GracefulServer *graceful.Server + PluginEnv *pluginenv.Environment } var allowedMethods []string = []string{ @@ -186,6 +189,10 @@ func StartServer() { }() } + if *utils.Cfg.PluginSettings.Enable { + StartupPlugins("plugins", "webapp/dist") + } + go func() { var err error if *utils.Cfg.ServiceSettings.ConnectionSecurity == model.CONN_SECURITY_TLS { @@ -223,3 +230,28 @@ func StopServer() { l4g.Info(utils.T("api.server.stop_server.stopped.info")) } + +func StartupPlugins(pluginPath, webappPath string) { + l4g.Info("Starting up plugins") + + err := os.Mkdir(pluginPath, 0744) + if err != nil { + if os.IsExist(err) { + err = nil + } else { + l4g.Error("failed to start up plugins: " + err.Error()) + return + } + } + + Srv.PluginEnv, err = pluginenv.New( + pluginenv.SearchPath(pluginPath), + pluginenv.WebappPath(webappPath), + ) + + if err != nil { + l4g.Error("failed to start up plugins: " + err.Error()) + } + + ActivatePlugins() +} -- cgit v1.2.3-1-g7c22