From a850704afe1dc667cfa304ba90d658ee4ea8535c Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Fri, 12 Nov 2021 10:35:14 +0530 Subject: [PATCH] Move pluginCommands into Channels (#18974) * Move pluginCommands into Channels We move pluginCommands, pluginCommandsLock into Channels. We also move the plugin related route handlers under Channels and move the init code under NewChannels. To achieve this, the router initialization is bumped up. Along with it, we clean up some App methods which were just wrappers over Channel methods. Instead, we call the Channel method directly to make things more readable and easy to understand. ```release-note NONE ``` * fix tests ```release-note NONE ``` --- api4/plugin.go | 4 +- api4/plugin_test.go | 2 +- app/app.go | 3 + app/app_iface.go | 12 +- app/channels.go | 15 ++- app/integration_action.go | 2 +- app/notification_push_test.go | 2 + app/opentracing/opentracing_layer.go | 158 +++------------------------ app/plugin.go | 16 +-- app/plugin_api.go | 2 +- app/plugin_api_test.go | 8 +- app/plugin_commands.go | 42 ++++--- app/plugin_commands_test.go | 10 +- app/plugin_hooks_test.go | 4 +- app/plugin_install.go | 34 +----- app/plugin_install_test.go | 8 +- app/plugin_requests.go | 30 ++--- app/plugin_requests_test.go | 2 +- app/plugin_test.go | 16 +-- app/server.go | 33 ++---- web/web_test.go | 2 +- 21 files changed, 120 insertions(+), 285 deletions(-) diff --git a/api4/plugin.go b/api4/plugin.go index 5163c9df07..0c7f7a773b 100644 --- a/api4/plugin.go +++ b/api4/plugin.go @@ -151,7 +151,7 @@ func installMarketplacePlugin(c *Context, w http.ResponseWriter, r *http.Request } auditRec.AddMeta("plugin_id", pluginRequest.Id) - manifest, appErr := c.App.InstallMarketplacePlugin(pluginRequest) + manifest, appErr := c.App.Channels().InstallMarketplacePlugin(pluginRequest) if appErr != nil { c.Err = appErr return @@ -231,7 +231,7 @@ func removePlugin(c *Context, w http.ResponseWriter, r *http.Request) { return } - err := c.App.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 9751aed198..e28ee48d19 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.RemovePlugin(manifest.Id) + th.App.Channels().RemovePlugin(manifest.Id) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PluginSettings.Enable = false }) diff --git a/app/app.go b/app/app.go index ca943e5b49..ca7dd7cc02 100644 --- a/app/app.go +++ b/app/app.go @@ -81,6 +81,9 @@ func (s *Server) getFirstServerRunTimestamp() (int64, *model.AppError) { return value, nil } +func (a *App) Channels() *Channels { + return a.ch +} func (a *App) Srv() *Server { return a.ch.srv } diff --git a/app/app_iface.go b/app/app_iface.go index d9733e9ef4..3579e96b82 100644 --- a/app/app_iface.go +++ b/app/app_iface.go @@ -212,13 +212,8 @@ type AppIface interface { HubRegister(webConn *WebConn) // HubUnregister unregisters a connection from a hub. HubUnregister(webConn *WebConn) - // 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. - InstallMarketplacePlugin(request *model.InstallMarketplacePluginRequest) (*model.Manifest, *model.AppError) // InstallPlugin unpacks and installs a plugin but does not enable or activate it. InstallPlugin(pluginFile io.ReadSeeker, replace bool) (*model.Manifest, *model.AppError) - // InstallPluginWithSignature verifies and installs plugin. - InstallPluginWithSignature(pluginFile, signature io.ReadSeeker) (*model.Manifest, *model.AppError) // LimitedClientConfigWithComputed gets the configuration in a format suitable for sending to the client. LimitedClientConfigWithComputed() map[string]string // LogAuditRec logs an audit record using default LvlAuditCLI. @@ -414,6 +409,7 @@ type AppIface interface { BulkImportWithPath(c *request.Context, jsonlReader io.Reader, attachmentsReader *zip.Reader, dryRun bool, workers int, importPath string) (*model.AppError, int) CancelJob(jobId string) *model.AppError ChannelMembersToRemove(teamID *string) ([]*model.ChannelMember, *model.AppError) + Channels() *Channels CheckAndSendUserLimitWarningEmails(c *request.Context) *model.AppError CheckCanInviteToSharedChannel(channelId string) error CheckCloudAccountAtLimit() (bool, *model.AppError) @@ -809,7 +805,6 @@ type AppIface interface { ImageProxyRemover() (f func(string) string) ImportPermissions(jsonl io.Reader) error InitPlugins(c *request.Context, pluginDir, webappPluginDir string) - InstallPluginFromData(data model.PluginEventData) InvalidateAllEmailInvites() *model.AppError InvalidateCacheForUser(userID string) InviteGuestsToChannels(teamID string, guestsInvite *model.GuestsInvite, senderId string) *model.AppError @@ -899,8 +894,6 @@ type AppIface interface { RemoveFile(path string) *model.AppError RemoveLdapPrivateCertificate() *model.AppError RemoveLdapPublicCertificate() *model.AppError - RemovePlugin(id string) *model.AppError - RemovePluginFromData(data model.PluginEventData) RemoveRecentCustomStatus(userID string, status *model.CustomStatus) *model.AppError RemoveSamlIdpCertificate() *model.AppError RemoveSamlPrivateCertificate() *model.AppError @@ -990,7 +983,6 @@ type AppIface interface { SetPluginKey(pluginID string, key string, value []byte) *model.AppError SetPluginKeyWithExpiry(pluginID string, key string, value []byte, expireInSeconds int64) *model.AppError SetPluginKeyWithOptions(pluginID string, key string, value []byte, options model.PluginKVSetOptions) (bool, *model.AppError) - SetPluginsEnvironment(pluginsEnvironment *plugin.Environment) SetProfileImage(userID string, imageData *multipart.FileHeader) *model.AppError SetProfileImageFromFile(userID string, file io.Reader) *model.AppError SetProfileImageFromMultiPartFile(userID string, file multipart.File) *model.AppError @@ -1014,7 +1006,6 @@ type AppIface interface { SwitchEmailToOAuth(w http.ResponseWriter, r *http.Request, email, password, code, service string) (string, *model.AppError) SwitchLdapToEmail(ldapPassword, code, email, newPassword string) (string, *model.AppError) SwitchOAuthToEmail(email, password, requesterId string) (string, *model.AppError) - SyncPluginsActiveState() TeamMembersToRemove(teamID *string) ([]*model.TeamMember, *model.AppError) TelemetryId() string TestElasticsearch(cfg *model.Config) *model.AppError @@ -1028,7 +1019,6 @@ type AppIface interface { TotalWebsocketConnections() int TriggerWebhook(c *request.Context, payload *model.OutgoingWebhookPayload, hook *model.OutgoingWebhook, post *model.Post, channel *model.Channel) UnregisterPluginCommand(pluginID, teamID, trigger string) - UnregisterPluginCommands(pluginID string) UpdateActive(c *request.Context, user *model.User, active bool) (*model.User, *model.AppError) UpdateChannelLastViewedAt(channelIDs []string, userID string) *model.AppError UpdateChannelMemberNotifyProps(data map[string]string, channelID string, userID string) (*model.ChannelMember, *model.AppError) diff --git a/app/channels.go b/app/channels.go index 82c17e91d5..d15d4860b0 100644 --- a/app/channels.go +++ b/app/channels.go @@ -18,9 +18,11 @@ import ( type Channels struct { srv *Server + pluginCommandsLock sync.RWMutex + pluginCommands []*PluginCommand + pluginsLock sync.RWMutex pluginsEnvironment *plugin.Environment pluginConfigListenerID string - pluginsLock sync.RWMutex imageProxy *imageproxy.ImageProxy @@ -44,10 +46,17 @@ func init() { } func NewChannels(s *Server) (*Channels, error) { - return &Channels{ + ch := &Channels{ srv: s, imageProxy: imageproxy.MakeImageProxy(s, s.httpService, s.Log), - }, nil + } + // 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) + + return ch, nil } func (ch *Channels) Start() error { diff --git a/app/integration_action.go b/app/integration_action.go index a09713c609..7a9e4644bb 100644 --- a/app/integration_action.go +++ b/app/integration_action.go @@ -423,7 +423,7 @@ func (a *App) doPluginRequest(c *request.Context, method, rawURL string, values params["plugin_id"] = pluginID r = mux.SetURLVars(r, params) - a.ch.srv.ServePluginRequest(w, r) + a.ch.ServePluginRequest(w, r) resp := &http.Response{ StatusCode: w.status, diff --git a/app/notification_push_test.go b/app/notification_push_test.go index 6a8538b6f8..297bc12304 100644 --- a/app/notification_push_test.go +++ b/app/notification_push_test.go @@ -12,6 +12,7 @@ import ( "testing" "time" + "github.com/gorilla/mux" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -1381,6 +1382,7 @@ func TestPushNotificationRace(t *testing.T) { configStore: memoryStore, Store: mockStore, products: make(map[string]Product), + Router: mux.NewRouter(), } ch, err := NewChannels(s) require.NoError(t, err) diff --git a/app/opentracing/opentracing_layer.go b/app/opentracing/opentracing_layer.go index 1f4b8525d5..927b2a3836 100644 --- a/app/opentracing/opentracing_layer.go +++ b/app/opentracing/opentracing_layer.go @@ -1112,6 +1112,23 @@ func (a *OpenTracingAppLayer) ChannelMembersToRemove(teamID *string) ([]*model.C return resultVar0, resultVar1 } +func (a *OpenTracingAppLayer) Channels() *app.Channels { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.Channels") + + 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.Channels() + + return resultVar0 +} + func (a *OpenTracingAppLayer) CheckAndSendUserLimitWarningEmails(c *request.Context) *model.AppError { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CheckAndSendUserLimitWarningEmails") @@ -10623,28 +10640,6 @@ func (a *OpenTracingAppLayer) InitPlugins(c *request.Context, pluginDir string, a.app.InitPlugins(c, pluginDir, webappPluginDir) } -func (a *OpenTracingAppLayer) InstallMarketplacePlugin(request *model.InstallMarketplacePluginRequest) (*model.Manifest, *model.AppError) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.InstallMarketplacePlugin") - - a.ctx = newCtx - a.app.Srv().Store.SetContext(newCtx) - defer func() { - a.app.Srv().Store.SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - resultVar0, resultVar1 := a.app.InstallMarketplacePlugin(request) - - if resultVar1 != nil { - span.LogFields(spanlog.Error(resultVar1)) - ext.Error.Set(span, true) - } - - return resultVar0, resultVar1 -} - func (a *OpenTracingAppLayer) InstallPlugin(pluginFile io.ReadSeeker, replace bool) (*model.Manifest, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.InstallPlugin") @@ -10667,43 +10662,6 @@ func (a *OpenTracingAppLayer) InstallPlugin(pluginFile io.ReadSeeker, replace bo return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) InstallPluginFromData(data model.PluginEventData) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.InstallPluginFromData") - - a.ctx = newCtx - a.app.Srv().Store.SetContext(newCtx) - defer func() { - a.app.Srv().Store.SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - a.app.InstallPluginFromData(data) -} - -func (a *OpenTracingAppLayer) InstallPluginWithSignature(pluginFile io.ReadSeeker, signature io.ReadSeeker) (*model.Manifest, *model.AppError) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.InstallPluginWithSignature") - - a.ctx = newCtx - a.app.Srv().Store.SetContext(newCtx) - defer func() { - a.app.Srv().Store.SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - resultVar0, resultVar1 := a.app.InstallPluginWithSignature(pluginFile, signature) - - if resultVar1 != nil { - span.LogFields(spanlog.Error(resultVar1)) - ext.Error.Set(span, true) - } - - return resultVar0, resultVar1 -} - func (a *OpenTracingAppLayer) InvalidateAllEmailInvites() *model.AppError { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.InvalidateAllEmailInvites") @@ -12769,43 +12727,6 @@ func (a *OpenTracingAppLayer) RemoveLdapPublicCertificate() *model.AppError { return resultVar0 } -func (a *OpenTracingAppLayer) RemovePlugin(id string) *model.AppError { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RemovePlugin") - - 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.RemovePlugin(id) - - if resultVar0 != nil { - span.LogFields(spanlog.Error(resultVar0)) - ext.Error.Set(span, true) - } - - return resultVar0 -} - -func (a *OpenTracingAppLayer) RemovePluginFromData(data model.PluginEventData) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RemovePluginFromData") - - a.ctx = newCtx - a.app.Srv().Store.SetContext(newCtx) - defer func() { - a.app.Srv().Store.SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - a.app.RemovePluginFromData(data) -} - func (a *OpenTracingAppLayer) RemoveRecentCustomStatus(userID string, status *model.CustomStatus) *model.AppError { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RemoveRecentCustomStatus") @@ -14835,21 +14756,6 @@ func (a *OpenTracingAppLayer) SetPluginKeyWithOptions(pluginID string, key strin return resultVar0, resultVar1 } -func (a *OpenTracingAppLayer) SetPluginsEnvironment(pluginsEnvironment *plugin.Environment) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SetPluginsEnvironment") - - a.ctx = newCtx - a.app.Srv().Store.SetContext(newCtx) - defer func() { - a.app.Srv().Store.SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - a.app.SetPluginsEnvironment(pluginsEnvironment) -} - func (a *OpenTracingAppLayer) SetProfileImage(userID string, imageData *multipart.FileHeader) *model.AppError { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SetProfileImage") @@ -15352,21 +15258,6 @@ func (a *OpenTracingAppLayer) SyncPlugins() *model.AppError { return resultVar0 } -func (a *OpenTracingAppLayer) SyncPluginsActiveState() { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SyncPluginsActiveState") - - a.ctx = newCtx - a.app.Srv().Store.SetContext(newCtx) - defer func() { - a.app.Srv().Store.SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - a.app.SyncPluginsActiveState() -} - func (a *OpenTracingAppLayer) SyncRolesAndMembership(c *request.Context, syncableID string, syncableType model.GroupSyncableType, includeRemovedMembers bool) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SyncRolesAndMembership") @@ -15688,21 +15579,6 @@ func (a *OpenTracingAppLayer) UnregisterPluginCommand(pluginID string, teamID st a.app.UnregisterPluginCommand(pluginID, teamID, trigger) } -func (a *OpenTracingAppLayer) UnregisterPluginCommands(pluginID string) { - origCtx := a.ctx - span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UnregisterPluginCommands") - - a.ctx = newCtx - a.app.Srv().Store.SetContext(newCtx) - defer func() { - a.app.Srv().Store.SetContext(origCtx) - a.ctx = origCtx - }() - - defer span.Finish() - a.app.UnregisterPluginCommands(pluginID) -} - func (a *OpenTracingAppLayer) UpdateActive(c *request.Context, user *model.User, active bool) (*model.User, *model.AppError) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateActive") diff --git a/app/plugin.go b/app/plugin.go index 6dcfabb3c0..e772fb80c9 100644 --- a/app/plugin.go +++ b/app/plugin.go @@ -62,15 +62,11 @@ func (a *App) GetPluginsEnvironment() *plugin.Environment { return a.ch.GetPluginsEnvironment() } -func (a *App) SetPluginsEnvironment(pluginsEnvironment *plugin.Environment) { - a.ch.pluginsLock.Lock() - defer a.ch.pluginsLock.Unlock() +func (ch *Channels) SetPluginsEnvironment(pluginsEnvironment *plugin.Environment) { + ch.pluginsLock.Lock() + defer ch.pluginsLock.Unlock() - a.ch.pluginsEnvironment = pluginsEnvironment -} - -func (a *App) SyncPluginsActiveState() { - a.ch.syncPluginsActiveState() + ch.pluginsEnvironment = pluginsEnvironment } func (ch *Channels) syncPluginsActiveState() { @@ -452,7 +448,7 @@ func (ch *Channels) disablePlugin(id string) *model.AppError { ch.srv.UpdateConfig(func(cfg *model.Config) { cfg.PluginSettings.PluginStates[id] = &model.PluginState{Enable: false} }) - ch.srv.unregisterPluginCommands(id) + ch.unregisterPluginCommands(id) // This call will implicitly invoke SyncPluginsActiveState which will deactivate disabled plugins. if _, _, err := ch.srv.SaveConfig(ch.srv.Config(), true); err != nil { @@ -994,7 +990,7 @@ func (ch *Channels) installFeatureFlagPlugins() { } } - _, err := ch.installMarketplacePlugin(&model.InstallMarketplacePluginRequest{ + _, err := ch.InstallMarketplacePlugin(&model.InstallMarketplacePluginRequest{ Id: pluginID, Version: version, }) diff --git a/app/plugin_api.go b/app/plugin_api.go index b21ca7d8d2..92403aa8b3 100644 --- a/app/plugin_api.go +++ b/app/plugin_api.go @@ -832,7 +832,7 @@ func (api *PluginAPI) DisablePlugin(id string) *model.AppError { } func (api *PluginAPI) RemovePlugin(id string) *model.AppError { - return api.app.RemovePlugin(id) + return api.app.Channels().RemovePlugin(id) } func (api *PluginAPI) GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) { diff --git a/app/plugin_api_test.go b/app/plugin_api_test.go index 3397ed2f67..d70d668d3c 100644 --- a/app/plugin_api_test.go +++ b/app/plugin_api_test.go @@ -119,7 +119,7 @@ func setupMultiPluginAPITest(t *testing.T, pluginCodes []string, pluginManifests }) } - app.SetPluginsEnvironment(env) + app.ch.SetPluginsEnvironment(env) return pluginDir } @@ -854,7 +854,7 @@ func TestPluginAPIGetPlugins(t *testing.T) { require.True(t, activated) pluginManifests = append(pluginManifests, manifest) } - th.App.SetPluginsEnvironment(env) + th.App.ch.SetPluginsEnvironment(env) // Deactivate the last one for testing success := env.Deactivate(pluginIDs[len(pluginIDs)-1]) @@ -928,7 +928,7 @@ func TestInstallPlugin(t *testing.T) { env, err := plugin.NewEnvironment(newPluginAPI, NewDriverImpl(app.Srv()), pluginDir, webappPluginDir, app.Log(), nil) require.NoError(t, err) - app.SetPluginsEnvironment(env) + app.ch.SetPluginsEnvironment(env) backend := filepath.Join(pluginDir, pluginID, "backend.exe") utils.CompileGo(t, pluginCode, backend) @@ -1604,7 +1604,7 @@ func TestAPIMetrics(t *testing.T) { env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server), pluginDir, webappPluginDir, th.App.Log(), metricsMock) require.NoError(t, err) - th.App.SetPluginsEnvironment(env) + th.App.ch.SetPluginsEnvironment(env) pluginID := model.NewId() backend := filepath.Join(pluginDir, pluginID, "backend.exe") diff --git a/app/plugin_commands.go b/app/plugin_commands.go index 47830f9f4d..edc5a2cc2a 100644 --- a/app/plugin_commands.go +++ b/app/plugin_commands.go @@ -54,10 +54,10 @@ func (a *App) RegisterPluginCommand(pluginID string, command *model.Command) err AutocompleteIconData: command.AutocompleteIconData, } - a.Srv().pluginCommandsLock.Lock() - defer a.Srv().pluginCommandsLock.Unlock() + a.ch.pluginCommandsLock.Lock() + defer a.ch.pluginCommandsLock.Unlock() - for _, pc := range a.Srv().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 @@ -66,7 +66,7 @@ func (a *App) RegisterPluginCommand(pluginID string, command *model.Command) err } } - a.Srv().pluginCommands = append(a.Srv().pluginCommands, &PluginCommand{ + a.ch.pluginCommands = append(a.ch.pluginCommands, &PluginCommand{ Command: command, PluginId: pluginID, }) @@ -76,41 +76,37 @@ func (a *App) RegisterPluginCommand(pluginID string, command *model.Command) err func (a *App) UnregisterPluginCommand(pluginID, teamID, trigger string) { trigger = strings.ToLower(trigger) - a.Srv().pluginCommandsLock.Lock() - defer a.Srv().pluginCommandsLock.Unlock() + a.ch.pluginCommandsLock.Lock() + defer a.ch.pluginCommandsLock.Unlock() var remaining []*PluginCommand - for _, pc := range a.Srv().pluginCommands { + for _, pc := range a.ch.pluginCommands { if pc.Command.TeamId != teamID || pc.Command.Trigger != trigger { remaining = append(remaining, pc) } } - a.Srv().pluginCommands = remaining + a.ch.pluginCommands = remaining } -func (a *App) UnregisterPluginCommands(pluginID string) { - a.Srv().unregisterPluginCommands(pluginID) -} - -func (s *Server) 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 { - a.Srv().pluginCommandsLock.RLock() - defer a.Srv().pluginCommandsLock.RUnlock() + a.ch.pluginCommandsLock.RLock() + defer a.ch.pluginCommandsLock.RUnlock() var commands []*model.Command - for _, pc := range a.Srv().pluginCommands { + for _, pc := range a.ch.pluginCommands { if pc.Command.TeamId == "" || pc.Command.TeamId == teamID { commands = append(commands, pc.Command) } @@ -126,14 +122,14 @@ func (a *App) tryExecutePluginCommand(c *request.Context, args *model.CommandArg trigger = strings.ToLower(trigger) var matched *PluginCommand - a.Srv().pluginCommandsLock.RLock() - for _, pc := range a.Srv().pluginCommands { + 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.Srv().pluginCommandsLock.RUnlock() + 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 f53b3c8e44..f611f85364 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.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.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.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.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.RemovePlugin(pluginIDs[0]) + th.App.ch.RemovePlugin(pluginIDs[0]) }) } diff --git a/app/plugin_hooks_test.go b/app/plugin_hooks_test.go index e8d9a1397c..581d583aa8 100644 --- a/app/plugin_hooks_test.go +++ b/app/plugin_hooks_test.go @@ -37,7 +37,7 @@ func SetAppEnvironmentWithPlugins(t *testing.T, pluginCode []string, app *App, a env, err := plugin.NewEnvironment(apiFunc, NewDriverImpl(app.Srv()), pluginDir, webappPluginDir, app.Log(), nil) require.NoError(t, err) - app.SetPluginsEnvironment(env) + app.ch.SetPluginsEnvironment(env) pluginIDs := []string{} activationErrors := []error{} for _, code := range pluginCode { @@ -1048,7 +1048,7 @@ func TestHookMetrics(t *testing.T) { env, err := plugin.NewEnvironment(th.NewPluginAPI, NewDriverImpl(th.Server), pluginDir, webappPluginDir, th.App.Log(), metricsMock) require.NoError(t, err) - th.App.SetPluginsEnvironment(env) + th.App.ch.SetPluginsEnvironment(env) pluginID := model.NewId() backend := filepath.Join(pluginDir, pluginID, "backend.exe") diff --git a/app/plugin_install.go b/app/plugin_install.go index 185a6fcedd..179f52ff4d 100644 --- a/app/plugin_install.go +++ b/app/plugin_install.go @@ -61,10 +61,6 @@ const managedPluginFileName = ".filestore" // fileStorePluginFolder is the folder name in the file store of the plugin bundles installed. const fileStorePluginFolder = "plugins" -func (a *App) InstallPluginFromData(data model.PluginEventData) { - a.ch.installPluginFromData(data) -} - func (ch *Channels) installPluginFromData(data model.PluginEventData) { mlog.Debug("Installing plugin as per cluster message", mlog.String("plugin_id", data.Id)) @@ -111,10 +107,6 @@ func (ch *Channels) installPluginFromData(data model.PluginEventData) { } } -func (a *App) RemovePluginFromData(data model.PluginEventData) { - a.ch.removePluginFromData(data) -} - func (ch *Channels) removePluginFromData(data model.PluginEventData) { mlog.Debug("Removing plugin as per cluster message", mlog.String("plugin_id", data.Id)) @@ -128,10 +120,6 @@ func (ch *Channels) removePluginFromData(data model.PluginEventData) { } // InstallPluginWithSignature verifies and installs plugin. -func (a *App) InstallPluginWithSignature(pluginFile, signature io.ReadSeeker) (*model.Manifest, *model.AppError) { - return a.ch.installPluginWithSignature(pluginFile, signature) -} - func (ch *Channels) installPluginWithSignature(pluginFile, signature io.ReadSeeker) (*model.Manifest, *model.AppError) { return ch.installPlugin(pluginFile, signature, installPluginLocallyAlways) } @@ -189,11 +177,7 @@ 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 (a *App) InstallMarketplacePlugin(request *model.InstallMarketplacePluginRequest) (*model.Manifest, *model.AppError) { - return a.ch.installMarketplacePlugin(request) -} - -func (ch *Channels) 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 := ch.getPrepackagedPlugin(request.Id, request.Version) @@ -257,10 +241,6 @@ const ( installPluginLocallyAlways ) -func (a *App) installPluginLocally(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { - return a.ch.installPluginLocally(pluginFile, signature, installationStrategy) -} - func (ch *Channels) installPluginLocally(pluginFile, signature io.ReadSeeker, installationStrategy pluginInstallationStrategy) (*model.Manifest, *model.AppError) { pluginsEnvironment := ch.GetPluginsEnvironment() if pluginsEnvironment == nil { @@ -412,11 +392,7 @@ func (ch *Channels) installExtractedPlugin(manifest *model.Manifest, fromPluginD return manifest, nil } -func (a *App) RemovePlugin(id string) *model.AppError { - return a.ch.removePlugin(id) -} - -func (ch *Channels) 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 := ch.disablePlugin(id); err != nil { @@ -457,10 +433,6 @@ func (ch *Channels) removePlugin(id string) *model.AppError { return nil } -func (a *App) removePluginLocally(id string) *model.AppError { - return a.ch.removePluginLocally(id) -} - func (ch *Channels) removePluginLocally(id string) *model.AppError { pluginsEnvironment := ch.GetPluginsEnvironment() if pluginsEnvironment == nil { @@ -488,7 +460,7 @@ func (ch *Channels) removePluginLocally(id string) *model.AppError { pluginsEnvironment.Deactivate(id) pluginsEnvironment.RemovePlugin(id) - ch.srv.unregisterPluginCommands(id) + ch.unregisterPluginCommands(id) if err := os.RemoveAll(pluginPath); err != nil { return model.NewAppError("removePlugin", "app.plugin.remove.app_error", nil, err.Error(), http.StatusInternalServerError) diff --git a/app/plugin_install_test.go b/app/plugin_install_test.go index 69abe2ae66..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.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.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.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.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 c9d48a6307..fcb6ae9950 100644 --- a/app/plugin_requests.go +++ b/app/plugin_requests.go @@ -20,11 +20,11 @@ import ( "github.com/mattermost/mattermost-server/v6/utils" ) -func (s *Server) ServePluginRequest(w http.ResponseWriter, r *http.Request) { - pluginsEnvironment := s.Channels().GetPluginsEnvironment() +func (ch *Channels) ServePluginRequest(w http.ResponseWriter, r *http.Request) { + pluginsEnvironment := ch.GetPluginsEnvironment() if pluginsEnvironment == nil { err := model.NewAppError("ServePluginRequest", "app.plugin.disabled.app_error", nil, "Enable plugins to serve plugin requests", http.StatusNotImplemented) - s.Log.Error(err.Error()) + mlog.Error(err.Error()) w.WriteHeader(err.StatusCode) w.Header().Set("Content-Type", "application/json") w.Write([]byte(err.ToJSON())) @@ -34,7 +34,7 @@ func (s *Server) ServePluginRequest(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) hooks, err := pluginsEnvironment.HooksForPlugin(params["plugin_id"]) if err != nil { - s.Log.Error("Access to route for non-existent plugin", + mlog.Error("Access to route for non-existent plugin", mlog.String("missing_plugin_id", params["plugin_id"]), mlog.String("url", r.URL.String()), mlog.Err(err)) @@ -42,7 +42,7 @@ func (s *Server) ServePluginRequest(w http.ResponseWriter, r *http.Request) { 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) { @@ -80,7 +80,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 *Server) 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 @@ -90,7 +90,7 @@ func (s *Server) ServePluginPublicRequest(w http.ResponseWriter, r *http.Request vars := mux.Vars(r) pluginID := vars["plugin_id"] - pluginsEnv := s.Channels().GetPluginsEnvironment() + pluginsEnv := ch.GetPluginsEnvironment() // Check if someone has nullified the pluginsEnv in the meantime if pluginsEnv == nil { @@ -114,11 +114,11 @@ func (s *Server) ServePluginPublicRequest(w http.ResponseWriter, r *http.Request http.ServeFile(w, r, publicFile) } -func (s *Server) 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.Config().ServiceSettings.TrustedProxyIPHeader), + IPAddress: utils.GetIPAddress(r, ch.srv.Config().ServiceSettings.TrustedProxyIPHeader), AcceptLanguage: r.Header.Get("Accept-Language"), UserAgent: r.UserAgent(), } @@ -141,8 +141,8 @@ func (s *Server) servePluginRequest(w http.ResponseWriter, r *http.Request, hand r.Header.Del("Mattermost-User-Id") if token != "" { - session, err := New(ServerConnector(s.Channels())).GetSession(token) - defer s.userService.ReturnSessionToPool(session) + session, err := New(ServerConnector(ch)).GetSession(token) + defer ch.srv.userService.ReturnSessionToPool(session) csrfCheckPassed := false @@ -183,10 +183,10 @@ func (s *Server) servePluginRequest(w http.ResponseWriter, r *http.Request, hand mlog.String("user_id", userID), } - if *s.Config().ServiceSettings.ExperimentalStrictCSRFEnforcement { - s.Log.Warn(csrfErrorMessage, fields...) + if *ch.srv.Config().ServiceSettings.ExperimentalStrictCSRFEnforcement { + mlog.Warn(csrfErrorMessage, fields...) } else { - s.Log.Debug(csrfErrorMessage, fields...) + mlog.Debug(csrfErrorMessage, fields...) csrfCheckPassed = true } } @@ -212,7 +212,7 @@ func (s *Server) servePluginRequest(w http.ResponseWriter, r *http.Request, hand params := mux.Vars(r) - subpath, _ := utils.GetSubpathFromConfig(s.Config()) + subpath, _ := utils.GetSubpathFromConfig(ch.srv.Config()) newQuery := r.URL.Query() newQuery.Del("access_token") diff --git a/app/plugin_requests_test.go b/app/plugin_requests_test.go index 20a6fc5af1..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.Srv().ServePluginPublicRequest) + handler := http.HandlerFunc(th.App.ch.ServePluginPublicRequest) handler.ServeHTTP(rr, req) assert.Equal(t, http.StatusNotFound, rr.Code) diff --git a/app/plugin_test.go b/app/plugin_test.go index 946ab93cd2..8c395a0b93 100644 --- a/app/plugin_test.go +++ b/app/plugin_test.go @@ -342,7 +342,7 @@ func TestServePluginRequest(t *testing.T) { w := httptest.NewRecorder() r := httptest.NewRequest("GET", "/plugins/foo/bar", nil) - th.App.ch.srv.ServePluginRequest(w, r) + th.App.ch.ServePluginRequest(w, r) assert.Equal(t, http.StatusNotImplemented, w.Result().StatusCode) } @@ -386,7 +386,7 @@ func TestPrivateServePluginRequest(t *testing.T) { request = mux.SetURLVars(request, map[string]string{"plugin_id": "id"}) - th.App.ch.srv.servePluginRequest(recorder, request, handler) + th.App.ch.servePluginRequest(recorder, request, handler) }) } @@ -409,7 +409,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.srv.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) }) }) @@ -621,7 +621,7 @@ func TestPluginSync(t *testing.T) { appErr = th.App.DeletePublicKey("pub_key") checkNoError(t, appErr) - appErr = th.App.RemovePlugin("testplugin") + appErr = th.App.ch.RemovePlugin("testplugin") checkNoError(t, appErr) }) }) @@ -758,7 +758,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { require.NoError(t, err) require.NotNil(t, pluginBytes) - manifest, appErr := th.App.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) @@ -785,7 +785,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { require.Len(t, pluginStatus, 1) require.Equal(t, pluginStatus[0].PluginId, "testplugin") - appErr = th.App.RemovePlugin("testplugin") + appErr = th.App.ch.RemovePlugin("testplugin") checkNoError(t, appErr) pluginStatus, err = env.Statuses() @@ -866,7 +866,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { require.NoError(t, err) require.NotNil(t, pluginBytes) - manifest, appErr := th.App.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) @@ -896,7 +896,7 @@ func TestProcessPrepackagedPlugins(t *testing.T) { require.Len(t, pluginStatus, 1) require.Equal(t, pluginStatus[0].PluginId, "testplugin") - appErr = th.App.RemovePlugin("testplugin") + appErr = th.App.ch.RemovePlugin("testplugin") checkNoError(t, appErr) pluginStatus, err = env.Statuses() diff --git a/app/server.go b/app/server.go index 422b2b1dd3..f2d47c4abd 100644 --- a/app/server.go +++ b/app/server.go @@ -142,9 +142,6 @@ type Server struct { configStore *config.Store postActionCookieSecret []byte - pluginCommands []*PluginCommand - pluginCommandsLock sync.RWMutex - telemetryService *telemetry.TelemetryService userService *users.UserService teamService *teams.TeamService @@ -213,6 +210,7 @@ func NewServer(options ...Option) (*Server, error) { licenseListeners: map[string]func(*model.License, *model.License){}, hashSeed: maphash.MakeSeed(), uploadLockMap: map[string]bool{}, + timezones: timezones.New(), products: make(map[string]Product), } @@ -245,6 +243,12 @@ func NewServer(options ...Option) (*Server, error) { mlog.Error("Could not initiate logging", mlog.Err(err)) } + subpath, err := utils.GetSubpathFromConfig(s.Config()) + if err != nil { + return nil, errors.Wrap(err, "failed to parse SiteURL subpath") + } + s.Router = s.RootRouter.PathPrefix(subpath).Subrouter() + // This is called after initLogging() to avoid a race condition. mlog.Info("Server is initializing...", mlog.String("go_version", runtime.Version())) @@ -253,9 +257,9 @@ func NewServer(options ...Option) (*Server, error) { // Step 3: Initialize products. // Depends on s.httpService. for name, initializer := range products { - prod, err := initializer(s) - if err != nil { - return nil, errors.Wrapf(err, "error initializing product: %s", name) + prod, err2 := initializer(s) + if err2 != nil { + return nil, errors.Wrapf(err2, "error initializing product: %s", name) } s.products[name] = prod @@ -280,8 +284,8 @@ func NewServer(options ...Option) (*Server, error) { // At the moment we only have this implementation // in the future the cache provider will be built based on the loaded config s.CacheProvider = cache.NewProvider() - if err := s.CacheProvider.Connect(); err != nil { - return nil, errors.Wrapf(err, "Unable to connect to cache provider") + if err2 := s.CacheProvider.Connect(); err2 != nil { + return nil, errors.Wrapf(err2, "Unable to connect to cache provider") } // It is important to initialize the hub only after the global logger is set @@ -326,7 +330,6 @@ func NewServer(options ...Option) (*Server, error) { } } - var err error s.Store, err = s.newStore() if err != nil { return nil, errors.Wrap(err, "cannot create store") @@ -532,17 +535,6 @@ func NewServer(options ...Option) (*Server, error) { return nil, errors.Wrapf(err, "unable to ensure first run timestamp") } - subpath, err := utils.GetSubpathFromConfig(s.Config()) - if err != nil { - return nil, errors.Wrap(err, "failed to parse SiteURL subpath") - } - s.Router = s.RootRouter.PathPrefix(subpath).Subrouter() - - pluginsRoute := s.Router.PathPrefix("/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}").Subrouter() - pluginsRoute.HandleFunc("", s.ServePluginRequest) - pluginsRoute.HandleFunc("/public/{public_file:.*}", s.ServePluginPublicRequest) - pluginsRoute.HandleFunc("/{anything:.*}", s.ServePluginRequest) - // If configured with a subpath, redirect 404s at the root back into the subpath. if subpath != "/" { s.RootRouter.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -580,7 +572,6 @@ func NewServer(options ...Option) (*Server, error) { } } - s.timezones = timezones.New() // Start email batching because it's not like the other jobs s.AddConfigListener(func(_, _ *model.Config) { s.EmailService.InitEmailBatching() diff --git a/web/web_test.go b/web/web_test.go index c2d85183ca..0e06a33cf0 100644 --- a/web/web_test.go +++ b/web/web_test.go @@ -327,7 +327,7 @@ func TestPublicFilesRequest(t *testing.T) { require.NotNil(t, manifest) require.True(t, activated) - th.App.SetPluginsEnvironment(env) + th.App.Channels().SetPluginsEnvironment(env) req, _ := http.NewRequest("GET", "/plugins/com.mattermost.sample/public/hello.html", nil) res := httptest.NewRecorder()