mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
MM-53355: tidy up plugins (#24194)
* remove feature flag managed plugins * remove unneeded plugin blocklist * remove unnecessary wrappers * documentation and logging improvements * avoid use of global logger * leverage wrapped loggers (e.g. consistently log `plugin_id`) * promote some logs from `Debug` to `Info` for better visibility. * extract installPluginToFilestore * rename some variables for consistency / clarity * make generated
This commit is contained in:
parent
0e30d0abb8
commit
8372267739
@ -248,7 +248,8 @@ type AppIface interface {
|
|||||||
HubRegister(webConn *platform.WebConn)
|
HubRegister(webConn *platform.WebConn)
|
||||||
// HubUnregister unregisters a connection from a hub.
|
// HubUnregister unregisters a connection from a hub.
|
||||||
HubUnregister(webConn *platform.WebConn)
|
HubUnregister(webConn *platform.WebConn)
|
||||||
// InstallPlugin unpacks and installs a plugin but does not enable or activate it.
|
// InstallPlugin unpacks and installs a plugin but does not enable or activate it unless the the
|
||||||
|
// plugin was already enabled.
|
||||||
InstallPlugin(pluginFile io.ReadSeeker, replace bool) (*model.Manifest, *model.AppError)
|
InstallPlugin(pluginFile io.ReadSeeker, replace bool) (*model.Manifest, *model.AppError)
|
||||||
// LogAuditRec logs an audit record using default LvlAuditCLI.
|
// LogAuditRec logs an audit record using default LvlAuditCLI.
|
||||||
LogAuditRec(rec *audit.Record, err error)
|
LogAuditRec(rec *audit.Record, err error)
|
||||||
|
@ -16,7 +16,7 @@ func (s *Server) clusterInstallPluginHandler(msg *model.ClusterMessage) {
|
|||||||
if jsonErr := json.Unmarshal(msg.Data, &data); jsonErr != nil {
|
if jsonErr := json.Unmarshal(msg.Data, &data); jsonErr != nil {
|
||||||
mlog.Warn("Failed to decode from JSON", mlog.Err(jsonErr))
|
mlog.Warn("Failed to decode from JSON", mlog.Err(jsonErr))
|
||||||
}
|
}
|
||||||
s.Channels().installPluginFromData(data)
|
s.Channels().installPluginFromClusterMessage(data.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) clusterRemovePluginHandler(msg *model.ClusterMessage) {
|
func (s *Server) clusterRemovePluginHandler(msg *model.ClusterMessage) {
|
||||||
@ -24,7 +24,7 @@ func (s *Server) clusterRemovePluginHandler(msg *model.ClusterMessage) {
|
|||||||
if jsonErr := json.Unmarshal(msg.Data, &data); jsonErr != nil {
|
if jsonErr := json.Unmarshal(msg.Data, &data); jsonErr != nil {
|
||||||
mlog.Warn("Failed to decode from JSON", mlog.Err(jsonErr))
|
mlog.Warn("Failed to decode from JSON", mlog.Err(jsonErr))
|
||||||
}
|
}
|
||||||
s.Channels().removePluginFromData(data)
|
s.Channels().removePluginFromClusterMessage(data.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) clusterPluginEventHandler(msg *model.ClusterMessage) {
|
func (s *Server) clusterPluginEventHandler(msg *model.ClusterMessage) {
|
||||||
|
@ -30,11 +30,14 @@ import (
|
|||||||
"github.com/mattermost/mattermost/server/v8/platform/shared/filestore"
|
"github.com/mattermost/mattermost/server/v8/platform/shared/filestore"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// prepackagedPluginsDir is the hard-coded folder name where prepackaged plugins are bundled
|
||||||
|
// alongside the server.
|
||||||
const prepackagedPluginsDir = "prepackaged_plugins"
|
const prepackagedPluginsDir = "prepackaged_plugins"
|
||||||
|
|
||||||
|
// pluginSignaturePath tracks the path to the plugin bundle and signature for the given plugin.
|
||||||
type pluginSignaturePath struct {
|
type pluginSignaturePath struct {
|
||||||
pluginID string
|
pluginID string
|
||||||
path string
|
bundlePath string
|
||||||
signaturePath string
|
signaturePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,16 +164,18 @@ func (ch *Channels) syncPluginsActiveState() {
|
|||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
pluginID := plugin.Manifest.Id
|
pluginID := plugin.Manifest.Id
|
||||||
|
logger := ch.srv.Log().With(mlog.String("plugin_id", pluginID), mlog.String("bundle_path", plugin.Path))
|
||||||
|
|
||||||
updatedManifest, activated, err := pluginsEnvironment.Activate(pluginID)
|
updatedManifest, activated, err := pluginsEnvironment.Activate(pluginID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
plugin.WrapLogger(ch.srv.Log()).Error("Unable to activate plugin", mlog.Err(err))
|
logger.Error("Unable to activate plugin", mlog.Err(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if activated {
|
if activated {
|
||||||
// Notify all cluster clients if ready
|
// Notify all cluster clients if ready
|
||||||
if err := ch.notifyPluginEnabled(updatedManifest); err != nil {
|
if err := ch.notifyPluginEnabled(updatedManifest); err != nil {
|
||||||
ch.srv.Log().Error("Failed to notify cluster on plugin enable", mlog.Err(err))
|
logger.Error("Failed to notify cluster on plugin enable", mlog.Err(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}(plugin)
|
}(plugin)
|
||||||
@ -181,7 +186,7 @@ func (ch *Channels) syncPluginsActiveState() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := ch.notifyPluginStatusesChanged(); err != nil {
|
if err := ch.notifyPluginStatusesChanged(); err != nil {
|
||||||
mlog.Warn("failed to notify plugin status changed", mlog.Err(err))
|
ch.srv.Log().Warn("failed to notify plugin status changed", mlog.Err(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,12 +218,12 @@ func (ch *Channels) initPlugins(c *request.Context, pluginDir, webappPluginDir s
|
|||||||
ch.srv.Log().Info("Starting up plugins")
|
ch.srv.Log().Info("Starting up plugins")
|
||||||
|
|
||||||
if err := os.Mkdir(pluginDir, 0744); err != nil && !os.IsExist(err) {
|
if err := os.Mkdir(pluginDir, 0744); err != nil && !os.IsExist(err) {
|
||||||
mlog.Error("Failed to start up plugins", mlog.Err(err))
|
ch.srv.Log().Error("Failed to start up plugins", mlog.Err(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Mkdir(webappPluginDir, 0744); err != nil && !os.IsExist(err) {
|
if err := os.Mkdir(webappPluginDir, 0744); err != nil && !os.IsExist(err) {
|
||||||
mlog.Error("Failed to start up plugins", mlog.Err(err))
|
ch.srv.Log().Error("Failed to start up plugins", mlog.Err(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,7 +240,7 @@ func (ch *Channels) initPlugins(c *request.Context, pluginDir, webappPluginDir s
|
|||||||
ch.srv.GetMetrics(),
|
ch.srv.GetMetrics(),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mlog.Error("Failed to start up plugins", mlog.Err(err))
|
ch.srv.Log().Error("Failed to start up plugins", mlog.Err(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ch.pluginsLock.Lock()
|
ch.pluginsLock.Lock()
|
||||||
@ -245,19 +250,17 @@ func (ch *Channels) initPlugins(c *request.Context, pluginDir, webappPluginDir s
|
|||||||
ch.pluginsEnvironment.TogglePluginHealthCheckJob(*ch.cfgSvc.Config().PluginSettings.EnableHealthCheck)
|
ch.pluginsEnvironment.TogglePluginHealthCheckJob(*ch.cfgSvc.Config().PluginSettings.EnableHealthCheck)
|
||||||
|
|
||||||
if err := ch.syncPlugins(); err != nil {
|
if err := ch.syncPlugins(); err != nil {
|
||||||
mlog.Error("Failed to sync plugins from the file store", mlog.Err(err))
|
ch.srv.Log().Error("Failed to sync plugins from the file store", mlog.Err(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins := ch.processPrepackagedPlugins(prepackagedPluginsDir)
|
plugins := ch.processPrepackagedPlugins(prepackagedPluginsDir)
|
||||||
pluginsEnvironment = ch.GetPluginsEnvironment()
|
pluginsEnvironment = ch.GetPluginsEnvironment()
|
||||||
if pluginsEnvironment == nil {
|
if pluginsEnvironment == nil {
|
||||||
mlog.Info("Plugins environment not found, server is likely shutting down")
|
ch.srv.Log().Info("Plugins environment not found, server is likely shutting down")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pluginsEnvironment.SetPrepackagedPlugins(plugins)
|
pluginsEnvironment.SetPrepackagedPlugins(plugins)
|
||||||
|
|
||||||
ch.installFeatureFlagPlugins()
|
|
||||||
|
|
||||||
// Sync plugin active state when config changes. Also notify plugins.
|
// Sync plugin active state when config changes. Also notify plugins.
|
||||||
ch.pluginsLock.Lock()
|
ch.pluginsLock.Lock()
|
||||||
ch.RemoveConfigListener(ch.pluginConfigListenerID)
|
ch.RemoveConfigListener(ch.pluginConfigListenerID)
|
||||||
@ -265,7 +268,6 @@ func (ch *Channels) initPlugins(c *request.Context, pluginDir, webappPluginDir s
|
|||||||
// If plugin status remains unchanged, only then run this.
|
// If plugin status remains unchanged, only then run this.
|
||||||
// Because (*App).InitPlugins is already run as a config change hook.
|
// Because (*App).InitPlugins is already run as a config change hook.
|
||||||
if *old.PluginSettings.Enable == *new.PluginSettings.Enable {
|
if *old.PluginSettings.Enable == *new.PluginSettings.Enable {
|
||||||
ch.installFeatureFlagPlugins()
|
|
||||||
ch.syncPluginsActiveState()
|
ch.syncPluginsActiveState()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,7 +292,7 @@ func (a *App) SyncPlugins() *model.AppError {
|
|||||||
// SyncPlugins synchronizes the plugins installed locally
|
// SyncPlugins synchronizes the plugins installed locally
|
||||||
// with the plugin bundles available in the file store.
|
// with the plugin bundles available in the file store.
|
||||||
func (ch *Channels) syncPlugins() *model.AppError {
|
func (ch *Channels) syncPlugins() *model.AppError {
|
||||||
mlog.Info("Syncing plugins from the file store")
|
ch.srv.Log().Info("Syncing plugins from the file store")
|
||||||
|
|
||||||
pluginsEnvironment := ch.GetPluginsEnvironment()
|
pluginsEnvironment := ch.GetPluginsEnvironment()
|
||||||
if pluginsEnvironment == nil {
|
if pluginsEnvironment == nil {
|
||||||
@ -307,16 +309,19 @@ func (ch *Channels) syncPlugins() *model.AppError {
|
|||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(pluginID string) {
|
go func(pluginID string) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
|
logger := ch.srv.Log().With(mlog.String("plugin_id", pluginID))
|
||||||
|
|
||||||
// Only handle managed plugins with .filestore flag file.
|
// Only handle managed plugins with .filestore flag file.
|
||||||
_, err := os.Stat(filepath.Join(*ch.cfgSvc.Config().PluginSettings.Directory, pluginID, managedPluginFileName))
|
_, err := os.Stat(filepath.Join(*ch.cfgSvc.Config().PluginSettings.Directory, pluginID, managedPluginFileName))
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
mlog.Warn("Skipping sync for unmanaged plugin", mlog.String("plugin_id", pluginID))
|
logger.Warn("Skipping sync for unmanaged plugin")
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
mlog.Error("Skipping sync for plugin after failure to check if managed", mlog.String("plugin_id", pluginID), mlog.Err(err))
|
logger.Error("Skipping sync for plugin after failure to check if managed", mlog.Err(err))
|
||||||
} else {
|
} else {
|
||||||
mlog.Debug("Removing local installation of managed plugin before sync", mlog.String("plugin_id", pluginID))
|
logger.Info("Removing local installation of managed plugin before sync")
|
||||||
if err := ch.removePluginLocally(pluginID); err != nil {
|
if err := ch.removePluginLocally(pluginID); err != nil {
|
||||||
mlog.Error("Failed to remove local installation of managed plugin before sync", mlog.String("plugin_id", pluginID), mlog.Err(err))
|
logger.Error("Failed to remove local installation of managed plugin before sync", mlog.Err(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}(plugin.Manifest.Id)
|
}(plugin.Manifest.Id)
|
||||||
@ -333,26 +338,28 @@ func (ch *Channels) syncPlugins() *model.AppError {
|
|||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(plugin *pluginSignaturePath) {
|
go func(plugin *pluginSignaturePath) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
reader, appErr := ch.srv.fileReader(plugin.path)
|
logger := ch.srv.Log().With(mlog.String("plugin_id", plugin.pluginID), mlog.String("bundle_path", plugin.bundlePath))
|
||||||
|
|
||||||
|
bundle, appErr := ch.srv.fileReader(plugin.bundlePath)
|
||||||
if appErr != nil {
|
if appErr != nil {
|
||||||
mlog.Error("Failed to open plugin bundle from file store.", mlog.String("bundle", plugin.path), mlog.Err(appErr))
|
logger.Error("Failed to open plugin bundle from file store.", mlog.Err(appErr))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer reader.Close()
|
defer bundle.Close()
|
||||||
|
|
||||||
var signature filestore.ReadCloseSeeker
|
var signature filestore.ReadCloseSeeker
|
||||||
if *ch.cfgSvc.Config().PluginSettings.RequirePluginSignature {
|
if *ch.cfgSvc.Config().PluginSettings.RequirePluginSignature {
|
||||||
signature, appErr = ch.srv.fileReader(plugin.signaturePath)
|
signature, appErr = ch.srv.fileReader(plugin.signaturePath)
|
||||||
if appErr != nil {
|
if appErr != nil {
|
||||||
mlog.Error("Failed to open plugin signature from file store.", mlog.Err(appErr))
|
logger.Error("Failed to open plugin signature from file store.", mlog.Err(appErr))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer signature.Close()
|
defer signature.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
mlog.Info("Syncing plugin from file store", mlog.String("bundle", plugin.path))
|
logger.Info("Syncing plugin from file store")
|
||||||
if _, err := ch.installPluginLocally(reader, signature, installPluginLocallyAlways); err != nil && err.Id != "app.plugin.blocked.app_error" && err.Id != "app.plugin.skip_installation.app_error" {
|
if _, err := ch.installPluginLocally(bundle, signature, installPluginLocallyAlways); err != nil && err.Id != "app.plugin.skip_installation.app_error" {
|
||||||
mlog.Error("Failed to sync plugin from file store", mlog.String("bundle", plugin.path), mlog.Err(err))
|
logger.Error("Failed to sync plugin from file store", mlog.Err(err))
|
||||||
}
|
}
|
||||||
}(plugin)
|
}(plugin)
|
||||||
}
|
}
|
||||||
@ -370,7 +377,7 @@ func (ch *Channels) ShutDownPlugins() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mlog.Info("Shutting down plugins")
|
ch.srv.Log().Info("Shutting down plugins")
|
||||||
|
|
||||||
pluginsEnvironment.Shutdown()
|
pluginsEnvironment.Shutdown()
|
||||||
|
|
||||||
@ -383,7 +390,7 @@ func (ch *Channels) ShutDownPlugins() {
|
|||||||
if ch.pluginsEnvironment == pluginsEnvironment {
|
if ch.pluginsEnvironment == pluginsEnvironment {
|
||||||
ch.pluginsEnvironment = nil
|
ch.pluginsEnvironment = nil
|
||||||
} else {
|
} else {
|
||||||
mlog.Warn("Another PluginsEnvironment detected while shutting down plugins.")
|
ch.srv.Log().Warn("Another PluginsEnvironment detected while shutting down plugins.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -735,7 +742,7 @@ func (a *App) mergeLocalPlugins(remoteMarketplacePlugins map[string]*model.Marke
|
|||||||
if plugin.Manifest.IconPath != "" {
|
if plugin.Manifest.IconPath != "" {
|
||||||
iconData, err = getIcon(filepath.Join(plugin.Path, plugin.Manifest.IconPath))
|
iconData, err = getIcon(filepath.Join(plugin.Path, plugin.Manifest.IconPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mlog.Warn("Error loading local plugin icon", mlog.String("plugin", plugin.Manifest.Id), mlog.String("icon_path", plugin.Manifest.IconPath), mlog.Err(err))
|
a.Log().Warn("Error loading local plugin icon", mlog.String("plugin_id", plugin.Manifest.Id), mlog.String("icon_path", plugin.Manifest.IconPath), mlog.Err(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -850,7 +857,7 @@ func (ch *Channels) notifyPluginEnabled(manifest *model.Manifest) error {
|
|||||||
// which may result in a 404.
|
// which may result in a 404.
|
||||||
for _, status := range statuses {
|
for _, status := range statuses {
|
||||||
if status.PluginId == manifest.Id && status.Version != manifest.Version {
|
if status.PluginId == manifest.Id && status.Version != manifest.Version {
|
||||||
mlog.Debug("Not ready to notify webclients", mlog.String("cluster_id", status.ClusterId), mlog.String("plugin_id", manifest.Id))
|
ch.srv.Log().Debug("Not ready to notify webclients", mlog.String("cluster_id", status.ClusterId), mlog.String("plugin_id", manifest.Id))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -879,7 +886,7 @@ func (ch *Channels) getPluginsFromFilePaths(fileStorePaths []string) map[string]
|
|||||||
id := strings.TrimSuffix(filepath.Base(path), ".tar.gz")
|
id := strings.TrimSuffix(filepath.Base(path), ".tar.gz")
|
||||||
helper := &pluginSignaturePath{
|
helper := &pluginSignaturePath{
|
||||||
pluginID: id,
|
pluginID: id,
|
||||||
path: path,
|
bundlePath: path,
|
||||||
signaturePath: "",
|
signaturePath: "",
|
||||||
}
|
}
|
||||||
pluginSignaturePathMap[id] = helper
|
pluginSignaturePathMap[id] = helper
|
||||||
@ -911,7 +918,7 @@ func (ch *Channels) processPrepackagedPlugins(pluginsDir string) []*plugin.Prepa
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mlog.Error("Failed to walk prepackaged plugins", mlog.Err(err))
|
ch.srv.Log().Error("Failed to walk prepackaged plugins", mlog.Err(err))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -927,11 +934,10 @@ func (ch *Channels) processPrepackagedPlugins(pluginsDir string) []*plugin.Prepa
|
|||||||
p, err := ch.processPrepackagedPlugin(psPath)
|
p, err := ch.processPrepackagedPlugin(psPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var appErr *model.AppError
|
var appErr *model.AppError
|
||||||
// A log line already appears if the plugin is on the blocklist
|
if errors.As(err, &appErr) && appErr.Id == "app.plugin.skip_installation.app_error" {
|
||||||
if errors.As(err, &appErr) && (appErr.Id == "app.plugin.blocked.app_error" || appErr.Id == "app.plugin.skip_installation.app_error") {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
mlog.Error("Failed to install prepackaged plugin", mlog.String("path", psPath.path), mlog.Err(err))
|
ch.srv.Log().Error("Failed to install prepackaged plugin", mlog.String("plugin_id", p.Manifest.Id), mlog.String("bundle_path", psPath.bundlePath), mlog.Err(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
prepackagedPlugins <- p
|
prepackagedPlugins <- p
|
||||||
@ -951,11 +957,13 @@ func (ch *Channels) processPrepackagedPlugins(pluginsDir string) []*plugin.Prepa
|
|||||||
// processPrepackagedPlugin will return the prepackaged plugin metadata and will also
|
// processPrepackagedPlugin will return the prepackaged plugin metadata and will also
|
||||||
// install the prepackaged plugin if it had been previously enabled and AutomaticPrepackagedPlugins is true.
|
// install the prepackaged plugin if it had been previously enabled and AutomaticPrepackagedPlugins is true.
|
||||||
func (ch *Channels) processPrepackagedPlugin(pluginPath *pluginSignaturePath) (*plugin.PrepackagedPlugin, error) {
|
func (ch *Channels) processPrepackagedPlugin(pluginPath *pluginSignaturePath) (*plugin.PrepackagedPlugin, error) {
|
||||||
mlog.Debug("Processing prepackaged plugin", mlog.String("path", pluginPath.path))
|
logger := ch.srv.Log().With(mlog.String("plugin_id", pluginPath.pluginID), mlog.String("bundle_path", pluginPath.bundlePath))
|
||||||
|
|
||||||
fileReader, err := os.Open(pluginPath.path)
|
logger.Info("Processing prepackaged plugin")
|
||||||
|
|
||||||
|
fileReader, err := os.Open(pluginPath.bundlePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "Failed to open prepackaged plugin %s", pluginPath.path)
|
return nil, errors.Wrapf(err, "Failed to open prepackaged plugin %s", pluginPath.bundlePath)
|
||||||
}
|
}
|
||||||
defer fileReader.Close()
|
defer fileReader.Close()
|
||||||
|
|
||||||
@ -965,9 +973,9 @@ func (ch *Channels) processPrepackagedPlugin(pluginPath *pluginSignaturePath) (*
|
|||||||
}
|
}
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
plugin, pluginDir, err := getPrepackagedPlugin(pluginPath, fileReader, tmpDir)
|
plugin, pluginDir, err := ch.buildPrepackagedPlugin(pluginPath, fileReader, tmpDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "Failed to get prepackaged plugin %s", pluginPath.path)
|
return nil, errors.Wrapf(err, "Failed to get prepackaged plugin %s", pluginPath.bundlePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip installing the plugin at all if automatic prepackaged plugins is disabled
|
// Skip installing the plugin at all if automatic prepackaged plugins is disabled
|
||||||
@ -981,87 +989,24 @@ func (ch *Channels) processPrepackagedPlugin(pluginPath *pluginSignaturePath) (*
|
|||||||
return plugin, nil
|
return plugin, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
mlog.Debug("Installing prepackaged plugin", mlog.String("path", pluginPath.path))
|
logger.Info("Installing prepackaged plugin")
|
||||||
if _, err := ch.installExtractedPlugin(plugin.Manifest, pluginDir, installPluginLocallyOnlyIfNewOrUpgrade); err != nil {
|
if _, err := ch.installExtractedPlugin(plugin.Manifest, pluginDir, installPluginLocallyOnlyIfNewOrUpgrade); err != nil {
|
||||||
return nil, errors.Wrapf(err, "Failed to install extracted prepackaged plugin %s", pluginPath.path)
|
return nil, errors.Wrapf(err, "Failed to install extracted prepackaged plugin %s", pluginPath.bundlePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
return plugin, nil
|
return plugin, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// installFeatureFlagPlugins handles the automatic installation/upgrade of plugins from feature flags
|
// buildPrepackagedPlugin builds a PrepackagedPlugin from the plugin at the given path, additionally returning the directory in which it was extracted.
|
||||||
func (ch *Channels) installFeatureFlagPlugins() {
|
func (ch *Channels) buildPrepackagedPlugin(pluginPath *pluginSignaturePath, pluginFile io.ReadSeeker, tmpDir string) (*plugin.PrepackagedPlugin, string, error) {
|
||||||
ffControledPlugins := ch.cfgSvc.Config().FeatureFlags.Plugins()
|
|
||||||
|
|
||||||
// Respect the automatic prepackaged disable setting
|
|
||||||
if !*ch.cfgSvc.Config().PluginSettings.AutomaticPrepackagedPlugins {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for pluginID, version := range ffControledPlugins {
|
|
||||||
// Skip installing if the plugin has been previously disabled.
|
|
||||||
pluginState := ch.cfgSvc.Config().PluginSettings.PluginStates[pluginID]
|
|
||||||
if pluginState != nil && !pluginState.Enable {
|
|
||||||
ch.srv.Log().Debug("Not auto installing/upgrade because plugin was disabled", mlog.String("plugin_id", pluginID), mlog.String("version", version))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we already installed this version as InstallMarketplacePlugin can't handle re-installs well.
|
|
||||||
pluginStatus, err := ch.GetPluginStatus(pluginID)
|
|
||||||
pluginExists := err == nil
|
|
||||||
if pluginExists && pluginStatus.Version == version {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if version != "" && version != "control" {
|
|
||||||
// If we are on-prem skip installation if this is a downgrade
|
|
||||||
license := ch.srv.License()
|
|
||||||
inCloud := license != nil && *license.Features.Cloud
|
|
||||||
if !inCloud && pluginExists {
|
|
||||||
parsedVersion, err := semver.Parse(version)
|
|
||||||
if err != nil {
|
|
||||||
ch.srv.Log().Debug("Bad version from feature flag", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", version))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
parsedExistingVersion, err := semver.Parse(pluginStatus.Version)
|
|
||||||
if err != nil {
|
|
||||||
ch.srv.Log().Debug("Bad version from plugin manifest", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", pluginStatus.Version))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if parsedVersion.LTE(parsedExistingVersion) {
|
|
||||||
ch.srv.Log().Debug("Skip installation because given version was a downgrade and on-prem installations should not downgrade.", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", pluginStatus.Version))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := ch.InstallMarketplacePlugin(&model.InstallMarketplacePluginRequest{
|
|
||||||
Id: pluginID,
|
|
||||||
Version: version,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
ch.srv.Log().Debug("Unable to install plugin from FF manifest", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", version))
|
|
||||||
} else {
|
|
||||||
if err := ch.enablePlugin(pluginID); err != nil {
|
|
||||||
ch.srv.Log().Debug("Unable to enable plugin installed from feature flag.", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", version))
|
|
||||||
} else {
|
|
||||||
ch.srv.Log().Debug("Installed and enabled plugin.", mlog.String("plugin_id", pluginID), mlog.String("version", version))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPrepackagedPlugin builds a PrepackagedPlugin from the plugin at the given path, additionally returning the directory in which it was extracted.
|
|
||||||
func getPrepackagedPlugin(pluginPath *pluginSignaturePath, pluginFile io.ReadSeeker, tmpDir string) (*plugin.PrepackagedPlugin, string, error) {
|
|
||||||
manifest, pluginDir, appErr := extractPlugin(pluginFile, tmpDir)
|
manifest, pluginDir, appErr := extractPlugin(pluginFile, tmpDir)
|
||||||
if appErr != nil {
|
if appErr != nil {
|
||||||
return nil, "", errors.Wrapf(appErr, "Failed to extract plugin with path %s", pluginPath.path)
|
return nil, "", errors.Wrapf(appErr, "Failed to extract plugin with path %s", pluginPath.bundlePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin := new(plugin.PrepackagedPlugin)
|
plugin := new(plugin.PrepackagedPlugin)
|
||||||
plugin.Manifest = manifest
|
plugin.Manifest = manifest
|
||||||
plugin.Path = pluginPath.path
|
plugin.Path = pluginPath.bundlePath
|
||||||
|
|
||||||
if pluginPath.signaturePath != "" {
|
if pluginPath.signaturePath != "" {
|
||||||
sig := pluginPath.signaturePath
|
sig := pluginPath.signaturePath
|
||||||
@ -1079,7 +1024,7 @@ func getPrepackagedPlugin(pluginPath *pluginSignaturePath, pluginFile io.ReadSee
|
|||||||
if manifest.IconPath != "" {
|
if manifest.IconPath != "" {
|
||||||
iconData, err := getIcon(filepath.Join(pluginDir, manifest.IconPath))
|
iconData, err := getIcon(filepath.Join(pluginDir, manifest.IconPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mlog.Warn("Error loading local plugin icon", mlog.String("plugin", plugin.Manifest.Id), mlog.String("icon_path", plugin.Manifest.IconPath), mlog.Err(err))
|
ch.srv.Log().Warn("Error loading local plugin icon", mlog.String("plugin_id", plugin.Manifest.Id), mlog.String("icon_path", plugin.Manifest.IconPath), mlog.Err(err))
|
||||||
}
|
}
|
||||||
plugin.IconData = iconData
|
plugin.IconData = iconData
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,6 @@ import (
|
|||||||
"github.com/blang/semver"
|
"github.com/blang/semver"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost/server/public/model"
|
"github.com/mattermost/mattermost/server/public/model"
|
||||||
"github.com/mattermost/mattermost/server/public/plugin"
|
|
||||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||||
"github.com/mattermost/mattermost/server/public/utils"
|
"github.com/mattermost/mattermost/server/public/utils"
|
||||||
"github.com/mattermost/mattermost/server/v8/platform/shared/filestore"
|
"github.com/mattermost/mattermost/server/v8/platform/shared/filestore"
|
||||||
@ -59,88 +58,93 @@ const managedPluginFileName = ".filestore"
|
|||||||
// fileStorePluginFolder is the folder name in the file store of the plugin bundles installed.
|
// fileStorePluginFolder is the folder name in the file store of the plugin bundles installed.
|
||||||
const fileStorePluginFolder = "plugins"
|
const fileStorePluginFolder = "plugins"
|
||||||
|
|
||||||
func (ch *Channels) installPluginFromData(data model.PluginEventData) {
|
// installPluginFromClusterMessage is called when a peer activates a plugin in the filestore,
|
||||||
mlog.Debug("Installing plugin as per cluster message", mlog.String("plugin_id", data.Id))
|
// signalling all other servers to do the same.
|
||||||
|
func (ch *Channels) installPluginFromClusterMessage(pluginID string) {
|
||||||
|
logger := ch.srv.Log().With(mlog.String("plugin_id", pluginID))
|
||||||
|
|
||||||
|
logger.Info("Installing plugin as per cluster message")
|
||||||
|
|
||||||
pluginSignaturePathMap, appErr := ch.getPluginsFromFolder()
|
pluginSignaturePathMap, appErr := ch.getPluginsFromFolder()
|
||||||
if appErr != nil {
|
if appErr != nil {
|
||||||
mlog.Error("Failed to get plugin signatures from filestore. Can't install plugin from data.", mlog.Err(appErr))
|
logger.Error("Failed to get plugin signatures from filestore.", mlog.Err(appErr))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
plugin, ok := pluginSignaturePathMap[data.Id]
|
plugin, ok := pluginSignaturePathMap[pluginID]
|
||||||
if !ok {
|
if !ok {
|
||||||
mlog.Error("Failed to get plugin signature from filestore. Can't install plugin from data.", mlog.String("plugin id", data.Id))
|
logger.Error("Failed to get plugin signature from filestore.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
reader, appErr := ch.srv.fileReader(plugin.path)
|
bundle, appErr := ch.srv.fileReader(plugin.bundlePath)
|
||||||
if appErr != nil {
|
if appErr != nil {
|
||||||
mlog.Error("Failed to open plugin bundle from file store.", mlog.String("bundle", plugin.path), mlog.Err(appErr))
|
logger.Error("Failed to open plugin bundle from file store.", mlog.Err(appErr))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer reader.Close()
|
defer bundle.Close()
|
||||||
|
|
||||||
var signature filestore.ReadCloseSeeker
|
var signature filestore.ReadCloseSeeker
|
||||||
if *ch.cfgSvc.Config().PluginSettings.RequirePluginSignature {
|
if *ch.cfgSvc.Config().PluginSettings.RequirePluginSignature {
|
||||||
signature, appErr = ch.srv.fileReader(plugin.signaturePath)
|
signature, appErr = ch.srv.fileReader(plugin.signaturePath)
|
||||||
if appErr != nil {
|
if appErr != nil {
|
||||||
mlog.Error("Failed to open plugin signature from file store.", mlog.Err(appErr))
|
logger.Error("Failed to open plugin signature from file store.", mlog.Err(appErr))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer signature.Close()
|
defer signature.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
manifest, appErr := ch.installPluginLocally(reader, signature, installPluginLocallyAlways)
|
manifest, appErr := ch.installPluginLocally(bundle, signature, installPluginLocallyAlways)
|
||||||
if appErr != nil {
|
if appErr != nil {
|
||||||
// A log line already appears if the plugin is on the blocklist or skipped
|
// A log line already appears if the plugin is on the blocklist or skipped
|
||||||
if appErr.Id != "app.plugin.blocked.app_error" && appErr.Id != "app.plugin.skip_installation.app_error" {
|
if appErr.Id != "app.plugin.blocked.app_error" && appErr.Id != "app.plugin.skip_installation.app_error" {
|
||||||
mlog.Error("Failed to sync plugin from file store", mlog.String("bundle", plugin.path), mlog.Err(appErr))
|
logger.Error("Failed to sync plugin from file store", mlog.Err(appErr))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ch.notifyPluginEnabled(manifest); err != nil {
|
if err := ch.notifyPluginEnabled(manifest); err != nil {
|
||||||
mlog.Error("Failed notify plugin enabled", mlog.Err(err))
|
logger.Error("Failed notify plugin enabled", mlog.Err(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ch.notifyPluginStatusesChanged(); err != nil {
|
if err := ch.notifyPluginStatusesChanged(); err != nil {
|
||||||
mlog.Error("Failed to notify plugin status changed", mlog.Err(err))
|
logger.Error("Failed to notify plugin status changed", mlog.Err(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ch *Channels) removePluginFromData(data model.PluginEventData) {
|
// removePluginFromClusterMessage is called when a peer removes a plugin, signalling all other
|
||||||
mlog.Debug("Removing plugin as per cluster message", mlog.String("plugin_id", data.Id))
|
// servers to do the same.
|
||||||
|
func (ch *Channels) removePluginFromClusterMessage(pluginID string) {
|
||||||
|
logger := ch.srv.Log().With(mlog.String("plugin_id", pluginID))
|
||||||
|
|
||||||
if err := ch.removePluginLocally(data.Id); err != nil {
|
logger.Info("Removing plugin as per cluster message")
|
||||||
mlog.Warn("Failed to remove plugin locally", mlog.Err(err), mlog.String("id", data.Id))
|
|
||||||
|
if err := ch.removePluginLocally(pluginID); err != nil {
|
||||||
|
logger.Error("Failed to remove plugin locally", mlog.Err(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ch.notifyPluginStatusesChanged(); err != nil {
|
if err := ch.notifyPluginStatusesChanged(); err != nil {
|
||||||
mlog.Warn("failed to notify plugin status changed", mlog.Err(err))
|
logger.Error("failed to notify plugin status changed", mlog.Err(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// InstallPluginWithSignature verifies and installs plugin.
|
// InstallPlugin unpacks and installs a plugin but does not enable or activate it unless the the
|
||||||
func (ch *Channels) installPluginWithSignature(pluginFile, signature io.ReadSeeker) (*model.Manifest, *model.AppError) {
|
// plugin was already enabled.
|
||||||
return ch.installPlugin(pluginFile, signature, installPluginLocallyAlways)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InstallPlugin unpacks and installs a plugin but does not enable or activate it.
|
|
||||||
func (a *App) InstallPlugin(pluginFile io.ReadSeeker, replace bool) (*model.Manifest, *model.AppError) {
|
func (a *App) InstallPlugin(pluginFile io.ReadSeeker, replace bool) (*model.Manifest, *model.AppError) {
|
||||||
installationStrategy := installPluginLocallyOnlyIfNew
|
installationStrategy := installPluginLocallyOnlyIfNew
|
||||||
if replace {
|
if replace {
|
||||||
installationStrategy = installPluginLocallyAlways
|
installationStrategy = installPluginLocallyAlways
|
||||||
}
|
}
|
||||||
|
|
||||||
return a.installPlugin(pluginFile, nil, installationStrategy)
|
return a.ch.installPlugin(pluginFile, nil, installationStrategy)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) installPlugin(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) {
|
// installPlugin extracts and installs the given plugin bundle (optionally signed) for the
|
||||||
return a.ch.installPlugin(pluginFile, signature, installationStrategy)
|
// current server, activating the plugin if already enabled, installs it to the filestore for
|
||||||
}
|
// cluster peers to use, and then broadcasts the change to connected websockets.
|
||||||
|
//
|
||||||
func (ch *Channels) installPlugin(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) {
|
// The given installation strategy decides how to handle upgrade scenarios.
|
||||||
manifest, appErr := ch.installPluginLocally(pluginFile, signature, installationStrategy)
|
func (ch *Channels) installPlugin(bundle, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) {
|
||||||
|
manifest, appErr := ch.installPluginLocally(bundle, signature, installationStrategy)
|
||||||
if appErr != nil {
|
if appErr != nil {
|
||||||
return nil, appErr
|
return nil, appErr
|
||||||
}
|
}
|
||||||
@ -149,17 +153,42 @@ func (ch *Channels) installPlugin(pluginFile, signature io.ReadSeeker, installat
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger := ch.srv.Log().With(mlog.String("plugin_id", manifest.Id))
|
||||||
|
|
||||||
|
appErr = ch.installPluginToFilestore(manifest, bundle, signature)
|
||||||
|
if appErr != nil {
|
||||||
|
return nil, appErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ch.notifyPluginEnabled(manifest); err != nil {
|
||||||
|
logger.Warn("Failed notify plugin enabled", mlog.Err(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ch.notifyPluginStatusesChanged(); err != nil {
|
||||||
|
logger.Warn("Failed to notify plugin status changed", mlog.Err(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return manifest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// installPluginToFilestore saves the given plugin bundle (optionally signed) to the filestore,
|
||||||
|
// notifying cluster peers accordingly.
|
||||||
|
func (ch *Channels) installPluginToFilestore(manifest *model.Manifest, bundle, signature io.ReadSeeker) *model.AppError {
|
||||||
if signature != nil {
|
if signature != nil {
|
||||||
signature.Seek(0, 0)
|
_, err := signature.Seek(0, 0)
|
||||||
if _, appErr = ch.srv.writeFile(signature, getSignatureStorePath(manifest.Id)); appErr != nil {
|
if err != nil {
|
||||||
return nil, model.NewAppError("saveSignature", "app.plugin.store_signature.app_error", nil, "", http.StatusInternalServerError).Wrap(appErr)
|
return model.NewAppError("saveSignature", "app.plugin.store_signature.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, appErr := ch.srv.writeFile(signature, getSignatureStorePath(manifest.Id)); appErr != nil {
|
||||||
|
return model.NewAppError("saveSignature", "app.plugin.store_signature.app_error", nil, "", http.StatusInternalServerError).Wrap(appErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store bundle in the file store to allow access from other servers.
|
// Store bundle in the file store to allow access from other servers.
|
||||||
pluginFile.Seek(0, 0)
|
bundle.Seek(0, 0)
|
||||||
if _, appErr := ch.srv.writeFile(pluginFile, getBundleStorePath(manifest.Id)); appErr != nil {
|
if _, appErr := ch.srv.writeFile(bundle, getBundleStorePath(manifest.Id)); appErr != nil {
|
||||||
return nil, model.NewAppError("uploadPlugin", "app.plugin.store_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(appErr)
|
return model.NewAppError("uploadPlugin", "app.plugin.store_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(appErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
ch.notifyClusterPluginEvent(
|
ch.notifyClusterPluginEvent(
|
||||||
@ -169,20 +198,15 @@ func (ch *Channels) installPlugin(pluginFile, signature io.ReadSeeker, installat
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := ch.notifyPluginEnabled(manifest); err != nil {
|
return nil
|
||||||
mlog.Warn("Failed notify plugin enabled", mlog.Err(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ch.notifyPluginStatusesChanged(); err != nil {
|
|
||||||
mlog.Warn("Failed to notify plugin status changed", mlog.Err(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return manifest, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// InstallMarketplacePlugin installs a plugin listed in the marketplace server. It will get the plugin bundle
|
// InstallMarketplacePlugin installs a plugin listed in the marketplace server. It will get the
|
||||||
// from the prepackaged folder, if available, or remotely if EnableRemoteMarketplace is true.
|
// plugin bundle from the prepackaged folder, if available, or remotely if EnableRemoteMarketplace
|
||||||
|
// is true.
|
||||||
func (ch *Channels) InstallMarketplacePlugin(request *model.InstallMarketplacePluginRequest) (*model.Manifest, *model.AppError) {
|
func (ch *Channels) InstallMarketplacePlugin(request *model.InstallMarketplacePluginRequest) (*model.Manifest, *model.AppError) {
|
||||||
|
logger := ch.srv.Log().With(mlog.String("plugin_id", request.Id))
|
||||||
|
|
||||||
var pluginFile, signatureFile io.ReadSeeker
|
var pluginFile, signatureFile io.ReadSeeker
|
||||||
|
|
||||||
prepackagedPlugin, appErr := ch.getPrepackagedPlugin(request.Id, request.Version)
|
prepackagedPlugin, appErr := ch.getPrepackagedPlugin(request.Id, request.Version)
|
||||||
@ -205,7 +229,7 @@ func (ch *Channels) InstallMarketplacePlugin(request *model.InstallMarketplacePl
|
|||||||
plugin, appErr = ch.getRemoteMarketplacePlugin(request.Id, request.Version)
|
plugin, appErr = ch.getRemoteMarketplacePlugin(request.Id, request.Version)
|
||||||
// The plugin might only be prepackaged and not on the Marketplace.
|
// The plugin might only be prepackaged and not on the Marketplace.
|
||||||
if appErr != nil && appErr.Id != "app.plugin.marketplace_plugins.not_found.app_error" {
|
if appErr != nil && appErr.Id != "app.plugin.marketplace_plugins.not_found.app_error" {
|
||||||
mlog.Warn("Failed to reach Marketplace to install plugin", mlog.String("plugin_id", request.Id), mlog.Err(appErr))
|
logger.Warn("Failed to reach Marketplace to install plugin", mlog.Err(appErr))
|
||||||
}
|
}
|
||||||
|
|
||||||
if plugin != nil {
|
if plugin != nil {
|
||||||
@ -245,7 +269,7 @@ func (ch *Channels) InstallMarketplacePlugin(request *model.InstallMarketplacePl
|
|||||||
return nil, model.NewAppError("InstallMarketplacePlugin", "app.plugin.marketplace_plugins.signature_not_found.app_error", nil, "", http.StatusInternalServerError)
|
return nil, model.NewAppError("InstallMarketplacePlugin", "app.plugin.marketplace_plugins.signature_not_found.app_error", nil, "", http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
manifest, appErr := ch.installPluginWithSignature(pluginFile, signatureFile)
|
manifest, appErr := ch.installPlugin(pluginFile, signatureFile, installPluginLocallyAlways)
|
||||||
if appErr != nil {
|
if appErr != nil {
|
||||||
return nil, appErr
|
return nil, appErr
|
||||||
}
|
}
|
||||||
@ -264,7 +288,11 @@ const (
|
|||||||
installPluginLocallyAlways
|
installPluginLocallyAlways
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ch *Channels) installPluginLocally(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) {
|
// installPluginLocally extracts and installs the given plugin bundle (optionally signed) for the
|
||||||
|
// current server, activating the plugin if already enabled.
|
||||||
|
//
|
||||||
|
// The given installation strategy decides how to handle upgrade scenarios.
|
||||||
|
func (ch *Channels) installPluginLocally(bundle, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) {
|
||||||
pluginsEnvironment := ch.GetPluginsEnvironment()
|
pluginsEnvironment := ch.GetPluginsEnvironment()
|
||||||
if pluginsEnvironment == nil {
|
if pluginsEnvironment == nil {
|
||||||
return nil, model.NewAppError("installPluginLocally", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
|
return nil, model.NewAppError("installPluginLocally", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
|
||||||
@ -272,7 +300,7 @@ func (ch *Channels) installPluginLocally(pluginFile, signature io.ReadSeeker, in
|
|||||||
|
|
||||||
// verify signature
|
// verify signature
|
||||||
if signature != nil {
|
if signature != nil {
|
||||||
if err := ch.verifyPlugin(pluginFile, signature); err != nil {
|
if err := ch.verifyPlugin(bundle, signature); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -283,7 +311,7 @@ func (ch *Channels) installPluginLocally(pluginFile, signature io.ReadSeeker, in
|
|||||||
}
|
}
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
manifest, pluginDir, appErr := extractPlugin(pluginFile, tmpDir)
|
manifest, pluginDir, appErr := extractPlugin(bundle, tmpDir)
|
||||||
if appErr != nil {
|
if appErr != nil {
|
||||||
return nil, appErr
|
return nil, appErr
|
||||||
}
|
}
|
||||||
@ -296,9 +324,10 @@ func (ch *Channels) installPluginLocally(pluginFile, signature io.ReadSeeker, in
|
|||||||
return manifest, nil
|
return manifest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractPlugin(pluginFile io.ReadSeeker, extractDir string) (*model.Manifest, string, *model.AppError) {
|
// extractPlugin unpacks the given plugin bundle into the specified directory.
|
||||||
pluginFile.Seek(0, 0)
|
func extractPlugin(bundle io.ReadSeeker, extractDir string) (*model.Manifest, string, *model.AppError) {
|
||||||
if err := extractTarGz(pluginFile, extractDir); err != nil {
|
bundle.Seek(0, 0)
|
||||||
|
if err := extractTarGz(bundle, extractDir); err != nil {
|
||||||
return nil, "", model.NewAppError("extractPlugin", "app.plugin.extract.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
return nil, "", model.NewAppError("extractPlugin", "app.plugin.extract.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,6 +336,8 @@ func extractPlugin(pluginFile io.ReadSeeker, extractDir string) (*model.Manifest
|
|||||||
return nil, "", model.NewAppError("extractPlugin", "app.plugin.filesystem.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
return nil, "", model.NewAppError("extractPlugin", "app.plugin.filesystem.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the root of the plugin bundle consists of exactly one directory, assume the plugin
|
||||||
|
// is contained therein. Otherwise the root directory is expected to contain the plugin.
|
||||||
if len(dir) == 1 && dir[0].IsDir() {
|
if len(dir) == 1 && dir[0].IsDir() {
|
||||||
extractDir = filepath.Join(extractDir, dir[0].Name())
|
extractDir = filepath.Join(extractDir, dir[0].Name())
|
||||||
}
|
}
|
||||||
@ -317,13 +348,21 @@ func extractPlugin(pluginFile io.ReadSeeker, extractDir string) (*model.Manifest
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !model.IsValidPluginId(manifest.Id) {
|
if !model.IsValidPluginId(manifest.Id) {
|
||||||
return nil, "", model.NewAppError("installPluginLocally", "app.plugin.invalid_id.app_error", map[string]any{"Min": model.MinIdLength, "Max": model.MaxIdLength, "Regex": model.ValidIdRegex}, "", http.StatusBadRequest)
|
return nil, "", model.NewAppError("extractPlugin", "app.plugin.invalid_id.app_error", map[string]any{"Min": model.MinIdLength, "Max": model.MaxIdLength, "Regex": model.ValidIdRegex}, "", http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
return manifest, extractDir, nil
|
return manifest, extractDir, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// installExtractedPlugin installs a plugin previously extracted to a temporary directory,
|
||||||
|
// activating the plugin automatically if already enabled by the server configuration.
|
||||||
|
//
|
||||||
|
// The given installation strategy decides how to handle upgrade scenarios.
|
||||||
func (ch *Channels) installExtractedPlugin(manifest *model.Manifest, fromPluginDir string, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) {
|
func (ch *Channels) installExtractedPlugin(manifest *model.Manifest, fromPluginDir string, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) {
|
||||||
|
logger := ch.srv.Log().With(mlog.String("plugin_id", manifest.Id))
|
||||||
|
|
||||||
|
logger.Info("Installing extracted plugin", mlog.String("version", manifest.Version))
|
||||||
|
|
||||||
pluginsEnvironment := ch.GetPluginsEnvironment()
|
pluginsEnvironment := ch.GetPluginsEnvironment()
|
||||||
if pluginsEnvironment == nil {
|
if pluginsEnvironment == nil {
|
||||||
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
|
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
|
||||||
@ -334,12 +373,6 @@ func (ch *Channels) installExtractedPlugin(manifest *model.Manifest, fromPluginD
|
|||||||
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.install.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.install.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check plugin id is not blocked
|
|
||||||
if plugin.PluginIDIsBlocked(manifest.Id) {
|
|
||||||
mlog.Debug("Skipping installation of plugin since plugin is on blocklist. Some plugins are blocked because they are built into this version of Mattermost.", mlog.String("plugin_id", manifest.Id))
|
|
||||||
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.blocked.app_error", map[string]any{"Id": manifest.Id}, "", http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for plugins installed with the same ID.
|
// Check for plugins installed with the same ID.
|
||||||
var existingManifest *model.Manifest
|
var existingManifest *model.Manifest
|
||||||
for _, bundle := range bundles {
|
for _, bundle := range bundles {
|
||||||
@ -366,30 +399,30 @@ func (ch *Channels) installExtractedPlugin(manifest *model.Manifest, fromPluginD
|
|||||||
|
|
||||||
existingVersion, err = semver.Parse(existingManifest.Version)
|
existingVersion, err = semver.Parse(existingManifest.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.invalid_version.app_error", nil, "", http.StatusBadRequest)
|
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.invalid_version.app_error", nil, "", http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
if version.LTE(existingVersion) {
|
if version.LTE(existingVersion) {
|
||||||
mlog.Debug("Skipping local installation of plugin since existing version is newer", mlog.String("plugin_id", manifest.Id))
|
logger.Warn("Skipping local installation of plugin since existing version is newer", mlog.String("version", version.String()), mlog.String("existing_version", existingVersion.String()))
|
||||||
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.skip_installation.app_error", map[string]any{"Id": manifest.Id}, "", http.StatusInternalServerError)
|
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.skip_installation.app_error", map[string]any{"Id": manifest.Id}, "", http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise remove the existing installation prior to install below.
|
// Otherwise remove the existing installation prior to installing below.
|
||||||
mlog.Debug("Removing existing installation of plugin before local install", mlog.String("plugin_id", existingManifest.Id), mlog.String("version", existingManifest.Version))
|
logger.Info("Removing existing installation of plugin before local install", mlog.String("existing_version", existingManifest.Version))
|
||||||
if err := ch.removePluginLocally(existingManifest.Id); err != nil {
|
if err := ch.removePluginLocally(existingManifest.Id); err != nil {
|
||||||
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.install_id_failed_remove.app_error", nil, "", http.StatusBadRequest)
|
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.install_id_failed_remove.app_error", nil, "", http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginPath := filepath.Join(*ch.cfgSvc.Config().PluginSettings.Directory, manifest.Id)
|
bundlePath := filepath.Join(*ch.cfgSvc.Config().PluginSettings.Directory, manifest.Id)
|
||||||
err = utils.CopyDir(fromPluginDir, pluginPath)
|
err = utils.CopyDir(fromPluginDir, bundlePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.mvdir.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.mvdir.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flag plugin locally as managed by the filestore.
|
// Flag plugin locally as managed by the filestore.
|
||||||
f, err := os.Create(filepath.Join(pluginPath, managedPluginFileName))
|
f, err := os.Create(filepath.Join(bundlePath, managedPluginFileName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.flag_managed.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
return nil, model.NewAppError("installExtractedPlugin", "app.plugin.flag_managed.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||||
}
|
}
|
||||||
@ -419,12 +452,13 @@ func (ch *Channels) installExtractedPlugin(manifest *model.Manifest, fromPluginD
|
|||||||
manifest = updatedManifest
|
manifest = updatedManifest
|
||||||
}
|
}
|
||||||
|
|
||||||
mlog.Debug("Installing plugin", mlog.String("plugin_id", manifest.Id), mlog.String("version", manifest.Version))
|
|
||||||
|
|
||||||
return manifest, nil
|
return manifest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemovePlugin removes a plugin from all servers.
|
||||||
func (ch *Channels) RemovePlugin(id string) *model.AppError {
|
func (ch *Channels) RemovePlugin(id string) *model.AppError {
|
||||||
|
logger := ch.srv.Log().With(mlog.String("plugin_id", id))
|
||||||
|
|
||||||
// Disable plugin before removal to make sure this
|
// Disable plugin before removal to make sure this
|
||||||
// plugin remains disabled on re-install.
|
// plugin remains disabled on re-install.
|
||||||
if err := ch.disablePlugin(id); err != nil {
|
if err := ch.disablePlugin(id); err != nil {
|
||||||
@ -436,19 +470,19 @@ func (ch *Channels) RemovePlugin(id string) *model.AppError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove bundle from the file store.
|
// Remove bundle from the file store.
|
||||||
storePluginFileName := getBundleStorePath(id)
|
bundlePath := getBundleStorePath(id)
|
||||||
bundleExist, err := ch.srv.fileExists(storePluginFileName)
|
bundleExists, err := ch.srv.fileExists(bundlePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return model.NewAppError("removePlugin", "app.plugin.remove_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
return model.NewAppError("removePlugin", "app.plugin.remove_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||||
}
|
}
|
||||||
if !bundleExist {
|
if !bundleExists {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err = ch.srv.removeFile(storePluginFileName); err != nil {
|
if err := ch.srv.removeFile(bundlePath); err != nil {
|
||||||
return model.NewAppError("removePlugin", "app.plugin.remove_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
return model.NewAppError("removePlugin", "app.plugin.remove_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||||
}
|
}
|
||||||
if err = ch.removeSignature(id); err != nil {
|
if err := ch.removeSignature(id); err != nil {
|
||||||
mlog.Warn("Can't remove signature", mlog.Err(err))
|
logger.Warn("Can't remove signature", mlog.Err(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
ch.notifyClusterPluginEvent(
|
ch.notifyClusterPluginEvent(
|
||||||
@ -459,12 +493,13 @@ func (ch *Channels) RemovePlugin(id string) *model.AppError {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if err := ch.notifyPluginStatusesChanged(); err != nil {
|
if err := ch.notifyPluginStatusesChanged(); err != nil {
|
||||||
mlog.Warn("Failed to notify plugin status changed", mlog.Err(err))
|
logger.Warn("Failed to notify plugin status changed", mlog.Err(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// removePluginLocally removes the given plugin from the current server.
|
||||||
func (ch *Channels) removePluginLocally(id string) *model.AppError {
|
func (ch *Channels) removePluginLocally(id string) *model.AppError {
|
||||||
pluginsEnvironment := ch.GetPluginsEnvironment()
|
pluginsEnvironment := ch.GetPluginsEnvironment()
|
||||||
if pluginsEnvironment == nil {
|
if pluginsEnvironment == nil {
|
||||||
@ -477,11 +512,11 @@ func (ch *Channels) removePluginLocally(id string) *model.AppError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var manifest *model.Manifest
|
var manifest *model.Manifest
|
||||||
var pluginPath string
|
var unpackedBundlePath string
|
||||||
for _, p := range plugins {
|
for _, p := range plugins {
|
||||||
if p.Manifest != nil && p.Manifest.Id == id {
|
if p.Manifest != nil && p.Manifest.Id == id {
|
||||||
manifest = p.Manifest
|
manifest = p.Manifest
|
||||||
pluginPath = filepath.Dir(p.ManifestPath)
|
unpackedBundlePath = filepath.Dir(p.ManifestPath)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -494,33 +529,39 @@ func (ch *Channels) removePluginLocally(id string) *model.AppError {
|
|||||||
pluginsEnvironment.RemovePlugin(id)
|
pluginsEnvironment.RemovePlugin(id)
|
||||||
ch.unregisterPluginCommands(id)
|
ch.unregisterPluginCommands(id)
|
||||||
|
|
||||||
if err := os.RemoveAll(pluginPath); err != nil {
|
if err := os.RemoveAll(unpackedBundlePath); err != nil {
|
||||||
return model.NewAppError("removePlugin", "app.plugin.remove.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
return model.NewAppError("removePlugin", "app.plugin.remove.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// removeSignature removes the signature file installed alongside the plugin.
|
||||||
func (ch *Channels) removeSignature(pluginID string) *model.AppError {
|
func (ch *Channels) removeSignature(pluginID string) *model.AppError {
|
||||||
filePath := getSignatureStorePath(pluginID)
|
logger := ch.srv.Log().With(mlog.String("plugin_id", pluginID))
|
||||||
exists, err := ch.srv.fileExists(filePath)
|
|
||||||
|
signaturePath := getSignatureStorePath(pluginID)
|
||||||
|
exists, err := ch.srv.fileExists(signaturePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return model.NewAppError("removeSignature", "app.plugin.remove_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
return model.NewAppError("removeSignature", "app.plugin.remove_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||||
}
|
}
|
||||||
if !exists {
|
if !exists {
|
||||||
mlog.Debug("no plugin signature to remove", mlog.String("plugin_id", pluginID))
|
logger.Debug("no plugin signature to remove")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err = ch.srv.removeFile(filePath); err != nil {
|
if err = ch.srv.removeFile(signaturePath); err != nil {
|
||||||
return model.NewAppError("removeSignature", "app.plugin.remove_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
return model.NewAppError("removeSignature", "app.plugin.remove_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getBundleStorePath maps the given plugin id to the file path of the corresponding plugin bundle.
|
||||||
func getBundleStorePath(id string) string {
|
func getBundleStorePath(id string) string {
|
||||||
return filepath.Join(fileStorePluginFolder, fmt.Sprintf("%s.tar.gz", id))
|
return filepath.Join(fileStorePluginFolder, fmt.Sprintf("%s.tar.gz", id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getSignatureStorePath maps the given plugin id to the file path of the corresponding plugin
|
||||||
|
// signature, if one exists.
|
||||||
func getSignatureStorePath(id string) string {
|
func getSignatureStorePath(id string) string {
|
||||||
return filepath.Join(fileStorePluginFolder, fmt.Sprintf("%s.tar.gz.sig", id))
|
return filepath.Join(fileStorePluginFolder, fmt.Sprintf("%s.tar.gz.sig", id))
|
||||||
}
|
}
|
||||||
|
@ -167,17 +167,6 @@ func TestInstallPluginLocally(t *testing.T) {
|
|||||||
assertBundleInfoManifests(t, th, []*model.Manifest{manifest})
|
assertBundleInfoManifests(t, th, []*model.Manifest{manifest})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("doesn't install if ID on block list", func(t *testing.T) {
|
|
||||||
th := Setup(t)
|
|
||||||
defer th.TearDown()
|
|
||||||
cleanExistingBundles(t, th)
|
|
||||||
|
|
||||||
_, appErr := installPlugin(t, th, "com.mattermost.plugin-incident-response", "0.0.1", installPluginLocallyAlways)
|
|
||||||
require.NotNil(t, appErr)
|
|
||||||
require.Equal(t, "app.plugin.blocked.app_error", appErr.Id)
|
|
||||||
assertBundleInfoManifests(t, th, []*model.Manifest{})
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("different plugin already installed", func(t *testing.T) {
|
t.Run("different plugin already installed", func(t *testing.T) {
|
||||||
th := Setup(t)
|
th := Setup(t)
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
@ -6023,10 +6023,6 @@
|
|||||||
"id": "app.oauth.update_app.updating.app_error",
|
"id": "app.oauth.update_app.updating.app_error",
|
||||||
"translation": "We encountered an error updating the app."
|
"translation": "We encountered an error updating the app."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "app.plugin.blocked.app_error",
|
|
||||||
"translation": "Plugin {{.Id}} is on the block list. Some plugins are blocked because they are built into this version of Mattermost."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "app.plugin.cluster.save_config.app_error",
|
"id": "app.plugin.cluster.save_config.app_error",
|
||||||
"translation": "The plugin configuration in your config.json file must be updated manually when using ReadOnlyConfig with clustering enabled."
|
"translation": "The plugin configuration in your config.json file must be updated manually when using ReadOnlyConfig with clustering enabled."
|
||||||
|
@ -22,12 +22,6 @@ type FeatureFlags struct {
|
|||||||
// AppsEnabled toggles the Apps framework functionalities both in server and client side
|
// AppsEnabled toggles the Apps framework functionalities both in server and client side
|
||||||
AppsEnabled bool
|
AppsEnabled bool
|
||||||
|
|
||||||
// Feature flags to control plugin versions
|
|
||||||
PluginPlaybooks string `plugin_id:"playbooks"`
|
|
||||||
PluginApps string `plugin_id:"com.mattermost.apps"`
|
|
||||||
PluginFocalboard string `plugin_id:"focalboard"`
|
|
||||||
PluginCalls string `plugin_id:"com.mattermost.calls"`
|
|
||||||
|
|
||||||
PermalinkPreviews bool
|
PermalinkPreviews bool
|
||||||
|
|
||||||
// CallsEnabled controls whether or not the Calls plugin should be enabled
|
// CallsEnabled controls whether or not the Calls plugin should be enabled
|
||||||
@ -64,8 +58,6 @@ func (f *FeatureFlags) SetDefaults() {
|
|||||||
f.TestBoolFeature = false
|
f.TestBoolFeature = false
|
||||||
f.EnableRemoteClusterService = false
|
f.EnableRemoteClusterService = false
|
||||||
f.AppsEnabled = true
|
f.AppsEnabled = true
|
||||||
f.PluginApps = ""
|
|
||||||
f.PluginFocalboard = ""
|
|
||||||
f.BoardsDataRetention = false
|
f.BoardsDataRetention = false
|
||||||
f.NormalizeLdapDNs = false
|
f.NormalizeLdapDNs = false
|
||||||
f.GraphQL = false
|
f.GraphQL = false
|
||||||
@ -79,26 +71,6 @@ func (f *FeatureFlags) SetDefaults() {
|
|||||||
f.DataRetentionConcurrencyEnabled = true
|
f.DataRetentionConcurrencyEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FeatureFlags) Plugins() map[string]string {
|
|
||||||
rFFVal := reflect.ValueOf(f).Elem()
|
|
||||||
rFFType := reflect.TypeOf(f).Elem()
|
|
||||||
|
|
||||||
pluginVersions := make(map[string]string)
|
|
||||||
for i := 0; i < rFFVal.NumField(); i++ {
|
|
||||||
rFieldVal := rFFVal.Field(i)
|
|
||||||
rFieldType := rFFType.Field(i)
|
|
||||||
|
|
||||||
pluginId, hasPluginId := rFieldType.Tag.Lookup("plugin_id")
|
|
||||||
if !hasPluginId {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
pluginVersions[pluginId] = rFieldVal.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return pluginVersions
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToMap returns the feature flags as a map[string]string
|
// ToMap returns the feature flags as a map[string]string
|
||||||
// Supports boolean and string feature flags.
|
// Supports boolean and string feature flags.
|
||||||
func (f *FeatureFlags) ToMap() map[string]string {
|
func (f *FeatureFlags) ToMap() map[string]string {
|
||||||
|
@ -103,34 +103,9 @@ func scanSearchPath(path string) ([]*model.BundleInfo, error) {
|
|||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var pluginIDBlocklist = map[string]bool{
|
|
||||||
"com.mattermost.plugin-incident-response": true,
|
|
||||||
"com.mattermost.plugin-incident-management": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
func PluginIDIsBlocked(id string) bool {
|
|
||||||
_, ok := pluginIDBlocklist[id]
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a list of all plugins within the environment.
|
// Returns a list of all plugins within the environment.
|
||||||
func (env *Environment) Available() ([]*model.BundleInfo, error) {
|
func (env *Environment) Available() ([]*model.BundleInfo, error) {
|
||||||
rawList, err := scanSearchPath(env.pluginDir)
|
return scanSearchPath(env.pluginDir)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter any plugins that match the blocklist
|
|
||||||
filteredList := make([]*model.BundleInfo, 0, len(rawList))
|
|
||||||
for _, bundleInfo := range rawList {
|
|
||||||
if PluginIDIsBlocked(bundleInfo.Manifest.Id) {
|
|
||||||
env.logger.Debug("Plugin ignored by blocklist", mlog.String("plugin_id", bundleInfo.Manifest.Id))
|
|
||||||
} else {
|
|
||||||
filteredList = append(filteredList, bundleInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return filteredList, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a list of prepackaged plugins available in the local prepackaged_plugins folder.
|
// Returns a list of prepackaged plugins available in the local prepackaged_plugins folder.
|
||||||
|
Loading…
Reference in New Issue
Block a user