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()
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
74b5e52c4e
Коммит
899ab31fff
144
app/plugins.go
144
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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user