From 0ce7c5e3da8b9a508f0467b31167cd65e50ed2a2 Mon Sep 17 00:00:00 2001 From: Tim Scheuermann Date: Tue, 20 Dec 2022 08:59:40 +0100 Subject: [PATCH 01/11] Use path rather than filepath for embedded files (#21896) Co-authored-by: Tim Scheuermann --- store/sqlstore/store.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/store/sqlstore/store.go b/store/sqlstore/store.go index 4c1bf1f05b..630e7705d3 100644 --- a/store/sqlstore/store.go +++ b/store/sqlstore/store.go @@ -9,7 +9,7 @@ import ( dbsql "database/sql" "fmt" "log" - "path/filepath" + "path" "strconv" "strings" "sync" @@ -1041,7 +1041,7 @@ func (ss *SqlStore) hasLicense() bool { func (ss *SqlStore) migrate(direction migrationDirection) error { assets := db.Assets() - assetsList, err := assets.ReadDir(filepath.Join("migrations", ss.DriverName())) + assetsList, err := assets.ReadDir(path.Join("migrations", ss.DriverName())) if err != nil { return err } @@ -1054,7 +1054,7 @@ func (ss *SqlStore) migrate(direction migrationDirection) error { src, err := mbindata.WithInstance(&mbindata.AssetSource{ Names: assetNamesForDriver, AssetFunc: func(name string) ([]byte, error) { - return assets.ReadFile(filepath.Join("migrations", ss.DriverName(), name)) + return assets.ReadFile(path.Join("migrations", ss.DriverName(), name)) }, }) if err != nil { From dcf499b51df70cdcb7133da13ddda3e3e6c3b740 Mon Sep 17 00:00:00 2001 From: Ibrahim Serdar Acikgoz Date: Wed, 21 Dec 2022 16:11:57 +0300 Subject: [PATCH 02/11] [MM-48626] Move plugins environment out of Channels (#21730) --- api4/plugin.go | 4 +- api4/plugin_test.go | 2 +- api4/websocket.go | 2 +- app/app_iface.go | 1 + app/channel.go | 10 +- app/channels.go | 74 +----- app/cluster_handlers.go | 11 +- app/collection.go | 16 +- app/download.go | 6 +- app/file.go | 2 +- app/integration_action.go | 6 +- app/login.go | 4 +- app/onboarding.go | 9 +- app/opentracing/opentracing_layer.go | 17 ++ app/plugin.go | 380 +++++++++++++++++---------- app/plugin_api.go | 4 +- app/plugin_api_test.go | 24 +- app/plugin_commands.go | 72 +++-- app/plugin_commands_test.go | 10 +- app/plugin_db_driver.go | 13 +- app/plugin_db_driver_test.go | 2 +- app/plugin_event.go | 6 +- app/plugin_hooks_test.go | 14 +- app/plugin_install.go | 118 ++++----- app/plugin_install_test.go | 8 +- app/plugin_requests.go | 28 +- app/plugin_requests_test.go | 2 +- app/plugin_shutdown_test.go | 2 +- app/plugin_signature.go | 8 +- app/plugin_statuses.go | 36 +-- app/plugin_test.go | 46 ++-- app/post.go | 8 +- app/reaction.go | 4 +- app/server.go | 14 +- app/team.go | 4 +- app/upload.go | 2 +- app/user.go | 2 +- app/web_conn.go | 2 +- cmd/mattermost/commands/init.go | 7 +- web/web_test.go | 4 +- 40 files changed, 537 insertions(+), 447 deletions(-) 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() From 45159a6b09765e46668bd2124b04170dd080691c Mon Sep 17 00:00:00 2001 From: mattermod Date: Wed, 21 Dec 2022 15:28:13 +0000 Subject: [PATCH 03/11] Update latest version to 7.5.2 --- build/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Dockerfile b/build/Dockerfile index d373e54fdc..aaa82b99ca 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -8,7 +8,7 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"] ENV PATH="/mattermost/bin:${PATH}" ARG PUID=2000 ARG PGID=2000 -ARG MM_PACKAGE="https://releases.mattermost.com/7.5.1/mattermost-7.5.1-linux-amd64.tar.gz?src=docker" +ARG MM_PACKAGE="https://releases.mattermost.com/7.5.2/mattermost-7.5.2-linux-amd64.tar.gz?src=docker" # # Install needed packages and indirect dependencies RUN apt-get update \ From c25270414086fc4ae1be89b09db0cd714a954962 Mon Sep 17 00:00:00 2001 From: Ibrahim Serdar Acikgoz Date: Wed, 21 Dec 2022 22:10:26 +0300 Subject: [PATCH 04/11] Revert "[MM-48626] Move plugins environment out of Channels (#21730)" (#21934) --- api4/plugin.go | 4 +- api4/plugin_test.go | 2 +- api4/websocket.go | 2 +- app/app_iface.go | 1 - app/channel.go | 10 +- app/channels.go | 74 +++++- app/cluster_handlers.go | 11 +- app/collection.go | 16 +- app/download.go | 6 +- app/file.go | 2 +- app/integration_action.go | 6 +- app/login.go | 4 +- app/onboarding.go | 9 +- app/opentracing/opentracing_layer.go | 17 -- app/plugin.go | 380 ++++++++++----------------- app/plugin_api.go | 4 +- app/plugin_api_test.go | 24 +- app/plugin_commands.go | 72 ++--- app/plugin_commands_test.go | 10 +- app/plugin_db_driver.go | 13 +- app/plugin_db_driver_test.go | 2 +- app/plugin_event.go | 6 +- app/plugin_hooks_test.go | 14 +- app/plugin_install.go | 118 ++++----- app/plugin_install_test.go | 8 +- app/plugin_requests.go | 28 +- app/plugin_requests_test.go | 2 +- app/plugin_shutdown_test.go | 2 +- app/plugin_signature.go | 8 +- app/plugin_statuses.go | 36 +-- app/plugin_test.go | 46 ++-- app/post.go | 8 +- app/reaction.go | 4 +- app/server.go | 14 +- app/team.go | 4 +- app/upload.go | 2 +- app/user.go | 2 +- app/web_conn.go | 2 +- cmd/mattermost/commands/init.go | 7 +- web/web_test.go | 4 +- 40 files changed, 447 insertions(+), 537 deletions(-) diff --git a/api4/plugin.go b/api4/plugin.go index 475aa62f21..be5b298d02 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.PluginService().InstallMarketplacePlugin(pluginRequest) + manifest, appErr := c.App.Channels().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.PluginService().RemovePlugin(c.Params.PluginId) + err := c.App.Channels().RemovePlugin(c.Params.PluginId) if err != nil { c.Err = err return diff --git a/api4/plugin_test.go b/api4/plugin_test.go index 3b656c209a..1967f9a617 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.PluginService().RemovePlugin(manifest.Id) + th.App.Channels().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 d8236e49b5..d2c1c10f44 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()) + wc := c.App.Srv().Platform().NewWebConn(cfg, c.App, c.App.Srv().Channels()) if c.AppContext.Session().UserId != "" { c.App.Srv().Platform().HubRegister(wc) } diff --git a/app/app_iface.go b/app/app_iface.go index 2674801101..f10ca2d8b9 100644 --- a/app/app_iface.go +++ b/app/app_iface.go @@ -936,7 +936,6 @@ 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 31a8673383..fdc8d17da3 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.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.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.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.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.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.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.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.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.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.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 c9771f25d6..7ab024e45a 100644 --- a/app/channels.go +++ b/app/channels.go @@ -6,11 +6,14 @@ 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" @@ -39,6 +42,12 @@ 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 @@ -70,6 +79,12 @@ 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() { @@ -92,9 +107,11 @@ 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{}, + srv: s, + imageProxy: imageproxy.MakeImageProxy(s.platform, s.httpService, s.Log()), + uploadLockMap: map[string]bool{}, + collectionTypes: map[string]string{}, + topicTypes: map[string]string{}, } // To get another service: @@ -191,6 +208,10 @@ 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}, @@ -222,6 +243,39 @@ 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") @@ -235,6 +289,8 @@ func (ch *Channels) Start() error { } func (ch *Channels) Stop() error { + ch.ShutDownPlugins() + ch.dndTaskMut.Lock() if ch.dndTask != nil { ch.dndTask.Cancel() @@ -276,18 +332,18 @@ func (s *hooksService) RegisterHooks(productID string, hooks any) error { return s.ch.srv.hooksManager.AddProduct(productID, hooks) } -func (s *Server) RunMultiHook(hookRunnerFunc func(hooks plugin.Hooks) bool, hookId int) { - if env := s.pluginService.GetPluginsEnvironment(); env != nil { +func (ch *Channels) RunMultiHook(hookRunnerFunc func(hooks plugin.Hooks) bool, hookId int) { + if env := ch.GetPluginsEnvironment(); env != nil { env.RunMultiPluginHook(hookRunnerFunc, hookId) } // run hook for the products - s.hooksManager.RunMultiHook(hookRunnerFunc, hookId) + ch.srv.hooksManager.RunMultiHook(hookRunnerFunc, hookId) } -func (s *Server) HooksForPluginOrProduct(id string) (plugin.Hooks, error) { +func (ch *Channels) HooksForPluginOrProduct(id string) (plugin.Hooks, error) { var hooks plugin.Hooks - if env := s.pluginService.GetPluginsEnvironment(); env != nil { + if env := ch.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) @@ -296,7 +352,7 @@ func (s *Server) HooksForPluginOrProduct(id string) (plugin.Hooks, error) { } } - hooks = s.hooksManager.HooksForProduct(id) + hooks = ch.srv.hooksManager.HooksForProduct(id) if hooks != nil { return hooks, nil } diff --git a/app/cluster_handlers.go b/app/cluster_handlers.go index 1cebe5596d..3fa90abf1e 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.pluginService.installPluginFromData(data) + s.Channels().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.pluginService.removePluginFromData(data) + s.Channels().removePluginFromData(data) } func (s *Server) clusterPluginEventHandler(msg *model.ClusterMessage) { @@ -44,7 +44,12 @@ func (s *Server) clusterPluginEventHandler(msg *model.ClusterMessage) { return } - hooks, err := s.HooksForPluginOrProduct(pluginID) + channels, ok := s.products["channels"].(*Channels) + if !ok { + return + } + + hooks, err := channels.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 ff489a18db..9b895e3bc0 100644 --- a/app/collection.go +++ b/app/collection.go @@ -10,26 +10,26 @@ import ( "github.com/mattermost/mattermost-server/v6/shared/mlog" ) -func (s *PluginService) registerCollectionAndTopic(pluginID, collectionType, topicType string) error { +func (a *App) registerCollectionAndTopic(pluginID, collectionType, topicType string) error { // we have a race condition due to multiple plugins calling this method - s.collectionAndTopicTypesMut.Lock() - defer s.collectionAndTopicTypesMut.Unlock() + a.ch.collectionAndTopicTypesMut.Lock() + defer a.ch.collectionAndTopicTypesMut.Unlock() // check if collectionType was already registered by other plugin - existingPluginID, ok := s.collectionTypes[collectionType] + existingPluginID, ok := a.ch.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 := s.topicTypes[topicType] + existingCollectionType, ok := a.ch.topicTypes[topicType] if ok && existingCollectionType != collectionType { return model.NewAppError("registerCollectionAndTopic", "app.collection.add_topic.exists.app_error", nil, "", http.StatusBadRequest) } - s.collectionTypes[collectionType] = pluginID - s.topicTypes[topicType] = collectionType + a.ch.collectionTypes[collectionType] = pluginID + a.ch.topicTypes[topicType] = collectionType - s.platform.Log().Info("registered collection and topic type", mlog.String("plugin_id", pluginID), mlog.String("collection_type", collectionType), mlog.String("topic_type", topicType)) + 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)) return nil } diff --git a/app/download.go b/app/download.go index 56438507cc..449f787c46 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().pluginService.downloadFromURL(downloadURL) + return a.Srv().downloadFromURL(downloadURL) } -func (s *PluginService) downloadFromURL(downloadURL string) ([]byte, error) { +func (s *Server) downloadFromURL(downloadURL string) ([]byte, error) { if !model.IsValidHTTPURL(downloadURL) { return nil, errors.Errorf("invalid url %s", downloadURL) } @@ -38,7 +38,7 @@ func (s *PluginService) 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 e1acafa827..7a66d25010 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.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.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 51bef2b86f..4ae7e97e07 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.srv.pluginService.doPluginRequest(c, method, rawURL, values, body) + return a.ch.doPluginRequest(c, method, rawURL, values, body) } -func (s *PluginService) doPluginRequest(c *request.Context, method, rawURL string, values url.Values, body []byte) (*http.Response, *model.AppError) { +func (ch *Channels) 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 (s *PluginService) doPluginRequest(c *request.Context, method, rawURL strin params["plugin_id"] = pluginID r = mux.SetURLVars(r, params) - s.ServePluginRequest(w, r) + ch.ServePluginRequest(w, r) resp := &http.Response{ StatusCode: w.status, diff --git a/app/login.go b/app/login.go index af322bd13b..e0854bef37 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.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.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.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.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 fe40e9d997..d76525f017 100644 --- a/app/onboarding.go +++ b/app/onboarding.go @@ -28,6 +28,11 @@ 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 { @@ -36,7 +41,7 @@ func (a *App) CompleteOnboarding(c *request.Context, request *model.CompleteOnbo installRequest := &model.InstallMarketplacePluginRequest{ Id: id, } - _, appErr := a.Srv().pluginService.InstallMarketplacePlugin(installRequest) + _, appErr := a.Channels().InstallMarketplacePlugin(installRequest) if appErr != nil { mlog.Error("Failed to install plugin for onboarding", mlog.String("id", id), mlog.Err(appErr)) return @@ -48,7 +53,7 @@ func (a *App) CompleteOnboarding(c *request.Context, request *model.CompleteOnbo return } - hooks, err := a.Srv().HooksForPluginOrProduct(id) + hooks, err := a.ch.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 3d6c2b5ad5..367d98ec39 100644 --- a/app/opentracing/opentracing_layer.go +++ b/app/opentracing/opentracing_layer.go @@ -13125,23 +13125,6 @@ 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 d985455e5b..679576e723 100644 --- a/app/plugin.go +++ b/app/plugin.go @@ -20,37 +20,16 @@ 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 { @@ -84,91 +63,20 @@ 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 (s *PluginService) GetPluginsEnvironment() *plugin.Environment { - if !*s.platform.Config().PluginSettings.Enable { +func (ch *Channels) GetPluginsEnvironment() *plugin.Environment { + if !*ch.cfgSvc.Config().PluginSettings.Enable { return nil } - s.pluginsLock.RLock() - defer s.pluginsLock.RUnlock() + ch.pluginsLock.RLock() + defer ch.pluginsLock.RUnlock() - return s.pluginsEnvironment + return ch.pluginsEnvironment } // GetPluginsEnvironment returns the plugin environment for use if plugins are enabled and @@ -177,39 +85,33 @@ func (s *PluginService) 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 { - // 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() + return a.ch.GetPluginsEnvironment() } -func (s *PluginService) SetPluginsEnvironment(pluginsEnvironment *plugin.Environment) { - s.pluginsLock.Lock() - defer s.pluginsLock.Unlock() +func (ch *Channels) SetPluginsEnvironment(pluginsEnvironment *plugin.Environment) { + ch.pluginsLock.Lock() + defer ch.pluginsLock.Unlock() - s.pluginsEnvironment = pluginsEnvironment - s.platform.SetPluginsEnvironment(s.channels.srv) + ch.pluginsEnvironment = pluginsEnvironment + ch.srv.Platform().SetPluginsEnvironment(ch) } -func (s *PluginService) syncPluginsActiveState() { +func (ch *Channels) syncPluginsActiveState() { // Acquiring lock manually, as plugins might be disabled. See GetPluginsEnvironment. - s.pluginsLock.RLock() - pluginsEnvironment := s.pluginsEnvironment - s.pluginsLock.RUnlock() + ch.pluginsLock.RLock() + pluginsEnvironment := ch.pluginsEnvironment + ch.pluginsLock.RUnlock() if pluginsEnvironment == nil { return } - config := s.platform.Config().PluginSettings + config := ch.cfgSvc.Config().PluginSettings if *config.Enable { availablePlugins, err := pluginsEnvironment.Available() if err != nil { - s.platform.Log().Error("Unable to get available plugins", mlog.Err(err)) + ch.srv.Log().Error("Unable to get available plugins", mlog.Err(err)) return } @@ -223,24 +125,24 @@ func (s *PluginService) syncPluginsActiveState() { pluginEnabled = state.Enable } - if hasOverride, value := s.getPluginStateOverride(pluginID); hasOverride { + if hasOverride, value := ch.getPluginStateOverride(pluginID); hasOverride { pluginEnabled = value } if pluginEnabled { // Disable focalboard in product mode. - if pluginID == model.PluginIdFocalboard && s.platform.Config().FeatureFlags.BoardsProduct { + if pluginID == model.PluginIdFocalboard && ch.cfgSvc.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. - s.platform.UpdateConfig(func(cfg *model.Config) { + ch.cfgSvc.UpdateConfig(func(cfg *model.Config) { cfg.PluginSettings.PluginStates[pluginID] = &model.PluginState{Enable: false} }) pluginsEnvironment.SetPluginError(pluginID, msg) - s.unregisterPluginCommands(pluginID) + ch.unregisterPluginCommands(pluginID) disabledPlugins = append(disabledPlugins, plugin) continue } @@ -264,7 +166,7 @@ func (s *PluginService) syncPluginsActiveState() { if deactivated && plugin.Manifest.HasClient() { message := model.NewWebSocketEvent(model.WebsocketEventPluginDisabled, "", "", "", nil, "") message.Add("manifest", plugin.Manifest.ClientManifest()) - s.platform.Publish(message) + ch.srv.platform.Publish(message) } }(plugin) } @@ -278,14 +180,14 @@ func (s *PluginService) syncPluginsActiveState() { pluginID := plugin.Manifest.Id updatedManifest, activated, err := pluginsEnvironment.Activate(pluginID) if err != nil { - plugin.WrapLogger(s.platform.Log().(*mlog.Logger)).Error("Unable to activate plugin", mlog.Err(err)) + plugin.WrapLogger(ch.srv.Log()).Error("Unable to activate plugin", mlog.Err(err)) return } if activated { // Notify all cluster clients if ready - if err := s.notifyPluginEnabled(updatedManifest); err != nil { - s.platform.Log().Error("Failed to notify cluster on plugin enable", mlog.Err(err)) + if err := ch.notifyPluginEnabled(updatedManifest); err != nil { + ch.srv.Log().Error("Failed to notify cluster on plugin enable", mlog.Err(err)) } } }(plugin) @@ -295,7 +197,7 @@ func (s *PluginService) syncPluginsActiveState() { pluginsEnvironment.Shutdown() } - if err := s.notifyPluginStatusesChanged(); err != nil { + if err := ch.notifyPluginStatusesChanged(); err != nil { mlog.Warn("failed to notify plugin status changed", mlog.Err(err)) } } @@ -305,29 +207,27 @@ func (a *App) NewPluginAPI(c *request.Context, manifest *model.Manifest) plugin. } func (a *App) InitPlugins(c *request.Context, pluginDir, webappPluginDir string) { - a.ch.srv.pluginService.initPlugins(c, pluginDir, webappPluginDir) + a.ch.initPlugins(c, pluginDir, webappPluginDir) } -func (s *PluginService) initPlugins(c *request.Context, pluginDir, webappPluginDir string) { +func (ch *Channels) initPlugins(c *request.Context, pluginDir, webappPluginDir string) { // Acquiring lock manually, as plugins might be disabled. See GetPluginsEnvironment. defer func() { - // 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.srv.Platform().SetPluginsEnvironment(ch) }() - s.pluginsLock.RLock() - pluginsEnvironment := s.pluginsEnvironment - s.pluginsLock.RUnlock() - if pluginsEnvironment != nil || !*s.platform.Config().PluginSettings.Enable { - s.syncPluginsActiveState() + ch.pluginsLock.RLock() + pluginsEnvironment := ch.pluginsEnvironment + ch.pluginsLock.RUnlock() + if pluginsEnvironment != nil || !*ch.cfgSvc.Config().PluginSettings.Enable { + ch.syncPluginsActiveState() if pluginsEnvironment != nil { - pluginsEnvironment.TogglePluginHealthCheckJob(*s.platform.Config().PluginSettings.EnableHealthCheck) + pluginsEnvironment.TogglePluginHealthCheckJob(*ch.cfgSvc.Config().PluginSettings.EnableHealthCheck) } return } - s.platform.Log().Info("Starting up plugins") + ch.srv.Log().Info("Starting up plugins") if err := os.Mkdir(pluginDir, 0744); err != nil && !os.IsExist(err) { mlog.Error("Failed to start up plugins", mlog.Err(err)) @@ -340,77 +240,77 @@ func (s *PluginService) initPlugins(c *request.Context, pluginDir, webappPluginD } newAPIFunc := func(manifest *model.Manifest) plugin.API { - return New(ServerConnector(s.channels)).NewPluginAPI(c, manifest) + return New(ServerConnector(ch)).NewPluginAPI(c, manifest) } env, err := plugin.NewEnvironment( newAPIFunc, - NewDriverImpl(s.platform), + NewDriverImpl(ch.srv), pluginDir, webappPluginDir, - *s.platform.Config().ExperimentalSettings.PatchPluginsReactDOM, - s.platform.Logger(), - s.platform.Metrics(), + *ch.cfgSvc.Config().ExperimentalSettings.PatchPluginsReactDOM, + ch.srv.Log(), + ch.srv.GetMetrics(), ) if err != nil { mlog.Error("Failed to start up plugins", mlog.Err(err)) return } - s.pluginsLock.Lock() - s.pluginsEnvironment = env - s.pluginsLock.Unlock() + ch.pluginsLock.Lock() + ch.pluginsEnvironment = env + ch.pluginsLock.Unlock() - s.pluginsEnvironment.TogglePluginHealthCheckJob(*s.platform.Config().PluginSettings.EnableHealthCheck) + ch.pluginsEnvironment.TogglePluginHealthCheckJob(*ch.cfgSvc.Config().PluginSettings.EnableHealthCheck) - if err := s.syncPlugins(); err != nil { + if err := ch.syncPlugins(); err != nil { mlog.Error("Failed to sync plugins from the file store", mlog.Err(err)) } - plugins := s.processPrepackagedPlugins(prepackagedPluginsDir) - pluginsEnvironment = s.GetPluginsEnvironment() + plugins := ch.processPrepackagedPlugins(prepackagedPluginsDir) + pluginsEnvironment = ch.GetPluginsEnvironment() if pluginsEnvironment == nil { mlog.Info("Plugins environment not found, server is likely shutting down") return } pluginsEnvironment.SetPrepackagedPlugins(plugins) - s.installFeatureFlagPlugins() + ch.installFeatureFlagPlugins() // Sync plugin active state when config changes. Also notify plugins. - s.pluginsLock.Lock() - s.platform.RemoveConfigListener(s.pluginConfigListenerID) - s.pluginConfigListenerID = s.platform.AddConfigListener(func(old, new *model.Config) { + ch.pluginsLock.Lock() + ch.RemoveConfigListener(ch.pluginConfigListenerID) + ch.pluginConfigListenerID = ch.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 { - s.installFeatureFlagPlugins() - s.syncPluginsActiveState() + ch.installFeatureFlagPlugins() + ch.syncPluginsActiveState() } - s.pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool { + ch.RunMultiHook(func(hooks plugin.Hooks) bool { if err := hooks.OnConfigurationChange(); err != nil { - s.platform.Log().Error("Plugin OnConfigurationChange hook failed", mlog.Err(err)) + ch.srv.Log().Error("Plugin OnConfigurationChange hook failed", mlog.Err(err)) } return true }, plugin.OnConfigurationChangeID) }) - s.pluginsLock.Unlock() + ch.pluginsLock.Unlock() - s.syncPluginsActiveState() + ch.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.srv.pluginService.syncPlugins() + return a.ch.syncPlugins() } // SyncPlugins synchronizes the plugins installed locally // with the plugin bundles available in the file store. -func (s *PluginService) syncPlugins() *model.AppError { +func (ch *Channels) syncPlugins() *model.AppError { mlog.Info("Syncing plugins from the file store") - pluginsEnvironment := s.GetPluginsEnvironment() + pluginsEnvironment := ch.GetPluginsEnvironment() if pluginsEnvironment == nil { return model.NewAppError("SyncPlugins", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } @@ -426,14 +326,14 @@ func (s *PluginService) syncPlugins() *model.AppError { go func(pluginID string) { defer wg.Done() // Only handle managed plugins with .filestore flag file. - _, err := os.Stat(filepath.Join(*s.platform.Config().PluginSettings.Directory, pluginID, managedPluginFileName)) + _, err := os.Stat(filepath.Join(*ch.cfgSvc.Config().PluginSettings.Directory, pluginID, managedPluginFileName)) if os.IsNotExist(err) { mlog.Warn("Skipping sync for unmanaged plugin", mlog.String("plugin_id", pluginID)) } 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 := s.removePluginLocally(pluginID); err != nil { + if err := ch.removePluginLocally(pluginID); err != nil { mlog.Error("Failed to remove local installation of managed plugin before sync", mlog.String("plugin_id", pluginID), mlog.Err(err)) } } @@ -442,7 +342,7 @@ func (s *PluginService) syncPlugins() *model.AppError { wg.Wait() // Install plugins from the file store. - pluginSignaturePathMap, appErr := s.getPluginsFromFolder() + pluginSignaturePathMap, appErr := ch.getPluginsFromFolder() if appErr != nil { return appErr } @@ -451,7 +351,7 @@ func (s *PluginService) syncPlugins() *model.AppError { wg.Add(1) go func(plugin *pluginSignaturePath) { defer wg.Done() - reader, appErr := s.fileStore.Reader(plugin.path) + 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)) return @@ -459,8 +359,8 @@ func (s *PluginService) syncPlugins() *model.AppError { defer reader.Close() var signature filestore.ReadCloseSeeker - if *s.platform.Config().PluginSettings.RequirePluginSignature { - signature, appErr = s.fileStore.Reader(plugin.signaturePath) + 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)) return @@ -469,7 +369,7 @@ func (s *PluginService) syncPlugins() *model.AppError { } mlog.Info("Syncing plugin from file store", mlog.String("bundle", plugin.path)) - if _, err := s.installPluginLocally(reader, signature, installPluginLocallyAlways); err != nil { + if _, err := ch.installPluginLocally(reader, signature, installPluginLocallyAlways); err != nil { mlog.Error("Failed to sync plugin from file store", mlog.String("bundle", plugin.path), mlog.Err(err)) } }(plugin) @@ -479,11 +379,11 @@ func (s *PluginService) syncPlugins() *model.AppError { return nil } -func (s *PluginService) ShutDownPlugins() { +func (ch *Channels) ShutDownPlugins() { // Acquiring lock manually, as plugins might be disabled. See GetPluginsEnvironment. - s.pluginsLock.RLock() - pluginsEnvironment := s.pluginsEnvironment - s.pluginsLock.RUnlock() + ch.pluginsLock.RLock() + pluginsEnvironment := ch.pluginsEnvironment + ch.pluginsLock.RUnlock() if pluginsEnvironment == nil { return } @@ -492,14 +392,14 @@ func (s *PluginService) ShutDownPlugins() { pluginsEnvironment.Shutdown() - s.platform.RemoveConfigListener(s.pluginConfigListenerID) - s.pluginConfigListenerID = "" + ch.RemoveConfigListener(ch.pluginConfigListenerID) + ch.pluginConfigListenerID = "" // Acquiring lock manually before cleaning up PluginsEnvironment. - s.pluginsLock.Lock() - defer s.pluginsLock.Unlock() - if s.pluginsEnvironment == pluginsEnvironment { - s.pluginsEnvironment = nil + ch.pluginsLock.Lock() + defer ch.pluginsLock.Unlock() + if ch.pluginsEnvironment == pluginsEnvironment { + ch.pluginsEnvironment = nil } else { mlog.Warn("Another PluginsEnvironment detected while shutting down plugins.") } @@ -525,11 +425,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.PluginService().enablePlugin(id) + return a.ch.enablePlugin(id) } -func (s *PluginService) enablePlugin(id string) *model.AppError { - pluginsEnvironment := s.GetPluginsEnvironment() +func (ch *Channels) enablePlugin(id string) *model.AppError { + pluginsEnvironment := ch.GetPluginsEnvironment() if pluginsEnvironment == nil { return model.NewAppError("EnablePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } @@ -553,16 +453,16 @@ func (s *PluginService) enablePlugin(id string) *model.AppError { return model.NewAppError("EnablePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusNotFound) } - if id == model.PluginIdFocalboard && s.platform.Config().FeatureFlags.BoardsProduct { + if id == model.PluginIdFocalboard && ch.cfgSvc.Config().FeatureFlags.BoardsProduct { return model.NewAppError("EnablePlugin", "app.plugin.product_mode.app_error", map[string]any{"Name": model.PluginIdFocalboard}, "", http.StatusBadRequest) } - s.platform.UpdateConfig(func(cfg *model.Config) { + ch.cfgSvc.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 := s.platform.SaveConfig(s.platform.Config(), true); err != nil { + if _, _, err := ch.cfgSvc.SaveConfig(ch.cfgSvc.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) } @@ -575,7 +475,7 @@ func (s *PluginService) 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.srv.pluginService.disablePlugin(id) + appErr := a.ch.disablePlugin(id) if appErr != nil { return appErr } @@ -583,22 +483,22 @@ func (a *App) DisablePlugin(id string) *model.AppError { return nil } -func (s *PluginService) disablePlugin(id string) *model.AppError { +func (ch *Channels) disablePlugin(id string) *model.AppError { // find all collectionTypes registered by plugin - for collectionTypeToRemove, existingPluginId := range s.collectionTypes { + for collectionTypeToRemove, existingPluginId := range ch.collectionTypes { if existingPluginId != id { continue } // find all topicTypes for existing collectionType - for topicTypeToRemove, existingCollectionType := range s.topicTypes { + for topicTypeToRemove, existingCollectionType := range ch.topicTypes { if existingCollectionType == collectionTypeToRemove { - delete(s.topicTypes, topicTypeToRemove) + delete(ch.topicTypes, topicTypeToRemove) } } - delete(s.collectionTypes, collectionTypeToRemove) + delete(ch.collectionTypes, collectionTypeToRemove) } - pluginsEnvironment := s.GetPluginsEnvironment() + pluginsEnvironment := ch.GetPluginsEnvironment() if pluginsEnvironment == nil { return model.NewAppError("DisablePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } @@ -622,13 +522,13 @@ func (s *PluginService) disablePlugin(id string) *model.AppError { return model.NewAppError("DisablePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusNotFound) } - s.platform.UpdateConfig(func(cfg *model.Config) { + ch.cfgSvc.UpdateConfig(func(cfg *model.Config) { cfg.PluginSettings.PluginStates[id] = &model.PluginState{Enable: false} }) - s.unregisterPluginCommands(id) + ch.unregisterPluginCommands(id) // This call will implicitly invoke SyncPluginsActiveState which will deactivate disabled plugins. - if _, _, err := s.platform.SaveConfig(s.platform.Config(), true); err != nil { + if _, _, err := ch.cfgSvc.SaveConfig(ch.cfgSvc.Config(), true); err != nil { return model.NewAppError("DisablePlugin", "app.plugin.config.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -715,8 +615,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 (s *PluginService) getPrepackagedPlugin(pluginID, version string) (*plugin.PrepackagedPlugin, *model.AppError) { - pluginsEnvironment := s.GetPluginsEnvironment() +func (ch *Channels) getPrepackagedPlugin(pluginID, version string) (*plugin.PrepackagedPlugin, *model.AppError) { + pluginsEnvironment := ch.GetPluginsEnvironment() if pluginsEnvironment == nil { return nil, model.NewAppError("getPrepackagedPlugin", "app.plugin.config.app_error", nil, "plugin environment is nil", http.StatusInternalServerError) } @@ -734,16 +634,16 @@ func (s *PluginService) getPrepackagedPlugin(pluginID, version string) (*plugin. // getRemoteMarketplacePlugin returns plugin from marketplace-server. // // If version is empty, the latest compatible version is used. -func (s *PluginService) getRemoteMarketplacePlugin(pluginID, version string) (*model.BaseMarketplacePlugin, *model.AppError) { +func (ch *Channels) getRemoteMarketplacePlugin(pluginID, version string) (*model.BaseMarketplacePlugin, *model.AppError) { marketplaceClient, err := marketplace.NewClient( - *s.platform.Config().PluginSettings.MarketplaceURL, - s.httpService, + *ch.cfgSvc.Config().PluginSettings.MarketplaceURL, + ch.srv.HTTPService(), ) if err != nil { return nil, model.NewAppError("GetMarketplacePlugin", "app.plugin.marketplace_client.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } - filter := s.getBaseMarketplaceFilter() + filter := ch.getBaseMarketplaceFilter() filter.PluginId = pluginID var plugin *model.BaseMarketplacePlugin @@ -898,15 +798,15 @@ func (a *App) mergeLocalPlugins(remoteMarketplacePlugins map[string]*model.Marke } func (a *App) getBaseMarketplaceFilter() *model.MarketplacePluginFilter { - return a.ch.srv.pluginService.getBaseMarketplaceFilter() + return a.ch.getBaseMarketplaceFilter() } -func (s *PluginService) getBaseMarketplaceFilter() *model.MarketplacePluginFilter { +func (ch *Channels) getBaseMarketplaceFilter() *model.MarketplacePluginFilter { filter := &model.MarketplacePluginFilter{ ServerVersion: model.CurrentVersion, } - license := s.platform.License() + license := ch.srv.License() if license != nil && license.HasEnterpriseMarketplacePlugins() { filter.EnterprisePlugins = true } @@ -953,8 +853,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 (s *PluginService) notifyPluginEnabled(manifest *model.Manifest) error { - pluginsEnvironment := s.GetPluginsEnvironment() +func (ch *Channels) notifyPluginEnabled(manifest *model.Manifest) error { + pluginsEnvironment := ch.GetPluginsEnvironment() if pluginsEnvironment == nil { return errors.New("pluginsEnvironment is nil") } @@ -964,15 +864,15 @@ func (s *PluginService) notifyPluginEnabled(manifest *model.Manifest) error { var statuses model.PluginStatuses - if s.platform.Cluster() != nil { + if ch.srv.platform.Cluster() != nil { var err *model.AppError - statuses, err = s.platform.Cluster().GetPluginStatuses() + statuses, err = ch.srv.platform.Cluster().GetPluginStatuses() if err != nil { return err } } - localStatus, err := s.GetPluginStatus(manifest.Id) + localStatus, err := ch.GetPluginStatus(manifest.Id) if err != nil { return err } @@ -992,26 +892,26 @@ func (s *PluginService) notifyPluginEnabled(manifest *model.Manifest) error { // Notify all cluster peer clients. message := model.NewWebSocketEvent(model.WebsocketEventPluginEnabled, "", "", "", nil, "") message.Add("manifest", manifest.ClientManifest()) - s.platform.Publish(message) + ch.srv.platform.Publish(message) return nil } -func (s *PluginService) getPluginsFromFolder() (map[string]*pluginSignaturePath, *model.AppError) { - fileStorePaths, appErr := s.fileStore.ListDirectory(fileStorePluginFolder) +func (ch *Channels) getPluginsFromFolder() (map[string]*pluginSignaturePath, *model.AppError) { + fileStorePaths, appErr := ch.srv.listDirectory(fileStorePluginFolder, false) if appErr != nil { return nil, model.NewAppError("getPluginsFromDir", "app.plugin.sync.list_filestore.app_error", nil, "", http.StatusInternalServerError).Wrap(appErr) } - return s.getPluginsFromFilePaths(fileStorePaths), nil + return ch.getPluginsFromFilePaths(fileStorePaths), nil } -func (s *PluginService) getPluginsFromFilePaths(fileStorePaths []string) map[string]*pluginSignaturePath { +func (ch *Channels) getPluginsFromFilePaths(fileStorePaths []string) map[string]*pluginSignaturePath { pluginSignaturePathMap := make(map[string]*pluginSignaturePath) fsPrefix := "" - if *s.platform.Config().FileSettings.DriverName == model.ImageDriverS3 { - ptr := s.platform.Config().FileSettings.AmazonS3PathPrefix + if *ch.cfgSvc.Config().FileSettings.DriverName == model.ImageDriverS3 { + ptr := ch.cfgSvc.Config().FileSettings.AmazonS3PathPrefix if ptr != nil && *ptr != "" { fsPrefix = *ptr + "/" } @@ -1044,7 +944,7 @@ func (s *PluginService) getPluginsFromFilePaths(fileStorePaths []string) map[str return pluginSignaturePathMap } -func (s *PluginService) processPrepackagedPlugins(pluginsDir string) []*plugin.PrepackagedPlugin { +func (ch *Channels) processPrepackagedPlugins(pluginsDir string) []*plugin.PrepackagedPlugin { prepackagedPluginsDir, found := fileutils.FindDir(pluginsDir) if !found { return nil @@ -1060,7 +960,7 @@ func (s *PluginService) processPrepackagedPlugins(pluginsDir string) []*plugin.P return nil } - pluginSignaturePathMap := s.getPluginsFromFilePaths(fileStorePaths) + pluginSignaturePathMap := ch.getPluginsFromFilePaths(fileStorePaths) plugins := make([]*plugin.PrepackagedPlugin, 0, len(pluginSignaturePathMap)) prepackagedPlugins := make(chan *plugin.PrepackagedPlugin, len(pluginSignaturePathMap)) @@ -1069,7 +969,7 @@ func (s *PluginService) processPrepackagedPlugins(pluginsDir string) []*plugin.P wg.Add(1) go func(psPath *pluginSignaturePath) { defer wg.Done() - p, err := s.processPrepackagedPlugin(psPath) + p, err := ch.processPrepackagedPlugin(psPath) if err != nil { mlog.Error("Failed to install prepackaged plugin", mlog.String("path", psPath.path), mlog.Err(err)) return @@ -1090,7 +990,7 @@ func (s *PluginService) processPrepackagedPlugins(pluginsDir string) []*plugin.P // 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 (s *PluginService) processPrepackagedPlugin(pluginPath *pluginSignaturePath) (*plugin.PrepackagedPlugin, error) { +func (ch *Channels) processPrepackagedPlugin(pluginPath *pluginSignaturePath) (*plugin.PrepackagedPlugin, error) { mlog.Debug("Processing prepackaged plugin", mlog.String("path", pluginPath.path)) fileReader, err := os.Open(pluginPath.path) @@ -1111,18 +1011,18 @@ func (s *PluginService) processPrepackagedPlugin(pluginPath *pluginSignaturePath } // Skip installing the plugin at all if automatic prepackaged plugins is disabled - if !*s.platform.Config().PluginSettings.AutomaticPrepackagedPlugins { + if !*ch.cfgSvc.Config().PluginSettings.AutomaticPrepackagedPlugins { return plugin, nil } // Skip installing if the plugin is has not been previously enabled. - pluginState := s.platform.Config().PluginSettings.PluginStates[plugin.Manifest.Id] + pluginState := ch.cfgSvc.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 := s.installExtractedPlugin(plugin.Manifest, pluginDir, installPluginLocallyOnlyIfNewOrUpgrade); err != nil { + if _, err := ch.installExtractedPlugin(plugin.Manifest, pluginDir, installPluginLocallyOnlyIfNewOrUpgrade); err != nil { return nil, errors.Wrapf(err, "Failed to install extracted prepackaged plugin %s", pluginPath.path) } @@ -1130,24 +1030,24 @@ func (s *PluginService) processPrepackagedPlugin(pluginPath *pluginSignaturePath } // installFeatureFlagPlugins handles the automatic installation/upgrade of plugins from feature flags -func (s *PluginService) installFeatureFlagPlugins() { - ffControledPlugins := s.platform.Config().FeatureFlags.Plugins() +func (ch *Channels) installFeatureFlagPlugins() { + ffControledPlugins := ch.cfgSvc.Config().FeatureFlags.Plugins() // Respect the automatic prepackaged disable setting - if !*s.platform.Config().PluginSettings.AutomaticPrepackagedPlugins { + if !*ch.cfgSvc.Config().PluginSettings.AutomaticPrepackagedPlugins { return } for pluginID, version := range ffControledPlugins { // Skip installing if the plugin has been previously disabled. - pluginState := s.platform.Config().PluginSettings.PluginStates[pluginID] + pluginState := ch.cfgSvc.Config().PluginSettings.PluginStates[pluginID] if pluginState != nil && !pluginState.Enable { - s.platform.Log().Debug("Not auto installing/upgrade because plugin was disabled", mlog.String("plugin_id", pluginID), mlog.String("version", version)) + ch.srv.Log().Debug("Not auto installing/upgrade because plugin was disabled", mlog.String("plugin_id", pluginID), mlog.String("version", version)) continue } // Check if we already installed this version as InstallMarketplacePlugin can't handle re-installs well. - pluginStatus, err := s.GetPluginStatus(pluginID) + pluginStatus, err := ch.GetPluginStatus(pluginID) pluginExists := err == nil if pluginExists && pluginStatus.Version == version { continue @@ -1155,37 +1055,37 @@ func (s *PluginService) installFeatureFlagPlugins() { if version != "" && version != "control" { // If we are on-prem skip installation if this is a downgrade - license := s.platform.License() + license := ch.srv.License() inCloud := license != nil && *license.Features.Cloud if !inCloud && pluginExists { parsedVersion, err := semver.Parse(version) if err != nil { - s.platform.Log().Debug("Bad version from feature flag", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", version)) + ch.srv.Log().Debug("Bad version from feature flag", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", version)) return } parsedExistingVersion, err := semver.Parse(pluginStatus.Version) if err != nil { - s.platform.Log().Debug("Bad version from plugin manifest", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", pluginStatus.Version)) + ch.srv.Log().Debug("Bad version from plugin manifest", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", pluginStatus.Version)) return } if parsedVersion.LTE(parsedExistingVersion) { - 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)) + ch.srv.Log().Debug("Skip installation because given version was a downgrade and on-prem installations should not downgrade.", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", pluginStatus.Version)) return } } - _, err := s.InstallMarketplacePlugin(&model.InstallMarketplacePluginRequest{ + _, err := ch.InstallMarketplacePlugin(&model.InstallMarketplacePluginRequest{ Id: pluginID, Version: version, }) if err != nil { - s.platform.Log().Debug("Unable to install plugin from FF manifest", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", version)) + ch.srv.Log().Debug("Unable to install plugin from FF manifest", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", version)) } else { - if err := 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)) + if err := ch.enablePlugin(pluginID); err != nil { + ch.srv.Log().Debug("Unable to enable plugin installed from feature flag.", mlog.String("plugin_id", pluginID), mlog.Err(err), mlog.String("version", version)) } else { - s.platform.Log().Debug("Installed and enabled plugin.", mlog.String("plugin_id", pluginID), mlog.String("version", version)) + ch.srv.Log().Debug("Installed and enabled plugin.", mlog.String("plugin_id", pluginID), mlog.String("version", version)) } } } @@ -1240,15 +1140,15 @@ func getIcon(iconPath string) (string, error) { return fmt.Sprintf("data:image/svg+xml;base64,%s", base64.StdEncoding.EncodeToString(icon)), nil } -func (s *PluginService) getPluginStateOverride(pluginID string) (bool, bool) { +func (ch *Channels) getPluginStateOverride(pluginID string) (bool, bool) { switch pluginID { case model.PluginIdApps: // Tie Apps proxy disabled status to the feature flag. - if !s.platform.Config().FeatureFlags.AppsEnabled { + if !ch.cfgSvc.Config().FeatureFlags.AppsEnabled { return true, false } case model.PluginIdCalls: - if !s.platform.Config().FeatureFlags.CallsEnabled { + if !ch.cfgSvc.Config().FeatureFlags.CallsEnabled { return true, false } } diff --git a/app/plugin_api.go b/app/plugin_api.go index 57ee5b4192..5c8ee9fad1 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.Srv().pluginService.RemovePlugin(id) + return api.app.Channels().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.Srv().pluginService.registerCollectionAndTopic(api.id, collectionType, topicType) + return api.app.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 cec5736eb3..759613781b 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().Platform()), pluginDir, webappPluginDir, false, app.Log(), nil) + env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(app.Srv()), 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.PluginService().SetPluginsEnvironment(env) + app.ch.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.Platform()), pluginDir, webappPluginDir, false, th.App.Log(), nil) + env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server), 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.PluginService().SetPluginsEnvironment(env) + th.App.ch.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().Platform()), pluginDir, webappPluginDir, false, app.Log(), nil) + env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(app.Srv()), pluginDir, webappPluginDir, false, app.Log(), nil) require.NoError(t, err) - app.PluginService().SetPluginsEnvironment(env) + app.ch.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.Platform()), pluginDir, webappPluginDir, false, th.App.Log(), metricsMock) + env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server), pluginDir, webappPluginDir, false, th.App.Log(), metricsMock) require.NoError(t, err) - th.App.PluginService().SetPluginsEnvironment(env) + th.App.ch.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.Server.Platform()), pluginDir, webappPluginDir, false, th.App.Log(), nil) + env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(th.App.Srv()), pluginDir, webappPluginDir, false, th.App.Log(), nil) require.NoError(t, err) - th.App.PluginService().SetPluginsEnvironment(env) + th.App.ch.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.Server.Platform()), pluginDir, webappPluginDir, false, th.App.Log(), nil) + env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(th.App.Srv()), pluginDir, webappPluginDir, false, th.App.Log(), nil) require.NoError(t, err) - th.App.PluginService().SetPluginsEnvironment(env) + th.App.ch.SetPluginsEnvironment(env) pluginID := "testplugin" pluginManifest := `{"id": "testplugin", "server": {"executable": "backend.exe"}}` diff --git a/app/plugin_commands.go b/app/plugin_commands.go index 8d034931e9..e70c69a38a 100644 --- a/app/plugin_commands.go +++ b/app/plugin_commands.go @@ -22,10 +22,6 @@ 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") } @@ -59,10 +55,10 @@ func (s *PluginService) registerPluginCommand(pluginID string, command *model.Co AutocompleteIconData: command.AutocompleteIconData, } - s.pluginCommandsLock.Lock() - defer s.pluginCommandsLock.Unlock() + a.ch.pluginCommandsLock.Lock() + defer a.ch.pluginCommandsLock.Unlock() - for _, pc := range s.pluginCommands { + for _, pc := range a.ch.pluginCommands { if pc.Command.Trigger == command.Trigger && pc.Command.TeamId == command.TeamId { if pc.PluginId == pluginID { pc.Command = command @@ -71,7 +67,7 @@ func (s *PluginService) registerPluginCommand(pluginID string, command *model.Co } } - s.pluginCommands = append(s.pluginCommands, &PluginCommand{ + a.ch.pluginCommands = append(a.ch.pluginCommands, &PluginCommand{ Command: command, PluginId: pluginID, }) @@ -79,47 +75,39 @@ func (s *PluginService) registerPluginCommand(pluginID string, command *model.Co } 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) - s.pluginCommandsLock.Lock() - defer s.pluginCommandsLock.Unlock() + a.ch.pluginCommandsLock.Lock() + defer a.ch.pluginCommandsLock.Unlock() var remaining []*PluginCommand - for _, pc := range s.pluginCommands { + for _, pc := range a.ch.pluginCommands { if pc.Command.TeamId != teamID || pc.Command.Trigger != trigger { remaining = append(remaining, pc) } } - s.pluginCommands = remaining + a.ch.pluginCommands = remaining } -func (s *PluginService) unregisterPluginCommands(pluginID string) { - s.pluginCommandsLock.Lock() - defer s.pluginCommandsLock.Unlock() +func (ch *Channels) unregisterPluginCommands(pluginID string) { + ch.pluginCommandsLock.Lock() + defer ch.pluginCommandsLock.Unlock() var remaining []*PluginCommand - for _, pc := range s.pluginCommands { + for _, pc := range ch.pluginCommands { if pc.PluginId != pluginID { remaining = append(remaining, pc) } } - s.pluginCommands = remaining + ch.pluginCommands = remaining } func (a *App) PluginCommandsForTeam(teamID string) []*model.Command { - return a.Srv().pluginService.PluginCommandsForTeam(teamID) -} - -func (s *PluginService) PluginCommandsForTeam(teamID string) []*model.Command { - s.pluginCommandsLock.RLock() - defer s.pluginCommandsLock.RUnlock() + a.ch.pluginCommandsLock.RLock() + defer a.ch.pluginCommandsLock.RUnlock() var commands []*model.Command - for _, pc := range s.pluginCommands { + for _, pc := range a.ch.pluginCommands { if pc.Command.TeamId == "" || pc.Command.TeamId == teamID { commands = append(commands, pc.Command) } @@ -127,24 +115,6 @@ func (s *PluginService) 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) { @@ -152,7 +122,15 @@ func (a *App) tryExecutePluginCommand(c request.CTX, args *model.CommandArgs) (* trigger := parts[0][1:] trigger = strings.ToLower(trigger) - matched := a.Srv().pluginService.getPluginCommandFromArgs(args) + 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() if matched == nil { return nil, nil, nil } diff --git a/app/plugin_commands_test.go b/app/plugin_commands_test.go index 1cf9751d9a..e56cf74718 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.PluginService().RemovePlugin(pluginIDs[0]) + th.App.ch.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.PluginService().RemovePlugin(pluginIDs[0]) + th.App.ch.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.PluginService().RemovePlugin(pluginIDs[0]) + th.App.ch.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.PluginService().RemovePlugin(pluginIDs[0]) + th.App.ch.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.PluginService().RemovePlugin(pluginIDs[0]) + th.App.ch.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 0b74577f36..a29fa7467f 100644 --- a/app/plugin_db_driver.go +++ b/app/plugin_db_driver.go @@ -10,7 +10,6 @@ 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" ) @@ -20,7 +19,7 @@ import ( // a new entry tracked centrally in a map. Further requests operate on the // object ID. type DriverImpl struct { - ps *platform.PlatformService + s *Server connMut sync.RWMutex connMap map[string]*sql.Conn txMut sync.Mutex @@ -31,9 +30,9 @@ type DriverImpl struct { rowsMap map[string]driver.Rows } -func NewDriverImpl(s *platform.PlatformService) *DriverImpl { +func NewDriverImpl(s *Server) *DriverImpl { return &DriverImpl{ - ps: s, + s: s, connMap: make(map[string]*sql.Conn), txMap: make(map[string]driver.Tx), stMap: make(map[string]driver.Stmt), @@ -42,11 +41,11 @@ func NewDriverImpl(s *platform.PlatformService) *DriverImpl { } func (d *DriverImpl) Conn(isMaster bool) (string, error) { - dbFunc := d.ps.Store.GetInternalMasterDB + dbFunc := d.s.Platform().Store.GetInternalMasterDB if !isMaster { - dbFunc = d.ps.Store.GetInternalReplicaDB + dbFunc = d.s.Platform().Store.GetInternalReplicaDB } - timeout := time.Duration(*d.ps.Config().SqlSettings.QueryTimeout) * time.Second + timeout := time.Duration(*d.s.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 797f677d59..a2c428fb6f 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.platform) + d := NewDriverImpl(th.Server) _, err := d.Conn(true) require.Error(t, err) } diff --git a/app/plugin_event.go b/app/plugin_event.go index 19d9f1dabc..c30e2d1af5 100644 --- a/app/plugin_event.go +++ b/app/plugin_event.go @@ -9,10 +9,10 @@ import ( "github.com/mattermost/mattermost-server/v6/model" ) -func (s *PluginService) notifyClusterPluginEvent(event model.ClusterEvent, data model.PluginEventData) { +func (ch *Channels) notifyClusterPluginEvent(event model.ClusterEvent, data model.PluginEventData) { buf, _ := json.Marshal(data) - if s.platform.Cluster() != nil { - s.platform.Cluster().SendClusterMessage(&model.ClusterMessage{ + if ch.srv.platform.Cluster() != nil { + ch.srv.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 105982a7b2..161994a1ab 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().Platform()), pluginDir, webappPluginDir, false, app.Log(), nil) + env, err := plugin.NewEnvironment(apiFunc, NewDriverImpl(app.Srv()), pluginDir, webappPluginDir, false, app.Log(), nil) require.NoError(t, err) - app.PluginService().SetPluginsEnvironment(env) + app.ch.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.Platform()), pluginDir, webappPluginDir, false, th.App.Log(), metricsMock) + env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server), pluginDir, webappPluginDir, false, th.App.Log(), metricsMock) require.NoError(t, err) - th.App.PluginService().SetPluginsEnvironment(env) + th.App.ch.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.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + th.App.Channels().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.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + th.App.Channels().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.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + th.App.Channels().RunMultiHook(func(hooks plugin.Hooks) bool { hooks.OnCloudLimitsUpdated(nil) hookCalled = true diff --git a/app/plugin_install.go b/app/plugin_install.go index f0c54a0da4..431c3abebe 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 (s *PluginService) installPluginFromData(data model.PluginEventData) { +func (ch *Channels) installPluginFromData(data model.PluginEventData) { mlog.Debug("Installing plugin as per cluster message", mlog.String("plugin_id", data.Id)) - pluginSignaturePathMap, appErr := s.getPluginsFromFolder() + pluginSignaturePathMap, appErr := ch.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 (s *PluginService) installPluginFromData(data model.PluginEventData) { return } - 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)) + 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)) return } defer reader.Close() var signature filestore.ReadCloseSeeker - 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)) + 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)) return } defer signature.Close() } - manifest, appErr := s.installPluginLocally(reader, signature, installPluginLocallyAlways) + manifest, appErr := ch.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 err2 := s.notifyPluginEnabled(manifest); err2 != nil { - mlog.Error("Failed notify plugin enabled", mlog.Err(err2)) + if err := ch.notifyPluginEnabled(manifest); err != nil { + mlog.Error("Failed notify plugin enabled", mlog.Err(err)) } - if err2 := s.notifyPluginStatusesChanged(); err2 != nil { - mlog.Error("Failed to notify plugin status changed", mlog.Err(err2)) + if err := ch.notifyPluginStatusesChanged(); err != nil { + mlog.Error("Failed to notify plugin status changed", mlog.Err(err)) } } -func (s *PluginService) removePluginFromData(data model.PluginEventData) { +func (ch *Channels) removePluginFromData(data model.PluginEventData) { mlog.Debug("Removing plugin as per cluster message", mlog.String("plugin_id", data.Id)) - if err := s.removePluginLocally(data.Id); err != nil { + if err := ch.removePluginLocally(data.Id); err != nil { mlog.Warn("Failed to remove plugin locally", mlog.Err(err), mlog.String("id", data.Id)) } - if err := s.notifyPluginStatusesChanged(); err != nil { + if err := ch.notifyPluginStatusesChanged(); err != nil { mlog.Warn("failed to notify plugin status changed", mlog.Err(err)) } } // InstallPluginWithSignature verifies and installs plugin. -func (s *PluginService) installPluginWithSignature(pluginFile, signature io.ReadSeeker) (*model.Manifest, *model.AppError) { - return s.installPlugin(pluginFile, signature, installPluginLocallyAlways) +func (ch *Channels) installPluginWithSignature(pluginFile, signature io.ReadSeeker) (*model.Manifest, *model.AppError) { + return ch.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.srv.pluginService.installPlugin(pluginFile, signature, installationStrategy) + return a.ch.installPlugin(pluginFile, signature, installationStrategy) } -func (s *PluginService) installPlugin(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { - manifest, appErr := s.installPluginLocally(pluginFile, signature, installationStrategy) +func (ch *Channels) installPlugin(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { + manifest, appErr := ch.installPluginLocally(pluginFile, signature, installationStrategy) if appErr != nil { return nil, appErr } if signature != nil { signature.Seek(0, 0) - 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) + 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) } } // Store bundle in the file store to allow access from other servers. pluginFile.Seek(0, 0) - if _, appErr := s.fileStore.WriteFile(pluginFile, getBundleStorePath(manifest.Id)); appErr != nil { + if _, appErr := ch.srv.writeFile(pluginFile, getBundleStorePath(manifest.Id)); appErr != nil { return nil, model.NewAppError("uploadPlugin", "app.plugin.store_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(appErr) } - s.notifyClusterPluginEvent( + ch.notifyClusterPluginEvent( model.ClusterEventInstallPlugin, model.PluginEventData{ Id: manifest.Id, }, ) - if err := s.notifyPluginEnabled(manifest); err != nil { + if err := ch.notifyPluginEnabled(manifest); err != nil { mlog.Warn("Failed notify plugin enabled", mlog.Err(err)) } - if err := s.notifyPluginStatusesChanged(); err != nil { + if err := ch.notifyPluginStatusesChanged(); err != nil { mlog.Warn("Failed to notify plugin status changed", mlog.Err(err)) } @@ -174,10 +174,10 @@ func (s *PluginService) installPlugin(pluginFile, signature io.ReadSeeker, insta // 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 (s *PluginService) InstallMarketplacePlugin(request *model.InstallMarketplacePluginRequest) (*model.Manifest, *model.AppError) { +func (ch *Channels) InstallMarketplacePlugin(request *model.InstallMarketplacePluginRequest) (*model.Manifest, *model.AppError) { var pluginFile, signatureFile io.ReadSeeker - prepackagedPlugin, appErr := s.getPrepackagedPlugin(request.Id, request.Version) + prepackagedPlugin, appErr := ch.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 (s *PluginService) InstallMarketplacePlugin(request *model.InstallMarketpla signatureFile = bytes.NewReader(prepackagedPlugin.Signature) } - if *s.platform.Config().PluginSettings.EnableRemoteMarketplace { + if *ch.cfgSvc.Config().PluginSettings.EnableRemoteMarketplace { var plugin *model.BaseMarketplacePlugin - plugin, appErr = s.getRemoteMarketplacePlugin(request.Id, request.Version) + plugin, appErr = ch.getRemoteMarketplacePlugin(request.Id, request.Version) if appErr != nil { return nil, appErr } @@ -214,7 +214,7 @@ func (s *PluginService) InstallMarketplacePlugin(request *model.InstallMarketpla } if prepackagedVersion.LT(marketplaceVersion) { // Always true if no prepackaged plugin was found - downloadedPluginBytes, err := s.downloadFromURL(plugin.DownloadURL) + downloadedPluginBytes, err := ch.srv.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 (s *PluginService) InstallMarketplacePlugin(request *model.InstallMarketpla return nil, model.NewAppError("InstallMarketplacePlugin", "app.plugin.marketplace_plugins.signature_not_found.app_error", nil, "", http.StatusInternalServerError) } - manifest, appErr := s.installPluginWithSignature(pluginFile, signatureFile) + manifest, appErr := ch.installPluginWithSignature(pluginFile, signatureFile) if appErr != nil { return nil, appErr } @@ -253,15 +253,15 @@ const ( installPluginLocallyAlways ) -func (s *PluginService) installPluginLocally(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { - pluginsEnvironment := s.GetPluginsEnvironment() +func (ch *Channels) installPluginLocally(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { + pluginsEnvironment := ch.GetPluginsEnvironment() if pluginsEnvironment == nil { return nil, model.NewAppError("installPluginLocally", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } // verify signature if signature != nil { - if err := s.verifyPlugin(pluginFile, signature); err != nil { + if err := ch.verifyPlugin(pluginFile, signature); err != nil { return nil, err } } @@ -277,7 +277,7 @@ func (s *PluginService) installPluginLocally(pluginFile, signature io.ReadSeeker return nil, appErr } - manifest, appErr = s.installExtractedPlugin(manifest, pluginDir, installationStrategy) + manifest, appErr = ch.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 (s *PluginService) installExtractedPlugin(manifest *model.Manifest, fromPluginDir string, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { - pluginsEnvironment := s.GetPluginsEnvironment() +func (ch *Channels) installExtractedPlugin(manifest *model.Manifest, fromPluginDir string, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { + pluginsEnvironment := ch.GetPluginsEnvironment() if pluginsEnvironment == nil { return nil, model.NewAppError("installExtractedPlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } @@ -360,12 +360,12 @@ func (s *PluginService) installExtractedPlugin(manifest *model.Manifest, fromPlu // 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 := s.removePluginLocally(existingManifest.Id); err != nil { + if err := ch.removePluginLocally(existingManifest.Id); err != nil { return nil, model.NewAppError("installExtractedPlugin", "app.plugin.install_id_failed_remove.app_error", nil, "", http.StatusBadRequest) } } - pluginPath := filepath.Join(*s.platform.Config().PluginSettings.Directory, manifest.Id) + pluginPath := filepath.Join(*ch.cfgSvc.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 (s *PluginService) installExtractedPlugin(manifest *model.Manifest, fromPlu } // Activate the plugin if enabled. - pluginState := s.platform.Config().PluginSettings.PluginStates[manifest.Id] + pluginState := ch.cfgSvc.Config().PluginSettings.PluginStates[manifest.Id] if pluginState != nil && pluginState.Enable { - if hasOverride, enabled := s.getPluginStateOverride(manifest.Id); hasOverride && !enabled { + if hasOverride, enabled := ch.getPluginStateOverride(manifest.Id); hasOverride && !enabled { return manifest, nil } @@ -405,49 +405,49 @@ func (s *PluginService) installExtractedPlugin(manifest *model.Manifest, fromPlu return manifest, nil } -func (s *PluginService) RemovePlugin(id string) *model.AppError { +func (ch *Channels) RemovePlugin(id string) *model.AppError { // Disable plugin before removal to make sure this // plugin remains disabled on re-install. - if err := s.disablePlugin(id); err != nil { + if err := ch.disablePlugin(id); err != nil { return err } - if err := s.removePluginLocally(id); err != nil { + if err := ch.removePluginLocally(id); err != nil { return err } // Remove bundle from the file store. storePluginFileName := getBundleStorePath(id) - bundleExist, err := s.fileStore.FileExists(storePluginFileName) + bundleExist, err := ch.srv.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 = s.fileStore.RemoveFile(storePluginFileName); err != nil { + if err = ch.srv.removeFile(storePluginFileName); err != nil { return model.NewAppError("removePlugin", "app.plugin.remove_bundle.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } - if err2 := s.removeSignature(id); err2 != nil { - mlog.Warn("Can't remove signature", mlog.Err(err2)) + if err = ch.removeSignature(id); err != nil { + mlog.Warn("Can't remove signature", mlog.Err(err)) } - s.notifyClusterPluginEvent( + ch.notifyClusterPluginEvent( model.ClusterEventRemovePlugin, model.PluginEventData{ Id: id, }, ) - if err := s.notifyPluginStatusesChanged(); err != nil { + if err := ch.notifyPluginStatusesChanged(); err != nil { mlog.Warn("Failed to notify plugin status changed", mlog.Err(err)) } return nil } -func (s *PluginService) removePluginLocally(id string) *model.AppError { - pluginsEnvironment := s.GetPluginsEnvironment() +func (ch *Channels) removePluginLocally(id string) *model.AppError { + pluginsEnvironment := ch.GetPluginsEnvironment() if pluginsEnvironment == nil { return model.NewAppError("removePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } @@ -473,7 +473,7 @@ func (s *PluginService) removePluginLocally(id string) *model.AppError { pluginsEnvironment.Deactivate(id) pluginsEnvironment.RemovePlugin(id) - s.unregisterPluginCommands(id) + ch.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 (s *PluginService) removePluginLocally(id string) *model.AppError { return nil } -func (s *PluginService) removeSignature(pluginID string) *model.AppError { +func (ch *Channels) removeSignature(pluginID string) *model.AppError { filePath := getSignatureStorePath(pluginID) - exists, err := s.fileStore.FileExists(filePath) + exists, err := ch.srv.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 (s *PluginService) removeSignature(pluginID string) *model.AppError { mlog.Debug("no plugin signature to remove", mlog.String("plugin_id", pluginID)) return nil } - if err = s.fileStore.RemoveFile(filePath); err != nil { + if err = ch.srv.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 ca15747865..a3eb6cfb2d 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.PluginService().installPluginLocally(&nilReadSeeker{}, nil, installPluginLocallyOnlyIfNew) + actualManifest, appErr := th.App.ch.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.PluginService().installPluginLocally(reader, nil, installPluginLocallyOnlyIfNew) + actualManifest, appErr := th.App.ch.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.PluginService().installPluginLocally(reader, nil, installationStrategy) + actualManifest, appError := th.App.ch.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.PluginService().removePluginLocally(bundleInfo.Manifest.Id) + err := th.App.ch.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 208adb11f4..1ccc966822 100644 --- a/app/plugin_requests.go +++ b/app/plugin_requests.go @@ -20,16 +20,16 @@ import ( "github.com/mattermost/mattermost-server/v6/utils" ) -func (s *PluginService) ServePluginRequest(w http.ResponseWriter, r *http.Request) { +func (ch *Channels) ServePluginRequest(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) - if handler, ok := s.channels.routerSvc.getHandler(params["plugin_id"]); ok { - s.servePluginRequest(w, r, func(*plugin.Context, http.ResponseWriter, *http.Request) { + if handler, ok := ch.routerSvc.getHandler(params["plugin_id"]); ok { + ch.servePluginRequest(w, r, func(*plugin.Context, http.ResponseWriter, *http.Request) { handler.ServeHTTP(w, r) }) return } - pluginsEnvironment := s.GetPluginsEnvironment() + pluginsEnvironment := ch.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 (s *PluginService) ServePluginRequest(w http.ResponseWriter, r *http.Reques return } - s.servePluginRequest(w, r, hooks.ServeHTTP) + ch.servePluginRequest(w, r, hooks.ServeHTTP) } func (a *App) ServeInterPluginRequest(w http.ResponseWriter, r *http.Request, sourcePluginId, destinationPluginId string) { - pluginsEnvironment := a.ch.srv.pluginService.GetPluginsEnvironment() + pluginsEnvironment := a.ch.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 (s *PluginService) ServePluginPublicRequest(w http.ResponseWriter, r *http.Request) { +func (ch *Channels) ServePluginPublicRequest(w http.ResponseWriter, r *http.Request) { if strings.HasSuffix(r.URL.Path, "/") { http.NotFound(w, r) return @@ -97,7 +97,7 @@ func (s *PluginService) ServePluginPublicRequest(w http.ResponseWriter, r *http. vars := mux.Vars(r) pluginID := vars["plugin_id"] - pluginsEnv := s.GetPluginsEnvironment() + pluginsEnv := ch.GetPluginsEnvironment() // Check if someone has nullified the pluginsEnv in the meantime if pluginsEnv == nil { @@ -121,11 +121,11 @@ func (s *PluginService) ServePluginPublicRequest(w http.ResponseWriter, r *http. http.ServeFile(w, r, publicFile) } -func (s *PluginService) servePluginRequest(w http.ResponseWriter, r *http.Request, handler func(*plugin.Context, http.ResponseWriter, *http.Request)) { +func (ch *Channels) 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, s.platform.Config().ServiceSettings.TrustedProxyIPHeader), + IPAddress: utils.GetIPAddress(r, ch.cfgSvc.Config().ServiceSettings.TrustedProxyIPHeader), AcceptLanguage: r.Header.Get("Accept-Language"), UserAgent: r.UserAgent(), } @@ -148,8 +148,8 @@ func (s *PluginService) servePluginRequest(w http.ResponseWriter, r *http.Reques r.Header.Del("Mattermost-User-Id") if token != "" { - session, err := New(ServerConnector(s.channels)).GetSession(token) - defer s.platform.ReturnSessionToPool(session) + session, err := New(ServerConnector(ch)).GetSession(token) + defer ch.srv.platform.ReturnSessionToPool(session) csrfCheckPassed := false @@ -190,7 +190,7 @@ func (s *PluginService) servePluginRequest(w http.ResponseWriter, r *http.Reques mlog.String("user_id", userID), } - if *s.platform.Config().ServiceSettings.ExperimentalStrictCSRFEnforcement { + if *ch.cfgSvc.Config().ServiceSettings.ExperimentalStrictCSRFEnforcement { mlog.Warn(csrfErrorMessage, fields...) } else { mlog.Debug(csrfErrorMessage, fields...) @@ -219,7 +219,7 @@ func (s *PluginService) servePluginRequest(w http.ResponseWriter, r *http.Reques params := mux.Vars(r) - subpath, _ := utils.GetSubpathFromConfig(s.platform.Config()) + subpath, _ := utils.GetSubpathFromConfig(ch.cfgSvc.Config()) newQuery := r.URL.Query() newQuery.Del("access_token") diff --git a/app/plugin_requests_test.go b/app/plugin_requests_test.go index e457d8e5f1..c41c70be6d 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.PluginService().ServePluginPublicRequest) + handler := http.HandlerFunc(th.App.ch.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 1c77fc3814..293d882f1f 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.PluginService().ShutDownPlugins() + th.App.ch.ShutDownPlugins() }() select { diff --git a/app/plugin_signature.go b/app/plugin_signature.go index 928a9687b8..0903aa08fc 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.srv.pluginService.verifyPlugin(plugin, signature) + return a.ch.verifyPlugin(plugin, signature) } -func (s *PluginService) verifyPlugin(plugin, signature io.ReadSeeker) *model.AppError { +func (ch *Channels) verifyPlugin(plugin, signature io.ReadSeeker) *model.AppError { if err := verifySignature(bytes.NewReader(mattermostPluginPublicKey), plugin, signature); err == nil { return nil } - publicKeys := s.platform.Config().PluginSettings.SignaturePublicKeyFiles + publicKeys := ch.cfgSvc.Config().PluginSettings.SignaturePublicKeyFiles for _, pk := range publicKeys { - pkBytes, appErr := s.platform.GetConfigFile(pk) + pkBytes, appErr := ch.srv.getPublicKey(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 2b27d7520c..399d58e5b2 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 (s *PluginService) GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) { - pluginsEnvironment := s.GetPluginsEnvironment() +func (ch *Channels) GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) { + pluginsEnvironment := ch.GetPluginsEnvironment() if pluginsEnvironment == nil { return nil, model.NewAppError("GetPluginStatus", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } @@ -24,8 +24,8 @@ func (s *PluginService) GetPluginStatus(id string) (*model.PluginStatus, *model. for _, status := range pluginStatuses { if status.PluginId == id { // Add our cluster ID - if s.platform.Cluster() != nil { - status.ClusterId = s.platform.Cluster().GetClusterId() + if ch.srv.platform.Cluster() != nil { + status.ClusterId = ch.srv.platform.Cluster().GetClusterId() } return status, nil @@ -37,12 +37,12 @@ func (s *PluginService) GetPluginStatus(id string) (*model.PluginStatus, *model. // GetPluginStatus returns the status for a plugin installed on this server. func (a *App) GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) { - return a.ch.srv.pluginService.GetPluginStatus(id) + return a.ch.GetPluginStatus(id) } // GetPluginStatuses returns the status for plugins installed on this server. -func (s *PluginService) GetPluginStatuses() (model.PluginStatuses, *model.AppError) { - pluginsEnvironment := s.GetPluginsEnvironment() +func (ch *Channels) GetPluginStatuses() (model.PluginStatuses, *model.AppError) { + pluginsEnvironment := ch.GetPluginsEnvironment() if pluginsEnvironment == nil { return nil, model.NewAppError("GetPluginStatuses", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented) } @@ -54,8 +54,8 @@ func (s *PluginService) GetPluginStatuses() (model.PluginStatuses, *model.AppErr // Add our cluster ID for _, status := range pluginStatuses { - if s.platform.Cluster() != nil { - status.ClusterId = s.platform.Cluster().GetClusterId() + if ch.srv.platform.Cluster() != nil { + status.ClusterId = ch.srv.platform.Cluster().GetClusterId() } else { status.ClusterId = "" } @@ -66,22 +66,22 @@ func (s *PluginService) GetPluginStatuses() (model.PluginStatuses, *model.AppErr // GetPluginStatuses returns the status for plugins installed on this server. func (a *App) GetPluginStatuses() (model.PluginStatuses, *model.AppError) { - return a.ch.srv.pluginService.GetPluginStatuses() + return a.ch.GetPluginStatuses() } // GetClusterPluginStatuses returns the status for plugins installed anywhere in the cluster. func (a *App) GetClusterPluginStatuses() (model.PluginStatuses, *model.AppError) { - return a.ch.srv.pluginService.getClusterPluginStatuses() + return a.ch.getClusterPluginStatuses() } -func (s *PluginService) getClusterPluginStatuses() (model.PluginStatuses, *model.AppError) { - pluginStatuses, err := s.GetPluginStatuses() +func (ch *Channels) getClusterPluginStatuses() (model.PluginStatuses, *model.AppError) { + pluginStatuses, err := ch.GetPluginStatuses() if err != nil { return nil, err } - if s.platform.Cluster() != nil && *s.platform.Config().ClusterSettings.Enable { - clusterPluginStatuses, err := s.platform.Cluster().GetPluginStatuses() + if ch.srv.platform.Cluster() != nil && *ch.cfgSvc.Config().ClusterSettings.Enable { + clusterPluginStatuses, err := ch.srv.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 (s *PluginService) getClusterPluginStatuses() (model.PluginStatuses, *model return pluginStatuses, nil } -func (s *PluginService) notifyPluginStatusesChanged() error { - pluginStatuses, err := s.getClusterPluginStatuses() +func (ch *Channels) notifyPluginStatusesChanged() error { + pluginStatuses, err := ch.getClusterPluginStatuses() if err != nil { return err } @@ -102,7 +102,7 @@ func (s *PluginService) notifyPluginStatusesChanged() error { message := model.NewWebSocketEvent(model.WebsocketEventPluginStatusesChanged, "", "", "", nil, "") message.Add("plugin_statuses", pluginStatuses) message.GetBroadcast().ContainsSensitiveData = true - s.platform.Publish(message) + ch.srv.platform.Publish(message) return nil } diff --git a/app/plugin_test.go b/app/plugin_test.go index 0d3ec65431..57802c67ac 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.PluginService().ServePluginRequest(w, r) + th.App.ch.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.PluginService().servePluginRequest(recorder, request, handler) + th.App.ch.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.PluginService().servePluginRequest(nil, r, func(_ *plugin.Context, _ http.ResponseWriter, r *http.Request) { + th.App.ch.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.PluginService().RemovePlugin("testplugin") + appErr = th.App.ch.RemovePlugin("testplugin") checkNoError(t, appErr) }) }) @@ -642,7 +642,7 @@ func TestChannelsPluginsInit(t *testing.T) { path, _ := fileutils.FindDir("tests") require.NotPanics(t, func() { - th.Server.pluginService.initPlugins(ctx, path, path) + th.Server.Channels().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.PluginService().ShutDownPlugins() + th.App.ch.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.PluginService().installPluginLocally(bytes.NewReader(pluginBytes), nil, installPluginLocallyAlways) + manifest, appErr := th.App.ch.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.PluginService().processPrepackagedPlugins(prepackagedPluginsDir) + plugins := th.App.ch.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.PluginService().RemovePlugin("testplugin") + appErr = th.App.ch.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.PluginService().processPrepackagedPlugins(prepackagedPluginsDir) + plugins := th.App.ch.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.PluginService().processPrepackagedPlugins(prepackagedPluginsDir) + plugins := th.App.ch.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.PluginService().installPluginLocally(bytes.NewReader(pluginBytes), nil, installPluginLocallyAlways) + manifest, appErr := th.App.ch.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.PluginService().processPrepackagedPlugins(prepackagedPluginsDir) + plugins := th.App.ch.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.PluginService().RemovePlugin("testplugin") + appErr = th.App.ch.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.PluginService().processPrepackagedPlugins(prepackagedPluginsDir) + plugins := th.App.ch.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.PluginService().getPluginStateOverride("focalboard") + overrides, value := th.App.ch.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.PluginService().getPluginStateOverride("com.mattermost.calls") + overrides, value := th.App.ch.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.PluginService().getPluginStateOverride("com.mattermost.calls") + overrides, value := th.App.ch.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.PluginService().getPluginStateOverride("com.mattermost.calls") + overrides, value := th2.App.ch.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.PluginService().getPluginStateOverride("com.mattermost.calls") + overrides, value := th2.App.ch.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.PluginService().getPluginStateOverride("com.mattermost.calls") + overrides, value := th2.App.ch.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.PluginService().getPluginStateOverride("com.mattermost.apps") + overrides, value := th.App.ch.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.PluginService().getPluginStateOverride("com.mattermost.apps") + overrides, value := th2.App.ch.getPluginStateOverride("com.mattermost.apps") require.True(t, overrides) require.False(t, value) }) diff --git a/app/post.go b/app/post.go index b98cb13759..095494d080 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.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.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.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.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.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.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.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.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 c79036b443..fc6d54699f 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.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.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.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.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 22f9d24c1c..fbd433a6db 100644 --- a/app/server.go +++ b/app/server.go @@ -119,7 +119,6 @@ type Server struct { telemetryService *telemetry.TelemetryService userService *users.UserService teamService *teams.TeamService - pluginService *PluginService serviceMux sync.RWMutex remoteClusterService remotecluster.RemoteClusterServiceIFace @@ -719,10 +718,6 @@ 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. @@ -829,18 +824,11 @@ 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 5724f683c2..222d10e442 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.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.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.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.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 b60f9be57b..318e3ede89 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.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.RunMultiHook(func(hooks plugin.Hooks) bool { once.Do(func() { hookHasRunCh <- struct{}{} }) diff --git a/app/user.go b/app/user.go index 81faa5b57d..6e12bda34b 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.Srv().RunMultiHook(func(hooks plugin.Hooks) bool { + a.ch.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 1db5866e0a..cdf59eb31e 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.Srv()) + return a.Srv().Platform().NewWebConn(cfg, a, a.ch) } diff --git a/cmd/mattermost/commands/init.go b/cmd/mattermost/commands/init.go index a90b4b02b3..e93d9640fe 100644 --- a/cmd/mattermost/commands/init.go +++ b/cmd/mattermost/commands/init.go @@ -7,6 +7,7 @@ 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" @@ -20,11 +21,7 @@ func initDBCommandContextCobra(command *cobra.Command, readOnlyConfigStore bool) panic(err) } - err = a.Srv().InitializePluginService() - if err != nil { - return nil, err - } - + a.InitPlugins(request.EmptyContext(a.Log()), *a.Config().PluginSettings.Directory, *a.Config().PluginSettings.ClientDirectory) a.DoAppMigrations() return a, nil diff --git a/web/web_test.go b/web/web_test.go index b57e3888bd..4214649031 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.Platform()), pluginDir, webappPluginDir, false, th.App.Log(), nil) + env, err := plugin.NewEnvironment(th.NewPluginAPI, app.NewDriverImpl(th.Server), 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.PluginService().SetPluginsEnvironment(env) + th.App.Channels().SetPluginsEnvironment(env) req, _ := http.NewRequest("GET", "/plugins/com.mattermost.sample/public/hello.html", nil) res := httptest.NewRecorder() From 37960c7680f57c9fd8f0717b99d0fd8a6664db7c Mon Sep 17 00:00:00 2001 From: Kyriakos Z <3829551+koox00@users.noreply.github.com> Date: Thu, 22 Dec 2022 16:15:11 +0200 Subject: [PATCH 05/11] MM-49077: fixes errors in migration job (#21911) * MM-49077: fixes errors in migration job UrgentMentionCount does not have a default values, so COALESCE has to be used. SELECT * FROM ChannelMembers fails for the above reason. This commit SELECTs each and every column from ChannelMembers instead. * Fixes syntax error Co-authored-by: Mattermod --- store/sqlstore/channel_store.go | 52 +++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/store/sqlstore/channel_store.go b/store/sqlstore/channel_store.go index 85864f3784..ca7575924a 100644 --- a/store/sqlstore/channel_store.go +++ b/store/sqlstore/channel_store.go @@ -3850,7 +3850,31 @@ func (s SqlChannelStore) MigrateChannelMembers(fromChannelId string, fromUserId defer finalizeTransactionX(transaction, &err) channelMembers := []channelMember{} - if err := transaction.Select(&channelMembers, "SELECT * from ChannelMembers WHERE (ChannelId, UserId) > (?, ?) ORDER BY ChannelId, UserId LIMIT 100", fromChannelId, fromUserId); err != nil { + query := ` + SELECT + ChannelId, + UserId, + Roles, + LastViewedAt, + MsgCount, + MentionCount, + MentionCountRoot, + COALESCE(UrgentMentionCount, 0) AS UrgentMentionCount, + MsgCountRoot, + NotifyProps, + LastUpdateAt, + SchemeUser, + SchemeAdmin, + SchemeGuest + FROM + ChannelMembers + WHERE + (ChannelId, UserId) > (?, ?) + ORDER BY ChannelId, UserId + LIMIT 100 + ` + + if err := transaction.Select(&channelMembers, query, fromChannelId, fromUserId); err != nil { return nil, errors.Wrap(err, "failed to find ChannelMembers") } @@ -3954,7 +3978,31 @@ func (s SqlChannelStore) ClearAllCustomRoleAssignments() (err error) { } channelMembers := []*channelMember{} - if err = transaction.Select(&channelMembers, "SELECT * from ChannelMembers WHERE (ChannelId, UserId) > (?, ?) ORDER BY ChannelId, UserId LIMIT 1000", lastChannelId, lastUserId); err != nil { + query := ` + SELECT + ChannelId, + UserId, + Roles, + LastViewedAt, + MsgCount, + MentionCount, + MentionCountRoot, + COALESCE(UrgentMentionCount, 0) AS UrgentMentionCount, + MsgCountRoot, + NotifyProps, + LastUpdateAt, + SchemeUser, + SchemeAdmin, + SchemeGuest + FROM + ChannelMembers + WHERE + (ChannelId, UserId) > (?, ?) + ORDER BY ChannelId, UserId + LIMIT 1000 + ` + + if err = transaction.Select(&channelMembers, query, lastChannelId, lastUserId); err != nil { finalizeTransactionX(transaction, &err) return errors.Wrap(err, "failed to find ChannelMembers") } From f8b6b6b0edd9c0f2e78f677fdfc6e6be2eb02cd5 Mon Sep 17 00:00:00 2001 From: Nathaniel Allred Date: Thu, 22 Dec 2022 11:32:23 -0600 Subject: [PATCH 06/11] always set CWSURL in client config (#21940) --- config/client.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/config/client.go b/config/client.go index b8160993fd..e26c2bea40 100644 --- a/config/client.go +++ b/config/client.go @@ -92,6 +92,8 @@ func GenerateClientConfig(c *model.Config, telemetryID string, license *model.Li props["EnableEmailInvitations"] = strconv.FormatBool(*c.ServiceSettings.EnableEmailInvitations) + props["CWSURL"] = *c.CloudSettings.CWSURL + // Set default values for all options that require a license. props["ExperimentalEnableAuthenticationTransfer"] = "true" props["LdapNicknameAttributeSet"] = "false" @@ -123,7 +125,6 @@ func GenerateClientConfig(c *model.Config, telemetryID string, license *model.Li props["DataRetentionFileRetentionDays"] = "0" props["DataRetentionEnableBoardsDeletion"] = "false" props["DataRetentionBoardsRetentionDays"] = "0" - props["CWSURL"] = "" props["CustomUrlSchemes"] = strings.Join(c.DisplaySettings.CustomURLSchemes, ",") props["IsDefaultMarketplace"] = strconv.FormatBool(*c.PluginSettings.MarketplaceURL == model.PluginSettingsDefaultMarketplaceURL) @@ -195,10 +196,6 @@ func GenerateClientConfig(c *model.Config, telemetryID string, license *model.Li props["DataRetentionBoardsRetentionDays"] = strconv.FormatInt(int64(*c.DataRetentionSettings.BoardsRetentionDays), 10) } - if license.IsCloud() { - props["CWSURL"] = *c.CloudSettings.CWSURL - } - if *license.Features.SharedChannels { props["ExperimentalSharedChannels"] = strconv.FormatBool(*c.ExperimentalSettings.EnableSharedChannels) props["ExperimentalRemoteClusterService"] = strconv.FormatBool(c.FeatureFlags.EnableRemoteClusterService && *c.ExperimentalSettings.EnableRemoteClusterService) From 7a8b09c242cc164fe0428e748b6458228c404e9a Mon Sep 17 00:00:00 2001 From: emmyni <44761757+emmyni@users.noreply.github.com> Date: Thu, 22 Dec 2022 15:33:04 -0500 Subject: [PATCH 07/11] Add comments for the subscriptionHistoryEvent call (#21946) --- app/user.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/user.go b/app/user.go index 6e12bda34b..d259439859 100644 --- a/app/user.go +++ b/app/user.go @@ -316,7 +316,11 @@ func (a *App) createUserOrGuest(c request.CTX, user *model.User, guest bool) (*m }, plugin.UserHasBeenCreatedID) }) - // Create/Update the subscriptionHistoryEvent + // For cloud yearly subscriptions, if the current user count of the workspace exceeds the number of seats initially purchased + // (plus the “threshold” of 10%), then a subscriptionHistoryEvent object would need to be created and added to the subscriptionHistory + // table in CWS. This is then used to calculate how much the customers have to pay in addition for the extra users. If the + // workspace is currently on a monthly plan, then this function will not do anything. + go func() { _, err := a.SendSubscriptionHistoryEvent(ruser.Id) if err != nil { From 06745d9b61941ad5e14465395b48e30b332747c6 Mon Sep 17 00:00:00 2001 From: Ibrahim Serdar Acikgoz Date: Fri, 23 Dec 2022 09:45:42 +0300 Subject: [PATCH 08/11] [MM-48878] cmd/mattermost/export: start export command without metrics (#21857) --- cmd/mattermost/commands/db.go | 3 ++- cmd/mattermost/commands/export.go | 7 ++++--- cmd/mattermost/commands/init.go | 22 ++++++++-------------- cmd/mattermost/commands/jobserver.go | 3 ++- cmd/mattermost/commands/test.go | 5 +++-- 5 files changed, 19 insertions(+), 21 deletions(-) diff --git a/cmd/mattermost/commands/db.go b/cmd/mattermost/commands/db.go index db33032297..c0d186c265 100644 --- a/cmd/mattermost/commands/db.go +++ b/cmd/mattermost/commands/db.go @@ -10,6 +10,7 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/mattermost/mattermost-server/v6/app" "github.com/mattermost/mattermost-server/v6/audit" "github.com/mattermost/mattermost-server/v6/config" "github.com/mattermost/mattermost-server/v6/store/sqlstore" @@ -100,7 +101,7 @@ func initDbCmdF(command *cobra.Command, _ []string) error { } func resetCmdF(command *cobra.Command, args []string) error { - a, err := InitDBCommandContextCobra(command) + a, err := InitDBCommandContextCobra(command, app.SkipPostInitialization()) if err != nil { return err } diff --git a/cmd/mattermost/commands/export.go b/cmd/mattermost/commands/export.go index fa0f251e1b..d5573ee975 100644 --- a/cmd/mattermost/commands/export.go +++ b/cmd/mattermost/commands/export.go @@ -10,6 +10,7 @@ import ( "path/filepath" "time" + "github.com/mattermost/mattermost-server/v6/app" "github.com/mattermost/mattermost-server/v6/app/request" "github.com/mattermost/mattermost-server/v6/audit" "github.com/mattermost/mattermost-server/v6/model" @@ -93,7 +94,7 @@ func init() { } func scheduleExportCmdF(command *cobra.Command, args []string) error { - a, err := InitDBCommandContextCobra(command) + a, err := InitDBCommandContextCobra(command, app.SkipPostInitialization()) if err != nil { return err } @@ -153,7 +154,7 @@ func scheduleExportCmdF(command *cobra.Command, args []string) error { func buildExportCmdF(format string) func(command *cobra.Command, args []string) error { return func(command *cobra.Command, args []string) error { - a, err := InitDBCommandContextCobra(command) + a, err := InitDBCommandContextCobra(command, app.SkipPostInitialization()) license := a.Srv().License() if err != nil { return err @@ -201,7 +202,7 @@ func buildExportCmdF(format string) func(command *cobra.Command, args []string) } func bulkExportCmdF(command *cobra.Command, args []string) error { - a, err := InitDBCommandContextCobra(command) + a, err := InitDBCommandContextCobra(command, app.SkipPostInitialization()) if err != nil { return err } diff --git a/cmd/mattermost/commands/init.go b/cmd/mattermost/commands/init.go index e93d9640fe..1c7e8baf07 100644 --- a/cmd/mattermost/commands/init.go +++ b/cmd/mattermost/commands/init.go @@ -14,8 +14,8 @@ import ( "github.com/mattermost/mattermost-server/v6/utils" ) -func initDBCommandContextCobra(command *cobra.Command, readOnlyConfigStore bool) (*app.App, error) { - a, err := initDBCommandContext(getConfigDSN(command, config.GetEnvironment()), readOnlyConfigStore) +func initDBCommandContextCobra(command *cobra.Command, readOnlyConfigStore bool, options ...app.Option) (*app.App, error) { + a, err := initDBCommandContext(getConfigDSN(command, config.GetEnvironment()), readOnlyConfigStore, options...) if err != nil { // Returning an error just prints the usage message, so actually panic panic(err) @@ -27,25 +27,19 @@ func initDBCommandContextCobra(command *cobra.Command, readOnlyConfigStore bool) return a, nil } -func InitDBCommandContextCobra(command *cobra.Command) (*app.App, error) { - return initDBCommandContextCobra(command, true) +func InitDBCommandContextCobra(command *cobra.Command, options ...app.Option) (*app.App, error) { + return initDBCommandContextCobra(command, true, options...) } -func InitDBCommandContextCobraReadWrite(command *cobra.Command) (*app.App, error) { - return initDBCommandContextCobra(command, false) -} - -func initDBCommandContext(configDSN string, readOnlyConfigStore bool) (*app.App, error) { +func initDBCommandContext(configDSN string, readOnlyConfigStore bool, options ...app.Option) (*app.App, error) { if err := utils.TranslationsPreInit(); err != nil { return nil, err } model.AppErrorInit(i18n.T) - s, err := app.NewServer( - // The option order is important as app.Config option reads app.StartMetrics option. - app.StartMetrics, - app.Config(configDSN, readOnlyConfigStore, nil), - ) + // The option order is important as app.Config option reads app.StartMetrics option. + options = append(options, app.Config(configDSN, readOnlyConfigStore, nil)) + s, err := app.NewServer(options...) if err != nil { return nil, err } diff --git a/cmd/mattermost/commands/jobserver.go b/cmd/mattermost/commands/jobserver.go index 488b3f8fe7..be59971aab 100644 --- a/cmd/mattermost/commands/jobserver.go +++ b/cmd/mattermost/commands/jobserver.go @@ -10,6 +10,7 @@ import ( "github.com/spf13/cobra" + "github.com/mattermost/mattermost-server/v6/app" "github.com/mattermost/mattermost-server/v6/audit" "github.com/mattermost/mattermost-server/v6/config" "github.com/mattermost/mattermost-server/v6/shared/mlog" @@ -34,7 +35,7 @@ func jobserverCmdF(command *cobra.Command, args []string) error { noSchedule, _ := command.Flags().GetBool("noschedule") // Initialize - a, err := initDBCommandContext(getConfigDSN(command, config.GetEnvironment()), false) + a, err := initDBCommandContext(getConfigDSN(command, config.GetEnvironment()), false, app.StartMetrics) if err != nil { return err } diff --git a/cmd/mattermost/commands/test.go b/cmd/mattermost/commands/test.go index 75c49a4ee7..1a50d53221 100644 --- a/cmd/mattermost/commands/test.go +++ b/cmd/mattermost/commands/test.go @@ -14,6 +14,7 @@ import ( "github.com/spf13/cobra" "github.com/mattermost/mattermost-server/v6/api4" + "github.com/mattermost/mattermost-server/v6/app" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/shared/i18n" "github.com/mattermost/mattermost-server/v6/wsapi" @@ -46,7 +47,7 @@ func init() { } func webClientTestsCmdF(command *cobra.Command, args []string) error { - a, err := InitDBCommandContextCobra(command) + a, err := InitDBCommandContextCobra(command, app.StartMetrics) if err != nil { return err } @@ -70,7 +71,7 @@ func webClientTestsCmdF(command *cobra.Command, args []string) error { } func serverForWebClientTestsCmdF(command *cobra.Command, args []string) error { - a, err := InitDBCommandContextCobra(command) + a, err := InitDBCommandContextCobra(command, app.StartMetrics) if err != nil { return err } From 40cd6b16561339ae9172a30c54990975e23d4ba4 Mon Sep 17 00:00:00 2001 From: Ibrahim Serdar Acikgoz Date: Fri, 23 Dec 2022 14:34:48 +0300 Subject: [PATCH 09/11] sqlstore/store: make SetupConnection public to be used by products (#21948) --- store/sqlstore/store.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/store/sqlstore/store.go b/store/sqlstore/store.go index 630e7705d3..28a86886cf 100644 --- a/store/sqlstore/store.go +++ b/store/sqlstore/store.go @@ -226,7 +226,9 @@ func New(settings model.SqlSettings, metrics einterfaces.MetricsInterface) *SqlS return store } -func setupConnection(connType string, dataSource string, settings *model.SqlSettings) *dbsql.DB { +// SetupConnection sets up the connection to the database and pings it to make sure it's alive. +// It also applies any database configuration settings that are required. +func SetupConnection(connType string, dataSource string, settings *model.SqlSettings) *dbsql.DB { db, err := dbsql.Open(*settings.DriverName, dataSource) if err != nil { mlog.Fatal("Failed to open SQL connection to err.", mlog.Err(err)) @@ -292,7 +294,7 @@ func (ss *SqlStore) initConnection() { } } - handle := setupConnection("master", dataSource, ss.settings) + handle := SetupConnection("master", dataSource, ss.settings) ss.masterX = newSqlxDBWrapper(sqlx.NewDb(handle, ss.DriverName()), time.Duration(*ss.settings.QueryTimeout)*time.Second, *ss.settings.Trace) @@ -303,7 +305,7 @@ func (ss *SqlStore) initConnection() { if len(ss.settings.DataSourceReplicas) > 0 { ss.ReplicaXs = make([]*sqlxDBWrapper, len(ss.settings.DataSourceReplicas)) for i, replica := range ss.settings.DataSourceReplicas { - handle := setupConnection(fmt.Sprintf("replica-%v", i), replica, ss.settings) + handle := SetupConnection(fmt.Sprintf("replica-%v", i), replica, ss.settings) ss.ReplicaXs[i] = newSqlxDBWrapper(sqlx.NewDb(handle, ss.DriverName()), time.Duration(*ss.settings.QueryTimeout)*time.Second, *ss.settings.Trace) @@ -316,7 +318,7 @@ func (ss *SqlStore) initConnection() { if len(ss.settings.DataSourceSearchReplicas) > 0 { ss.searchReplicaXs = make([]*sqlxDBWrapper, len(ss.settings.DataSourceSearchReplicas)) for i, replica := range ss.settings.DataSourceSearchReplicas { - handle := setupConnection(fmt.Sprintf("search-replica-%v", i), replica, ss.settings) + handle := SetupConnection(fmt.Sprintf("search-replica-%v", i), replica, ss.settings) ss.searchReplicaXs[i] = newSqlxDBWrapper(sqlx.NewDb(handle, ss.DriverName()), time.Duration(*ss.settings.QueryTimeout)*time.Second, *ss.settings.Trace) @@ -332,7 +334,7 @@ func (ss *SqlStore) initConnection() { if src.DataSource == nil { continue } - ss.replicaLagHandles[i] = setupConnection(fmt.Sprintf(replicaLagPrefix+"-%d", i), *src.DataSource, ss.settings) + ss.replicaLagHandles[i] = SetupConnection(fmt.Sprintf(replicaLagPrefix+"-%d", i), *src.DataSource, ss.settings) } } } @@ -1073,7 +1075,7 @@ func (ss *SqlStore) migrate(direction migrationDirection) error { if err != nil { return err } - db := setupConnection("master", dataSource, ss.settings) + db := SetupConnection("master", dataSource, ss.settings) driver, err = ms.WithInstance(db) defer db.Close() case model.DatabaseDriverPostgres: From 1e02b99bf10f6aa5147f5888e2c01f994b6a9c5a Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Fri, 23 Dec 2022 19:56:49 +0530 Subject: [PATCH 10/11] MM-49354: Make compliance export query cancellable (#21943) We accept a context that can cancel the query. This allows us to exit a job faster. ```release-note Compliance export job can now cancel the SQL query execution during server shutdown which will allow the job to exit faster. ``` Co-authored-by: Mattermod --- store/opentracinglayer/opentracinglayer.go | 4 +-- store/retrylayer/retrylayer.go | 4 +-- store/sqlstore/compliance_store.go | 5 +-- store/sqlstore/sqlx_wrapper.go | 6 +++- store/store.go | 2 +- store/storetest/compliance_store.go | 37 +++++++++++----------- store/storetest/mocks/ComplianceStore.go | 20 ++++++------ store/timerlayer/timerlayer.go | 4 +-- 8 files changed, 45 insertions(+), 37 deletions(-) diff --git a/store/opentracinglayer/opentracinglayer.go b/store/opentracinglayer/opentracinglayer.go index 87b443640a..129ef5254b 100644 --- a/store/opentracinglayer/opentracinglayer.go +++ b/store/opentracinglayer/opentracinglayer.go @@ -3197,7 +3197,7 @@ func (s *OpenTracingLayerComplianceStore) GetAll(offset int, limit int) (model.C return result, err } -func (s *OpenTracingLayerComplianceStore) MessageExport(cursor model.MessageExportCursor, limit int) ([]*model.MessageExport, model.MessageExportCursor, error) { +func (s *OpenTracingLayerComplianceStore) MessageExport(ctx context.Context, cursor model.MessageExportCursor, limit int) ([]*model.MessageExport, model.MessageExportCursor, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ComplianceStore.MessageExport") s.Root.Store.SetContext(newCtx) @@ -3206,7 +3206,7 @@ func (s *OpenTracingLayerComplianceStore) MessageExport(cursor model.MessageExpo }() defer span.Finish() - result, resultVar1, err := s.ComplianceStore.MessageExport(cursor, limit) + result, resultVar1, err := s.ComplianceStore.MessageExport(ctx, cursor, limit) if err != nil { span.LogFields(spanlog.Error(err)) ext.Error.Set(span, true) diff --git a/store/retrylayer/retrylayer.go b/store/retrylayer/retrylayer.go index 38a3f52dbf..065a1400a9 100644 --- a/store/retrylayer/retrylayer.go +++ b/store/retrylayer/retrylayer.go @@ -3567,11 +3567,11 @@ func (s *RetryLayerComplianceStore) GetAll(offset int, limit int) (model.Complia } -func (s *RetryLayerComplianceStore) MessageExport(cursor model.MessageExportCursor, limit int) ([]*model.MessageExport, model.MessageExportCursor, error) { +func (s *RetryLayerComplianceStore) MessageExport(ctx context.Context, cursor model.MessageExportCursor, limit int) ([]*model.MessageExport, model.MessageExportCursor, error) { tries := 0 for { - result, resultVar1, err := s.ComplianceStore.MessageExport(cursor, limit) + result, resultVar1, err := s.ComplianceStore.MessageExport(ctx, cursor, limit) if err == nil { return result, resultVar1, nil } diff --git a/store/sqlstore/compliance_store.go b/store/sqlstore/compliance_store.go index 6e2ae38ac1..9c76cea3d6 100644 --- a/store/sqlstore/compliance_store.go +++ b/store/sqlstore/compliance_store.go @@ -4,6 +4,7 @@ package sqlstore import ( + "context" "database/sql" "fmt" "strings" @@ -270,7 +271,7 @@ func (s SqlComplianceStore) ComplianceExport(job *model.Compliance, cursor model return append(channelPosts, directMessagePosts...), cursor, nil } -func (s SqlComplianceStore) MessageExport(cursor model.MessageExportCursor, limit int) ([]*model.MessageExport, model.MessageExportCursor, error) { +func (s SqlComplianceStore) MessageExport(ctx context.Context, cursor model.MessageExportCursor, limit int) ([]*model.MessageExport, model.MessageExportCursor, error) { var args []any args = append(args, model.ChannelTypeDirect, model.ChannelTypeGroup, cursor.LastPostUpdateAt, cursor.LastPostUpdateAt, cursor.LastPostId, limit) query := @@ -317,7 +318,7 @@ func (s SqlComplianceStore) MessageExport(cursor model.MessageExportCursor, limi LIMIT ?` cposts := []*model.MessageExport{} - if err := s.GetReplicaX().Select(&cposts, query, args...); err != nil { + if err := s.GetReplicaX().SelectCtx(ctx, &cposts, query, args...); err != nil { return nil, cursor, errors.Wrap(err, "unable to export messages") } if len(cposts) > 0 { diff --git a/store/sqlstore/sqlx_wrapper.go b/store/sqlstore/sqlx_wrapper.go index 95b274aae9..3d215ff8a5 100644 --- a/store/sqlstore/sqlx_wrapper.go +++ b/store/sqlstore/sqlx_wrapper.go @@ -224,8 +224,12 @@ func (w *sqlxDBWrapper) QueryX(query string, args ...any) (*sqlx.Rows, error) { } func (w *sqlxDBWrapper) Select(dest any, query string, args ...any) error { + return w.SelectCtx(context.Background(), dest, query, args...) +} + +func (w *sqlxDBWrapper) SelectCtx(ctx context.Context, dest any, query string, args ...any) error { query = w.DB.Rebind(query) - ctx, cancel := context.WithTimeout(context.Background(), w.queryTimeout) + ctx, cancel := context.WithTimeout(ctx, w.queryTimeout) defer cancel() if w.trace { diff --git a/store/store.go b/store/store.go index 49490afdbb..8a1c69d91a 100644 --- a/store/store.go +++ b/store/store.go @@ -549,7 +549,7 @@ type ComplianceStore interface { Get(id string) (*model.Compliance, error) GetAll(offset, limit int) (model.Compliances, error) ComplianceExport(compliance *model.Compliance, cursor model.ComplianceExportCursor, limit int) ([]*model.CompliancePost, model.ComplianceExportCursor, error) - MessageExport(cursor model.MessageExportCursor, limit int) ([]*model.MessageExport, model.MessageExportCursor, error) + MessageExport(ctx context.Context, cursor model.MessageExportCursor, limit int) ([]*model.MessageExport, model.MessageExportCursor, error) } type OAuthStore interface { diff --git a/store/storetest/compliance_store.go b/store/storetest/compliance_store.go index f39b1c43ef..7c2d106325 100644 --- a/store/storetest/compliance_store.go +++ b/store/storetest/compliance_store.go @@ -4,6 +4,7 @@ package storetest import ( + "context" "encoding/json" "testing" "time" @@ -399,7 +400,7 @@ func testMessageExportPublicChannel(t *testing.T, ss store.Store) { // get the starting number of message export entries startTime := model.GetMillis() - messages, _, err := ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) + messages, _, err := ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) require.NoError(t, err) assert.Equal(t, 0, len(messages)) @@ -469,7 +470,7 @@ func testMessageExportPublicChannel(t *testing.T, ss store.Store) { // fetch the message exports for both posts that user1 sent messageExportMap := map[string]model.MessageExport{} - messages, _, err = ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) + messages, _, err = ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) require.NoError(t, err) assert.Equal(t, 2, len(messages)) @@ -503,7 +504,7 @@ func testMessageExportPrivateChannel(t *testing.T, ss store.Store) { // get the starting number of message export entries startTime := model.GetMillis() - messages, _, err := ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) + messages, _, err := ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) require.NoError(t, err) assert.Equal(t, 0, len(messages)) @@ -573,7 +574,7 @@ func testMessageExportPrivateChannel(t *testing.T, ss store.Store) { // fetch the message exports for both posts that user1 sent messageExportMap := map[string]model.MessageExport{} - messages, _, err = ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) + messages, _, err = ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) require.NoError(t, err) assert.Equal(t, 2, len(messages)) @@ -609,7 +610,7 @@ func testMessageExportDirectMessageChannel(t *testing.T, ss store.Store) { // get the starting number of message export entries startTime := model.GetMillis() - messages, _, err := ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) + messages, _, err := ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) require.NoError(t, err) assert.Equal(t, 0, len(messages)) @@ -664,7 +665,7 @@ func testMessageExportDirectMessageChannel(t *testing.T, ss store.Store) { // fetch the message export for the post that user1 sent messageExportMap := map[string]model.MessageExport{} - messages, _, err = ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) + messages, _, err = ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) require.NoError(t, err) assert.Equal(t, 1, len(messages)) @@ -690,7 +691,7 @@ func testMessageExportGroupMessageChannel(t *testing.T, ss store.Store) { // get the starting number of message export entries startTime := model.GetMillis() - messages, _, err := ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) + messages, _, err := ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) require.NoError(t, err) assert.Equal(t, 0, len(messages)) @@ -762,7 +763,7 @@ func testMessageExportGroupMessageChannel(t *testing.T, ss store.Store) { // fetch the message export for the post that user1 sent messageExportMap := map[string]model.MessageExport{} - messages, _, err = ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) + messages, _, err = ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 10}, 10) require.NoError(t, err) assert.Equal(t, 1, len(messages)) @@ -787,7 +788,7 @@ func testEditExportMessage(t *testing.T, ss store.Store) { defer cleanupStoreState(t, ss) // get the starting number of message export entries startTime := model.GetMillis() - messages, _, err := ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) + messages, _, err := ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) require.NoError(t, err) assert.Equal(t, 0, len(messages)) @@ -842,7 +843,7 @@ func testEditExportMessage(t *testing.T, ss store.Store) { require.NoError(t, err) // fetch the message exports from the start - messages, _, err = ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) + messages, _, err = ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) require.NoError(t, err) assert.Equal(t, 2, len(messages)) @@ -879,7 +880,7 @@ func testEditAfterExportMessage(t *testing.T, ss store.Store) { defer cleanupStoreState(t, ss) // get the starting number of message export entries startTime := model.GetMillis() - messages, _, err := ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) + messages, _, err := ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) require.NoError(t, err) assert.Equal(t, 0, len(messages)) @@ -927,7 +928,7 @@ func testEditAfterExportMessage(t *testing.T, ss store.Store) { require.NoError(t, err) // fetch the message exports from the start - messages, _, err = ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) + messages, _, err = ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) require.NoError(t, err) assert.Equal(t, 1, len(messages)) @@ -953,7 +954,7 @@ func testEditAfterExportMessage(t *testing.T, ss store.Store) { require.NoError(t, err) // fetch the message exports after edit - messages, _, err = ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: postEditTime - 1}, 10) + messages, _, err = ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: postEditTime - 1}, 10) require.NoError(t, err) assert.Equal(t, 2, len(messages)) @@ -990,7 +991,7 @@ func testDeleteExportMessage(t *testing.T, ss store.Store) { defer cleanupStoreState(t, ss) // get the starting number of message export entries startTime := model.GetMillis() - messages, _, err := ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) + messages, _, err := ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) require.NoError(t, err) assert.Equal(t, 0, len(messages)) @@ -1043,7 +1044,7 @@ func testDeleteExportMessage(t *testing.T, ss store.Store) { require.NoError(t, err) // fetch the message exports from the start - messages, _, err = ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) + messages, _, err = ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) require.NoError(t, err) assert.Equal(t, 1, len(messages)) @@ -1075,7 +1076,7 @@ func testDeleteAfterExportMessage(t *testing.T, ss store.Store) { defer cleanupStoreState(t, ss) // get the starting number of message export entries startTime := model.GetMillis() - messages, _, err := ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) + messages, _, err := ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) require.NoError(t, err) assert.Equal(t, 0, len(messages)) @@ -1123,7 +1124,7 @@ func testDeleteAfterExportMessage(t *testing.T, ss store.Store) { require.NoError(t, err) // fetch the message exports from the start - messages, _, err = ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) + messages, _, err = ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: startTime - 1}, 10) require.NoError(t, err) assert.Equal(t, 1, len(messages)) @@ -1146,7 +1147,7 @@ func testDeleteAfterExportMessage(t *testing.T, ss store.Store) { require.NoError(t, err) // fetch the message exports after delete - messages, _, err = ss.Compliance().MessageExport(model.MessageExportCursor{LastPostUpdateAt: postDeleteTime - 1}, 10) + messages, _, err = ss.Compliance().MessageExport(context.Background(), model.MessageExportCursor{LastPostUpdateAt: postDeleteTime - 1}, 10) require.NoError(t, err) assert.Equal(t, 1, len(messages)) diff --git a/store/storetest/mocks/ComplianceStore.go b/store/storetest/mocks/ComplianceStore.go index 657670b023..a1041e3bae 100644 --- a/store/storetest/mocks/ComplianceStore.go +++ b/store/storetest/mocks/ComplianceStore.go @@ -5,6 +5,8 @@ package mocks import ( + context "context" + model "github.com/mattermost/mattermost-server/v6/model" mock "github.com/stretchr/testify/mock" ) @@ -90,13 +92,13 @@ func (_m *ComplianceStore) GetAll(offset int, limit int) (model.Compliances, err return r0, r1 } -// MessageExport provides a mock function with given fields: cursor, limit -func (_m *ComplianceStore) MessageExport(cursor model.MessageExportCursor, limit int) ([]*model.MessageExport, model.MessageExportCursor, error) { - ret := _m.Called(cursor, limit) +// MessageExport provides a mock function with given fields: ctx, cursor, limit +func (_m *ComplianceStore) MessageExport(ctx context.Context, cursor model.MessageExportCursor, limit int) ([]*model.MessageExport, model.MessageExportCursor, error) { + ret := _m.Called(ctx, cursor, limit) var r0 []*model.MessageExport - if rf, ok := ret.Get(0).(func(model.MessageExportCursor, int) []*model.MessageExport); ok { - r0 = rf(cursor, limit) + if rf, ok := ret.Get(0).(func(context.Context, model.MessageExportCursor, int) []*model.MessageExport); ok { + r0 = rf(ctx, cursor, limit) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]*model.MessageExport) @@ -104,15 +106,15 @@ func (_m *ComplianceStore) MessageExport(cursor model.MessageExportCursor, limit } var r1 model.MessageExportCursor - if rf, ok := ret.Get(1).(func(model.MessageExportCursor, int) model.MessageExportCursor); ok { - r1 = rf(cursor, limit) + if rf, ok := ret.Get(1).(func(context.Context, model.MessageExportCursor, int) model.MessageExportCursor); ok { + r1 = rf(ctx, cursor, limit) } else { r1 = ret.Get(1).(model.MessageExportCursor) } var r2 error - if rf, ok := ret.Get(2).(func(model.MessageExportCursor, int) error); ok { - r2 = rf(cursor, limit) + if rf, ok := ret.Get(2).(func(context.Context, model.MessageExportCursor, int) error); ok { + r2 = rf(ctx, cursor, limit) } else { r2 = ret.Error(2) } diff --git a/store/timerlayer/timerlayer.go b/store/timerlayer/timerlayer.go index 02be7b110d..19ccaf656d 100644 --- a/store/timerlayer/timerlayer.go +++ b/store/timerlayer/timerlayer.go @@ -2932,10 +2932,10 @@ func (s *TimerLayerComplianceStore) GetAll(offset int, limit int) (model.Complia return result, err } -func (s *TimerLayerComplianceStore) MessageExport(cursor model.MessageExportCursor, limit int) ([]*model.MessageExport, model.MessageExportCursor, error) { +func (s *TimerLayerComplianceStore) MessageExport(ctx context.Context, cursor model.MessageExportCursor, limit int) ([]*model.MessageExport, model.MessageExportCursor, error) { start := time.Now() - result, resultVar1, err := s.ComplianceStore.MessageExport(cursor, limit) + result, resultVar1, err := s.ComplianceStore.MessageExport(ctx, cursor, limit) elapsed := float64(time.Since(start)) / float64(time.Second) if s.Root.Metrics != nil { From 7a9354cd0962ce0ddf13f4c4171e039149fffd5a Mon Sep 17 00:00:00 2001 From: Doug Lauder Date: Fri, 23 Dec 2022 10:47:29 -0500 Subject: [PATCH 11/11] Expose boards APIs for MPA (#21917) * add first batch of Boards service APIs for MPA --- go.mod | 6 ++++-- go.sum | 10 ++++++++-- product/api.go | 21 +++++++++++++++++++++ product/service.go | 1 + 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index c157155e51..2e04b4e93c 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,7 @@ require ( github.com/jmoiron/sqlx v1.3.5 github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 github.com/lib/pq v1.10.7 + github.com/mattermost/focalboard/server v0.0.0-20221222174020-fd4cf95f8ac9 github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 github.com/mattermost/gziphandler v0.0.1 github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d @@ -85,6 +86,7 @@ require ( github.com/aymerick/douceur v0.2.0 // indirect github.com/bits-and-blooms/bitset v1.3.3 // indirect github.com/bits-and-blooms/bloom/v3 v3.3.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect github.com/blevesearch/bleve_index_api v1.0.5 // indirect github.com/blevesearch/geo v0.1.15 // indirect github.com/blevesearch/go-porterstemmer v1.0.3 // indirect @@ -131,10 +133,10 @@ require ( github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/levigross/exp-html v0.0.0-20120902181939-8df60c69a8f5 // indirect + github.com/mattermost/mattermost-plugin-api v0.0.29-0.20220801143717-73008cfda2fb // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect - github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect github.com/minio/md5-simd v1.1.2 // indirect github.com/minio/sha256-simd v1.0.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect @@ -179,7 +181,7 @@ require ( gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect - lukechampine.com/uint128 v1.1.1 // indirect + lukechampine.com/uint128 v1.2.0 // indirect modernc.org/cc/v3 v3.36.0 // indirect modernc.org/ccgo/v3 v3.16.6 // indirect modernc.org/libc v1.16.7 // indirect diff --git a/go.sum b/go.sum index 5bfeee94ff..196d356a53 100644 --- a/go.sum +++ b/go.sum @@ -216,6 +216,8 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/blevesearch/bleve/v2 v2.3.6-0.20221111171245-56dc9b25507e h1:r/cWPLUPgAM3SWWniQ6j0Hzb++h+uEycDD9UOUuC1Vk= github.com/blevesearch/bleve/v2 v2.3.6-0.20221111171245-56dc9b25507e/go.mod h1:mfCWvuwg/XnPVZHEejATm5TyFqyeLmm8p9Y3xDvwz4k= github.com/blevesearch/bleve_index_api v1.0.3/go.mod h1:fiwKS0xLEm+gBRgv5mumf0dhgFr2mDgZah1pqv1c1M4= @@ -979,6 +981,8 @@ github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsI github.com/markbates/pkger v0.15.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= +github.com/mattermost/focalboard/server v0.0.0-20221222174020-fd4cf95f8ac9 h1:UE3KuILWJwTnaXAy2YHqkCu1f7i+VdX2VNukBGYbPuI= +github.com/mattermost/focalboard/server v0.0.0-20221222174020-fd4cf95f8ac9/go.mod h1:h1HQ8UVoNMyDHzjPD7UtYbTPMWjP6d1qJZuLdT6ElNg= github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 h1:Khvh6waxG1cHc4Cz5ef9n3XVCxRWpAKUtqg9PJl5+y8= github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404/go.mod h1:RyS7FDNQlzF1PsjbJWHRI35exqaKGSO9qD4iv8QjE34= github.com/mattermost/gziphandler v0.0.1 h1:uXHcXF5agnQ6bXabvpiwwwZOlCYoa7mKHH0lxns/o8w= @@ -987,6 +991,8 @@ github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d h1:/RJ/UV7M5c7L2TQ github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d/go.mod h1:HLbgMEI5K131jpxGazJ97AxfPDt31osq36YS1oxFQPQ= github.com/mattermost/logr/v2 v2.0.15 h1:+WNbGcsc3dBao65eXlceB6dTILNJRIrvubnsTl3zBew= github.com/mattermost/logr/v2 v2.0.15/go.mod h1:mpPp935r5dIkFDo2y9Q87cQWhFR/4xXpNh0k/y8Hmwg= +github.com/mattermost/mattermost-plugin-api v0.0.29-0.20220801143717-73008cfda2fb h1:q1qXKVv59rA2gcQ7lVLc5OlWBmfsR3i8mdGD5EZesyk= +github.com/mattermost/mattermost-plugin-api v0.0.29-0.20220801143717-73008cfda2fb/go.mod h1:PIeo40t9VTA4Wu1FwjzH7QmcgC3SRyk/ohCwJw4/oSo= github.com/mattermost/morph v1.0.5-0.20221115094356-4c18a75b1f5e h1:VfNz+fvJ3DxOlALM22Eea8ONp5jHrybKBCcCtDPVlss= github.com/mattermost/morph v1.0.5-0.20221115094356-4c18a75b1f5e/go.mod h1:xo0ljDknTpPxEdhhrUdwhLCexIsYyDKS6b41HqG8wGU= github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0 h1:G9tL6JXRBMzjuD1kkBtcnd42kUiT6QDwxfFYu7adM6o= @@ -1028,7 +1034,6 @@ github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= -github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= @@ -2282,8 +2287,9 @@ k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/b v1.0.0/go.mod h1:uZWcZfRj1BpYzfN9JTerzlNUnnPsV9O2ZA8JsRcubNg= modernc.org/cc/v3 v3.32.4/go.mod h1:0R6jl1aZlIl2avnYfbfHBS1QB6/f+16mihBObaBC878= modernc.org/cc/v3 v3.36.0 h1:0kmRkTmqNidmu3c7BNDSdVHCxXCkWLmWmCIVX4LUboo= diff --git a/product/api.go b/product/api.go index 5067c20ce9..587f9d788c 100644 --- a/product/api.go +++ b/product/api.go @@ -12,6 +12,8 @@ import ( "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/shared/filestore" "github.com/mattermost/mattermost-server/v6/shared/mlog" + + fb_model "github.com/mattermost/focalboard/server/model" ) // RouterService enables registering the product router to the server. After registering the @@ -189,3 +191,22 @@ type PreferencesService interface { UpdatePreferencesForUser(userID string, preferences model.Preferences) *model.AppError DeletePreferencesForUser(userID string, preferences model.Preferences) *model.AppError } + +// BoardsService is the API for accessing Boards service APIs. +// +// The service shall be registered via app.BoardsKey service key. +type BoardsService interface { + GetTemplates(teamID string, userID string) ([]*fb_model.Board, error) + GetBoard(boardID string) (*fb_model.Board, error) + CreateBoard(board *fb_model.Board, userID string, addmember bool) (*fb_model.Board, error) + PatchBoard(boardPatch *fb_model.BoardPatch, boardID string, userID string) (*fb_model.Board, error) + DeleteBoard(boardID string, userID string) error + SearchBoards(searchTerm string, searchField fb_model.BoardSearchField, userID string, includePublicBoards bool) ([]*fb_model.Board, error) + LinkBoardToChannel(boardID string, channelID string, userID string) (*fb_model.Board, error) + GetCards(boardID string) ([]*fb_model.Card, error) + GetCard(cardID string) (*fb_model.Card, error) + CreateCard(card *fb_model.Card, boardID string, userID string) (*fb_model.Card, error) + PatchCard(cardPatch *fb_model.CardPatch, cardID string, userID string) (*fb_model.Card, error) + DeleteCard(cardID string, userID string) error + HasPermissionToBoard(userID, boardID string, permission *model.Permission) bool +} diff --git a/product/service.go b/product/service.go index 221fa1c168..a0a92e6adc 100644 --- a/product/service.go +++ b/product/service.go @@ -25,4 +25,5 @@ const ( StoreKey ServiceKey = "storekey" SystemKey ServiceKey = "systemkey" PreferencesKey ServiceKey = "preferenceskey" + BoardsKey ServiceKey = "boards" )