[MM-48626] Move plugins environment out of Channels (#21730)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
0ce7c5e3da
Коммит
dcf499b51d
380
app/plugin.go
380
app/plugin.go
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user