summaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
authorJoram Wilander <jwawilander@gmail.com>2017-09-01 09:00:27 -0400
committerGitHub <noreply@github.com>2017-09-01 09:00:27 -0400
commit899ab31fff9b34bc125faf75b79a89e390deb2cf (patch)
tree41dc5832268504e54a0b2188eedcf89b7828dd12 /app
parent74b5e52c4eb54000dcb5a7b46c0977d732bce80f (diff)
downloadchat-899ab31fff9b34bc125faf75b79a89e390deb2cf.tar.gz
chat-899ab31fff9b34bc125faf75b79a89e390deb2cf.tar.bz2
chat-899ab31fff9b34bc125faf75b79a89e390deb2cf.zip
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()
Diffstat (limited to 'app')
-rw-r--r--app/plugins.go144
-rw-r--r--app/server.go32
2 files changed, 176 insertions, 0 deletions
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()
+}