diff --git a/api4/plugin.go b/api4/plugin.go index be5b298d02..475aa62f21 100644 --- a/api4/plugin.go +++ b/api4/plugin.go @@ -155,7 +155,7 @@ func installMarketplacePlugin(c *Context, w http.ResponseWriter, r *http.Request // https://mattermost.atlassian.net/browse/MM-41981 pluginRequest.Version = "" - manifest, appErr := c.App.Channels().InstallMarketplacePlugin(pluginRequest) + manifest, appErr := c.App.PluginService().InstallMarketplacePlugin(pluginRequest) if appErr != nil { c.Err = appErr return @@ -235,7 +235,7 @@ func removePlugin(c *Context, w http.ResponseWriter, r *http.Request) { return } - err := c.App.Channels().RemovePlugin(c.Params.PluginId) + err := c.App.PluginService().RemovePlugin(c.Params.PluginId) if err != nil { c.Err = err return diff --git a/api4/plugin_test.go b/api4/plugin_test.go index 1967f9a617..3b656c209a 100644 --- a/api4/plugin_test.go +++ b/api4/plugin_test.go @@ -94,7 +94,7 @@ func TestPlugin(t *testing.T) { assert.Equal(t, "testplugin", manifest.Id) }) - th.App.Channels().RemovePlugin(manifest.Id) + th.App.PluginService().RemovePlugin(manifest.Id) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = false }) diff --git a/api4/websocket.go b/api4/websocket.go index d2c1c10f44..d8236e49b5 100644 --- a/api4/websocket.go +++ b/api4/websocket.go @@ -61,7 +61,7 @@ func connectWebSocket(c *Context, w http.ResponseWriter, r *http.Request) { } } - wc := c.App.Srv().Platform().NewWebConn(cfg, c.App, c.App.Srv().Channels()) + wc := c.App.Srv().Platform().NewWebConn(cfg, c.App, c.App.Srv()) if c.AppContext.Session().UserId != "" { c.App.Srv().Platform().HubRegister(wc) } diff --git a/app/app_iface.go b/app/app_iface.go index f10ca2d8b9..2674801101 100644 --- a/app/app_iface.go +++ b/app/app_iface.go @@ -936,6 +936,7 @@ type AppIface interface { PermanentDeleteTeamId(c request.CTX, teamID string) *model.AppError PermanentDeleteUser(c *request.Context, user *model.User) *model.AppError PluginCommandsForTeam(teamID string) []*model.Command + PluginService() *PluginService PostActionCookieSecret() []byte PostAddToChannelMessage(c request.CTX, user *model.User, addedUser *model.User, channel *model.Channel, postRootId string) *model.AppError PostPatchWithProxyRemovedFromImageURLs(patch *model.PostPatch) *model.PostPatch diff --git a/app/channel.go b/app/channel.go index fdc8d17da3..31a8673383 100644 --- a/app/channel.go +++ b/app/channel.go @@ -345,7 +345,7 @@ func (a *App) CreateChannel(c request.CTX, channel *model.Channel, addMember boo a.Srv().Go(func() { pluginContext := pluginContext(c) - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.ChannelHasBeenCreated(pluginContext, sc) return true }, plugin.ChannelHasBeenCreatedID) @@ -429,7 +429,7 @@ func (a *App) handleCreationEvent(c request.CTX, userID, otherUserID string, cha a.Srv().Go(func() { pluginContext := pluginContext(c) - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.ChannelHasBeenCreated(pluginContext, channel) return true }, plugin.ChannelHasBeenCreatedID) @@ -1597,7 +1597,7 @@ func (a *App) AddChannelMember(c request.CTX, userID string, channel *model.Chan a.Srv().Go(func() { pluginContext := pluginContext(c) - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.UserHasJoinedChannel(pluginContext, cm, userRequestor) return true }, plugin.UserHasJoinedChannelID) @@ -2173,7 +2173,7 @@ func (a *App) JoinChannel(c request.CTX, channel *model.Channel, userID string) a.Srv().Go(func() { pluginContext := pluginContext(c) - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.UserHasJoinedChannel(pluginContext, cm, nil) return true }, plugin.UserHasJoinedChannelID) @@ -2483,7 +2483,7 @@ func (a *App) removeUserFromChannel(c request.CTX, userIDToRemove string, remove a.Srv().Go(func() { pluginContext := pluginContext(c) - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.UserHasLeftChannel(pluginContext, cm, actorUser) return true }, plugin.UserHasLeftChannelID) diff --git a/app/channels.go b/app/channels.go index 7ab024e45a..c9771f25d6 100644 --- a/app/channels.go +++ b/app/channels.go @@ -6,14 +6,11 @@ package app import ( "fmt" "runtime" - "strings" "sync" "github.com/pkg/errors" "github.com/mattermost/mattermost-server/v6/app/imaging" - "github.com/mattermost/mattermost-server/v6/app/request" - "github.com/mattermost/mattermost-server/v6/config" "github.com/mattermost/mattermost-server/v6/einterfaces" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/plugin" @@ -42,12 +39,6 @@ type Channels struct { postActionCookieSecret []byte - pluginCommandsLock sync.RWMutex - pluginCommands []*PluginCommand - pluginsLock sync.RWMutex - pluginsEnvironment *plugin.Environment - pluginConfigListenerID string - imageProxy *imageproxy.ImageProxy // cached counts that are used during notice condition validation @@ -79,12 +70,6 @@ type Channels struct { postReminderMut sync.Mutex postReminderTask *model.ScheduledTask - - // 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 } func init() { @@ -107,11 +92,9 @@ func NewChannels(services map[product.ServiceKey]any) (*Channels, error) { return nil, errors.New("server not passed") } ch := &Channels{ - srv: s, - imageProxy: imageproxy.MakeImageProxy(s.platform, s.httpService, s.Log()), - uploadLockMap: map[string]bool{}, - collectionTypes: map[string]string{}, - topicTypes: map[string]string{}, + srv: s, + imageProxy: imageproxy.MakeImageProxy(s.platform, s.httpService, s.Log()), + uploadLockMap: map[string]bool{}, } // To get another service: @@ -208,10 +191,6 @@ func NewChannels(services map[product.ServiceKey]any) (*Channels, error) { services[product.RouterKey] = ch.routerSvc // Setup routes. - pluginsRoute := ch.srv.Router.PathPrefix("/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}").Subrouter() - pluginsRoute.HandleFunc("", ch.ServePluginRequest) - pluginsRoute.HandleFunc("/public/{public_file:.*}", ch.ServePluginPublicRequest) - pluginsRoute.HandleFunc("/{anything:.*}", ch.ServePluginRequest) services[product.PostKey] = &postServiceWrapper{ app: &App{ch: ch}, @@ -243,39 +222,6 @@ func NewChannels(services map[product.ServiceKey]any) (*Channels, error) { } func (ch *Channels) Start() error { - // Start plugins - ctx := request.EmptyContext(ch.srv.Log()) - ch.initPlugins(ctx, *ch.cfgSvc.Config().PluginSettings.Directory, *ch.cfgSvc.Config().PluginSettings.ClientDirectory) - - ch.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 { - ch.srv.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 { - ch.initPlugins(ctx, *cfg.PluginSettings.Directory, *ch.cfgSvc.Config().PluginSettings.ClientDirectory) - } else { - ch.ShutDownPlugins() - } - } - - }) - // TODO: This should be moved to the platform service. if err := ch.srv.platform.EnsureAsymmetricSigningKey(); err != nil { return errors.Wrapf(err, "unable to ensure asymmetric signing key") @@ -289,8 +235,6 @@ func (ch *Channels) Start() error { } func (ch *Channels) Stop() error { - ch.ShutDownPlugins() - ch.dndTaskMut.Lock() if ch.dndTask != nil { ch.dndTask.Cancel() @@ -332,18 +276,18 @@ func (s *hooksService) RegisterHooks(productID string, hooks any) error { return s.ch.srv.hooksManager.AddProduct(productID, hooks) } -func (ch *Channels) RunMultiHook(hookRunnerFunc func(hooks plugin.Hooks) bool, hookId int) { - if env := ch.GetPluginsEnvironment(); env != nil { +func (s *Server) RunMultiHook(hookRunnerFunc func(hooks plugin.Hooks) bool, hookId int) { + if env := s.pluginService.GetPluginsEnvironment(); env != nil { env.RunMultiPluginHook(hookRunnerFunc, hookId) } // run hook for the products - ch.srv.hooksManager.RunMultiHook(hookRunnerFunc, hookId) + s.hooksManager.RunMultiHook(hookRunnerFunc, hookId) } -func (ch *Channels) HooksForPluginOrProduct(id string) (plugin.Hooks, error) { +func (s *Server) HooksForPluginOrProduct(id string) (plugin.Hooks, error) { var hooks plugin.Hooks - if env := ch.GetPluginsEnvironment(); env != nil { + if env := s.pluginService.GetPluginsEnvironment(); env != nil { // we intentionally ignore the error here, because the id can be a product id // we are going to check if we have the hooks or not hooks, _ = env.HooksForPlugin(id) @@ -352,7 +296,7 @@ func (ch *Channels) HooksForPluginOrProduct(id string) (plugin.Hooks, error) { } } - hooks = ch.srv.hooksManager.HooksForProduct(id) + hooks = s.hooksManager.HooksForProduct(id) if hooks != nil { return hooks, nil } diff --git a/app/cluster_handlers.go b/app/cluster_handlers.go index 3fa90abf1e..1cebe5596d 100644 --- a/app/cluster_handlers.go +++ b/app/cluster_handlers.go @@ -16,7 +16,7 @@ func (s *Server) clusterInstallPluginHandler(msg *model.ClusterMessage) { if jsonErr := json.Unmarshal(msg.Data, &data); jsonErr != nil { mlog.Warn("Failed to decode from JSON", mlog.Err(jsonErr)) } - s.Channels().installPluginFromData(data) + s.pluginService.installPluginFromData(data) } func (s *Server) clusterRemovePluginHandler(msg *model.ClusterMessage) { @@ -24,7 +24,7 @@ func (s *Server) clusterRemovePluginHandler(msg *model.ClusterMessage) { if jsonErr := json.Unmarshal(msg.Data, &data); jsonErr != nil { mlog.Warn("Failed to decode from JSON", mlog.Err(jsonErr)) } - s.Channels().removePluginFromData(data) + s.pluginService.removePluginFromData(data) } func (s *Server) clusterPluginEventHandler(msg *model.ClusterMessage) { @@ -44,12 +44,7 @@ func (s *Server) clusterPluginEventHandler(msg *model.ClusterMessage) { return } - channels, ok := s.products["channels"].(*Channels) - if !ok { - return - } - - hooks, err := channels.HooksForPluginOrProduct(pluginID) + hooks, err := s.HooksForPluginOrProduct(pluginID) if err != nil { mlog.Warn("Getting hooks for plugin failed", mlog.String("plugin_id", pluginID), mlog.Err(err)) return diff --git a/app/collection.go b/app/collection.go index 9b895e3bc0..ff489a18db 100644 --- a/app/collection.go +++ b/app/collection.go @@ -10,26 +10,26 @@ import ( "github.com/mattermost/mattermost-server/v6/shared/mlog" ) -func (a *App) registerCollectionAndTopic(pluginID, collectionType, topicType string) error { +func (s *PluginService) registerCollectionAndTopic(pluginID, collectionType, topicType string) error { // we have a race condition due to multiple plugins calling this method - a.ch.collectionAndTopicTypesMut.Lock() - defer a.ch.collectionAndTopicTypesMut.Unlock() + s.collectionAndTopicTypesMut.Lock() + defer s.collectionAndTopicTypesMut.Unlock() // check if collectionType was already registered by other plugin - existingPluginID, ok := a.ch.collectionTypes[collectionType] + existingPluginID, ok := s.collectionTypes[collectionType] if ok && existingPluginID != pluginID { return model.NewAppError("registerCollectionAndTopic", "app.collection.add_collection.exists.app_error", nil, "", http.StatusBadRequest) } // check if topicType was already registered to other collection - existingCollectionType, ok := a.ch.topicTypes[topicType] + existingCollectionType, ok := s.topicTypes[topicType] if ok && existingCollectionType != collectionType { return model.NewAppError("registerCollectionAndTopic", "app.collection.add_topic.exists.app_error", nil, "", http.StatusBadRequest) } - a.ch.collectionTypes[collectionType] = pluginID - a.ch.topicTypes[topicType] = collectionType + s.collectionTypes[collectionType] = pluginID + s.topicTypes[topicType] = collectionType - a.ch.srv.Log().Info("registered collection and topic type", mlog.String("plugin_id", pluginID), mlog.String("collection_type", collectionType), mlog.String("topic_type", topicType)) + s.platform.Log().Info("registered collection and topic type", mlog.String("plugin_id", pluginID), mlog.String("collection_type", collectionType), mlog.String("topic_type", topicType)) return nil } diff --git a/app/download.go b/app/download.go index 449f787c46..56438507cc 100644 --- a/app/download.go +++ b/app/download.go @@ -22,10 +22,10 @@ const ( ) func (a *App) DownloadFromURL(downloadURL string) ([]byte, error) { - return a.Srv().downloadFromURL(downloadURL) + return a.Srv().pluginService.downloadFromURL(downloadURL) } -func (s *Server) downloadFromURL(downloadURL string) ([]byte, error) { +func (s *PluginService) downloadFromURL(downloadURL string) ([]byte, error) { if !model.IsValidHTTPURL(downloadURL) { return nil, errors.Errorf("invalid url %s", downloadURL) } @@ -38,7 +38,7 @@ func (s *Server) downloadFromURL(downloadURL string) ([]byte, error) { return nil, errors.Errorf("insecure url not allowed %s", downloadURL) } - client := s.HTTPService().MakeClient(true) + client := s.httpService.MakeClient(true) client.Timeout = HTTPRequestTimeout var resp *http.Response diff --git a/app/file.go b/app/file.go index 7a66d25010..e1acafa827 100644 --- a/app/file.go +++ b/app/file.go @@ -926,7 +926,7 @@ func (a *App) DoUploadFileExpectModification(c request.CTX, now time.Time, rawTe var rejectionError *model.AppError pluginContext := pluginContext(c) - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { var newBytes bytes.Buffer replacementInfo, rejectionReason := hooks.FileWillBeUploaded(pluginContext, info, bytes.NewReader(data), &newBytes) if rejectionReason != "" { diff --git a/app/integration_action.go b/app/integration_action.go index 4ae7e97e07..51bef2b86f 100644 --- a/app/integration_action.go +++ b/app/integration_action.go @@ -375,10 +375,10 @@ func (w *LocalResponseWriter) WriteHeader(statusCode int) { } func (a *App) doPluginRequest(c *request.Context, method, rawURL string, values url.Values, body []byte) (*http.Response, *model.AppError) { - return a.ch.doPluginRequest(c, method, rawURL, values, body) + return a.ch.srv.pluginService.doPluginRequest(c, method, rawURL, values, body) } -func (ch *Channels) doPluginRequest(c *request.Context, method, rawURL string, values url.Values, body []byte) (*http.Response, *model.AppError) { +func (s *PluginService) doPluginRequest(c *request.Context, method, rawURL string, values url.Values, body []byte) (*http.Response, *model.AppError) { rawURL = strings.TrimPrefix(rawURL, "/") inURL, err := url.Parse(rawURL) if err != nil { @@ -427,7 +427,7 @@ func (ch *Channels) doPluginRequest(c *request.Context, method, rawURL string, v params["plugin_id"] = pluginID r = mux.SetURLVars(r, params) - ch.ServePluginRequest(w, r) + s.ServePluginRequest(w, r) resp := &http.Response{ StatusCode: w.status, diff --git a/app/login.go b/app/login.go index e0854bef37..af322bd13b 100644 --- a/app/login.go +++ b/app/login.go @@ -159,7 +159,7 @@ func (a *App) GetUserForLogin(id, loginId string) (*model.User, *model.AppError) func (a *App) DoLogin(c *request.Context, w http.ResponseWriter, r *http.Request, user *model.User, deviceID string, isMobile, isOAuthUser, isSaml bool) *model.AppError { var rejectionReason string pluginContext := pluginContext(c) - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { rejectionReason = hooks.UserWillLogIn(pluginContext, user) return rejectionReason == "" }, plugin.UserWillLogInID) @@ -225,7 +225,7 @@ func (a *App) DoLogin(c *request.Context, w http.ResponseWriter, r *http.Request } a.Srv().Go(func() { - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.UserHasLoggedIn(pluginContext, user) return true }, plugin.UserHasLoggedInID) diff --git a/app/onboarding.go b/app/onboarding.go index d76525f017..fe40e9d997 100644 --- a/app/onboarding.go +++ b/app/onboarding.go @@ -28,11 +28,6 @@ func (a *App) markAdminOnboardingComplete(c *request.Context) *model.AppError { } func (a *App) CompleteOnboarding(c *request.Context, request *model.CompleteOnboardingRequest) *model.AppError { - pluginsEnvironment := a.Channels().GetPluginsEnvironment() - if pluginsEnvironment == nil { - return a.markAdminOnboardingComplete(c) - } - pluginContext := pluginContext(c) for _, pluginID := range request.InstallPlugins { @@ -41,7 +36,7 @@ func (a *App) CompleteOnboarding(c *request.Context, request *model.CompleteOnbo installRequest := &model.InstallMarketplacePluginRequest{ Id: id, } - _, appErr := a.Channels().InstallMarketplacePlugin(installRequest) + _, appErr := a.Srv().pluginService.InstallMarketplacePlugin(installRequest) if appErr != nil { mlog.Error("Failed to install plugin for onboarding", mlog.String("id", id), mlog.Err(appErr)) return @@ -53,7 +48,7 @@ func (a *App) CompleteOnboarding(c *request.Context, request *model.CompleteOnbo return } - hooks, err := a.ch.HooksForPluginOrProduct(id) + hooks, err := a.Srv().HooksForPluginOrProduct(id) if err != nil { mlog.Warn("Getting hooks for plugin failed", mlog.String("plugin_id", id), mlog.Err(err)) return diff --git a/app/opentracing/opentracing_layer.go b/app/opentracing/opentracing_layer.go index 367d98ec39..3d6c2b5ad5 100644 --- a/app/opentracing/opentracing_layer.go +++ b/app/opentracing/opentracing_layer.go @@ -13125,6 +13125,23 @@ func (a *OpenTracingAppLayer) PluginCommandsForTeam(teamID string) []*model.Comm return resultVar0 } +func (a *OpenTracingAppLayer) PluginService() *app.PluginService { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PluginService") + + a.ctx = newCtx + a.app.Srv().Store().SetContext(newCtx) + defer func() { + a.app.Srv().Store().SetContext(origCtx) + a.ctx = origCtx + }() + + defer span.Finish() + resultVar0 := a.app.PluginService() + + return resultVar0 +} + func (a *OpenTracingAppLayer) PopulateWebConnConfig(s *model.Session, cfg *platform.WebConnConfig, seqVal string) (*platform.WebConnConfig, error) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.PopulateWebConnConfig") diff --git a/app/plugin.go b/app/plugin.go index 679576e723..d985455e5b 100644 --- a/app/plugin.go +++ b/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 } } diff --git a/app/plugin_api.go b/app/plugin_api.go index 5c8ee9fad1..57ee5b4192 100644 --- a/app/plugin_api.go +++ b/app/plugin_api.go @@ -886,7 +886,7 @@ func (api *PluginAPI) DisablePlugin(id string) *model.AppError { } func (api *PluginAPI) RemovePlugin(id string) *model.AppError { - return api.app.Channels().RemovePlugin(id) + return api.app.Srv().pluginService.RemovePlugin(id) } func (api *PluginAPI) GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) { @@ -1235,7 +1235,7 @@ func (api *PluginAPI) GetCloudLimits() (*model.ProductLimits, error) { // RegisterCollectionAndTopic informs the server that this plugin handles // the given collection and topic types. func (api *PluginAPI) RegisterCollectionAndTopic(collectionType, topicType string) error { - return api.app.registerCollectionAndTopic(api.id, collectionType, topicType) + return api.app.Srv().pluginService.registerCollectionAndTopic(api.id, collectionType, topicType) } func (api *PluginAPI) CreateUploadSession(us *model.UploadSession) (*model.UploadSession, error) { diff --git a/app/plugin_api_test.go b/app/plugin_api_test.go index 759613781b..cec5736eb3 100644 --- a/app/plugin_api_test.go +++ b/app/plugin_api_test.go @@ -92,7 +92,7 @@ func setupMultiPluginAPITest(t *testing.T, pluginCodes []string, pluginManifests return app.NewPluginAPI(c, manifest) } - env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(app.Srv()), pluginDir, webappPluginDir, false, app.Log(), nil) + env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(app.Srv().Platform()), pluginDir, webappPluginDir, false, app.Log(), nil) require.NoError(t, err) require.Equal(t, len(pluginCodes), len(pluginIDs)) @@ -119,7 +119,7 @@ func setupMultiPluginAPITest(t *testing.T, pluginCodes []string, pluginManifests }) } - app.ch.SetPluginsEnvironment(env) + app.PluginService().SetPluginsEnvironment(env) return pluginDir } @@ -849,7 +849,7 @@ func TestPluginAPIGetPlugins(t *testing.T) { defer os.RemoveAll(pluginDir) defer os.RemoveAll(webappPluginDir) - env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server), pluginDir, webappPluginDir, false, th.App.Log(), nil) + env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server.Platform()), pluginDir, webappPluginDir, false, th.App.Log(), nil) require.NoError(t, err) pluginIDs := []string{"pluginid1", "pluginid2", "pluginid3"} @@ -866,7 +866,7 @@ func TestPluginAPIGetPlugins(t *testing.T) { require.True(t, activated) pluginManifests = append(pluginManifests, manifest) } - th.App.ch.SetPluginsEnvironment(env) + th.App.PluginService().SetPluginsEnvironment(env) // Deactivate the last one for testing success := env.Deactivate(pluginIDs[len(pluginIDs)-1]) @@ -937,10 +937,10 @@ func TestInstallPlugin(t *testing.T) { return app.NewPluginAPI(c, manifest) } - env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(app.Srv()), pluginDir, webappPluginDir, false, app.Log(), nil) + env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(app.Srv().Platform()), pluginDir, webappPluginDir, false, app.Log(), nil) require.NoError(t, err) - app.ch.SetPluginsEnvironment(env) + app.PluginService().SetPluginsEnvironment(env) backend := filepath.Join(pluginDir, pluginID, "backend.exe") utils.CompileGo(t, pluginCode, backend) @@ -1632,10 +1632,10 @@ func TestAPIMetrics(t *testing.T) { defer os.RemoveAll(pluginDir) defer os.RemoveAll(webappPluginDir) - env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server), pluginDir, webappPluginDir, false, th.App.Log(), metricsMock) + env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server.Platform()), pluginDir, webappPluginDir, false, th.App.Log(), metricsMock) require.NoError(t, err) - th.App.ch.SetPluginsEnvironment(env) + th.App.PluginService().SetPluginsEnvironment(env) pluginID := model.NewId() backend := filepath.Join(pluginDir, pluginID, "backend.exe") @@ -2079,10 +2079,10 @@ func TestRegisterCollectionAndTopic(t *testing.T) { return th.App.NewPluginAPI(th.Context, manifest) } - env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(th.App.Srv()), pluginDir, webappPluginDir, false, th.App.Log(), nil) + env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(th.Server.Platform()), pluginDir, webappPluginDir, false, th.App.Log(), nil) require.NoError(t, err) - th.App.ch.SetPluginsEnvironment(env) + th.App.PluginService().SetPluginsEnvironment(env) pluginID := "testplugin" pluginManifest := `{"id": "testplugin", "server": {"executable": "backend.exe"}}` @@ -2179,10 +2179,10 @@ func TestPluginUploadsAPI(t *testing.T) { newPluginAPI := func(manifest *model.Manifest) plugin.API { return th.App.NewPluginAPI(th.Context, manifest) } - env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(th.App.Srv()), pluginDir, webappPluginDir, false, th.App.Log(), nil) + env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(th.Server.Platform()), pluginDir, webappPluginDir, false, th.App.Log(), nil) require.NoError(t, err) - th.App.ch.SetPluginsEnvironment(env) + th.App.PluginService().SetPluginsEnvironment(env) pluginID := "testplugin" pluginManifest := `{"id": "testplugin", "server": {"executable": "backend.exe"}}` diff --git a/app/plugin_commands.go b/app/plugin_commands.go index e70c69a38a..8d034931e9 100644 --- a/app/plugin_commands.go +++ b/app/plugin_commands.go @@ -22,6 +22,10 @@ type PluginCommand struct { } func (a *App) RegisterPluginCommand(pluginID string, command *model.Command) error { + return a.Srv().pluginService.registerPluginCommand(pluginID, command) +} + +func (s *PluginService) registerPluginCommand(pluginID string, command *model.Command) error { if command.Trigger == "" { return errors.New("invalid command") } @@ -55,10 +59,10 @@ func (a *App) RegisterPluginCommand(pluginID string, command *model.Command) err AutocompleteIconData: command.AutocompleteIconData, } - a.ch.pluginCommandsLock.Lock() - defer a.ch.pluginCommandsLock.Unlock() + s.pluginCommandsLock.Lock() + defer s.pluginCommandsLock.Unlock() - for _, pc := range a.ch.pluginCommands { + for _, pc := range s.pluginCommands { if pc.Command.Trigger == command.Trigger && pc.Command.TeamId == command.TeamId { if pc.PluginId == pluginID { pc.Command = command @@ -67,7 +71,7 @@ func (a *App) RegisterPluginCommand(pluginID string, command *model.Command) err } } - a.ch.pluginCommands = append(a.ch.pluginCommands, &PluginCommand{ + s.pluginCommands = append(s.pluginCommands, &PluginCommand{ Command: command, PluginId: pluginID, }) @@ -75,39 +79,47 @@ func (a *App) RegisterPluginCommand(pluginID string, command *model.Command) err } func (a *App) UnregisterPluginCommand(pluginID, teamID, trigger string) { + a.Srv().pluginService.unregisterPluginCommand(pluginID, teamID, trigger) +} + +func (s *PluginService) unregisterPluginCommand(pluginID, teamID, trigger string) { trigger = strings.ToLower(trigger) - a.ch.pluginCommandsLock.Lock() - defer a.ch.pluginCommandsLock.Unlock() + s.pluginCommandsLock.Lock() + defer s.pluginCommandsLock.Unlock() var remaining []*PluginCommand - for _, pc := range a.ch.pluginCommands { + for _, pc := range s.pluginCommands { if pc.Command.TeamId != teamID || pc.Command.Trigger != trigger { remaining = append(remaining, pc) } } - a.ch.pluginCommands = remaining + s.pluginCommands = remaining } -func (ch *Channels) unregisterPluginCommands(pluginID string) { - ch.pluginCommandsLock.Lock() - defer ch.pluginCommandsLock.Unlock() +func (s *PluginService) unregisterPluginCommands(pluginID string) { + s.pluginCommandsLock.Lock() + defer s.pluginCommandsLock.Unlock() var remaining []*PluginCommand - for _, pc := range ch.pluginCommands { + for _, pc := range s.pluginCommands { if pc.PluginId != pluginID { remaining = append(remaining, pc) } } - ch.pluginCommands = remaining + s.pluginCommands = remaining } func (a *App) PluginCommandsForTeam(teamID string) []*model.Command { - a.ch.pluginCommandsLock.RLock() - defer a.ch.pluginCommandsLock.RUnlock() + return a.Srv().pluginService.PluginCommandsForTeam(teamID) +} + +func (s *PluginService) PluginCommandsForTeam(teamID string) []*model.Command { + s.pluginCommandsLock.RLock() + defer s.pluginCommandsLock.RUnlock() var commands []*model.Command - for _, pc := range a.ch.pluginCommands { + for _, pc := range s.pluginCommands { if pc.Command.TeamId == "" || pc.Command.TeamId == teamID { commands = append(commands, pc.Command) } @@ -115,6 +127,24 @@ func (a *App) PluginCommandsForTeam(teamID string) []*model.Command { return commands } +func (s *PluginService) getPluginCommandFromArgs(args *model.CommandArgs) *PluginCommand { + parts := strings.Split(args.Command, " ") + trigger := parts[0][1:] + trigger = strings.ToLower(trigger) + + var matched *PluginCommand + s.pluginCommandsLock.RLock() + for _, pc := range s.pluginCommands { + if (pc.Command.TeamId == "" || pc.Command.TeamId == args.TeamId) && pc.Command.Trigger == trigger { + matched = pc + break + } + } + s.pluginCommandsLock.RUnlock() + + return matched +} + // tryExecutePluginCommand attempts to run a command provided by a plugin based on the given arguments. If no such // command can be found, returns nil for all arguments. func (a *App) tryExecutePluginCommand(c request.CTX, args *model.CommandArgs) (*model.Command, *model.CommandResponse, *model.AppError) { @@ -122,15 +152,7 @@ func (a *App) tryExecutePluginCommand(c request.CTX, args *model.CommandArgs) (* trigger := parts[0][1:] trigger = strings.ToLower(trigger) - var matched *PluginCommand - a.ch.pluginCommandsLock.RLock() - for _, pc := range a.ch.pluginCommands { - if (pc.Command.TeamId == "" || pc.Command.TeamId == args.TeamId) && pc.Command.Trigger == trigger { - matched = pc - break - } - } - a.ch.pluginCommandsLock.RUnlock() + matched := a.Srv().pluginService.getPluginCommandFromArgs(args) if matched == nil { return nil, nil, nil } diff --git a/app/plugin_commands_test.go b/app/plugin_commands_test.go index e56cf74718..1cf9751d9a 100644 --- a/app/plugin_commands_test.go +++ b/app/plugin_commands_test.go @@ -106,7 +106,7 @@ func TestPluginCommand(t *testing.T) { require.NotEqual(t, "plugin", commands.Trigger) } - th.App.ch.RemovePlugin(pluginIDs[0]) + th.App.PluginService().RemovePlugin(pluginIDs[0]) }) t.Run("re-entrant command registration on config change", func(t *testing.T) { @@ -207,7 +207,7 @@ func TestPluginCommand(t *testing.T) { killed = true } - th.App.ch.RemovePlugin(pluginIDs[0]) + th.App.PluginService().RemovePlugin(pluginIDs[0]) require.False(t, killed, "execute command appears to have deadlocked") }) @@ -285,7 +285,7 @@ func TestPluginCommand(t *testing.T) { require.Equal(t, model.CommandResponseTypeEphemeral, resp.ResponseType) require.Equal(t, "text", resp.Text) - th.App.ch.RemovePlugin(pluginIDs[0]) + th.App.PluginService().RemovePlugin(pluginIDs[0]) }) t.Run("plugin has crashed before execution of command", func(t *testing.T) { tearDown, pluginIDs, activationErrors := SetAppEnvironmentWithPlugins(t, []string{` @@ -329,7 +329,7 @@ func TestPluginCommand(t *testing.T) { require.Nil(t, resp) require.NotNil(t, err) require.Equal(t, err.Id, "model.plugin_command_error.error.app_error") - th.App.ch.RemovePlugin(pluginIDs[0]) + th.App.PluginService().RemovePlugin(pluginIDs[0]) }) t.Run("plugin has crashed due to the execution of the command", func(t *testing.T) { @@ -374,7 +374,7 @@ func TestPluginCommand(t *testing.T) { require.Nil(t, resp) require.NotNil(t, err) require.Equal(t, err.Id, "model.plugin_command_crash.error.app_error") - th.App.ch.RemovePlugin(pluginIDs[0]) + th.App.PluginService().RemovePlugin(pluginIDs[0]) }) t.Run("plugin returning status code 0", func(t *testing.T) { diff --git a/app/plugin_db_driver.go b/app/plugin_db_driver.go index a29fa7467f..0b74577f36 100644 --- a/app/plugin_db_driver.go +++ b/app/plugin_db_driver.go @@ -10,6 +10,7 @@ import ( "sync" "time" + "github.com/mattermost/mattermost-server/v6/app/platform" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/plugin" ) @@ -19,7 +20,7 @@ import ( // a new entry tracked centrally in a map. Further requests operate on the // object ID. type DriverImpl struct { - s *Server + ps *platform.PlatformService connMut sync.RWMutex connMap map[string]*sql.Conn txMut sync.Mutex @@ -30,9 +31,9 @@ type DriverImpl struct { rowsMap map[string]driver.Rows } -func NewDriverImpl(s *Server) *DriverImpl { +func NewDriverImpl(s *platform.PlatformService) *DriverImpl { return &DriverImpl{ - s: s, + ps: s, connMap: make(map[string]*sql.Conn), txMap: make(map[string]driver.Tx), stMap: make(map[string]driver.Stmt), @@ -41,11 +42,11 @@ func NewDriverImpl(s *Server) *DriverImpl { } func (d *DriverImpl) Conn(isMaster bool) (string, error) { - dbFunc := d.s.Platform().Store.GetInternalMasterDB + dbFunc := d.ps.Store.GetInternalMasterDB if !isMaster { - dbFunc = d.s.Platform().Store.GetInternalReplicaDB + dbFunc = d.ps.Store.GetInternalReplicaDB } - timeout := time.Duration(*d.s.Config().SqlSettings.QueryTimeout) * time.Second + timeout := time.Duration(*d.ps.Config().SqlSettings.QueryTimeout) * time.Second ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() conn, err := dbFunc().Conn(ctx) diff --git a/app/plugin_db_driver_test.go b/app/plugin_db_driver_test.go index a2c428fb6f..797f677d59 100644 --- a/app/plugin_db_driver_test.go +++ b/app/plugin_db_driver_test.go @@ -15,7 +15,7 @@ func TestConnCreateTimeout(t *testing.T) { *th.App.Config().SqlSettings.QueryTimeout = 0 - d := NewDriverImpl(th.Server) + d := NewDriverImpl(th.Server.platform) _, err := d.Conn(true) require.Error(t, err) } diff --git a/app/plugin_event.go b/app/plugin_event.go index c30e2d1af5..19d9f1dabc 100644 --- a/app/plugin_event.go +++ b/app/plugin_event.go @@ -9,10 +9,10 @@ import ( "github.com/mattermost/mattermost-server/v6/model" ) -func (ch *Channels) notifyClusterPluginEvent(event model.ClusterEvent, data model.PluginEventData) { +func (s *PluginService) notifyClusterPluginEvent(event model.ClusterEvent, data model.PluginEventData) { buf, _ := json.Marshal(data) - if ch.srv.platform.Cluster() != nil { - ch.srv.platform.Cluster().SendClusterMessage(&model.ClusterMessage{ + if s.platform.Cluster() != nil { + s.platform.Cluster().SendClusterMessage(&model.ClusterMessage{ Event: event, SendType: model.ClusterSendReliable, WaitForAllToSend: true, diff --git a/app/plugin_hooks_test.go b/app/plugin_hooks_test.go index 161994a1ab..105982a7b2 100644 --- a/app/plugin_hooks_test.go +++ b/app/plugin_hooks_test.go @@ -33,10 +33,10 @@ func SetAppEnvironmentWithPlugins(t *testing.T, pluginCode []string, app *App, a webappPluginDir, err := os.MkdirTemp("", "") require.NoError(t, err) - env, err := plugin.NewEnvironment(apiFunc, NewDriverImpl(app.Srv()), pluginDir, webappPluginDir, false, app.Log(), nil) + env, err := plugin.NewEnvironment(apiFunc, NewDriverImpl(app.Srv().Platform()), pluginDir, webappPluginDir, false, app.Log(), nil) require.NoError(t, err) - app.ch.SetPluginsEnvironment(env) + app.PluginService().SetPluginsEnvironment(env) pluginIDs := []string{} activationErrors := []error{} for _, code := range pluginCode { @@ -1030,10 +1030,10 @@ func TestHookMetrics(t *testing.T) { defer os.RemoveAll(pluginDir) defer os.RemoveAll(webappPluginDir) - env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server), pluginDir, webappPluginDir, false, th.App.Log(), metricsMock) + env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server.Platform()), pluginDir, webappPluginDir, false, th.App.Log(), metricsMock) require.NoError(t, err) - th.App.ch.SetPluginsEnvironment(env) + th.App.PluginService().SetPluginsEnvironment(env) pluginID := model.NewId() backend := filepath.Join(pluginDir, pluginID, "backend.exe") @@ -1234,7 +1234,7 @@ func TestHookRunDataRetention(t *testing.T) { require.True(t, th.App.GetPluginsEnvironment().IsActive(pluginID)) hookCalled := false - th.App.Channels().RunMultiHook(func(hooks plugin.Hooks) bool { + th.App.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { n, _ := hooks.RunDataRetention(0, 0) // Ensure return it correct assert.Equal(t, int64(100), n) @@ -1278,7 +1278,7 @@ func TestHookOnSendDailyTelemetry(t *testing.T) { require.True(t, th.App.GetPluginsEnvironment().IsActive(pluginID)) hookCalled := false - th.App.Channels().RunMultiHook(func(hooks plugin.Hooks) bool { + th.App.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.OnSendDailyTelemetry() hookCalled = true @@ -1322,7 +1322,7 @@ func TestHookOnCloudLimitsUpdated(t *testing.T) { require.True(t, th.App.GetPluginsEnvironment().IsActive(pluginID)) hookCalled := false - th.App.Channels().RunMultiHook(func(hooks plugin.Hooks) bool { + th.App.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.OnCloudLimitsUpdated(nil) hookCalled = true diff --git a/app/plugin_install.go b/app/plugin_install.go index 431c3abebe..f0c54a0da4 100644 --- a/app/plugin_install.go +++ b/app/plugin_install.go @@ -58,10 +58,10 @@ const managedPluginFileName = ".filestore" // fileStorePluginFolder is the folder name in the file store of the plugin bundles installed. const fileStorePluginFolder = "plugins" -func (ch *Channels) installPluginFromData(data model.PluginEventData) { +func (s *PluginService) installPluginFromData(data model.PluginEventData) { mlog.Debug("Installing plugin as per cluster message", mlog.String("plugin_id", data.Id)) - pluginSignaturePathMap, appErr := ch.getPluginsFromFolder() + pluginSignaturePathMap, appErr := s.getPluginsFromFolder() if appErr != nil { mlog.Error("Failed to get plugin signatures from filestore. Can't install plugin from data.", mlog.Err(appErr)) return @@ -72,53 +72,53 @@ func (ch *Channels) installPluginFromData(data model.PluginEventData) { return } - reader, appErr := ch.srv.fileReader(plugin.path) - if appErr != nil { - mlog.Error("Failed to open plugin bundle from file store.", mlog.String("bundle", plugin.path), mlog.Err(appErr)) + reader, err := s.fileStore.Reader(plugin.path) + if err != nil { + mlog.Error("Failed to open plugin bundle from file store.", mlog.String("bundle", plugin.path), mlog.Err(err)) return } defer reader.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)) + if *s.platform.Config().PluginSettings.RequirePluginSignature { + signature, err = s.fileStore.Reader(plugin.signaturePath) + if err != nil { + mlog.Error("Failed to open plugin signature from file store.", mlog.Err(err)) return } defer signature.Close() } - manifest, appErr := ch.installPluginLocally(reader, signature, installPluginLocallyAlways) + manifest, appErr := s.installPluginLocally(reader, signature, installPluginLocallyAlways) if appErr != nil { mlog.Error("Failed to sync plugin from file store", mlog.String("bundle", plugin.path), mlog.Err(appErr)) return } - if err := ch.notifyPluginEnabled(manifest); err != nil { - mlog.Error("Failed notify plugin enabled", mlog.Err(err)) + if err2 := s.notifyPluginEnabled(manifest); err2 != nil { + mlog.Error("Failed notify plugin enabled", mlog.Err(err2)) } - if err := ch.notifyPluginStatusesChanged(); err != nil { - mlog.Error("Failed to notify plugin status changed", mlog.Err(err)) + if err2 := s.notifyPluginStatusesChanged(); err2 != nil { + mlog.Error("Failed to notify plugin status changed", mlog.Err(err2)) } } -func (ch *Channels) removePluginFromData(data model.PluginEventData) { +func (s *PluginService) removePluginFromData(data model.PluginEventData) { mlog.Debug("Removing plugin as per cluster message", mlog.String("plugin_id", data.Id)) - if err := ch.removePluginLocally(data.Id); err != nil { + if err := s.removePluginLocally(data.Id); err != nil { mlog.Warn("Failed to remove plugin locally", mlog.Err(err), mlog.String("id", data.Id)) } - if err := ch.notifyPluginStatusesChanged(); err != nil { + if err := s.notifyPluginStatusesChanged(); err != nil { mlog.Warn("failed to notify plugin status changed", mlog.Err(err)) } } // InstallPluginWithSignature verifies and installs plugin. -func (ch *Channels) installPluginWithSignature(pluginFile, signature io.ReadSeeker) (*model.Manifest, *model.AppError) { - return ch.installPlugin(pluginFile, signature, installPluginLocallyAlways) +func (s *PluginService) installPluginWithSignature(pluginFile, signature io.ReadSeeker) (*model.Manifest, *model.AppError) { + return s.installPlugin(pluginFile, signature, installPluginLocallyAlways) } // InstallPlugin unpacks and installs a plugin but does not enable or activate it. @@ -132,40 +132,40 @@ func (a *App) InstallPlugin(pluginFile io.ReadSeeker, replace bool) (*model.Mani } func (a *App) installPlugin(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { - return a.ch.installPlugin(pluginFile, signature, installationStrategy) + return a.ch.srv.pluginService.installPlugin(pluginFile, signature, installationStrategy) } -func (ch *Channels) installPlugin(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { - manifest, appErr := ch.installPluginLocally(pluginFile, signature, installationStrategy) +func (s *PluginService) installPlugin(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { + manifest, appErr := s.installPluginLocally(pluginFile, signature, installationStrategy) if appErr != nil { return nil, appErr } if signature != nil { signature.Seek(0, 0) - if _, appErr = ch.srv.writeFile(signature, getSignatureStorePath(manifest.Id)); appErr != nil { - return nil, model.NewAppError("saveSignature", "app.plugin.store_signature.app_error", nil, "", http.StatusInternalServerError).Wrap(appErr) + if _, err := s.fileStore.WriteFile(signature, getSignatureStorePath(manifest.Id)); err != nil { + return nil, model.NewAppError("saveSignature", "app.plugin.store_signature.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } } // Store bundle in the file store to allow access from other servers. pluginFile.Seek(0, 0) - if _, appErr := ch.srv.writeFile(pluginFile, getBundleStorePath(manifest.Id)); appErr != nil { + if _, appErr := s.fileStore.WriteFile(pluginFile, getBundleStorePath(manifest.Id)); appErr != nil { return nil, model.NewAppError("uploadPlugin", "app.plugin.store_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(appErr) } - ch.notifyClusterPluginEvent( + s.notifyClusterPluginEvent( model.ClusterEventInstallPlugin, model.PluginEventData{ Id: manifest.Id, }, ) - if err := ch.notifyPluginEnabled(manifest); err != nil { + if err := s.notifyPluginEnabled(manifest); err != nil { mlog.Warn("Failed notify plugin enabled", mlog.Err(err)) } - if err := ch.notifyPluginStatusesChanged(); err != nil { + if err := s.notifyPluginStatusesChanged(); err != nil { mlog.Warn("Failed to notify plugin status changed", mlog.Err(err)) } @@ -174,10 +174,10 @@ func (ch *Channels) installPlugin(pluginFile, signature io.ReadSeeker, installat // InstallMarketplacePlugin installs a plugin listed in the marketplace server. It will get the plugin bundle // from the prepackaged folder, if available, or remotely if EnableRemoteMarketplace is true. -func (ch *Channels) InstallMarketplacePlugin(request *model.InstallMarketplacePluginRequest) (*model.Manifest, *model.AppError) { +func (s *PluginService) InstallMarketplacePlugin(request *model.InstallMarketplacePluginRequest) (*model.Manifest, *model.AppError) { var pluginFile, signatureFile io.ReadSeeker - prepackagedPlugin, appErr := ch.getPrepackagedPlugin(request.Id, request.Version) + prepackagedPlugin, appErr := s.getPrepackagedPlugin(request.Id, request.Version) if appErr != nil && appErr.Id != "app.plugin.marketplace_plugins.not_found.app_error" { return nil, appErr } @@ -192,9 +192,9 @@ func (ch *Channels) InstallMarketplacePlugin(request *model.InstallMarketplacePl signatureFile = bytes.NewReader(prepackagedPlugin.Signature) } - if *ch.cfgSvc.Config().PluginSettings.EnableRemoteMarketplace { + if *s.platform.Config().PluginSettings.EnableRemoteMarketplace { var plugin *model.BaseMarketplacePlugin - plugin, appErr = ch.getRemoteMarketplacePlugin(request.Id, request.Version) + plugin, appErr = s.getRemoteMarketplacePlugin(request.Id, request.Version) if appErr != nil { return nil, appErr } @@ -214,7 +214,7 @@ func (ch *Channels) InstallMarketplacePlugin(request *model.InstallMarketplacePl } if prepackagedVersion.LT(marketplaceVersion) { // Always true if no prepackaged plugin was found - downloadedPluginBytes, err := ch.srv.downloadFromURL(plugin.DownloadURL) + downloadedPluginBytes, err := s.downloadFromURL(plugin.DownloadURL) if err != nil { return nil, model.NewAppError("InstallMarketplacePlugin", "app.plugin.install_marketplace_plugin.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -234,7 +234,7 @@ func (ch *Channels) InstallMarketplacePlugin(request *model.InstallMarketplacePl return nil, model.NewAppError("InstallMarketplacePlugin", "app.plugin.marketplace_plugins.signature_not_found.app_error", nil, "", http.StatusInternalServerError) } - manifest, appErr := ch.installPluginWithSignature(pluginFile, signatureFile) + manifest, appErr := s.installPluginWithSignature(pluginFile, signatureFile) if appErr != nil { return nil, appErr } @@ -253,15 +253,15 @@ const ( installPluginLocallyAlways ) -func (ch *Channels) installPluginLocally(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { - pluginsEnvironment := ch.GetPluginsEnvironment() +func (s *PluginService) installPluginLocally(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { + pluginsEnvironment := s.GetPluginsEnvironment() if pluginsEnvironment == nil { return nil, model.NewAppError("installPluginLocally", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } // verify signature if signature != nil { - if err := ch.verifyPlugin(pluginFile, signature); err != nil { + if err := s.verifyPlugin(pluginFile, signature); err != nil { return nil, err } } @@ -277,7 +277,7 @@ func (ch *Channels) installPluginLocally(pluginFile, signature io.ReadSeeker, in return nil, appErr } - manifest, appErr = ch.installExtractedPlugin(manifest, pluginDir, installationStrategy) + manifest, appErr = s.installExtractedPlugin(manifest, pluginDir, installationStrategy) if appErr != nil { return nil, appErr } @@ -312,8 +312,8 @@ func extractPlugin(pluginFile io.ReadSeeker, extractDir string) (*model.Manifest return manifest, extractDir, nil } -func (ch *Channels) installExtractedPlugin(manifest *model.Manifest, fromPluginDir string, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { - pluginsEnvironment := ch.GetPluginsEnvironment() +func (s *PluginService) installExtractedPlugin(manifest *model.Manifest, fromPluginDir string, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { + pluginsEnvironment := s.GetPluginsEnvironment() if pluginsEnvironment == nil { return nil, model.NewAppError("installExtractedPlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } @@ -360,12 +360,12 @@ func (ch *Channels) installExtractedPlugin(manifest *model.Manifest, fromPluginD // Otherwise remove the existing installation prior to install below. mlog.Debug("Removing existing installation of plugin before local install", mlog.String("plugin_id", existingManifest.Id), mlog.String("version", existingManifest.Version)) - if err := ch.removePluginLocally(existingManifest.Id); err != nil { + if err := s.removePluginLocally(existingManifest.Id); err != nil { return nil, model.NewAppError("installExtractedPlugin", "app.plugin.install_id_failed_remove.app_error", nil, "", http.StatusBadRequest) } } - pluginPath := filepath.Join(*ch.cfgSvc.Config().PluginSettings.Directory, manifest.Id) + pluginPath := filepath.Join(*s.platform.Config().PluginSettings.Directory, manifest.Id) err = utils.CopyDir(fromPluginDir, pluginPath) if err != nil { return nil, model.NewAppError("installExtractedPlugin", "app.plugin.mvdir.app_error", nil, "", http.StatusInternalServerError).Wrap(err) @@ -387,9 +387,9 @@ func (ch *Channels) installExtractedPlugin(manifest *model.Manifest, fromPluginD } // Activate the plugin if enabled. - pluginState := ch.cfgSvc.Config().PluginSettings.PluginStates[manifest.Id] + pluginState := s.platform.Config().PluginSettings.PluginStates[manifest.Id] if pluginState != nil && pluginState.Enable { - if hasOverride, enabled := ch.getPluginStateOverride(manifest.Id); hasOverride && !enabled { + if hasOverride, enabled := s.getPluginStateOverride(manifest.Id); hasOverride && !enabled { return manifest, nil } @@ -405,49 +405,49 @@ func (ch *Channels) installExtractedPlugin(manifest *model.Manifest, fromPluginD return manifest, nil } -func (ch *Channels) RemovePlugin(id string) *model.AppError { +func (s *PluginService) RemovePlugin(id string) *model.AppError { // Disable plugin before removal to make sure this // plugin remains disabled on re-install. - if err := ch.disablePlugin(id); err != nil { + if err := s.disablePlugin(id); err != nil { return err } - if err := ch.removePluginLocally(id); err != nil { + if err := s.removePluginLocally(id); err != nil { return err } // Remove bundle from the file store. storePluginFileName := getBundleStorePath(id) - bundleExist, err := ch.srv.fileExists(storePluginFileName) + bundleExist, err := s.fileStore.FileExists(storePluginFileName) if err != nil { return model.NewAppError("removePlugin", "app.plugin.remove_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } if !bundleExist { return nil } - if err = ch.srv.removeFile(storePluginFileName); err != nil { + if err = s.fileStore.RemoveFile(storePluginFileName); err != nil { return model.NewAppError("removePlugin", "app.plugin.remove_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } - if err = ch.removeSignature(id); err != nil { - mlog.Warn("Can't remove signature", mlog.Err(err)) + if err2 := s.removeSignature(id); err2 != nil { + mlog.Warn("Can't remove signature", mlog.Err(err2)) } - ch.notifyClusterPluginEvent( + s.notifyClusterPluginEvent( model.ClusterEventRemovePlugin, model.PluginEventData{ Id: id, }, ) - if err := ch.notifyPluginStatusesChanged(); err != nil { + if err := s.notifyPluginStatusesChanged(); err != nil { mlog.Warn("Failed to notify plugin status changed", mlog.Err(err)) } return nil } -func (ch *Channels) removePluginLocally(id string) *model.AppError { - pluginsEnvironment := ch.GetPluginsEnvironment() +func (s *PluginService) removePluginLocally(id string) *model.AppError { + pluginsEnvironment := s.GetPluginsEnvironment() if pluginsEnvironment == nil { return model.NewAppError("removePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } @@ -473,7 +473,7 @@ func (ch *Channels) removePluginLocally(id string) *model.AppError { pluginsEnvironment.Deactivate(id) pluginsEnvironment.RemovePlugin(id) - ch.unregisterPluginCommands(id) + s.unregisterPluginCommands(id) if err := os.RemoveAll(pluginPath); err != nil { return model.NewAppError("removePlugin", "app.plugin.remove.app_error", nil, "", http.StatusInternalServerError).Wrap(err) @@ -482,9 +482,9 @@ func (ch *Channels) removePluginLocally(id string) *model.AppError { return nil } -func (ch *Channels) removeSignature(pluginID string) *model.AppError { +func (s *PluginService) removeSignature(pluginID string) *model.AppError { filePath := getSignatureStorePath(pluginID) - exists, err := ch.srv.fileExists(filePath) + exists, err := s.fileStore.FileExists(filePath) if err != nil { return model.NewAppError("removeSignature", "app.plugin.remove_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -492,7 +492,7 @@ func (ch *Channels) removeSignature(pluginID string) *model.AppError { mlog.Debug("no plugin signature to remove", mlog.String("plugin_id", pluginID)) return nil } - if err = ch.srv.removeFile(filePath); err != nil { + if err = s.fileStore.RemoveFile(filePath); err != nil { return model.NewAppError("removeSignature", "app.plugin.remove_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } return nil diff --git a/app/plugin_install_test.go b/app/plugin_install_test.go index a3eb6cfb2d..ca15747865 100644 --- a/app/plugin_install_test.go +++ b/app/plugin_install_test.go @@ -73,7 +73,7 @@ func TestInstallPluginLocally(t *testing.T) { th := Setup(t) defer th.TearDown() - actualManifest, appErr := th.App.ch.installPluginLocally(&nilReadSeeker{}, nil, installPluginLocallyOnlyIfNew) + actualManifest, appErr := th.App.PluginService().installPluginLocally(&nilReadSeeker{}, nil, installPluginLocallyOnlyIfNew) require.NotNil(t, appErr) assert.Equal(t, "app.plugin.extract.app_error", appErr.Id, appErr.Error()) require.Nil(t, actualManifest) @@ -87,7 +87,7 @@ func TestInstallPluginLocally(t *testing.T) { {"test", "test file"}, }) - actualManifest, appErr := th.App.ch.installPluginLocally(reader, nil, installPluginLocallyOnlyIfNew) + actualManifest, appErr := th.App.PluginService().installPluginLocally(reader, nil, installPluginLocallyOnlyIfNew) require.NotNil(t, appErr) assert.Equal(t, "app.plugin.manifest.app_error", appErr.Id, appErr.Error()) require.Nil(t, actualManifest) @@ -106,7 +106,7 @@ func TestInstallPluginLocally(t *testing.T) { {"plugin.json", string(manifestJSON)}, }) - actualManifest, appError := th.App.ch.installPluginLocally(reader, nil, installationStrategy) + actualManifest, appError := th.App.PluginService().installPluginLocally(reader, nil, installationStrategy) if actualManifest != nil { require.Equal(t, manifest, actualManifest) } @@ -134,7 +134,7 @@ func TestInstallPluginLocally(t *testing.T) { require.NoError(t, err) for _, bundleInfo := range bundleInfos { - err := th.App.ch.removePluginLocally(bundleInfo.Manifest.Id) + err := th.App.PluginService().removePluginLocally(bundleInfo.Manifest.Id) require.Nilf(t, err, "failed to remove existing plugin %s", bundleInfo.Manifest.Id) } } diff --git a/app/plugin_requests.go b/app/plugin_requests.go index 1ccc966822..208adb11f4 100644 --- a/app/plugin_requests.go +++ b/app/plugin_requests.go @@ -20,16 +20,16 @@ import ( "github.com/mattermost/mattermost-server/v6/utils" ) -func (ch *Channels) ServePluginRequest(w http.ResponseWriter, r *http.Request) { +func (s *PluginService) ServePluginRequest(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) - if handler, ok := ch.routerSvc.getHandler(params["plugin_id"]); ok { - ch.servePluginRequest(w, r, func(*plugin.Context, http.ResponseWriter, *http.Request) { + if handler, ok := s.channels.routerSvc.getHandler(params["plugin_id"]); ok { + s.servePluginRequest(w, r, func(*plugin.Context, http.ResponseWriter, *http.Request) { handler.ServeHTTP(w, r) }) return } - pluginsEnvironment := ch.GetPluginsEnvironment() + pluginsEnvironment := s.GetPluginsEnvironment() if pluginsEnvironment == nil { err := model.NewAppError("ServePluginRequest", "app.plugin.disabled.app_error", nil, "Enable plugins to serve plugin requests", http.StatusNotImplemented) mlog.Error(err.Error()) @@ -49,11 +49,11 @@ func (ch *Channels) ServePluginRequest(w http.ResponseWriter, r *http.Request) { return } - ch.servePluginRequest(w, r, hooks.ServeHTTP) + s.servePluginRequest(w, r, hooks.ServeHTTP) } func (a *App) ServeInterPluginRequest(w http.ResponseWriter, r *http.Request, sourcePluginId, destinationPluginId string) { - pluginsEnvironment := a.ch.GetPluginsEnvironment() + pluginsEnvironment := a.ch.srv.pluginService.GetPluginsEnvironment() if pluginsEnvironment == nil { err := model.NewAppError("ServeInterPluginRequest", "app.plugin.disabled.app_error", nil, "Plugin environment not found.", http.StatusNotImplemented) a.Log().Error(err.Error()) @@ -87,7 +87,7 @@ func (a *App) ServeInterPluginRequest(w http.ResponseWriter, r *http.Request, so // ServePluginPublicRequest serves public plugin files // at the URL http(s)://$SITE_URL/plugins/$PLUGIN_ID/public/{anything} -func (ch *Channels) ServePluginPublicRequest(w http.ResponseWriter, r *http.Request) { +func (s *PluginService) ServePluginPublicRequest(w http.ResponseWriter, r *http.Request) { if strings.HasSuffix(r.URL.Path, "/") { http.NotFound(w, r) return @@ -97,7 +97,7 @@ func (ch *Channels) ServePluginPublicRequest(w http.ResponseWriter, r *http.Requ vars := mux.Vars(r) pluginID := vars["plugin_id"] - pluginsEnv := ch.GetPluginsEnvironment() + pluginsEnv := s.GetPluginsEnvironment() // Check if someone has nullified the pluginsEnv in the meantime if pluginsEnv == nil { @@ -121,11 +121,11 @@ func (ch *Channels) ServePluginPublicRequest(w http.ResponseWriter, r *http.Requ http.ServeFile(w, r, publicFile) } -func (ch *Channels) servePluginRequest(w http.ResponseWriter, r *http.Request, handler func(*plugin.Context, http.ResponseWriter, *http.Request)) { +func (s *PluginService) servePluginRequest(w http.ResponseWriter, r *http.Request, handler func(*plugin.Context, http.ResponseWriter, *http.Request)) { token := "" context := &plugin.Context{ RequestId: model.NewId(), - IPAddress: utils.GetIPAddress(r, ch.cfgSvc.Config().ServiceSettings.TrustedProxyIPHeader), + IPAddress: utils.GetIPAddress(r, s.platform.Config().ServiceSettings.TrustedProxyIPHeader), AcceptLanguage: r.Header.Get("Accept-Language"), UserAgent: r.UserAgent(), } @@ -148,8 +148,8 @@ func (ch *Channels) servePluginRequest(w http.ResponseWriter, r *http.Request, h r.Header.Del("Mattermost-User-Id") if token != "" { - session, err := New(ServerConnector(ch)).GetSession(token) - defer ch.srv.platform.ReturnSessionToPool(session) + session, err := New(ServerConnector(s.channels)).GetSession(token) + defer s.platform.ReturnSessionToPool(session) csrfCheckPassed := false @@ -190,7 +190,7 @@ func (ch *Channels) servePluginRequest(w http.ResponseWriter, r *http.Request, h mlog.String("user_id", userID), } - if *ch.cfgSvc.Config().ServiceSettings.ExperimentalStrictCSRFEnforcement { + if *s.platform.Config().ServiceSettings.ExperimentalStrictCSRFEnforcement { mlog.Warn(csrfErrorMessage, fields...) } else { mlog.Debug(csrfErrorMessage, fields...) @@ -219,7 +219,7 @@ func (ch *Channels) servePluginRequest(w http.ResponseWriter, r *http.Request, h params := mux.Vars(r) - subpath, _ := utils.GetSubpathFromConfig(ch.cfgSvc.Config()) + subpath, _ := utils.GetSubpathFromConfig(s.platform.Config()) newQuery := r.URL.Query() newQuery.Del("access_token") diff --git a/app/plugin_requests_test.go b/app/plugin_requests_test.go index c41c70be6d..e457d8e5f1 100644 --- a/app/plugin_requests_test.go +++ b/app/plugin_requests_test.go @@ -24,7 +24,7 @@ func TestServePluginPublicRequest(t *testing.T) { require.NoError(t, err) rr := httptest.NewRecorder() - handler := http.HandlerFunc(th.App.ch.ServePluginPublicRequest) + handler := http.HandlerFunc(th.App.PluginService().ServePluginPublicRequest) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusNotFound, rr.Code) diff --git a/app/plugin_shutdown_test.go b/app/plugin_shutdown_test.go index 293d882f1f..1c77fc3814 100644 --- a/app/plugin_shutdown_test.go +++ b/app/plugin_shutdown_test.go @@ -63,7 +63,7 @@ func TestPluginShutdownTest(t *testing.T) { done := make(chan bool) go func() { defer close(done) - th.App.ch.ShutDownPlugins() + th.App.PluginService().ShutDownPlugins() }() select { diff --git a/app/plugin_signature.go b/app/plugin_signature.go index 0903aa08fc..928a9687b8 100644 --- a/app/plugin_signature.go +++ b/app/plugin_signature.go @@ -73,16 +73,16 @@ func (a *App) DeletePublicKey(name string) *model.AppError { // VerifyPlugin checks that the given signature corresponds to the given plugin and matches a trusted certificate. func (a *App) VerifyPlugin(plugin, signature io.ReadSeeker) *model.AppError { - return a.ch.verifyPlugin(plugin, signature) + return a.ch.srv.pluginService.verifyPlugin(plugin, signature) } -func (ch *Channels) verifyPlugin(plugin, signature io.ReadSeeker) *model.AppError { +func (s *PluginService) verifyPlugin(plugin, signature io.ReadSeeker) *model.AppError { if err := verifySignature(bytes.NewReader(mattermostPluginPublicKey), plugin, signature); err == nil { return nil } - publicKeys := ch.cfgSvc.Config().PluginSettings.SignaturePublicKeyFiles + publicKeys := s.platform.Config().PluginSettings.SignaturePublicKeyFiles for _, pk := range publicKeys { - pkBytes, appErr := ch.srv.getPublicKey(pk) + pkBytes, appErr := s.platform.GetConfigFile(pk) if appErr != nil { mlog.Warn("Unable to get public key for ", mlog.String("filename", pk)) continue diff --git a/app/plugin_statuses.go b/app/plugin_statuses.go index 399d58e5b2..2b27d7520c 100644 --- a/app/plugin_statuses.go +++ b/app/plugin_statuses.go @@ -10,8 +10,8 @@ import ( ) // GetPluginStatus returns the status for a plugin installed on this server. -func (ch *Channels) GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) { - pluginsEnvironment := ch.GetPluginsEnvironment() +func (s *PluginService) GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) { + pluginsEnvironment := s.GetPluginsEnvironment() if pluginsEnvironment == nil { return nil, model.NewAppError("GetPluginStatus", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } @@ -24,8 +24,8 @@ func (ch *Channels) GetPluginStatus(id string) (*model.PluginStatus, *model.AppE for _, status := range pluginStatuses { if status.PluginId == id { // Add our cluster ID - if ch.srv.platform.Cluster() != nil { - status.ClusterId = ch.srv.platform.Cluster().GetClusterId() + if s.platform.Cluster() != nil { + status.ClusterId = s.platform.Cluster().GetClusterId() } return status, nil @@ -37,12 +37,12 @@ func (ch *Channels) GetPluginStatus(id string) (*model.PluginStatus, *model.AppE // GetPluginStatus returns the status for a plugin installed on this server. func (a *App) GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) { - return a.ch.GetPluginStatus(id) + return a.ch.srv.pluginService.GetPluginStatus(id) } // GetPluginStatuses returns the status for plugins installed on this server. -func (ch *Channels) GetPluginStatuses() (model.PluginStatuses, *model.AppError) { - pluginsEnvironment := ch.GetPluginsEnvironment() +func (s *PluginService) GetPluginStatuses() (model.PluginStatuses, *model.AppError) { + pluginsEnvironment := s.GetPluginsEnvironment() if pluginsEnvironment == nil { return nil, model.NewAppError("GetPluginStatuses", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } @@ -54,8 +54,8 @@ func (ch *Channels) GetPluginStatuses() (model.PluginStatuses, *model.AppError) // Add our cluster ID for _, status := range pluginStatuses { - if ch.srv.platform.Cluster() != nil { - status.ClusterId = ch.srv.platform.Cluster().GetClusterId() + if s.platform.Cluster() != nil { + status.ClusterId = s.platform.Cluster().GetClusterId() } else { status.ClusterId = "" } @@ -66,22 +66,22 @@ func (ch *Channels) GetPluginStatuses() (model.PluginStatuses, *model.AppError) // GetPluginStatuses returns the status for plugins installed on this server. func (a *App) GetPluginStatuses() (model.PluginStatuses, *model.AppError) { - return a.ch.GetPluginStatuses() + return a.ch.srv.pluginService.GetPluginStatuses() } // GetClusterPluginStatuses returns the status for plugins installed anywhere in the cluster. func (a *App) GetClusterPluginStatuses() (model.PluginStatuses, *model.AppError) { - return a.ch.getClusterPluginStatuses() + return a.ch.srv.pluginService.getClusterPluginStatuses() } -func (ch *Channels) getClusterPluginStatuses() (model.PluginStatuses, *model.AppError) { - pluginStatuses, err := ch.GetPluginStatuses() +func (s *PluginService) getClusterPluginStatuses() (model.PluginStatuses, *model.AppError) { + pluginStatuses, err := s.GetPluginStatuses() if err != nil { return nil, err } - if ch.srv.platform.Cluster() != nil && *ch.cfgSvc.Config().ClusterSettings.Enable { - clusterPluginStatuses, err := ch.srv.platform.Cluster().GetPluginStatuses() + if s.platform.Cluster() != nil && *s.platform.Config().ClusterSettings.Enable { + clusterPluginStatuses, err := s.platform.Cluster().GetPluginStatuses() if err != nil { return nil, model.NewAppError("GetClusterPluginStatuses", "app.plugin.get_cluster_plugin_statuses.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -92,8 +92,8 @@ func (ch *Channels) getClusterPluginStatuses() (model.PluginStatuses, *model.App return pluginStatuses, nil } -func (ch *Channels) notifyPluginStatusesChanged() error { - pluginStatuses, err := ch.getClusterPluginStatuses() +func (s *PluginService) notifyPluginStatusesChanged() error { + pluginStatuses, err := s.getClusterPluginStatuses() if err != nil { return err } @@ -102,7 +102,7 @@ func (ch *Channels) notifyPluginStatusesChanged() error { message := model.NewWebSocketEvent(model.WebsocketEventPluginStatusesChanged, "", "", "", nil, "") message.Add("plugin_statuses", pluginStatuses) message.GetBroadcast().ContainsSensitiveData = true - ch.srv.platform.Publish(message) + s.platform.Publish(message) return nil } diff --git a/app/plugin_test.go b/app/plugin_test.go index 57802c67ac..0d3ec65431 100644 --- a/app/plugin_test.go +++ b/app/plugin_test.go @@ -346,7 +346,7 @@ func TestServePluginRequest(t *testing.T) { w := httptest.NewRecorder() r := httptest.NewRequest("GET", "/plugins/foo/bar", nil) - th.App.ch.ServePluginRequest(w, r) + th.App.PluginService().ServePluginRequest(w, r) assert.Equal(t, http.StatusNotImplemented, w.Result().StatusCode) } @@ -390,7 +390,7 @@ func TestPrivateServePluginRequest(t *testing.T) { request = mux.SetURLVars(request, map[string]string{"plugin_id": "id"}) - th.App.ch.servePluginRequest(recorder, request, handler) + th.App.PluginService().servePluginRequest(recorder, request, handler) }) } @@ -413,7 +413,7 @@ func TestHandlePluginRequest(t *testing.T) { var assertions func(*http.Request) router := mux.NewRouter() router.HandleFunc("/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}/{anything:.*}", func(_ http.ResponseWriter, r *http.Request) { - th.App.ch.servePluginRequest(nil, r, func(_ *plugin.Context, _ http.ResponseWriter, r *http.Request) { + th.App.PluginService().servePluginRequest(nil, r, func(_ *plugin.Context, _ http.ResponseWriter, r *http.Request) { assertions(r) }) }) @@ -625,7 +625,7 @@ func TestPluginSync(t *testing.T) { appErr = th.App.DeletePublicKey("pub_key") checkNoError(t, appErr) - appErr = th.App.ch.RemovePlugin("testplugin") + appErr = th.App.PluginService().RemovePlugin("testplugin") checkNoError(t, appErr) }) }) @@ -642,7 +642,7 @@ func TestChannelsPluginsInit(t *testing.T) { path, _ := fileutils.FindDir("tests") require.NotPanics(t, func() { - th.Server.Channels().initPlugins(ctx, path, path) + th.Server.pluginService.initPlugins(ctx, path, path) }) } @@ -763,7 +763,7 @@ func TestPluginPanicLogs(t *testing.T) { th.TestLogger.Flush() // We shutdown plugins first so that the read on the log buffer is race-free. - th.App.ch.ShutDownPlugins() + th.App.PluginService().ShutDownPlugins() tearDown() testlib.AssertLog(t, th.LogBuffer, mlog.LvlDebug.Name, "panic: some text from panic") @@ -831,7 +831,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { require.NoError(t, err) require.NotNil(t, pluginBytes) - manifest, appErr := th.App.ch.installPluginLocally(bytes.NewReader(pluginBytes), nil, installPluginLocallyAlways) + manifest, appErr := th.App.PluginService().installPluginLocally(bytes.NewReader(pluginBytes), nil, installPluginLocallyAlways) require.Nil(t, appErr) require.Equal(t, "testplugin", manifest.Id) @@ -848,7 +848,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { *cfg.PluginSettings.EnableRemoteMarketplace = false }) - plugins := th.App.ch.processPrepackagedPlugins(prepackagedPluginsDir) + plugins := th.App.PluginService().processPrepackagedPlugins(prepackagedPluginsDir) require.Len(t, plugins, 1) require.Equal(t, plugins[0].Manifest.Id, "testplugin") require.Empty(t, plugins[0].Signature, 0) @@ -858,7 +858,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { require.Len(t, pluginStatus, 1) require.Equal(t, pluginStatus[0].PluginId, "testplugin") - appErr = th.App.ch.RemovePlugin("testplugin") + appErr = th.App.PluginService().RemovePlugin("testplugin") checkNoError(t, appErr) pluginStatus, err = env.Statuses() @@ -875,7 +875,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { env := th.App.GetPluginsEnvironment() - plugins := th.App.ch.processPrepackagedPlugins(prepackagedPluginsDir) + plugins := th.App.PluginService().processPrepackagedPlugins(prepackagedPluginsDir) require.Len(t, plugins, 1) require.Equal(t, plugins[0].Manifest.Id, "testplugin") require.Empty(t, plugins[0].Signature, 0) @@ -908,7 +908,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { err = testlib.CopyFile(testPlugin2SignaturePath, filepath.Join(prepackagedPluginsDir, "testplugin2.tar.gz.sig")) require.NoError(t, err) - plugins := th.App.ch.processPrepackagedPlugins(prepackagedPluginsDir) + plugins := th.App.PluginService().processPrepackagedPlugins(prepackagedPluginsDir) require.Len(t, plugins, 2) require.Contains(t, []string{"testplugin", "testplugin2"}, plugins[0].Manifest.Id) require.NotEmpty(t, plugins[0].Signature) @@ -939,7 +939,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { require.NoError(t, err) require.NotNil(t, pluginBytes) - manifest, appErr := th.App.ch.installPluginLocally(bytes.NewReader(pluginBytes), nil, installPluginLocallyAlways) + manifest, appErr := th.App.PluginService().installPluginLocally(bytes.NewReader(pluginBytes), nil, installPluginLocallyAlways) require.Nil(t, appErr) require.Equal(t, "testplugin", manifest.Id) @@ -957,7 +957,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { err = testlib.CopyFile(testPlugin2SignaturePath, filepath.Join(prepackagedPluginsDir, "testplugin2.tar.gz.sig")) require.NoError(t, err) - plugins := th.App.ch.processPrepackagedPlugins(prepackagedPluginsDir) + plugins := th.App.PluginService().processPrepackagedPlugins(prepackagedPluginsDir) require.Len(t, plugins, 2) require.Contains(t, []string{"testplugin", "testplugin2"}, plugins[0].Manifest.Id) require.NotEmpty(t, plugins[0].Signature) @@ -969,7 +969,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { require.Len(t, pluginStatus, 1) require.Equal(t, pluginStatus[0].PluginId, "testplugin") - appErr = th.App.ch.RemovePlugin("testplugin") + appErr = th.App.PluginService().RemovePlugin("testplugin") checkNoError(t, appErr) pluginStatus, err = env.Statuses() @@ -994,7 +994,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { err = testlib.CopyFile(testPlugin2SignaturePath, filepath.Join(prepackagedPluginsDir, "testplugin2.tar.gz.sig")) require.NoError(t, err) - plugins := th.App.ch.processPrepackagedPlugins(prepackagedPluginsDir) + plugins := th.App.PluginService().processPrepackagedPlugins(prepackagedPluginsDir) require.Len(t, plugins, 2) require.Contains(t, []string{"testplugin", "testplugin2"}, plugins[0].Manifest.Id) require.NotEmpty(t, plugins[0].Signature) @@ -1071,14 +1071,14 @@ func TestGetPluginStateOverride(t *testing.T) { defer th.TearDown() t.Run("no override", func(t *testing.T) { - overrides, value := th.App.ch.getPluginStateOverride("focalboard") + overrides, value := th.App.PluginService().getPluginStateOverride("focalboard") require.False(t, overrides) require.False(t, value) }) t.Run("calls override", func(t *testing.T) { t.Run("on-prem", func(t *testing.T) { - overrides, value := th.App.ch.getPluginStateOverride("com.mattermost.calls") + overrides, value := th.App.PluginService().getPluginStateOverride("com.mattermost.calls") require.False(t, overrides) require.False(t, value) }) @@ -1086,7 +1086,7 @@ func TestGetPluginStateOverride(t *testing.T) { t.Run("Cloud, without enabled flag", func(t *testing.T) { os.Setenv("MM_CLOUD_INSTALLATION_ID", "test") defer os.Unsetenv("MM_CLOUD_INSTALLATION_ID") - overrides, value := th.App.ch.getPluginStateOverride("com.mattermost.calls") + overrides, value := th.App.PluginService().getPluginStateOverride("com.mattermost.calls") require.False(t, overrides) require.False(t, value) }) @@ -1100,7 +1100,7 @@ func TestGetPluginStateOverride(t *testing.T) { th2 := Setup(t) defer th2.TearDown() - overrides, value := th2.App.ch.getPluginStateOverride("com.mattermost.calls") + overrides, value := th2.App.PluginService().getPluginStateOverride("com.mattermost.calls") require.False(t, overrides) require.False(t, value) }) @@ -1114,7 +1114,7 @@ func TestGetPluginStateOverride(t *testing.T) { th2 := Setup(t) defer th2.TearDown() - overrides, value := th2.App.ch.getPluginStateOverride("com.mattermost.calls") + overrides, value := th2.App.PluginService().getPluginStateOverride("com.mattermost.calls") require.True(t, overrides) require.False(t, value) }) @@ -1126,7 +1126,7 @@ func TestGetPluginStateOverride(t *testing.T) { th2 := Setup(t) defer th2.TearDown() - overrides, value := th2.App.ch.getPluginStateOverride("com.mattermost.calls") + overrides, value := th2.App.PluginService().getPluginStateOverride("com.mattermost.calls") require.True(t, overrides) require.False(t, value) }) @@ -1134,7 +1134,7 @@ func TestGetPluginStateOverride(t *testing.T) { t.Run("apps override", func(t *testing.T) { t.Run("without enabled flag", func(t *testing.T) { - overrides, value := th.App.ch.getPluginStateOverride("com.mattermost.apps") + overrides, value := th.App.PluginService().getPluginStateOverride("com.mattermost.apps") require.False(t, overrides) require.False(t, value) }) @@ -1146,7 +1146,7 @@ func TestGetPluginStateOverride(t *testing.T) { th2 := Setup(t) defer th2.TearDown() - overrides, value := th2.App.ch.getPluginStateOverride("com.mattermost.apps") + overrides, value := th2.App.PluginService().getPluginStateOverride("com.mattermost.apps") require.True(t, overrides) require.False(t, value) }) diff --git a/app/post.go b/app/post.go index 095494d080..b98cb13759 100644 --- a/app/post.go +++ b/app/post.go @@ -269,7 +269,7 @@ func (a *App) CreatePost(c request.CTX, post *model.Post, channel *model.Channel } var rejectionError *model.AppError pluginContext := pluginContext(c) - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { replacementPost, rejectionReason := hooks.MessageWillBePosted(pluginContext, post.ForPlugin()) if rejectionReason != "" { id := "Post rejected by plugin. " + rejectionReason @@ -328,7 +328,7 @@ func (a *App) CreatePost(c request.CTX, post *model.Post, channel *model.Channel // and to remove the non-GOB-encodable Metadata from it. pluginPost := rpost.ForPlugin() a.Srv().Go(func() { - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.MessageHasBeenPosted(pluginContext, pluginPost) return true }, plugin.MessageHasBeenPostedID) @@ -655,7 +655,7 @@ func (a *App) UpdatePost(c *request.Context, post *model.Post, safeUpdate bool) var rejectionReason string pluginContext := pluginContext(c) - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { newPost, rejectionReason = hooks.MessageWillBeUpdated(pluginContext, newPost.ForPlugin(), oldPost.ForPlugin()) return post != nil }, plugin.MessageWillBeUpdatedID) @@ -680,7 +680,7 @@ func (a *App) UpdatePost(c *request.Context, post *model.Post, safeUpdate bool) pluginOldPost := oldPost.ForPlugin() pluginNewPost := newPost.ForPlugin() a.Srv().Go(func() { - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.MessageHasBeenUpdated(pluginContext, pluginNewPost, pluginOldPost) return true }, plugin.MessageHasBeenUpdatedID) diff --git a/app/reaction.go b/app/reaction.go index fc6d54699f..c79036b443 100644 --- a/app/reaction.go +++ b/app/reaction.go @@ -45,7 +45,7 @@ func (a *App) SaveReactionForPost(c *request.Context, reaction *model.Reaction) pluginContext := pluginContext(c) a.Srv().Go(func() { - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.ReactionHasBeenAdded(pluginContext, reaction) return true }, plugin.ReactionHasBeenAddedID) @@ -142,7 +142,7 @@ func (a *App) DeleteReactionForPost(c *request.Context, reaction *model.Reaction pluginContext := pluginContext(c) a.Srv().Go(func() { - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.ReactionHasBeenRemoved(pluginContext, reaction) return true }, plugin.ReactionHasBeenRemovedID) diff --git a/app/server.go b/app/server.go index fbd433a6db..22f9d24c1c 100644 --- a/app/server.go +++ b/app/server.go @@ -119,6 +119,7 @@ type Server struct { telemetryService *telemetry.TelemetryService userService *users.UserService teamService *teams.TeamService + pluginService *PluginService serviceMux sync.RWMutex remoteClusterService remotecluster.RemoteClusterServiceIFace @@ -718,6 +719,10 @@ func (s *Server) Shutdown() { } } + // Stop the plugin service, we need to stop plugin service before stopping the + // product as products are being consumed by this service. + s.pluginService.ShutDownPlugins() + // Stop products. // This needs to happen last because products are dependent // on parent services. @@ -824,11 +829,18 @@ func stripPort(hostport string) string { func (s *Server) Start() error { // Start products. // This needs to happen before because products are dependent on the HTTP server. - // make sure channels starts first if err := s.products["channels"].Start(); err != nil { return errors.Wrap(err, "Unable to start channels") } + + // This should actually be started after products, but we have a product hooks + // dependency for now, once that get sorted out, this should be moved to the appropriate + // order. + if err := s.InitializePluginService(); err != nil { + return errors.Wrap(err, "Unable to start plugin service") + } + for name, product := range s.products { if name == "channels" { continue diff --git a/app/team.go b/app/team.go index 222d10e442..5724f683c2 100644 --- a/app/team.go +++ b/app/team.go @@ -853,7 +853,7 @@ func (a *App) JoinUserToTeam(c request.CTX, team *model.Team, user *model.User, a.Srv().Go(func() { pluginContext := pluginContext(c) - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.UserHasJoinedTeam(pluginContext, teamMember, actor) return true }, plugin.UserHasJoinedTeamID) @@ -1225,7 +1225,7 @@ func (a *App) postProcessTeamMemberLeave(c request.CTX, teamMember *model.TeamMe a.Srv().Go(func() { pluginContext := pluginContext(c) - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.UserHasLeftTeam(pluginContext, teamMember, actor) return true }, plugin.UserHasLeftTeamID) diff --git a/app/upload.go b/app/upload.go index 318e3ede89..b60f9be57b 100644 --- a/app/upload.go +++ b/app/upload.go @@ -62,7 +62,7 @@ func (a *App) runPluginsHook(c request.CTX, info *model.FileInfo, file io.Reader var rejErr *model.AppError var once sync.Once pluginContext := pluginContext(c) - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { once.Do(func() { hookHasRunCh <- struct{}{} }) diff --git a/app/user.go b/app/user.go index 6e12bda34b..81faa5b57d 100644 --- a/app/user.go +++ b/app/user.go @@ -310,7 +310,7 @@ func (a *App) createUserOrGuest(c request.CTX, user *model.User, guest bool) (*m pluginContext := pluginContext(c) a.Srv().Go(func() { - a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { + a.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.UserHasBeenCreated(pluginContext, ruser) return true }, plugin.UserHasBeenCreatedID) diff --git a/app/web_conn.go b/app/web_conn.go index cdf59eb31e..1db5866e0a 100644 --- a/app/web_conn.go +++ b/app/web_conn.go @@ -16,5 +16,5 @@ func (a *App) PopulateWebConnConfig(s *model.Session, cfg *platform.WebConnConfi // NewWebConn returns a new WebConn instance. func (a *App) NewWebConn(cfg *platform.WebConnConfig) *platform.WebConn { - return a.Srv().Platform().NewWebConn(cfg, a, a.ch) + return a.Srv().Platform().NewWebConn(cfg, a, a.Srv()) } diff --git a/cmd/mattermost/commands/init.go b/cmd/mattermost/commands/init.go index e93d9640fe..a90b4b02b3 100644 --- a/cmd/mattermost/commands/init.go +++ b/cmd/mattermost/commands/init.go @@ -7,7 +7,6 @@ import ( "github.com/spf13/cobra" "github.com/mattermost/mattermost-server/v6/app" - "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/shared/i18n" @@ -21,7 +20,11 @@ func initDBCommandContextCobra(command *cobra.Command, readOnlyConfigStore bool) panic(err) } - a.InitPlugins(request.EmptyContext(a.Log()), *a.Config().PluginSettings.Directory, *a.Config().PluginSettings.ClientDirectory) + err = a.Srv().InitializePluginService() + if err != nil { + return nil, err + } + a.DoAppMigrations() return a, nil diff --git a/web/web_test.go b/web/web_test.go index 4214649031..b57e3888bd 100644 --- a/web/web_test.go +++ b/web/web_test.go @@ -282,7 +282,7 @@ func TestPublicFilesRequest(t *testing.T) { defer os.RemoveAll(pluginDir) defer os.RemoveAll(webappPluginDir) - env, err := plugin.NewEnvironment(th.NewPluginAPI, app.NewDriverImpl(th.Server), pluginDir, webappPluginDir, false, th.App.Log(), nil) + env, err := plugin.NewEnvironment(th.NewPluginAPI, app.NewDriverImpl(th.Server.Platform()), pluginDir, webappPluginDir, false, th.App.Log(), nil) require.NoError(t, err) pluginID := "com.mattermost.sample" @@ -329,7 +329,7 @@ func TestPublicFilesRequest(t *testing.T) { require.NotNil(t, manifest) require.True(t, activated) - th.App.Channels().SetPluginsEnvironment(env) + th.App.PluginService().SetPluginsEnvironment(env) req, _ := http.NewRequest("GET", "/plugins/com.mattermost.sample/public/hello.html", nil) res := httptest.NewRecorder()