[MM-48626] Move plugins environment out of Channels (#21730)

Этот коммит содержится в:
Ibrahim Serdar Acikgoz
2022-12-21 16:11:57 +03:00
коммит произвёл GitHub
родитель 0ce7c5e3da
Коммит dcf499b51d
40 изменённых файлов: 537 добавлений и 447 удалений

Просмотреть файл

@@ -20,16 +20,37 @@ import (
svg "github.com/h2non/go-is-svg"
"github.com/pkg/errors"
"github.com/mattermost/mattermost-server/v6/app/platform"
"github.com/mattermost/mattermost-server/v6/app/request"
"github.com/mattermost/mattermost-server/v6/config"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/plugin"
"github.com/mattermost/mattermost-server/v6/product"
"github.com/mattermost/mattermost-server/v6/services/httpservice"
"github.com/mattermost/mattermost-server/v6/services/marketplace"
"github.com/mattermost/mattermost-server/v6/shared/filestore"
"github.com/mattermost/mattermost-server/v6/shared/mlog"
"github.com/mattermost/mattermost-server/v6/utils/fileutils"
)
type PluginService struct {
platform *platform.PlatformService
channels *Channels
fileStore filestore.FileBackend
httpService httpservice.HTTPService
pluginCommandsLock sync.RWMutex
pluginCommands []*PluginCommand
pluginsLock sync.RWMutex
pluginsEnvironment *plugin.Environment
pluginConfigListenerID string
// collectionTypes maps from collection types to the registering plugin id
collectionTypes map[string]string
// topicTypes maps from topic types to collection types
topicTypes map[string]string
collectionAndTopicTypesMut sync.Mutex
}
const prepackagedPluginsDir = "prepackaged_plugins"
type pluginSignaturePath struct {
@@ -63,20 +84,91 @@ func (rs *routerService) getHandler(productID string) (http.Handler, bool) {
return handler, ok
}
func (a *App) PluginService() *PluginService {
return a.ch.srv.pluginService
}
func (s *Server) InitializePluginService() error {
product, ok := s.products["channels"]
if !ok {
return errors.New("unable to find channels product")
}
channels, ok := product.(*Channels)
if !ok {
return errors.New("unable to cast product to channels product")
}
ps := &PluginService{
platform: s.platform,
channels: channels,
fileStore: s.platform.FileBackend(),
httpService: s.httpService,
collectionTypes: make(map[string]string),
topicTypes: make(map[string]string),
}
s.pluginService = ps
pluginsRoute := s.Router.PathPrefix("/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}").Subrouter()
pluginsRoute.HandleFunc("", ps.ServePluginRequest)
pluginsRoute.HandleFunc("/public/{public_file:.*}", ps.ServePluginPublicRequest)
pluginsRoute.HandleFunc("/{anything:.*}", ps.ServePluginRequest)
ps.initPlugins(request.EmptyContext(s.platform.Log()), *s.platform.Config().PluginSettings.Directory, *s.platform.Config().PluginSettings.ClientDirectory)
// Start plugins
ctx := request.EmptyContext(s.platform.Log())
// Add the config listener to enable/disable plugins
s.platform.AddConfigListener(func(prevCfg, cfg *model.Config) {
// We compute the difference between configs
// to ensure we don't re-init plugins unnecessarily.
diffs, err := config.Diff(prevCfg, cfg)
if err != nil {
s.platform.Log().Warn("Error in comparing configs", mlog.Err(err))
return
}
hasDiff := false
// TODO: This could be a method on ConfigDiffs itself
for _, diff := range diffs {
if strings.HasPrefix(diff.Path, "PluginSettings.") {
hasDiff = true
break
}
}
// Do only if some plugin related settings has changed.
if hasDiff {
if *cfg.PluginSettings.Enable {
s.pluginService.initPlugins(ctx, *cfg.PluginSettings.Directory, *s.Config().PluginSettings.ClientDirectory)
} else {
s.pluginService.ShutDownPlugins()
}
}
})
return nil
}
func (s *Server) GetPluginsEnvironment() *plugin.Environment {
return s.pluginService.GetPluginsEnvironment()
}
// GetPluginsEnvironment returns the plugin environment for use if plugins are enabled and
// initialized.
//
// To get the plugins environment when the plugins are disabled, manually acquire the plugins
// lock instead.
func (ch *Channels) GetPluginsEnvironment() *plugin.Environment {
if !*ch.cfgSvc.Config().PluginSettings.Enable {
func (s *PluginService) GetPluginsEnvironment() *plugin.Environment {
if !*s.platform.Config().PluginSettings.Enable {
return nil
}
ch.pluginsLock.RLock()
defer ch.pluginsLock.RUnlock()
s.pluginsLock.RLock()
defer s.pluginsLock.RUnlock()
return ch.pluginsEnvironment
return s.pluginsEnvironment
}
// GetPluginsEnvironment returns the plugin environment for use if plugins are enabled and
@@ -85,33 +177,39 @@ func (ch *Channels) GetPluginsEnvironment() *plugin.Environment {
// To get the plugins environment when the plugins are disabled, manually acquire the plugins
// lock instead.
func (a *App) GetPluginsEnvironment() *plugin.Environment {
return a.ch.GetPluginsEnvironment()
// TODO: Telemetry service starts before products start, so we need to check if the plugin service is initialized.
// Move the telemetry service to start after products start.
if a.ch.srv.pluginService == nil {
return nil
}
return a.ch.srv.pluginService.GetPluginsEnvironment()
}
func (ch *Channels) SetPluginsEnvironment(pluginsEnvironment *plugin.Environment) {
ch.pluginsLock.Lock()
defer ch.pluginsLock.Unlock()
func (s *PluginService) SetPluginsEnvironment(pluginsEnvironment *plugin.Environment) {
s.pluginsLock.Lock()
defer s.pluginsLock.Unlock()
ch.pluginsEnvironment = pluginsEnvironment
ch.srv.Platform().SetPluginsEnvironment(ch)
s.pluginsEnvironment = pluginsEnvironment
s.platform.SetPluginsEnvironment(s.channels.srv)
}
func (ch *Channels) syncPluginsActiveState() {
func (s *PluginService) syncPluginsActiveState() {
// Acquiring lock manually, as plugins might be disabled. See GetPluginsEnvironment.
ch.pluginsLock.RLock()
pluginsEnvironment := ch.pluginsEnvironment
ch.pluginsLock.RUnlock()
s.pluginsLock.RLock()
pluginsEnvironment := s.pluginsEnvironment
s.pluginsLock.RUnlock()
if pluginsEnvironment == nil {
return
}
config := ch.cfgSvc.Config().PluginSettings
config := s.platform.Config().PluginSettings
if *config.Enable {
availablePlugins, err := pluginsEnvironment.Available()
if err != nil {
ch.srv.Log().Error("Unable to get available plugins", mlog.Err(err))
s.platform.Log().Error("Unable to get available plugins", mlog.Err(err))
return
}
@@ -125,24 +223,24 @@ func (ch *Channels) syncPluginsActiveState() {
pluginEnabled = state.Enable
}
if hasOverride, value := ch.getPluginStateOverride(pluginID); hasOverride {
if hasOverride, value := s.getPluginStateOverride(pluginID); hasOverride {
pluginEnabled = value
}
if pluginEnabled {
// Disable focalboard in product mode.
if pluginID == model.PluginIdFocalboard && ch.cfgSvc.Config().FeatureFlags.BoardsProduct {
if pluginID == model.PluginIdFocalboard && s.platform.Config().FeatureFlags.BoardsProduct {
msg := "Plugin cannot run in product mode. Disabling."
mlog.Warn(msg, mlog.String("plugin_id", model.PluginIdFocalboard))
// This is a mini-version of ch.disablePlugin.
// We don't call that directly, because that will recursively call
// this method.
ch.cfgSvc.UpdateConfig(func(cfg *model.Config) {
s.platform.UpdateConfig(func(cfg *model.Config) {
cfg.PluginSettings.PluginStates[pluginID] = &model.PluginState{Enable: false}
})
pluginsEnvironment.SetPluginError(pluginID, msg)
ch.unregisterPluginCommands(pluginID)
s.unregisterPluginCommands(pluginID)
disabledPlugins = append(disabledPlugins, plugin)
continue
}
@@ -166,7 +264,7 @@ func (ch *Channels) syncPluginsActiveState() {
if deactivated && plugin.Manifest.HasClient() {
message := model.NewWebSocketEvent(model.WebsocketEventPluginDisabled, "", "", "", nil, "")
message.Add("manifest", plugin.Manifest.ClientManifest())
ch.srv.platform.Publish(message)
s.platform.Publish(message)
}
}(plugin)
}
@@ -180,14 +278,14 @@ func (ch *Channels) syncPluginsActiveState() {
pluginID := plugin.Manifest.Id
updatedManifest, activated, err := pluginsEnvironment.Activate(pluginID)
if err != nil {
plugin.WrapLogger(ch.srv.Log()).Error("Unable to activate plugin", mlog.Err(err))
plugin.WrapLogger(s.platform.Log().(*mlog.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))
if err := s.notifyPluginEnabled(updatedManifest); err != nil {
s.platform.Log().Error("Failed to notify cluster on plugin enable", mlog.Err(err))
}
}
}(plugin)
@@ -197,7 +295,7 @@ func (ch *Channels) syncPluginsActiveState() {
pluginsEnvironment.Shutdown()
}
if err := ch.notifyPluginStatusesChanged(); err != nil {
if err := s.notifyPluginStatusesChanged(); err != nil {
mlog.Warn("failed to notify plugin status changed", mlog.Err(err))
}
}
@@ -207,27 +305,29 @@ func (a *App) NewPluginAPI(c *request.Context, manifest *model.Manifest) plugin.
}
func (a *App) InitPlugins(c *request.Context, pluginDir, webappPluginDir string) {
a.ch.initPlugins(c, pluginDir, webappPluginDir)
a.ch.srv.pluginService.initPlugins(c, pluginDir, webappPluginDir)
}
func (ch *Channels) initPlugins(c *request.Context, pluginDir, webappPluginDir string) {
func (s *PluginService) initPlugins(c *request.Context, pluginDir, webappPluginDir string) {
// Acquiring lock manually, as plugins might be disabled. See GetPluginsEnvironment.
defer func() {
ch.srv.Platform().SetPluginsEnvironment(ch)
// platform service requires plugins environment to be initialized
// so that it can use it in cluster service initialization
s.platform.SetPluginsEnvironment(s.channels.srv)
}()
ch.pluginsLock.RLock()
pluginsEnvironment := ch.pluginsEnvironment
ch.pluginsLock.RUnlock()
if pluginsEnvironment != nil || !*ch.cfgSvc.Config().PluginSettings.Enable {
ch.syncPluginsActiveState()
s.pluginsLock.RLock()
pluginsEnvironment := s.pluginsEnvironment
s.pluginsLock.RUnlock()
if pluginsEnvironment != nil || !*s.platform.Config().PluginSettings.Enable {
s.syncPluginsActiveState()
if pluginsEnvironment != nil {
pluginsEnvironment.TogglePluginHealthCheckJob(*ch.cfgSvc.Config().PluginSettings.EnableHealthCheck)
pluginsEnvironment.TogglePluginHealthCheckJob(*s.platform.Config().PluginSettings.EnableHealthCheck)
}
return
}
ch.srv.Log().Info("Starting up plugins")
s.platform.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))
@@ -240,77 +340,77 @@ func (ch *Channels) initPlugins(c *request.Context, pluginDir, webappPluginDir s
}
newAPIFunc := func(manifest *model.Manifest) plugin.API {
return New(ServerConnector(ch)).NewPluginAPI(c, manifest)
return New(ServerConnector(s.channels)).NewPluginAPI(c, manifest)
}
env, err := plugin.NewEnvironment(
newAPIFunc,
NewDriverImpl(ch.srv),
NewDriverImpl(s.platform),
pluginDir,
webappPluginDir,
*ch.cfgSvc.Config().ExperimentalSettings.PatchPluginsReactDOM,
ch.srv.Log(),
ch.srv.GetMetrics(),
*s.platform.Config().ExperimentalSettings.PatchPluginsReactDOM,
s.platform.Logger(),
s.platform.Metrics(),
)
if err != nil {
mlog.Error("Failed to start up plugins", mlog.Err(err))
return
}
ch.pluginsLock.Lock()
ch.pluginsEnvironment = env
ch.pluginsLock.Unlock()
s.pluginsLock.Lock()
s.pluginsEnvironment = env
s.pluginsLock.Unlock()
ch.pluginsEnvironment.TogglePluginHealthCheckJob(*ch.cfgSvc.Config().PluginSettings.EnableHealthCheck)
s.pluginsEnvironment.TogglePluginHealthCheckJob(*s.platform.Config().PluginSettings.EnableHealthCheck)
if err := ch.syncPlugins(); err != nil {
if err := s.syncPlugins(); err != nil {
mlog.Error("Failed to sync plugins from the file store", mlog.Err(err))
}
plugins := ch.processPrepackagedPlugins(prepackagedPluginsDir)
pluginsEnvironment = ch.GetPluginsEnvironment()
plugins := s.processPrepackagedPlugins(prepackagedPluginsDir)
pluginsEnvironment = s.GetPluginsEnvironment()
if pluginsEnvironment == nil {
mlog.Info("Plugins environment not found, server is likely shutting down")
return
}
pluginsEnvironment.SetPrepackagedPlugins(plugins)
ch.installFeatureFlagPlugins()
s.installFeatureFlagPlugins()
// Sync plugin active state when config changes. Also notify plugins.
ch.pluginsLock.Lock()
ch.RemoveConfigListener(ch.pluginConfigListenerID)
ch.pluginConfigListenerID = ch.AddConfigListener(func(old, new *model.Config) {
s.pluginsLock.Lock()
s.platform.RemoveConfigListener(s.pluginConfigListenerID)
s.pluginConfigListenerID = s.platform.AddConfigListener(func(old, new *model.Config) {
// 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()
s.installFeatureFlagPlugins()
s.syncPluginsActiveState()
}
ch.RunMultiHook(func(hooks plugin.Hooks) bool {
s.pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
if err := hooks.OnConfigurationChange(); err != nil {
ch.srv.Log().Error("Plugin OnConfigurationChange hook failed", mlog.Err(err))
s.platform.Log().Error("Plugin OnConfigurationChange hook failed", mlog.Err(err))
}
return true
}, plugin.OnConfigurationChangeID)
})
ch.pluginsLock.Unlock()
s.pluginsLock.Unlock()
ch.syncPluginsActiveState()
s.syncPluginsActiveState()
}
// SyncPlugins synchronizes the plugins installed locally
// with the plugin bundles available in the file store.
func (a *App) SyncPlugins() *model.AppError {
return a.ch.syncPlugins()
return a.ch.srv.pluginService.syncPlugins()
}
// SyncPlugins synchronizes the plugins installed locally
// with the plugin bundles available in the file store.
func (ch *Channels) syncPlugins() *model.AppError {
func (s *PluginService) syncPlugins() *model.AppError {
mlog.Info("Syncing plugins from the file store")
pluginsEnvironment := ch.GetPluginsEnvironment()
pluginsEnvironment := s.GetPluginsEnvironment()
if pluginsEnvironment == nil {
return model.NewAppError("SyncPlugins", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
}
@@ -326,14 +426,14 @@ func (ch *Channels) syncPlugins() *model.AppError {
go func(pluginID string) {
defer wg.Done()
// 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(*s.platform.Config().PluginSettings.Directory, pluginID, managedPluginFileName))
if os.IsNotExist(err) {
mlog.Warn("Skipping sync for unmanaged plugin", mlog.String("plugin_id", pluginID))
} else if err != nil {
mlog.Error("Skipping sync for plugin after failure to check if managed", mlog.String("plugin_id", pluginID), mlog.Err(err))
} else {
mlog.Debug("Removing local installation of managed plugin before sync", mlog.String("plugin_id", pluginID))
if err := ch.removePluginLocally(pluginID); err != nil {
if err := s.removePluginLocally(pluginID); err != nil {
mlog.Error("Failed to remove local installation of managed plugin before sync", mlog.String("plugin_id", pluginID), mlog.Err(err))
}
}
@@ -342,7 +442,7 @@ func (ch *Channels) syncPlugins() *model.AppError {
wg.Wait()
// Install plugins from the file store.
pluginSignaturePathMap, appErr := ch.getPluginsFromFolder()
pluginSignaturePathMap, appErr := s.getPluginsFromFolder()
if appErr != nil {
return appErr
}
@@ -351,7 +451,7 @@ func (ch *Channels) syncPlugins() *model.AppError {
wg.Add(1)
go func(plugin *pluginSignaturePath) {
defer wg.Done()
reader, appErr := ch.srv.fileReader(plugin.path)
reader, appErr := s.fileStore.Reader(plugin.path)
if appErr != nil {
mlog.Error("Failed to open plugin bundle from file store.", mlog.String("bundle", plugin.path), mlog.Err(appErr))
return
@@ -359,8 +459,8 @@ func (ch *Channels) syncPlugins() *model.AppError {
defer reader.Close()
var signature filestore.ReadCloseSeeker
if *ch.cfgSvc.Config().PluginSettings.RequirePluginSignature {
signature, appErr = ch.srv.fileReader(plugin.signaturePath)
if *s.platform.Config().PluginSettings.RequirePluginSignature {
signature, appErr = s.fileStore.Reader(plugin.signaturePath)
if appErr != nil {
mlog.Error("Failed to open plugin signature from file store.", mlog.Err(appErr))
return
@@ -369,7 +469,7 @@ func (ch *Channels) syncPlugins() *model.AppError {
}
mlog.Info("Syncing plugin from file store", mlog.String("bundle", plugin.path))
if _, err := ch.installPluginLocally(reader, signature, installPluginLocallyAlways); err != nil {
if _, err := s.installPluginLocally(reader, signature, installPluginLocallyAlways); err != nil {
mlog.Error("Failed to sync plugin from file store", mlog.String("bundle", plugin.path), mlog.Err(err))
}
}(plugin)
@@ -379,11 +479,11 @@ func (ch *Channels) syncPlugins() *model.AppError {
return nil
}
func (ch *Channels) ShutDownPlugins() {
func (s *PluginService) ShutDownPlugins() {
// Acquiring lock manually, as plugins might be disabled. See GetPluginsEnvironment.
ch.pluginsLock.RLock()
pluginsEnvironment := ch.pluginsEnvironment
ch.pluginsLock.RUnlock()
s.pluginsLock.RLock()
pluginsEnvironment := s.pluginsEnvironment
s.pluginsLock.RUnlock()
if pluginsEnvironment == nil {
return
}
@@ -392,14 +492,14 @@ func (ch *Channels) ShutDownPlugins() {
pluginsEnvironment.Shutdown()
ch.RemoveConfigListener(ch.pluginConfigListenerID)
ch.pluginConfigListenerID = ""
s.platform.RemoveConfigListener(s.pluginConfigListenerID)
s.pluginConfigListenerID = ""
// Acquiring lock manually before cleaning up PluginsEnvironment.
ch.pluginsLock.Lock()
defer ch.pluginsLock.Unlock()
if ch.pluginsEnvironment == pluginsEnvironment {
ch.pluginsEnvironment = nil
s.pluginsLock.Lock()
defer s.pluginsLock.Unlock()
if s.pluginsEnvironment == pluginsEnvironment {
s.pluginsEnvironment = nil
} else {
mlog.Warn("Another PluginsEnvironment detected while shutting down plugins.")
}
@@ -425,11 +525,11 @@ func (a *App) GetActivePluginManifests() ([]*model.Manifest, *model.AppError) {
// activation if inactive anywhere in the cluster.
// Notifies cluster peers through config change.
func (a *App) EnablePlugin(id string) *model.AppError {
return a.ch.enablePlugin(id)
return a.PluginService().enablePlugin(id)
}
func (ch *Channels) enablePlugin(id string) *model.AppError {
pluginsEnvironment := ch.GetPluginsEnvironment()
func (s *PluginService) enablePlugin(id string) *model.AppError {
pluginsEnvironment := s.GetPluginsEnvironment()
if pluginsEnvironment == nil {
return model.NewAppError("EnablePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
}
@@ -453,16 +553,16 @@ func (ch *Channels) enablePlugin(id string) *model.AppError {
return model.NewAppError("EnablePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusNotFound)
}
if id == model.PluginIdFocalboard && ch.cfgSvc.Config().FeatureFlags.BoardsProduct {
if id == model.PluginIdFocalboard && s.platform.Config().FeatureFlags.BoardsProduct {
return model.NewAppError("EnablePlugin", "app.plugin.product_mode.app_error", map[string]any{"Name": model.PluginIdFocalboard}, "", http.StatusBadRequest)
}
ch.cfgSvc.UpdateConfig(func(cfg *model.Config) {
s.platform.UpdateConfig(func(cfg *model.Config) {
cfg.PluginSettings.PluginStates[id] = &model.PluginState{Enable: true}
})
// This call will implicitly invoke SyncPluginsActiveState which will activate enabled plugins.
if _, _, err := ch.cfgSvc.SaveConfig(ch.cfgSvc.Config(), true); err != nil {
if _, _, err := s.platform.SaveConfig(s.platform.Config(), true); err != nil {
if err.Id == "ent.cluster.save_config.error" {
return model.NewAppError("EnablePlugin", "app.plugin.cluster.save_config.app_error", nil, "", http.StatusInternalServerError)
}
@@ -475,7 +575,7 @@ func (ch *Channels) enablePlugin(id string) *model.AppError {
// DisablePlugin will set the config for an installed plugin to disabled, triggering deactivation if active.
// Notifies cluster peers through config change.
func (a *App) DisablePlugin(id string) *model.AppError {
appErr := a.ch.disablePlugin(id)
appErr := a.ch.srv.pluginService.disablePlugin(id)
if appErr != nil {
return appErr
}
@@ -483,22 +583,22 @@ func (a *App) DisablePlugin(id string) *model.AppError {
return nil
}
func (ch *Channels) disablePlugin(id string) *model.AppError {
func (s *PluginService) disablePlugin(id string) *model.AppError {
// find all collectionTypes registered by plugin
for collectionTypeToRemove, existingPluginId := range ch.collectionTypes {
for collectionTypeToRemove, existingPluginId := range s.collectionTypes {
if existingPluginId != id {
continue
}
// find all topicTypes for existing collectionType
for topicTypeToRemove, existingCollectionType := range ch.topicTypes {
for topicTypeToRemove, existingCollectionType := range s.topicTypes {
if existingCollectionType == collectionTypeToRemove {
delete(ch.topicTypes, topicTypeToRemove)
delete(s.topicTypes, topicTypeToRemove)
}
}
delete(ch.collectionTypes, collectionTypeToRemove)
delete(s.collectionTypes, collectionTypeToRemove)
}
pluginsEnvironment := ch.GetPluginsEnvironment()
pluginsEnvironment := s.GetPluginsEnvironment()
if pluginsEnvironment == nil {
return model.NewAppError("DisablePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
}
@@ -522,13 +622,13 @@ func (ch *Channels) disablePlugin(id string) *model.AppError {
return model.NewAppError("DisablePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusNotFound)
}
ch.cfgSvc.UpdateConfig(func(cfg *model.Config) {
s.platform.UpdateConfig(func(cfg *model.Config) {
cfg.PluginSettings.PluginStates[id] = &model.PluginState{Enable: false}
})
ch.unregisterPluginCommands(id)
s.unregisterPluginCommands(id)
// This call will implicitly invoke SyncPluginsActiveState which will deactivate disabled plugins.
if _, _, err := ch.cfgSvc.SaveConfig(ch.cfgSvc.Config(), true); err != nil {
if _, _, err := s.platform.SaveConfig(s.platform.Config(), true); err != nil {
return model.NewAppError("DisablePlugin", "app.plugin.config.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
@@ -615,8 +715,8 @@ func (a *App) GetMarketplacePlugins(filter *model.MarketplacePluginFilter) ([]*m
// getPrepackagedPlugin returns a pre-packaged plugin.
//
// If version is empty, the first matching plugin is returned.
func (ch *Channels) getPrepackagedPlugin(pluginID, version string) (*plugin.PrepackagedPlugin, *model.AppError) {
pluginsEnvironment := ch.GetPluginsEnvironment()
func (s *PluginService) getPrepackagedPlugin(pluginID, version string) (*plugin.PrepackagedPlugin, *model.AppError) {
pluginsEnvironment := s.GetPluginsEnvironment()
if pluginsEnvironment == nil {
return nil, model.NewAppError("getPrepackagedPlugin", "app.plugin.config.app_error", nil, "plugin environment is nil", http.StatusInternalServerError)
}
@@ -634,16 +734,16 @@ func (ch *Channels) getPrepackagedPlugin(pluginID, version string) (*plugin.Prep
// getRemoteMarketplacePlugin returns plugin from marketplace-server.
//
// If version is empty, the latest compatible version is used.
func (ch *Channels) getRemoteMarketplacePlugin(pluginID, version string) (*model.BaseMarketplacePlugin, *model.AppError) {
func (s *PluginService) getRemoteMarketplacePlugin(pluginID, version string) (*model.BaseMarketplacePlugin, *model.AppError) {
marketplaceClient, err := marketplace.NewClient(
*ch.cfgSvc.Config().PluginSettings.MarketplaceURL,
ch.srv.HTTPService(),
*s.platform.Config().PluginSettings.MarketplaceURL,
s.httpService,
)
if err != nil {
return nil, model.NewAppError("GetMarketplacePlugin", "app.plugin.marketplace_client.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
filter := ch.getBaseMarketplaceFilter()
filter := s.getBaseMarketplaceFilter()
filter.PluginId = pluginID
var plugin *model.BaseMarketplacePlugin
@@ -798,15 +898,15 @@ func (a *App) mergeLocalPlugins(remoteMarketplacePlugins map[string]*model.Marke
}
func (a *App) getBaseMarketplaceFilter() *model.MarketplacePluginFilter {
return a.ch.getBaseMarketplaceFilter()
return a.ch.srv.pluginService.getBaseMarketplaceFilter()
}
func (ch *Channels) getBaseMarketplaceFilter() *model.MarketplacePluginFilter {
func (s *PluginService) getBaseMarketplaceFilter() *model.MarketplacePluginFilter {
filter := &model.MarketplacePluginFilter{
ServerVersion: model.CurrentVersion,
}
license := ch.srv.License()
license := s.platform.License()
if license != nil && license.HasEnterpriseMarketplacePlugins() {
filter.EnterprisePlugins = true
}
@@ -853,8 +953,8 @@ func pluginMatchesFilter(manifest *model.Manifest, filter string) bool {
// it will notify all connected websocket clients (across all peers) to trigger the (re-)installation.
// There is a small chance that this never occurs, because the last server to finish installing dies before it can announce.
// There is also a chance that multiple servers notify, but the webapp handles this idempotently.
func (ch *Channels) notifyPluginEnabled(manifest *model.Manifest) error {
pluginsEnvironment := ch.GetPluginsEnvironment()
func (s *PluginService) notifyPluginEnabled(manifest *model.Manifest) error {
pluginsEnvironment := s.GetPluginsEnvironment()
if pluginsEnvironment == nil {
return errors.New("pluginsEnvironment is nil")
}
@@ -864,15 +964,15 @@ func (ch *Channels) notifyPluginEnabled(manifest *model.Manifest) error {
var statuses model.PluginStatuses
if ch.srv.platform.Cluster() != nil {
if s.platform.Cluster() != nil {
var err *model.AppError
statuses, err = ch.srv.platform.Cluster().GetPluginStatuses()
statuses, err = s.platform.Cluster().GetPluginStatuses()
if err != nil {
return err
}
}
localStatus, err := ch.GetPluginStatus(manifest.Id)
localStatus, err := s.GetPluginStatus(manifest.Id)
if err != nil {
return err
}
@@ -892,26 +992,26 @@ func (ch *Channels) notifyPluginEnabled(manifest *model.Manifest) error {
// Notify all cluster peer clients.
message := model.NewWebSocketEvent(model.WebsocketEventPluginEnabled, "", "", "", nil, "")
message.Add("manifest", manifest.ClientManifest())
ch.srv.platform.Publish(message)
s.platform.Publish(message)
return nil
}
func (ch *Channels) getPluginsFromFolder() (map[string]*pluginSignaturePath, *model.AppError) {
fileStorePaths, appErr := ch.srv.listDirectory(fileStorePluginFolder, false)
func (s *PluginService) getPluginsFromFolder() (map[string]*pluginSignaturePath, *model.AppError) {
fileStorePaths, appErr := s.fileStore.ListDirectory(fileStorePluginFolder)
if appErr != nil {
return nil, model.NewAppError("getPluginsFromDir", "app.plugin.sync.list_filestore.app_error", nil, "", http.StatusInternalServerError).Wrap(appErr)
}
return ch.getPluginsFromFilePaths(fileStorePaths), nil
return s.getPluginsFromFilePaths(fileStorePaths), nil
}
func (ch *Channels) getPluginsFromFilePaths(fileStorePaths []string) map[string]*pluginSignaturePath {
func (s *PluginService) getPluginsFromFilePaths(fileStorePaths []string) map[string]*pluginSignaturePath {
pluginSignaturePathMap := make(map[string]*pluginSignaturePath)
fsPrefix := ""
if *ch.cfgSvc.Config().FileSettings.DriverName == model.ImageDriverS3 {
ptr := ch.cfgSvc.Config().FileSettings.AmazonS3PathPrefix
if *s.platform.Config().FileSettings.DriverName == model.ImageDriverS3 {
ptr := s.platform.Config().FileSettings.AmazonS3PathPrefix
if ptr != nil && *ptr != "" {
fsPrefix = *ptr + "/"
}
@@ -944,7 +1044,7 @@ func (ch *Channels) getPluginsFromFilePaths(fileStorePaths []string) map[string]
return pluginSignaturePathMap
}
func (ch *Channels) processPrepackagedPlugins(pluginsDir string) []*plugin.PrepackagedPlugin {
func (s *PluginService) processPrepackagedPlugins(pluginsDir string) []*plugin.PrepackagedPlugin {
prepackagedPluginsDir, found := fileutils.FindDir(pluginsDir)
if !found {
return nil
@@ -960,7 +1060,7 @@ func (ch *Channels) processPrepackagedPlugins(pluginsDir string) []*plugin.Prepa
return nil
}
pluginSignaturePathMap := ch.getPluginsFromFilePaths(fileStorePaths)
pluginSignaturePathMap := s.getPluginsFromFilePaths(fileStorePaths)
plugins := make([]*plugin.PrepackagedPlugin, 0, len(pluginSignaturePathMap))
prepackagedPlugins := make(chan *plugin.PrepackagedPlugin, len(pluginSignaturePathMap))
@@ -969,7 +1069,7 @@ func (ch *Channels) processPrepackagedPlugins(pluginsDir string) []*plugin.Prepa
wg.Add(1)
go func(psPath *pluginSignaturePath) {
defer wg.Done()
p, err := ch.processPrepackagedPlugin(psPath)
p, err := s.processPrepackagedPlugin(psPath)
if err != nil {
mlog.Error("Failed to install prepackaged plugin", mlog.String("path", psPath.path), mlog.Err(err))
return
@@ -990,7 +1090,7 @@ 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) {
func (s *PluginService) processPrepackagedPlugin(pluginPath *pluginSignaturePath) (*plugin.PrepackagedPlugin, error) {
mlog.Debug("Processing prepackaged plugin", mlog.String("path", pluginPath.path))
fileReader, err := os.Open(pluginPath.path)
@@ -1011,18 +1111,18 @@ func (ch *Channels) processPrepackagedPlugin(pluginPath *pluginSignaturePath) (*
}
// Skip installing the plugin at all if automatic prepackaged plugins is disabled
if !*ch.cfgSvc.Config().PluginSettings.AutomaticPrepackagedPlugins {
if !*s.platform.Config().PluginSettings.AutomaticPrepackagedPlugins {
return plugin, nil
}
// Skip installing if the plugin is has not been previously enabled.
pluginState := ch.cfgSvc.Config().PluginSettings.PluginStates[plugin.Manifest.Id]
pluginState := s.platform.Config().PluginSettings.PluginStates[plugin.Manifest.Id]
if pluginState == nil || !pluginState.Enable {
return plugin, nil
}
mlog.Debug("Installing prepackaged plugin", mlog.String("path", pluginPath.path))
if _, err := ch.installExtractedPlugin(plugin.Manifest, pluginDir, installPluginLocallyOnlyIfNewOrUpgrade); err != nil {
if _, err := s.installExtractedPlugin(plugin.Manifest, pluginDir, installPluginLocallyOnlyIfNewOrUpgrade); err != nil {
return nil, errors.Wrapf(err, "Failed to install extracted prepackaged plugin %s", pluginPath.path)
}
@@ -1030,24 +1130,24 @@ func (ch *Channels) processPrepackagedPlugin(pluginPath *pluginSignaturePath) (*
}
// installFeatureFlagPlugins handles the automatic installation/upgrade of plugins from feature flags
func (ch *Channels) installFeatureFlagPlugins() {
ffControledPlugins := ch.cfgSvc.Config().FeatureFlags.Plugins()
func (s *PluginService) installFeatureFlagPlugins() {
ffControledPlugins := s.platform.Config().FeatureFlags.Plugins()
// Respect the automatic prepackaged disable setting
if !*ch.cfgSvc.Config().PluginSettings.AutomaticPrepackagedPlugins {
if !*s.platform.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]
pluginState := s.platform.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))
s.platform.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)
pluginStatus, err := s.GetPluginStatus(pluginID)
pluginExists := err == nil
if pluginExists && pluginStatus.Version == version {
continue
@@ -1055,37 +1155,37 @@ func (ch *Channels) installFeatureFlagPlugins() {
if version != "" && version != "control" {
// If we are on-prem skip installation if this is a downgrade
license := ch.srv.License()
license := s.platform.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))
s.platform.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))
s.platform.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))
s.platform.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{
_, err := s.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))
s.platform.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))
if err := s.enablePlugin(pluginID); err != nil {
s.platform.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))
s.platform.Log().Debug("Installed and enabled plugin.", mlog.String("plugin_id", pluginID), mlog.String("version", version))
}
}
}
@@ -1140,15 +1240,15 @@ func getIcon(iconPath string) (string, error) {
return fmt.Sprintf("data:image/svg+xml;base64,%s", base64.StdEncoding.EncodeToString(icon)), nil
}
func (ch *Channels) getPluginStateOverride(pluginID string) (bool, bool) {
func (s *PluginService) getPluginStateOverride(pluginID string) (bool, bool) {
switch pluginID {
case model.PluginIdApps:
// Tie Apps proxy disabled status to the feature flag.
if !ch.cfgSvc.Config().FeatureFlags.AppsEnabled {
if !s.platform.Config().FeatureFlags.AppsEnabled {
return true, false
}
case model.PluginIdCalls:
if !ch.cfgSvc.Config().FeatureFlags.CallsEnabled {
if !s.platform.Config().FeatureFlags.CallsEnabled {
return true, false
}
}