From 859c5715589db4f50f46e1f8cc18246be469d197 Mon Sep 17 00:00:00 2001 From: Maria A Nunez Date: Wed, 10 Jul 2019 16:05:33 -0400 Subject: [PATCH] MM-16261 - Store Plugin in File Store (#11511) * Implemneted saving the plugin bundle on the file store upon plugin upload * Fixed compilation error * Fixed compilation issue * Added deletion from file store upon plugin uninstall * Added condition to delete from store only when exists. Added case of saving the bundle to the store when uploading from url. Added checks in plugin tests * Fixed compilation error * Moved storage of plugin bundle within app/installPlugin * Moved storing to filestore before enabling the plugin * Fixed error handling * Code styling improvements * Minor styling fix --- api4/plugin.go | 12 ++++++++++-- api4/plugin_test.go | 10 ++++++++++ app/plugin_install.go | 25 +++++++++++++++++++++++-- i18n/en.json | 12 ++++++++++++ 4 files changed, 55 insertions(+), 4 deletions(-) diff --git a/api4/plugin.go b/api4/plugin.go index 0c2b576a26..9feb48700e 100644 --- a/api4/plugin.go +++ b/api4/plugin.go @@ -6,6 +6,8 @@ package api4 import ( + "bytes" + "io/ioutil" "net/http" "net/url" @@ -124,13 +126,19 @@ func installPluginFromUrl(c *Context, w http.ResponseWriter, r *http.Request) { if r.URL.Query().Get("force") == "true" { force = true } - manifest, unpackErr := c.App.InstallPlugin(resp.Body, force) + + fileBytes, readErr := ioutil.ReadAll(resp.Body) + if readErr != nil { + c.Err = model.NewAppError("installPluginFromUrl", "api.plugin.install.reading_stream_failed.app_error", nil, err.Error(), http.StatusBadRequest) + return + } + + manifest, unpackErr := c.App.InstallPlugin(bytes.NewReader(fileBytes), force) if unpackErr != nil { c.Err = unpackErr return } - w.WriteHeader(http.StatusCreated) w.Write([]byte(manifest.ToJson())) } diff --git a/api4/plugin_test.go b/api4/plugin_test.go index 9b24c579f7..88c6301c01 100644 --- a/api4/plugin_test.go +++ b/api4/plugin_test.go @@ -57,6 +57,11 @@ func TestPlugin(t *testing.T) { CheckNoError(t, resp) assert.Equal(t, "testplugin", manifest.Id) + // Stored in File Store: Install Plugin from URL case + pluginStored, err := th.App.FileExists("./plugins/" + manifest.Id + ".tar.gz") + assert.Nil(t, err) + assert.True(t, pluginStored) + th.App.RemovePlugin(manifest.Id) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = false }) @@ -89,6 +94,11 @@ func TestPlugin(t *testing.T) { assert.Equal(t, "testplugin", manifest.Id) + // Stored in File Store: Upload Plugin case + pluginStored, err = th.App.FileExists("./plugins/" + manifest.Id + ".tar.gz") + assert.Nil(t, err) + assert.True(t, pluginStored) + // Upload error cases _, resp = th.SystemAdminClient.UploadPlugin(bytes.NewReader([]byte("badfile"))) CheckBadRequestStatus(t, resp) diff --git a/app/plugin_install.go b/app/plugin_install.go index c6d8df17b0..367a33656b 100644 --- a/app/plugin_install.go +++ b/app/plugin_install.go @@ -17,11 +17,11 @@ import ( ) // InstallPlugin unpacks and installs a plugin but does not enable or activate it. -func (a *App) InstallPlugin(pluginFile io.Reader, replace bool) (*model.Manifest, *model.AppError) { +func (a *App) InstallPlugin(pluginFile io.ReadSeeker, replace bool) (*model.Manifest, *model.AppError) { return a.installPlugin(pluginFile, replace) } -func (a *App) installPlugin(pluginFile io.Reader, replace bool) (*model.Manifest, *model.AppError) { +func (a *App) installPlugin(pluginFile io.ReadSeeker, replace bool) (*model.Manifest, *model.AppError) { pluginsEnvironment := a.GetPluginsEnvironment() if pluginsEnvironment == nil { return nil, model.NewAppError("installPlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) @@ -83,6 +83,14 @@ func (a *App) installPlugin(pluginFile io.Reader, replace bool) (*model.Manifest return nil, model.NewAppError("installPlugin", "app.plugin.mvdir.app_error", nil, err.Error(), http.StatusInternalServerError) } + // Store bundle in the file store to allow access from other servers. + pluginFile.Seek(0, 0) + + storePluginFileName := filepath.Join("./plugins", manifest.Id) + ".tar.gz" + if _, err := a.WriteFile(pluginFile, storePluginFileName); err != nil { + return nil, model.NewAppError("uploadPlugin", "app.plugin.store_bundle.app_error", nil, err.Error(), http.StatusInternalServerError) + } + if stashed != nil && stashed.Enable { a.EnablePlugin(manifest.Id) } @@ -138,5 +146,18 @@ func (a *App) removePlugin(id string) *model.AppError { return model.NewAppError("removePlugin", "app.plugin.remove.app_error", nil, err.Error(), http.StatusInternalServerError) } + // Remove bundle from the file store. + storePluginFileName := filepath.Join("./plugins", manifest.Id) + ".tar.gz" + bundleExist, fileErr := a.FileExists(storePluginFileName) + if fileErr != nil { + return model.NewAppError("removePlugin", "app.plugin.remove_bundle.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + if bundleExist { + if err := a.RemoveFile(storePluginFileName); err != nil { + return model.NewAppError("removePlugin", "app.plugin.remove_bundle.app_error", nil, err.Error(), http.StatusInternalServerError) + } + } + return nil } diff --git a/i18n/en.json b/i18n/en.json index 62aa9f6b3d..d826ff26dd 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1512,6 +1512,10 @@ "id": "api.plugin.install.invalid_url.app_error", "translation": "An invalid url was given to download the plugin." }, + { + "id": "api.plugin.install.reading_stream_failed.app_error", + "translation": "An error ocurred reading the plugin file stream." + }, { "id": "api.plugin.upload.array.app_error", "translation": "File array is empty in multipart/form request" @@ -3430,6 +3434,14 @@ "id": "app.plugin.remove.app_error", "translation": "Unable to delete plugin" }, + { + "id": "app.plugin.remove_bundle.app_error", + "translation": "Unable to remove plugin bundle from file store." + }, + { + "id": "app.plugin.store_bundle.app_error", + "translation": "Unable to store the plugin to the configured file store." + }, { "id": "app.plugin.upload_disabled.app_error", "translation": "Plugins and/or plugin uploads have been disabled."