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
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
0e30d0abb8
Коммит
8372267739
@@ -30,11 +30,14 @@ import (
|
||||
"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"
|
||||
|
||||
// pluginSignaturePath tracks the path to the plugin bundle and signature for the given plugin.
|
||||
type pluginSignaturePath struct {
|
||||
pluginID string
|
||||
path string
|
||||
bundlePath string
|
||||
signaturePath string
|
||||
}
|
||||
|
||||
@@ -161,16 +164,18 @@ func (ch *Channels) syncPluginsActiveState() {
|
||||
defer wg.Done()
|
||||
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
if activated {
|
||||
// Notify all cluster clients if ready
|
||||
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)
|
||||
@@ -181,7 +186,7 @@ func (ch *Channels) syncPluginsActiveState() {
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -235,7 +240,7 @@ func (ch *Channels) initPlugins(c *request.Context, pluginDir, webappPluginDir s
|
||||
ch.srv.GetMetrics(),
|
||||
)
|
||||
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
|
||||
}
|
||||
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)
|
||||
|
||||
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)
|
||||
pluginsEnvironment = ch.GetPluginsEnvironment()
|
||||
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
|
||||
}
|
||||
pluginsEnvironment.SetPrepackagedPlugins(plugins)
|
||||
|
||||
ch.installFeatureFlagPlugins()
|
||||
|
||||
// Sync plugin active state when config changes. Also notify plugins.
|
||||
ch.pluginsLock.Lock()
|
||||
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.
|
||||
// Because (*App).InitPlugins is already run as a config change hook.
|
||||
if *old.PluginSettings.Enable == *new.PluginSettings.Enable {
|
||||
ch.installFeatureFlagPlugins()
|
||||
ch.syncPluginsActiveState()
|
||||
}
|
||||
|
||||
@@ -290,7 +292,7 @@ func (a *App) SyncPlugins() *model.AppError {
|
||||
// SyncPlugins synchronizes the plugins installed locally
|
||||
// with the plugin bundles available in the file store.
|
||||
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()
|
||||
if pluginsEnvironment == nil {
|
||||
@@ -307,16 +309,19 @@ func (ch *Channels) syncPlugins() *model.AppError {
|
||||
wg.Add(1)
|
||||
go func(pluginID string) {
|
||||
defer wg.Done()
|
||||
|
||||
logger := ch.srv.Log().With(mlog.String("plugin_id", pluginID))
|
||||
|
||||
// Only handle managed plugins with .filestore flag file.
|
||||
_, err := os.Stat(filepath.Join(*ch.cfgSvc.Config().PluginSettings.Directory, pluginID, managedPluginFileName))
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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)
|
||||
@@ -333,26 +338,28 @@ func (ch *Channels) syncPlugins() *model.AppError {
|
||||
wg.Add(1)
|
||||
go func(plugin *pluginSignaturePath) {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
defer reader.Close()
|
||||
defer bundle.Close()
|
||||
|
||||
var signature filestore.ReadCloseSeeker
|
||||
if *ch.cfgSvc.Config().PluginSettings.RequirePluginSignature {
|
||||
signature, appErr = ch.srv.fileReader(plugin.signaturePath)
|
||||
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
|
||||
}
|
||||
defer signature.Close()
|
||||
}
|
||||
|
||||
mlog.Info("Syncing plugin from file store", mlog.String("bundle", plugin.path))
|
||||
if _, err := ch.installPluginLocally(reader, signature, installPluginLocallyAlways); err != nil && err.Id != "app.plugin.blocked.app_error" && 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.Info("Syncing plugin from file store")
|
||||
if _, err := ch.installPluginLocally(bundle, signature, installPluginLocallyAlways); err != nil && err.Id != "app.plugin.skip_installation.app_error" {
|
||||
logger.Error("Failed to sync plugin from file store", mlog.Err(err))
|
||||
}
|
||||
}(plugin)
|
||||
}
|
||||
@@ -370,7 +377,7 @@ func (ch *Channels) ShutDownPlugins() {
|
||||
return
|
||||
}
|
||||
|
||||
mlog.Info("Shutting down plugins")
|
||||
ch.srv.Log().Info("Shutting down plugins")
|
||||
|
||||
pluginsEnvironment.Shutdown()
|
||||
|
||||
@@ -383,7 +390,7 @@ func (ch *Channels) ShutDownPlugins() {
|
||||
if ch.pluginsEnvironment == pluginsEnvironment {
|
||||
ch.pluginsEnvironment = nil
|
||||
} 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 != "" {
|
||||
iconData, err = getIcon(filepath.Join(plugin.Path, plugin.Manifest.IconPath))
|
||||
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.
|
||||
for _, status := range statuses {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -879,7 +886,7 @@ func (ch *Channels) getPluginsFromFilePaths(fileStorePaths []string) map[string]
|
||||
id := strings.TrimSuffix(filepath.Base(path), ".tar.gz")
|
||||
helper := &pluginSignaturePath{
|
||||
pluginID: id,
|
||||
path: path,
|
||||
bundlePath: path,
|
||||
signaturePath: "",
|
||||
}
|
||||
pluginSignaturePathMap[id] = helper
|
||||
@@ -911,7 +918,7 @@ func (ch *Channels) processPrepackagedPlugins(pluginsDir string) []*plugin.Prepa
|
||||
return 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
|
||||
}
|
||||
|
||||
@@ -927,11 +934,10 @@ func (ch *Channels) processPrepackagedPlugins(pluginsDir string) []*plugin.Prepa
|
||||
p, err := ch.processPrepackagedPlugin(psPath)
|
||||
if err != nil {
|
||||
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.blocked.app_error" || appErr.Id == "app.plugin.skip_installation.app_error") {
|
||||
if errors.As(err, &appErr) && appErr.Id == "app.plugin.skip_installation.app_error" {
|
||||
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
|
||||
}
|
||||
prepackagedPlugins <- p
|
||||
@@ -951,11 +957,13 @@ func (ch *Channels) processPrepackagedPlugins(pluginsDir string) []*plugin.Prepa
|
||||
// processPrepackagedPlugin will return the prepackaged plugin metadata and will also
|
||||
// install the prepackaged plugin if it had been previously enabled and AutomaticPrepackagedPlugins is true.
|
||||
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 {
|
||||
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()
|
||||
|
||||
@@ -965,9 +973,9 @@ func (ch *Channels) processPrepackagedPlugin(pluginPath *pluginSignaturePath) (*
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
plugin, pluginDir, err := getPrepackagedPlugin(pluginPath, fileReader, tmpDir)
|
||||
plugin, pluginDir, err := ch.buildPrepackagedPlugin(pluginPath, fileReader, tmpDir)
|
||||
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
|
||||
@@ -981,87 +989,24 @@ func (ch *Channels) processPrepackagedPlugin(pluginPath *pluginSignaturePath) (*
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
// installFeatureFlagPlugins handles the automatic installation/upgrade of plugins from feature flags
|
||||
func (ch *Channels) installFeatureFlagPlugins() {
|
||||
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) {
|
||||
// buildPrepackagedPlugin builds a PrepackagedPlugin from the plugin at the given path, additionally returning the directory in which it was extracted.
|
||||
func (ch *Channels) buildPrepackagedPlugin(pluginPath *pluginSignaturePath, pluginFile io.ReadSeeker, tmpDir string) (*plugin.PrepackagedPlugin, string, error) {
|
||||
manifest, pluginDir, appErr := extractPlugin(pluginFile, tmpDir)
|
||||
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.Manifest = manifest
|
||||
plugin.Path = pluginPath.path
|
||||
plugin.Path = pluginPath.bundlePath
|
||||
|
||||
if pluginPath.signaturePath != "" {
|
||||
sig := pluginPath.signaturePath
|
||||
@@ -1079,7 +1024,7 @@ func getPrepackagedPlugin(pluginPath *pluginSignaturePath, pluginFile io.ReadSee
|
||||
if manifest.IconPath != "" {
|
||||
iconData, err := getIcon(filepath.Join(pluginDir, manifest.IconPath))
|
||||
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
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user