summaryrefslogtreecommitdiffstats
path: root/plugin
diff options
context:
space:
mode:
Diffstat (limited to 'plugin')
-rw-r--r--plugin/api.go84
-rw-r--r--plugin/client.go63
-rw-r--r--plugin/client_rpc.go328
-rw-r--r--plugin/client_rpc_generated.go1829
-rw-r--r--plugin/context.go10
-rw-r--r--plugin/doc.go9
-rw-r--r--plugin/environment.go271
-rw-r--r--plugin/example_hello_user_test.go39
-rw-r--r--plugin/example_hello_world_test.go6
-rw-r--r--plugin/example_help_test.go71
-rw-r--r--plugin/example_test.go35
-rw-r--r--plugin/hclog_adapter.go99
-rw-r--r--plugin/hooks.go80
-rw-r--r--plugin/http.go83
-rw-r--r--plugin/interface_generator/main.go397
-rw-r--r--plugin/io_rpc.go (renamed from plugin/rpcplugin/io.go)20
-rw-r--r--plugin/mock_api_test.go1018
-rw-r--r--plugin/plugin.go18
-rw-r--r--plugin/pluginenv/environment.go396
-rw-r--r--plugin/pluginenv/environment_test.go409
-rw-r--r--plugin/pluginenv/options.go50
-rw-r--r--plugin/pluginenv/options_test.go32
-rw-r--r--plugin/pluginenv/search_path.go35
-rw-r--r--plugin/pluginenv/search_path_test.go62
-rw-r--r--plugin/plugintest/api.go396
-rw-r--r--plugin/plugintest/apioverride.go18
-rw-r--r--plugin/plugintest/hooks.go118
-rw-r--r--plugin/plugintest/key_value_store.go70
-rw-r--r--plugin/rpcplugin/api.go718
-rw-r--r--plugin/rpcplugin/api_test.go300
-rw-r--r--plugin/rpcplugin/hooks.go398
-rw-r--r--plugin/rpcplugin/hooks_test.go237
-rw-r--r--plugin/rpcplugin/http.go91
-rw-r--r--plugin/rpcplugin/http_test.go61
-rw-r--r--plugin/rpcplugin/ipc.go31
-rw-r--r--plugin/rpcplugin/ipc_test.go63
-rw-r--r--plugin/rpcplugin/main.go47
-rw-r--r--plugin/rpcplugin/main_test.go63
-rw-r--r--plugin/rpcplugin/muxer.go264
-rw-r--r--plugin/rpcplugin/muxer_test.go197
-rw-r--r--plugin/rpcplugin/process.go26
-rw-r--r--plugin/rpcplugin/process_test.go60
-rw-r--r--plugin/rpcplugin/process_unix.go48
-rw-r--r--plugin/rpcplugin/process_windows.go648
-rw-r--r--plugin/rpcplugin/rpcplugintest/rpcplugintest.go26
-rw-r--r--plugin/rpcplugin/rpcplugintest/supervisor.go312
-rw-r--r--plugin/rpcplugin/sandbox/main_test.go18
-rw-r--r--plugin/rpcplugin/sandbox/sandbox.go34
-rw-r--r--plugin/rpcplugin/sandbox/sandbox_linux.go488
-rw-r--r--plugin/rpcplugin/sandbox/sandbox_linux_test.go159
-rw-r--r--plugin/rpcplugin/sandbox/sandbox_other.go22
-rw-r--r--plugin/rpcplugin/sandbox/sandbox_test.go25
-rw-r--r--plugin/rpcplugin/sandbox/seccomp_linux.go178
-rw-r--r--plugin/rpcplugin/sandbox/seccomp_linux_amd64.go301
-rw-r--r--plugin/rpcplugin/sandbox/seccomp_linux_other.go10
-rw-r--r--plugin/rpcplugin/sandbox/seccomp_linux_test.go210
-rw-r--r--plugin/rpcplugin/sandbox/supervisor.go33
-rw-r--r--plugin/rpcplugin/sandbox/supervisor_test.go18
-rw-r--r--plugin/rpcplugin/supervisor.go176
-rw-r--r--plugin/rpcplugin/supervisor_test.go14
-rw-r--r--plugin/supervisor.go101
-rw-r--r--plugin/supervisor_test.go147
-rw-r--r--plugin/valid.go17
63 files changed, 5017 insertions, 6570 deletions
diff --git a/plugin/api.go b/plugin/api.go
index d62c2f069..76df9377a 100644
--- a/plugin/api.go
+++ b/plugin/api.go
@@ -4,13 +4,15 @@
package plugin
import (
+ "github.com/hashicorp/go-plugin"
"github.com/mattermost/mattermost-server/model"
)
// The API can be used to retrieve data or perform actions on behalf of the plugin. Most methods
// have direct counterparts in the REST API and very similar behavior.
//
-// Plugins can obtain access to the API by implementing the OnActivate hook.
+// Plugins obtain access to the API by embedding MattermostPlugin and accessing the API member
+// directly.
type API interface {
// LoadPluginConfiguration loads the plugin's configuration. dest should be a pointer to a
// struct that the configuration JSON can be unmarshalled to.
@@ -23,6 +25,12 @@ type API interface {
// UnregisterCommand unregisters a command previously registered via RegisterCommand.
UnregisterCommand(teamId, trigger string) error
+ // GetConfig fetches the currently persisted config
+ GetConfig() *model.Config
+
+ // SaveConfig sets the given config and persists the changes
+ SaveConfig(config *model.Config) *model.AppError
+
// CreateUser creates a user.
CreateUser(user *model.User) (*model.User, *model.AppError)
@@ -47,6 +55,9 @@ type API interface {
// DeleteTeam deletes a team.
DeleteTeam(teamId string) *model.AppError
+ // GetTeam gets all teams.
+ GetTeams() ([]*model.Team, *model.AppError)
+
// GetTeam gets a team.
GetTeam(teamId string) (*model.Team, *model.AppError)
@@ -56,12 +67,33 @@ type API interface {
// UpdateTeam updates a team.
UpdateTeam(team *model.Team) (*model.Team, *model.AppError)
+ // CreateTeamMember creates a team membership.
+ CreateTeamMember(teamId, userId string) (*model.TeamMember, *model.AppError)
+
+ // CreateTeamMember creates a team membership for all provided user ids.
+ CreateTeamMembers(teamId string, userIds []string, requestorId string) ([]*model.TeamMember, *model.AppError)
+
+ // DeleteTeamMember deletes a team membership.
+ DeleteTeamMember(teamId, userId, requestorId string) *model.AppError
+
+ // GetTeamMembers returns the memberships of a specific team.
+ GetTeamMembers(teamId string, offset, limit int) ([]*model.TeamMember, *model.AppError)
+
+ // GetTeamMember returns a specific membership.
+ GetTeamMember(teamId, userId string) (*model.TeamMember, *model.AppError)
+
+ // UpdateTeamMemberRoles updates the role for a team membership.
+ UpdateTeamMemberRoles(teamId, userId, newRoles string) (*model.TeamMember, *model.AppError)
+
// CreateChannel creates a channel.
CreateChannel(channel *model.Channel) (*model.Channel, *model.AppError)
// DeleteChannel deletes a channel.
DeleteChannel(channelId string) *model.AppError
+ // GetChannels gets a list of all channels.
+ GetPublicChannelsForTeam(teamId string, offset, limit int) (*model.ChannelList, *model.AppError)
+
// GetChannel gets a channel.
GetChannel(channelId string) (*model.Channel, *model.AppError)
@@ -95,6 +127,9 @@ type API interface {
// CreatePost creates a post.
CreatePost(post *model.Post) (*model.Post, *model.AppError)
+ // SendEphemeralPost creates an ephemeral post.
+ SendEphemeralPost(userId string, post *model.Post) *model.Post
+
// DeletePost deletes a post.
DeletePost(postId string) *model.AppError
@@ -104,17 +139,48 @@ type API interface {
// UpdatePost updates a post.
UpdatePost(post *model.Post) (*model.Post, *model.AppError)
- // KeyValueStore returns an object for accessing the persistent key value storage.
- KeyValueStore() KeyValueStore
-}
-
-type KeyValueStore interface {
// Set will store a key-value pair, unique per plugin.
- Set(key string, value []byte) *model.AppError
+ KVSet(key string, value []byte) *model.AppError
// Get will retrieve a value based on the key. Returns nil for non-existent keys.
- Get(key string) ([]byte, *model.AppError)
+ KVGet(key string) ([]byte, *model.AppError)
// Delete will remove a key-value pair. Returns nil for non-existent keys.
- Delete(key string) *model.AppError
+ KVDelete(key string) *model.AppError
+
+ // PublishWebSocketEvent sends an event to WebSocket connections.
+ // event is the type and will be prepended with "custom_<pluginid>_"
+ // payload is the data sent with the event. Interface values must be primitive Go types or mattermost-server/model types
+ // broadcast determines to which users to send the event
+ PublishWebSocketEvent(event string, payload map[string]interface{}, broadcast *model.WebsocketBroadcast)
+
+ // LogDebug writes a log message to the Mattermost server log file.
+ // Appropriate context such as the plugin name will already be added as fields so plugins
+ // do not need to add that info.
+ // keyValuePairs should be primitive go types or other values that can be encoded by encoding/gob
+ LogDebug(msg string, keyValuePairs ...interface{})
+
+ // LogInfo writes a log message to the Mattermost server log file.
+ // Appropriate context such as the plugin name will already be added as fields so plugins
+ // do not need to add that info.
+ // keyValuePairs should be primitive go types or other values that can be encoded by encoding/gob
+ LogInfo(msg string, keyValuePairs ...interface{})
+
+ // LogError writes a log message to the Mattermost server log file.
+ // Appropriate context such as the plugin name will already be added as fields so plugins
+ // do not need to add that info.
+ // keyValuePairs should be primitive go types or other values that can be encoded by encoding/gob
+ LogError(msg string, keyValuePairs ...interface{})
+
+ // LogWarn writes a log message to the Mattermost server log file.
+ // Appropriate context such as the plugin name will already be added as fields so plugins
+ // do not need to add that info.
+ // keyValuePairs should be primitive go types or other values that can be encoded by encoding/gob
+ LogWarn(msg string, keyValuePairs ...interface{})
+}
+
+var handshake = plugin.HandshakeConfig{
+ ProtocolVersion: 1,
+ MagicCookieKey: "MATTERMOST_PLUGIN",
+ MagicCookieValue: "Securely message teams, anywhere.",
}
diff --git a/plugin/client.go b/plugin/client.go
new file mode 100644
index 000000000..457a16cf4
--- /dev/null
+++ b/plugin/client.go
@@ -0,0 +1,63 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package plugin
+
+import (
+ "github.com/hashicorp/go-plugin"
+)
+
+// Starts the serving of a Mattermost plugin over net/rpc. gRPC is not yet supported.
+//
+// Call this when your plugin is ready to start.
+func ClientMain(pluginImplementation interface{}) {
+ if impl, ok := pluginImplementation.(interface {
+ SetAPI(api API)
+ SetSelfRef(ref interface{})
+ }); !ok {
+ panic("Plugin implementation given must embed plugin.MattermostPlugin")
+ } else {
+ impl.SetAPI(nil)
+ impl.SetSelfRef(pluginImplementation)
+ }
+
+ pluginMap := map[string]plugin.Plugin{
+ "hooks": &hooksPlugin{hooks: pluginImplementation},
+ }
+
+ plugin.Serve(&plugin.ServeConfig{
+ HandshakeConfig: handshake,
+ Plugins: pluginMap,
+ })
+}
+
+type MattermostPlugin struct {
+ // API exposes the plugin api, and becomes available just prior to the OnActive hook.
+ API API
+
+ selfRef interface{} // This is so we can unmarshal into our parent
+}
+
+// SetAPI persists the given API interface to the plugin. It is invoked just prior to the
+// OnActivate hook, exposing the API for use by the plugin.
+func (p *MattermostPlugin) SetAPI(api API) {
+ p.API = api
+}
+
+// SetSelfRef is called by ClientMain to maintain a pointer to the plugin interface originally
+// registered. This allows for the default implementation of OnConfigurationChange.
+func (p *MattermostPlugin) SetSelfRef(ref interface{}) {
+ p.selfRef = ref
+}
+
+// OnConfigurationChange provides a default implementation of this hook event that unmarshals the
+// plugin configuration directly onto the plugin struct.
+//
+// Feel free to implement your own version of OnConfigurationChange if you need more advanced
+// configuration handling.
+func (p *MattermostPlugin) OnConfigurationChange() error {
+ if p.selfRef != nil {
+ return p.API.LoadPluginConfiguration(p.selfRef)
+ }
+ return nil
+}
diff --git a/plugin/client_rpc.go b/plugin/client_rpc.go
new file mode 100644
index 000000000..ed76dc6e8
--- /dev/null
+++ b/plugin/client_rpc.go
@@ -0,0 +1,328 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+//go:generate go run interface_generator/main.go
+
+package plugin
+
+import (
+ "bytes"
+ "encoding/gob"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "net/rpc"
+ "os"
+ "reflect"
+
+ "github.com/hashicorp/go-plugin"
+ "github.com/mattermost/mattermost-server/mlog"
+ "github.com/mattermost/mattermost-server/model"
+)
+
+var hookNameToId map[string]int = make(map[string]int)
+
+type hooksRPCClient struct {
+ client *rpc.Client
+ log *mlog.Logger
+ muxBroker *plugin.MuxBroker
+ apiImpl API
+ implemented [TotalHooksId]bool
+}
+
+type hooksRPCServer struct {
+ impl interface{}
+ muxBroker *plugin.MuxBroker
+ apiRPCClient *apiRPCClient
+}
+
+// Implements hashicorp/go-plugin/plugin.Plugin interface to connect the hooks of a plugin
+type hooksPlugin struct {
+ hooks interface{}
+ apiImpl API
+ log *mlog.Logger
+}
+
+func (p *hooksPlugin) Server(b *plugin.MuxBroker) (interface{}, error) {
+ return &hooksRPCServer{impl: p.hooks, muxBroker: b}, nil
+}
+
+func (p *hooksPlugin) Client(b *plugin.MuxBroker, client *rpc.Client) (interface{}, error) {
+ return &hooksRPCClient{client: client, log: p.log, muxBroker: b, apiImpl: p.apiImpl}, nil
+}
+
+type apiRPCClient struct {
+ client *rpc.Client
+ log *mlog.Logger
+}
+
+type apiRPCServer struct {
+ impl API
+}
+
+// Registering some types used by MM for encoding/gob used by rpc
+func init() {
+ gob.Register([]*model.SlackAttachment{})
+ gob.Register([]interface{}{})
+ gob.Register(map[string]interface{}{})
+}
+
+// These enforce compile time checks to make sure types implement the interface
+// If you are getting an error here, you probably need to run `make pluginapi` to
+// autogenerate RPC glue code
+var _ plugin.Plugin = &hooksPlugin{}
+var _ Hooks = &hooksRPCClient{}
+
+//
+// Below are specal cases for hooks or APIs that can not be auto generated
+//
+
+func (g *hooksRPCClient) Implemented() (impl []string, err error) {
+ err = g.client.Call("Plugin.Implemented", struct{}{}, &impl)
+ for _, hookName := range impl {
+ if hookId, ok := hookNameToId[hookName]; ok {
+ g.implemented[hookId] = true
+ }
+ }
+ return
+}
+
+// Implemented replies with the names of the hooks that are implemented.
+func (s *hooksRPCServer) Implemented(args struct{}, reply *[]string) error {
+ ifaceType := reflect.TypeOf((*Hooks)(nil)).Elem()
+ implType := reflect.TypeOf(s.impl)
+ selfType := reflect.TypeOf(s)
+ var methods []string
+ for i := 0; i < ifaceType.NumMethod(); i++ {
+ method := ifaceType.Method(i)
+ if m, ok := implType.MethodByName(method.Name); !ok {
+ continue
+ } else if m.Type.NumIn() != method.Type.NumIn()+1 {
+ continue
+ } else if m.Type.NumOut() != method.Type.NumOut() {
+ continue
+ } else {
+ match := true
+ for j := 0; j < method.Type.NumIn(); j++ {
+ if m.Type.In(j+1) != method.Type.In(j) {
+ match = false
+ break
+ }
+ }
+ for j := 0; j < method.Type.NumOut(); j++ {
+ if m.Type.Out(j) != method.Type.Out(j) {
+ match = false
+ break
+ }
+ }
+ if !match {
+ continue
+ }
+ }
+ if _, ok := selfType.MethodByName(method.Name); !ok {
+ continue
+ }
+ methods = append(methods, method.Name)
+ }
+ *reply = methods
+ return nil
+}
+
+type Z_OnActivateArgs struct {
+ APIMuxId uint32
+}
+
+type Z_OnActivateReturns struct {
+ A error
+}
+
+func (g *hooksRPCClient) OnActivate() error {
+ muxId := g.muxBroker.NextId()
+ go g.muxBroker.AcceptAndServe(muxId, &apiRPCServer{
+ impl: g.apiImpl,
+ })
+
+ _args := &Z_OnActivateArgs{
+ APIMuxId: muxId,
+ }
+ _returns := &Z_OnActivateReturns{}
+
+ if err := g.client.Call("Plugin.OnActivate", _args, _returns); err != nil {
+ g.log.Error("RPC call to OnActivate plugin failed.", mlog.Err(err))
+ }
+ return _returns.A
+}
+
+func (s *hooksRPCServer) OnActivate(args *Z_OnActivateArgs, returns *Z_OnActivateReturns) error {
+ connection, err := s.muxBroker.Dial(args.APIMuxId)
+ if err != nil {
+ return err
+ }
+
+ s.apiRPCClient = &apiRPCClient{
+ client: rpc.NewClient(connection),
+ }
+
+ if mmplugin, ok := s.impl.(interface {
+ SetAPI(api API)
+ OnConfigurationChange() error
+ }); !ok {
+ } else {
+ mmplugin.SetAPI(s.apiRPCClient)
+ mmplugin.OnConfigurationChange()
+ }
+
+ // Capture output of standard logger because go-plugin
+ // redirects it.
+ log.SetOutput(os.Stderr)
+
+ if hook, ok := s.impl.(interface {
+ OnActivate() error
+ }); ok {
+ returns.A = hook.OnActivate()
+ }
+ return nil
+}
+
+type Z_LoadPluginConfigurationArgsArgs struct {
+}
+
+type Z_LoadPluginConfigurationArgsReturns struct {
+ A []byte
+}
+
+func (g *apiRPCClient) LoadPluginConfiguration(dest interface{}) error {
+ _args := &Z_LoadPluginConfigurationArgsArgs{}
+ _returns := &Z_LoadPluginConfigurationArgsReturns{}
+ if err := g.client.Call("Plugin.LoadPluginConfiguration", _args, _returns); err != nil {
+ g.log.Error("RPC call to LoadPluginConfiguration API failed.", mlog.Err(err))
+ }
+ return json.Unmarshal(_returns.A, dest)
+}
+
+func (s *apiRPCServer) LoadPluginConfiguration(args *Z_LoadPluginConfigurationArgsArgs, returns *Z_LoadPluginConfigurationArgsReturns) error {
+ var config interface{}
+ if hook, ok := s.impl.(interface {
+ LoadPluginConfiguration(dest interface{}) error
+ }); ok {
+ if err := hook.LoadPluginConfiguration(&config); err != nil {
+ return err
+ }
+ }
+ b, err := json.Marshal(config)
+ if err != nil {
+ return err
+ }
+ returns.A = b
+ return nil
+}
+
+func init() {
+ hookNameToId["ServeHTTP"] = ServeHTTPId
+}
+
+type Z_ServeHTTPArgs struct {
+ ResponseWriterStream uint32
+ Request *http.Request
+ Context *Context
+ RequestBodyStream uint32
+}
+
+func (g *hooksRPCClient) ServeHTTP(c *Context, w http.ResponseWriter, r *http.Request) {
+ if !g.implemented[ServeHTTPId] {
+ http.NotFound(w, r)
+ return
+ }
+
+ serveHTTPStreamId := g.muxBroker.NextId()
+ go func() {
+ connection, err := g.muxBroker.Accept(serveHTTPStreamId)
+ if err != nil {
+ g.log.Error("Plugin failed to ServeHTTP, muxBroker couldn't accept connection", mlog.Uint32("serve_http_stream_id", serveHTTPStreamId), mlog.Err(err))
+ http.Error(w, "500 internal server error", http.StatusInternalServerError)
+ return
+ }
+ defer connection.Close()
+
+ rpcServer := rpc.NewServer()
+ if err := rpcServer.RegisterName("Plugin", &httpResponseWriterRPCServer{w: w}); err != nil {
+ g.log.Error("Plugin failed to ServeHTTP, coulden't register RPC name", mlog.Err(err))
+ http.Error(w, "500 internal server error", http.StatusInternalServerError)
+ return
+ }
+ rpcServer.ServeConn(connection)
+ }()
+
+ requestBodyStreamId := uint32(0)
+ if r.Body != nil {
+ requestBodyStreamId = g.muxBroker.NextId()
+ go func() {
+ bodyConnection, err := g.muxBroker.Accept(requestBodyStreamId)
+ if err != nil {
+ g.log.Error("Plugin failed to ServeHTTP, muxBroker couldn't Accept request body connecion", mlog.Err(err))
+ http.Error(w, "500 internal server error", http.StatusInternalServerError)
+ return
+ }
+ defer bodyConnection.Close()
+ serveIOReader(r.Body, bodyConnection)
+ }()
+ }
+
+ forwardedRequest := &http.Request{
+ Method: r.Method,
+ URL: r.URL,
+ Proto: r.Proto,
+ ProtoMajor: r.ProtoMajor,
+ ProtoMinor: r.ProtoMinor,
+ Header: r.Header,
+ Host: r.Host,
+ RemoteAddr: r.RemoteAddr,
+ RequestURI: r.RequestURI,
+ }
+
+ if err := g.client.Call("Plugin.ServeHTTP", Z_ServeHTTPArgs{
+ Context: c,
+ ResponseWriterStream: serveHTTPStreamId,
+ Request: forwardedRequest,
+ RequestBodyStream: requestBodyStreamId,
+ }, nil); err != nil {
+ g.log.Error("Plugin failed to ServeHTTP, RPC call failed", mlog.Err(err))
+ http.Error(w, "500 internal server error", http.StatusInternalServerError)
+ }
+ return
+}
+
+func (s *hooksRPCServer) ServeHTTP(args *Z_ServeHTTPArgs, returns *struct{}) error {
+ connection, err := s.muxBroker.Dial(args.ResponseWriterStream)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote response writer stream, error: %v", err.Error())
+ return err
+ }
+ w := connectHTTPResponseWriter(connection)
+ defer w.Close()
+
+ r := args.Request
+ if args.RequestBodyStream != 0 {
+ connection, err := s.muxBroker.Dial(args.RequestBodyStream)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote request body stream, error: %v", err.Error())
+ return err
+ }
+ r.Body = connectIOReader(connection)
+ } else {
+ r.Body = ioutil.NopCloser(&bytes.Buffer{})
+ }
+ defer r.Body.Close()
+
+ if hook, ok := s.impl.(interface {
+ ServeHTTP(c *Context, w http.ResponseWriter, r *http.Request)
+ }); ok {
+ hook.ServeHTTP(args.Context, w, r)
+ } else {
+ http.NotFound(w, r)
+ }
+
+ return nil
+}
diff --git a/plugin/client_rpc_generated.go b/plugin/client_rpc_generated.go
new file mode 100644
index 000000000..ab884e647
--- /dev/null
+++ b/plugin/client_rpc_generated.go
@@ -0,0 +1,1829 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+// Code generated by "make pluginapi"
+// DO NOT EDIT
+
+package plugin
+
+import (
+ "fmt"
+
+ "github.com/mattermost/mattermost-server/mlog"
+ "github.com/mattermost/mattermost-server/model"
+)
+
+func init() {
+ hookNameToId["OnDeactivate"] = OnDeactivateId
+}
+
+type Z_OnDeactivateArgs struct {
+}
+
+type Z_OnDeactivateReturns struct {
+ A error
+}
+
+func (g *hooksRPCClient) OnDeactivate() error {
+ _args := &Z_OnDeactivateArgs{}
+ _returns := &Z_OnDeactivateReturns{}
+ if g.implemented[OnDeactivateId] {
+ if err := g.client.Call("Plugin.OnDeactivate", _args, _returns); err != nil {
+ g.log.Error("RPC call OnDeactivate to plugin failed.", mlog.Err(err))
+ }
+ }
+ return _returns.A
+}
+
+func (s *hooksRPCServer) OnDeactivate(args *Z_OnDeactivateArgs, returns *Z_OnDeactivateReturns) error {
+ if hook, ok := s.impl.(interface {
+ OnDeactivate() error
+ }); ok {
+ returns.A = hook.OnDeactivate()
+ } else {
+ return fmt.Errorf("Hook OnDeactivate called but not implemented.")
+ }
+ return nil
+}
+
+func init() {
+ hookNameToId["OnConfigurationChange"] = OnConfigurationChangeId
+}
+
+type Z_OnConfigurationChangeArgs struct {
+}
+
+type Z_OnConfigurationChangeReturns struct {
+ A error
+}
+
+func (g *hooksRPCClient) OnConfigurationChange() error {
+ _args := &Z_OnConfigurationChangeArgs{}
+ _returns := &Z_OnConfigurationChangeReturns{}
+ if g.implemented[OnConfigurationChangeId] {
+ if err := g.client.Call("Plugin.OnConfigurationChange", _args, _returns); err != nil {
+ g.log.Error("RPC call OnConfigurationChange to plugin failed.", mlog.Err(err))
+ }
+ }
+ return _returns.A
+}
+
+func (s *hooksRPCServer) OnConfigurationChange(args *Z_OnConfigurationChangeArgs, returns *Z_OnConfigurationChangeReturns) error {
+ if hook, ok := s.impl.(interface {
+ OnConfigurationChange() error
+ }); ok {
+ returns.A = hook.OnConfigurationChange()
+ } else {
+ return fmt.Errorf("Hook OnConfigurationChange called but not implemented.")
+ }
+ return nil
+}
+
+func init() {
+ hookNameToId["ExecuteCommand"] = ExecuteCommandId
+}
+
+type Z_ExecuteCommandArgs struct {
+ A *Context
+ B *model.CommandArgs
+}
+
+type Z_ExecuteCommandReturns struct {
+ A *model.CommandResponse
+ B *model.AppError
+}
+
+func (g *hooksRPCClient) ExecuteCommand(c *Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
+ _args := &Z_ExecuteCommandArgs{c, args}
+ _returns := &Z_ExecuteCommandReturns{}
+ if g.implemented[ExecuteCommandId] {
+ if err := g.client.Call("Plugin.ExecuteCommand", _args, _returns); err != nil {
+ g.log.Error("RPC call ExecuteCommand to plugin failed.", mlog.Err(err))
+ }
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *hooksRPCServer) ExecuteCommand(args *Z_ExecuteCommandArgs, returns *Z_ExecuteCommandReturns) error {
+ if hook, ok := s.impl.(interface {
+ ExecuteCommand(c *Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.ExecuteCommand(args.A, args.B)
+ } else {
+ return fmt.Errorf("Hook ExecuteCommand called but not implemented.")
+ }
+ return nil
+}
+
+func init() {
+ hookNameToId["MessageWillBePosted"] = MessageWillBePostedId
+}
+
+type Z_MessageWillBePostedArgs struct {
+ A *Context
+ B *model.Post
+}
+
+type Z_MessageWillBePostedReturns struct {
+ A *model.Post
+ B string
+}
+
+func (g *hooksRPCClient) MessageWillBePosted(c *Context, post *model.Post) (*model.Post, string) {
+ _args := &Z_MessageWillBePostedArgs{c, post}
+ _returns := &Z_MessageWillBePostedReturns{}
+ if g.implemented[MessageWillBePostedId] {
+ if err := g.client.Call("Plugin.MessageWillBePosted", _args, _returns); err != nil {
+ g.log.Error("RPC call MessageWillBePosted to plugin failed.", mlog.Err(err))
+ }
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *hooksRPCServer) MessageWillBePosted(args *Z_MessageWillBePostedArgs, returns *Z_MessageWillBePostedReturns) error {
+ if hook, ok := s.impl.(interface {
+ MessageWillBePosted(c *Context, post *model.Post) (*model.Post, string)
+ }); ok {
+ returns.A, returns.B = hook.MessageWillBePosted(args.A, args.B)
+ } else {
+ return fmt.Errorf("Hook MessageWillBePosted called but not implemented.")
+ }
+ return nil
+}
+
+func init() {
+ hookNameToId["MessageWillBeUpdated"] = MessageWillBeUpdatedId
+}
+
+type Z_MessageWillBeUpdatedArgs struct {
+ A *Context
+ B *model.Post
+ C *model.Post
+}
+
+type Z_MessageWillBeUpdatedReturns struct {
+ A *model.Post
+ B string
+}
+
+func (g *hooksRPCClient) MessageWillBeUpdated(c *Context, newPost, oldPost *model.Post) (*model.Post, string) {
+ _args := &Z_MessageWillBeUpdatedArgs{c, newPost, oldPost}
+ _returns := &Z_MessageWillBeUpdatedReturns{}
+ if g.implemented[MessageWillBeUpdatedId] {
+ if err := g.client.Call("Plugin.MessageWillBeUpdated", _args, _returns); err != nil {
+ g.log.Error("RPC call MessageWillBeUpdated to plugin failed.", mlog.Err(err))
+ }
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *hooksRPCServer) MessageWillBeUpdated(args *Z_MessageWillBeUpdatedArgs, returns *Z_MessageWillBeUpdatedReturns) error {
+ if hook, ok := s.impl.(interface {
+ MessageWillBeUpdated(c *Context, newPost, oldPost *model.Post) (*model.Post, string)
+ }); ok {
+ returns.A, returns.B = hook.MessageWillBeUpdated(args.A, args.B, args.C)
+ } else {
+ return fmt.Errorf("Hook MessageWillBeUpdated called but not implemented.")
+ }
+ return nil
+}
+
+func init() {
+ hookNameToId["MessageHasBeenPosted"] = MessageHasBeenPostedId
+}
+
+type Z_MessageHasBeenPostedArgs struct {
+ A *Context
+ B *model.Post
+}
+
+type Z_MessageHasBeenPostedReturns struct {
+}
+
+func (g *hooksRPCClient) MessageHasBeenPosted(c *Context, post *model.Post) {
+ _args := &Z_MessageHasBeenPostedArgs{c, post}
+ _returns := &Z_MessageHasBeenPostedReturns{}
+ if g.implemented[MessageHasBeenPostedId] {
+ if err := g.client.Call("Plugin.MessageHasBeenPosted", _args, _returns); err != nil {
+ g.log.Error("RPC call MessageHasBeenPosted to plugin failed.", mlog.Err(err))
+ }
+ }
+ return
+}
+
+func (s *hooksRPCServer) MessageHasBeenPosted(args *Z_MessageHasBeenPostedArgs, returns *Z_MessageHasBeenPostedReturns) error {
+ if hook, ok := s.impl.(interface {
+ MessageHasBeenPosted(c *Context, post *model.Post)
+ }); ok {
+ hook.MessageHasBeenPosted(args.A, args.B)
+ } else {
+ return fmt.Errorf("Hook MessageHasBeenPosted called but not implemented.")
+ }
+ return nil
+}
+
+func init() {
+ hookNameToId["MessageHasBeenUpdated"] = MessageHasBeenUpdatedId
+}
+
+type Z_MessageHasBeenUpdatedArgs struct {
+ A *Context
+ B *model.Post
+ C *model.Post
+}
+
+type Z_MessageHasBeenUpdatedReturns struct {
+}
+
+func (g *hooksRPCClient) MessageHasBeenUpdated(c *Context, newPost, oldPost *model.Post) {
+ _args := &Z_MessageHasBeenUpdatedArgs{c, newPost, oldPost}
+ _returns := &Z_MessageHasBeenUpdatedReturns{}
+ if g.implemented[MessageHasBeenUpdatedId] {
+ if err := g.client.Call("Plugin.MessageHasBeenUpdated", _args, _returns); err != nil {
+ g.log.Error("RPC call MessageHasBeenUpdated to plugin failed.", mlog.Err(err))
+ }
+ }
+ return
+}
+
+func (s *hooksRPCServer) MessageHasBeenUpdated(args *Z_MessageHasBeenUpdatedArgs, returns *Z_MessageHasBeenUpdatedReturns) error {
+ if hook, ok := s.impl.(interface {
+ MessageHasBeenUpdated(c *Context, newPost, oldPost *model.Post)
+ }); ok {
+ hook.MessageHasBeenUpdated(args.A, args.B, args.C)
+ } else {
+ return fmt.Errorf("Hook MessageHasBeenUpdated called but not implemented.")
+ }
+ return nil
+}
+
+func init() {
+ hookNameToId["ChannelHasBeenCreated"] = ChannelHasBeenCreatedId
+}
+
+type Z_ChannelHasBeenCreatedArgs struct {
+ A *Context
+ B *model.Channel
+}
+
+type Z_ChannelHasBeenCreatedReturns struct {
+}
+
+func (g *hooksRPCClient) ChannelHasBeenCreated(c *Context, channel *model.Channel) {
+ _args := &Z_ChannelHasBeenCreatedArgs{c, channel}
+ _returns := &Z_ChannelHasBeenCreatedReturns{}
+ if g.implemented[ChannelHasBeenCreatedId] {
+ if err := g.client.Call("Plugin.ChannelHasBeenCreated", _args, _returns); err != nil {
+ g.log.Error("RPC call ChannelHasBeenCreated to plugin failed.", mlog.Err(err))
+ }
+ }
+ return
+}
+
+func (s *hooksRPCServer) ChannelHasBeenCreated(args *Z_ChannelHasBeenCreatedArgs, returns *Z_ChannelHasBeenCreatedReturns) error {
+ if hook, ok := s.impl.(interface {
+ ChannelHasBeenCreated(c *Context, channel *model.Channel)
+ }); ok {
+ hook.ChannelHasBeenCreated(args.A, args.B)
+ } else {
+ return fmt.Errorf("Hook ChannelHasBeenCreated called but not implemented.")
+ }
+ return nil
+}
+
+func init() {
+ hookNameToId["UserHasJoinedChannel"] = UserHasJoinedChannelId
+}
+
+type Z_UserHasJoinedChannelArgs struct {
+ A *Context
+ B *model.ChannelMember
+ C *model.User
+}
+
+type Z_UserHasJoinedChannelReturns struct {
+}
+
+func (g *hooksRPCClient) UserHasJoinedChannel(c *Context, channelMember *model.ChannelMember, actor *model.User) {
+ _args := &Z_UserHasJoinedChannelArgs{c, channelMember, actor}
+ _returns := &Z_UserHasJoinedChannelReturns{}
+ if g.implemented[UserHasJoinedChannelId] {
+ if err := g.client.Call("Plugin.UserHasJoinedChannel", _args, _returns); err != nil {
+ g.log.Error("RPC call UserHasJoinedChannel to plugin failed.", mlog.Err(err))
+ }
+ }
+ return
+}
+
+func (s *hooksRPCServer) UserHasJoinedChannel(args *Z_UserHasJoinedChannelArgs, returns *Z_UserHasJoinedChannelReturns) error {
+ if hook, ok := s.impl.(interface {
+ UserHasJoinedChannel(c *Context, channelMember *model.ChannelMember, actor *model.User)
+ }); ok {
+ hook.UserHasJoinedChannel(args.A, args.B, args.C)
+ } else {
+ return fmt.Errorf("Hook UserHasJoinedChannel called but not implemented.")
+ }
+ return nil
+}
+
+func init() {
+ hookNameToId["UserHasLeftChannel"] = UserHasLeftChannelId
+}
+
+type Z_UserHasLeftChannelArgs struct {
+ A *Context
+ B *model.ChannelMember
+ C *model.User
+}
+
+type Z_UserHasLeftChannelReturns struct {
+}
+
+func (g *hooksRPCClient) UserHasLeftChannel(c *Context, channelMember *model.ChannelMember, actor *model.User) {
+ _args := &Z_UserHasLeftChannelArgs{c, channelMember, actor}
+ _returns := &Z_UserHasLeftChannelReturns{}
+ if g.implemented[UserHasLeftChannelId] {
+ if err := g.client.Call("Plugin.UserHasLeftChannel", _args, _returns); err != nil {
+ g.log.Error("RPC call UserHasLeftChannel to plugin failed.", mlog.Err(err))
+ }
+ }
+ return
+}
+
+func (s *hooksRPCServer) UserHasLeftChannel(args *Z_UserHasLeftChannelArgs, returns *Z_UserHasLeftChannelReturns) error {
+ if hook, ok := s.impl.(interface {
+ UserHasLeftChannel(c *Context, channelMember *model.ChannelMember, actor *model.User)
+ }); ok {
+ hook.UserHasLeftChannel(args.A, args.B, args.C)
+ } else {
+ return fmt.Errorf("Hook UserHasLeftChannel called but not implemented.")
+ }
+ return nil
+}
+
+func init() {
+ hookNameToId["UserHasJoinedTeam"] = UserHasJoinedTeamId
+}
+
+type Z_UserHasJoinedTeamArgs struct {
+ A *Context
+ B *model.TeamMember
+ C *model.User
+}
+
+type Z_UserHasJoinedTeamReturns struct {
+}
+
+func (g *hooksRPCClient) UserHasJoinedTeam(c *Context, teamMember *model.TeamMember, actor *model.User) {
+ _args := &Z_UserHasJoinedTeamArgs{c, teamMember, actor}
+ _returns := &Z_UserHasJoinedTeamReturns{}
+ if g.implemented[UserHasJoinedTeamId] {
+ if err := g.client.Call("Plugin.UserHasJoinedTeam", _args, _returns); err != nil {
+ g.log.Error("RPC call UserHasJoinedTeam to plugin failed.", mlog.Err(err))
+ }
+ }
+ return
+}
+
+func (s *hooksRPCServer) UserHasJoinedTeam(args *Z_UserHasJoinedTeamArgs, returns *Z_UserHasJoinedTeamReturns) error {
+ if hook, ok := s.impl.(interface {
+ UserHasJoinedTeam(c *Context, teamMember *model.TeamMember, actor *model.User)
+ }); ok {
+ hook.UserHasJoinedTeam(args.A, args.B, args.C)
+ } else {
+ return fmt.Errorf("Hook UserHasJoinedTeam called but not implemented.")
+ }
+ return nil
+}
+
+func init() {
+ hookNameToId["UserHasLeftTeam"] = UserHasLeftTeamId
+}
+
+type Z_UserHasLeftTeamArgs struct {
+ A *Context
+ B *model.TeamMember
+ C *model.User
+}
+
+type Z_UserHasLeftTeamReturns struct {
+}
+
+func (g *hooksRPCClient) UserHasLeftTeam(c *Context, teamMember *model.TeamMember, actor *model.User) {
+ _args := &Z_UserHasLeftTeamArgs{c, teamMember, actor}
+ _returns := &Z_UserHasLeftTeamReturns{}
+ if g.implemented[UserHasLeftTeamId] {
+ if err := g.client.Call("Plugin.UserHasLeftTeam", _args, _returns); err != nil {
+ g.log.Error("RPC call UserHasLeftTeam to plugin failed.", mlog.Err(err))
+ }
+ }
+ return
+}
+
+func (s *hooksRPCServer) UserHasLeftTeam(args *Z_UserHasLeftTeamArgs, returns *Z_UserHasLeftTeamReturns) error {
+ if hook, ok := s.impl.(interface {
+ UserHasLeftTeam(c *Context, teamMember *model.TeamMember, actor *model.User)
+ }); ok {
+ hook.UserHasLeftTeam(args.A, args.B, args.C)
+ } else {
+ return fmt.Errorf("Hook UserHasLeftTeam called but not implemented.")
+ }
+ return nil
+}
+
+type Z_RegisterCommandArgs struct {
+ A *model.Command
+}
+
+type Z_RegisterCommandReturns struct {
+ A error
+}
+
+func (g *apiRPCClient) RegisterCommand(command *model.Command) error {
+ _args := &Z_RegisterCommandArgs{command}
+ _returns := &Z_RegisterCommandReturns{}
+ if err := g.client.Call("Plugin.RegisterCommand", _args, _returns); err != nil {
+ g.log.Error("RPC call to RegisterCommand API failed.", mlog.Err(err))
+ }
+ return _returns.A
+}
+
+func (s *apiRPCServer) RegisterCommand(args *Z_RegisterCommandArgs, returns *Z_RegisterCommandReturns) error {
+ if hook, ok := s.impl.(interface {
+ RegisterCommand(command *model.Command) error
+ }); ok {
+ returns.A = hook.RegisterCommand(args.A)
+ } else {
+ return fmt.Errorf("API RegisterCommand called but not implemented.")
+ }
+ return nil
+}
+
+type Z_UnregisterCommandArgs struct {
+ A string
+ B string
+}
+
+type Z_UnregisterCommandReturns struct {
+ A error
+}
+
+func (g *apiRPCClient) UnregisterCommand(teamId, trigger string) error {
+ _args := &Z_UnregisterCommandArgs{teamId, trigger}
+ _returns := &Z_UnregisterCommandReturns{}
+ if err := g.client.Call("Plugin.UnregisterCommand", _args, _returns); err != nil {
+ g.log.Error("RPC call to UnregisterCommand API failed.", mlog.Err(err))
+ }
+ return _returns.A
+}
+
+func (s *apiRPCServer) UnregisterCommand(args *Z_UnregisterCommandArgs, returns *Z_UnregisterCommandReturns) error {
+ if hook, ok := s.impl.(interface {
+ UnregisterCommand(teamId, trigger string) error
+ }); ok {
+ returns.A = hook.UnregisterCommand(args.A, args.B)
+ } else {
+ return fmt.Errorf("API UnregisterCommand called but not implemented.")
+ }
+ return nil
+}
+
+type Z_GetConfigArgs struct {
+}
+
+type Z_GetConfigReturns struct {
+ A *model.Config
+}
+
+func (g *apiRPCClient) GetConfig() *model.Config {
+ _args := &Z_GetConfigArgs{}
+ _returns := &Z_GetConfigReturns{}
+ if err := g.client.Call("Plugin.GetConfig", _args, _returns); err != nil {
+ g.log.Error("RPC call to GetConfig API failed.", mlog.Err(err))
+ }
+ return _returns.A
+}
+
+func (s *apiRPCServer) GetConfig(args *Z_GetConfigArgs, returns *Z_GetConfigReturns) error {
+ if hook, ok := s.impl.(interface {
+ GetConfig() *model.Config
+ }); ok {
+ returns.A = hook.GetConfig()
+ } else {
+ return fmt.Errorf("API GetConfig called but not implemented.")
+ }
+ return nil
+}
+
+type Z_SaveConfigArgs struct {
+ A *model.Config
+}
+
+type Z_SaveConfigReturns struct {
+ A *model.AppError
+}
+
+func (g *apiRPCClient) SaveConfig(config *model.Config) *model.AppError {
+ _args := &Z_SaveConfigArgs{config}
+ _returns := &Z_SaveConfigReturns{}
+ if err := g.client.Call("Plugin.SaveConfig", _args, _returns); err != nil {
+ g.log.Error("RPC call to SaveConfig API failed.", mlog.Err(err))
+ }
+ return _returns.A
+}
+
+func (s *apiRPCServer) SaveConfig(args *Z_SaveConfigArgs, returns *Z_SaveConfigReturns) error {
+ if hook, ok := s.impl.(interface {
+ SaveConfig(config *model.Config) *model.AppError
+ }); ok {
+ returns.A = hook.SaveConfig(args.A)
+ } else {
+ return fmt.Errorf("API SaveConfig called but not implemented.")
+ }
+ return nil
+}
+
+type Z_CreateUserArgs struct {
+ A *model.User
+}
+
+type Z_CreateUserReturns struct {
+ A *model.User
+ B *model.AppError
+}
+
+func (g *apiRPCClient) CreateUser(user *model.User) (*model.User, *model.AppError) {
+ _args := &Z_CreateUserArgs{user}
+ _returns := &Z_CreateUserReturns{}
+ if err := g.client.Call("Plugin.CreateUser", _args, _returns); err != nil {
+ g.log.Error("RPC call to CreateUser API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) CreateUser(args *Z_CreateUserArgs, returns *Z_CreateUserReturns) error {
+ if hook, ok := s.impl.(interface {
+ CreateUser(user *model.User) (*model.User, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.CreateUser(args.A)
+ } else {
+ return fmt.Errorf("API CreateUser called but not implemented.")
+ }
+ return nil
+}
+
+type Z_DeleteUserArgs struct {
+ A string
+}
+
+type Z_DeleteUserReturns struct {
+ A *model.AppError
+}
+
+func (g *apiRPCClient) DeleteUser(userId string) *model.AppError {
+ _args := &Z_DeleteUserArgs{userId}
+ _returns := &Z_DeleteUserReturns{}
+ if err := g.client.Call("Plugin.DeleteUser", _args, _returns); err != nil {
+ g.log.Error("RPC call to DeleteUser API failed.", mlog.Err(err))
+ }
+ return _returns.A
+}
+
+func (s *apiRPCServer) DeleteUser(args *Z_DeleteUserArgs, returns *Z_DeleteUserReturns) error {
+ if hook, ok := s.impl.(interface {
+ DeleteUser(userId string) *model.AppError
+ }); ok {
+ returns.A = hook.DeleteUser(args.A)
+ } else {
+ return fmt.Errorf("API DeleteUser called but not implemented.")
+ }
+ return nil
+}
+
+type Z_GetUserArgs struct {
+ A string
+}
+
+type Z_GetUserReturns struct {
+ A *model.User
+ B *model.AppError
+}
+
+func (g *apiRPCClient) GetUser(userId string) (*model.User, *model.AppError) {
+ _args := &Z_GetUserArgs{userId}
+ _returns := &Z_GetUserReturns{}
+ if err := g.client.Call("Plugin.GetUser", _args, _returns); err != nil {
+ g.log.Error("RPC call to GetUser API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) GetUser(args *Z_GetUserArgs, returns *Z_GetUserReturns) error {
+ if hook, ok := s.impl.(interface {
+ GetUser(userId string) (*model.User, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.GetUser(args.A)
+ } else {
+ return fmt.Errorf("API GetUser called but not implemented.")
+ }
+ return nil
+}
+
+type Z_GetUserByEmailArgs struct {
+ A string
+}
+
+type Z_GetUserByEmailReturns struct {
+ A *model.User
+ B *model.AppError
+}
+
+func (g *apiRPCClient) GetUserByEmail(email string) (*model.User, *model.AppError) {
+ _args := &Z_GetUserByEmailArgs{email}
+ _returns := &Z_GetUserByEmailReturns{}
+ if err := g.client.Call("Plugin.GetUserByEmail", _args, _returns); err != nil {
+ g.log.Error("RPC call to GetUserByEmail API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) GetUserByEmail(args *Z_GetUserByEmailArgs, returns *Z_GetUserByEmailReturns) error {
+ if hook, ok := s.impl.(interface {
+ GetUserByEmail(email string) (*model.User, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.GetUserByEmail(args.A)
+ } else {
+ return fmt.Errorf("API GetUserByEmail called but not implemented.")
+ }
+ return nil
+}
+
+type Z_GetUserByUsernameArgs struct {
+ A string
+}
+
+type Z_GetUserByUsernameReturns struct {
+ A *model.User
+ B *model.AppError
+}
+
+func (g *apiRPCClient) GetUserByUsername(name string) (*model.User, *model.AppError) {
+ _args := &Z_GetUserByUsernameArgs{name}
+ _returns := &Z_GetUserByUsernameReturns{}
+ if err := g.client.Call("Plugin.GetUserByUsername", _args, _returns); err != nil {
+ g.log.Error("RPC call to GetUserByUsername API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) GetUserByUsername(args *Z_GetUserByUsernameArgs, returns *Z_GetUserByUsernameReturns) error {
+ if hook, ok := s.impl.(interface {
+ GetUserByUsername(name string) (*model.User, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.GetUserByUsername(args.A)
+ } else {
+ return fmt.Errorf("API GetUserByUsername called but not implemented.")
+ }
+ return nil
+}
+
+type Z_UpdateUserArgs struct {
+ A *model.User
+}
+
+type Z_UpdateUserReturns struct {
+ A *model.User
+ B *model.AppError
+}
+
+func (g *apiRPCClient) UpdateUser(user *model.User) (*model.User, *model.AppError) {
+ _args := &Z_UpdateUserArgs{user}
+ _returns := &Z_UpdateUserReturns{}
+ if err := g.client.Call("Plugin.UpdateUser", _args, _returns); err != nil {
+ g.log.Error("RPC call to UpdateUser API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) UpdateUser(args *Z_UpdateUserArgs, returns *Z_UpdateUserReturns) error {
+ if hook, ok := s.impl.(interface {
+ UpdateUser(user *model.User) (*model.User, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.UpdateUser(args.A)
+ } else {
+ return fmt.Errorf("API UpdateUser called but not implemented.")
+ }
+ return nil
+}
+
+type Z_CreateTeamArgs struct {
+ A *model.Team
+}
+
+type Z_CreateTeamReturns struct {
+ A *model.Team
+ B *model.AppError
+}
+
+func (g *apiRPCClient) CreateTeam(team *model.Team) (*model.Team, *model.AppError) {
+ _args := &Z_CreateTeamArgs{team}
+ _returns := &Z_CreateTeamReturns{}
+ if err := g.client.Call("Plugin.CreateTeam", _args, _returns); err != nil {
+ g.log.Error("RPC call to CreateTeam API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) CreateTeam(args *Z_CreateTeamArgs, returns *Z_CreateTeamReturns) error {
+ if hook, ok := s.impl.(interface {
+ CreateTeam(team *model.Team) (*model.Team, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.CreateTeam(args.A)
+ } else {
+ return fmt.Errorf("API CreateTeam called but not implemented.")
+ }
+ return nil
+}
+
+type Z_DeleteTeamArgs struct {
+ A string
+}
+
+type Z_DeleteTeamReturns struct {
+ A *model.AppError
+}
+
+func (g *apiRPCClient) DeleteTeam(teamId string) *model.AppError {
+ _args := &Z_DeleteTeamArgs{teamId}
+ _returns := &Z_DeleteTeamReturns{}
+ if err := g.client.Call("Plugin.DeleteTeam", _args, _returns); err != nil {
+ g.log.Error("RPC call to DeleteTeam API failed.", mlog.Err(err))
+ }
+ return _returns.A
+}
+
+func (s *apiRPCServer) DeleteTeam(args *Z_DeleteTeamArgs, returns *Z_DeleteTeamReturns) error {
+ if hook, ok := s.impl.(interface {
+ DeleteTeam(teamId string) *model.AppError
+ }); ok {
+ returns.A = hook.DeleteTeam(args.A)
+ } else {
+ return fmt.Errorf("API DeleteTeam called but not implemented.")
+ }
+ return nil
+}
+
+type Z_GetTeamsArgs struct {
+}
+
+type Z_GetTeamsReturns struct {
+ A []*model.Team
+ B *model.AppError
+}
+
+func (g *apiRPCClient) GetTeams() ([]*model.Team, *model.AppError) {
+ _args := &Z_GetTeamsArgs{}
+ _returns := &Z_GetTeamsReturns{}
+ if err := g.client.Call("Plugin.GetTeams", _args, _returns); err != nil {
+ g.log.Error("RPC call to GetTeams API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) GetTeams(args *Z_GetTeamsArgs, returns *Z_GetTeamsReturns) error {
+ if hook, ok := s.impl.(interface {
+ GetTeams() ([]*model.Team, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.GetTeams()
+ } else {
+ return fmt.Errorf("API GetTeams called but not implemented.")
+ }
+ return nil
+}
+
+type Z_GetTeamArgs struct {
+ A string
+}
+
+type Z_GetTeamReturns struct {
+ A *model.Team
+ B *model.AppError
+}
+
+func (g *apiRPCClient) GetTeam(teamId string) (*model.Team, *model.AppError) {
+ _args := &Z_GetTeamArgs{teamId}
+ _returns := &Z_GetTeamReturns{}
+ if err := g.client.Call("Plugin.GetTeam", _args, _returns); err != nil {
+ g.log.Error("RPC call to GetTeam API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) GetTeam(args *Z_GetTeamArgs, returns *Z_GetTeamReturns) error {
+ if hook, ok := s.impl.(interface {
+ GetTeam(teamId string) (*model.Team, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.GetTeam(args.A)
+ } else {
+ return fmt.Errorf("API GetTeam called but not implemented.")
+ }
+ return nil
+}
+
+type Z_GetTeamByNameArgs struct {
+ A string
+}
+
+type Z_GetTeamByNameReturns struct {
+ A *model.Team
+ B *model.AppError
+}
+
+func (g *apiRPCClient) GetTeamByName(name string) (*model.Team, *model.AppError) {
+ _args := &Z_GetTeamByNameArgs{name}
+ _returns := &Z_GetTeamByNameReturns{}
+ if err := g.client.Call("Plugin.GetTeamByName", _args, _returns); err != nil {
+ g.log.Error("RPC call to GetTeamByName API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) GetTeamByName(args *Z_GetTeamByNameArgs, returns *Z_GetTeamByNameReturns) error {
+ if hook, ok := s.impl.(interface {
+ GetTeamByName(name string) (*model.Team, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.GetTeamByName(args.A)
+ } else {
+ return fmt.Errorf("API GetTeamByName called but not implemented.")
+ }
+ return nil
+}
+
+type Z_UpdateTeamArgs struct {
+ A *model.Team
+}
+
+type Z_UpdateTeamReturns struct {
+ A *model.Team
+ B *model.AppError
+}
+
+func (g *apiRPCClient) UpdateTeam(team *model.Team) (*model.Team, *model.AppError) {
+ _args := &Z_UpdateTeamArgs{team}
+ _returns := &Z_UpdateTeamReturns{}
+ if err := g.client.Call("Plugin.UpdateTeam", _args, _returns); err != nil {
+ g.log.Error("RPC call to UpdateTeam API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) UpdateTeam(args *Z_UpdateTeamArgs, returns *Z_UpdateTeamReturns) error {
+ if hook, ok := s.impl.(interface {
+ UpdateTeam(team *model.Team) (*model.Team, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.UpdateTeam(args.A)
+ } else {
+ return fmt.Errorf("API UpdateTeam called but not implemented.")
+ }
+ return nil
+}
+
+type Z_CreateTeamMemberArgs struct {
+ A string
+ B string
+}
+
+type Z_CreateTeamMemberReturns struct {
+ A *model.TeamMember
+ B *model.AppError
+}
+
+func (g *apiRPCClient) CreateTeamMember(teamId, userId string) (*model.TeamMember, *model.AppError) {
+ _args := &Z_CreateTeamMemberArgs{teamId, userId}
+ _returns := &Z_CreateTeamMemberReturns{}
+ if err := g.client.Call("Plugin.CreateTeamMember", _args, _returns); err != nil {
+ g.log.Error("RPC call to CreateTeamMember API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) CreateTeamMember(args *Z_CreateTeamMemberArgs, returns *Z_CreateTeamMemberReturns) error {
+ if hook, ok := s.impl.(interface {
+ CreateTeamMember(teamId, userId string) (*model.TeamMember, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.CreateTeamMember(args.A, args.B)
+ } else {
+ return fmt.Errorf("API CreateTeamMember called but not implemented.")
+ }
+ return nil
+}
+
+type Z_CreateTeamMembersArgs struct {
+ A string
+ B []string
+ C string
+}
+
+type Z_CreateTeamMembersReturns struct {
+ A []*model.TeamMember
+ B *model.AppError
+}
+
+func (g *apiRPCClient) CreateTeamMembers(teamId string, userIds []string, requestorId string) ([]*model.TeamMember, *model.AppError) {
+ _args := &Z_CreateTeamMembersArgs{teamId, userIds, requestorId}
+ _returns := &Z_CreateTeamMembersReturns{}
+ if err := g.client.Call("Plugin.CreateTeamMembers", _args, _returns); err != nil {
+ g.log.Error("RPC call to CreateTeamMembers API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) CreateTeamMembers(args *Z_CreateTeamMembersArgs, returns *Z_CreateTeamMembersReturns) error {
+ if hook, ok := s.impl.(interface {
+ CreateTeamMembers(teamId string, userIds []string, requestorId string) ([]*model.TeamMember, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.CreateTeamMembers(args.A, args.B, args.C)
+ } else {
+ return fmt.Errorf("API CreateTeamMembers called but not implemented.")
+ }
+ return nil
+}
+
+type Z_DeleteTeamMemberArgs struct {
+ A string
+ B string
+ C string
+}
+
+type Z_DeleteTeamMemberReturns struct {
+ A *model.AppError
+}
+
+func (g *apiRPCClient) DeleteTeamMember(teamId, userId, requestorId string) *model.AppError {
+ _args := &Z_DeleteTeamMemberArgs{teamId, userId, requestorId}
+ _returns := &Z_DeleteTeamMemberReturns{}
+ if err := g.client.Call("Plugin.DeleteTeamMember", _args, _returns); err != nil {
+ g.log.Error("RPC call to DeleteTeamMember API failed.", mlog.Err(err))
+ }
+ return _returns.A
+}
+
+func (s *apiRPCServer) DeleteTeamMember(args *Z_DeleteTeamMemberArgs, returns *Z_DeleteTeamMemberReturns) error {
+ if hook, ok := s.impl.(interface {
+ DeleteTeamMember(teamId, userId, requestorId string) *model.AppError
+ }); ok {
+ returns.A = hook.DeleteTeamMember(args.A, args.B, args.C)
+ } else {
+ return fmt.Errorf("API DeleteTeamMember called but not implemented.")
+ }
+ return nil
+}
+
+type Z_GetTeamMembersArgs struct {
+ A string
+ B int
+ C int
+}
+
+type Z_GetTeamMembersReturns struct {
+ A []*model.TeamMember
+ B *model.AppError
+}
+
+func (g *apiRPCClient) GetTeamMembers(teamId string, offset, limit int) ([]*model.TeamMember, *model.AppError) {
+ _args := &Z_GetTeamMembersArgs{teamId, offset, limit}
+ _returns := &Z_GetTeamMembersReturns{}
+ if err := g.client.Call("Plugin.GetTeamMembers", _args, _returns); err != nil {
+ g.log.Error("RPC call to GetTeamMembers API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) GetTeamMembers(args *Z_GetTeamMembersArgs, returns *Z_GetTeamMembersReturns) error {
+ if hook, ok := s.impl.(interface {
+ GetTeamMembers(teamId string, offset, limit int) ([]*model.TeamMember, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.GetTeamMembers(args.A, args.B, args.C)
+ } else {
+ return fmt.Errorf("API GetTeamMembers called but not implemented.")
+ }
+ return nil
+}
+
+type Z_GetTeamMemberArgs struct {
+ A string
+ B string
+}
+
+type Z_GetTeamMemberReturns struct {
+ A *model.TeamMember
+ B *model.AppError
+}
+
+func (g *apiRPCClient) GetTeamMember(teamId, userId string) (*model.TeamMember, *model.AppError) {
+ _args := &Z_GetTeamMemberArgs{teamId, userId}
+ _returns := &Z_GetTeamMemberReturns{}
+ if err := g.client.Call("Plugin.GetTeamMember", _args, _returns); err != nil {
+ g.log.Error("RPC call to GetTeamMember API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) GetTeamMember(args *Z_GetTeamMemberArgs, returns *Z_GetTeamMemberReturns) error {
+ if hook, ok := s.impl.(interface {
+ GetTeamMember(teamId, userId string) (*model.TeamMember, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.GetTeamMember(args.A, args.B)
+ } else {
+ return fmt.Errorf("API GetTeamMember called but not implemented.")
+ }
+ return nil
+}
+
+type Z_UpdateTeamMemberRolesArgs struct {
+ A string
+ B string
+ C string
+}
+
+type Z_UpdateTeamMemberRolesReturns struct {
+ A *model.TeamMember
+ B *model.AppError
+}
+
+func (g *apiRPCClient) UpdateTeamMemberRoles(teamId, userId, newRoles string) (*model.TeamMember, *model.AppError) {
+ _args := &Z_UpdateTeamMemberRolesArgs{teamId, userId, newRoles}
+ _returns := &Z_UpdateTeamMemberRolesReturns{}
+ if err := g.client.Call("Plugin.UpdateTeamMemberRoles", _args, _returns); err != nil {
+ g.log.Error("RPC call to UpdateTeamMemberRoles API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) UpdateTeamMemberRoles(args *Z_UpdateTeamMemberRolesArgs, returns *Z_UpdateTeamMemberRolesReturns) error {
+ if hook, ok := s.impl.(interface {
+ UpdateTeamMemberRoles(teamId, userId, newRoles string) (*model.TeamMember, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.UpdateTeamMemberRoles(args.A, args.B, args.C)
+ } else {
+ return fmt.Errorf("API UpdateTeamMemberRoles called but not implemented.")
+ }
+ return nil
+}
+
+type Z_CreateChannelArgs struct {
+ A *model.Channel
+}
+
+type Z_CreateChannelReturns struct {
+ A *model.Channel
+ B *model.AppError
+}
+
+func (g *apiRPCClient) CreateChannel(channel *model.Channel) (*model.Channel, *model.AppError) {
+ _args := &Z_CreateChannelArgs{channel}
+ _returns := &Z_CreateChannelReturns{}
+ if err := g.client.Call("Plugin.CreateChannel", _args, _returns); err != nil {
+ g.log.Error("RPC call to CreateChannel API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) CreateChannel(args *Z_CreateChannelArgs, returns *Z_CreateChannelReturns) error {
+ if hook, ok := s.impl.(interface {
+ CreateChannel(channel *model.Channel) (*model.Channel, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.CreateChannel(args.A)
+ } else {
+ return fmt.Errorf("API CreateChannel called but not implemented.")
+ }
+ return nil
+}
+
+type Z_DeleteChannelArgs struct {
+ A string
+}
+
+type Z_DeleteChannelReturns struct {
+ A *model.AppError
+}
+
+func (g *apiRPCClient) DeleteChannel(channelId string) *model.AppError {
+ _args := &Z_DeleteChannelArgs{channelId}
+ _returns := &Z_DeleteChannelReturns{}
+ if err := g.client.Call("Plugin.DeleteChannel", _args, _returns); err != nil {
+ g.log.Error("RPC call to DeleteChannel API failed.", mlog.Err(err))
+ }
+ return _returns.A
+}
+
+func (s *apiRPCServer) DeleteChannel(args *Z_DeleteChannelArgs, returns *Z_DeleteChannelReturns) error {
+ if hook, ok := s.impl.(interface {
+ DeleteChannel(channelId string) *model.AppError
+ }); ok {
+ returns.A = hook.DeleteChannel(args.A)
+ } else {
+ return fmt.Errorf("API DeleteChannel called but not implemented.")
+ }
+ return nil
+}
+
+type Z_GetPublicChannelsForTeamArgs struct {
+ A string
+ B int
+ C int
+}
+
+type Z_GetPublicChannelsForTeamReturns struct {
+ A *model.ChannelList
+ B *model.AppError
+}
+
+func (g *apiRPCClient) GetPublicChannelsForTeam(teamId string, offset, limit int) (*model.ChannelList, *model.AppError) {
+ _args := &Z_GetPublicChannelsForTeamArgs{teamId, offset, limit}
+ _returns := &Z_GetPublicChannelsForTeamReturns{}
+ if err := g.client.Call("Plugin.GetPublicChannelsForTeam", _args, _returns); err != nil {
+ g.log.Error("RPC call to GetPublicChannelsForTeam API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) GetPublicChannelsForTeam(args *Z_GetPublicChannelsForTeamArgs, returns *Z_GetPublicChannelsForTeamReturns) error {
+ if hook, ok := s.impl.(interface {
+ GetPublicChannelsForTeam(teamId string, offset, limit int) (*model.ChannelList, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.GetPublicChannelsForTeam(args.A, args.B, args.C)
+ } else {
+ return fmt.Errorf("API GetPublicChannelsForTeam called but not implemented.")
+ }
+ return nil
+}
+
+type Z_GetChannelArgs struct {
+ A string
+}
+
+type Z_GetChannelReturns struct {
+ A *model.Channel
+ B *model.AppError
+}
+
+func (g *apiRPCClient) GetChannel(channelId string) (*model.Channel, *model.AppError) {
+ _args := &Z_GetChannelArgs{channelId}
+ _returns := &Z_GetChannelReturns{}
+ if err := g.client.Call("Plugin.GetChannel", _args, _returns); err != nil {
+ g.log.Error("RPC call to GetChannel API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) GetChannel(args *Z_GetChannelArgs, returns *Z_GetChannelReturns) error {
+ if hook, ok := s.impl.(interface {
+ GetChannel(channelId string) (*model.Channel, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.GetChannel(args.A)
+ } else {
+ return fmt.Errorf("API GetChannel called but not implemented.")
+ }
+ return nil
+}
+
+type Z_GetChannelByNameArgs struct {
+ A string
+ B string
+}
+
+type Z_GetChannelByNameReturns struct {
+ A *model.Channel
+ B *model.AppError
+}
+
+func (g *apiRPCClient) GetChannelByName(name, teamId string) (*model.Channel, *model.AppError) {
+ _args := &Z_GetChannelByNameArgs{name, teamId}
+ _returns := &Z_GetChannelByNameReturns{}
+ if err := g.client.Call("Plugin.GetChannelByName", _args, _returns); err != nil {
+ g.log.Error("RPC call to GetChannelByName API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) GetChannelByName(args *Z_GetChannelByNameArgs, returns *Z_GetChannelByNameReturns) error {
+ if hook, ok := s.impl.(interface {
+ GetChannelByName(name, teamId string) (*model.Channel, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.GetChannelByName(args.A, args.B)
+ } else {
+ return fmt.Errorf("API GetChannelByName called but not implemented.")
+ }
+ return nil
+}
+
+type Z_GetDirectChannelArgs struct {
+ A string
+ B string
+}
+
+type Z_GetDirectChannelReturns struct {
+ A *model.Channel
+ B *model.AppError
+}
+
+func (g *apiRPCClient) GetDirectChannel(userId1, userId2 string) (*model.Channel, *model.AppError) {
+ _args := &Z_GetDirectChannelArgs{userId1, userId2}
+ _returns := &Z_GetDirectChannelReturns{}
+ if err := g.client.Call("Plugin.GetDirectChannel", _args, _returns); err != nil {
+ g.log.Error("RPC call to GetDirectChannel API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) GetDirectChannel(args *Z_GetDirectChannelArgs, returns *Z_GetDirectChannelReturns) error {
+ if hook, ok := s.impl.(interface {
+ GetDirectChannel(userId1, userId2 string) (*model.Channel, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.GetDirectChannel(args.A, args.B)
+ } else {
+ return fmt.Errorf("API GetDirectChannel called but not implemented.")
+ }
+ return nil
+}
+
+type Z_GetGroupChannelArgs struct {
+ A []string
+}
+
+type Z_GetGroupChannelReturns struct {
+ A *model.Channel
+ B *model.AppError
+}
+
+func (g *apiRPCClient) GetGroupChannel(userIds []string) (*model.Channel, *model.AppError) {
+ _args := &Z_GetGroupChannelArgs{userIds}
+ _returns := &Z_GetGroupChannelReturns{}
+ if err := g.client.Call("Plugin.GetGroupChannel", _args, _returns); err != nil {
+ g.log.Error("RPC call to GetGroupChannel API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) GetGroupChannel(args *Z_GetGroupChannelArgs, returns *Z_GetGroupChannelReturns) error {
+ if hook, ok := s.impl.(interface {
+ GetGroupChannel(userIds []string) (*model.Channel, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.GetGroupChannel(args.A)
+ } else {
+ return fmt.Errorf("API GetGroupChannel called but not implemented.")
+ }
+ return nil
+}
+
+type Z_UpdateChannelArgs struct {
+ A *model.Channel
+}
+
+type Z_UpdateChannelReturns struct {
+ A *model.Channel
+ B *model.AppError
+}
+
+func (g *apiRPCClient) UpdateChannel(channel *model.Channel) (*model.Channel, *model.AppError) {
+ _args := &Z_UpdateChannelArgs{channel}
+ _returns := &Z_UpdateChannelReturns{}
+ if err := g.client.Call("Plugin.UpdateChannel", _args, _returns); err != nil {
+ g.log.Error("RPC call to UpdateChannel API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) UpdateChannel(args *Z_UpdateChannelArgs, returns *Z_UpdateChannelReturns) error {
+ if hook, ok := s.impl.(interface {
+ UpdateChannel(channel *model.Channel) (*model.Channel, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.UpdateChannel(args.A)
+ } else {
+ return fmt.Errorf("API UpdateChannel called but not implemented.")
+ }
+ return nil
+}
+
+type Z_AddChannelMemberArgs struct {
+ A string
+ B string
+}
+
+type Z_AddChannelMemberReturns struct {
+ A *model.ChannelMember
+ B *model.AppError
+}
+
+func (g *apiRPCClient) AddChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError) {
+ _args := &Z_AddChannelMemberArgs{channelId, userId}
+ _returns := &Z_AddChannelMemberReturns{}
+ if err := g.client.Call("Plugin.AddChannelMember", _args, _returns); err != nil {
+ g.log.Error("RPC call to AddChannelMember API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) AddChannelMember(args *Z_AddChannelMemberArgs, returns *Z_AddChannelMemberReturns) error {
+ if hook, ok := s.impl.(interface {
+ AddChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.AddChannelMember(args.A, args.B)
+ } else {
+ return fmt.Errorf("API AddChannelMember called but not implemented.")
+ }
+ return nil
+}
+
+type Z_GetChannelMemberArgs struct {
+ A string
+ B string
+}
+
+type Z_GetChannelMemberReturns struct {
+ A *model.ChannelMember
+ B *model.AppError
+}
+
+func (g *apiRPCClient) GetChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError) {
+ _args := &Z_GetChannelMemberArgs{channelId, userId}
+ _returns := &Z_GetChannelMemberReturns{}
+ if err := g.client.Call("Plugin.GetChannelMember", _args, _returns); err != nil {
+ g.log.Error("RPC call to GetChannelMember API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) GetChannelMember(args *Z_GetChannelMemberArgs, returns *Z_GetChannelMemberReturns) error {
+ if hook, ok := s.impl.(interface {
+ GetChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.GetChannelMember(args.A, args.B)
+ } else {
+ return fmt.Errorf("API GetChannelMember called but not implemented.")
+ }
+ return nil
+}
+
+type Z_UpdateChannelMemberRolesArgs struct {
+ A string
+ B string
+ C string
+}
+
+type Z_UpdateChannelMemberRolesReturns struct {
+ A *model.ChannelMember
+ B *model.AppError
+}
+
+func (g *apiRPCClient) UpdateChannelMemberRoles(channelId, userId, newRoles string) (*model.ChannelMember, *model.AppError) {
+ _args := &Z_UpdateChannelMemberRolesArgs{channelId, userId, newRoles}
+ _returns := &Z_UpdateChannelMemberRolesReturns{}
+ if err := g.client.Call("Plugin.UpdateChannelMemberRoles", _args, _returns); err != nil {
+ g.log.Error("RPC call to UpdateChannelMemberRoles API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) UpdateChannelMemberRoles(args *Z_UpdateChannelMemberRolesArgs, returns *Z_UpdateChannelMemberRolesReturns) error {
+ if hook, ok := s.impl.(interface {
+ UpdateChannelMemberRoles(channelId, userId, newRoles string) (*model.ChannelMember, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.UpdateChannelMemberRoles(args.A, args.B, args.C)
+ } else {
+ return fmt.Errorf("API UpdateChannelMemberRoles called but not implemented.")
+ }
+ return nil
+}
+
+type Z_UpdateChannelMemberNotificationsArgs struct {
+ A string
+ B string
+ C map[string]string
+}
+
+type Z_UpdateChannelMemberNotificationsReturns struct {
+ A *model.ChannelMember
+ B *model.AppError
+}
+
+func (g *apiRPCClient) UpdateChannelMemberNotifications(channelId, userId string, notifications map[string]string) (*model.ChannelMember, *model.AppError) {
+ _args := &Z_UpdateChannelMemberNotificationsArgs{channelId, userId, notifications}
+ _returns := &Z_UpdateChannelMemberNotificationsReturns{}
+ if err := g.client.Call("Plugin.UpdateChannelMemberNotifications", _args, _returns); err != nil {
+ g.log.Error("RPC call to UpdateChannelMemberNotifications API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) UpdateChannelMemberNotifications(args *Z_UpdateChannelMemberNotificationsArgs, returns *Z_UpdateChannelMemberNotificationsReturns) error {
+ if hook, ok := s.impl.(interface {
+ UpdateChannelMemberNotifications(channelId, userId string, notifications map[string]string) (*model.ChannelMember, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.UpdateChannelMemberNotifications(args.A, args.B, args.C)
+ } else {
+ return fmt.Errorf("API UpdateChannelMemberNotifications called but not implemented.")
+ }
+ return nil
+}
+
+type Z_DeleteChannelMemberArgs struct {
+ A string
+ B string
+}
+
+type Z_DeleteChannelMemberReturns struct {
+ A *model.AppError
+}
+
+func (g *apiRPCClient) DeleteChannelMember(channelId, userId string) *model.AppError {
+ _args := &Z_DeleteChannelMemberArgs{channelId, userId}
+ _returns := &Z_DeleteChannelMemberReturns{}
+ if err := g.client.Call("Plugin.DeleteChannelMember", _args, _returns); err != nil {
+ g.log.Error("RPC call to DeleteChannelMember API failed.", mlog.Err(err))
+ }
+ return _returns.A
+}
+
+func (s *apiRPCServer) DeleteChannelMember(args *Z_DeleteChannelMemberArgs, returns *Z_DeleteChannelMemberReturns) error {
+ if hook, ok := s.impl.(interface {
+ DeleteChannelMember(channelId, userId string) *model.AppError
+ }); ok {
+ returns.A = hook.DeleteChannelMember(args.A, args.B)
+ } else {
+ return fmt.Errorf("API DeleteChannelMember called but not implemented.")
+ }
+ return nil
+}
+
+type Z_CreatePostArgs struct {
+ A *model.Post
+}
+
+type Z_CreatePostReturns struct {
+ A *model.Post
+ B *model.AppError
+}
+
+func (g *apiRPCClient) CreatePost(post *model.Post) (*model.Post, *model.AppError) {
+ _args := &Z_CreatePostArgs{post}
+ _returns := &Z_CreatePostReturns{}
+ if err := g.client.Call("Plugin.CreatePost", _args, _returns); err != nil {
+ g.log.Error("RPC call to CreatePost API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) CreatePost(args *Z_CreatePostArgs, returns *Z_CreatePostReturns) error {
+ if hook, ok := s.impl.(interface {
+ CreatePost(post *model.Post) (*model.Post, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.CreatePost(args.A)
+ } else {
+ return fmt.Errorf("API CreatePost called but not implemented.")
+ }
+ return nil
+}
+
+type Z_SendEphemeralPostArgs struct {
+ A string
+ B *model.Post
+}
+
+type Z_SendEphemeralPostReturns struct {
+ A *model.Post
+}
+
+func (g *apiRPCClient) SendEphemeralPost(userId string, post *model.Post) *model.Post {
+ _args := &Z_SendEphemeralPostArgs{userId, post}
+ _returns := &Z_SendEphemeralPostReturns{}
+ if err := g.client.Call("Plugin.SendEphemeralPost", _args, _returns); err != nil {
+ g.log.Error("RPC call to SendEphemeralPost API failed.", mlog.Err(err))
+ }
+ return _returns.A
+}
+
+func (s *apiRPCServer) SendEphemeralPost(args *Z_SendEphemeralPostArgs, returns *Z_SendEphemeralPostReturns) error {
+ if hook, ok := s.impl.(interface {
+ SendEphemeralPost(userId string, post *model.Post) *model.Post
+ }); ok {
+ returns.A = hook.SendEphemeralPost(args.A, args.B)
+ } else {
+ return fmt.Errorf("API SendEphemeralPost called but not implemented.")
+ }
+ return nil
+}
+
+type Z_DeletePostArgs struct {
+ A string
+}
+
+type Z_DeletePostReturns struct {
+ A *model.AppError
+}
+
+func (g *apiRPCClient) DeletePost(postId string) *model.AppError {
+ _args := &Z_DeletePostArgs{postId}
+ _returns := &Z_DeletePostReturns{}
+ if err := g.client.Call("Plugin.DeletePost", _args, _returns); err != nil {
+ g.log.Error("RPC call to DeletePost API failed.", mlog.Err(err))
+ }
+ return _returns.A
+}
+
+func (s *apiRPCServer) DeletePost(args *Z_DeletePostArgs, returns *Z_DeletePostReturns) error {
+ if hook, ok := s.impl.(interface {
+ DeletePost(postId string) *model.AppError
+ }); ok {
+ returns.A = hook.DeletePost(args.A)
+ } else {
+ return fmt.Errorf("API DeletePost called but not implemented.")
+ }
+ return nil
+}
+
+type Z_GetPostArgs struct {
+ A string
+}
+
+type Z_GetPostReturns struct {
+ A *model.Post
+ B *model.AppError
+}
+
+func (g *apiRPCClient) GetPost(postId string) (*model.Post, *model.AppError) {
+ _args := &Z_GetPostArgs{postId}
+ _returns := &Z_GetPostReturns{}
+ if err := g.client.Call("Plugin.GetPost", _args, _returns); err != nil {
+ g.log.Error("RPC call to GetPost API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) GetPost(args *Z_GetPostArgs, returns *Z_GetPostReturns) error {
+ if hook, ok := s.impl.(interface {
+ GetPost(postId string) (*model.Post, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.GetPost(args.A)
+ } else {
+ return fmt.Errorf("API GetPost called but not implemented.")
+ }
+ return nil
+}
+
+type Z_UpdatePostArgs struct {
+ A *model.Post
+}
+
+type Z_UpdatePostReturns struct {
+ A *model.Post
+ B *model.AppError
+}
+
+func (g *apiRPCClient) UpdatePost(post *model.Post) (*model.Post, *model.AppError) {
+ _args := &Z_UpdatePostArgs{post}
+ _returns := &Z_UpdatePostReturns{}
+ if err := g.client.Call("Plugin.UpdatePost", _args, _returns); err != nil {
+ g.log.Error("RPC call to UpdatePost API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) UpdatePost(args *Z_UpdatePostArgs, returns *Z_UpdatePostReturns) error {
+ if hook, ok := s.impl.(interface {
+ UpdatePost(post *model.Post) (*model.Post, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.UpdatePost(args.A)
+ } else {
+ return fmt.Errorf("API UpdatePost called but not implemented.")
+ }
+ return nil
+}
+
+type Z_KVSetArgs struct {
+ A string
+ B []byte
+}
+
+type Z_KVSetReturns struct {
+ A *model.AppError
+}
+
+func (g *apiRPCClient) KVSet(key string, value []byte) *model.AppError {
+ _args := &Z_KVSetArgs{key, value}
+ _returns := &Z_KVSetReturns{}
+ if err := g.client.Call("Plugin.KVSet", _args, _returns); err != nil {
+ g.log.Error("RPC call to KVSet API failed.", mlog.Err(err))
+ }
+ return _returns.A
+}
+
+func (s *apiRPCServer) KVSet(args *Z_KVSetArgs, returns *Z_KVSetReturns) error {
+ if hook, ok := s.impl.(interface {
+ KVSet(key string, value []byte) *model.AppError
+ }); ok {
+ returns.A = hook.KVSet(args.A, args.B)
+ } else {
+ return fmt.Errorf("API KVSet called but not implemented.")
+ }
+ return nil
+}
+
+type Z_KVGetArgs struct {
+ A string
+}
+
+type Z_KVGetReturns struct {
+ A []byte
+ B *model.AppError
+}
+
+func (g *apiRPCClient) KVGet(key string) ([]byte, *model.AppError) {
+ _args := &Z_KVGetArgs{key}
+ _returns := &Z_KVGetReturns{}
+ if err := g.client.Call("Plugin.KVGet", _args, _returns); err != nil {
+ g.log.Error("RPC call to KVGet API failed.", mlog.Err(err))
+ }
+ return _returns.A, _returns.B
+}
+
+func (s *apiRPCServer) KVGet(args *Z_KVGetArgs, returns *Z_KVGetReturns) error {
+ if hook, ok := s.impl.(interface {
+ KVGet(key string) ([]byte, *model.AppError)
+ }); ok {
+ returns.A, returns.B = hook.KVGet(args.A)
+ } else {
+ return fmt.Errorf("API KVGet called but not implemented.")
+ }
+ return nil
+}
+
+type Z_KVDeleteArgs struct {
+ A string
+}
+
+type Z_KVDeleteReturns struct {
+ A *model.AppError
+}
+
+func (g *apiRPCClient) KVDelete(key string) *model.AppError {
+ _args := &Z_KVDeleteArgs{key}
+ _returns := &Z_KVDeleteReturns{}
+ if err := g.client.Call("Plugin.KVDelete", _args, _returns); err != nil {
+ g.log.Error("RPC call to KVDelete API failed.", mlog.Err(err))
+ }
+ return _returns.A
+}
+
+func (s *apiRPCServer) KVDelete(args *Z_KVDeleteArgs, returns *Z_KVDeleteReturns) error {
+ if hook, ok := s.impl.(interface {
+ KVDelete(key string) *model.AppError
+ }); ok {
+ returns.A = hook.KVDelete(args.A)
+ } else {
+ return fmt.Errorf("API KVDelete called but not implemented.")
+ }
+ return nil
+}
+
+type Z_PublishWebSocketEventArgs struct {
+ A string
+ B map[string]interface{}
+ C *model.WebsocketBroadcast
+}
+
+type Z_PublishWebSocketEventReturns struct {
+}
+
+func (g *apiRPCClient) PublishWebSocketEvent(event string, payload map[string]interface{}, broadcast *model.WebsocketBroadcast) {
+ _args := &Z_PublishWebSocketEventArgs{event, payload, broadcast}
+ _returns := &Z_PublishWebSocketEventReturns{}
+ if err := g.client.Call("Plugin.PublishWebSocketEvent", _args, _returns); err != nil {
+ g.log.Error("RPC call to PublishWebSocketEvent API failed.", mlog.Err(err))
+ }
+ return
+}
+
+func (s *apiRPCServer) PublishWebSocketEvent(args *Z_PublishWebSocketEventArgs, returns *Z_PublishWebSocketEventReturns) error {
+ if hook, ok := s.impl.(interface {
+ PublishWebSocketEvent(event string, payload map[string]interface{}, broadcast *model.WebsocketBroadcast)
+ }); ok {
+ hook.PublishWebSocketEvent(args.A, args.B, args.C)
+ } else {
+ return fmt.Errorf("API PublishWebSocketEvent called but not implemented.")
+ }
+ return nil
+}
+
+type Z_LogDebugArgs struct {
+ A string
+ B []interface{}
+}
+
+type Z_LogDebugReturns struct {
+}
+
+func (g *apiRPCClient) LogDebug(msg string, keyValuePairs ...interface{}) {
+ _args := &Z_LogDebugArgs{msg, keyValuePairs}
+ _returns := &Z_LogDebugReturns{}
+ if err := g.client.Call("Plugin.LogDebug", _args, _returns); err != nil {
+ g.log.Error("RPC call to LogDebug API failed.", mlog.Err(err))
+ }
+ return
+}
+
+func (s *apiRPCServer) LogDebug(args *Z_LogDebugArgs, returns *Z_LogDebugReturns) error {
+ if hook, ok := s.impl.(interface {
+ LogDebug(msg string, keyValuePairs ...interface{})
+ }); ok {
+ hook.LogDebug(args.A, args.B...)
+ } else {
+ return fmt.Errorf("API LogDebug called but not implemented.")
+ }
+ return nil
+}
+
+type Z_LogInfoArgs struct {
+ A string
+ B []interface{}
+}
+
+type Z_LogInfoReturns struct {
+}
+
+func (g *apiRPCClient) LogInfo(msg string, keyValuePairs ...interface{}) {
+ _args := &Z_LogInfoArgs{msg, keyValuePairs}
+ _returns := &Z_LogInfoReturns{}
+ if err := g.client.Call("Plugin.LogInfo", _args, _returns); err != nil {
+ g.log.Error("RPC call to LogInfo API failed.", mlog.Err(err))
+ }
+ return
+}
+
+func (s *apiRPCServer) LogInfo(args *Z_LogInfoArgs, returns *Z_LogInfoReturns) error {
+ if hook, ok := s.impl.(interface {
+ LogInfo(msg string, keyValuePairs ...interface{})
+ }); ok {
+ hook.LogInfo(args.A, args.B...)
+ } else {
+ return fmt.Errorf("API LogInfo called but not implemented.")
+ }
+ return nil
+}
+
+type Z_LogErrorArgs struct {
+ A string
+ B []interface{}
+}
+
+type Z_LogErrorReturns struct {
+}
+
+func (g *apiRPCClient) LogError(msg string, keyValuePairs ...interface{}) {
+ _args := &Z_LogErrorArgs{msg, keyValuePairs}
+ _returns := &Z_LogErrorReturns{}
+ if err := g.client.Call("Plugin.LogError", _args, _returns); err != nil {
+ g.log.Error("RPC call to LogError API failed.", mlog.Err(err))
+ }
+ return
+}
+
+func (s *apiRPCServer) LogError(args *Z_LogErrorArgs, returns *Z_LogErrorReturns) error {
+ if hook, ok := s.impl.(interface {
+ LogError(msg string, keyValuePairs ...interface{})
+ }); ok {
+ hook.LogError(args.A, args.B...)
+ } else {
+ return fmt.Errorf("API LogError called but not implemented.")
+ }
+ return nil
+}
+
+type Z_LogWarnArgs struct {
+ A string
+ B []interface{}
+}
+
+type Z_LogWarnReturns struct {
+}
+
+func (g *apiRPCClient) LogWarn(msg string, keyValuePairs ...interface{}) {
+ _args := &Z_LogWarnArgs{msg, keyValuePairs}
+ _returns := &Z_LogWarnReturns{}
+ if err := g.client.Call("Plugin.LogWarn", _args, _returns); err != nil {
+ g.log.Error("RPC call to LogWarn API failed.", mlog.Err(err))
+ }
+ return
+}
+
+func (s *apiRPCServer) LogWarn(args *Z_LogWarnArgs, returns *Z_LogWarnReturns) error {
+ if hook, ok := s.impl.(interface {
+ LogWarn(msg string, keyValuePairs ...interface{})
+ }); ok {
+ hook.LogWarn(args.A, args.B...)
+ } else {
+ return fmt.Errorf("API LogWarn called but not implemented.")
+ }
+ return nil
+}
diff --git a/plugin/context.go b/plugin/context.go
new file mode 100644
index 000000000..60d01bbe4
--- /dev/null
+++ b/plugin/context.go
@@ -0,0 +1,10 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package plugin
+
+// Context passes through metadata about the request or hook event.
+//
+// It is currently a placeholder while the implementation details are sorted out.
+type Context struct {
+}
diff --git a/plugin/doc.go b/plugin/doc.go
new file mode 100644
index 000000000..b6806365b
--- /dev/null
+++ b/plugin/doc.go
@@ -0,0 +1,9 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+// The plugin package is used by Mattermost server plugins written in go. It also enables the
+// Mattermost server to manage and interact with the running plugin environment.
+//
+// Note that this package exports a large number of types prefixed with Z_. These are public only
+// to allow their use with Hashicorp's go-plugin (and net/rpc). Do not use these directly.
+package plugin
diff --git a/plugin/environment.go b/plugin/environment.go
new file mode 100644
index 000000000..450078893
--- /dev/null
+++ b/plugin/environment.go
@@ -0,0 +1,271 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package plugin
+
+import (
+ "fmt"
+ "io/ioutil"
+ "path/filepath"
+ "sync"
+
+ "github.com/mattermost/mattermost-server/mlog"
+ "github.com/mattermost/mattermost-server/model"
+ "github.com/pkg/errors"
+)
+
+type apiImplCreatorFunc func(*model.Manifest) API
+type supervisorCreatorFunc func(*model.BundleInfo, *mlog.Logger, API) (*supervisor, error)
+
+// multiPluginHookRunnerFunc is a callback function to invoke as part of RunMultiPluginHook.
+//
+// Return false to stop the hook from iterating to subsequent plugins.
+type multiPluginHookRunnerFunc func(hooks Hooks) bool
+
+type activePlugin struct {
+ BundleInfo *model.BundleInfo
+ State int
+
+ supervisor *supervisor
+}
+
+// Environment represents the execution environment of active plugins.
+//
+// It is meant for use by the Mattermost server to manipulate, interact with and report on the set
+// of active plugins.
+type Environment struct {
+ activePlugins map[string]activePlugin
+ mutex sync.RWMutex
+ logger *mlog.Logger
+ newAPIImpl apiImplCreatorFunc
+ pluginDir string
+ webappPluginDir string
+}
+
+func NewEnvironment(newAPIImpl apiImplCreatorFunc, pluginDir string, webappPluginDir string, logger *mlog.Logger) (*Environment, error) {
+ return &Environment{
+ activePlugins: make(map[string]activePlugin),
+ logger: logger,
+ newAPIImpl: newAPIImpl,
+ pluginDir: pluginDir,
+ webappPluginDir: webappPluginDir,
+ }, nil
+}
+
+// Performs a full scan of the given path.
+//
+// This function will return info for all subdirectories that appear to be plugins (i.e. all
+// subdirectories containing plugin manifest files, regardless of whether they could actually be
+// parsed).
+//
+// Plugins are found non-recursively and paths beginning with a dot are always ignored.
+func scanSearchPath(path string) ([]*model.BundleInfo, error) {
+ files, err := ioutil.ReadDir(path)
+ if err != nil {
+ return nil, err
+ }
+ var ret []*model.BundleInfo
+ for _, file := range files {
+ if !file.IsDir() || file.Name()[0] == '.' {
+ continue
+ }
+ if info := model.BundleInfoForPath(filepath.Join(path, file.Name())); info.ManifestPath != "" {
+ ret = append(ret, info)
+ }
+ }
+ return ret, nil
+}
+
+// Returns a list of all plugins within the environment.
+func (env *Environment) Available() ([]*model.BundleInfo, error) {
+ return scanSearchPath(env.pluginDir)
+}
+
+// Returns a list of all currently active plugins within the environment.
+func (env *Environment) Active() []*model.BundleInfo {
+ env.mutex.RLock()
+ defer env.mutex.RUnlock()
+
+ activePlugins := []*model.BundleInfo{}
+ for _, p := range env.activePlugins {
+ activePlugins = append(activePlugins, p.BundleInfo)
+ }
+
+ return activePlugins
+}
+
+// IsActive returns true if the plugin with the given id is active.
+func (env *Environment) IsActive(id string) bool {
+ _, ok := env.activePlugins[id]
+ return ok
+}
+
+// Statuses returns a list of plugin statuses representing the state of every plugin
+func (env *Environment) Statuses() (model.PluginStatuses, error) {
+ env.mutex.RLock()
+ defer env.mutex.RUnlock()
+
+ plugins, err := env.Available()
+ if err != nil {
+ return nil, errors.Wrap(err, "unable to get plugin statuses")
+ }
+
+ pluginStatuses := make(model.PluginStatuses, 0, len(plugins))
+ for _, plugin := range plugins {
+ // For now we don't handle bad manifests, we should
+ if plugin.Manifest == nil {
+ continue
+ }
+
+ pluginState := model.PluginStateNotRunning
+ if plugin, ok := env.activePlugins[plugin.Manifest.Id]; ok {
+ pluginState = plugin.State
+ }
+
+ status := &model.PluginStatus{
+ PluginId: plugin.Manifest.Id,
+ PluginPath: filepath.Dir(plugin.ManifestPath),
+ State: pluginState,
+ Name: plugin.Manifest.Name,
+ Description: plugin.Manifest.Description,
+ Version: plugin.Manifest.Version,
+ }
+
+ pluginStatuses = append(pluginStatuses, status)
+ }
+
+ return pluginStatuses, nil
+}
+
+// Activate activates the plugin with the given id.
+func (env *Environment) Activate(id string) (reterr error) {
+ env.mutex.Lock()
+ defer env.mutex.Unlock()
+
+ // Check if we are already active
+ if _, ok := env.activePlugins[id]; ok {
+ return nil
+ }
+
+ plugins, err := env.Available()
+ if err != nil {
+ return err
+ }
+ var pluginInfo *model.BundleInfo
+ for _, p := range plugins {
+ if p.Manifest != nil && p.Manifest.Id == id {
+ if pluginInfo != nil {
+ return fmt.Errorf("multiple plugins found: %v", id)
+ }
+ pluginInfo = p
+ }
+ }
+ if pluginInfo == nil {
+ return fmt.Errorf("plugin not found: %v", id)
+ }
+
+ activePlugin := activePlugin{BundleInfo: pluginInfo}
+ defer func() {
+ if reterr == nil {
+ activePlugin.State = model.PluginStateRunning
+ } else {
+ activePlugin.State = model.PluginStateFailedToStart
+ }
+ env.activePlugins[pluginInfo.Manifest.Id] = activePlugin
+ }()
+
+ if pluginInfo.Manifest.Webapp != nil {
+ bundlePath := filepath.Clean(pluginInfo.Manifest.Webapp.BundlePath)
+ if bundlePath == "" || bundlePath[0] == '.' {
+ return fmt.Errorf("invalid webapp bundle path")
+ }
+ bundlePath = filepath.Join(env.pluginDir, id, bundlePath)
+
+ webappBundle, err := ioutil.ReadFile(bundlePath)
+ if err != nil {
+ return errors.Wrapf(err, "unable to read webapp bundle: %v", id)
+ }
+
+ err = ioutil.WriteFile(fmt.Sprintf("%s/%s_bundle.js", env.webappPluginDir, id), webappBundle, 0644)
+ if err != nil {
+ return errors.Wrapf(err, "unable to write webapp bundle: %v", id)
+ }
+ }
+
+ if pluginInfo.Manifest.Backend != nil {
+ supervisor, err := newSupervisor(pluginInfo, env.logger, env.newAPIImpl(pluginInfo.Manifest))
+ if err != nil {
+ return errors.Wrapf(err, "unable to start plugin: %v", id)
+ }
+ activePlugin.supervisor = supervisor
+ }
+
+ return nil
+}
+
+// Deactivates the plugin with the given id.
+func (env *Environment) Deactivate(id string) {
+ env.mutex.Lock()
+ defer env.mutex.Unlock()
+
+ if activePlugin, ok := env.activePlugins[id]; !ok {
+ return
+ } else {
+ delete(env.activePlugins, id)
+ if activePlugin.supervisor != nil {
+ if err := activePlugin.supervisor.Hooks().OnDeactivate(); err != nil {
+ env.logger.Error("Plugin OnDeactivate() error", mlog.String("plugin_id", activePlugin.BundleInfo.Manifest.Id), mlog.Err(err))
+ }
+ activePlugin.supervisor.Shutdown()
+ }
+ }
+}
+
+// Shutdown deactivates all plugins and gracefully shuts down the environment.
+func (env *Environment) Shutdown() {
+ env.mutex.Lock()
+ defer env.mutex.Unlock()
+
+ for _, activePlugin := range env.activePlugins {
+ if activePlugin.supervisor != nil {
+ if err := activePlugin.supervisor.Hooks().OnDeactivate(); err != nil {
+ env.logger.Error("Plugin OnDeactivate() error", mlog.String("plugin_id", activePlugin.BundleInfo.Manifest.Id), mlog.Err(err))
+ }
+ activePlugin.supervisor.Shutdown()
+ }
+ }
+ env.activePlugins = make(map[string]activePlugin)
+ return
+}
+
+// HooksForPlugin returns the hooks API for the plugin with the given id.
+//
+// Consider using RunMultiPluginHook instead.
+func (env *Environment) HooksForPlugin(id string) (Hooks, error) {
+ env.mutex.RLock()
+ defer env.mutex.RUnlock()
+
+ if plug, ok := env.activePlugins[id]; ok && plug.supervisor != nil {
+ return plug.supervisor.Hooks(), nil
+ }
+
+ return nil, fmt.Errorf("plugin not found: %v", id)
+}
+
+// RunMultiPluginHook invokes hookRunnerFunc for each plugin that implements the given hookId.
+//
+// If hookRunnerFunc returns false, iteration will not continue. The iteration order among active
+// plugins is not specified.
+func (env *Environment) RunMultiPluginHook(hookRunnerFunc multiPluginHookRunnerFunc, hookId int) {
+ env.mutex.RLock()
+ defer env.mutex.RUnlock()
+
+ for _, activePlugin := range env.activePlugins {
+ if activePlugin.supervisor == nil || !activePlugin.supervisor.Implements(hookId) {
+ continue
+ }
+ if !hookRunnerFunc(activePlugin.supervisor.Hooks()) {
+ break
+ }
+ }
+}
diff --git a/plugin/example_hello_user_test.go b/plugin/example_hello_user_test.go
deleted file mode 100644
index 989cca0f2..000000000
--- a/plugin/example_hello_user_test.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package plugin_test
-
-import (
- "fmt"
- "net/http"
-
- "github.com/mattermost/mattermost-server/plugin"
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
-)
-
-type HelloUserPlugin struct {
- api plugin.API
-}
-
-func (p *HelloUserPlugin) OnActivate(api plugin.API) error {
- // Just save api for later when we need to look up users.
- p.api = api
- return nil
-}
-
-func (p *HelloUserPlugin) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- if userId := r.Header.Get("Mattermost-User-Id"); userId == "" {
- // Our visitor is unauthenticated.
- fmt.Fprintf(w, "Hello, stranger!")
- } else if user, err := p.api.GetUser(userId); err == nil {
- // Greet the user by name!
- fmt.Fprintf(w, "Welcome back, %v!", user.Username)
- } else {
- // This won't happen in normal circumstances, but let's just be safe.
- w.WriteHeader(http.StatusInternalServerError)
- fmt.Fprintf(w, err.Error())
- }
-}
-
-// This example demonstrates a plugin that handles HTTP requests which respond by greeting the user
-// by name.
-func Example_helloUser() {
- rpcplugin.Main(&HelloUserPlugin{})
-}
diff --git a/plugin/example_hello_world_test.go b/plugin/example_hello_world_test.go
index 5dea28823..955c6df3c 100644
--- a/plugin/example_hello_world_test.go
+++ b/plugin/example_hello_world_test.go
@@ -4,17 +4,17 @@ import (
"fmt"
"net/http"
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
+ "github.com/mattermost/mattermost-server/plugin"
)
type HelloWorldPlugin struct{}
-func (p *HelloWorldPlugin) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+func (p *HelloWorldPlugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, world!")
}
// This example demonstrates a plugin that handles HTTP requests which respond by greeting the
// world.
func Example_helloWorld() {
- rpcplugin.Main(&HelloWorldPlugin{})
+ plugin.ClientMain(&HelloWorldPlugin{})
}
diff --git a/plugin/example_help_test.go b/plugin/example_help_test.go
new file mode 100644
index 000000000..3f9be9a20
--- /dev/null
+++ b/plugin/example_help_test.go
@@ -0,0 +1,71 @@
+package plugin_test
+
+import (
+ "strings"
+
+ "github.com/mattermost/mattermost-server/model"
+ "github.com/mattermost/mattermost-server/plugin"
+)
+
+type HelpPlugin struct {
+ plugin.MattermostPlugin
+
+ TeamName string
+ ChannelName string
+
+ channelId string
+}
+
+func (p *HelpPlugin) OnConfigurationChange() error {
+ // Reuse the default implementation of OnConfigurationChange to automatically load the
+ // required TeamName and ChannelName.
+ if err := p.MattermostPlugin.OnConfigurationChange(); err != nil {
+ p.API.LogError(err.Error())
+ return nil
+ }
+
+ team, err := p.API.GetTeamByName(p.TeamName)
+ if err != nil {
+ p.API.LogError("failed to find team", "team_name", p.TeamName)
+ return nil
+ }
+
+ channel, err := p.API.GetChannelByName(p.ChannelName, team.Id)
+ if err != nil {
+ p.API.LogError("failed to find channel", "channel_name", p.ChannelName)
+ return nil
+ }
+
+ p.channelId = channel.Id
+
+ return nil
+}
+
+func (p *HelpPlugin) MessageHasBeenPosted(c *plugin.Context, post *model.Post) {
+ // Ignore posts not in the configured channel
+ if post.ChannelId != p.channelId {
+ return
+ }
+
+ // Ignore posts this plugin made.
+ if sentByPlugin, _ := post.Props["sent_by_plugin"].(bool); sentByPlugin {
+ return
+ }
+
+ // Ignore posts without a plea for help.
+ if !strings.Contains(post.Message, "help") {
+ return
+ }
+
+ p.API.SendEphemeralPost(post.UserId, &model.Post{
+ ChannelId: p.channelId,
+ Message: "You asked for help? Checkout https://about.mattermost.com/help/",
+ Props: map[string]interface{}{
+ "sent_by_plugin": true,
+ },
+ })
+}
+
+func Example_helpPlugin() {
+ plugin.ClientMain(&HelpPlugin{})
+}
diff --git a/plugin/example_test.go b/plugin/example_test.go
deleted file mode 100644
index e6ae3c2ea..000000000
--- a/plugin/example_test.go
+++ /dev/null
@@ -1,35 +0,0 @@
-package plugin_test
-
-import (
- "io/ioutil"
- "net/http/httptest"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/plugin/plugintest"
-)
-
-func TestHelloUserPlugin(t *testing.T) {
- user := &model.User{
- Id: model.NewId(),
- Username: "billybob",
- }
-
- api := &plugintest.API{}
- api.On("GetUser", user.Id).Return(user, nil)
- defer api.AssertExpectations(t)
-
- p := &HelloUserPlugin{}
- p.OnActivate(api)
-
- w := httptest.NewRecorder()
- r := httptest.NewRequest("GET", "/", nil)
- r.Header.Add("Mattermost-User-Id", user.Id)
- p.ServeHTTP(w, r)
- body, err := ioutil.ReadAll(w.Result().Body)
- require.NoError(t, err)
- assert.Equal(t, "Welcome back, billybob!", string(body))
-}
diff --git a/plugin/hclog_adapter.go b/plugin/hclog_adapter.go
new file mode 100644
index 000000000..64b1e5243
--- /dev/null
+++ b/plugin/hclog_adapter.go
@@ -0,0 +1,99 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package plugin
+
+import (
+ "fmt"
+ "log"
+ "strings"
+
+ "github.com/hashicorp/go-hclog"
+ "github.com/mattermost/mattermost-server/mlog"
+)
+
+type hclogAdapter struct {
+ wrappedLogger *mlog.Logger
+ extrasKey string
+}
+
+func (h *hclogAdapter) Trace(msg string, args ...interface{}) {
+ extras := strings.TrimSpace(fmt.Sprint(args...))
+ if extras != "" {
+ h.wrappedLogger.Debug(msg, mlog.String(h.extrasKey, extras))
+ } else {
+ h.wrappedLogger.Debug(msg)
+ }
+}
+
+func (h *hclogAdapter) Debug(msg string, args ...interface{}) {
+ extras := strings.TrimSpace(fmt.Sprint(args...))
+ if extras != "" {
+ h.wrappedLogger.Debug(msg, mlog.String(h.extrasKey, extras))
+ } else {
+ h.wrappedLogger.Debug(msg)
+ }
+}
+
+func (h *hclogAdapter) Info(msg string, args ...interface{}) {
+ extras := strings.TrimSpace(fmt.Sprint(args...))
+ if extras != "" {
+ h.wrappedLogger.Info(msg, mlog.String(h.extrasKey, extras))
+ } else {
+ h.wrappedLogger.Info(msg)
+ }
+}
+
+func (h *hclogAdapter) Warn(msg string, args ...interface{}) {
+ extras := strings.TrimSpace(fmt.Sprint(args...))
+ if extras != "" {
+ h.wrappedLogger.Warn(msg, mlog.String(h.extrasKey, extras))
+ } else {
+ h.wrappedLogger.Warn(msg)
+ }
+}
+
+func (h *hclogAdapter) Error(msg string, args ...interface{}) {
+ extras := strings.TrimSpace(fmt.Sprint(args...))
+ if extras != "" {
+ h.wrappedLogger.Error(msg, mlog.String(h.extrasKey, extras))
+ } else {
+ h.wrappedLogger.Error(msg)
+ }
+}
+
+func (h *hclogAdapter) IsTrace() bool {
+ return false
+}
+
+func (h *hclogAdapter) IsDebug() bool {
+ return true
+}
+
+func (h *hclogAdapter) IsInfo() bool {
+ return true
+}
+
+func (h *hclogAdapter) IsWarn() bool {
+ return true
+}
+
+func (h *hclogAdapter) IsError() bool {
+ return true
+}
+
+func (h *hclogAdapter) With(args ...interface{}) hclog.Logger {
+ return h
+}
+
+func (h *hclogAdapter) Named(name string) hclog.Logger {
+ return h
+}
+
+func (h *hclogAdapter) ResetNamed(name string) hclog.Logger {
+ return h
+}
+
+func (h *hclogAdapter) StandardLogger(opts *hclog.StandardLoggerOptions) *log.Logger {
+ return h.wrappedLogger.StdLog()
+}
diff --git a/plugin/hooks.go b/plugin/hooks.go
index e41081e48..5200291f2 100644
--- a/plugin/hooks.go
+++ b/plugin/hooks.go
@@ -9,15 +9,40 @@ import (
"github.com/mattermost/mattermost-server/model"
)
-// Methods from the Hooks interface can be used by a plugin to respond to events. Methods are likely
-// to be added over time, and plugins are not expected to implement all of them. Instead, plugins
-// are expected to implement a subset of them and pass an instance to plugin/rpcplugin.Main, which
-// will take over execution of the process and add default behaviors for missing hooks.
+// These assignments are part of the wire protocol used to trigger hook events in plugins.
+//
+// Feel free to add more, but do not change existing assignments. Follow the naming convention of
+// <HookName>Id as the autogenerated glue code depends on that.
+const (
+ OnActivateId = 0
+ OnDeactivateId = 1
+ ServeHTTPId = 2
+ OnConfigurationChangeId = 3
+ ExecuteCommandId = 4
+ MessageWillBePostedId = 5
+ MessageWillBeUpdatedId = 6
+ MessageHasBeenPostedId = 7
+ MessageHasBeenUpdatedId = 8
+ UserHasJoinedChannelId = 9
+ UserHasLeftChannelId = 10
+ UserHasJoinedTeamId = 11
+ UserHasLeftTeamId = 12
+ ChannelHasBeenCreatedId = 13
+ TotalHooksId = iota
+)
+
+// Hooks describes the methods a plugin may implement to automatically receive the corresponding
+// event.
+//
+// A plugin only need implement the hooks it cares about. The MattermostPlugin provides some
+// default implementations for convenience but may be overridden.
type Hooks interface {
- // OnActivate is invoked when the plugin is activated. Implementations will usually want to save
- // the api argument for later use. Loading configuration for the first time is also a commonly
- // done here.
- OnActivate(API) error
+ // OnActivate is invoked when the plugin is activated.
+ OnActivate() error
+
+ // Implemented returns a list of hooks that are implemented by the plugin.
+ // Plugins do not need to provide an implementation. Any given will be ignored.
+ Implemented() ([]string, error)
// OnDeactivate is invoked when the plugin is deactivated. This is the plugin's last chance to
// use the API, and the plugin will be terminated shortly after this invocation.
@@ -31,13 +56,13 @@ type Hooks interface {
//
// The Mattermost-User-Id header will be present if (and only if) the request is by an
// authenticated user.
- ServeHTTP(http.ResponseWriter, *http.Request)
+ ServeHTTP(c *Context, w http.ResponseWriter, r *http.Request)
// ExecuteCommand executes a command that has been previously registered via the RegisterCommand
// API.
- ExecuteCommand(args *model.CommandArgs) (*model.CommandResponse, *model.AppError)
+ ExecuteCommand(c *Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError)
- // MessageWillBePosted is invoked when a message is posted by a user before it is commited
+ // MessageWillBePosted is invoked when a message is posted by a user before it is committed
// to the database. If you also want to act on edited posts, see MessageWillBeUpdated.
// Return values should be the modified post or nil if rejected and an explanation for the user.
//
@@ -45,9 +70,9 @@ type Hooks interface {
//
// Note that this method will be called for posts created by plugins, including the plugin that
// created the post.
- MessageWillBePosted(post *model.Post) (*model.Post, string)
+ MessageWillBePosted(c *Context, post *model.Post) (*model.Post, string)
- // MessageWillBeUpdated is invoked when a message is updated by a user before it is commited
+ // MessageWillBeUpdated is invoked when a message is updated by a user before it is committed
// to the database. If you also want to act on new posts, see MessageWillBePosted.
// Return values should be the modified post or nil if rejected and an explanation for the user.
// On rejection, the post will be kept in its previous state.
@@ -56,17 +81,36 @@ type Hooks interface {
//
// Note that this method will be called for posts updated by plugins, including the plugin that
// updated the post.
- MessageWillBeUpdated(newPost, oldPost *model.Post) (*model.Post, string)
+ MessageWillBeUpdated(c *Context, newPost, oldPost *model.Post) (*model.Post, string)
- // MessageHasBeenPosted is invoked after the message has been commited to the databse.
+ // MessageHasBeenPosted is invoked after the message has been committed to the database.
// If you need to modify or reject the post, see MessageWillBePosted
// Note that this method will be called for posts created by plugins, including the plugin that
// created the post.
- MessageHasBeenPosted(post *model.Post)
+ MessageHasBeenPosted(c *Context, post *model.Post)
- // MessageHasBeenUpdated is invoked after a message is updated and has been updated in the databse.
+ // MessageHasBeenUpdated is invoked after a message is updated and has been updated in the database.
// If you need to modify or reject the post, see MessageWillBeUpdated
// Note that this method will be called for posts created by plugins, including the plugin that
// created the post.
- MessageHasBeenUpdated(newPost, oldPost *model.Post)
+ MessageHasBeenUpdated(c *Context, newPost, oldPost *model.Post)
+
+ // ChannelHasBeenCreated is invoked after the channel has been committed to the database.
+ ChannelHasBeenCreated(c *Context, channel *model.Channel)
+
+ // UserHasJoinedChannel is invoked after the membership has been committed to the database.
+ // If actor is not nil, the user was invited to the channel by the actor.
+ UserHasJoinedChannel(c *Context, channelMember *model.ChannelMember, actor *model.User)
+
+ // UserHasLeftChannel is invoked after the membership has been removed from the database.
+ // If actor is not nil, the user was removed from the channel by the actor.
+ UserHasLeftChannel(c *Context, channelMember *model.ChannelMember, actor *model.User)
+
+ // UserHasJoinedTeam is invoked after the membership has been committed to the database.
+ // If actor is not nil, the user was added to the team by the actor.
+ UserHasJoinedTeam(c *Context, teamMember *model.TeamMember, actor *model.User)
+
+ // UserHasLeftTeam is invoked after the membership has been removed from the database.
+ // If actor is not nil, the user was removed from the team by the actor.
+ UserHasLeftTeam(c *Context, teamMember *model.TeamMember, actor *model.User)
}
diff --git a/plugin/http.go b/plugin/http.go
new file mode 100644
index 000000000..7d1650369
--- /dev/null
+++ b/plugin/http.go
@@ -0,0 +1,83 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package plugin
+
+import (
+ "io"
+ "net/http"
+ "net/rpc"
+)
+
+type httpResponseWriterRPCServer struct {
+ w http.ResponseWriter
+}
+
+func (w *httpResponseWriterRPCServer) Header(args struct{}, reply *http.Header) error {
+ *reply = w.w.Header()
+ return nil
+}
+
+func (w *httpResponseWriterRPCServer) Write(args []byte, reply *struct{}) error {
+ _, err := w.w.Write(args)
+ return err
+}
+
+func (w *httpResponseWriterRPCServer) WriteHeader(args int, reply *struct{}) error {
+ w.w.WriteHeader(args)
+ return nil
+}
+
+func (w *httpResponseWriterRPCServer) SyncHeader(args http.Header, reply *struct{}) error {
+ dest := w.w.Header()
+ for k := range dest {
+ if _, ok := args[k]; !ok {
+ delete(dest, k)
+ }
+ }
+ for k, v := range args {
+ dest[k] = v
+ }
+ return nil
+}
+
+type httpResponseWriterRPCClient struct {
+ client *rpc.Client
+ header http.Header
+}
+
+var _ http.ResponseWriter = (*httpResponseWriterRPCClient)(nil)
+
+func (w *httpResponseWriterRPCClient) Header() http.Header {
+ if w.header == nil {
+ w.client.Call("Plugin.Header", struct{}{}, &w.header)
+ }
+ return w.header
+}
+
+func (w *httpResponseWriterRPCClient) Write(b []byte) (int, error) {
+ if err := w.client.Call("Plugin.SyncHeader", w.header, nil); err != nil {
+ return 0, err
+ }
+ if err := w.client.Call("Plugin.Write", b, nil); err != nil {
+ return 0, err
+ }
+ return len(b), nil
+}
+
+func (w *httpResponseWriterRPCClient) WriteHeader(statusCode int) {
+ if err := w.client.Call("Plugin.SyncHeader", w.header, nil); err != nil {
+ return
+ }
+ w.client.Call("Plugin.WriteHeader", statusCode, nil)
+}
+
+func (h *httpResponseWriterRPCClient) Close() error {
+ return h.client.Close()
+}
+
+func connectHTTPResponseWriter(conn io.ReadWriteCloser) *httpResponseWriterRPCClient {
+ return &httpResponseWriterRPCClient{
+ client: rpc.NewClient(conn),
+ }
+}
diff --git a/plugin/interface_generator/main.go b/plugin/interface_generator/main.go
new file mode 100644
index 000000000..4b8b6786f
--- /dev/null
+++ b/plugin/interface_generator/main.go
@@ -0,0 +1,397 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/printer"
+ "go/token"
+ "io/ioutil"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+
+ "github.com/alecthomas/template"
+ "github.com/pkg/errors"
+)
+
+type IHookEntry struct {
+ FuncName string
+ Args *ast.FieldList
+ Results *ast.FieldList
+}
+
+type PluginInterfaceInfo struct {
+ Hooks []IHookEntry
+ API []IHookEntry
+ FileSet *token.FileSet
+}
+
+func FieldListToFuncList(fieldList *ast.FieldList, fileset *token.FileSet) string {
+ result := []string{}
+ if fieldList == nil || len(fieldList.List) == 0 {
+ return "()"
+ }
+ for _, field := range fieldList.List {
+ typeNameBuffer := &bytes.Buffer{}
+ err := printer.Fprint(typeNameBuffer, fileset, field.Type)
+ if err != nil {
+ panic(err)
+ }
+ typeName := typeNameBuffer.String()
+ names := []string{}
+ for _, name := range field.Names {
+ names = append(names, name.Name)
+ }
+ result = append(result, strings.Join(names, ", ")+" "+typeName)
+ }
+
+ return "(" + strings.Join(result, ", ") + ")"
+}
+
+func FieldListToNames(fieldList *ast.FieldList, fileset *token.FileSet) string {
+ result := []string{}
+ if fieldList == nil || len(fieldList.List) == 0 {
+ return ""
+ }
+ for _, field := range fieldList.List {
+ for _, name := range field.Names {
+ result = append(result, name.Name)
+ }
+ }
+
+ return strings.Join(result, ", ")
+}
+
+func FieldListDestruct(structPrefix string, fieldList *ast.FieldList, fileset *token.FileSet) string {
+ result := []string{}
+ if fieldList == nil || len(fieldList.List) == 0 {
+ return ""
+ }
+ nextLetter := 'A'
+ for _, field := range fieldList.List {
+ typeNameBuffer := &bytes.Buffer{}
+ err := printer.Fprint(typeNameBuffer, fileset, field.Type)
+ if err != nil {
+ panic(err)
+ }
+ typeName := typeNameBuffer.String()
+ suffix := ""
+ if strings.HasPrefix(typeName, "...") {
+ suffix = "..."
+ }
+ if len(field.Names) == 0 {
+ result = append(result, structPrefix+string(nextLetter)+suffix)
+ nextLetter += 1
+ } else {
+ for range field.Names {
+ result = append(result, structPrefix+string(nextLetter)+suffix)
+ nextLetter += 1
+ }
+ }
+ }
+
+ return strings.Join(result, ", ")
+}
+
+func FieldListToStructList(fieldList *ast.FieldList, fileset *token.FileSet) string {
+ result := []string{}
+ if fieldList == nil || len(fieldList.List) == 0 {
+ return ""
+ }
+ nextLetter := 'A'
+ for _, field := range fieldList.List {
+ typeNameBuffer := &bytes.Buffer{}
+ err := printer.Fprint(typeNameBuffer, fileset, field.Type)
+ if err != nil {
+ panic(err)
+ }
+ typeName := typeNameBuffer.String()
+ if strings.HasPrefix(typeName, "...") {
+ typeName = strings.Replace(typeName, "...", "[]", 1)
+ }
+ if len(field.Names) == 0 {
+ result = append(result, string(nextLetter)+" "+typeName)
+ nextLetter += 1
+ } else {
+ for range field.Names {
+ result = append(result, string(nextLetter)+" "+typeName)
+ nextLetter += 1
+ }
+ }
+ }
+
+ return strings.Join(result, "\n\t")
+}
+
+func goList(dir string) ([]string, error) {
+ cmd := exec.Command("go", "list", "-f", "{{.Dir}}", dir)
+ bytes, err := cmd.Output()
+ if err != nil {
+ return nil, errors.Wrap(err, "Can't list packages")
+ }
+
+ return strings.Fields(string(bytes)), nil
+}
+
+func (info *PluginInterfaceInfo) addHookMethod(method *ast.Field) {
+ info.Hooks = append(info.Hooks, IHookEntry{
+ FuncName: method.Names[0].Name,
+ Args: method.Type.(*ast.FuncType).Params,
+ Results: method.Type.(*ast.FuncType).Results,
+ })
+}
+
+func (info *PluginInterfaceInfo) addAPIMethod(method *ast.Field) {
+ info.API = append(info.API, IHookEntry{
+ FuncName: method.Names[0].Name,
+ Args: method.Type.(*ast.FuncType).Params,
+ Results: method.Type.(*ast.FuncType).Results,
+ })
+}
+
+func (info *PluginInterfaceInfo) makeHookInspector() func(node ast.Node) bool {
+ return func(node ast.Node) bool {
+ if typeSpec, ok := node.(*ast.TypeSpec); ok {
+ if typeSpec.Name.Name == "Hooks" {
+ for _, method := range typeSpec.Type.(*ast.InterfaceType).Methods.List {
+ info.addHookMethod(method)
+ }
+ return false
+ } else if typeSpec.Name.Name == "API" {
+ for _, method := range typeSpec.Type.(*ast.InterfaceType).Methods.List {
+ info.addAPIMethod(method)
+ }
+ return false
+ }
+ }
+ return true
+ }
+}
+
+func getPluginInfo(dir string) (*PluginInterfaceInfo, error) {
+ pluginInfo := &PluginInterfaceInfo{
+ Hooks: make([]IHookEntry, 0),
+ FileSet: token.NewFileSet(),
+ }
+
+ packages, err := parser.ParseDir(pluginInfo.FileSet, dir, nil, parser.ParseComments)
+ if err != nil {
+ log.Println("Parser error in dir "+dir+": ", err)
+ }
+
+ for _, pkg := range packages {
+ if pkg.Name != "plugin" {
+ continue
+ }
+
+ for _, file := range pkg.Files {
+ ast.Inspect(file, pluginInfo.makeHookInspector())
+ }
+ }
+
+ return pluginInfo, nil
+}
+
+var hooksTemplate = `// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+// Code generated by "make pluginapi"
+// DO NOT EDIT
+
+package plugin
+
+{{range .HooksMethods}}
+
+func init() {
+ hookNameToId["{{.Name}}"] = {{.Name}}Id
+}
+
+type {{.Name | obscure}}Args struct {
+ {{structStyle .Params}}
+}
+
+type {{.Name | obscure}}Returns struct {
+ {{structStyle .Return}}
+}
+
+func (g *hooksRPCClient) {{.Name}}{{funcStyle .Params}} {{funcStyle .Return}} {
+ _args := &{{.Name | obscure}}Args{ {{valuesOnly .Params}} }
+ _returns := &{{.Name | obscure}}Returns{}
+ if g.implemented[{{.Name}}Id] {
+ if err := g.client.Call("Plugin.{{.Name}}", _args, _returns); err != nil {
+ g.log.Error("RPC call {{.Name}} to plugin failed.", mlog.Err(err))
+ }
+ }
+ return {{destruct "_returns." .Return}}
+}
+
+func (s *hooksRPCServer) {{.Name}}(args *{{.Name | obscure}}Args, returns *{{.Name | obscure}}Returns) error {
+ if hook, ok := s.impl.(interface {
+ {{.Name}}{{funcStyle .Params}} {{funcStyle .Return}}
+ }); ok {
+ {{if .Return}}{{destruct "returns." .Return}} = {{end}}hook.{{.Name}}({{destruct "args." .Params}})
+ } else {
+ return fmt.Errorf("Hook {{.Name}} called but not implemented.")
+ }
+ return nil
+}
+{{end}}
+
+{{range .APIMethods}}
+
+type {{.Name | obscure}}Args struct {
+ {{structStyle .Params}}
+}
+
+type {{.Name | obscure}}Returns struct {
+ {{structStyle .Return}}
+}
+
+func (g *apiRPCClient) {{.Name}}{{funcStyle .Params}} {{funcStyle .Return}} {
+ _args := &{{.Name | obscure}}Args{ {{valuesOnly .Params}} }
+ _returns := &{{.Name | obscure}}Returns{}
+ if err := g.client.Call("Plugin.{{.Name}}", _args, _returns); err != nil {
+ g.log.Error("RPC call to {{.Name}} API failed.", mlog.Err(err))
+ }
+ return {{destruct "_returns." .Return}}
+}
+
+func (s *apiRPCServer) {{.Name}}(args *{{.Name | obscure}}Args, returns *{{.Name | obscure}}Returns) error {
+ if hook, ok := s.impl.(interface {
+ {{.Name}}{{funcStyle .Params}} {{funcStyle .Return}}
+ }); ok {
+ {{if .Return}}{{destruct "returns." .Return}} = {{end}}hook.{{.Name}}({{destruct "args." .Params}})
+ } else {
+ return fmt.Errorf("API {{.Name}} called but not implemented.")
+ }
+ return nil
+}
+{{end}}
+`
+
+type MethodParams struct {
+ Name string
+ Params *ast.FieldList
+ Return *ast.FieldList
+}
+
+type HooksTemplateParams struct {
+ HooksMethods []MethodParams
+ APIMethods []MethodParams
+}
+
+func generateGlue(info *PluginInterfaceInfo) {
+ templateFunctions := map[string]interface{}{
+ "funcStyle": func(fields *ast.FieldList) string { return FieldListToFuncList(fields, info.FileSet) },
+ "structStyle": func(fields *ast.FieldList) string { return FieldListToStructList(fields, info.FileSet) },
+ "valuesOnly": func(fields *ast.FieldList) string { return FieldListToNames(fields, info.FileSet) },
+ "destruct": func(structPrefix string, fields *ast.FieldList) string {
+ return FieldListDestruct(structPrefix, fields, info.FileSet)
+ },
+ "obscure": func(name string) string {
+ return "Z_" + name
+ },
+ }
+
+ hooksTemplate, err := template.New("hooks").Funcs(templateFunctions).Parse(hooksTemplate)
+ if err != nil {
+ panic(err)
+ }
+
+ templateParams := HooksTemplateParams{}
+ for _, hook := range info.Hooks {
+ templateParams.HooksMethods = append(templateParams.HooksMethods, MethodParams{
+ Name: hook.FuncName,
+ Params: hook.Args,
+ Return: hook.Results,
+ })
+ }
+ for _, api := range info.API {
+ templateParams.APIMethods = append(templateParams.APIMethods, MethodParams{
+ Name: api.FuncName,
+ Params: api.Args,
+ Return: api.Results,
+ })
+ }
+ templateResult := &bytes.Buffer{}
+ hooksTemplate.Execute(templateResult, &templateParams)
+
+ importsBuffer := &bytes.Buffer{}
+ cmd := exec.Command("goimports")
+ cmd.Stdin = templateResult
+ cmd.Stdout = importsBuffer
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ panic(err)
+ }
+
+ if err := ioutil.WriteFile(filepath.Join(getPluginPackageDir(), "client_rpc_generated.go"), importsBuffer.Bytes(), 0664); err != nil {
+ panic(err)
+ }
+}
+
+func getPluginPackageDir() string {
+ dirs, err := goList("github.com/mattermost/mattermost-server/plugin")
+ if err != nil {
+ panic(err)
+ } else if len(dirs) != 1 {
+ panic("More than one package dir, or no dirs!")
+ }
+
+ return dirs[0]
+}
+
+func removeExcluded(info *PluginInterfaceInfo) *PluginInterfaceInfo {
+ toBeExcluded := func(item string) bool {
+ excluded := []string{
+ "OnActivate",
+ "Implemented",
+ "LoadPluginConfiguration",
+ "ServeHTTP",
+ }
+ for _, exclusion := range excluded {
+ if exclusion == item {
+ return true
+ }
+ }
+ return false
+ }
+ hooksResult := make([]IHookEntry, 0, len(info.Hooks))
+ for _, hook := range info.Hooks {
+ if !toBeExcluded(hook.FuncName) {
+ hooksResult = append(hooksResult, hook)
+ }
+ }
+ info.Hooks = hooksResult
+
+ apiResult := make([]IHookEntry, 0, len(info.API))
+ for _, api := range info.API {
+ if !toBeExcluded(api.FuncName) {
+ apiResult = append(apiResult, api)
+ }
+ }
+ info.API = apiResult
+
+ return info
+}
+
+func main() {
+ pluginPackageDir := getPluginPackageDir()
+
+ log.Println("Generating plugin glue")
+ info, err := getPluginInfo(pluginPackageDir)
+ if err != nil {
+ fmt.Println("Unable to get plugin info: " + err.Error())
+ }
+
+ info = removeExcluded(info)
+
+ generateGlue(info)
+}
diff --git a/plugin/rpcplugin/io.go b/plugin/io_rpc.go
index 44b89956c..18a1eb525 100644
--- a/plugin/rpcplugin/io.go
+++ b/plugin/io_rpc.go
@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
+// See LICENSE.txt for license information.
-package rpcplugin
+package plugin
import (
"bufio"
@@ -22,15 +22,11 @@ func (rwc *rwc) Close() (err error) {
return
}
-func NewReadWriteCloser(r io.ReadCloser, w io.WriteCloser) io.ReadWriteCloser {
- return &rwc{r, w}
-}
-
-type RemoteIOReader struct {
+type remoteIOReader struct {
conn io.ReadWriteCloser
}
-func (r *RemoteIOReader) Read(b []byte) (int, error) {
+func (r *remoteIOReader) Read(b []byte) (int, error) {
var buf [10]byte
n := binary.PutVarint(buf[:], int64(len(b)))
if _, err := r.conn.Write(buf[:n]); err != nil {
@@ -39,15 +35,15 @@ func (r *RemoteIOReader) Read(b []byte) (int, error) {
return r.conn.Read(b)
}
-func (r *RemoteIOReader) Close() error {
+func (r *remoteIOReader) Close() error {
return r.conn.Close()
}
-func ConnectIOReader(conn io.ReadWriteCloser) io.ReadCloser {
- return &RemoteIOReader{conn}
+func connectIOReader(conn io.ReadWriteCloser) io.ReadCloser {
+ return &remoteIOReader{conn}
}
-func ServeIOReader(r io.Reader, conn io.ReadWriteCloser) {
+func serveIOReader(r io.Reader, conn io.ReadWriteCloser) {
cr := bufio.NewReader(conn)
defer conn.Close()
buf := make([]byte, 32*1024)
diff --git a/plugin/mock_api_test.go b/plugin/mock_api_test.go
new file mode 100644
index 000000000..1ffa3aa46
--- /dev/null
+++ b/plugin/mock_api_test.go
@@ -0,0 +1,1018 @@
+// Code generated by mockery v1.0.0. DO NOT EDIT.
+
+// Regenerate this file using `make plugin-mocks`.
+
+package plugin
+
+import mock "github.com/stretchr/testify/mock"
+import model "github.com/mattermost/mattermost-server/model"
+
+// MockAPI is an autogenerated mock type for the API type
+type MockAPI struct {
+ mock.Mock
+}
+
+// AddChannelMember provides a mock function with given fields: channelId, userId
+func (_m *MockAPI) AddChannelMember(channelId string, userId string) (*model.ChannelMember, *model.AppError) {
+ ret := _m.Called(channelId, userId)
+
+ var r0 *model.ChannelMember
+ if rf, ok := ret.Get(0).(func(string, string) *model.ChannelMember); ok {
+ r0 = rf(channelId, userId)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.ChannelMember)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string, string) *model.AppError); ok {
+ r1 = rf(channelId, userId)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// CreateChannel provides a mock function with given fields: channel
+func (_m *MockAPI) CreateChannel(channel *model.Channel) (*model.Channel, *model.AppError) {
+ ret := _m.Called(channel)
+
+ var r0 *model.Channel
+ if rf, ok := ret.Get(0).(func(*model.Channel) *model.Channel); ok {
+ r0 = rf(channel)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.Channel)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(*model.Channel) *model.AppError); ok {
+ r1 = rf(channel)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// CreatePost provides a mock function with given fields: post
+func (_m *MockAPI) CreatePost(post *model.Post) (*model.Post, *model.AppError) {
+ ret := _m.Called(post)
+
+ var r0 *model.Post
+ if rf, ok := ret.Get(0).(func(*model.Post) *model.Post); ok {
+ r0 = rf(post)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.Post)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(*model.Post) *model.AppError); ok {
+ r1 = rf(post)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// CreateTeam provides a mock function with given fields: team
+func (_m *MockAPI) CreateTeam(team *model.Team) (*model.Team, *model.AppError) {
+ ret := _m.Called(team)
+
+ var r0 *model.Team
+ if rf, ok := ret.Get(0).(func(*model.Team) *model.Team); ok {
+ r0 = rf(team)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.Team)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(*model.Team) *model.AppError); ok {
+ r1 = rf(team)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// CreateTeamMember provides a mock function with given fields: teamId, userId
+func (_m *MockAPI) CreateTeamMember(teamId string, userId string) (*model.TeamMember, *model.AppError) {
+ ret := _m.Called(teamId, userId)
+
+ var r0 *model.TeamMember
+ if rf, ok := ret.Get(0).(func(string, string) *model.TeamMember); ok {
+ r0 = rf(teamId, userId)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.TeamMember)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string, string) *model.AppError); ok {
+ r1 = rf(teamId, userId)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// CreateTeamMembers provides a mock function with given fields: teamId, userIds, requestorId
+func (_m *MockAPI) CreateTeamMembers(teamId string, userIds []string, requestorId string) ([]*model.TeamMember, *model.AppError) {
+ ret := _m.Called(teamId, userIds, requestorId)
+
+ var r0 []*model.TeamMember
+ if rf, ok := ret.Get(0).(func(string, []string, string) []*model.TeamMember); ok {
+ r0 = rf(teamId, userIds, requestorId)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).([]*model.TeamMember)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string, []string, string) *model.AppError); ok {
+ r1 = rf(teamId, userIds, requestorId)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// CreateUser provides a mock function with given fields: user
+func (_m *MockAPI) CreateUser(user *model.User) (*model.User, *model.AppError) {
+ ret := _m.Called(user)
+
+ var r0 *model.User
+ if rf, ok := ret.Get(0).(func(*model.User) *model.User); ok {
+ r0 = rf(user)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.User)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(*model.User) *model.AppError); ok {
+ r1 = rf(user)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// DeleteChannel provides a mock function with given fields: channelId
+func (_m *MockAPI) DeleteChannel(channelId string) *model.AppError {
+ ret := _m.Called(channelId)
+
+ var r0 *model.AppError
+ if rf, ok := ret.Get(0).(func(string) *model.AppError); ok {
+ r0 = rf(channelId)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.AppError)
+ }
+ }
+
+ return r0
+}
+
+// DeleteChannelMember provides a mock function with given fields: channelId, userId
+func (_m *MockAPI) DeleteChannelMember(channelId string, userId string) *model.AppError {
+ ret := _m.Called(channelId, userId)
+
+ var r0 *model.AppError
+ if rf, ok := ret.Get(0).(func(string, string) *model.AppError); ok {
+ r0 = rf(channelId, userId)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.AppError)
+ }
+ }
+
+ return r0
+}
+
+// DeletePost provides a mock function with given fields: postId
+func (_m *MockAPI) DeletePost(postId string) *model.AppError {
+ ret := _m.Called(postId)
+
+ var r0 *model.AppError
+ if rf, ok := ret.Get(0).(func(string) *model.AppError); ok {
+ r0 = rf(postId)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.AppError)
+ }
+ }
+
+ return r0
+}
+
+// DeleteTeam provides a mock function with given fields: teamId
+func (_m *MockAPI) DeleteTeam(teamId string) *model.AppError {
+ ret := _m.Called(teamId)
+
+ var r0 *model.AppError
+ if rf, ok := ret.Get(0).(func(string) *model.AppError); ok {
+ r0 = rf(teamId)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.AppError)
+ }
+ }
+
+ return r0
+}
+
+// DeleteTeamMember provides a mock function with given fields: teamId, userId, requestorId
+func (_m *MockAPI) DeleteTeamMember(teamId string, userId string, requestorId string) *model.AppError {
+ ret := _m.Called(teamId, userId, requestorId)
+
+ var r0 *model.AppError
+ if rf, ok := ret.Get(0).(func(string, string, string) *model.AppError); ok {
+ r0 = rf(teamId, userId, requestorId)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.AppError)
+ }
+ }
+
+ return r0
+}
+
+// DeleteUser provides a mock function with given fields: userId
+func (_m *MockAPI) DeleteUser(userId string) *model.AppError {
+ ret := _m.Called(userId)
+
+ var r0 *model.AppError
+ if rf, ok := ret.Get(0).(func(string) *model.AppError); ok {
+ r0 = rf(userId)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.AppError)
+ }
+ }
+
+ return r0
+}
+
+// GetChannel provides a mock function with given fields: channelId
+func (_m *MockAPI) GetChannel(channelId string) (*model.Channel, *model.AppError) {
+ ret := _m.Called(channelId)
+
+ var r0 *model.Channel
+ if rf, ok := ret.Get(0).(func(string) *model.Channel); ok {
+ r0 = rf(channelId)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.Channel)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string) *model.AppError); ok {
+ r1 = rf(channelId)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// GetChannelByName provides a mock function with given fields: name, teamId
+func (_m *MockAPI) GetChannelByName(name string, teamId string) (*model.Channel, *model.AppError) {
+ ret := _m.Called(name, teamId)
+
+ var r0 *model.Channel
+ if rf, ok := ret.Get(0).(func(string, string) *model.Channel); ok {
+ r0 = rf(name, teamId)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.Channel)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string, string) *model.AppError); ok {
+ r1 = rf(name, teamId)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// GetChannelMember provides a mock function with given fields: channelId, userId
+func (_m *MockAPI) GetChannelMember(channelId string, userId string) (*model.ChannelMember, *model.AppError) {
+ ret := _m.Called(channelId, userId)
+
+ var r0 *model.ChannelMember
+ if rf, ok := ret.Get(0).(func(string, string) *model.ChannelMember); ok {
+ r0 = rf(channelId, userId)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.ChannelMember)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string, string) *model.AppError); ok {
+ r1 = rf(channelId, userId)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// GetConfig provides a mock function with given fields:
+func (_m *MockAPI) GetConfig() *model.Config {
+ ret := _m.Called()
+
+ var r0 *model.Config
+ if rf, ok := ret.Get(0).(func() *model.Config); ok {
+ r0 = rf()
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.Config)
+ }
+ }
+
+ return r0
+}
+
+// GetDirectChannel provides a mock function with given fields: userId1, userId2
+func (_m *MockAPI) GetDirectChannel(userId1 string, userId2 string) (*model.Channel, *model.AppError) {
+ ret := _m.Called(userId1, userId2)
+
+ var r0 *model.Channel
+ if rf, ok := ret.Get(0).(func(string, string) *model.Channel); ok {
+ r0 = rf(userId1, userId2)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.Channel)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string, string) *model.AppError); ok {
+ r1 = rf(userId1, userId2)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// GetGroupChannel provides a mock function with given fields: userIds
+func (_m *MockAPI) GetGroupChannel(userIds []string) (*model.Channel, *model.AppError) {
+ ret := _m.Called(userIds)
+
+ var r0 *model.Channel
+ if rf, ok := ret.Get(0).(func([]string) *model.Channel); ok {
+ r0 = rf(userIds)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.Channel)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func([]string) *model.AppError); ok {
+ r1 = rf(userIds)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// GetPost provides a mock function with given fields: postId
+func (_m *MockAPI) GetPost(postId string) (*model.Post, *model.AppError) {
+ ret := _m.Called(postId)
+
+ var r0 *model.Post
+ if rf, ok := ret.Get(0).(func(string) *model.Post); ok {
+ r0 = rf(postId)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.Post)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string) *model.AppError); ok {
+ r1 = rf(postId)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// GetPublicChannelsForTeam provides a mock function with given fields: teamId, offset, limit
+func (_m *MockAPI) GetPublicChannelsForTeam(teamId string, offset int, limit int) (*model.ChannelList, *model.AppError) {
+ ret := _m.Called(teamId, offset, limit)
+
+ var r0 *model.ChannelList
+ if rf, ok := ret.Get(0).(func(string, int, int) *model.ChannelList); ok {
+ r0 = rf(teamId, offset, limit)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.ChannelList)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string, int, int) *model.AppError); ok {
+ r1 = rf(teamId, offset, limit)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// GetTeam provides a mock function with given fields: teamId
+func (_m *MockAPI) GetTeam(teamId string) (*model.Team, *model.AppError) {
+ ret := _m.Called(teamId)
+
+ var r0 *model.Team
+ if rf, ok := ret.Get(0).(func(string) *model.Team); ok {
+ r0 = rf(teamId)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.Team)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string) *model.AppError); ok {
+ r1 = rf(teamId)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// GetTeamByName provides a mock function with given fields: name
+func (_m *MockAPI) GetTeamByName(name string) (*model.Team, *model.AppError) {
+ ret := _m.Called(name)
+
+ var r0 *model.Team
+ if rf, ok := ret.Get(0).(func(string) *model.Team); ok {
+ r0 = rf(name)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.Team)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string) *model.AppError); ok {
+ r1 = rf(name)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// GetTeamMember provides a mock function with given fields: teamId, userId
+func (_m *MockAPI) GetTeamMember(teamId string, userId string) (*model.TeamMember, *model.AppError) {
+ ret := _m.Called(teamId, userId)
+
+ var r0 *model.TeamMember
+ if rf, ok := ret.Get(0).(func(string, string) *model.TeamMember); ok {
+ r0 = rf(teamId, userId)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.TeamMember)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string, string) *model.AppError); ok {
+ r1 = rf(teamId, userId)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// GetTeamMembers provides a mock function with given fields: teamId, offset, limit
+func (_m *MockAPI) GetTeamMembers(teamId string, offset int, limit int) ([]*model.TeamMember, *model.AppError) {
+ ret := _m.Called(teamId, offset, limit)
+
+ var r0 []*model.TeamMember
+ if rf, ok := ret.Get(0).(func(string, int, int) []*model.TeamMember); ok {
+ r0 = rf(teamId, offset, limit)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).([]*model.TeamMember)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string, int, int) *model.AppError); ok {
+ r1 = rf(teamId, offset, limit)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// GetTeams provides a mock function with given fields:
+func (_m *MockAPI) GetTeams() ([]*model.Team, *model.AppError) {
+ ret := _m.Called()
+
+ var r0 []*model.Team
+ if rf, ok := ret.Get(0).(func() []*model.Team); ok {
+ r0 = rf()
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).([]*model.Team)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func() *model.AppError); ok {
+ r1 = rf()
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// GetUser provides a mock function with given fields: userId
+func (_m *MockAPI) GetUser(userId string) (*model.User, *model.AppError) {
+ ret := _m.Called(userId)
+
+ var r0 *model.User
+ if rf, ok := ret.Get(0).(func(string) *model.User); ok {
+ r0 = rf(userId)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.User)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string) *model.AppError); ok {
+ r1 = rf(userId)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// GetUserByEmail provides a mock function with given fields: email
+func (_m *MockAPI) GetUserByEmail(email string) (*model.User, *model.AppError) {
+ ret := _m.Called(email)
+
+ var r0 *model.User
+ if rf, ok := ret.Get(0).(func(string) *model.User); ok {
+ r0 = rf(email)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.User)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string) *model.AppError); ok {
+ r1 = rf(email)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// GetUserByUsername provides a mock function with given fields: name
+func (_m *MockAPI) GetUserByUsername(name string) (*model.User, *model.AppError) {
+ ret := _m.Called(name)
+
+ var r0 *model.User
+ if rf, ok := ret.Get(0).(func(string) *model.User); ok {
+ r0 = rf(name)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.User)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string) *model.AppError); ok {
+ r1 = rf(name)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// KVDelete provides a mock function with given fields: key
+func (_m *MockAPI) KVDelete(key string) *model.AppError {
+ ret := _m.Called(key)
+
+ var r0 *model.AppError
+ if rf, ok := ret.Get(0).(func(string) *model.AppError); ok {
+ r0 = rf(key)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.AppError)
+ }
+ }
+
+ return r0
+}
+
+// KVGet provides a mock function with given fields: key
+func (_m *MockAPI) KVGet(key string) ([]byte, *model.AppError) {
+ ret := _m.Called(key)
+
+ var r0 []byte
+ if rf, ok := ret.Get(0).(func(string) []byte); ok {
+ r0 = rf(key)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).([]byte)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string) *model.AppError); ok {
+ r1 = rf(key)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// KVSet provides a mock function with given fields: key, value
+func (_m *MockAPI) KVSet(key string, value []byte) *model.AppError {
+ ret := _m.Called(key, value)
+
+ var r0 *model.AppError
+ if rf, ok := ret.Get(0).(func(string, []byte) *model.AppError); ok {
+ r0 = rf(key, value)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.AppError)
+ }
+ }
+
+ return r0
+}
+
+// LoadPluginConfiguration provides a mock function with given fields: dest
+func (_m *MockAPI) LoadPluginConfiguration(dest interface{}) error {
+ ret := _m.Called(dest)
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(interface{}) error); ok {
+ r0 = rf(dest)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// LogDebug provides a mock function with given fields: msg, keyValuePairs
+func (_m *MockAPI) LogDebug(msg string, keyValuePairs ...interface{}) {
+ var _ca []interface{}
+ _ca = append(_ca, msg)
+ _ca = append(_ca, keyValuePairs...)
+ _m.Called(_ca...)
+}
+
+// LogError provides a mock function with given fields: msg, keyValuePairs
+func (_m *MockAPI) LogError(msg string, keyValuePairs ...interface{}) {
+ var _ca []interface{}
+ _ca = append(_ca, msg)
+ _ca = append(_ca, keyValuePairs...)
+ _m.Called(_ca...)
+}
+
+// LogInfo provides a mock function with given fields: msg, keyValuePairs
+func (_m *MockAPI) LogInfo(msg string, keyValuePairs ...interface{}) {
+ var _ca []interface{}
+ _ca = append(_ca, msg)
+ _ca = append(_ca, keyValuePairs...)
+ _m.Called(_ca...)
+}
+
+// LogWarn provides a mock function with given fields: msg, keyValuePairs
+func (_m *MockAPI) LogWarn(msg string, keyValuePairs ...interface{}) {
+ var _ca []interface{}
+ _ca = append(_ca, msg)
+ _ca = append(_ca, keyValuePairs...)
+ _m.Called(_ca...)
+}
+
+// PublishWebSocketEvent provides a mock function with given fields: event, payload, broadcast
+func (_m *MockAPI) PublishWebSocketEvent(event string, payload map[string]interface{}, broadcast *model.WebsocketBroadcast) {
+ _m.Called(event, payload, broadcast)
+}
+
+// RegisterCommand provides a mock function with given fields: command
+func (_m *MockAPI) RegisterCommand(command *model.Command) error {
+ ret := _m.Called(command)
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(*model.Command) error); ok {
+ r0 = rf(command)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// SaveConfig provides a mock function with given fields: config
+func (_m *MockAPI) SaveConfig(config *model.Config) *model.AppError {
+ ret := _m.Called(config)
+
+ var r0 *model.AppError
+ if rf, ok := ret.Get(0).(func(*model.Config) *model.AppError); ok {
+ r0 = rf(config)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.AppError)
+ }
+ }
+
+ return r0
+}
+
+// SendEphemeralPost provides a mock function with given fields: userId, post
+func (_m *MockAPI) SendEphemeralPost(userId string, post *model.Post) *model.Post {
+ ret := _m.Called(userId, post)
+
+ var r0 *model.Post
+ if rf, ok := ret.Get(0).(func(string, *model.Post) *model.Post); ok {
+ r0 = rf(userId, post)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.Post)
+ }
+ }
+
+ return r0
+}
+
+// UnregisterCommand provides a mock function with given fields: teamId, trigger
+func (_m *MockAPI) UnregisterCommand(teamId string, trigger string) error {
+ ret := _m.Called(teamId, trigger)
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(string, string) error); ok {
+ r0 = rf(teamId, trigger)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// UpdateChannel provides a mock function with given fields: channel
+func (_m *MockAPI) UpdateChannel(channel *model.Channel) (*model.Channel, *model.AppError) {
+ ret := _m.Called(channel)
+
+ var r0 *model.Channel
+ if rf, ok := ret.Get(0).(func(*model.Channel) *model.Channel); ok {
+ r0 = rf(channel)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.Channel)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(*model.Channel) *model.AppError); ok {
+ r1 = rf(channel)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// UpdateChannelMemberNotifications provides a mock function with given fields: channelId, userId, notifications
+func (_m *MockAPI) UpdateChannelMemberNotifications(channelId string, userId string, notifications map[string]string) (*model.ChannelMember, *model.AppError) {
+ ret := _m.Called(channelId, userId, notifications)
+
+ var r0 *model.ChannelMember
+ if rf, ok := ret.Get(0).(func(string, string, map[string]string) *model.ChannelMember); ok {
+ r0 = rf(channelId, userId, notifications)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.ChannelMember)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string, string, map[string]string) *model.AppError); ok {
+ r1 = rf(channelId, userId, notifications)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// UpdateChannelMemberRoles provides a mock function with given fields: channelId, userId, newRoles
+func (_m *MockAPI) UpdateChannelMemberRoles(channelId string, userId string, newRoles string) (*model.ChannelMember, *model.AppError) {
+ ret := _m.Called(channelId, userId, newRoles)
+
+ var r0 *model.ChannelMember
+ if rf, ok := ret.Get(0).(func(string, string, string) *model.ChannelMember); ok {
+ r0 = rf(channelId, userId, newRoles)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.ChannelMember)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string, string, string) *model.AppError); ok {
+ r1 = rf(channelId, userId, newRoles)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// UpdatePost provides a mock function with given fields: post
+func (_m *MockAPI) UpdatePost(post *model.Post) (*model.Post, *model.AppError) {
+ ret := _m.Called(post)
+
+ var r0 *model.Post
+ if rf, ok := ret.Get(0).(func(*model.Post) *model.Post); ok {
+ r0 = rf(post)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.Post)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(*model.Post) *model.AppError); ok {
+ r1 = rf(post)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// UpdateTeam provides a mock function with given fields: team
+func (_m *MockAPI) UpdateTeam(team *model.Team) (*model.Team, *model.AppError) {
+ ret := _m.Called(team)
+
+ var r0 *model.Team
+ if rf, ok := ret.Get(0).(func(*model.Team) *model.Team); ok {
+ r0 = rf(team)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.Team)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(*model.Team) *model.AppError); ok {
+ r1 = rf(team)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// UpdateTeamMemberRoles provides a mock function with given fields: teamId, userId, newRoles
+func (_m *MockAPI) UpdateTeamMemberRoles(teamId string, userId string, newRoles string) (*model.TeamMember, *model.AppError) {
+ ret := _m.Called(teamId, userId, newRoles)
+
+ var r0 *model.TeamMember
+ if rf, ok := ret.Get(0).(func(string, string, string) *model.TeamMember); ok {
+ r0 = rf(teamId, userId, newRoles)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.TeamMember)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string, string, string) *model.AppError); ok {
+ r1 = rf(teamId, userId, newRoles)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// UpdateUser provides a mock function with given fields: user
+func (_m *MockAPI) UpdateUser(user *model.User) (*model.User, *model.AppError) {
+ ret := _m.Called(user)
+
+ var r0 *model.User
+ if rf, ok := ret.Get(0).(func(*model.User) *model.User); ok {
+ r0 = rf(user)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.User)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(*model.User) *model.AppError); ok {
+ r1 = rf(user)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
diff --git a/plugin/plugin.go b/plugin/plugin.go
deleted file mode 100644
index 3150bf56a..000000000
--- a/plugin/plugin.go
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-// The plugin package defines the primary interfaces for interacting with a Mattermost server: the
-// API and the hook interfaces.
-//
-// The API interface is used to perform actions. The Hook interface is used to respond to actions.
-//
-// Plugins should define a type that implements some of the methods from the Hook interface, then
-// pass an instance of that object into the rpcplugin package's Main function (See the HelloWorld
-// example.).
-//
-// Testing
-//
-// To make testing plugins easier, you can use the plugintest package to create a mock API for your
-// plugin to interact with. See
-// https://godoc.org/github.com/mattermost/mattermost-server/plugin/plugintest
-package plugin
diff --git a/plugin/pluginenv/environment.go b/plugin/pluginenv/environment.go
deleted file mode 100644
index f704aa5bb..000000000
--- a/plugin/pluginenv/environment.go
+++ /dev/null
@@ -1,396 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-// Package pluginenv provides high level functionality for discovering and launching plugins.
-package pluginenv
-
-import (
- "fmt"
- "io/ioutil"
- "net/http"
- "path/filepath"
- "sync"
-
- "github.com/pkg/errors"
-
- "github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/plugin"
-)
-
-type APIProviderFunc func(*model.Manifest) (plugin.API, error)
-type SupervisorProviderFunc func(*model.BundleInfo) (plugin.Supervisor, error)
-
-type ActivePlugin struct {
- BundleInfo *model.BundleInfo
- Supervisor plugin.Supervisor
-}
-
-// Environment represents an environment that plugins are discovered and launched in.
-type Environment struct {
- searchPath string
- webappPath string
- apiProvider APIProviderFunc
- supervisorProvider SupervisorProviderFunc
- activePlugins map[string]ActivePlugin
- mutex sync.RWMutex
-}
-
-type Option func(*Environment)
-
-// Creates a new environment. At a minimum, the APIProvider and SearchPath options are required.
-func New(options ...Option) (*Environment, error) {
- env := &Environment{
- activePlugins: make(map[string]ActivePlugin),
- }
- for _, opt := range options {
- opt(env)
- }
- if env.supervisorProvider == nil {
- env.supervisorProvider = DefaultSupervisorProvider
- }
- if env.searchPath == "" {
- return nil, fmt.Errorf("a search path must be provided")
- }
- return env, nil
-}
-
-// Returns the configured webapp path.
-func (env *Environment) WebappPath() string {
- return env.webappPath
-}
-
-// Returns the configured search path.
-func (env *Environment) SearchPath() string {
- return env.searchPath
-}
-
-// Returns a list of all plugins found within the environment.
-func (env *Environment) Plugins() ([]*model.BundleInfo, error) {
- return ScanSearchPath(env.searchPath)
-}
-
-// Returns a list of all currently active plugins within the environment.
-func (env *Environment) ActivePlugins() []*model.BundleInfo {
- env.mutex.RLock()
- defer env.mutex.RUnlock()
-
- activePlugins := []*model.BundleInfo{}
- for _, p := range env.activePlugins {
- activePlugins = append(activePlugins, p.BundleInfo)
- }
-
- return activePlugins
-}
-
-// Returns the ids of the currently active plugins.
-func (env *Environment) ActivePluginIds() (ids []string) {
- env.mutex.RLock()
- defer env.mutex.RUnlock()
-
- for id := range env.activePlugins {
- ids = append(ids, id)
- }
- return
-}
-
-// Returns true if the plugin is active, false otherwise.
-func (env *Environment) IsPluginActive(pluginId string) bool {
- env.mutex.RLock()
- defer env.mutex.RUnlock()
-
- for id := range env.activePlugins {
- if id == pluginId {
- return true
- }
- }
-
- return false
-}
-
-// Activates the plugin with the given id.
-func (env *Environment) ActivatePlugin(id string, onError func(error)) error {
- env.mutex.Lock()
- defer env.mutex.Unlock()
-
- if !plugin.IsValidId(id) {
- return fmt.Errorf("invalid plugin id: %s", id)
- }
-
- if _, ok := env.activePlugins[id]; ok {
- return fmt.Errorf("plugin already active: %v", id)
- }
- plugins, err := ScanSearchPath(env.searchPath)
- if err != nil {
- return err
- }
- var bundle *model.BundleInfo
- for _, p := range plugins {
- if p.Manifest != nil && p.Manifest.Id == id {
- if bundle != nil {
- return fmt.Errorf("multiple plugins found: %v", id)
- }
- bundle = p
- }
- }
- if bundle == nil {
- return fmt.Errorf("plugin not found: %v", id)
- }
-
- activePlugin := ActivePlugin{BundleInfo: bundle}
-
- var supervisor plugin.Supervisor
-
- if bundle.Manifest.Backend != nil {
- if env.apiProvider == nil {
- return fmt.Errorf("env missing api provider, cannot activate plugin: %v", id)
- }
-
- supervisor, err = env.supervisorProvider(bundle)
- if err != nil {
- return errors.Wrapf(err, "unable to create supervisor for plugin: %v", id)
- }
- api, err := env.apiProvider(bundle.Manifest)
- if err != nil {
- return errors.Wrapf(err, "unable to get api for plugin: %v", id)
- }
- if err := supervisor.Start(api); err != nil {
- return errors.Wrapf(err, "unable to start plugin: %v", id)
- }
- if onError != nil {
- go func() {
- err := supervisor.Wait()
- if err != nil {
- onError(err)
- }
- }()
- }
-
- activePlugin.Supervisor = supervisor
- }
-
- if bundle.Manifest.Webapp != nil {
- if env.webappPath == "" {
- if supervisor != nil {
- supervisor.Stop()
- }
- return fmt.Errorf("env missing webapp path, cannot activate plugin: %v", id)
- }
-
- bundlePath := filepath.Clean(bundle.Manifest.Webapp.BundlePath)
- if bundlePath == "" || bundlePath[0] == '.' {
- return fmt.Errorf("invalid webapp bundle path")
- }
- bundlePath = filepath.Join(env.searchPath, id, bundlePath)
-
- webappBundle, err := ioutil.ReadFile(bundlePath)
- if err != nil {
- // Backwards compatibility for plugins where webapp.bundle_path was ignored. This should
- // be removed eventually.
- if webappBundle2, err2 := ioutil.ReadFile(fmt.Sprintf("%s/%s/webapp/%s_bundle.js", env.searchPath, id, id)); err2 == nil {
- webappBundle = webappBundle2
- } else {
- if supervisor != nil {
- supervisor.Stop()
- }
- return errors.Wrapf(err, "unable to read webapp bundle: %v", id)
- }
- }
-
- err = ioutil.WriteFile(fmt.Sprintf("%s/%s_bundle.js", env.webappPath, id), webappBundle, 0644)
- if err != nil {
- if supervisor != nil {
- supervisor.Stop()
- }
- return errors.Wrapf(err, "unable to write webapp bundle: %v", id)
- }
- }
-
- env.activePlugins[id] = activePlugin
- return nil
-}
-
-// Deactivates the plugin with the given id.
-func (env *Environment) DeactivatePlugin(id string) error {
- env.mutex.Lock()
- defer env.mutex.Unlock()
-
- if activePlugin, ok := env.activePlugins[id]; !ok {
- return fmt.Errorf("plugin not active: %v", id)
- } else {
- delete(env.activePlugins, id)
- var err error
- if activePlugin.Supervisor != nil {
- err = activePlugin.Supervisor.Hooks().OnDeactivate()
- if serr := activePlugin.Supervisor.Stop(); err == nil {
- err = serr
- }
- }
- return err
- }
-}
-
-// Deactivates all plugins and gracefully shuts down the environment.
-func (env *Environment) Shutdown() (errs []error) {
- env.mutex.Lock()
- defer env.mutex.Unlock()
-
- for _, activePlugin := range env.activePlugins {
- if activePlugin.Supervisor != nil {
- if err := activePlugin.Supervisor.Hooks().OnDeactivate(); err != nil {
- errs = append(errs, errors.Wrapf(err, "OnDeactivate() error for %v", activePlugin.BundleInfo.Manifest.Id))
- }
- if err := activePlugin.Supervisor.Stop(); err != nil {
- errs = append(errs, errors.Wrapf(err, "error stopping supervisor for %v", activePlugin.BundleInfo.Manifest.Id))
- }
- }
- }
- env.activePlugins = make(map[string]ActivePlugin)
- return
-}
-
-type MultiPluginHooks struct {
- env *Environment
-}
-
-type SinglePluginHooks struct {
- env *Environment
- pluginId string
-}
-
-func (env *Environment) Hooks() *MultiPluginHooks {
- return &MultiPluginHooks{
- env: env,
- }
-}
-
-func (env *Environment) HooksForPlugin(id string) *SinglePluginHooks {
- return &SinglePluginHooks{
- env: env,
- pluginId: id,
- }
-}
-
-func (h *MultiPluginHooks) invoke(f func(plugin.Hooks) error) (errs []error) {
- h.env.mutex.RLock()
- defer h.env.mutex.RUnlock()
-
- for _, activePlugin := range h.env.activePlugins {
- if activePlugin.Supervisor == nil {
- continue
- }
- if err := f(activePlugin.Supervisor.Hooks()); err != nil {
- errs = append(errs, errors.Wrapf(err, "hook error for %v", activePlugin.BundleInfo.Manifest.Id))
- }
- }
- return
-}
-
-// OnConfigurationChange invokes the OnConfigurationChange hook for all plugins. Any errors
-// encountered will be returned.
-func (h *MultiPluginHooks) OnConfigurationChange() []error {
- return h.invoke(func(hooks plugin.Hooks) error {
- if err := hooks.OnConfigurationChange(); err != nil {
- return errors.Wrapf(err, "error calling OnConfigurationChange hook")
- }
- return nil
- })
-}
-
-// ServeHTTP invokes the ServeHTTP hook for the plugin identified by the request or responds with a
-// 404 not found.
-//
-// It expects the request's context to have a plugin_id set.
-func (h *MultiPluginHooks) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- if id := r.Context().Value("plugin_id"); id != nil {
- if idstr, ok := id.(string); ok {
- h.env.mutex.RLock()
- defer h.env.mutex.RUnlock()
- if plugin, ok := h.env.activePlugins[idstr]; ok && plugin.Supervisor != nil {
- plugin.Supervisor.Hooks().ServeHTTP(w, r)
- return
- }
- }
- }
- http.NotFound(w, r)
-}
-
-// MessageWillBePosted invokes the MessageWillBePosted hook for all plugins. Ordering
-// is not guaranteed and the next plugin will get the previous one's modifications.
-// if a plugin rejects a post, the rest of the plugins will not know that an attempt was made.
-// Returns the final result post, or nil if the post was rejected and a string with a reason
-// for the user the message was rejected.
-func (h *MultiPluginHooks) MessageWillBePosted(post *model.Post) (*model.Post, string) {
- h.env.mutex.RLock()
- defer h.env.mutex.RUnlock()
-
- for _, activePlugin := range h.env.activePlugins {
- if activePlugin.Supervisor == nil {
- continue
- }
- var rejectionReason string
- post, rejectionReason = activePlugin.Supervisor.Hooks().MessageWillBePosted(post)
- if post == nil {
- return nil, rejectionReason
- }
- }
- return post, ""
-}
-
-// MessageWillBeUpdated invokes the MessageWillBeUpdated hook for all plugins. Ordering
-// is not guaranteed and the next plugin will get the previous one's modifications.
-// if a plugin rejects a post, the rest of the plugins will not know that an attempt was made.
-// Returns the final result post, or nil if the post was rejected and a string with a reason
-// for the user the message was rejected.
-func (h *MultiPluginHooks) MessageWillBeUpdated(newPost, oldPost *model.Post) (*model.Post, string) {
- h.env.mutex.RLock()
- defer h.env.mutex.RUnlock()
-
- post := newPost
- for _, activePlugin := range h.env.activePlugins {
- if activePlugin.Supervisor == nil {
- continue
- }
- var rejectionReason string
- post, rejectionReason = activePlugin.Supervisor.Hooks().MessageWillBeUpdated(post, oldPost)
- if post == nil {
- return nil, rejectionReason
- }
- }
- return post, ""
-}
-
-func (h *MultiPluginHooks) MessageHasBeenPosted(post *model.Post) {
- h.invoke(func(hooks plugin.Hooks) error {
- hooks.MessageHasBeenPosted(post)
- return nil
- })
-}
-
-func (h *MultiPluginHooks) MessageHasBeenUpdated(newPost, oldPost *model.Post) {
- h.invoke(func(hooks plugin.Hooks) error {
- hooks.MessageHasBeenUpdated(newPost, oldPost)
- return nil
- })
-}
-
-func (h *SinglePluginHooks) invoke(f func(plugin.Hooks) error) error {
- h.env.mutex.RLock()
- defer h.env.mutex.RUnlock()
-
- if activePlugin, ok := h.env.activePlugins[h.pluginId]; ok && activePlugin.Supervisor != nil {
- if err := f(activePlugin.Supervisor.Hooks()); err != nil {
- return errors.Wrapf(err, "hook error for plugin: %v", activePlugin.BundleInfo.Manifest.Id)
- }
- return nil
- }
- return fmt.Errorf("unable to invoke hook for plugin: %v", h.pluginId)
-}
-
-// ExecuteCommand invokes the ExecuteCommand hook for the plugin.
-func (h *SinglePluginHooks) ExecuteCommand(args *model.CommandArgs) (resp *model.CommandResponse, appErr *model.AppError, err error) {
- err = h.invoke(func(hooks plugin.Hooks) error {
- resp, appErr = hooks.ExecuteCommand(args)
- return nil
- })
- return
-}
diff --git a/plugin/pluginenv/environment_test.go b/plugin/pluginenv/environment_test.go
deleted file mode 100644
index 8c1397799..000000000
--- a/plugin/pluginenv/environment_test.go
+++ /dev/null
@@ -1,409 +0,0 @@
-package pluginenv
-
-import (
- "context"
- "fmt"
- "io/ioutil"
- "net/http"
- "net/http/httptest"
- "os"
- "path/filepath"
- "sync"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
- "github.com/stretchr/testify/require"
-
- "github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/plugin"
- "github.com/mattermost/mattermost-server/plugin/plugintest"
-)
-
-type MockProvider struct {
- mock.Mock
-}
-
-func (m *MockProvider) API(manifest *model.Manifest) (plugin.API, error) {
- ret := m.Called()
- if ret.Get(0) == nil {
- return nil, ret.Error(1)
- }
- return ret.Get(0).(plugin.API), ret.Error(1)
-}
-
-func (m *MockProvider) Supervisor(bundle *model.BundleInfo) (plugin.Supervisor, error) {
- ret := m.Called()
- if ret.Get(0) == nil {
- return nil, ret.Error(1)
- }
- return ret.Get(0).(plugin.Supervisor), ret.Error(1)
-}
-
-type MockSupervisor struct {
- mock.Mock
-}
-
-func (m *MockSupervisor) Start(api plugin.API) error {
- return m.Called(api).Error(0)
-}
-
-func (m *MockSupervisor) Stop() error {
- return m.Called().Error(0)
-}
-
-func (m *MockSupervisor) Hooks() plugin.Hooks {
- return m.Called().Get(0).(plugin.Hooks)
-}
-
-func (m *MockSupervisor) Wait() error {
- return m.Called().Get(0).(error)
-}
-
-func initTmpDir(t *testing.T, files map[string]string) string {
- success := false
- dir, err := ioutil.TempDir("", "mm-plugin-test")
- require.NoError(t, err)
- defer func() {
- if !success {
- os.RemoveAll(dir)
- }
- }()
-
- for name, contents := range files {
- path := filepath.Join(dir, name)
- parent := filepath.Dir(path)
- require.NoError(t, os.MkdirAll(parent, 0700))
- f, err := os.Create(path)
- require.NoError(t, err)
- _, err = f.WriteString(contents)
- f.Close()
- require.NoError(t, err)
- }
-
- success = true
- return dir
-}
-
-func TestNew_MissingOptions(t *testing.T) {
- dir := initTmpDir(t, map[string]string{
- "foo/plugin.json": `{"id": "foo"}`,
- })
- defer os.RemoveAll(dir)
-
- var provider MockProvider
- defer provider.AssertExpectations(t)
-
- env, err := New(
- APIProvider(provider.API),
- )
- assert.Nil(t, env)
- assert.Error(t, err)
-}
-
-func TestEnvironment(t *testing.T) {
- dir := initTmpDir(t, map[string]string{
- ".foo/plugin.json": `{"id": "foo"}`,
- "foo/bar": "asdf",
- "foo/plugin.json": `{"id": "foo", "backend": {}}`,
- "bar/zxc": "qwer",
- "baz/plugin.yaml": "id: baz",
- "bad/plugin.json": "asd",
- "qwe": "asd",
- })
- defer os.RemoveAll(dir)
-
- webappDir := "notarealdirectory"
-
- var provider MockProvider
- defer provider.AssertExpectations(t)
-
- env, err := New(
- SearchPath(dir),
- WebappPath(webappDir),
- APIProvider(provider.API),
- SupervisorProvider(provider.Supervisor),
- )
- require.NoError(t, err)
- defer env.Shutdown()
-
- plugins, err := env.Plugins()
- assert.NoError(t, err)
- assert.Len(t, plugins, 3)
-
- activePlugins := env.ActivePlugins()
- assert.Len(t, activePlugins, 0)
-
- assert.Error(t, env.ActivatePlugin("x", nil))
-
- var api struct{ plugin.API }
- var supervisor MockSupervisor
- defer supervisor.AssertExpectations(t)
- var hooks plugintest.Hooks
- defer hooks.AssertExpectations(t)
-
- provider.On("API").Return(&api, nil)
- provider.On("Supervisor").Return(&supervisor, nil)
-
- supervisor.On("Start", &api).Return(nil)
- supervisor.On("Stop").Return(nil)
- supervisor.On("Hooks").Return(&hooks)
-
- assert.NoError(t, env.ActivatePlugin("foo", nil))
- assert.Equal(t, env.ActivePluginIds(), []string{"foo"})
- activePlugins = env.ActivePlugins()
- assert.Len(t, activePlugins, 1)
- assert.Error(t, env.ActivatePlugin("foo", nil))
- assert.True(t, env.IsPluginActive("foo"))
-
- hooks.On("OnDeactivate").Return(nil)
- assert.NoError(t, env.DeactivatePlugin("foo"))
- assert.Error(t, env.DeactivatePlugin("foo"))
- assert.False(t, env.IsPluginActive("foo"))
-
- assert.NoError(t, env.ActivatePlugin("foo", nil))
- assert.Equal(t, env.ActivePluginIds(), []string{"foo"})
-
- assert.Equal(t, env.SearchPath(), dir)
- assert.Equal(t, env.WebappPath(), webappDir)
-
- assert.Empty(t, env.Shutdown())
-}
-
-func TestEnvironment_DuplicatePluginError(t *testing.T) {
- dir := initTmpDir(t, map[string]string{
- "foo/plugin.json": `{"id": "foo"}`,
- "foo2/plugin.json": `{"id": "foo"}`,
- })
- defer os.RemoveAll(dir)
-
- var provider MockProvider
- defer provider.AssertExpectations(t)
-
- env, err := New(
- SearchPath(dir),
- APIProvider(provider.API),
- SupervisorProvider(provider.Supervisor),
- )
- require.NoError(t, err)
- defer env.Shutdown()
-
- assert.Error(t, env.ActivatePlugin("foo", nil))
- assert.Empty(t, env.ActivePluginIds())
-}
-
-func TestEnvironment_BadSearchPathError(t *testing.T) {
- var provider MockProvider
- defer provider.AssertExpectations(t)
-
- env, err := New(
- SearchPath("thissearchpathshouldnotexist!"),
- APIProvider(provider.API),
- SupervisorProvider(provider.Supervisor),
- )
- require.NoError(t, err)
- defer env.Shutdown()
-
- assert.Error(t, env.ActivatePlugin("foo", nil))
- assert.Empty(t, env.ActivePluginIds())
-}
-
-func TestEnvironment_ActivatePluginErrors(t *testing.T) {
- dir := initTmpDir(t, map[string]string{
- "foo/plugin.json": `{"id": "foo", "backend": {}}`,
- })
- defer os.RemoveAll(dir)
-
- var provider MockProvider
-
- env, err := New(
- SearchPath(dir),
- APIProvider(provider.API),
- SupervisorProvider(provider.Supervisor),
- )
- require.NoError(t, err)
- defer env.Shutdown()
-
- var api struct{ plugin.API }
- var supervisor MockSupervisor
- var hooks plugintest.Hooks
-
- for name, setup := range map[string]func(){
- "SupervisorProviderError": func() {
- provider.On("Supervisor").Return(nil, fmt.Errorf("test error"))
- },
- "APIProviderError": func() {
- provider.On("API").Return(plugin.API(nil), fmt.Errorf("test error"))
- provider.On("Supervisor").Return(&supervisor, nil)
- },
- "SupervisorError": func() {
- provider.On("API").Return(&api, nil)
- provider.On("Supervisor").Return(&supervisor, nil)
-
- supervisor.On("Start", &api).Return(fmt.Errorf("test error"))
- },
- } {
- t.Run(name, func(t *testing.T) {
- supervisor.Mock = mock.Mock{}
- hooks.Mock = mock.Mock{}
- provider.Mock = mock.Mock{}
- setup()
- assert.Error(t, env.ActivatePlugin("foo", nil))
- assert.Empty(t, env.ActivePluginIds())
- supervisor.AssertExpectations(t)
- hooks.AssertExpectations(t)
- provider.AssertExpectations(t)
- })
- }
-}
-
-func TestEnvironment_ShutdownError(t *testing.T) {
- dir := initTmpDir(t, map[string]string{
- "foo/plugin.json": `{"id": "foo", "backend": {}}`,
- })
- defer os.RemoveAll(dir)
-
- var provider MockProvider
- defer provider.AssertExpectations(t)
-
- env, err := New(
- SearchPath(dir),
- APIProvider(provider.API),
- SupervisorProvider(provider.Supervisor),
- )
- require.NoError(t, err)
- defer env.Shutdown()
-
- var api struct{ plugin.API }
- var supervisor MockSupervisor
- defer supervisor.AssertExpectations(t)
- var hooks plugintest.Hooks
- defer hooks.AssertExpectations(t)
-
- provider.On("API").Return(&api, nil)
- provider.On("Supervisor").Return(&supervisor, nil)
-
- supervisor.On("Start", &api).Return(nil)
- supervisor.On("Stop").Return(fmt.Errorf("test error"))
- supervisor.On("Hooks").Return(&hooks)
-
- hooks.On("OnDeactivate").Return(fmt.Errorf("test error"))
-
- assert.NoError(t, env.ActivatePlugin("foo", nil))
- assert.Equal(t, env.ActivePluginIds(), []string{"foo"})
- assert.Len(t, env.Shutdown(), 2)
-}
-
-func TestEnvironment_ConcurrentHookInvocations(t *testing.T) {
- dir := initTmpDir(t, map[string]string{
- "foo/plugin.json": `{"id": "foo", "backend": {}}`,
- })
- defer os.RemoveAll(dir)
-
- var provider MockProvider
- defer provider.AssertExpectations(t)
-
- var api struct{ plugin.API }
- var supervisor MockSupervisor
- defer supervisor.AssertExpectations(t)
- var hooks plugintest.Hooks
- defer hooks.AssertExpectations(t)
-
- env, err := New(
- SearchPath(dir),
- APIProvider(provider.API),
- SupervisorProvider(provider.Supervisor),
- )
- require.NoError(t, err)
- defer env.Shutdown()
-
- provider.On("API").Return(&api, nil)
- provider.On("Supervisor").Return(&supervisor, nil)
-
- supervisor.On("Start", &api).Return(nil)
- supervisor.On("Stop").Return(nil)
- supervisor.On("Hooks").Return(&hooks)
-
- ch := make(chan bool)
-
- hooks.On("OnDeactivate").Return(nil)
- hooks.On("ServeHTTP", mock.AnythingOfType("*httptest.ResponseRecorder"), mock.AnythingOfType("*http.Request")).Run(func(args mock.Arguments) {
- r := args.Get(1).(*http.Request)
- if r.URL.Path == "/1" {
- <-ch
- } else {
- ch <- true
- }
- })
-
- assert.NoError(t, env.ActivatePlugin("foo", nil))
-
- rec := httptest.NewRecorder()
-
- wg := sync.WaitGroup{}
- wg.Add(2)
-
- go func() {
- req, err := http.NewRequest("GET", "/1", nil)
- require.NoError(t, err)
- env.Hooks().ServeHTTP(rec, req.WithContext(context.WithValue(context.Background(), "plugin_id", "foo")))
- wg.Done()
- }()
-
- go func() {
- req, err := http.NewRequest("GET", "/2", nil)
- require.NoError(t, err)
- env.Hooks().ServeHTTP(rec, req.WithContext(context.WithValue(context.Background(), "plugin_id", "foo")))
- wg.Done()
- }()
-
- wg.Wait()
-}
-
-func TestEnvironment_HooksForPlugins(t *testing.T) {
- dir := initTmpDir(t, map[string]string{
- "foo/plugin.json": `{"id": "foo", "backend": {}}`,
- })
- defer os.RemoveAll(dir)
-
- var provider MockProvider
- defer provider.AssertExpectations(t)
-
- env, err := New(
- SearchPath(dir),
- APIProvider(provider.API),
- SupervisorProvider(provider.Supervisor),
- )
- require.NoError(t, err)
- defer env.Shutdown()
-
- var api struct{ plugin.API }
- var supervisor MockSupervisor
- defer supervisor.AssertExpectations(t)
- var hooks plugintest.Hooks
- defer hooks.AssertExpectations(t)
-
- provider.On("API").Return(&api, nil)
- provider.On("Supervisor").Return(&supervisor, nil)
-
- supervisor.On("Start", &api).Return(nil)
- supervisor.On("Stop").Return(nil)
- supervisor.On("Hooks").Return(&hooks)
-
- hooks.On("OnDeactivate").Return(nil)
- hooks.On("ExecuteCommand", mock.AnythingOfType("*model.CommandArgs")).Return(&model.CommandResponse{
- Text: "bar",
- }, nil)
-
- assert.NoError(t, env.ActivatePlugin("foo", nil))
- assert.Equal(t, env.ActivePluginIds(), []string{"foo"})
-
- resp, appErr, err := env.HooksForPlugin("foo").ExecuteCommand(&model.CommandArgs{
- Command: "/foo",
- })
- assert.Equal(t, "bar", resp.Text)
- assert.Nil(t, appErr)
- assert.NoError(t, err)
-
- assert.Empty(t, env.Shutdown())
-}
diff --git a/plugin/pluginenv/options.go b/plugin/pluginenv/options.go
deleted file mode 100644
index 43cbdac68..000000000
--- a/plugin/pluginenv/options.go
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package pluginenv
-
-import (
- "github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/plugin"
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
- "github.com/mattermost/mattermost-server/plugin/rpcplugin/sandbox"
-)
-
-// APIProvider specifies a function that provides an API implementation to each plugin.
-func APIProvider(provider APIProviderFunc) Option {
- return func(env *Environment) {
- env.apiProvider = provider
- }
-}
-
-// SupervisorProvider specifies a function that provides a Supervisor implementation to each plugin.
-// If unspecified, DefaultSupervisorProvider is used.
-func SupervisorProvider(provider SupervisorProviderFunc) Option {
- return func(env *Environment) {
- env.supervisorProvider = provider
- }
-}
-
-// SearchPath specifies a directory that contains the plugins to launch.
-func SearchPath(path string) Option {
- return func(env *Environment) {
- env.searchPath = path
- }
-}
-
-// WebappPath specifies the static directory serving the webapp.
-func WebappPath(path string) Option {
- return func(env *Environment) {
- env.webappPath = path
- }
-}
-
-// DefaultSupervisorProvider chooses a supervisor based on the system and the plugin's manifest
-// contents. E.g. if the manifest specifies a backend executable, it will be given an
-// rpcplugin.Supervisor.
-func DefaultSupervisorProvider(bundle *model.BundleInfo) (plugin.Supervisor, error) {
- if err := sandbox.CheckSupport(); err == nil {
- return sandbox.SupervisorProvider(bundle)
- }
- return rpcplugin.SupervisorProvider(bundle)
-}
diff --git a/plugin/pluginenv/options_test.go b/plugin/pluginenv/options_test.go
deleted file mode 100644
index 01fa206d0..000000000
--- a/plugin/pluginenv/options_test.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package pluginenv
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
-)
-
-func TestDefaultSupervisorProvider(t *testing.T) {
- _, err := DefaultSupervisorProvider(&model.BundleInfo{})
- assert.Error(t, err)
-
- _, err = DefaultSupervisorProvider(&model.BundleInfo{
- Manifest: &model.Manifest{},
- })
- assert.Error(t, err)
-
- supervisor, err := DefaultSupervisorProvider(&model.BundleInfo{
- Manifest: &model.Manifest{
- Backend: &model.ManifestBackend{
- Executable: "foo",
- },
- },
- })
- require.NoError(t, err)
- _, ok := supervisor.(*rpcplugin.Supervisor)
- assert.True(t, ok)
-}
diff --git a/plugin/pluginenv/search_path.go b/plugin/pluginenv/search_path.go
deleted file mode 100644
index 698424332..000000000
--- a/plugin/pluginenv/search_path.go
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package pluginenv
-
-import (
- "io/ioutil"
- "path/filepath"
-
- "github.com/mattermost/mattermost-server/model"
-)
-
-// Performs a full scan of the given path.
-//
-// This function will return info for all subdirectories that appear to be plugins (i.e. all
-// subdirectories containing plugin manifest files, regardless of whether they could actually be
-// parsed).
-//
-// Plugins are found non-recursively and paths beginning with a dot are always ignored.
-func ScanSearchPath(path string) ([]*model.BundleInfo, error) {
- files, err := ioutil.ReadDir(path)
- if err != nil {
- return nil, err
- }
- var ret []*model.BundleInfo
- for _, file := range files {
- if !file.IsDir() || file.Name()[0] == '.' {
- continue
- }
- if info := model.BundleInfoForPath(filepath.Join(path, file.Name())); info.ManifestPath != "" {
- ret = append(ret, info)
- }
- }
- return ret, nil
-}
diff --git a/plugin/pluginenv/search_path_test.go b/plugin/pluginenv/search_path_test.go
deleted file mode 100644
index dd9ff9a6b..000000000
--- a/plugin/pluginenv/search_path_test.go
+++ /dev/null
@@ -1,62 +0,0 @@
-package pluginenv
-
-import (
- "encoding/json"
- "os"
- "path/filepath"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/mattermost/mattermost-server/model"
-)
-
-func TestScanSearchPath(t *testing.T) {
- dir := initTmpDir(t, map[string]string{
- ".foo/plugin.json": `{"id": "foo"}`,
- "foo/bar": "asdf",
- "foo/plugin.json": `{"id": "foo"}`,
- "bar/zxc": "qwer",
- "baz/plugin.yaml": "id: baz",
- "bad/plugin.json": "asd",
- "qwe": "asd",
- })
- defer os.RemoveAll(dir)
-
- plugins, err := ScanSearchPath(dir)
- require.NoError(t, err)
- assert.Len(t, plugins, 3)
- assert.Contains(t, plugins, &model.BundleInfo{
- Path: filepath.Join(dir, "foo"),
- ManifestPath: filepath.Join(dir, "foo", "plugin.json"),
- Manifest: &model.Manifest{
- Id: "foo",
- },
- })
- assert.Contains(t, plugins, &model.BundleInfo{
- Path: filepath.Join(dir, "baz"),
- ManifestPath: filepath.Join(dir, "baz", "plugin.yaml"),
- Manifest: &model.Manifest{
- Id: "baz",
- },
- })
- foundError := false
- for _, x := range plugins {
- if x.ManifestError != nil {
- assert.Equal(t, x.Path, filepath.Join(dir, "bad"))
- assert.Equal(t, x.ManifestPath, filepath.Join(dir, "bad", "plugin.json"))
- syntexError, ok := x.ManifestError.(*json.SyntaxError)
- assert.True(t, ok)
- assert.EqualValues(t, 1, syntexError.Offset)
- foundError = true
- }
- }
- assert.True(t, foundError)
-}
-
-func TestScanSearchPath_Error(t *testing.T) {
- plugins, err := ScanSearchPath("not a valid path!")
- assert.Nil(t, plugins)
- assert.Error(t, err)
-}
diff --git a/plugin/plugintest/api.go b/plugin/plugintest/api.go
index f1281a5ff..3ce1d0145 100644
--- a/plugin/plugintest/api.go
+++ b/plugin/plugintest/api.go
@@ -6,15 +6,14 @@ package plugintest
import mock "github.com/stretchr/testify/mock"
import model "github.com/mattermost/mattermost-server/model"
-import plugin "github.com/mattermost/mattermost-server/plugin"
-// APIMOCKINTERNAL is an autogenerated mock type for the APIMOCKINTERNAL type
-type APIMOCKINTERNAL struct {
+// API is an autogenerated mock type for the API type
+type API struct {
mock.Mock
}
// AddChannelMember provides a mock function with given fields: channelId, userId
-func (_m *APIMOCKINTERNAL) AddChannelMember(channelId string, userId string) (*model.ChannelMember, *model.AppError) {
+func (_m *API) AddChannelMember(channelId string, userId string) (*model.ChannelMember, *model.AppError) {
ret := _m.Called(channelId, userId)
var r0 *model.ChannelMember
@@ -39,7 +38,7 @@ func (_m *APIMOCKINTERNAL) AddChannelMember(channelId string, userId string) (*m
}
// CreateChannel provides a mock function with given fields: channel
-func (_m *APIMOCKINTERNAL) CreateChannel(channel *model.Channel) (*model.Channel, *model.AppError) {
+func (_m *API) CreateChannel(channel *model.Channel) (*model.Channel, *model.AppError) {
ret := _m.Called(channel)
var r0 *model.Channel
@@ -64,7 +63,7 @@ func (_m *APIMOCKINTERNAL) CreateChannel(channel *model.Channel) (*model.Channel
}
// CreatePost provides a mock function with given fields: post
-func (_m *APIMOCKINTERNAL) CreatePost(post *model.Post) (*model.Post, *model.AppError) {
+func (_m *API) CreatePost(post *model.Post) (*model.Post, *model.AppError) {
ret := _m.Called(post)
var r0 *model.Post
@@ -89,7 +88,7 @@ func (_m *APIMOCKINTERNAL) CreatePost(post *model.Post) (*model.Post, *model.App
}
// CreateTeam provides a mock function with given fields: team
-func (_m *APIMOCKINTERNAL) CreateTeam(team *model.Team) (*model.Team, *model.AppError) {
+func (_m *API) CreateTeam(team *model.Team) (*model.Team, *model.AppError) {
ret := _m.Called(team)
var r0 *model.Team
@@ -113,8 +112,58 @@ func (_m *APIMOCKINTERNAL) CreateTeam(team *model.Team) (*model.Team, *model.App
return r0, r1
}
+// CreateTeamMember provides a mock function with given fields: teamId, userId
+func (_m *API) CreateTeamMember(teamId string, userId string) (*model.TeamMember, *model.AppError) {
+ ret := _m.Called(teamId, userId)
+
+ var r0 *model.TeamMember
+ if rf, ok := ret.Get(0).(func(string, string) *model.TeamMember); ok {
+ r0 = rf(teamId, userId)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.TeamMember)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string, string) *model.AppError); ok {
+ r1 = rf(teamId, userId)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// CreateTeamMembers provides a mock function with given fields: teamId, userIds, requestorId
+func (_m *API) CreateTeamMembers(teamId string, userIds []string, requestorId string) ([]*model.TeamMember, *model.AppError) {
+ ret := _m.Called(teamId, userIds, requestorId)
+
+ var r0 []*model.TeamMember
+ if rf, ok := ret.Get(0).(func(string, []string, string) []*model.TeamMember); ok {
+ r0 = rf(teamId, userIds, requestorId)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).([]*model.TeamMember)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string, []string, string) *model.AppError); ok {
+ r1 = rf(teamId, userIds, requestorId)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
// CreateUser provides a mock function with given fields: user
-func (_m *APIMOCKINTERNAL) CreateUser(user *model.User) (*model.User, *model.AppError) {
+func (_m *API) CreateUser(user *model.User) (*model.User, *model.AppError) {
ret := _m.Called(user)
var r0 *model.User
@@ -139,7 +188,7 @@ func (_m *APIMOCKINTERNAL) CreateUser(user *model.User) (*model.User, *model.App
}
// DeleteChannel provides a mock function with given fields: channelId
-func (_m *APIMOCKINTERNAL) DeleteChannel(channelId string) *model.AppError {
+func (_m *API) DeleteChannel(channelId string) *model.AppError {
ret := _m.Called(channelId)
var r0 *model.AppError
@@ -155,7 +204,7 @@ func (_m *APIMOCKINTERNAL) DeleteChannel(channelId string) *model.AppError {
}
// DeleteChannelMember provides a mock function with given fields: channelId, userId
-func (_m *APIMOCKINTERNAL) DeleteChannelMember(channelId string, userId string) *model.AppError {
+func (_m *API) DeleteChannelMember(channelId string, userId string) *model.AppError {
ret := _m.Called(channelId, userId)
var r0 *model.AppError
@@ -171,7 +220,7 @@ func (_m *APIMOCKINTERNAL) DeleteChannelMember(channelId string, userId string)
}
// DeletePost provides a mock function with given fields: postId
-func (_m *APIMOCKINTERNAL) DeletePost(postId string) *model.AppError {
+func (_m *API) DeletePost(postId string) *model.AppError {
ret := _m.Called(postId)
var r0 *model.AppError
@@ -187,7 +236,7 @@ func (_m *APIMOCKINTERNAL) DeletePost(postId string) *model.AppError {
}
// DeleteTeam provides a mock function with given fields: teamId
-func (_m *APIMOCKINTERNAL) DeleteTeam(teamId string) *model.AppError {
+func (_m *API) DeleteTeam(teamId string) *model.AppError {
ret := _m.Called(teamId)
var r0 *model.AppError
@@ -202,8 +251,24 @@ func (_m *APIMOCKINTERNAL) DeleteTeam(teamId string) *model.AppError {
return r0
}
+// DeleteTeamMember provides a mock function with given fields: teamId, userId, requestorId
+func (_m *API) DeleteTeamMember(teamId string, userId string, requestorId string) *model.AppError {
+ ret := _m.Called(teamId, userId, requestorId)
+
+ var r0 *model.AppError
+ if rf, ok := ret.Get(0).(func(string, string, string) *model.AppError); ok {
+ r0 = rf(teamId, userId, requestorId)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.AppError)
+ }
+ }
+
+ return r0
+}
+
// DeleteUser provides a mock function with given fields: userId
-func (_m *APIMOCKINTERNAL) DeleteUser(userId string) *model.AppError {
+func (_m *API) DeleteUser(userId string) *model.AppError {
ret := _m.Called(userId)
var r0 *model.AppError
@@ -219,7 +284,7 @@ func (_m *APIMOCKINTERNAL) DeleteUser(userId string) *model.AppError {
}
// GetChannel provides a mock function with given fields: channelId
-func (_m *APIMOCKINTERNAL) GetChannel(channelId string) (*model.Channel, *model.AppError) {
+func (_m *API) GetChannel(channelId string) (*model.Channel, *model.AppError) {
ret := _m.Called(channelId)
var r0 *model.Channel
@@ -244,7 +309,7 @@ func (_m *APIMOCKINTERNAL) GetChannel(channelId string) (*model.Channel, *model.
}
// GetChannelByName provides a mock function with given fields: name, teamId
-func (_m *APIMOCKINTERNAL) GetChannelByName(name string, teamId string) (*model.Channel, *model.AppError) {
+func (_m *API) GetChannelByName(name string, teamId string) (*model.Channel, *model.AppError) {
ret := _m.Called(name, teamId)
var r0 *model.Channel
@@ -269,7 +334,7 @@ func (_m *APIMOCKINTERNAL) GetChannelByName(name string, teamId string) (*model.
}
// GetChannelMember provides a mock function with given fields: channelId, userId
-func (_m *APIMOCKINTERNAL) GetChannelMember(channelId string, userId string) (*model.ChannelMember, *model.AppError) {
+func (_m *API) GetChannelMember(channelId string, userId string) (*model.ChannelMember, *model.AppError) {
ret := _m.Called(channelId, userId)
var r0 *model.ChannelMember
@@ -293,8 +358,24 @@ func (_m *APIMOCKINTERNAL) GetChannelMember(channelId string, userId string) (*m
return r0, r1
}
+// GetConfig provides a mock function with given fields:
+func (_m *API) GetConfig() *model.Config {
+ ret := _m.Called()
+
+ var r0 *model.Config
+ if rf, ok := ret.Get(0).(func() *model.Config); ok {
+ r0 = rf()
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.Config)
+ }
+ }
+
+ return r0
+}
+
// GetDirectChannel provides a mock function with given fields: userId1, userId2
-func (_m *APIMOCKINTERNAL) GetDirectChannel(userId1 string, userId2 string) (*model.Channel, *model.AppError) {
+func (_m *API) GetDirectChannel(userId1 string, userId2 string) (*model.Channel, *model.AppError) {
ret := _m.Called(userId1, userId2)
var r0 *model.Channel
@@ -319,7 +400,7 @@ func (_m *APIMOCKINTERNAL) GetDirectChannel(userId1 string, userId2 string) (*mo
}
// GetGroupChannel provides a mock function with given fields: userIds
-func (_m *APIMOCKINTERNAL) GetGroupChannel(userIds []string) (*model.Channel, *model.AppError) {
+func (_m *API) GetGroupChannel(userIds []string) (*model.Channel, *model.AppError) {
ret := _m.Called(userIds)
var r0 *model.Channel
@@ -344,7 +425,7 @@ func (_m *APIMOCKINTERNAL) GetGroupChannel(userIds []string) (*model.Channel, *m
}
// GetPost provides a mock function with given fields: postId
-func (_m *APIMOCKINTERNAL) GetPost(postId string) (*model.Post, *model.AppError) {
+func (_m *API) GetPost(postId string) (*model.Post, *model.AppError) {
ret := _m.Called(postId)
var r0 *model.Post
@@ -368,8 +449,33 @@ func (_m *APIMOCKINTERNAL) GetPost(postId string) (*model.Post, *model.AppError)
return r0, r1
}
+// GetPublicChannelsForTeam provides a mock function with given fields: teamId, offset, limit
+func (_m *API) GetPublicChannelsForTeam(teamId string, offset int, limit int) (*model.ChannelList, *model.AppError) {
+ ret := _m.Called(teamId, offset, limit)
+
+ var r0 *model.ChannelList
+ if rf, ok := ret.Get(0).(func(string, int, int) *model.ChannelList); ok {
+ r0 = rf(teamId, offset, limit)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.ChannelList)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string, int, int) *model.AppError); ok {
+ r1 = rf(teamId, offset, limit)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
// GetTeam provides a mock function with given fields: teamId
-func (_m *APIMOCKINTERNAL) GetTeam(teamId string) (*model.Team, *model.AppError) {
+func (_m *API) GetTeam(teamId string) (*model.Team, *model.AppError) {
ret := _m.Called(teamId)
var r0 *model.Team
@@ -394,7 +500,7 @@ func (_m *APIMOCKINTERNAL) GetTeam(teamId string) (*model.Team, *model.AppError)
}
// GetTeamByName provides a mock function with given fields: name
-func (_m *APIMOCKINTERNAL) GetTeamByName(name string) (*model.Team, *model.AppError) {
+func (_m *API) GetTeamByName(name string) (*model.Team, *model.AppError) {
ret := _m.Called(name)
var r0 *model.Team
@@ -418,8 +524,83 @@ func (_m *APIMOCKINTERNAL) GetTeamByName(name string) (*model.Team, *model.AppEr
return r0, r1
}
+// GetTeamMember provides a mock function with given fields: teamId, userId
+func (_m *API) GetTeamMember(teamId string, userId string) (*model.TeamMember, *model.AppError) {
+ ret := _m.Called(teamId, userId)
+
+ var r0 *model.TeamMember
+ if rf, ok := ret.Get(0).(func(string, string) *model.TeamMember); ok {
+ r0 = rf(teamId, userId)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.TeamMember)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string, string) *model.AppError); ok {
+ r1 = rf(teamId, userId)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// GetTeamMembers provides a mock function with given fields: teamId, offset, limit
+func (_m *API) GetTeamMembers(teamId string, offset int, limit int) ([]*model.TeamMember, *model.AppError) {
+ ret := _m.Called(teamId, offset, limit)
+
+ var r0 []*model.TeamMember
+ if rf, ok := ret.Get(0).(func(string, int, int) []*model.TeamMember); ok {
+ r0 = rf(teamId, offset, limit)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).([]*model.TeamMember)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string, int, int) *model.AppError); ok {
+ r1 = rf(teamId, offset, limit)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// GetTeams provides a mock function with given fields:
+func (_m *API) GetTeams() ([]*model.Team, *model.AppError) {
+ ret := _m.Called()
+
+ var r0 []*model.Team
+ if rf, ok := ret.Get(0).(func() []*model.Team); ok {
+ r0 = rf()
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).([]*model.Team)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func() *model.AppError); ok {
+ r1 = rf()
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
// GetUser provides a mock function with given fields: userId
-func (_m *APIMOCKINTERNAL) GetUser(userId string) (*model.User, *model.AppError) {
+func (_m *API) GetUser(userId string) (*model.User, *model.AppError) {
ret := _m.Called(userId)
var r0 *model.User
@@ -444,7 +625,7 @@ func (_m *APIMOCKINTERNAL) GetUser(userId string) (*model.User, *model.AppError)
}
// GetUserByEmail provides a mock function with given fields: email
-func (_m *APIMOCKINTERNAL) GetUserByEmail(email string) (*model.User, *model.AppError) {
+func (_m *API) GetUserByEmail(email string) (*model.User, *model.AppError) {
ret := _m.Called(email)
var r0 *model.User
@@ -469,7 +650,7 @@ func (_m *APIMOCKINTERNAL) GetUserByEmail(email string) (*model.User, *model.App
}
// GetUserByUsername provides a mock function with given fields: name
-func (_m *APIMOCKINTERNAL) GetUserByUsername(name string) (*model.User, *model.AppError) {
+func (_m *API) GetUserByUsername(name string) (*model.User, *model.AppError) {
ret := _m.Called(name)
var r0 *model.User
@@ -493,16 +674,57 @@ func (_m *APIMOCKINTERNAL) GetUserByUsername(name string) (*model.User, *model.A
return r0, r1
}
-// KeyValueStore provides a mock function with given fields:
-func (_m *APIMOCKINTERNAL) KeyValueStore() plugin.KeyValueStore {
- ret := _m.Called()
+// KVDelete provides a mock function with given fields: key
+func (_m *API) KVDelete(key string) *model.AppError {
+ ret := _m.Called(key)
- var r0 plugin.KeyValueStore
- if rf, ok := ret.Get(0).(func() plugin.KeyValueStore); ok {
- r0 = rf()
+ var r0 *model.AppError
+ if rf, ok := ret.Get(0).(func(string) *model.AppError); ok {
+ r0 = rf(key)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.AppError)
+ }
+ }
+
+ return r0
+}
+
+// KVGet provides a mock function with given fields: key
+func (_m *API) KVGet(key string) ([]byte, *model.AppError) {
+ ret := _m.Called(key)
+
+ var r0 []byte
+ if rf, ok := ret.Get(0).(func(string) []byte); ok {
+ r0 = rf(key)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).([]byte)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string) *model.AppError); ok {
+ r1 = rf(key)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
+// KVSet provides a mock function with given fields: key, value
+func (_m *API) KVSet(key string, value []byte) *model.AppError {
+ ret := _m.Called(key, value)
+
+ var r0 *model.AppError
+ if rf, ok := ret.Get(0).(func(string, []byte) *model.AppError); ok {
+ r0 = rf(key, value)
} else {
if ret.Get(0) != nil {
- r0 = ret.Get(0).(plugin.KeyValueStore)
+ r0 = ret.Get(0).(*model.AppError)
}
}
@@ -510,7 +732,7 @@ func (_m *APIMOCKINTERNAL) KeyValueStore() plugin.KeyValueStore {
}
// LoadPluginConfiguration provides a mock function with given fields: dest
-func (_m *APIMOCKINTERNAL) LoadPluginConfiguration(dest interface{}) error {
+func (_m *API) LoadPluginConfiguration(dest interface{}) error {
ret := _m.Called(dest)
var r0 error
@@ -523,8 +745,45 @@ func (_m *APIMOCKINTERNAL) LoadPluginConfiguration(dest interface{}) error {
return r0
}
+// LogDebug provides a mock function with given fields: msg, keyValuePairs
+func (_m *API) LogDebug(msg string, keyValuePairs ...interface{}) {
+ var _ca []interface{}
+ _ca = append(_ca, msg)
+ _ca = append(_ca, keyValuePairs...)
+ _m.Called(_ca...)
+}
+
+// LogError provides a mock function with given fields: msg, keyValuePairs
+func (_m *API) LogError(msg string, keyValuePairs ...interface{}) {
+ var _ca []interface{}
+ _ca = append(_ca, msg)
+ _ca = append(_ca, keyValuePairs...)
+ _m.Called(_ca...)
+}
+
+// LogInfo provides a mock function with given fields: msg, keyValuePairs
+func (_m *API) LogInfo(msg string, keyValuePairs ...interface{}) {
+ var _ca []interface{}
+ _ca = append(_ca, msg)
+ _ca = append(_ca, keyValuePairs...)
+ _m.Called(_ca...)
+}
+
+// LogWarn provides a mock function with given fields: msg, keyValuePairs
+func (_m *API) LogWarn(msg string, keyValuePairs ...interface{}) {
+ var _ca []interface{}
+ _ca = append(_ca, msg)
+ _ca = append(_ca, keyValuePairs...)
+ _m.Called(_ca...)
+}
+
+// PublishWebSocketEvent provides a mock function with given fields: event, payload, broadcast
+func (_m *API) PublishWebSocketEvent(event string, payload map[string]interface{}, broadcast *model.WebsocketBroadcast) {
+ _m.Called(event, payload, broadcast)
+}
+
// RegisterCommand provides a mock function with given fields: command
-func (_m *APIMOCKINTERNAL) RegisterCommand(command *model.Command) error {
+func (_m *API) RegisterCommand(command *model.Command) error {
ret := _m.Called(command)
var r0 error
@@ -537,8 +796,40 @@ func (_m *APIMOCKINTERNAL) RegisterCommand(command *model.Command) error {
return r0
}
+// SaveConfig provides a mock function with given fields: config
+func (_m *API) SaveConfig(config *model.Config) *model.AppError {
+ ret := _m.Called(config)
+
+ var r0 *model.AppError
+ if rf, ok := ret.Get(0).(func(*model.Config) *model.AppError); ok {
+ r0 = rf(config)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.AppError)
+ }
+ }
+
+ return r0
+}
+
+// SendEphemeralPost provides a mock function with given fields: userId, post
+func (_m *API) SendEphemeralPost(userId string, post *model.Post) *model.Post {
+ ret := _m.Called(userId, post)
+
+ var r0 *model.Post
+ if rf, ok := ret.Get(0).(func(string, *model.Post) *model.Post); ok {
+ r0 = rf(userId, post)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.Post)
+ }
+ }
+
+ return r0
+}
+
// UnregisterCommand provides a mock function with given fields: teamId, trigger
-func (_m *APIMOCKINTERNAL) UnregisterCommand(teamId string, trigger string) error {
+func (_m *API) UnregisterCommand(teamId string, trigger string) error {
ret := _m.Called(teamId, trigger)
var r0 error
@@ -552,7 +843,7 @@ func (_m *APIMOCKINTERNAL) UnregisterCommand(teamId string, trigger string) erro
}
// UpdateChannel provides a mock function with given fields: channel
-func (_m *APIMOCKINTERNAL) UpdateChannel(channel *model.Channel) (*model.Channel, *model.AppError) {
+func (_m *API) UpdateChannel(channel *model.Channel) (*model.Channel, *model.AppError) {
ret := _m.Called(channel)
var r0 *model.Channel
@@ -577,7 +868,7 @@ func (_m *APIMOCKINTERNAL) UpdateChannel(channel *model.Channel) (*model.Channel
}
// UpdateChannelMemberNotifications provides a mock function with given fields: channelId, userId, notifications
-func (_m *APIMOCKINTERNAL) UpdateChannelMemberNotifications(channelId string, userId string, notifications map[string]string) (*model.ChannelMember, *model.AppError) {
+func (_m *API) UpdateChannelMemberNotifications(channelId string, userId string, notifications map[string]string) (*model.ChannelMember, *model.AppError) {
ret := _m.Called(channelId, userId, notifications)
var r0 *model.ChannelMember
@@ -602,7 +893,7 @@ func (_m *APIMOCKINTERNAL) UpdateChannelMemberNotifications(channelId string, us
}
// UpdateChannelMemberRoles provides a mock function with given fields: channelId, userId, newRoles
-func (_m *APIMOCKINTERNAL) UpdateChannelMemberRoles(channelId string, userId string, newRoles string) (*model.ChannelMember, *model.AppError) {
+func (_m *API) UpdateChannelMemberRoles(channelId string, userId string, newRoles string) (*model.ChannelMember, *model.AppError) {
ret := _m.Called(channelId, userId, newRoles)
var r0 *model.ChannelMember
@@ -627,7 +918,7 @@ func (_m *APIMOCKINTERNAL) UpdateChannelMemberRoles(channelId string, userId str
}
// UpdatePost provides a mock function with given fields: post
-func (_m *APIMOCKINTERNAL) UpdatePost(post *model.Post) (*model.Post, *model.AppError) {
+func (_m *API) UpdatePost(post *model.Post) (*model.Post, *model.AppError) {
ret := _m.Called(post)
var r0 *model.Post
@@ -652,7 +943,7 @@ func (_m *APIMOCKINTERNAL) UpdatePost(post *model.Post) (*model.Post, *model.App
}
// UpdateTeam provides a mock function with given fields: team
-func (_m *APIMOCKINTERNAL) UpdateTeam(team *model.Team) (*model.Team, *model.AppError) {
+func (_m *API) UpdateTeam(team *model.Team) (*model.Team, *model.AppError) {
ret := _m.Called(team)
var r0 *model.Team
@@ -676,8 +967,33 @@ func (_m *APIMOCKINTERNAL) UpdateTeam(team *model.Team) (*model.Team, *model.App
return r0, r1
}
+// UpdateTeamMemberRoles provides a mock function with given fields: teamId, userId, newRoles
+func (_m *API) UpdateTeamMemberRoles(teamId string, userId string, newRoles string) (*model.TeamMember, *model.AppError) {
+ ret := _m.Called(teamId, userId, newRoles)
+
+ var r0 *model.TeamMember
+ if rf, ok := ret.Get(0).(func(string, string, string) *model.TeamMember); ok {
+ r0 = rf(teamId, userId, newRoles)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.TeamMember)
+ }
+ }
+
+ var r1 *model.AppError
+ if rf, ok := ret.Get(1).(func(string, string, string) *model.AppError); ok {
+ r1 = rf(teamId, userId, newRoles)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(*model.AppError)
+ }
+ }
+
+ return r0, r1
+}
+
// UpdateUser provides a mock function with given fields: user
-func (_m *APIMOCKINTERNAL) UpdateUser(user *model.User) (*model.User, *model.AppError) {
+func (_m *API) UpdateUser(user *model.User) (*model.User, *model.AppError) {
ret := _m.Called(user)
var r0 *model.User
diff --git a/plugin/plugintest/apioverride.go b/plugin/plugintest/apioverride.go
deleted file mode 100644
index 54cfe27bc..000000000
--- a/plugin/plugintest/apioverride.go
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See LICENSE.txt for license information.
-
-package plugintest
-
-import "github.com/mattermost/mattermost-server/plugin"
-
-type API struct {
- APIMOCKINTERNAL
- Store *KeyValueStore
-}
-
-var _ plugin.API = (*API)(nil)
-var _ plugin.KeyValueStore = (*KeyValueStore)(nil)
-
-func (m *API) KeyValueStore() plugin.KeyValueStore {
- return m.Store
-}
diff --git a/plugin/plugintest/hooks.go b/plugin/plugintest/hooks.go
index 3de257c76..d88792f58 100644
--- a/plugin/plugintest/hooks.go
+++ b/plugin/plugintest/hooks.go
@@ -14,13 +14,18 @@ type Hooks struct {
mock.Mock
}
-// ExecuteCommand provides a mock function with given fields: args
-func (_m *Hooks) ExecuteCommand(args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
- ret := _m.Called(args)
+// ChannelHasBeenCreated provides a mock function with given fields: c, channel
+func (_m *Hooks) ChannelHasBeenCreated(c *plugin.Context, channel *model.Channel) {
+ _m.Called(c, channel)
+}
+
+// ExecuteCommand provides a mock function with given fields: c, args
+func (_m *Hooks) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
+ ret := _m.Called(c, args)
var r0 *model.CommandResponse
- if rf, ok := ret.Get(0).(func(*model.CommandArgs) *model.CommandResponse); ok {
- r0 = rf(args)
+ if rf, ok := ret.Get(0).(func(*plugin.Context, *model.CommandArgs) *model.CommandResponse); ok {
+ r0 = rf(c, args)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.CommandResponse)
@@ -28,8 +33,8 @@ func (_m *Hooks) ExecuteCommand(args *model.CommandArgs) (*model.CommandResponse
}
var r1 *model.AppError
- if rf, ok := ret.Get(1).(func(*model.CommandArgs) *model.AppError); ok {
- r1 = rf(args)
+ if rf, ok := ret.Get(1).(func(*plugin.Context, *model.CommandArgs) *model.AppError); ok {
+ r1 = rf(c, args)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
@@ -39,23 +44,46 @@ func (_m *Hooks) ExecuteCommand(args *model.CommandArgs) (*model.CommandResponse
return r0, r1
}
-// MessageHasBeenPosted provides a mock function with given fields: post
-func (_m *Hooks) MessageHasBeenPosted(post *model.Post) {
- _m.Called(post)
+// Implemented provides a mock function with given fields:
+func (_m *Hooks) Implemented() ([]string, error) {
+ ret := _m.Called()
+
+ var r0 []string
+ if rf, ok := ret.Get(0).(func() []string); ok {
+ r0 = rf()
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).([]string)
+ }
+ }
+
+ var r1 error
+ if rf, ok := ret.Get(1).(func() error); ok {
+ r1 = rf()
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
}
-// MessageHasBeenUpdated provides a mock function with given fields: newPost, oldPost
-func (_m *Hooks) MessageHasBeenUpdated(newPost *model.Post, oldPost *model.Post) {
- _m.Called(newPost, oldPost)
+// MessageHasBeenPosted provides a mock function with given fields: c, post
+func (_m *Hooks) MessageHasBeenPosted(c *plugin.Context, post *model.Post) {
+ _m.Called(c, post)
}
-// MessageWillBePosted provides a mock function with given fields: post
-func (_m *Hooks) MessageWillBePosted(post *model.Post) (*model.Post, string) {
- ret := _m.Called(post)
+// MessageHasBeenUpdated provides a mock function with given fields: c, newPost, oldPost
+func (_m *Hooks) MessageHasBeenUpdated(c *plugin.Context, newPost *model.Post, oldPost *model.Post) {
+ _m.Called(c, newPost, oldPost)
+}
+
+// MessageWillBePosted provides a mock function with given fields: c, post
+func (_m *Hooks) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) {
+ ret := _m.Called(c, post)
var r0 *model.Post
- if rf, ok := ret.Get(0).(func(*model.Post) *model.Post); ok {
- r0 = rf(post)
+ if rf, ok := ret.Get(0).(func(*plugin.Context, *model.Post) *model.Post); ok {
+ r0 = rf(c, post)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Post)
@@ -63,8 +91,8 @@ func (_m *Hooks) MessageWillBePosted(post *model.Post) (*model.Post, string) {
}
var r1 string
- if rf, ok := ret.Get(1).(func(*model.Post) string); ok {
- r1 = rf(post)
+ if rf, ok := ret.Get(1).(func(*plugin.Context, *model.Post) string); ok {
+ r1 = rf(c, post)
} else {
r1 = ret.Get(1).(string)
}
@@ -72,13 +100,13 @@ func (_m *Hooks) MessageWillBePosted(post *model.Post) (*model.Post, string) {
return r0, r1
}
-// MessageWillBeUpdated provides a mock function with given fields: newPost, oldPost
-func (_m *Hooks) MessageWillBeUpdated(newPost *model.Post, oldPost *model.Post) (*model.Post, string) {
- ret := _m.Called(newPost, oldPost)
+// MessageWillBeUpdated provides a mock function with given fields: c, newPost, oldPost
+func (_m *Hooks) MessageWillBeUpdated(c *plugin.Context, newPost *model.Post, oldPost *model.Post) (*model.Post, string) {
+ ret := _m.Called(c, newPost, oldPost)
var r0 *model.Post
- if rf, ok := ret.Get(0).(func(*model.Post, *model.Post) *model.Post); ok {
- r0 = rf(newPost, oldPost)
+ if rf, ok := ret.Get(0).(func(*plugin.Context, *model.Post, *model.Post) *model.Post); ok {
+ r0 = rf(c, newPost, oldPost)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Post)
@@ -86,8 +114,8 @@ func (_m *Hooks) MessageWillBeUpdated(newPost *model.Post, oldPost *model.Post)
}
var r1 string
- if rf, ok := ret.Get(1).(func(*model.Post, *model.Post) string); ok {
- r1 = rf(newPost, oldPost)
+ if rf, ok := ret.Get(1).(func(*plugin.Context, *model.Post, *model.Post) string); ok {
+ r1 = rf(c, newPost, oldPost)
} else {
r1 = ret.Get(1).(string)
}
@@ -95,13 +123,13 @@ func (_m *Hooks) MessageWillBeUpdated(newPost *model.Post, oldPost *model.Post)
return r0, r1
}
-// OnActivate provides a mock function with given fields: _a0
-func (_m *Hooks) OnActivate(_a0 plugin.API) error {
- ret := _m.Called(_a0)
+// OnActivate provides a mock function with given fields:
+func (_m *Hooks) OnActivate() error {
+ ret := _m.Called()
var r0 error
- if rf, ok := ret.Get(0).(func(plugin.API) error); ok {
- r0 = rf(_a0)
+ if rf, ok := ret.Get(0).(func() error); ok {
+ r0 = rf()
} else {
r0 = ret.Error(0)
}
@@ -137,7 +165,27 @@ func (_m *Hooks) OnDeactivate() error {
return r0
}
-// ServeHTTP provides a mock function with given fields: _a0, _a1
-func (_m *Hooks) ServeHTTP(_a0 http.ResponseWriter, _a1 *http.Request) {
- _m.Called(_a0, _a1)
+// ServeHTTP provides a mock function with given fields: c, w, r
+func (_m *Hooks) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
+ _m.Called(c, w, r)
+}
+
+// UserHasJoinedChannel provides a mock function with given fields: c, channelMember, actor
+func (_m *Hooks) UserHasJoinedChannel(c *plugin.Context, channelMember *model.ChannelMember, actor *model.User) {
+ _m.Called(c, channelMember, actor)
+}
+
+// UserHasJoinedTeam provides a mock function with given fields: c, teamMember, actor
+func (_m *Hooks) UserHasJoinedTeam(c *plugin.Context, teamMember *model.TeamMember, actor *model.User) {
+ _m.Called(c, teamMember, actor)
+}
+
+// UserHasLeftChannel provides a mock function with given fields: c, channelMember, actor
+func (_m *Hooks) UserHasLeftChannel(c *plugin.Context, channelMember *model.ChannelMember, actor *model.User) {
+ _m.Called(c, channelMember, actor)
+}
+
+// UserHasLeftTeam provides a mock function with given fields: c, teamMember, actor
+func (_m *Hooks) UserHasLeftTeam(c *plugin.Context, teamMember *model.TeamMember, actor *model.User) {
+ _m.Called(c, teamMember, actor)
}
diff --git a/plugin/plugintest/key_value_store.go b/plugin/plugintest/key_value_store.go
deleted file mode 100644
index 30d60d708..000000000
--- a/plugin/plugintest/key_value_store.go
+++ /dev/null
@@ -1,70 +0,0 @@
-// Code generated by mockery v1.0.0. DO NOT EDIT.
-
-// Regenerate this file using `make plugin-mocks`.
-
-package plugintest
-
-import mock "github.com/stretchr/testify/mock"
-import model "github.com/mattermost/mattermost-server/model"
-
-// KeyValueStore is an autogenerated mock type for the KeyValueStore type
-type KeyValueStore struct {
- mock.Mock
-}
-
-// Delete provides a mock function with given fields: key
-func (_m *KeyValueStore) Delete(key string) *model.AppError {
- ret := _m.Called(key)
-
- var r0 *model.AppError
- if rf, ok := ret.Get(0).(func(string) *model.AppError); ok {
- r0 = rf(key)
- } else {
- if ret.Get(0) != nil {
- r0 = ret.Get(0).(*model.AppError)
- }
- }
-
- return r0
-}
-
-// Get provides a mock function with given fields: key
-func (_m *KeyValueStore) Get(key string) ([]byte, *model.AppError) {
- ret := _m.Called(key)
-
- var r0 []byte
- if rf, ok := ret.Get(0).(func(string) []byte); ok {
- r0 = rf(key)
- } else {
- if ret.Get(0) != nil {
- r0 = ret.Get(0).([]byte)
- }
- }
-
- var r1 *model.AppError
- if rf, ok := ret.Get(1).(func(string) *model.AppError); ok {
- r1 = rf(key)
- } else {
- if ret.Get(1) != nil {
- r1 = ret.Get(1).(*model.AppError)
- }
- }
-
- return r0, r1
-}
-
-// Set provides a mock function with given fields: key, value
-func (_m *KeyValueStore) Set(key string, value []byte) *model.AppError {
- ret := _m.Called(key, value)
-
- var r0 *model.AppError
- if rf, ok := ret.Get(0).(func(string, []byte) *model.AppError); ok {
- r0 = rf(key, value)
- } else {
- if ret.Get(0) != nil {
- r0 = ret.Get(0).(*model.AppError)
- }
- }
-
- return r0
-}
diff --git a/plugin/rpcplugin/api.go b/plugin/rpcplugin/api.go
deleted file mode 100644
index c81bbb7c5..000000000
--- a/plugin/rpcplugin/api.go
+++ /dev/null
@@ -1,718 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package rpcplugin
-
-import (
- "encoding/gob"
- "encoding/json"
- "io"
- "net/http"
- "net/rpc"
-
- "github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/plugin"
-)
-
-type LocalAPI struct {
- api plugin.API
- muxer *Muxer
-}
-
-func (api *LocalAPI) LoadPluginConfiguration(args struct{}, reply *[]byte) error {
- var config interface{}
- if err := api.api.LoadPluginConfiguration(&config); err != nil {
- return err
- }
- b, err := json.Marshal(config)
- if err != nil {
- return err
- }
- *reply = b
- return nil
-}
-
-func (api *LocalAPI) RegisterCommand(args *model.Command, reply *APITeamReply) error {
- return api.api.RegisterCommand(args)
-}
-
-func (api *LocalAPI) UnregisterCommand(args *APIUnregisterCommandArgs, reply *APITeamReply) error {
- return api.api.UnregisterCommand(args.TeamId, args.Trigger)
-}
-
-type APIErrorReply struct {
- Error *model.AppError
-}
-
-type APITeamReply struct {
- Team *model.Team
- Error *model.AppError
-}
-
-func (api *LocalAPI) CreateTeam(args *model.Team, reply *APITeamReply) error {
- team, err := api.api.CreateTeam(args)
- *reply = APITeamReply{
- Team: team,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) DeleteTeam(args string, reply *APIErrorReply) error {
- *reply = APIErrorReply{
- Error: api.api.DeleteTeam(args),
- }
- return nil
-}
-
-func (api *LocalAPI) GetTeam(args string, reply *APITeamReply) error {
- team, err := api.api.GetTeam(args)
- *reply = APITeamReply{
- Team: team,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) GetTeamByName(args string, reply *APITeamReply) error {
- team, err := api.api.GetTeamByName(args)
- *reply = APITeamReply{
- Team: team,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) UpdateTeam(args *model.Team, reply *APITeamReply) error {
- team, err := api.api.UpdateTeam(args)
- *reply = APITeamReply{
- Team: team,
- Error: err,
- }
- return nil
-}
-
-type APIUserReply struct {
- User *model.User
- Error *model.AppError
-}
-
-func (api *LocalAPI) CreateUser(args *model.User, reply *APIUserReply) error {
- user, err := api.api.CreateUser(args)
- *reply = APIUserReply{
- User: user,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) DeleteUser(args string, reply *APIErrorReply) error {
- *reply = APIErrorReply{
- Error: api.api.DeleteUser(args),
- }
- return nil
-}
-
-func (api *LocalAPI) GetUser(args string, reply *APIUserReply) error {
- user, err := api.api.GetUser(args)
- *reply = APIUserReply{
- User: user,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) GetUserByEmail(args string, reply *APIUserReply) error {
- user, err := api.api.GetUserByEmail(args)
- *reply = APIUserReply{
- User: user,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) GetUserByUsername(args string, reply *APIUserReply) error {
- user, err := api.api.GetUserByUsername(args)
- *reply = APIUserReply{
- User: user,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) UpdateUser(args *model.User, reply *APIUserReply) error {
- user, err := api.api.UpdateUser(args)
- *reply = APIUserReply{
- User: user,
- Error: err,
- }
- return nil
-}
-
-type APIGetChannelByNameArgs struct {
- Name string
- TeamId string
-}
-
-type APIGetDirectChannelArgs struct {
- UserId1 string
- UserId2 string
-}
-
-type APIGetGroupChannelArgs struct {
- UserIds []string
-}
-
-type APIAddChannelMemberArgs struct {
- ChannelId string
- UserId string
-}
-
-type APIGetChannelMemberArgs struct {
- ChannelId string
- UserId string
-}
-
-type APIUpdateChannelMemberRolesArgs struct {
- ChannelId string
- UserId string
- NewRoles string
-}
-
-type APIUpdateChannelMemberNotificationsArgs struct {
- ChannelId string
- UserId string
- Notifications map[string]string
-}
-
-type APIDeleteChannelMemberArgs struct {
- ChannelId string
- UserId string
-}
-
-type APIChannelReply struct {
- Channel *model.Channel
- Error *model.AppError
-}
-
-type APIChannelMemberReply struct {
- ChannelMember *model.ChannelMember
- Error *model.AppError
-}
-
-func (api *LocalAPI) CreateChannel(args *model.Channel, reply *APIChannelReply) error {
- channel, err := api.api.CreateChannel(args)
- *reply = APIChannelReply{
- Channel: channel,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) DeleteChannel(args string, reply *APIErrorReply) error {
- *reply = APIErrorReply{
- Error: api.api.DeleteChannel(args),
- }
- return nil
-}
-
-func (api *LocalAPI) GetChannel(args string, reply *APIChannelReply) error {
- channel, err := api.api.GetChannel(args)
- *reply = APIChannelReply{
- Channel: channel,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) GetChannelByName(args *APIGetChannelByNameArgs, reply *APIChannelReply) error {
- channel, err := api.api.GetChannelByName(args.Name, args.TeamId)
- *reply = APIChannelReply{
- Channel: channel,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) GetDirectChannel(args *APIGetDirectChannelArgs, reply *APIChannelReply) error {
- channel, err := api.api.GetDirectChannel(args.UserId1, args.UserId2)
- *reply = APIChannelReply{
- Channel: channel,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) GetGroupChannel(args *APIGetGroupChannelArgs, reply *APIChannelReply) error {
- channel, err := api.api.GetGroupChannel(args.UserIds)
- *reply = APIChannelReply{
- Channel: channel,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) UpdateChannel(args *model.Channel, reply *APIChannelReply) error {
- channel, err := api.api.UpdateChannel(args)
- *reply = APIChannelReply{
- Channel: channel,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) AddChannelMember(args *APIAddChannelMemberArgs, reply *APIChannelMemberReply) error {
- member, err := api.api.AddChannelMember(args.ChannelId, args.UserId)
- *reply = APIChannelMemberReply{
- ChannelMember: member,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) GetChannelMember(args *APIGetChannelMemberArgs, reply *APIChannelMemberReply) error {
- member, err := api.api.GetChannelMember(args.ChannelId, args.UserId)
- *reply = APIChannelMemberReply{
- ChannelMember: member,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) UpdateChannelMemberRoles(args *APIUpdateChannelMemberRolesArgs, reply *APIChannelMemberReply) error {
- member, err := api.api.UpdateChannelMemberRoles(args.ChannelId, args.UserId, args.NewRoles)
- *reply = APIChannelMemberReply{
- ChannelMember: member,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) UpdateChannelMemberNotifications(args *APIUpdateChannelMemberNotificationsArgs, reply *APIChannelMemberReply) error {
- member, err := api.api.UpdateChannelMemberNotifications(args.ChannelId, args.UserId, args.Notifications)
- *reply = APIChannelMemberReply{
- ChannelMember: member,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) DeleteChannelMember(args *APIDeleteChannelMemberArgs, reply *APIErrorReply) error {
- err := api.api.DeleteChannelMember(args.ChannelId, args.UserId)
- *reply = APIErrorReply{
- Error: err,
- }
- return nil
-}
-
-type APIPostReply struct {
- Post *model.Post
- Error *model.AppError
-}
-
-func (api *LocalAPI) CreatePost(args *model.Post, reply *APIPostReply) error {
- post, err := api.api.CreatePost(args)
- *reply = APIPostReply{
- Post: post,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) DeletePost(args string, reply *APIErrorReply) error {
- *reply = APIErrorReply{
- Error: api.api.DeletePost(args),
- }
- return nil
-}
-
-func (api *LocalAPI) GetPost(args string, reply *APIPostReply) error {
- post, err := api.api.GetPost(args)
- *reply = APIPostReply{
- Post: post,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) UpdatePost(args *model.Post, reply *APIPostReply) error {
- post, err := api.api.UpdatePost(args)
- *reply = APIPostReply{
- Post: post,
- Error: err,
- }
- return nil
-}
-
-type APIKeyValueStoreReply struct {
- Value []byte
- Error *model.AppError
-}
-
-type APIKeyValueStoreSetArgs struct {
- Key string
- Value []byte
-}
-
-func (api *LocalAPI) KeyValueStoreSet(args *APIKeyValueStoreSetArgs, reply *APIErrorReply) error {
- err := api.api.KeyValueStore().Set(args.Key, args.Value)
- *reply = APIErrorReply{
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) KeyValueStoreGet(args string, reply *APIKeyValueStoreReply) error {
- v, err := api.api.KeyValueStore().Get(args)
- *reply = APIKeyValueStoreReply{
- Value: v,
- Error: err,
- }
- return nil
-}
-
-func (api *LocalAPI) KeyValueStoreDelete(args string, reply *APIErrorReply) error {
- err := api.api.KeyValueStore().Delete(args)
- *reply = APIErrorReply{
- Error: err,
- }
- return nil
-}
-
-func ServeAPI(api plugin.API, conn io.ReadWriteCloser, muxer *Muxer) {
- server := rpc.NewServer()
- server.Register(&LocalAPI{
- api: api,
- muxer: muxer,
- })
- server.ServeConn(conn)
-}
-
-type RemoteAPI struct {
- client *rpc.Client
- muxer *Muxer
- keyValueStore *RemoteKeyValueStore
-}
-
-type RemoteKeyValueStore struct {
- api *RemoteAPI
-}
-
-var _ plugin.API = (*RemoteAPI)(nil)
-var _ plugin.KeyValueStore = (*RemoteKeyValueStore)(nil)
-
-func (api *RemoteAPI) LoadPluginConfiguration(dest interface{}) error {
- var config []byte
- if err := api.client.Call("LocalAPI.LoadPluginConfiguration", struct{}{}, &config); err != nil {
- return err
- }
- return json.Unmarshal(config, dest)
-}
-
-func (api *RemoteAPI) RegisterCommand(command *model.Command) error {
- return api.client.Call("LocalAPI.RegisterCommand", command, nil)
-}
-
-type APIUnregisterCommandArgs struct {
- TeamId string
- Trigger string
-}
-
-func (api *RemoteAPI) UnregisterCommand(teamId, trigger string) error {
- return api.client.Call("LocalAPI.UnregisterCommand", &APIUnregisterCommandArgs{
- TeamId: teamId,
- Trigger: trigger,
- }, nil)
-}
-
-func (api *RemoteAPI) CreateUser(user *model.User) (*model.User, *model.AppError) {
- var reply APIUserReply
- if err := api.client.Call("LocalAPI.CreateUser", user, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.CreateUser", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.User, reply.Error
-}
-
-func (api *RemoteAPI) DeleteUser(userId string) *model.AppError {
- var reply APIErrorReply
- if err := api.client.Call("LocalAPI.DeleteUser", userId, &reply); err != nil {
- return model.NewAppError("RemoteAPI.DeleteUser", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Error
-}
-
-func (api *RemoteAPI) GetUser(userId string) (*model.User, *model.AppError) {
- var reply APIUserReply
- if err := api.client.Call("LocalAPI.GetUser", userId, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.GetUser", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.User, reply.Error
-}
-
-func (api *RemoteAPI) GetUserByEmail(email string) (*model.User, *model.AppError) {
- var reply APIUserReply
- if err := api.client.Call("LocalAPI.GetUserByEmail", email, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.GetUserByEmail", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.User, reply.Error
-}
-
-func (api *RemoteAPI) GetUserByUsername(name string) (*model.User, *model.AppError) {
- var reply APIUserReply
- if err := api.client.Call("LocalAPI.GetUserByUsername", name, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.GetUserByUsername", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.User, reply.Error
-}
-
-func (api *RemoteAPI) UpdateUser(user *model.User) (*model.User, *model.AppError) {
- var reply APIUserReply
- if err := api.client.Call("LocalAPI.UpdateUser", user, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.UpdateUser", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.User, reply.Error
-}
-
-func (api *RemoteAPI) CreateTeam(team *model.Team) (*model.Team, *model.AppError) {
- var reply APITeamReply
- if err := api.client.Call("LocalAPI.CreateTeam", team, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.CreateTeam", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Team, reply.Error
-}
-
-func (api *RemoteAPI) DeleteTeam(teamId string) *model.AppError {
- var reply APIErrorReply
- if err := api.client.Call("LocalAPI.DeleteTeam", teamId, &reply); err != nil {
- return model.NewAppError("RemoteAPI.DeleteTeam", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Error
-}
-
-func (api *RemoteAPI) GetTeam(teamId string) (*model.Team, *model.AppError) {
- var reply APITeamReply
- if err := api.client.Call("LocalAPI.GetTeam", teamId, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.GetTeam", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Team, reply.Error
-}
-
-func (api *RemoteAPI) GetTeamByName(name string) (*model.Team, *model.AppError) {
- var reply APITeamReply
- if err := api.client.Call("LocalAPI.GetTeamByName", name, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.GetTeamByName", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Team, reply.Error
-}
-
-func (api *RemoteAPI) UpdateTeam(team *model.Team) (*model.Team, *model.AppError) {
- var reply APITeamReply
- if err := api.client.Call("LocalAPI.UpdateTeam", team, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.UpdateTeam", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Team, reply.Error
-}
-
-func (api *RemoteAPI) CreateChannel(channel *model.Channel) (*model.Channel, *model.AppError) {
- var reply APIChannelReply
- if err := api.client.Call("LocalAPI.CreateChannel", channel, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.CreateChannel", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Channel, reply.Error
-}
-
-func (api *RemoteAPI) DeleteChannel(channelId string) *model.AppError {
- var reply APIErrorReply
- if err := api.client.Call("LocalAPI.DeleteChannel", channelId, &reply); err != nil {
- return model.NewAppError("RemoteAPI.DeleteChannel", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Error
-}
-
-func (api *RemoteAPI) GetChannel(channelId string) (*model.Channel, *model.AppError) {
- var reply APIChannelReply
- if err := api.client.Call("LocalAPI.GetChannel", channelId, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.GetChannel", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Channel, reply.Error
-}
-
-func (api *RemoteAPI) GetChannelByName(name, teamId string) (*model.Channel, *model.AppError) {
- var reply APIChannelReply
- if err := api.client.Call("LocalAPI.GetChannelByName", &APIGetChannelByNameArgs{
- Name: name,
- TeamId: teamId,
- }, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.GetChannelByName", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Channel, reply.Error
-}
-
-func (api *RemoteAPI) GetDirectChannel(userId1, userId2 string) (*model.Channel, *model.AppError) {
- var reply APIChannelReply
- if err := api.client.Call("LocalAPI.GetDirectChannel", &APIGetDirectChannelArgs{
- UserId1: userId1,
- UserId2: userId2,
- }, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.GetDirectChannel", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Channel, reply.Error
-}
-
-func (api *RemoteAPI) GetGroupChannel(userIds []string) (*model.Channel, *model.AppError) {
- var reply APIChannelReply
- if err := api.client.Call("LocalAPI.GetGroupChannel", &APIGetGroupChannelArgs{
- UserIds: userIds,
- }, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.GetGroupChannel", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Channel, reply.Error
-}
-
-func (api *RemoteAPI) UpdateChannel(channel *model.Channel) (*model.Channel, *model.AppError) {
- var reply APIChannelReply
- if err := api.client.Call("LocalAPI.UpdateChannel", channel, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.UpdateChannel", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Channel, reply.Error
-}
-
-func (api *RemoteAPI) AddChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError) {
- var reply APIChannelMemberReply
- if err := api.client.Call("LocalAPI.AddChannelMember", &APIAddChannelMemberArgs{
- ChannelId: channelId,
- UserId: userId,
- }, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.AddChannelMember", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.ChannelMember, reply.Error
-}
-
-func (api *RemoteAPI) GetChannelMember(channelId, userId string) (*model.ChannelMember, *model.AppError) {
- var reply APIChannelMemberReply
- if err := api.client.Call("LocalAPI.GetChannelMember", &APIGetChannelMemberArgs{
- ChannelId: channelId,
- UserId: userId,
- }, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.GetChannelMember", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.ChannelMember, reply.Error
-}
-
-func (api *RemoteAPI) UpdateChannelMemberRoles(channelId, userId, newRoles string) (*model.ChannelMember, *model.AppError) {
- var reply APIChannelMemberReply
- if err := api.client.Call("LocalAPI.UpdateChannelMemberRoles", &APIUpdateChannelMemberRolesArgs{
- ChannelId: channelId,
- UserId: userId,
- NewRoles: newRoles,
- }, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.UpdateChannelMemberRoles", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.ChannelMember, reply.Error
-}
-
-func (api *RemoteAPI) UpdateChannelMemberNotifications(channelId, userId string, notifications map[string]string) (*model.ChannelMember, *model.AppError) {
- var reply APIChannelMemberReply
- if err := api.client.Call("LocalAPI.UpdateChannelMemberNotifications", &APIUpdateChannelMemberNotificationsArgs{
- ChannelId: channelId,
- UserId: userId,
- Notifications: notifications,
- }, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.UpdateChannelMemberNotifications", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.ChannelMember, reply.Error
-}
-
-func (api *RemoteAPI) DeleteChannelMember(channelId, userId string) *model.AppError {
- var reply APIErrorReply
- if err := api.client.Call("LocalAPI.DeleteChannelMember", &APIDeleteChannelMemberArgs{
- ChannelId: channelId,
- UserId: userId,
- }, &reply); err != nil {
- return model.NewAppError("RemoteAPI.DeleteChannelMember", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Error
-}
-
-func (api *RemoteAPI) CreatePost(post *model.Post) (*model.Post, *model.AppError) {
- var reply APIPostReply
- if err := api.client.Call("LocalAPI.CreatePost", post, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.CreatePost", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Post, reply.Error
-}
-
-func (api *RemoteAPI) DeletePost(postId string) *model.AppError {
- var reply APIErrorReply
- if err := api.client.Call("LocalAPI.DeletePost", postId, &reply); err != nil {
- return model.NewAppError("RemoteAPI.DeletePost", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Error
-}
-
-func (api *RemoteAPI) GetPost(postId string) (*model.Post, *model.AppError) {
- var reply APIPostReply
- if err := api.client.Call("LocalAPI.GetPost", postId, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.GetPost", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Post, reply.Error
-}
-
-func (api *RemoteAPI) UpdatePost(post *model.Post) (*model.Post, *model.AppError) {
- var reply APIPostReply
- if err := api.client.Call("LocalAPI.UpdatePost", post, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.UpdatePost", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Post, reply.Error
-}
-
-func (api *RemoteAPI) KeyValueStore() plugin.KeyValueStore {
- return api.keyValueStore
-}
-
-func (s *RemoteKeyValueStore) Set(key string, value []byte) *model.AppError {
- var reply APIErrorReply
- if err := s.api.client.Call("LocalAPI.KeyValueStoreSet", &APIKeyValueStoreSetArgs{Key: key, Value: value}, &reply); err != nil {
- return model.NewAppError("RemoteAPI.KeyValueStoreSet", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Error
-}
-
-func (s *RemoteKeyValueStore) Get(key string) ([]byte, *model.AppError) {
- var reply APIKeyValueStoreReply
- if err := s.api.client.Call("LocalAPI.KeyValueStoreGet", key, &reply); err != nil {
- return nil, model.NewAppError("RemoteAPI.KeyValueStoreGet", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Value, reply.Error
-}
-
-func (s *RemoteKeyValueStore) Delete(key string) *model.AppError {
- var reply APIErrorReply
- if err := s.api.client.Call("LocalAPI.KeyValueStoreDelete", key, &reply); err != nil {
- return model.NewAppError("RemoteAPI.KeyValueStoreDelete", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Error
-}
-
-func (h *RemoteAPI) Close() error {
- return h.client.Close()
-}
-
-func ConnectAPI(conn io.ReadWriteCloser, muxer *Muxer) *RemoteAPI {
- remoteKeyValueStore := &RemoteKeyValueStore{}
- remoteApi := &RemoteAPI{
- client: rpc.NewClient(conn),
- muxer: muxer,
- keyValueStore: remoteKeyValueStore,
- }
-
- remoteKeyValueStore.api = remoteApi
-
- return remoteApi
-}
-
-func init() {
- gob.Register([]*model.SlackAttachment{})
- gob.Register([]interface{}{})
- gob.Register(map[string]interface{}{})
-}
diff --git a/plugin/rpcplugin/api_test.go b/plugin/rpcplugin/api_test.go
deleted file mode 100644
index 04d8e5d86..000000000
--- a/plugin/rpcplugin/api_test.go
+++ /dev/null
@@ -1,300 +0,0 @@
-package rpcplugin
-
-import (
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
- "github.com/stretchr/testify/require"
-
- "github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/plugin"
- "github.com/mattermost/mattermost-server/plugin/plugintest"
-)
-
-func testAPIRPC(api plugin.API, f func(plugin.API)) {
- r1, w1 := io.Pipe()
- r2, w2 := io.Pipe()
-
- c1 := NewMuxer(NewReadWriteCloser(r1, w2), false)
- defer c1.Close()
-
- c2 := NewMuxer(NewReadWriteCloser(r2, w1), true)
- defer c2.Close()
-
- id, server := c1.Serve()
- go ServeAPI(api, server, c1)
-
- remote := ConnectAPI(c2.Connect(id), c2)
- defer remote.Close()
-
- f(remote)
-}
-
-func TestAPI(t *testing.T) {
- keyValueStore := &plugintest.KeyValueStore{}
- api := plugintest.API{Store: keyValueStore}
- defer api.AssertExpectations(t)
-
- type Config struct {
- Foo string
- Bar struct {
- Baz string
- }
- }
-
- api.On("LoadPluginConfiguration", mock.MatchedBy(func(x interface{}) bool { return true })).Run(func(args mock.Arguments) {
- dest := args.Get(0).(interface{})
- json.Unmarshal([]byte(`{"Foo": "foo", "Bar": {"Baz": "baz"}}`), dest)
- }).Return(nil)
-
- testChannel := &model.Channel{
- Id: "thechannelid",
- }
-
- testChannelMember := &model.ChannelMember{
- ChannelId: "thechannelid",
- UserId: "theuserid",
- }
-
- testTeam := &model.Team{
- Id: "theteamid",
- }
- teamNotFoundError := model.NewAppError("SqlTeamStore.GetByName", "store.sql_team.get_by_name.app_error", nil, "name=notateam", http.StatusNotFound)
-
- testUser := &model.User{
- Id: "theuserid",
- }
-
- testPost := &model.Post{
- Message: "hello",
- }
-
- testAPIRPC(&api, func(remote plugin.API) {
- var config Config
- assert.NoError(t, remote.LoadPluginConfiguration(&config))
- assert.Equal(t, "foo", config.Foo)
- assert.Equal(t, "baz", config.Bar.Baz)
-
- api.On("RegisterCommand", mock.AnythingOfType("*model.Command")).Return(fmt.Errorf("foo")).Once()
- assert.Error(t, remote.RegisterCommand(&model.Command{}))
- api.On("RegisterCommand", mock.AnythingOfType("*model.Command")).Return(nil).Once()
- assert.NoError(t, remote.RegisterCommand(&model.Command{}))
-
- api.On("UnregisterCommand", "team", "trigger").Return(fmt.Errorf("foo")).Once()
- assert.Error(t, remote.UnregisterCommand("team", "trigger"))
- api.On("UnregisterCommand", "team", "trigger").Return(nil).Once()
- assert.NoError(t, remote.UnregisterCommand("team", "trigger"))
-
- api.On("CreateChannel", mock.AnythingOfType("*model.Channel")).Return(func(c *model.Channel) *model.Channel {
- c.Id = "thechannelid"
- return c
- }, nil).Once()
- channel, err := remote.CreateChannel(testChannel)
- assert.Equal(t, "thechannelid", channel.Id)
- assert.Nil(t, err)
-
- api.On("DeleteChannel", "thechannelid").Return(nil).Once()
- assert.Nil(t, remote.DeleteChannel("thechannelid"))
-
- api.On("GetChannel", "thechannelid").Return(testChannel, nil).Once()
- channel, err = remote.GetChannel("thechannelid")
- assert.Equal(t, testChannel, channel)
- assert.Nil(t, err)
-
- api.On("GetChannelByName", "foo", "theteamid").Return(testChannel, nil).Once()
- channel, err = remote.GetChannelByName("foo", "theteamid")
- assert.Equal(t, testChannel, channel)
- assert.Nil(t, err)
-
- api.On("GetDirectChannel", "user1", "user2").Return(testChannel, nil).Once()
- channel, err = remote.GetDirectChannel("user1", "user2")
- assert.Equal(t, testChannel, channel)
- assert.Nil(t, err)
-
- api.On("GetGroupChannel", []string{"user1", "user2", "user3"}).Return(testChannel, nil).Once()
- channel, err = remote.GetGroupChannel([]string{"user1", "user2", "user3"})
- assert.Equal(t, testChannel, channel)
- assert.Nil(t, err)
-
- api.On("UpdateChannel", mock.AnythingOfType("*model.Channel")).Return(func(c *model.Channel) *model.Channel {
- return c
- }, nil).Once()
- channel, err = remote.UpdateChannel(testChannel)
- assert.Equal(t, testChannel, channel)
- assert.Nil(t, err)
-
- api.On("AddChannelMember", testChannel.Id, "theuserid").Return(testChannelMember, nil).Once()
- member, err := remote.AddChannelMember(testChannel.Id, "theuserid")
- assert.Equal(t, testChannelMember, member)
- assert.Nil(t, err)
-
- api.On("GetChannelMember", "thechannelid", "theuserid").Return(testChannelMember, nil).Once()
- member, err = remote.GetChannelMember("thechannelid", "theuserid")
- assert.Equal(t, testChannelMember, member)
- assert.Nil(t, err)
-
- api.On("UpdateChannelMemberRoles", testChannel.Id, "theuserid", model.CHANNEL_ADMIN_ROLE_ID).Return(testChannelMember, nil).Once()
- member, err = remote.UpdateChannelMemberRoles(testChannel.Id, "theuserid", model.CHANNEL_ADMIN_ROLE_ID)
- assert.Equal(t, testChannelMember, member)
- assert.Nil(t, err)
-
- notifications := map[string]string{}
- notifications[model.MARK_UNREAD_NOTIFY_PROP] = model.CHANNEL_MARK_UNREAD_MENTION
- api.On("UpdateChannelMemberNotifications", testChannel.Id, "theuserid", notifications).Return(testChannelMember, nil).Once()
- member, err = remote.UpdateChannelMemberNotifications(testChannel.Id, "theuserid", notifications)
- assert.Equal(t, testChannelMember, member)
- assert.Nil(t, err)
-
- api.On("DeleteChannelMember", "thechannelid", "theuserid").Return(nil).Once()
- err = remote.DeleteChannelMember("thechannelid", "theuserid")
- assert.Nil(t, err)
-
- api.On("CreateUser", mock.AnythingOfType("*model.User")).Return(func(u *model.User) *model.User {
- u.Id = "theuserid"
- return u
- }, nil).Once()
- user, err := remote.CreateUser(testUser)
- assert.Equal(t, "theuserid", user.Id)
- assert.Nil(t, err)
-
- api.On("DeleteUser", "theuserid").Return(nil).Once()
- assert.Nil(t, remote.DeleteUser("theuserid"))
-
- api.On("GetUser", "theuserid").Return(testUser, nil).Once()
- user, err = remote.GetUser("theuserid")
- assert.Equal(t, testUser, user)
- assert.Nil(t, err)
-
- api.On("GetUserByEmail", "foo@foo").Return(testUser, nil).Once()
- user, err = remote.GetUserByEmail("foo@foo")
- assert.Equal(t, testUser, user)
- assert.Nil(t, err)
-
- api.On("GetUserByUsername", "foo").Return(testUser, nil).Once()
- user, err = remote.GetUserByUsername("foo")
- assert.Equal(t, testUser, user)
- assert.Nil(t, err)
-
- api.On("UpdateUser", mock.AnythingOfType("*model.User")).Return(func(u *model.User) *model.User {
- return u
- }, nil).Once()
- user, err = remote.UpdateUser(testUser)
- assert.Equal(t, testUser, user)
- assert.Nil(t, err)
-
- api.On("CreateTeam", mock.AnythingOfType("*model.Team")).Return(func(t *model.Team) *model.Team {
- t.Id = "theteamid"
- return t
- }, nil).Once()
- team, err := remote.CreateTeam(testTeam)
- assert.Equal(t, "theteamid", team.Id)
- assert.Nil(t, err)
-
- api.On("DeleteTeam", "theteamid").Return(nil).Once()
- assert.Nil(t, remote.DeleteTeam("theteamid"))
-
- api.On("GetTeam", "theteamid").Return(testTeam, nil).Once()
- team, err = remote.GetTeam("theteamid")
- assert.Equal(t, testTeam, team)
- assert.Nil(t, err)
-
- api.On("GetTeamByName", "foo").Return(testTeam, nil).Once()
- team, err = remote.GetTeamByName("foo")
- assert.Equal(t, testTeam, team)
- assert.Nil(t, err)
-
- api.On("GetTeamByName", "notateam").Return(nil, teamNotFoundError).Once()
- team, err = remote.GetTeamByName("notateam")
- assert.Nil(t, team)
- assert.Equal(t, teamNotFoundError, err)
-
- api.On("UpdateTeam", mock.AnythingOfType("*model.Team")).Return(func(t *model.Team) *model.Team {
- return t
- }, nil).Once()
- team, err = remote.UpdateTeam(testTeam)
- assert.Equal(t, testTeam, team)
- assert.Nil(t, err)
-
- api.On("CreatePost", mock.AnythingOfType("*model.Post")).Return(func(p *model.Post) *model.Post {
- p.Id = "thepostid"
- return p
- }, nil).Once()
- post, err := remote.CreatePost(testPost)
- require.Nil(t, err)
- assert.NotEmpty(t, post.Id)
- assert.Equal(t, testPost.Message, post.Message)
-
- api.On("DeletePost", "thepostid").Return(nil).Once()
- assert.Nil(t, remote.DeletePost("thepostid"))
-
- api.On("GetPost", "thepostid").Return(testPost, nil).Once()
- post, err = remote.GetPost("thepostid")
- assert.Equal(t, testPost, post)
- assert.Nil(t, err)
-
- api.On("UpdatePost", mock.AnythingOfType("*model.Post")).Return(func(p *model.Post) *model.Post {
- return p
- }, nil).Once()
- post, err = remote.UpdatePost(testPost)
- assert.Equal(t, testPost, post)
- assert.Nil(t, err)
-
- api.KeyValueStore().(*plugintest.KeyValueStore).On("Set", "thekey", []byte("thevalue")).Return(nil).Once()
- err = remote.KeyValueStore().Set("thekey", []byte("thevalue"))
- assert.Nil(t, err)
-
- api.KeyValueStore().(*plugintest.KeyValueStore).On("Get", "thekey").Return(func(key string) []byte {
- return []byte("thevalue")
- }, nil).Once()
- ret, err := remote.KeyValueStore().Get("thekey")
- assert.Nil(t, err)
- assert.Equal(t, []byte("thevalue"), ret)
-
- api.KeyValueStore().(*plugintest.KeyValueStore).On("Delete", "thekey").Return(nil).Once()
- err = remote.KeyValueStore().Delete("thekey")
- assert.Nil(t, err)
- })
-}
-
-func TestAPI_GobRegistration(t *testing.T) {
- keyValueStore := &plugintest.KeyValueStore{}
- api := plugintest.API{Store: keyValueStore}
- defer api.AssertExpectations(t)
-
- testAPIRPC(&api, func(remote plugin.API) {
- api.On("CreatePost", mock.AnythingOfType("*model.Post")).Return(func(p *model.Post) *model.Post {
- p.Id = "thepostid"
- return p
- }, nil).Once()
- _, err := remote.CreatePost(&model.Post{
- Message: "hello",
- Props: map[string]interface{}{
- "attachments": []*model.SlackAttachment{
- &model.SlackAttachment{
- Actions: []*model.PostAction{
- &model.PostAction{
- Integration: &model.PostActionIntegration{
- Context: map[string]interface{}{
- "foo": "bar",
- "foos": []interface{}{"bar", "baz", 1, 2},
- "foo_map": map[string]interface{}{
- "1": "bar",
- "2": 2,
- },
- },
- },
- },
- },
- Timestamp: 1,
- },
- },
- },
- })
- require.Nil(t, err)
- })
-}
diff --git a/plugin/rpcplugin/hooks.go b/plugin/rpcplugin/hooks.go
deleted file mode 100644
index 6af98873a..000000000
--- a/plugin/rpcplugin/hooks.go
+++ /dev/null
@@ -1,398 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package rpcplugin
-
-import (
- "bytes"
- "io"
- "io/ioutil"
- "net/http"
- "net/rpc"
- "reflect"
-
- "github.com/mattermost/mattermost-server/mlog"
- "github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/plugin"
-)
-
-type LocalHooks struct {
- hooks interface{}
- muxer *Muxer
- remoteAPI *RemoteAPI
-}
-
-// Implemented replies with the names of the hooks that are implemented.
-func (h *LocalHooks) Implemented(args struct{}, reply *[]string) error {
- ifaceType := reflect.TypeOf((*plugin.Hooks)(nil)).Elem()
- implType := reflect.TypeOf(h.hooks)
- selfType := reflect.TypeOf(h)
- var methods []string
- for i := 0; i < ifaceType.NumMethod(); i++ {
- method := ifaceType.Method(i)
- if m, ok := implType.MethodByName(method.Name); !ok {
- continue
- } else if m.Type.NumIn() != method.Type.NumIn()+1 {
- continue
- } else if m.Type.NumOut() != method.Type.NumOut() {
- continue
- } else {
- match := true
- for j := 0; j < method.Type.NumIn(); j++ {
- if m.Type.In(j+1) != method.Type.In(j) {
- match = false
- break
- }
- }
- for j := 0; j < method.Type.NumOut(); j++ {
- if m.Type.Out(j) != method.Type.Out(j) {
- match = false
- break
- }
- }
- if !match {
- continue
- }
- }
- if _, ok := selfType.MethodByName(method.Name); !ok {
- continue
- }
- methods = append(methods, method.Name)
- }
- *reply = methods
- return nil
-}
-
-func (h *LocalHooks) OnActivate(args int64, reply *struct{}) error {
- if h.remoteAPI != nil {
- h.remoteAPI.Close()
- h.remoteAPI = nil
- }
- if hook, ok := h.hooks.(interface {
- OnActivate(plugin.API) error
- }); ok {
- stream := h.muxer.Connect(args)
- h.remoteAPI = ConnectAPI(stream, h.muxer)
- return hook.OnActivate(h.remoteAPI)
- }
- return nil
-}
-
-func (h *LocalHooks) OnDeactivate(args, reply *struct{}) (err error) {
- if hook, ok := h.hooks.(interface {
- OnDeactivate() error
- }); ok {
- err = hook.OnDeactivate()
- }
- if h.remoteAPI != nil {
- h.remoteAPI.Close()
- h.remoteAPI = nil
- }
- return
-}
-
-func (h *LocalHooks) OnConfigurationChange(args, reply *struct{}) error {
- if hook, ok := h.hooks.(interface {
- OnConfigurationChange() error
- }); ok {
- return hook.OnConfigurationChange()
- }
- return nil
-}
-
-type ServeHTTPArgs struct {
- ResponseWriterStream int64
- Request *http.Request
- RequestBodyStream int64
-}
-
-func (h *LocalHooks) ServeHTTP(args ServeHTTPArgs, reply *struct{}) error {
- w := ConnectHTTPResponseWriter(h.muxer.Connect(args.ResponseWriterStream))
- defer w.Close()
-
- r := args.Request
- if args.RequestBodyStream != 0 {
- r.Body = ConnectIOReader(h.muxer.Connect(args.RequestBodyStream))
- } else {
- r.Body = ioutil.NopCloser(&bytes.Buffer{})
- }
- defer r.Body.Close()
-
- if hook, ok := h.hooks.(http.Handler); ok {
- hook.ServeHTTP(w, r)
- } else {
- http.NotFound(w, r)
- }
-
- return nil
-}
-
-type HooksExecuteCommandReply struct {
- Response *model.CommandResponse
- Error *model.AppError
-}
-
-func (h *LocalHooks) ExecuteCommand(args *model.CommandArgs, reply *HooksExecuteCommandReply) error {
- if hook, ok := h.hooks.(interface {
- ExecuteCommand(*model.CommandArgs) (*model.CommandResponse, *model.AppError)
- }); ok {
- reply.Response, reply.Error = hook.ExecuteCommand(args)
- }
- return nil
-}
-
-type MessageWillBeReply struct {
- Post *model.Post
- RejectionReason string
-}
-
-type MessageUpdatedArgs struct {
- NewPost *model.Post
- OldPost *model.Post
-}
-
-func (h *LocalHooks) MessageWillBePosted(args *model.Post, reply *MessageWillBeReply) error {
- if hook, ok := h.hooks.(interface {
- MessageWillBePosted(*model.Post) (*model.Post, string)
- }); ok {
- reply.Post, reply.RejectionReason = hook.MessageWillBePosted(args)
- }
- return nil
-}
-
-func (h *LocalHooks) MessageWillBeUpdated(args *MessageUpdatedArgs, reply *MessageWillBeReply) error {
- if hook, ok := h.hooks.(interface {
- MessageWillBeUpdated(*model.Post, *model.Post) (*model.Post, string)
- }); ok {
- reply.Post, reply.RejectionReason = hook.MessageWillBeUpdated(args.NewPost, args.OldPost)
- }
- return nil
-}
-
-func (h *LocalHooks) MessageHasBeenPosted(args *model.Post, reply *struct{}) error {
- if hook, ok := h.hooks.(interface {
- MessageHasBeenPosted(*model.Post)
- }); ok {
- hook.MessageHasBeenPosted(args)
- }
- return nil
-}
-
-func (h *LocalHooks) MessageHasBeenUpdated(args *MessageUpdatedArgs, reply *struct{}) error {
- if hook, ok := h.hooks.(interface {
- MessageHasBeenUpdated(*model.Post, *model.Post)
- }); ok {
- hook.MessageHasBeenUpdated(args.NewPost, args.OldPost)
- }
- return nil
-}
-
-func ServeHooks(hooks interface{}, conn io.ReadWriteCloser, muxer *Muxer) {
- server := rpc.NewServer()
- server.Register(&LocalHooks{
- hooks: hooks,
- muxer: muxer,
- })
- server.ServeConn(conn)
-}
-
-// These assignments are part of the wire protocol. You can add more, but should not change existing
-// assignments.
-const (
- remoteOnActivate = 0
- remoteOnDeactivate = 1
- remoteServeHTTP = 2
- remoteOnConfigurationChange = 3
- remoteExecuteCommand = 4
- remoteMessageWillBePosted = 5
- remoteMessageWillBeUpdated = 6
- remoteMessageHasBeenPosted = 7
- remoteMessageHasBeenUpdated = 8
- maxRemoteHookCount = iota
-)
-
-type RemoteHooks struct {
- client *rpc.Client
- muxer *Muxer
- apiCloser io.Closer
- implemented [maxRemoteHookCount]bool
- pluginId string
-}
-
-var _ plugin.Hooks = (*RemoteHooks)(nil)
-
-func (h *RemoteHooks) Implemented() (impl []string, err error) {
- err = h.client.Call("LocalHooks.Implemented", struct{}{}, &impl)
- return
-}
-
-func (h *RemoteHooks) OnActivate(api plugin.API) error {
- if h.apiCloser != nil {
- h.apiCloser.Close()
- h.apiCloser = nil
- }
- if !h.implemented[remoteOnActivate] {
- return nil
- }
- id, stream := h.muxer.Serve()
- h.apiCloser = stream
- go ServeAPI(api, stream, h.muxer)
- return h.client.Call("LocalHooks.OnActivate", id, nil)
-}
-
-func (h *RemoteHooks) OnDeactivate() error {
- if !h.implemented[remoteOnDeactivate] {
- return nil
- }
- return h.client.Call("LocalHooks.OnDeactivate", struct{}{}, nil)
-}
-
-func (h *RemoteHooks) OnConfigurationChange() error {
- if !h.implemented[remoteOnConfigurationChange] {
- return nil
- }
- return h.client.Call("LocalHooks.OnConfigurationChange", struct{}{}, nil)
-}
-
-func (h *RemoteHooks) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- if !h.implemented[remoteServeHTTP] {
- http.NotFound(w, r)
- return
- }
-
- responseWriterStream, stream := h.muxer.Serve()
- defer stream.Close()
- go ServeHTTPResponseWriter(w, stream)
-
- requestBodyStream := int64(0)
- if r.Body != nil {
- rid, rstream := h.muxer.Serve()
- defer rstream.Close()
- go ServeIOReader(r.Body, rstream)
- requestBodyStream = rid
- }
-
- forwardedRequest := &http.Request{
- Method: r.Method,
- URL: r.URL,
- Proto: r.Proto,
- ProtoMajor: r.ProtoMajor,
- ProtoMinor: r.ProtoMinor,
- Header: r.Header,
- Host: r.Host,
- RemoteAddr: r.RemoteAddr,
- RequestURI: r.RequestURI,
- }
-
- if err := h.client.Call("LocalHooks.ServeHTTP", ServeHTTPArgs{
- ResponseWriterStream: responseWriterStream,
- Request: forwardedRequest,
- RequestBodyStream: requestBodyStream,
- }, nil); err != nil {
- mlog.Error("Plugin failed to ServeHTTP", mlog.String("plugin_id", h.pluginId), mlog.Err(err))
- http.Error(w, "500 internal server error", http.StatusInternalServerError)
- }
-}
-
-func (h *RemoteHooks) ExecuteCommand(args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
- if !h.implemented[remoteExecuteCommand] {
- return nil, model.NewAppError("RemoteHooks.ExecuteCommand", "plugin.rpcplugin.invocation.error", nil, "err=ExecuteCommand hook not implemented", http.StatusInternalServerError)
- }
- var reply HooksExecuteCommandReply
- if err := h.client.Call("LocalHooks.ExecuteCommand", args, &reply); err != nil {
- return nil, model.NewAppError("RemoteHooks.ExecuteCommand", "plugin.rpcplugin.invocation.error", nil, "err="+err.Error(), http.StatusInternalServerError)
- }
- return reply.Response, reply.Error
-}
-
-func (h *RemoteHooks) MessageWillBePosted(args *model.Post) (*model.Post, string) {
- if !h.implemented[remoteMessageWillBePosted] {
- return args, ""
- }
- var reply MessageWillBeReply
- if err := h.client.Call("LocalHooks.MessageWillBePosted", args, &reply); err != nil {
- return nil, ""
- }
- return reply.Post, reply.RejectionReason
-}
-
-func (h *RemoteHooks) MessageWillBeUpdated(newPost, oldPost *model.Post) (*model.Post, string) {
- if !h.implemented[remoteMessageWillBeUpdated] {
- return newPost, ""
- }
- var reply MessageWillBeReply
- args := &MessageUpdatedArgs{
- NewPost: newPost,
- OldPost: oldPost,
- }
- if err := h.client.Call("LocalHooks.MessageWillBeUpdated", args, &reply); err != nil {
- return nil, ""
- }
- return reply.Post, reply.RejectionReason
-}
-
-func (h *RemoteHooks) MessageHasBeenPosted(args *model.Post) {
- if !h.implemented[remoteMessageHasBeenPosted] {
- return
- }
- if err := h.client.Call("LocalHooks.MessageHasBeenPosted", args, nil); err != nil {
- return
- }
-}
-
-func (h *RemoteHooks) MessageHasBeenUpdated(newPost, oldPost *model.Post) {
- if !h.implemented[remoteMessageHasBeenUpdated] {
- return
- }
- args := &MessageUpdatedArgs{
- NewPost: newPost,
- OldPost: oldPost,
- }
- if err := h.client.Call("LocalHooks.MessageHasBeenUpdated", args, nil); err != nil {
- return
- }
-}
-
-func (h *RemoteHooks) Close() error {
- if h.apiCloser != nil {
- h.apiCloser.Close()
- h.apiCloser = nil
- }
- return h.client.Close()
-}
-
-func ConnectHooks(conn io.ReadWriteCloser, muxer *Muxer, pluginId string) (*RemoteHooks, error) {
- remote := &RemoteHooks{
- client: rpc.NewClient(conn),
- muxer: muxer,
- pluginId: pluginId,
- }
- implemented, err := remote.Implemented()
- if err != nil {
- remote.Close()
- return nil, err
- }
- for _, method := range implemented {
- switch method {
- case "OnActivate":
- remote.implemented[remoteOnActivate] = true
- case "OnDeactivate":
- remote.implemented[remoteOnDeactivate] = true
- case "OnConfigurationChange":
- remote.implemented[remoteOnConfigurationChange] = true
- case "ServeHTTP":
- remote.implemented[remoteServeHTTP] = true
- case "ExecuteCommand":
- remote.implemented[remoteExecuteCommand] = true
- case "MessageWillBePosted":
- remote.implemented[remoteMessageWillBePosted] = true
- case "MessageWillBeUpdated":
- remote.implemented[remoteMessageWillBeUpdated] = true
- case "MessageHasBeenPosted":
- remote.implemented[remoteMessageHasBeenPosted] = true
- case "MessageHasBeenUpdated":
- remote.implemented[remoteMessageHasBeenUpdated] = true
- }
- }
- return remote, nil
-}
diff --git a/plugin/rpcplugin/hooks_test.go b/plugin/rpcplugin/hooks_test.go
deleted file mode 100644
index a7bac982e..000000000
--- a/plugin/rpcplugin/hooks_test.go
+++ /dev/null
@@ -1,237 +0,0 @@
-package rpcplugin
-
-import (
- "io"
- "io/ioutil"
- "net/http"
- "net/http/httptest"
- "strings"
- "sync"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
- "github.com/stretchr/testify/require"
-
- "github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/plugin"
- "github.com/mattermost/mattermost-server/plugin/plugintest"
-)
-
-func testHooksRPC(hooks interface{}, f func(*RemoteHooks)) error {
- r1, w1 := io.Pipe()
- r2, w2 := io.Pipe()
-
- c1 := NewMuxer(NewReadWriteCloser(r1, w2), false)
- defer c1.Close()
-
- c2 := NewMuxer(NewReadWriteCloser(r2, w1), true)
- defer c2.Close()
-
- id, server := c1.Serve()
- go ServeHooks(hooks, server, c1)
-
- remote, err := ConnectHooks(c2.Connect(id), c2, "plugin_id")
- if err != nil {
- return err
- }
- defer remote.Close()
-
- f(remote)
- return nil
-}
-
-func TestHooks(t *testing.T) {
- var api plugintest.API
- var hooks plugintest.Hooks
- defer hooks.AssertExpectations(t)
-
- assert.NoError(t, testHooksRPC(&hooks, func(remote *RemoteHooks) {
- hooks.On("OnActivate", mock.AnythingOfType("*rpcplugin.RemoteAPI")).Return(nil)
- assert.NoError(t, remote.OnActivate(&api))
-
- hooks.On("OnDeactivate").Return(nil)
- assert.NoError(t, remote.OnDeactivate())
-
- hooks.On("OnConfigurationChange").Return(nil)
- assert.NoError(t, remote.OnConfigurationChange())
-
- hooks.On("ServeHTTP", mock.AnythingOfType("*rpcplugin.RemoteHTTPResponseWriter"), mock.AnythingOfType("*http.Request")).Run(func(args mock.Arguments) {
- w := args.Get(0).(http.ResponseWriter)
- r := args.Get(1).(*http.Request)
- assert.Equal(t, "/foo", r.URL.Path)
- assert.Equal(t, "POST", r.Method)
- body, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
- assert.Equal(t, "asdf", string(body))
- assert.Equal(t, "header", r.Header.Get("Test-Header"))
- w.Write([]byte("bar"))
- })
-
- w := httptest.NewRecorder()
- r, err := http.NewRequest("POST", "/foo", strings.NewReader("asdf"))
- r.Header.Set("Test-Header", "header")
- assert.NoError(t, err)
- remote.ServeHTTP(w, r)
-
- resp := w.Result()
- defer resp.Body.Close()
- assert.Equal(t, http.StatusOK, resp.StatusCode)
- body, err := ioutil.ReadAll(resp.Body)
- assert.NoError(t, err)
- assert.Equal(t, "bar", string(body))
-
- hooks.On("ExecuteCommand", &model.CommandArgs{
- Command: "/foo",
- }).Return(&model.CommandResponse{
- Text: "bar",
- }, nil)
- commandResponse, appErr := hooks.ExecuteCommand(&model.CommandArgs{
- Command: "/foo",
- })
- assert.Equal(t, "bar", commandResponse.Text)
- assert.Nil(t, appErr)
-
- hooks.On("MessageWillBePosted", mock.AnythingOfType("*model.Post")).Return(func(post *model.Post) *model.Post {
- post.Message += "_testing"
- return post
- }, "changemessage")
- post, changemessage := remote.MessageWillBePosted(&model.Post{Id: "1", Message: "base"})
- assert.Equal(t, "changemessage", changemessage)
- assert.Equal(t, "base_testing", post.Message)
- assert.Equal(t, "1", post.Id)
-
- hooks.On("MessageWillBeUpdated", mock.AnythingOfType("*model.Post"), mock.AnythingOfType("*model.Post")).Return(func(newPost, oldPost *model.Post) *model.Post {
- newPost.Message += "_testing"
- return newPost
- }, "changemessage2")
- post2, changemessage2 := remote.MessageWillBeUpdated(&model.Post{Id: "2", Message: "base2"}, &model.Post{Id: "OLD", Message: "OLDMESSAGE"})
- assert.Equal(t, "changemessage2", changemessage2)
- assert.Equal(t, "base2_testing", post2.Message)
- assert.Equal(t, "2", post2.Id)
-
- hooks.On("MessageHasBeenPosted", mock.AnythingOfType("*model.Post")).Return(nil)
- remote.MessageHasBeenPosted(&model.Post{})
-
- hooks.On("MessageHasBeenUpdated", mock.AnythingOfType("*model.Post"), mock.AnythingOfType("*model.Post")).Return(nil)
- remote.MessageHasBeenUpdated(&model.Post{}, &model.Post{})
- }))
-}
-
-func TestHooks_Concurrency(t *testing.T) {
- var hooks plugintest.Hooks
- defer hooks.AssertExpectations(t)
-
- assert.NoError(t, testHooksRPC(&hooks, func(remote *RemoteHooks) {
- ch := make(chan bool)
-
- hooks.On("ServeHTTP", mock.AnythingOfType("*rpcplugin.RemoteHTTPResponseWriter"), mock.AnythingOfType("*http.Request")).Run(func(args mock.Arguments) {
- r := args.Get(1).(*http.Request)
- if r.URL.Path == "/1" {
- <-ch
- } else {
- ch <- true
- }
- })
-
- rec := httptest.NewRecorder()
-
- wg := sync.WaitGroup{}
- wg.Add(2)
-
- go func() {
- req, err := http.NewRequest("GET", "/1", nil)
- require.NoError(t, err)
- remote.ServeHTTP(rec, req)
- wg.Done()
- }()
-
- go func() {
- req, err := http.NewRequest("GET", "/2", nil)
- require.NoError(t, err)
- remote.ServeHTTP(rec, req)
- wg.Done()
- }()
-
- wg.Wait()
- }))
-}
-
-type testHooks struct {
- mock.Mock
-}
-
-func (h *testHooks) OnActivate(api plugin.API) error {
- return h.Called(api).Error(0)
-}
-
-func TestHooks_PartiallyImplemented(t *testing.T) {
- var api plugintest.API
- var hooks testHooks
- defer hooks.AssertExpectations(t)
-
- assert.NoError(t, testHooksRPC(&hooks, func(remote *RemoteHooks) {
- implemented, err := remote.Implemented()
- assert.NoError(t, err)
- assert.Equal(t, []string{"OnActivate"}, implemented)
-
- hooks.On("OnActivate", mock.AnythingOfType("*rpcplugin.RemoteAPI")).Return(nil)
- assert.NoError(t, remote.OnActivate(&api))
-
- assert.NoError(t, remote.OnDeactivate())
- }))
-}
-
-type benchmarkHooks struct{}
-
-func (*benchmarkHooks) OnDeactivate() error { return nil }
-
-func (*benchmarkHooks) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- ioutil.ReadAll(r.Body)
- w.Header().Set("Foo-Header", "foo")
- http.Error(w, "foo", http.StatusBadRequest)
-}
-
-func BenchmarkHooks_OnDeactivate(b *testing.B) {
- var hooks benchmarkHooks
-
- if err := testHooksRPC(&hooks, func(remote *RemoteHooks) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- remote.OnDeactivate()
- }
- b.StopTimer()
- }); err != nil {
- b.Fatal(err.Error())
- }
-}
-
-func BenchmarkHooks_ServeHTTP(b *testing.B) {
- var hooks benchmarkHooks
-
- if err := testHooksRPC(&hooks, func(remote *RemoteHooks) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- w := httptest.NewRecorder()
- r, _ := http.NewRequest("POST", "/foo", strings.NewReader("12345678901234567890"))
- remote.ServeHTTP(w, r)
- }
- b.StopTimer()
- }); err != nil {
- b.Fatal(err.Error())
- }
-}
-
-func BenchmarkHooks_Unimplemented(b *testing.B) {
- var hooks testHooks
-
- if err := testHooksRPC(&hooks, func(remote *RemoteHooks) {
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
- remote.OnDeactivate()
- }
- b.StopTimer()
- }); err != nil {
- b.Fatal(err.Error())
- }
-}
diff --git a/plugin/rpcplugin/http.go b/plugin/rpcplugin/http.go
deleted file mode 100644
index 72b1aa445..000000000
--- a/plugin/rpcplugin/http.go
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package rpcplugin
-
-import (
- "io"
- "net/http"
- "net/rpc"
-)
-
-type LocalHTTPResponseWriter struct {
- w http.ResponseWriter
-}
-
-func (w *LocalHTTPResponseWriter) Header(args struct{}, reply *http.Header) error {
- *reply = w.w.Header()
- return nil
-}
-
-func (w *LocalHTTPResponseWriter) Write(args []byte, reply *struct{}) error {
- _, err := w.w.Write(args)
- return err
-}
-
-func (w *LocalHTTPResponseWriter) WriteHeader(args int, reply *struct{}) error {
- w.w.WriteHeader(args)
- return nil
-}
-
-func (w *LocalHTTPResponseWriter) SyncHeader(args http.Header, reply *struct{}) error {
- dest := w.w.Header()
- for k := range dest {
- if _, ok := args[k]; !ok {
- delete(dest, k)
- }
- }
- for k, v := range args {
- dest[k] = v
- }
- return nil
-}
-
-func ServeHTTPResponseWriter(w http.ResponseWriter, conn io.ReadWriteCloser) {
- server := rpc.NewServer()
- server.Register(&LocalHTTPResponseWriter{
- w: w,
- })
- server.ServeConn(conn)
-}
-
-type RemoteHTTPResponseWriter struct {
- client *rpc.Client
- header http.Header
-}
-
-var _ http.ResponseWriter = (*RemoteHTTPResponseWriter)(nil)
-
-func (w *RemoteHTTPResponseWriter) Header() http.Header {
- if w.header == nil {
- w.client.Call("LocalHTTPResponseWriter.Header", struct{}{}, &w.header)
- }
- return w.header
-}
-
-func (w *RemoteHTTPResponseWriter) Write(b []byte) (int, error) {
- if err := w.client.Call("LocalHTTPResponseWriter.SyncHeader", w.header, nil); err != nil {
- return 0, err
- }
- if err := w.client.Call("LocalHTTPResponseWriter.Write", b, nil); err != nil {
- return 0, err
- }
- return len(b), nil
-}
-
-func (w *RemoteHTTPResponseWriter) WriteHeader(statusCode int) {
- if err := w.client.Call("LocalHTTPResponseWriter.SyncHeader", w.header, nil); err != nil {
- return
- }
- w.client.Call("LocalHTTPResponseWriter.WriteHeader", statusCode, nil)
-}
-
-func (h *RemoteHTTPResponseWriter) Close() error {
- return h.client.Close()
-}
-
-func ConnectHTTPResponseWriter(conn io.ReadWriteCloser) *RemoteHTTPResponseWriter {
- return &RemoteHTTPResponseWriter{
- client: rpc.NewClient(conn),
- }
-}
diff --git a/plugin/rpcplugin/http_test.go b/plugin/rpcplugin/http_test.go
deleted file mode 100644
index afaaf7756..000000000
--- a/plugin/rpcplugin/http_test.go
+++ /dev/null
@@ -1,61 +0,0 @@
-package rpcplugin
-
-import (
- "io"
- "io/ioutil"
- "net/http"
- "net/http/httptest"
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func testHTTPResponseWriterRPC(w http.ResponseWriter, f func(w http.ResponseWriter)) {
- r1, w1 := io.Pipe()
- r2, w2 := io.Pipe()
-
- c1 := NewMuxer(NewReadWriteCloser(r1, w2), false)
- defer c1.Close()
-
- c2 := NewMuxer(NewReadWriteCloser(r2, w1), true)
- defer c2.Close()
-
- id, server := c1.Serve()
- go ServeHTTPResponseWriter(w, server)
-
- remote := ConnectHTTPResponseWriter(c2.Connect(id))
- defer remote.Close()
-
- f(remote)
-}
-
-func TestHTTP(t *testing.T) {
- w := httptest.NewRecorder()
-
- testHTTPResponseWriterRPC(w, func(w http.ResponseWriter) {
- headers := w.Header()
- headers.Set("Test-Header-A", "a")
- headers.Set("Test-Header-B", "b")
- w.Header().Set("Test-Header-C", "c")
- w.WriteHeader(http.StatusPaymentRequired)
- n, err := w.Write([]byte("this is "))
- assert.Equal(t, 8, n)
- assert.NoError(t, err)
- n, err = w.Write([]byte("a test"))
- assert.Equal(t, 6, n)
- assert.NoError(t, err)
- })
-
- r := w.Result()
- defer r.Body.Close()
-
- assert.Equal(t, http.StatusPaymentRequired, r.StatusCode)
-
- body, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
- assert.EqualValues(t, "this is a test", body)
-
- assert.Equal(t, "a", r.Header.Get("Test-Header-A"))
- assert.Equal(t, "b", r.Header.Get("Test-Header-B"))
- assert.Equal(t, "c", r.Header.Get("Test-Header-C"))
-}
diff --git a/plugin/rpcplugin/ipc.go b/plugin/rpcplugin/ipc.go
deleted file mode 100644
index e8dd43c04..000000000
--- a/plugin/rpcplugin/ipc.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package rpcplugin
-
-import (
- "io"
- "os"
-)
-
-// Returns a new IPC for the parent process and a set of files to pass on to the child.
-//
-// The returned files must be closed after the child process is started.
-func NewIPC() (io.ReadWriteCloser, []*os.File, error) {
- parentReader, childWriter, err := os.Pipe()
- if err != nil {
- return nil, nil, err
- }
- childReader, parentWriter, err := os.Pipe()
- if err != nil {
- parentReader.Close()
- childWriter.Close()
- return nil, nil, err
- }
- return NewReadWriteCloser(parentReader, parentWriter), []*os.File{childReader, childWriter}, nil
-}
-
-// Returns the IPC instance inherited by the process from its parent.
-func InheritedIPC(fd0, fd1 uintptr) (io.ReadWriteCloser, error) {
- return NewReadWriteCloser(os.NewFile(fd0, ""), os.NewFile(fd1, "")), nil
-}
diff --git a/plugin/rpcplugin/ipc_test.go b/plugin/rpcplugin/ipc_test.go
deleted file mode 100644
index 76699a11e..000000000
--- a/plugin/rpcplugin/ipc_test.go
+++ /dev/null
@@ -1,63 +0,0 @@
-package rpcplugin
-
-import (
- "context"
- "io/ioutil"
- "os"
- "path/filepath"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/mattermost/mattermost-server/plugin/rpcplugin/rpcplugintest"
-)
-
-func TestIPC(t *testing.T) {
- dir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(dir)
-
- pingpong := filepath.Join(dir, "pingpong.exe")
- rpcplugintest.CompileGo(t, `
- package main
-
- import (
- "log"
-
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
- )
-
- func main() {
- ipc, err := rpcplugin.InheritedProcessIPC()
- if err != nil {
- log.Fatal("unable to get inherited ipc")
- }
- defer ipc.Close()
- _, err = ipc.Write([]byte("ping"))
- if err != nil {
- log.Fatal("unable to write to ipc")
- }
- b := make([]byte, 10)
- n, err := ipc.Read(b)
- if err != nil {
- log.Fatal("unable to read from ipc")
- }
- if n != 4 || string(b[:4]) != "pong" {
- log.Fatal("unexpected response")
- }
- }
- `, pingpong)
-
- p, ipc, err := NewProcess(context.Background(), pingpong)
- require.NoError(t, err)
- defer ipc.Close()
- b := make([]byte, 10)
- n, err := ipc.Read(b)
- require.NoError(t, err)
- assert.Equal(t, 4, n)
- assert.Equal(t, "ping", string(b[:4]))
- _, err = ipc.Write([]byte("pong"))
- require.NoError(t, err)
- require.NoError(t, p.Wait())
-}
diff --git a/plugin/rpcplugin/main.go b/plugin/rpcplugin/main.go
deleted file mode 100644
index efb880605..000000000
--- a/plugin/rpcplugin/main.go
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package rpcplugin
-
-import (
- "bufio"
- "encoding/binary"
- "fmt"
- "log"
- "os"
-)
-
-// Makes a set of hooks available via RPC. This function never returns.
-func Main(hooks interface{}) {
- ipc, err := InheritedProcessIPC()
- if err != nil {
- log.Fatal(err.Error())
- }
- muxer := NewMuxer(ipc, true)
- id, conn := muxer.Serve()
- buf := make([]byte, 11)
- buf[0] = 0
- n := binary.PutVarint(buf[1:], id)
- if _, err := muxer.Write(buf[:1+n]); err != nil {
- log.Fatal(err.Error())
- }
- ServeHooks(hooks, conn, muxer)
- os.Exit(0)
-}
-
-// Returns the hooks being served by a call to Main.
-func ConnectMain(muxer *Muxer, pluginId string) (*RemoteHooks, error) {
- buf := make([]byte, 1)
- if _, err := muxer.Read(buf); err != nil {
- return nil, err
- } else if buf[0] != 0 {
- return nil, fmt.Errorf("unexpected control byte")
- }
- reader := bufio.NewReader(muxer)
- id, err := binary.ReadVarint(reader)
- if err != nil {
- return nil, err
- }
-
- return ConnectHooks(muxer.Connect(id), muxer, pluginId)
-}
diff --git a/plugin/rpcplugin/main_test.go b/plugin/rpcplugin/main_test.go
deleted file mode 100644
index 06423106c..000000000
--- a/plugin/rpcplugin/main_test.go
+++ /dev/null
@@ -1,63 +0,0 @@
-package rpcplugin
-
-import (
- "context"
- "io/ioutil"
- "os"
- "path/filepath"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/mattermost/mattermost-server/mlog"
- "github.com/mattermost/mattermost-server/plugin/plugintest"
- "github.com/mattermost/mattermost-server/plugin/rpcplugin/rpcplugintest"
-)
-
-func TestMain(t *testing.T) {
- // Setup a global logger to catch tests logging outside of app context
- // The global logger will be stomped by apps initalizing but that's fine for testing. Ideally this won't happen.
- mlog.InitGlobalLogger(mlog.NewLogger(&mlog.LoggerConfiguration{
- EnableConsole: true,
- ConsoleJson: true,
- ConsoleLevel: "error",
- EnableFile: false,
- }))
-
- dir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(dir)
-
- plugin := filepath.Join(dir, "plugin.exe")
- rpcplugintest.CompileGo(t, `
- package main
-
- import (
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
- )
-
- type MyPlugin struct {}
-
- func main() {
- rpcplugin.Main(&MyPlugin{})
- }
- `, plugin)
-
- ctx, cancel := context.WithCancel(context.Background())
- p, ipc, err := NewProcess(ctx, plugin)
- require.NoError(t, err)
- defer p.Wait()
-
- muxer := NewMuxer(ipc, false)
- defer muxer.Close()
-
- defer cancel()
-
- var api plugintest.API
-
- hooks, err := ConnectMain(muxer, "plugin_id")
- require.NoError(t, err)
- assert.NoError(t, hooks.OnActivate(&api))
- assert.NoError(t, hooks.OnDeactivate())
-}
diff --git a/plugin/rpcplugin/muxer.go b/plugin/rpcplugin/muxer.go
deleted file mode 100644
index a7260c399..000000000
--- a/plugin/rpcplugin/muxer.go
+++ /dev/null
@@ -1,264 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package rpcplugin
-
-import (
- "bufio"
- "bytes"
- "encoding/binary"
- "fmt"
- "io"
- "sync"
- "sync/atomic"
-)
-
-// Muxer allows multiple bidirectional streams to be transmitted over a single connection.
-//
-// Muxer is safe for use by multiple goroutines.
-//
-// Streams opened on the muxer must be periodically drained in order to reclaim read buffer memory.
-// In other words, readers must consume incoming data as it comes in.
-type Muxer struct {
- // writeMutex guards conn writes
- writeMutex sync.Mutex
- conn io.ReadWriteCloser
-
- // didCloseConn is a boolean (0 or 1) used from multiple goroutines via atomic operations
- didCloseConn int32
-
- // streamsMutex guards streams and nextId
- streamsMutex sync.Mutex
- nextId int64
- streams map[int64]*muxerStream
-
- stream0Reader *io.PipeReader
- stream0Writer *io.PipeWriter
- result chan error
-}
-
-// Creates a new Muxer.
-//
-// conn must be safe for simultaneous reads by one goroutine and writes by another.
-//
-// For two muxers communicating with each other via a connection, parity must be true for exactly
-// one of them.
-func NewMuxer(conn io.ReadWriteCloser, parity bool) *Muxer {
- s0r, s0w := io.Pipe()
- muxer := &Muxer{
- conn: conn,
- streams: make(map[int64]*muxerStream),
- result: make(chan error, 1),
- nextId: 1,
- stream0Reader: s0r,
- stream0Writer: s0w,
- }
- if parity {
- muxer.nextId = 2
- }
- go muxer.run()
- return muxer
-}
-
-// Opens a new stream with a unique id.
-//
-// Writes made to the stream before the other end calls Connect will be discarded.
-func (m *Muxer) Serve() (int64, io.ReadWriteCloser) {
- m.streamsMutex.Lock()
- id := m.nextId
- m.nextId += 2
- m.streamsMutex.Unlock()
- return id, m.Connect(id)
-}
-
-// Opens a remotely opened stream.
-func (m *Muxer) Connect(id int64) io.ReadWriteCloser {
- m.streamsMutex.Lock()
- defer m.streamsMutex.Unlock()
- mutex := &sync.Mutex{}
- stream := &muxerStream{
- id: id,
- muxer: m,
- mutex: mutex,
- readWake: sync.NewCond(mutex),
- }
- m.streams[id] = stream
- return stream
-}
-
-// Calling Read on the muxer directly performs a read on a dedicated, always-open channel.
-func (m *Muxer) Read(p []byte) (int, error) {
- return m.stream0Reader.Read(p)
-}
-
-// Calling Write on the muxer directly performs a write on a dedicated, always-open channel.
-func (m *Muxer) Write(p []byte) (int, error) {
- return m.write(p, 0)
-}
-
-// Closes the muxer.
-func (m *Muxer) Close() error {
- if atomic.CompareAndSwapInt32(&m.didCloseConn, 0, 1) {
- m.conn.Close()
- }
- m.stream0Reader.Close()
- m.stream0Writer.Close()
- <-m.result
- return nil
-}
-
-func (m *Muxer) IsClosed() bool {
- return atomic.LoadInt32(&m.didCloseConn) > 0
-}
-
-func (m *Muxer) write(p []byte, sid int64) (int, error) {
- m.writeMutex.Lock()
- defer m.writeMutex.Unlock()
- if m.IsClosed() {
- return 0, fmt.Errorf("muxer closed")
- }
- var buf [10]byte
- n := binary.PutVarint(buf[:], sid)
- if _, err := m.conn.Write(buf[:n]); err != nil {
- m.shutdown(err)
- return 0, err
- }
- n = binary.PutVarint(buf[:], int64(len(p)))
- if _, err := m.conn.Write(buf[:n]); err != nil {
- m.shutdown(err)
- return 0, err
- }
- if len(p) > 0 {
- if _, err := m.conn.Write(p); err != nil {
- m.shutdown(err)
- return 0, err
- }
- }
- return len(p), nil
-}
-
-func (m *Muxer) rm(sid int64) {
- m.streamsMutex.Lock()
- defer m.streamsMutex.Unlock()
- delete(m.streams, sid)
-}
-
-func (m *Muxer) run() {
- m.shutdown(m.loop())
-}
-
-func (m *Muxer) loop() error {
- reader := bufio.NewReader(m.conn)
-
- for {
- sid, err := binary.ReadVarint(reader)
- if err != nil {
- return err
- }
- len, err := binary.ReadVarint(reader)
- if err != nil {
- return err
- }
-
- if sid == 0 {
- if _, err := io.CopyN(m.stream0Writer, reader, len); err != nil {
- return err
- }
- continue
- }
-
- m.streamsMutex.Lock()
- stream, ok := m.streams[sid]
- m.streamsMutex.Unlock()
- if !ok {
- if _, err := reader.Discard(int(len)); err != nil {
- return err
- }
- continue
- }
-
- stream.mutex.Lock()
- if stream.isClosed {
- stream.mutex.Unlock()
- if _, err := reader.Discard(int(len)); err != nil {
- return err
- }
- continue
- }
- if len == 0 {
- stream.remoteClosed = true
- } else {
- _, err = io.CopyN(&stream.readBuf, reader, len)
- }
- stream.mutex.Unlock()
- if err != nil {
- return err
- }
- stream.readWake.Signal()
- }
-}
-
-func (m *Muxer) shutdown(err error) {
- if atomic.CompareAndSwapInt32(&m.didCloseConn, 0, 1) {
- m.conn.Close()
- }
- go func() {
- m.streamsMutex.Lock()
- for _, stream := range m.streams {
- stream.mutex.Lock()
- stream.readWake.Signal()
- stream.mutex.Unlock()
- }
- m.streams = make(map[int64]*muxerStream)
- m.streamsMutex.Unlock()
- }()
- m.result <- err
-}
-
-type muxerStream struct {
- id int64
- muxer *Muxer
- readBuf bytes.Buffer
- mutex *sync.Mutex
- readWake *sync.Cond
- isClosed bool
- remoteClosed bool
-}
-
-func (s *muxerStream) Read(p []byte) (int, error) {
- s.mutex.Lock()
- defer s.mutex.Unlock()
- for {
- if s.muxer.IsClosed() {
- return 0, fmt.Errorf("muxer closed")
- } else if s.isClosed {
- return 0, io.EOF
- } else if s.readBuf.Len() > 0 {
- return s.readBuf.Read(p)
- } else if s.remoteClosed {
- return 0, io.EOF
- }
- s.readWake.Wait()
- }
-}
-
-func (s *muxerStream) Write(p []byte) (int, error) {
- s.mutex.Lock()
- defer s.mutex.Unlock()
- if s.isClosed {
- return 0, fmt.Errorf("stream closed")
- }
- return s.muxer.write(p, s.id)
-}
-
-func (s *muxerStream) Close() error {
- s.mutex.Lock()
- defer s.mutex.Unlock()
- if !s.isClosed {
- s.muxer.write(nil, s.id)
- s.isClosed = true
- s.muxer.rm(s.id)
- }
- s.readWake.Signal()
- return nil
-}
diff --git a/plugin/rpcplugin/muxer_test.go b/plugin/rpcplugin/muxer_test.go
deleted file mode 100644
index 795a4fb1d..000000000
--- a/plugin/rpcplugin/muxer_test.go
+++ /dev/null
@@ -1,197 +0,0 @@
-package rpcplugin
-
-import (
- "io"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-func TestMuxer(t *testing.T) {
- r1, w1 := io.Pipe()
- r2, w2 := io.Pipe()
-
- alice := NewMuxer(NewReadWriteCloser(r1, w2), false)
- defer func() { assert.NoError(t, alice.Close()) }()
-
- bob := NewMuxer(NewReadWriteCloser(r2, w1), true)
- defer func() { assert.NoError(t, bob.Close()) }()
-
- id1, alice1 := alice.Serve()
- defer func() { assert.NoError(t, alice1.Close()) }()
-
- id2, bob2 := bob.Serve()
- defer func() { assert.NoError(t, bob2.Close()) }()
-
- done1 := make(chan bool)
- done2 := make(chan bool)
-
- go func() {
- bob1 := bob.Connect(id1)
- defer func() { assert.NoError(t, bob1.Close()) }()
-
- n, err := bob1.Write([]byte("ping1.0"))
- require.NoError(t, err)
- assert.Equal(t, n, 7)
-
- n, err = bob1.Write([]byte("ping1.1"))
- require.NoError(t, err)
- assert.Equal(t, n, 7)
- }()
-
- go func() {
- alice2 := alice.Connect(id2)
- defer func() { assert.NoError(t, alice2.Close()) }()
-
- n, err := alice2.Write([]byte("ping2.0"))
- require.NoError(t, err)
- assert.Equal(t, n, 7)
-
- buf := make([]byte, 20)
- n, err = alice2.Read(buf)
- require.NoError(t, err)
- assert.Equal(t, n, 7)
- assert.Equal(t, []byte("pong2.0"), buf[:n])
-
- done2 <- true
- }()
-
- go func() {
- buf := make([]byte, 7)
- n, err := io.ReadFull(alice1, buf)
- require.NoError(t, err)
- assert.Equal(t, n, 7)
- assert.Equal(t, []byte("ping1.0"), buf[:n])
-
- n, err = alice1.Read(buf)
- require.NoError(t, err)
- assert.Equal(t, n, 7)
- assert.Equal(t, []byte("ping1.1"), buf[:n])
-
- done1 <- true
- }()
-
- go func() {
- buf := make([]byte, 20)
- n, err := bob2.Read(buf)
- require.NoError(t, err)
- assert.Equal(t, n, 7)
- assert.Equal(t, []byte("ping2.0"), buf[:n])
-
- n, err = bob2.Write([]byte("pong2.0"))
- require.NoError(t, err)
- assert.Equal(t, n, 7)
- }()
-
- <-done1
- <-done2
-}
-
-// Closing a muxer during a read should unblock, but return an error.
-func TestMuxer_CloseDuringRead(t *testing.T) {
- r1, w1 := io.Pipe()
- r2, w2 := io.Pipe()
-
- alice := NewMuxer(NewReadWriteCloser(r1, w2), false)
-
- bob := NewMuxer(NewReadWriteCloser(r2, w1), true)
- defer func() { assert.NoError(t, bob.Close()) }()
-
- _, s := alice.Serve()
-
- go alice.Close()
- buf := make([]byte, 20)
- n, err := s.Read(buf)
- assert.Equal(t, 0, n)
- assert.NotNil(t, err)
- assert.NotEqual(t, io.EOF, err)
-}
-
-// Closing a stream during a read should unblock and return io.EOF since this is the way to
-// gracefully close a connection.
-func TestMuxer_StreamCloseDuringRead(t *testing.T) {
- r1, w1 := io.Pipe()
- r2, w2 := io.Pipe()
-
- alice := NewMuxer(NewReadWriteCloser(r1, w2), false)
- defer func() { assert.NoError(t, alice.Close()) }()
-
- bob := NewMuxer(NewReadWriteCloser(r2, w1), true)
- defer func() { assert.NoError(t, bob.Close()) }()
-
- _, s := alice.Serve()
-
- go s.Close()
- buf := make([]byte, 20)
- n, err := s.Read(buf)
- assert.Equal(t, 0, n)
- assert.Equal(t, io.EOF, err)
-}
-
-// Closing a stream during a read should unblock and return io.EOF since this is the way for the
-// remote to gracefully close a connection.
-func TestMuxer_RemoteStreamCloseDuringRead(t *testing.T) {
- r1, w1 := io.Pipe()
- r2, w2 := io.Pipe()
-
- alice := NewMuxer(NewReadWriteCloser(r1, w2), false)
- defer func() { assert.NoError(t, alice.Close()) }()
-
- bob := NewMuxer(NewReadWriteCloser(r2, w1), true)
- defer func() { assert.NoError(t, bob.Close()) }()
-
- id, as := alice.Serve()
- bs := bob.Connect(id)
-
- go func() {
- as.Write([]byte("foo"))
- as.Close()
- }()
- buf := make([]byte, 20)
- n, err := bs.Read(buf)
- assert.Equal(t, 3, n)
- assert.Equal(t, "foo", string(buf[:n]))
- n, err = bs.Read(buf)
- assert.Equal(t, 0, n)
- assert.Equal(t, io.EOF, err)
-}
-
-// Closing a muxer during a write should unblock, but return an error.
-func TestMuxer_CloseDuringWrite(t *testing.T) {
- r1, w1 := io.Pipe()
- r2, w2 := io.Pipe()
-
- alice := NewMuxer(NewReadWriteCloser(r1, w2), false)
-
- // Don't connect bob to let writes will block forever.
- defer r2.Close()
- defer w1.Close()
-
- _, s := alice.Serve()
-
- go alice.Close()
- buf := make([]byte, 20)
- n, err := s.Write(buf)
- assert.Equal(t, 0, n)
- assert.NotNil(t, err)
- assert.NotEqual(t, io.EOF, err)
-}
-
-func TestMuxer_ReadWrite(t *testing.T) {
- r1, w1 := io.Pipe()
- r2, w2 := io.Pipe()
-
- alice := NewMuxer(NewReadWriteCloser(r1, w2), false)
- defer func() { assert.NoError(t, alice.Close()) }()
-
- bob := NewMuxer(NewReadWriteCloser(r2, w1), true)
- defer func() { assert.NoError(t, bob.Close()) }()
-
- go alice.Write([]byte("hello"))
- buf := make([]byte, 20)
- n, err := bob.Read(buf)
- assert.Equal(t, 5, n)
- assert.Nil(t, err)
- assert.Equal(t, []byte("hello"), buf[:n])
-}
diff --git a/plugin/rpcplugin/process.go b/plugin/rpcplugin/process.go
deleted file mode 100644
index a795be133..000000000
--- a/plugin/rpcplugin/process.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package rpcplugin
-
-import (
- "context"
- "io"
-)
-
-type Process interface {
- // Waits for the process to exit and returns an error if a problem occurred or the process exited
- // with a non-zero status.
- Wait() error
-}
-
-// NewProcess launches an RPC executable in a new process and returns an IPC that can be used to
-// communicate with it.
-func NewProcess(ctx context.Context, path string) (Process, io.ReadWriteCloser, error) {
- return newProcess(ctx, path)
-}
-
-// When called on a process launched with NewProcess, returns the inherited IPC.
-func InheritedProcessIPC() (io.ReadWriteCloser, error) {
- return inheritedProcessIPC()
-}
diff --git a/plugin/rpcplugin/process_test.go b/plugin/rpcplugin/process_test.go
deleted file mode 100644
index 8d1794293..000000000
--- a/plugin/rpcplugin/process_test.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package rpcplugin
-
-import (
- "context"
- "io/ioutil"
- "os"
- "path/filepath"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/mattermost/mattermost-server/plugin/rpcplugin/rpcplugintest"
-)
-
-func TestProcess(t *testing.T) {
- dir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(dir)
-
- ping := filepath.Join(dir, "ping.exe")
- rpcplugintest.CompileGo(t, `
- package main
-
- import (
- "log"
-
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
- )
-
- func main() {
- ipc, err := rpcplugin.InheritedProcessIPC()
- if err != nil {
- log.Fatal("unable to get inherited ipc")
- }
- defer ipc.Close()
- _, err = ipc.Write([]byte("ping"))
- if err != nil {
- log.Fatal("unable to write to ipc")
- }
- }
- `, ping)
-
- p, ipc, err := NewProcess(context.Background(), ping)
- require.NoError(t, err)
- defer ipc.Close()
- b := make([]byte, 10)
- n, err := ipc.Read(b)
- require.NoError(t, err)
- assert.Equal(t, 4, n)
- assert.Equal(t, "ping", string(b[:4]))
- require.NoError(t, p.Wait())
-}
-
-func TestInvalidProcess(t *testing.T) {
- p, ipc, err := NewProcess(context.Background(), "thisfileshouldnotexist")
- require.Nil(t, p)
- require.Nil(t, ipc)
- require.Error(t, err)
-}
diff --git a/plugin/rpcplugin/process_unix.go b/plugin/rpcplugin/process_unix.go
deleted file mode 100644
index 142043cc6..000000000
--- a/plugin/rpcplugin/process_unix.go
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-// +build !windows
-
-package rpcplugin
-
-import (
- "context"
- "io"
- "os"
- "os/exec"
-)
-
-type process struct {
- command *exec.Cmd
-}
-
-func newProcess(ctx context.Context, path string) (Process, io.ReadWriteCloser, error) {
- ipc, childFiles, err := NewIPC()
- if err != nil {
- return nil, nil, err
- }
- defer childFiles[0].Close()
- defer childFiles[1].Close()
-
- cmd := exec.CommandContext(ctx, path)
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- cmd.ExtraFiles = childFiles
- err = cmd.Start()
- if err != nil {
- ipc.Close()
- return nil, nil, err
- }
-
- return &process{
- command: cmd,
- }, ipc, nil
-}
-
-func (p *process) Wait() error {
- return p.command.Wait()
-}
-
-func inheritedProcessIPC() (io.ReadWriteCloser, error) {
- return InheritedIPC(3, 4)
-}
diff --git a/plugin/rpcplugin/process_windows.go b/plugin/rpcplugin/process_windows.go
deleted file mode 100644
index 069f147c1..000000000
--- a/plugin/rpcplugin/process_windows.go
+++ /dev/null
@@ -1,648 +0,0 @@
-package rpcplugin
-
-import (
- "context"
- "errors"
- "fmt"
- "io"
- "os"
- "os/exec"
- "path/filepath"
- "runtime"
- "strconv"
- "strings"
- "syscall"
- "unicode/utf16"
- "unsafe"
-
- pkgerrors "github.com/pkg/errors"
-)
-
-type process struct {
- command *cmd
-}
-
-func newProcess(ctx context.Context, path string) (Process, io.ReadWriteCloser, error) {
- ipc, childFiles, err := NewIPC()
- if err != nil {
- return nil, nil, err
- }
- defer childFiles[0].Close()
- defer childFiles[1].Close()
-
- cmd := commandContext(ctx, path)
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- cmd.ExtraFiles = childFiles
- cmd.Env = append(os.Environ(),
- fmt.Sprintf("MM_IPC_FD0=%v", childFiles[0].Fd()),
- fmt.Sprintf("MM_IPC_FD1=%v", childFiles[1].Fd()),
- )
- err = cmd.Start()
- if err != nil {
- ipc.Close()
- return nil, nil, err
- }
-
- return &process{
- command: cmd,
- }, ipc, nil
-}
-
-func (p *process) Wait() error {
- return p.command.Wait()
-}
-
-func inheritedProcessIPC() (io.ReadWriteCloser, error) {
- fd0, err := strconv.ParseUint(os.Getenv("MM_IPC_FD0"), 0, 64)
- if err != nil {
- return nil, pkgerrors.Wrapf(err, "unable to get ipc file descriptor 0")
- }
- fd1, err := strconv.ParseUint(os.Getenv("MM_IPC_FD1"), 0, 64)
- if err != nil {
- return nil, pkgerrors.Wrapf(err, "unable to get ipc file descriptor 1")
- }
- return InheritedIPC(uintptr(fd0), uintptr(fd1))
-}
-
-// XXX: EVERYTHING BELOW THIS IS COPIED / PASTED STANDARD LIBRARY CODE!
-// IT CAN BE DELETED IF / WHEN THIS ISSUE IS RESOLVED: https://github.com/golang/go/issues/21085
-
-// Just about all of os/exec/exec.go is copied / pasted below, altered to use our modified startProcess functions even
-// further below.
-
-type cmd struct {
- // Path is the path of the command to run.
- //
- // This is the only field that must be set to a non-zero
- // value. If Path is relative, it is evaluated relative
- // to Dir.
- Path string
-
- // Args holds command line arguments, including the command as Args[0].
- // If the Args field is empty or nil, Run uses {Path}.
- //
- // In typical use, both Path and Args are set by calling Command.
- Args []string
-
- // Env specifies the environment of the process.
- // If Env is nil, Run uses the current process's environment.
- Env []string
-
- // Dir specifies the working directory of the command.
- // If Dir is the empty string, Run runs the command in the
- // calling process's current directory.
- Dir string
-
- // Stdin specifies the process's standard input.
- // If Stdin is nil, the process reads from the null device (os.DevNull).
- // If Stdin is an *os.File, the process's standard input is connected
- // directly to that file.
- // Otherwise, during the execution of the command a separate
- // goroutine reads from Stdin and delivers that data to the command
- // over a pipe. In this case, Wait does not complete until the goroutine
- // stops copying, either because it has reached the end of Stdin
- // (EOF or a read error) or because writing to the pipe returned an error.
- Stdin io.Reader
-
- // Stdout and Stderr specify the process's standard output and error.
- //
- // If either is nil, Run connects the corresponding file descriptor
- // to the null device (os.DevNull).
- //
- // If Stdout and Stderr are the same writer, at most one
- // goroutine at a time will call Write.
- Stdout io.Writer
- Stderr io.Writer
-
- // ExtraFiles specifies additional open files to be inherited by the
- // new process. It does not include standard input, standard output, or
- // standard error. If non-nil, entry i becomes file descriptor 3+i.
- //
- // BUG(rsc): On OS X 10.6, child processes may sometimes inherit unwanted fds.
- // https://golang.org/issue/2603
- ExtraFiles []*os.File
-
- // SysProcAttr holds optional, operating system-specific attributes.
- // Run passes it to os.StartProcess as the os.ProcAttr's Sys field.
- SysProcAttr *syscall.SysProcAttr
-
- // Process is the underlying process, once started.
- Process *os.Process
-
- // ProcessState contains information about an exited process,
- // available after a call to Wait or Run.
- ProcessState *os.ProcessState
-
- ctx context.Context // nil means none
- lookPathErr error // LookPath error, if any.
- finished bool // when Wait was called
- childFiles []*os.File
- closeAfterStart []io.Closer
- closeAfterWait []io.Closer
- goroutine []func() error
- errch chan error // one send per goroutine
- waitDone chan struct{}
-}
-
-func command(name string, arg ...string) *cmd {
- cmd := &cmd{
- Path: name,
- Args: append([]string{name}, arg...),
- }
- if filepath.Base(name) == name {
- if lp, err := exec.LookPath(name); err != nil {
- cmd.lookPathErr = err
- } else {
- cmd.Path = lp
- }
- }
- return cmd
-}
-
-func commandContext(ctx context.Context, name string, arg ...string) *cmd {
- if ctx == nil {
- panic("nil Context")
- }
- cmd := command(name, arg...)
- cmd.ctx = ctx
- return cmd
-}
-
-func interfaceEqual(a, b interface{}) bool {
- defer func() {
- recover()
- }()
- return a == b
-}
-
-func (c *cmd) envv() []string {
- if c.Env != nil {
- return c.Env
- }
- return os.Environ()
-}
-
-func (c *cmd) argv() []string {
- if len(c.Args) > 0 {
- return c.Args
- }
- return []string{c.Path}
-}
-
-var skipStdinCopyError func(error) bool
-
-func (c *cmd) stdin() (f *os.File, err error) {
- if c.Stdin == nil {
- f, err = os.Open(os.DevNull)
- if err != nil {
- return
- }
- c.closeAfterStart = append(c.closeAfterStart, f)
- return
- }
-
- if f, ok := c.Stdin.(*os.File); ok {
- return f, nil
- }
-
- pr, pw, err := os.Pipe()
- if err != nil {
- return
- }
-
- c.closeAfterStart = append(c.closeAfterStart, pr)
- c.closeAfterWait = append(c.closeAfterWait, pw)
- c.goroutine = append(c.goroutine, func() error {
- _, err := io.Copy(pw, c.Stdin)
- if skip := skipStdinCopyError; skip != nil && skip(err) {
- err = nil
- }
- if err1 := pw.Close(); err == nil {
- err = err1
- }
- return err
- })
- return pr, nil
-}
-
-func (c *cmd) stdout() (f *os.File, err error) {
- return c.writerDescriptor(c.Stdout)
-}
-
-func (c *cmd) stderr() (f *os.File, err error) {
- if c.Stderr != nil && interfaceEqual(c.Stderr, c.Stdout) {
- return c.childFiles[1], nil
- }
- return c.writerDescriptor(c.Stderr)
-}
-
-func (c *cmd) writerDescriptor(w io.Writer) (f *os.File, err error) {
- if w == nil {
- f, err = os.OpenFile(os.DevNull, os.O_WRONLY, 0)
- if err != nil {
- return
- }
- c.closeAfterStart = append(c.closeAfterStart, f)
- return
- }
-
- if f, ok := w.(*os.File); ok {
- return f, nil
- }
-
- pr, pw, err := os.Pipe()
- if err != nil {
- return
- }
-
- c.closeAfterStart = append(c.closeAfterStart, pw)
- c.closeAfterWait = append(c.closeAfterWait, pr)
- c.goroutine = append(c.goroutine, func() error {
- _, err := io.Copy(w, pr)
- pr.Close() // in case io.Copy stopped due to write error
- return err
- })
- return pw, nil
-}
-
-func (c *cmd) closeDescriptors(closers []io.Closer) {
- for _, fd := range closers {
- fd.Close()
- }
-}
-
-func lookExtensions(path, dir string) (string, error) {
- if filepath.Base(path) == path {
- path = filepath.Join(".", path)
- }
- if dir == "" {
- return exec.LookPath(path)
- }
- if filepath.VolumeName(path) != "" {
- return exec.LookPath(path)
- }
- if len(path) > 1 && os.IsPathSeparator(path[0]) {
- return exec.LookPath(path)
- }
- dirandpath := filepath.Join(dir, path)
- // We assume that LookPath will only add file extension.
- lp, err := exec.LookPath(dirandpath)
- if err != nil {
- return "", err
- }
- ext := strings.TrimPrefix(lp, dirandpath)
- return path + ext, nil
-}
-
-// Copied from os/exec/exec.go, altered to use osStartProcess (defined below).
-func (c *cmd) Start() error {
- if c.lookPathErr != nil {
- c.closeDescriptors(c.closeAfterStart)
- c.closeDescriptors(c.closeAfterWait)
- return c.lookPathErr
- }
- if runtime.GOOS == "windows" {
- lp, err := lookExtensions(c.Path, c.Dir)
- if err != nil {
- c.closeDescriptors(c.closeAfterStart)
- c.closeDescriptors(c.closeAfterWait)
- return err
- }
- c.Path = lp
- }
- if c.Process != nil {
- return errors.New("exec: already started")
- }
- if c.ctx != nil {
- select {
- case <-c.ctx.Done():
- c.closeDescriptors(c.closeAfterStart)
- c.closeDescriptors(c.closeAfterWait)
- return c.ctx.Err()
- default:
- }
- }
-
- type F func(*cmd) (*os.File, error)
- for _, setupFd := range []F{(*cmd).stdin, (*cmd).stdout, (*cmd).stderr} {
- fd, err := setupFd(c)
- if err != nil {
- c.closeDescriptors(c.closeAfterStart)
- c.closeDescriptors(c.closeAfterWait)
- return err
- }
- c.childFiles = append(c.childFiles, fd)
- }
- c.childFiles = append(c.childFiles, c.ExtraFiles...)
-
- var err error
- c.Process, err = osStartProcess(c.Path, c.argv(), &os.ProcAttr{
- Dir: c.Dir,
- Files: c.childFiles,
- Env: c.envv(),
- Sys: c.SysProcAttr,
- })
- if err != nil {
- c.closeDescriptors(c.closeAfterStart)
- c.closeDescriptors(c.closeAfterWait)
- return err
- }
-
- c.closeDescriptors(c.closeAfterStart)
-
- c.errch = make(chan error, len(c.goroutine))
- for _, fn := range c.goroutine {
- go func(fn func() error) {
- c.errch <- fn()
- }(fn)
- }
-
- if c.ctx != nil {
- c.waitDone = make(chan struct{})
- go func() {
- select {
- case <-c.ctx.Done():
- c.Process.Kill()
- case <-c.waitDone:
- }
- }()
- }
-
- return nil
-}
-
-func (c *cmd) Wait() error {
- if c.Process == nil {
- return errors.New("exec: not started")
- }
- if c.finished {
- return errors.New("exec: Wait was already called")
- }
- c.finished = true
-
- state, err := c.Process.Wait()
- if c.waitDone != nil {
- close(c.waitDone)
- }
- c.ProcessState = state
-
- var copyError error
- for range c.goroutine {
- if err := <-c.errch; err != nil && copyError == nil {
- copyError = err
- }
- }
-
- c.closeDescriptors(c.closeAfterWait)
-
- if err != nil {
- return err
- } else if !state.Success() {
- return &exec.ExitError{ProcessState: state}
- }
-
- return copyError
-}
-
-// Copied from os/exec_posix.go, altered to use syscallStartProcess (defined below).
-func osStartProcess(name string, argv []string, attr *os.ProcAttr) (p *os.Process, err error) {
- // If there is no SysProcAttr (ie. no Chroot or changed
- // UID/GID), double-check existence of the directory we want
- // to chdir into. We can make the error clearer this way.
- if attr != nil && attr.Sys == nil && attr.Dir != "" {
- if _, err := os.Stat(attr.Dir); err != nil {
- pe := err.(*os.PathError)
- pe.Op = "chdir"
- return nil, pe
- }
- }
-
- sysattr := &syscall.ProcAttr{
- Dir: attr.Dir,
- Env: attr.Env,
- Sys: attr.Sys,
- }
- if sysattr.Env == nil {
- sysattr.Env = os.Environ()
- }
- for _, f := range attr.Files {
- sysattr.Files = append(sysattr.Files, f.Fd())
- }
-
- pid, _, e := syscallStartProcess(name, argv, sysattr)
- if e != nil {
- return nil, &os.PathError{Op: "fork/exec", Path: name, Err: e}
- }
- return os.FindProcess(pid)
-}
-
-// Everything from this point on is copied from syscall/exec_windows.go
-
-func makeCmdLine(args []string) string {
- var s string
- for _, v := range args {
- if s != "" {
- s += " "
- }
- s += syscall.EscapeArg(v)
- }
- return s
-}
-
-func createEnvBlock(envv []string) *uint16 {
- if len(envv) == 0 {
- return &utf16.Encode([]rune("\x00\x00"))[0]
- }
- length := 0
- for _, s := range envv {
- length += len(s) + 1
- }
- length += 1
-
- b := make([]byte, length)
- i := 0
- for _, s := range envv {
- l := len(s)
- copy(b[i:i+l], []byte(s))
- copy(b[i+l:i+l+1], []byte{0})
- i = i + l + 1
- }
- copy(b[i:i+1], []byte{0})
-
- return &utf16.Encode([]rune(string(b)))[0]
-}
-
-func isSlash(c uint8) bool {
- return c == '\\' || c == '/'
-}
-
-func normalizeDir(dir string) (name string, err error) {
- ndir, err := syscall.FullPath(dir)
- if err != nil {
- return "", err
- }
- if len(ndir) > 2 && isSlash(ndir[0]) && isSlash(ndir[1]) {
- // dir cannot have \\server\share\path form
- return "", syscall.EINVAL
- }
- return ndir, nil
-}
-
-func volToUpper(ch int) int {
- if 'a' <= ch && ch <= 'z' {
- ch += 'A' - 'a'
- }
- return ch
-}
-
-func joinExeDirAndFName(dir, p string) (name string, err error) {
- if len(p) == 0 {
- return "", syscall.EINVAL
- }
- if len(p) > 2 && isSlash(p[0]) && isSlash(p[1]) {
- // \\server\share\path form
- return p, nil
- }
- if len(p) > 1 && p[1] == ':' {
- // has drive letter
- if len(p) == 2 {
- return "", syscall.EINVAL
- }
- if isSlash(p[2]) {
- return p, nil
- } else {
- d, err := normalizeDir(dir)
- if err != nil {
- return "", err
- }
- if volToUpper(int(p[0])) == volToUpper(int(d[0])) {
- return syscall.FullPath(d + "\\" + p[2:])
- } else {
- return syscall.FullPath(p)
- }
- }
- } else {
- // no drive letter
- d, err := normalizeDir(dir)
- if err != nil {
- return "", err
- }
- if isSlash(p[0]) {
- return syscall.FullPath(d[:2] + p)
- } else {
- return syscall.FullPath(d + "\\" + p)
- }
- }
-}
-
-var zeroProcAttr syscall.ProcAttr
-var zeroSysProcAttr syscall.SysProcAttr
-
-// Has minor changes to support file inheritance.
-func syscallStartProcess(argv0 string, argv []string, attr *syscall.ProcAttr) (pid int, handle uintptr, err error) {
- if len(argv0) == 0 {
- return 0, 0, syscall.EWINDOWS
- }
- if attr == nil {
- attr = &zeroProcAttr
- }
- sys := attr.Sys
- if sys == nil {
- sys = &zeroSysProcAttr
- }
-
- if len(attr.Files) < 3 {
- return 0, 0, syscall.EINVAL
- }
-
- if len(attr.Dir) != 0 {
- // StartProcess assumes that argv0 is relative to attr.Dir,
- // because it implies Chdir(attr.Dir) before executing argv0.
- // Windows CreateProcess assumes the opposite: it looks for
- // argv0 relative to the current directory, and, only once the new
- // process is started, it does Chdir(attr.Dir). We are adjusting
- // for that difference here by making argv0 absolute.
- var err error
- argv0, err = joinExeDirAndFName(attr.Dir, argv0)
- if err != nil {
- return 0, 0, err
- }
- }
- argv0p, err := syscall.UTF16PtrFromString(argv0)
- if err != nil {
- return 0, 0, err
- }
-
- var cmdline string
- // Windows CreateProcess takes the command line as a single string:
- // use attr.CmdLine if set, else build the command line by escaping
- // and joining each argument with spaces
- if sys.CmdLine != "" {
- cmdline = sys.CmdLine
- } else {
- cmdline = makeCmdLine(argv)
- }
-
- var argvp *uint16
- if len(cmdline) != 0 {
- argvp, err = syscall.UTF16PtrFromString(cmdline)
- if err != nil {
- return 0, 0, err
- }
- }
-
- var dirp *uint16
- if len(attr.Dir) != 0 {
- dirp, err = syscall.UTF16PtrFromString(attr.Dir)
- if err != nil {
- return 0, 0, err
- }
- }
-
- // Acquire the fork lock so that no other threads
- // create new fds that are not yet close-on-exec
- // before we fork.
- syscall.ForkLock.Lock()
- defer syscall.ForkLock.Unlock()
-
- p, _ := syscall.GetCurrentProcess()
- fd := make([]syscall.Handle, len(attr.Files))
- for i := range attr.Files {
- if attr.Files[i] <= 0 {
- continue
- }
- if i < 3 {
- err := syscall.DuplicateHandle(p, syscall.Handle(attr.Files[i]), p, &fd[i], 0, true, syscall.DUPLICATE_SAME_ACCESS)
- if err != nil {
- return 0, 0, err
- }
- defer syscall.CloseHandle(syscall.Handle(fd[i]))
- } else {
- // This is the modification that allows files to be inherited.
- syscall.SetHandleInformation(syscall.Handle(attr.Files[i]), syscall.HANDLE_FLAG_INHERIT, 1)
- defer syscall.SetHandleInformation(syscall.Handle(attr.Files[i]), syscall.HANDLE_FLAG_INHERIT, 0)
- }
- }
- si := new(syscall.StartupInfo)
- si.Cb = uint32(unsafe.Sizeof(*si))
- si.Flags = syscall.STARTF_USESTDHANDLES
- if sys.HideWindow {
- si.Flags |= syscall.STARTF_USESHOWWINDOW
- si.ShowWindow = syscall.SW_HIDE
- }
- si.StdInput = fd[0]
- si.StdOutput = fd[1]
- si.StdErr = fd[2]
-
- pi := new(syscall.ProcessInformation)
-
- flags := sys.CreationFlags | syscall.CREATE_UNICODE_ENVIRONMENT
- err = syscall.CreateProcess(argv0p, argvp, nil, nil, true, flags, createEnvBlock(attr.Env), dirp, si, pi)
- if err != nil {
- return 0, 0, err
- }
- defer syscall.CloseHandle(syscall.Handle(pi.Thread))
-
- return int(pi.ProcessId), uintptr(pi.Process), nil
-}
diff --git a/plugin/rpcplugin/rpcplugintest/rpcplugintest.go b/plugin/rpcplugin/rpcplugintest/rpcplugintest.go
deleted file mode 100644
index 185f741c1..000000000
--- a/plugin/rpcplugin/rpcplugintest/rpcplugintest.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package rpcplugintest
-
-import (
- "io/ioutil"
- "os"
- "os/exec"
- "path/filepath"
- "testing"
-
- "github.com/stretchr/testify/require"
-)
-
-func CompileGo(t *testing.T, sourceCode, outputPath string) {
- dir, err := ioutil.TempDir(".", "")
- require.NoError(t, err)
- defer os.RemoveAll(dir)
- require.NoError(t, ioutil.WriteFile(filepath.Join(dir, "main.go"), []byte(sourceCode), 0600))
- cmd := exec.Command("go", "build", "-o", outputPath, "main.go")
- cmd.Dir = dir
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- require.NoError(t, cmd.Run())
-}
diff --git a/plugin/rpcplugin/rpcplugintest/supervisor.go b/plugin/rpcplugin/rpcplugintest/supervisor.go
deleted file mode 100644
index f3ff847a2..000000000
--- a/plugin/rpcplugin/rpcplugintest/supervisor.go
+++ /dev/null
@@ -1,312 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package rpcplugintest
-
-import (
- "encoding/json"
- "fmt"
- "io/ioutil"
- "net/http"
- "net/http/httptest"
- "os"
- "path/filepath"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
- "github.com/stretchr/testify/require"
-
- "github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/plugin"
- "github.com/mattermost/mattermost-server/plugin/plugintest"
-)
-
-type SupervisorProviderFunc = func(*model.BundleInfo) (plugin.Supervisor, error)
-
-func TestSupervisorProvider(t *testing.T, sp SupervisorProviderFunc) {
- for name, f := range map[string]func(*testing.T, SupervisorProviderFunc){
- "Supervisor": testSupervisor,
- "Supervisor_InvalidExecutablePath": testSupervisor_InvalidExecutablePath,
- "Supervisor_NonExistentExecutablePath": testSupervisor_NonExistentExecutablePath,
- "Supervisor_StartTimeout": testSupervisor_StartTimeout,
- // "Supervisor_PluginCrash": testSupervisor_PluginCrash,
- // "Supervisor_PluginRepeatedlyCrash": testSupervisor_PluginRepeatedlyCrash,
- } {
- t.Run(name, func(t *testing.T) { f(t, sp) })
- }
-}
-
-func testSupervisor(t *testing.T, sp SupervisorProviderFunc) {
- dir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(dir)
-
- backend := filepath.Join(dir, "backend.exe")
- CompileGo(t, `
- package main
-
- import (
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
- )
-
- type MyPlugin struct {}
-
- func main() {
- rpcplugin.Main(&MyPlugin{})
- }
- `, backend)
-
- ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "backend": {"executable": "backend.exe"}}`), 0600)
-
- bundle := model.BundleInfoForPath(dir)
- supervisor, err := sp(bundle)
- require.NoError(t, err)
- require.NoError(t, supervisor.Start(nil))
- require.NoError(t, supervisor.Stop())
-}
-
-func testSupervisor_InvalidExecutablePath(t *testing.T, sp SupervisorProviderFunc) {
- dir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(dir)
-
- ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "backend": {"executable": "/foo/../../backend.exe"}}`), 0600)
-
- bundle := model.BundleInfoForPath(dir)
- supervisor, err := sp(bundle)
- assert.Nil(t, supervisor)
- assert.Error(t, err)
-}
-
-func testSupervisor_NonExistentExecutablePath(t *testing.T, sp SupervisorProviderFunc) {
- dir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(dir)
-
- ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "backend": {"executable": "thisfileshouldnotexist"}}`), 0600)
-
- bundle := model.BundleInfoForPath(dir)
- supervisor, err := sp(bundle)
- require.NotNil(t, supervisor)
- require.NoError(t, err)
-
- require.Error(t, supervisor.Start(nil))
-}
-
-// If plugin development goes really wrong, let's make sure plugin activation won't block forever.
-func testSupervisor_StartTimeout(t *testing.T, sp SupervisorProviderFunc) {
- dir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(dir)
-
- backend := filepath.Join(dir, "backend.exe")
- CompileGo(t, `
- package main
-
- func main() {
- for {
- }
- }
- `, backend)
-
- ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "backend": {"executable": "backend.exe"}}`), 0600)
-
- bundle := model.BundleInfoForPath(dir)
- supervisor, err := sp(bundle)
- require.NoError(t, err)
- require.Error(t, supervisor.Start(nil))
-}
-
-// Crashed plugins should be relaunched.
-func testSupervisor_PluginCrash(t *testing.T, sp SupervisorProviderFunc) {
- dir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(dir)
-
- backend := filepath.Join(dir, "backend.exe")
- CompileGo(t, `
- package main
-
- import (
- "os"
-
- "github.com/mattermost/mattermost-server/plugin"
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
- )
-
- type Configuration struct {
- ShouldExit bool
- }
-
- type MyPlugin struct {
- config Configuration
- }
-
- func (p *MyPlugin) OnActivate(api plugin.API) error {
- api.LoadPluginConfiguration(&p.config)
- return nil
- }
-
- func (p *MyPlugin) OnDeactivate() error {
- if p.config.ShouldExit {
- os.Exit(1)
- }
- return nil
- }
-
- func main() {
- rpcplugin.Main(&MyPlugin{})
- }
- `, backend)
-
- ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "backend": {"executable": "backend.exe"}}`), 0600)
-
- var api plugintest.API
- shouldExit := true
- api.On("LoadPluginConfiguration", mock.MatchedBy(func(x interface{}) bool { return true })).Return(func(dest interface{}) error {
- err := json.Unmarshal([]byte(fmt.Sprintf(`{"ShouldExit": %v}`, shouldExit)), dest)
- shouldExit = false
- return err
- })
-
- bundle := model.BundleInfoForPath(dir)
- supervisor, err := sp(bundle)
- require.NoError(t, err)
-
- var supervisorWaitErr error
- supervisorWaitDone := make(chan bool, 1)
- go func() {
- supervisorWaitErr = supervisor.Wait()
- close(supervisorWaitDone)
- }()
-
- require.NoError(t, supervisor.Start(&api))
-
- failed := false
- recovered := false
- for i := 0; i < 30; i++ {
- if supervisor.Hooks().OnDeactivate() == nil {
- require.True(t, failed)
- recovered = true
- break
- } else {
- failed = true
- }
- time.Sleep(time.Millisecond * 100)
- }
- assert.True(t, recovered)
-
- select {
- case <-supervisorWaitDone:
- require.Fail(t, "supervisor.Wait() unexpectedly returned")
- case <-time.After(500 * time.Millisecond):
- }
-
- require.NoError(t, supervisor.Stop())
-
- select {
- case <-supervisorWaitDone:
- require.Nil(t, supervisorWaitErr)
- case <-time.After(5000 * time.Millisecond):
- require.Fail(t, "supervisor.Wait() failed to return")
- }
-}
-
-// Crashed plugins should be relaunched at most three times.
-func testSupervisor_PluginRepeatedlyCrash(t *testing.T, sp SupervisorProviderFunc) {
- dir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(dir)
-
- backend := filepath.Join(dir, "backend.exe")
- CompileGo(t, `
- package main
-
- import (
- "net/http"
- "os"
-
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
- )
-
- type MyPlugin struct {
- crashing bool
- }
-
- func (p *MyPlugin) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- if r.Method == http.MethodPost {
- p.crashing = true
- go func() {
- os.Exit(1)
- }()
- }
-
- if p.crashing {
- w.WriteHeader(http.StatusInternalServerError)
- } else {
- w.WriteHeader(http.StatusOK)
- }
- }
-
- func main() {
- rpcplugin.Main(&MyPlugin{})
- }
- `, backend)
-
- ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "backend": {"executable": "backend.exe"}}`), 0600)
-
- var api plugintest.API
- bundle := model.BundleInfoForPath(dir)
- supervisor, err := sp(bundle)
- require.NoError(t, err)
-
- var supervisorWaitErr error
- supervisorWaitDone := make(chan bool, 1)
- go func() {
- supervisorWaitErr = supervisor.Wait()
- close(supervisorWaitDone)
- }()
-
- require.NoError(t, supervisor.Start(&api))
-
- for attempt := 1; attempt <= 4; attempt++ {
- // Verify that the plugin is operational
- response := httptest.NewRecorder()
- supervisor.Hooks().ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/plugins/id", nil))
- require.Equal(t, http.StatusOK, response.Result().StatusCode)
-
- // Crash the plugin
- supervisor.Hooks().ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodPost, "/plugins/id", nil))
-
- // Wait for it to potentially recover
- recovered := false
- for i := 0; i < 125; i++ {
- response := httptest.NewRecorder()
- supervisor.Hooks().ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/plugins/id", nil))
- if response.Result().StatusCode == http.StatusOK {
- recovered = true
- break
- }
-
- time.Sleep(time.Millisecond * 100)
- }
-
- if attempt < 4 {
- require.Nil(t, supervisorWaitErr)
- require.True(t, recovered, "failed to recover after attempt %d", attempt)
- } else {
- require.False(t, recovered, "unexpectedly recovered after attempt %d", attempt)
- }
- }
-
- select {
- case <-supervisorWaitDone:
- require.NotNil(t, supervisorWaitErr)
- case <-time.After(500 * time.Millisecond):
- require.Fail(t, "supervisor.Wait() failed to return after plugin crashed")
- }
-
- require.NoError(t, supervisor.Stop())
-}
diff --git a/plugin/rpcplugin/sandbox/main_test.go b/plugin/rpcplugin/sandbox/main_test.go
deleted file mode 100644
index 4be4a42af..000000000
--- a/plugin/rpcplugin/sandbox/main_test.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package sandbox
-
-import (
- "testing"
-
- "github.com/mattermost/mattermost-server/mlog"
-)
-
-func TestMain(t *testing.T) {
- // Setup a global logger to catch tests logging outside of app context
- // The global logger will be stomped by apps initalizing but that's fine for testing. Ideally this won't happen.
- mlog.InitGlobalLogger(mlog.NewLogger(&mlog.LoggerConfiguration{
- EnableConsole: true,
- ConsoleJson: true,
- ConsoleLevel: "error",
- EnableFile: false,
- }))
-}
diff --git a/plugin/rpcplugin/sandbox/sandbox.go b/plugin/rpcplugin/sandbox/sandbox.go
deleted file mode 100644
index 96eff02dd..000000000
--- a/plugin/rpcplugin/sandbox/sandbox.go
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package sandbox
-
-import (
- "context"
- "io"
-
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
-)
-
-type MountPoint struct {
- Source string
- Destination string
- Type string
- ReadOnly bool
-}
-
-type Configuration struct {
- MountPoints []*MountPoint
- WorkingDirectory string
-}
-
-// NewProcess is like rpcplugin.NewProcess, but launches the process in a sandbox.
-func NewProcess(ctx context.Context, config *Configuration, path string) (rpcplugin.Process, io.ReadWriteCloser, error) {
- return newProcess(ctx, config, path)
-}
-
-// CheckSupport inspects the platform and environment to determine whether or not there are any
-// expected issues with sandboxing. If nil is returned, sandboxing should be used.
-func CheckSupport() error {
- return checkSupport()
-}
diff --git a/plugin/rpcplugin/sandbox/sandbox_linux.go b/plugin/rpcplugin/sandbox/sandbox_linux.go
deleted file mode 100644
index beb00995d..000000000
--- a/plugin/rpcplugin/sandbox/sandbox_linux.go
+++ /dev/null
@@ -1,488 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package sandbox
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "os/exec"
- "path/filepath"
- "syscall"
- "unsafe"
-
- "github.com/pkg/errors"
- "golang.org/x/sys/unix"
-
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
-)
-
-func init() {
- if len(os.Args) < 4 || os.Args[0] != "sandbox.runProcess" {
- return
- }
-
- var config Configuration
- if err := json.Unmarshal([]byte(os.Args[1]), &config); err != nil {
- fmt.Println(err.Error())
- os.Exit(1)
- }
- if err := runProcess(&config, os.Args[2], os.Args[3]); err != nil {
- if eerr, ok := err.(*exec.ExitError); ok {
- if status, ok := eerr.Sys().(syscall.WaitStatus); ok {
- os.Exit(status.ExitStatus())
- }
- }
- fmt.Println(err.Error())
- os.Exit(1)
- }
- os.Exit(0)
-}
-
-func systemMountPoints() (points []*MountPoint) {
- points = append(points, &MountPoint{
- Source: "proc",
- Destination: "/proc",
- Type: "proc",
- }, &MountPoint{
- Source: "/dev/null",
- Destination: "/dev/null",
- }, &MountPoint{
- Source: "/dev/zero",
- Destination: "/dev/zero",
- }, &MountPoint{
- Source: "/dev/full",
- Destination: "/dev/full",
- })
-
- readOnly := []string{
- "/dev/random",
- "/dev/urandom",
- "/etc/resolv.conf",
- "/lib",
- "/lib32",
- "/lib64",
- "/usr/lib",
- "/usr/lib32",
- "/usr/lib64",
- "/etc/ca-certificates",
- "/etc/ssl/certs",
- "/system/etc/security/cacerts",
- "/usr/local/share/certs",
- "/etc/pki/tls/certs",
- "/etc/openssl/certs",
- "/etc/ssl/ca-bundle.pem",
- "/etc/pki/tls/cacert.pem",
- "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem",
- }
-
- for _, v := range []string{"SSL_CERT_FILE", "SSL_CERT_DIR"} {
- if path := os.Getenv(v); path != "" {
- readOnly = append(readOnly, path)
- }
- }
-
- for _, point := range readOnly {
- points = append(points, &MountPoint{
- Source: point,
- Destination: point,
- ReadOnly: true,
- })
- }
-
- return
-}
-
-func runProcess(config *Configuration, path, root string) error {
- if err := syscall.Mount("", "/", "", syscall.MS_PRIVATE|syscall.MS_REC, ""); err != nil {
- return errors.Wrapf(err, "unable to make root private")
- }
-
- if err := mountMountPoints(root, systemMountPoints()); err != nil {
- return errors.Wrapf(err, "unable to mount sandbox system mount points")
- }
-
- if err := mountMountPoints(root, config.MountPoints); err != nil {
- return errors.Wrapf(err, "unable to mount sandbox config mount points")
- }
-
- if err := pivotRoot(root); err != nil {
- return errors.Wrapf(err, "unable to pivot sandbox root")
- }
-
- if err := os.Mkdir("/tmp", 0755); err != nil {
- return errors.Wrapf(err, "unable to create /tmp")
- }
-
- if config.WorkingDirectory != "" {
- if err := os.Chdir(config.WorkingDirectory); err != nil {
- return errors.Wrapf(err, "unable to set working directory")
- }
- }
-
- if err := dropInheritableCapabilities(); err != nil {
- return errors.Wrapf(err, "unable to drop inheritable capabilities")
- }
-
- if err := enableSeccompFilter(); err != nil {
- return errors.Wrapf(err, "unable to enable seccomp filter")
- }
-
- return runExecutable(path)
-}
-
-func mountMountPoint(root string, mountPoint *MountPoint) error {
- isDir := true
- if mountPoint.Type == "" {
- stat, err := os.Lstat(mountPoint.Source)
- if err != nil {
- return nil
- }
- if (stat.Mode() & os.ModeSymlink) != 0 {
- if path, err := filepath.EvalSymlinks(mountPoint.Source); err == nil {
- newMountPoint := *mountPoint
- newMountPoint.Source = path
- if err := mountMountPoint(root, &newMountPoint); err != nil {
- return errors.Wrapf(err, "unable to mount symbolic link target: "+mountPoint.Source)
- }
- return nil
- }
- }
- isDir = stat.IsDir()
- }
-
- target := filepath.Join(root, mountPoint.Destination)
-
- if isDir {
- if err := os.MkdirAll(target, 0755); err != nil {
- return errors.Wrapf(err, "unable to create directory: "+target)
- }
- } else {
- if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
- return errors.Wrapf(err, "unable to create directory: "+target)
- }
- f, err := os.Create(target)
- if err != nil {
- return errors.Wrapf(err, "unable to create file: "+target)
- }
- f.Close()
- }
-
- flags := uintptr(syscall.MS_NOSUID | syscall.MS_NODEV)
- if mountPoint.Type == "" {
- flags |= syscall.MS_BIND
- }
- if mountPoint.ReadOnly {
- flags |= syscall.MS_RDONLY
- }
-
- if err := syscall.Mount(mountPoint.Source, target, mountPoint.Type, flags, ""); err != nil {
- return errors.Wrapf(err, "unable to mount "+mountPoint.Source)
- }
-
- if (flags & syscall.MS_BIND) != 0 {
- // If this was a bind mount, our other flags actually got silently ignored during the above syscall:
- //
- // If mountflags includes MS_BIND [...] The remaining bits in the mountflags argument are
- // also ignored, with the exception of MS_REC.
- //
- // Furthermore, remounting will fail if we attempt to unset a bit that was inherited from
- // the mount's parent:
- //
- // The mount(2) flags MS_RDONLY, MS_NOSUID, MS_NOEXEC, and the "atime" flags
- // (MS_NOATIME, MS_NODIRATIME, MS_RELATIME) settings become locked when propagated from
- // a more privileged to a less privileged mount namespace, and may not be changed in the
- // less privileged mount namespace.
- //
- // So we need to get the actual flags, add our new ones, then do a remount if needed.
- var stats syscall.Statfs_t
- if err := syscall.Statfs(target, &stats); err != nil {
- return errors.Wrap(err, "unable to get mount flags for target: "+target)
- }
- const lockedFlagsMask = unix.MS_RDONLY | unix.MS_NOSUID | unix.MS_NOEXEC | unix.MS_NOATIME | unix.MS_NODIRATIME | unix.MS_RELATIME
- lockedFlags := uintptr(stats.Flags & lockedFlagsMask)
- if lockedFlags != ((flags | lockedFlags) & lockedFlagsMask) {
- if err := syscall.Mount("", target, "", flags|lockedFlags|syscall.MS_REMOUNT, ""); err != nil {
- return errors.Wrapf(err, "unable to remount "+mountPoint.Source)
- }
- }
- }
-
- return nil
-}
-
-func mountMountPoints(root string, mountPoints []*MountPoint) error {
- for _, mountPoint := range mountPoints {
- if err := mountMountPoint(root, mountPoint); err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func pivotRoot(newRoot string) error {
- if err := syscall.Mount(newRoot, newRoot, "", syscall.MS_BIND|syscall.MS_REC, ""); err != nil {
- return errors.Wrapf(err, "unable to mount new root")
- }
-
- prevRoot := filepath.Join(newRoot, ".prev_root")
-
- if err := os.MkdirAll(prevRoot, 0700); err != nil {
- return errors.Wrapf(err, "unable to create directory for previous root")
- }
-
- if err := syscall.PivotRoot(newRoot, prevRoot); err != nil {
- return errors.Wrapf(err, "syscall error")
- }
-
- if err := os.Chdir("/"); err != nil {
- return errors.Wrapf(err, "unable to change directory")
- }
-
- prevRoot = "/.prev_root"
-
- if err := syscall.Unmount(prevRoot, syscall.MNT_DETACH); err != nil {
- return errors.Wrapf(err, "unable to unmount previous root")
- }
-
- if err := os.RemoveAll(prevRoot); err != nil {
- return errors.Wrapf(err, "unable to remove previous root directory")
- }
-
- return nil
-}
-
-func dropInheritableCapabilities() error {
- type capHeader struct {
- version uint32
- pid int32
- }
-
- type capData struct {
- effective uint32
- permitted uint32
- inheritable uint32
- }
-
- var hdr capHeader
- var data [2]capData
-
- if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&hdr)), 0, 0); errno != 0 {
- return errors.Wrapf(syscall.Errno(errno), "unable to get capabilities version")
- }
-
- if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&hdr)), uintptr(unsafe.Pointer(&data[0])), 0); errno != 0 {
- return errors.Wrapf(syscall.Errno(errno), "unable to get capabilities")
- }
-
- data[0].inheritable = 0
- data[1].inheritable = 0
- if _, _, errno := syscall.Syscall(syscall.SYS_CAPSET, uintptr(unsafe.Pointer(&hdr)), uintptr(unsafe.Pointer(&data[0])), 0); errno != 0 {
- return errors.Wrapf(syscall.Errno(errno), "unable to set inheritable capabilities")
- }
-
- for i := 0; i < 64; i++ {
- if _, _, errno := syscall.Syscall(syscall.SYS_PRCTL, syscall.PR_CAPBSET_DROP, uintptr(i), 0); errno != 0 && errno != syscall.EINVAL {
- return errors.Wrapf(syscall.Errno(errno), "unable to drop bounding set capability")
- }
- }
-
- return nil
-}
-
-func enableSeccompFilter() error {
- return EnableSeccompFilter(SeccompFilter(NATIVE_AUDIT_ARCH, AllowedSyscalls))
-}
-
-func runExecutable(path string) error {
- childFiles := []*os.File{
- os.NewFile(3, ""), os.NewFile(4, ""),
- }
- defer childFiles[0].Close()
- defer childFiles[1].Close()
-
- cmd := exec.Command(path)
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- cmd.ExtraFiles = childFiles
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Pdeathsig: syscall.SIGTERM,
- }
-
- if err := cmd.Run(); err != nil {
- return err
- }
-
- return nil
-}
-
-type process struct {
- command *exec.Cmd
- root string
-}
-
-func newProcess(ctx context.Context, config *Configuration, path string) (pOut rpcplugin.Process, rwcOut io.ReadWriteCloser, errOut error) {
- configJSON, err := json.Marshal(config)
- if err != nil {
- return nil, nil, err
- }
-
- ipc, childFiles, err := rpcplugin.NewIPC()
- if err != nil {
- return nil, nil, err
- }
- defer childFiles[0].Close()
- defer childFiles[1].Close()
-
- root, err := ioutil.TempDir("", "")
- if err != nil {
- return nil, nil, err
- }
- defer func() {
- if errOut != nil {
- os.RemoveAll(root)
- }
- }()
-
- cmd := exec.CommandContext(ctx, "/proc/self/exe")
- cmd.Args = []string{"sandbox.runProcess", string(configJSON), path, root}
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- cmd.ExtraFiles = childFiles
-
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Cloneflags: syscall.CLONE_NEWNS | syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWUSER,
- Pdeathsig: syscall.SIGTERM,
- GidMappings: []syscall.SysProcIDMap{
- {
- ContainerID: 0,
- HostID: os.Getgid(),
- Size: 1,
- },
- },
- UidMappings: []syscall.SysProcIDMap{
- {
- ContainerID: 0,
- HostID: os.Getuid(),
- Size: 1,
- },
- },
- }
-
- err = cmd.Start()
- if err != nil {
- ipc.Close()
- return nil, nil, err
- }
-
- return &process{
- command: cmd,
- root: root,
- }, ipc, nil
-}
-
-func (p *process) Wait() error {
- defer os.RemoveAll(p.root)
- return p.command.Wait()
-}
-
-func init() {
- if len(os.Args) < 2 || os.Args[0] != "sandbox.checkSupportInNamespace" {
- return
- }
-
- if err := checkSupportInNamespace(os.Args[1]); err != nil {
- fmt.Fprintf(os.Stderr, "%v", err.Error())
- os.Exit(1)
- }
-
- os.Exit(0)
-}
-
-func checkSupportInNamespace(root string) error {
- if err := syscall.Mount("", "/", "", syscall.MS_PRIVATE|syscall.MS_REC, ""); err != nil {
- return errors.Wrapf(err, "unable to make root private")
- }
-
- if err := mountMountPoints(root, systemMountPoints()); err != nil {
- return errors.Wrapf(err, "unable to mount sandbox system mount points")
- }
-
- if err := pivotRoot(root); err != nil {
- return errors.Wrapf(err, "unable to pivot sandbox root")
- }
-
- if err := dropInheritableCapabilities(); err != nil {
- return errors.Wrapf(err, "unable to drop inheritable capabilities")
- }
-
- if err := enableSeccompFilter(); err != nil {
- return errors.Wrapf(err, "unable to enable seccomp filter")
- }
-
- if f, err := os.Create(os.DevNull); err != nil {
- return errors.Wrapf(err, "unable to open os.DevNull")
- } else {
- defer f.Close()
- if _, err = f.Write([]byte("foo")); err != nil {
- return errors.Wrapf(err, "unable to write to os.DevNull")
- }
- }
-
- return nil
-}
-
-func checkSupport() error {
- if AllowedSyscalls == nil {
- return fmt.Errorf("unsupported architecture")
- }
-
- stderr := &bytes.Buffer{}
-
- root, err := ioutil.TempDir("", "")
- if err != nil {
- return err
- }
- defer os.RemoveAll(root)
-
- cmd := exec.Command("/proc/self/exe")
- cmd.Args = []string{"sandbox.checkSupportInNamespace", root}
- cmd.Stderr = stderr
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Cloneflags: syscall.CLONE_NEWNS | syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWUSER,
- Pdeathsig: syscall.SIGTERM,
- GidMappings: []syscall.SysProcIDMap{
- {
- ContainerID: 0,
- HostID: os.Getgid(),
- Size: 1,
- },
- },
- UidMappings: []syscall.SysProcIDMap{
- {
- ContainerID: 0,
- HostID: os.Getuid(),
- Size: 1,
- },
- },
- }
-
- if err := cmd.Start(); err != nil {
- return errors.Wrapf(err, "unable to create user namespace")
- }
-
- if err := cmd.Wait(); err != nil {
- if _, ok := err.(*exec.ExitError); ok {
- return errors.Wrapf(fmt.Errorf("%v", stderr.String()), "unable to prepare namespace")
- }
- return errors.Wrapf(err, "unable to prepare namespace")
- }
-
- return nil
-}
diff --git a/plugin/rpcplugin/sandbox/sandbox_linux_test.go b/plugin/rpcplugin/sandbox/sandbox_linux_test.go
deleted file mode 100644
index 2bcbf0c57..000000000
--- a/plugin/rpcplugin/sandbox/sandbox_linux_test.go
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package sandbox
-
-import (
- "context"
- "io/ioutil"
- "os"
- "path/filepath"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/mattermost/mattermost-server/plugin/rpcplugin/rpcplugintest"
-)
-
-func TestNewProcess(t *testing.T) {
- if err := CheckSupport(); err != nil {
- t.Skip("sandboxing not supported:", err)
- }
-
- dir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(dir)
-
- ping := filepath.Join(dir, "ping.exe")
- rpcplugintest.CompileGo(t, `
- package main
-
- import (
- "crypto/rand"
- "fmt"
- "io/ioutil"
- "net/http"
- "os"
- "os/exec"
- "syscall"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
- )
-
- var failures int
-
- type T struct {}
- func (T) Errorf(format string, args ...interface{}) {
- fmt.Printf(format, args...)
- failures++
- }
- func (T) FailNow() {
- os.Exit(1)
- }
-
- func init() {
- if len(os.Args) > 0 && os.Args[0] == "exitImmediately" {
- os.Exit(0)
- }
- }
-
- func main() {
- t := &T{}
-
- pwd, err := os.Getwd()
- assert.NoError(t, err)
- assert.Equal(t, "/dir", pwd)
-
- assert.Equal(t, 0, os.Getgid(), "we should see ourselves as root")
- assert.Equal(t, 0, os.Getuid(), "we should see ourselves as root")
-
- f, err := ioutil.TempFile("", "")
- require.NoError(t, err, "we should be able to create temporary files")
- f.Close()
-
- _, err = os.Stat("ping.exe")
- assert.NoError(t, err, "we should be able to read files in the working directory")
-
- buf := make([]byte, 20)
- n, err := rand.Read(buf)
- assert.Equal(t, 20, n)
- assert.NoError(t, err, "we should be able to read from /dev/urandom")
-
- f, err = os.Create("/dev/zero")
- require.NoError(t, err, "we should be able to write to /dev/zero")
- defer f.Close()
- n, err = f.Write([]byte("foo"))
- assert.Equal(t, 3, n)
- require.NoError(t, err, "we should be able to write to /dev/zero")
-
- f, err = os.Create("/dir/foo")
- if f != nil {
- defer f.Close()
- }
- assert.Error(t, err, "we shouldn't be able to write to this read-only mount point")
-
- _, err = ioutil.ReadFile("/etc/resolv.conf")
- require.NoError(t, err, "we should be able to read /etc/resolv.conf")
-
- resp, err := http.Get("https://github.com")
- require.NoError(t, err, "we should be able to use the network")
- resp.Body.Close()
-
- status, err := ioutil.ReadFile("/proc/self/status")
- require.NoError(t, err, "we should be able to read from /proc")
- assert.Regexp(t, status, "CapEff:\\s+0000000000000000", "we should have no effective capabilities")
-
- require.NoError(t, os.MkdirAll("/tmp/dir2", 0755))
- err = syscall.Mount("/dir", "/tmp/dir2", "", syscall.MS_BIND, "")
- assert.Equal(t, syscall.EPERM, err, "we shouldn't be allowed to mount things")
-
- cmd := exec.Command("/proc/self/exe")
- cmd.Args = []string{"exitImmediately"}
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Pdeathsig: syscall.SIGTERM,
- }
- assert.NoError(t, cmd.Run(), "we should be able to re-exec ourself")
-
- cmd = exec.Command("/proc/self/exe")
- cmd.Args = []string{"exitImmediately"}
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Cloneflags: syscall.CLONE_NEWNS | syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWUSER,
- Pdeathsig: syscall.SIGTERM,
- }
- assert.Error(t, cmd.Run(), "we shouldn't be able to create new namespaces anymore")
-
- ipc, err := rpcplugin.InheritedProcessIPC()
- require.NoError(t, err)
- defer ipc.Close()
- _, err = ipc.Write([]byte("ping"))
- require.NoError(t, err)
-
- if failures > 0 {
- os.Exit(1)
- }
- }
- `, ping)
-
- p, ipc, err := NewProcess(context.Background(), &Configuration{
- MountPoints: []*MountPoint{
- {
- Source: dir,
- Destination: "/dir",
- ReadOnly: true,
- },
- },
- WorkingDirectory: "/dir",
- }, "/dir/ping.exe")
- require.NoError(t, err)
- defer ipc.Close()
- b := make([]byte, 10)
- n, err := ipc.Read(b)
- require.NoError(t, err)
- assert.Equal(t, 4, n)
- assert.Equal(t, "ping", string(b[:4]))
- require.NoError(t, p.Wait())
-}
diff --git a/plugin/rpcplugin/sandbox/sandbox_other.go b/plugin/rpcplugin/sandbox/sandbox_other.go
deleted file mode 100644
index 3889ecdcc..000000000
--- a/plugin/rpcplugin/sandbox/sandbox_other.go
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-// +build !linux
-
-package sandbox
-
-import (
- "context"
- "fmt"
- "io"
-
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
-)
-
-func newProcess(ctx context.Context, config *Configuration, path string) (rpcplugin.Process, io.ReadWriteCloser, error) {
- return nil, nil, checkSupport()
-}
-
-func checkSupport() error {
- return fmt.Errorf("sandboxing is not supported on this platform")
-}
diff --git a/plugin/rpcplugin/sandbox/sandbox_test.go b/plugin/rpcplugin/sandbox/sandbox_test.go
deleted file mode 100644
index e0149e28d..000000000
--- a/plugin/rpcplugin/sandbox/sandbox_test.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package sandbox
-
-import (
- "testing"
-)
-
-// TestCheckSupport is here for debugging purposes and has no assertions. You can quickly test
-// sandboxing support with various systems by compiling the test executable and running this test on
-// your target systems. For example, with docker, executed from the root of the repo:
-//
-// docker run --rm -it -w /go/src/github.com/mattermost/mattermost-server
-// -v $(pwd):/go/src/github.com/mattermost/mattermost-server golang:1.9
-// go test -c ./plugin/rpcplugin
-//
-// docker run --rm -it --privileged -w /opt/mattermost
-// -v $(pwd):/opt/mattermost centos:6
-// ./rpcplugin.test --test.v --test.run TestCheckSupport
-func TestCheckSupport(t *testing.T) {
- if err := CheckSupport(); err != nil {
- t.Log(err.Error())
- }
-}
diff --git a/plugin/rpcplugin/sandbox/seccomp_linux.go b/plugin/rpcplugin/sandbox/seccomp_linux.go
deleted file mode 100644
index afe86e90a..000000000
--- a/plugin/rpcplugin/sandbox/seccomp_linux.go
+++ /dev/null
@@ -1,178 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package sandbox
-
-import (
- "syscall"
- "unsafe"
-
- "github.com/pkg/errors"
- "golang.org/x/net/bpf"
- "golang.org/x/sys/unix"
-)
-
-const (
- SECCOMP_RET_ALLOW = 0x7fff0000
- SECCOMP_RET_ERRNO = 0x00050000
-)
-
-const (
- EM_X86_64 = 62
-
- __AUDIT_ARCH_64BIT = 0x80000000
- __AUDIT_ARCH_LE = 0x40000000
-
- AUDIT_ARCH_X86_64 = EM_X86_64 | __AUDIT_ARCH_64BIT | __AUDIT_ARCH_LE
-
- nrSize = 4
- archOffset = nrSize
- ipOffset = archOffset + 4
- argsOffset = ipOffset + 8
-)
-
-type SeccompCondition interface {
- Filter(littleEndian bool, skipFalseSentinel uint8) []bpf.Instruction
-}
-
-func seccompArgLowWord(arg int, littleEndian bool) uint32 {
- offset := uint32(argsOffset + arg*8)
- if !littleEndian {
- offset += 4
- }
- return offset
-}
-
-func seccompArgHighWord(arg int, littleEndian bool) uint32 {
- offset := uint32(argsOffset + arg*8)
- if littleEndian {
- offset += 4
- }
- return offset
-}
-
-type SeccompArgHasNoBits struct {
- Arg int
- Mask uint64
-}
-
-func (c SeccompArgHasNoBits) Filter(littleEndian bool, skipFalseSentinel uint8) []bpf.Instruction {
- return []bpf.Instruction{
- bpf.LoadAbsolute{Off: seccompArgHighWord(c.Arg, littleEndian), Size: 4},
- bpf.JumpIf{Cond: bpf.JumpBitsSet, Val: uint32(c.Mask >> 32), SkipTrue: skipFalseSentinel},
- bpf.LoadAbsolute{Off: seccompArgLowWord(c.Arg, littleEndian), Size: 4},
- bpf.JumpIf{Cond: bpf.JumpBitsSet, Val: uint32(c.Mask), SkipTrue: skipFalseSentinel},
- }
-}
-
-type SeccompArgHasAnyBit struct {
- Arg int
- Mask uint64
-}
-
-func (c SeccompArgHasAnyBit) Filter(littleEndian bool, skipFalseSentinel uint8) []bpf.Instruction {
- return []bpf.Instruction{
- bpf.LoadAbsolute{Off: seccompArgHighWord(c.Arg, littleEndian), Size: 4},
- bpf.JumpIf{Cond: bpf.JumpBitsSet, Val: uint32(c.Mask >> 32), SkipTrue: 2},
- bpf.LoadAbsolute{Off: seccompArgLowWord(c.Arg, littleEndian), Size: 4},
- bpf.JumpIf{Cond: bpf.JumpBitsSet, Val: uint32(c.Mask), SkipFalse: skipFalseSentinel},
- }
-}
-
-type SeccompArgEquals struct {
- Arg int
- Value uint64
-}
-
-func (c SeccompArgEquals) Filter(littleEndian bool, skipFalseSentinel uint8) []bpf.Instruction {
- return []bpf.Instruction{
- bpf.LoadAbsolute{Off: seccompArgHighWord(c.Arg, littleEndian), Size: 4},
- bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(c.Value >> 32), SkipFalse: skipFalseSentinel},
- bpf.LoadAbsolute{Off: seccompArgLowWord(c.Arg, littleEndian), Size: 4},
- bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(c.Value), SkipFalse: skipFalseSentinel},
- }
-}
-
-type SeccompConditions struct {
- All []SeccompCondition
-}
-
-type SeccompSyscall struct {
- Syscall uint32
- Any []SeccompConditions
-}
-
-func SeccompFilter(arch uint32, allowedSyscalls []SeccompSyscall) (filter []bpf.Instruction) {
- filter = append(filter,
- bpf.LoadAbsolute{Off: archOffset, Size: 4},
- bpf.JumpIf{Cond: bpf.JumpEqual, Val: arch, SkipTrue: 1},
- bpf.RetConstant{Val: uint32(SECCOMP_RET_ERRNO | unix.EPERM)},
- )
-
- filter = append(filter, bpf.LoadAbsolute{Off: 0, Size: nrSize})
- for _, s := range allowedSyscalls {
- if s.Any != nil {
- syscallStart := len(filter)
- filter = append(filter, bpf.Instruction(nil))
- for _, cs := range s.Any {
- anyStart := len(filter)
- for _, c := range cs.All {
- filter = append(filter, c.Filter((arch&__AUDIT_ARCH_LE) != 0, 255)...)
- }
- filter = append(filter, bpf.RetConstant{Val: SECCOMP_RET_ALLOW})
- for i := anyStart; i < len(filter); i++ {
- if jump, ok := filter[i].(bpf.JumpIf); ok {
- if len(filter)-i-1 > 255 {
- panic("condition too long")
- }
- if jump.SkipFalse == 255 {
- jump.SkipFalse = uint8(len(filter) - i - 1)
- }
- if jump.SkipTrue == 255 {
- jump.SkipTrue = uint8(len(filter) - i - 1)
- }
- filter[i] = jump
- }
- }
- }
- filter = append(filter, bpf.RetConstant{Val: uint32(SECCOMP_RET_ERRNO | unix.EPERM)})
- if len(filter)-syscallStart-1 > 255 {
- panic("conditions too long")
- }
- filter[syscallStart] = bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(s.Syscall), SkipFalse: uint8(len(filter) - syscallStart - 1)}
- } else {
- filter = append(filter,
- bpf.JumpIf{Cond: bpf.JumpEqual, Val: uint32(s.Syscall), SkipFalse: 1},
- bpf.RetConstant{Val: SECCOMP_RET_ALLOW},
- )
- }
- }
-
- return append(filter, bpf.RetConstant{Val: uint32(SECCOMP_RET_ERRNO | unix.EPERM)})
-}
-
-func EnableSeccompFilter(filter []bpf.Instruction) error {
- assembled, err := bpf.Assemble(filter)
- if err != nil {
- return errors.Wrapf(err, "unable to assemble filter")
- }
-
- sockFilter := make([]unix.SockFilter, len(filter))
- for i, instruction := range assembled {
- sockFilter[i].Code = instruction.Op
- sockFilter[i].Jt = instruction.Jt
- sockFilter[i].Jf = instruction.Jf
- sockFilter[i].K = instruction.K
- }
-
- prog := unix.SockFprog{
- Len: uint16(len(sockFilter)),
- Filter: &sockFilter[0],
- }
-
- if _, _, errno := syscall.Syscall(syscall.SYS_PRCTL, unix.PR_SET_SECCOMP, unix.SECCOMP_MODE_FILTER, uintptr(unsafe.Pointer(&prog))); errno != 0 {
- return errors.Wrapf(syscall.Errno(errno), "syscall error")
- }
-
- return nil
-}
diff --git a/plugin/rpcplugin/sandbox/seccomp_linux_amd64.go b/plugin/rpcplugin/sandbox/seccomp_linux_amd64.go
deleted file mode 100644
index 7338ebbe0..000000000
--- a/plugin/rpcplugin/sandbox/seccomp_linux_amd64.go
+++ /dev/null
@@ -1,301 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package sandbox
-
-import (
- "golang.org/x/sys/unix"
-)
-
-const NATIVE_AUDIT_ARCH = AUDIT_ARCH_X86_64
-
-var AllowedSyscalls = []SeccompSyscall{
- {Syscall: unix.SYS_ACCEPT},
- {Syscall: unix.SYS_ACCEPT4},
- {Syscall: unix.SYS_ACCESS},
- {Syscall: unix.SYS_ADJTIMEX},
- {Syscall: unix.SYS_ALARM},
- {Syscall: unix.SYS_ARCH_PRCTL},
- {Syscall: unix.SYS_BIND},
- {Syscall: unix.SYS_BRK},
- {Syscall: unix.SYS_CAPGET},
- {Syscall: unix.SYS_CAPSET},
- {Syscall: unix.SYS_CHDIR},
- {Syscall: unix.SYS_CHMOD},
- {Syscall: unix.SYS_CHOWN},
- {Syscall: unix.SYS_CLOCK_GETRES},
- {Syscall: unix.SYS_CLOCK_GETTIME},
- {Syscall: unix.SYS_CLOCK_NANOSLEEP},
- {
- Syscall: unix.SYS_CLONE,
- Any: []SeccompConditions{{
- All: []SeccompCondition{SeccompArgHasNoBits{
- Arg: 0,
- Mask: unix.CLONE_NEWCGROUP | unix.CLONE_NEWIPC | unix.CLONE_NEWNET | unix.CLONE_NEWNS | unix.CLONE_NEWPID | unix.CLONE_NEWUSER | unix.CLONE_NEWUTS,
- }},
- }},
- },
- {Syscall: unix.SYS_CLOSE},
- {Syscall: unix.SYS_CONNECT},
- {Syscall: unix.SYS_COPY_FILE_RANGE},
- {Syscall: unix.SYS_CREAT},
- {Syscall: unix.SYS_DUP},
- {Syscall: unix.SYS_DUP2},
- {Syscall: unix.SYS_DUP3},
- {Syscall: unix.SYS_EPOLL_CREATE},
- {Syscall: unix.SYS_EPOLL_CREATE1},
- {Syscall: unix.SYS_EPOLL_CTL},
- {Syscall: unix.SYS_EPOLL_CTL_OLD},
- {Syscall: unix.SYS_EPOLL_PWAIT},
- {Syscall: unix.SYS_EPOLL_WAIT},
- {Syscall: unix.SYS_EPOLL_WAIT_OLD},
- {Syscall: unix.SYS_EVENTFD},
- {Syscall: unix.SYS_EVENTFD2},
- {Syscall: unix.SYS_EXECVE},
- {Syscall: unix.SYS_EXECVEAT},
- {Syscall: unix.SYS_EXIT},
- {Syscall: unix.SYS_EXIT_GROUP},
- {Syscall: unix.SYS_FACCESSAT},
- {Syscall: unix.SYS_FADVISE64},
- {Syscall: unix.SYS_FALLOCATE},
- {Syscall: unix.SYS_FANOTIFY_MARK},
- {Syscall: unix.SYS_FCHDIR},
- {Syscall: unix.SYS_FCHMOD},
- {Syscall: unix.SYS_FCHMODAT},
- {Syscall: unix.SYS_FCHOWN},
- {Syscall: unix.SYS_FCHOWNAT},
- {Syscall: unix.SYS_FCNTL},
- {Syscall: unix.SYS_FDATASYNC},
- {Syscall: unix.SYS_FGETXATTR},
- {Syscall: unix.SYS_FLISTXATTR},
- {Syscall: unix.SYS_FLOCK},
- {Syscall: unix.SYS_FORK},
- {Syscall: unix.SYS_FREMOVEXATTR},
- {Syscall: unix.SYS_FSETXATTR},
- {Syscall: unix.SYS_FSTAT},
- {Syscall: unix.SYS_FSTATFS},
- {Syscall: unix.SYS_FSYNC},
- {Syscall: unix.SYS_FTRUNCATE},
- {Syscall: unix.SYS_FUTEX},
- {Syscall: unix.SYS_FUTIMESAT},
- {Syscall: unix.SYS_GETCPU},
- {Syscall: unix.SYS_GETCWD},
- {Syscall: unix.SYS_GETDENTS},
- {Syscall: unix.SYS_GETDENTS64},
- {Syscall: unix.SYS_GETEGID},
- {Syscall: unix.SYS_GETEUID},
- {Syscall: unix.SYS_GETGID},
- {Syscall: unix.SYS_GETGROUPS},
- {Syscall: unix.SYS_GETITIMER},
- {Syscall: unix.SYS_GETPEERNAME},
- {Syscall: unix.SYS_GETPGID},
- {Syscall: unix.SYS_GETPGRP},
- {Syscall: unix.SYS_GETPID},
- {Syscall: unix.SYS_GETPPID},
- {Syscall: unix.SYS_GETPRIORITY},
- {Syscall: unix.SYS_GETRANDOM},
- {Syscall: unix.SYS_GETRESGID},
- {Syscall: unix.SYS_GETRESUID},
- {Syscall: unix.SYS_GETRLIMIT},
- {Syscall: unix.SYS_GET_ROBUST_LIST},
- {Syscall: unix.SYS_GETRUSAGE},
- {Syscall: unix.SYS_GETSID},
- {Syscall: unix.SYS_GETSOCKNAME},
- {Syscall: unix.SYS_GETSOCKOPT},
- {Syscall: unix.SYS_GET_THREAD_AREA},
- {Syscall: unix.SYS_GETTID},
- {Syscall: unix.SYS_GETTIMEOFDAY},
- {Syscall: unix.SYS_GETUID},
- {Syscall: unix.SYS_GETXATTR},
- {Syscall: unix.SYS_INOTIFY_ADD_WATCH},
- {Syscall: unix.SYS_INOTIFY_INIT},
- {Syscall: unix.SYS_INOTIFY_INIT1},
- {Syscall: unix.SYS_INOTIFY_RM_WATCH},
- {Syscall: unix.SYS_IO_CANCEL},
- {Syscall: unix.SYS_IOCTL},
- {Syscall: unix.SYS_IO_DESTROY},
- {Syscall: unix.SYS_IO_GETEVENTS},
- {Syscall: unix.SYS_IOPRIO_GET},
- {Syscall: unix.SYS_IOPRIO_SET},
- {Syscall: unix.SYS_IO_SETUP},
- {Syscall: unix.SYS_IO_SUBMIT},
- {Syscall: unix.SYS_KILL},
- {Syscall: unix.SYS_LCHOWN},
- {Syscall: unix.SYS_LGETXATTR},
- {Syscall: unix.SYS_LINK},
- {Syscall: unix.SYS_LINKAT},
- {Syscall: unix.SYS_LISTEN},
- {Syscall: unix.SYS_LISTXATTR},
- {Syscall: unix.SYS_LLISTXATTR},
- {Syscall: unix.SYS_LREMOVEXATTR},
- {Syscall: unix.SYS_LSEEK},
- {Syscall: unix.SYS_LSETXATTR},
- {Syscall: unix.SYS_LSTAT},
- {Syscall: unix.SYS_MADVISE},
- {Syscall: unix.SYS_MEMFD_CREATE},
- {Syscall: unix.SYS_MINCORE},
- {Syscall: unix.SYS_MKDIR},
- {Syscall: unix.SYS_MKDIRAT},
- {Syscall: unix.SYS_MKNOD},
- {Syscall: unix.SYS_MKNODAT},
- {Syscall: unix.SYS_MLOCK},
- {Syscall: unix.SYS_MLOCK2},
- {Syscall: unix.SYS_MLOCKALL},
- {Syscall: unix.SYS_MMAP},
- {Syscall: unix.SYS_MODIFY_LDT},
- {Syscall: unix.SYS_MPROTECT},
- {Syscall: unix.SYS_MQ_GETSETATTR},
- {Syscall: unix.SYS_MQ_NOTIFY},
- {Syscall: unix.SYS_MQ_OPEN},
- {Syscall: unix.SYS_MQ_TIMEDRECEIVE},
- {Syscall: unix.SYS_MQ_TIMEDSEND},
- {Syscall: unix.SYS_MQ_UNLINK},
- {Syscall: unix.SYS_MREMAP},
- {Syscall: unix.SYS_MSGCTL},
- {Syscall: unix.SYS_MSGGET},
- {Syscall: unix.SYS_MSGRCV},
- {Syscall: unix.SYS_MSGSND},
- {Syscall: unix.SYS_MSYNC},
- {Syscall: unix.SYS_MUNLOCK},
- {Syscall: unix.SYS_MUNLOCKALL},
- {Syscall: unix.SYS_MUNMAP},
- {Syscall: unix.SYS_NANOSLEEP},
- {Syscall: unix.SYS_NEWFSTATAT},
- {Syscall: unix.SYS_OPEN},
- {Syscall: unix.SYS_OPENAT},
- {Syscall: unix.SYS_PAUSE},
- {
- Syscall: unix.SYS_PERSONALITY,
- Any: []SeccompConditions{
- {All: []SeccompCondition{SeccompArgEquals{Arg: 0, Value: 0}}},
- {All: []SeccompCondition{SeccompArgEquals{Arg: 0, Value: 8}}},
- {All: []SeccompCondition{SeccompArgEquals{Arg: 0, Value: 0x20000}}},
- {All: []SeccompCondition{SeccompArgEquals{Arg: 0, Value: 0x20008}}},
- {All: []SeccompCondition{SeccompArgEquals{Arg: 0, Value: 0xffffffff}}},
- },
- },
- {Syscall: unix.SYS_PIPE},
- {Syscall: unix.SYS_PIPE2},
- {Syscall: unix.SYS_POLL},
- {Syscall: unix.SYS_PPOLL},
- {Syscall: unix.SYS_PRCTL},
- {Syscall: unix.SYS_PREAD64},
- {Syscall: unix.SYS_PREADV},
- {Syscall: unix.SYS_PREADV2},
- {Syscall: unix.SYS_PRLIMIT64},
- {Syscall: unix.SYS_PSELECT6},
- {Syscall: unix.SYS_PWRITE64},
- {Syscall: unix.SYS_PWRITEV},
- {Syscall: unix.SYS_PWRITEV2},
- {Syscall: unix.SYS_READ},
- {Syscall: unix.SYS_READAHEAD},
- {Syscall: unix.SYS_READLINK},
- {Syscall: unix.SYS_READLINKAT},
- {Syscall: unix.SYS_READV},
- {Syscall: unix.SYS_RECVFROM},
- {Syscall: unix.SYS_RECVMMSG},
- {Syscall: unix.SYS_RECVMSG},
- {Syscall: unix.SYS_REMAP_FILE_PAGES},
- {Syscall: unix.SYS_REMOVEXATTR},
- {Syscall: unix.SYS_RENAME},
- {Syscall: unix.SYS_RENAMEAT},
- {Syscall: unix.SYS_RENAMEAT2},
- {Syscall: unix.SYS_RESTART_SYSCALL},
- {Syscall: unix.SYS_RMDIR},
- {Syscall: unix.SYS_RT_SIGACTION},
- {Syscall: unix.SYS_RT_SIGPENDING},
- {Syscall: unix.SYS_RT_SIGPROCMASK},
- {Syscall: unix.SYS_RT_SIGQUEUEINFO},
- {Syscall: unix.SYS_RT_SIGRETURN},
- {Syscall: unix.SYS_RT_SIGSUSPEND},
- {Syscall: unix.SYS_RT_SIGTIMEDWAIT},
- {Syscall: unix.SYS_RT_TGSIGQUEUEINFO},
- {Syscall: unix.SYS_SCHED_GETAFFINITY},
- {Syscall: unix.SYS_SCHED_GETATTR},
- {Syscall: unix.SYS_SCHED_GETPARAM},
- {Syscall: unix.SYS_SCHED_GET_PRIORITY_MAX},
- {Syscall: unix.SYS_SCHED_GET_PRIORITY_MIN},
- {Syscall: unix.SYS_SCHED_GETSCHEDULER},
- {Syscall: unix.SYS_SCHED_RR_GET_INTERVAL},
- {Syscall: unix.SYS_SCHED_SETAFFINITY},
- {Syscall: unix.SYS_SCHED_SETATTR},
- {Syscall: unix.SYS_SCHED_SETPARAM},
- {Syscall: unix.SYS_SCHED_SETSCHEDULER},
- {Syscall: unix.SYS_SCHED_YIELD},
- {Syscall: unix.SYS_SECCOMP},
- {Syscall: unix.SYS_SELECT},
- {Syscall: unix.SYS_SEMCTL},
- {Syscall: unix.SYS_SEMGET},
- {Syscall: unix.SYS_SEMOP},
- {Syscall: unix.SYS_SEMTIMEDOP},
- {Syscall: unix.SYS_SENDFILE},
- {Syscall: unix.SYS_SENDMMSG},
- {Syscall: unix.SYS_SENDMSG},
- {Syscall: unix.SYS_SENDTO},
- {Syscall: unix.SYS_SETFSGID},
- {Syscall: unix.SYS_SETFSUID},
- {Syscall: unix.SYS_SETGID},
- {Syscall: unix.SYS_SETGROUPS},
- {Syscall: unix.SYS_SETITIMER},
- {Syscall: unix.SYS_SETPGID},
- {Syscall: unix.SYS_SETPRIORITY},
- {Syscall: unix.SYS_SETREGID},
- {Syscall: unix.SYS_SETRESGID},
- {Syscall: unix.SYS_SETRESUID},
- {Syscall: unix.SYS_SETREUID},
- {Syscall: unix.SYS_SETRLIMIT},
- {Syscall: unix.SYS_SET_ROBUST_LIST},
- {Syscall: unix.SYS_SETSID},
- {Syscall: unix.SYS_SETSOCKOPT},
- {Syscall: unix.SYS_SET_THREAD_AREA},
- {Syscall: unix.SYS_SET_TID_ADDRESS},
- {Syscall: unix.SYS_SETUID},
- {Syscall: unix.SYS_SETXATTR},
- {Syscall: unix.SYS_SHMAT},
- {Syscall: unix.SYS_SHMCTL},
- {Syscall: unix.SYS_SHMDT},
- {Syscall: unix.SYS_SHMGET},
- {Syscall: unix.SYS_SHUTDOWN},
- {Syscall: unix.SYS_SIGALTSTACK},
- {Syscall: unix.SYS_SIGNALFD},
- {Syscall: unix.SYS_SIGNALFD4},
- {Syscall: unix.SYS_SOCKET},
- {Syscall: unix.SYS_SOCKETPAIR},
- {Syscall: unix.SYS_SPLICE},
- {Syscall: unix.SYS_STAT},
- {Syscall: unix.SYS_STATFS},
- {Syscall: unix.SYS_SYMLINK},
- {Syscall: unix.SYS_SYMLINKAT},
- {Syscall: unix.SYS_SYNC},
- {Syscall: unix.SYS_SYNC_FILE_RANGE},
- {Syscall: unix.SYS_SYNCFS},
- {Syscall: unix.SYS_SYSINFO},
- {Syscall: unix.SYS_SYSLOG},
- {Syscall: unix.SYS_TEE},
- {Syscall: unix.SYS_TGKILL},
- {Syscall: unix.SYS_TIME},
- {Syscall: unix.SYS_TIMER_CREATE},
- {Syscall: unix.SYS_TIMER_DELETE},
- {Syscall: unix.SYS_TIMERFD_CREATE},
- {Syscall: unix.SYS_TIMERFD_GETTIME},
- {Syscall: unix.SYS_TIMERFD_SETTIME},
- {Syscall: unix.SYS_TIMER_GETOVERRUN},
- {Syscall: unix.SYS_TIMER_GETTIME},
- {Syscall: unix.SYS_TIMER_SETTIME},
- {Syscall: unix.SYS_TIMES},
- {Syscall: unix.SYS_TKILL},
- {Syscall: unix.SYS_TRUNCATE},
- {Syscall: unix.SYS_UMASK},
- {Syscall: unix.SYS_UNAME},
- {Syscall: unix.SYS_UNLINK},
- {Syscall: unix.SYS_UNLINKAT},
- {Syscall: unix.SYS_UTIME},
- {Syscall: unix.SYS_UTIMENSAT},
- {Syscall: unix.SYS_UTIMES},
- {Syscall: unix.SYS_VFORK},
- {Syscall: unix.SYS_VMSPLICE},
- {Syscall: unix.SYS_WAIT4},
- {Syscall: unix.SYS_WAITID},
- {Syscall: unix.SYS_WRITE},
- {Syscall: unix.SYS_WRITEV},
-}
diff --git a/plugin/rpcplugin/sandbox/seccomp_linux_other.go b/plugin/rpcplugin/sandbox/seccomp_linux_other.go
deleted file mode 100644
index 5573943cd..000000000
--- a/plugin/rpcplugin/sandbox/seccomp_linux_other.go
+++ /dev/null
@@ -1,10 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-// +build linux,!amd64
-
-package sandbox
-
-const NATIVE_AUDIT_ARCH = 0
-
-var AllowedSyscalls []SeccompSyscall
diff --git a/plugin/rpcplugin/sandbox/seccomp_linux_test.go b/plugin/rpcplugin/sandbox/seccomp_linux_test.go
deleted file mode 100644
index 46fe38fe0..000000000
--- a/plugin/rpcplugin/sandbox/seccomp_linux_test.go
+++ /dev/null
@@ -1,210 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package sandbox
-
-import (
- "encoding/binary"
- "syscall"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "golang.org/x/net/bpf"
-)
-
-func seccompData(nr int32, arch uint32, ip uint64, args ...uint64) []byte {
- var buf [64]byte
- binary.BigEndian.PutUint32(buf[0:], uint32(nr))
- binary.BigEndian.PutUint32(buf[4:], arch)
- binary.BigEndian.PutUint64(buf[8:], ip)
- for i := 0; i < 6 && i < len(args); i++ {
- binary.BigEndian.PutUint64(buf[16+i*8:], args[i])
- }
- return buf[:]
-}
-
-func TestSeccompFilter(t *testing.T) {
- for name, tc := range map[string]struct {
- Filter []bpf.Instruction
- Data []byte
- Expected bool
- }{
- "Allowed": {
- Filter: SeccompFilter(0xf00, []SeccompSyscall{
- {Syscall: syscall.SYS_READ},
- {Syscall: syscall.SYS_WRITE},
- }),
- Data: seccompData(syscall.SYS_READ, 0xf00, 0),
- Expected: true,
- },
- "AllFail": {
- Filter: SeccompFilter(0xf00, []SeccompSyscall{
- {
- Syscall: syscall.SYS_READ,
- Any: []SeccompConditions{
- {All: []SeccompCondition{
- &SeccompArgHasAnyBit{Arg: 0, Mask: 2},
- &SeccompArgHasAnyBit{Arg: 1, Mask: 2},
- &SeccompArgHasAnyBit{Arg: 2, Mask: 2},
- &SeccompArgHasAnyBit{Arg: 3, Mask: 2},
- }},
- },
- },
- {Syscall: syscall.SYS_WRITE},
- }),
- Data: seccompData(syscall.SYS_READ, 0xf00, 0, 1, 2, 3, 4),
- Expected: false,
- },
- "AllPass": {
- Filter: SeccompFilter(0xf00, []SeccompSyscall{
- {
- Syscall: syscall.SYS_READ,
- Any: []SeccompConditions{
- {All: []SeccompCondition{
- &SeccompArgHasAnyBit{Arg: 0, Mask: 7},
- &SeccompArgHasAnyBit{Arg: 1, Mask: 7},
- &SeccompArgHasAnyBit{Arg: 2, Mask: 7},
- &SeccompArgHasAnyBit{Arg: 3, Mask: 7},
- }},
- },
- },
- {Syscall: syscall.SYS_WRITE},
- }),
- Data: seccompData(syscall.SYS_READ, 0xf00, 0, 1, 2, 3, 4),
- Expected: true,
- },
- "AnyFail": {
- Filter: SeccompFilter(0xf00, []SeccompSyscall{
- {
- Syscall: syscall.SYS_READ,
- Any: []SeccompConditions{
- {All: []SeccompCondition{&SeccompArgHasAnyBit{Arg: 0, Mask: 8}}},
- {All: []SeccompCondition{&SeccompArgHasAnyBit{Arg: 1, Mask: 8}}},
- {All: []SeccompCondition{&SeccompArgHasAnyBit{Arg: 2, Mask: 8}}},
- {All: []SeccompCondition{&SeccompArgHasAnyBit{Arg: 3, Mask: 8}}},
- },
- },
- {Syscall: syscall.SYS_WRITE},
- }),
- Data: seccompData(syscall.SYS_READ, 0xf00, 0, 1, 2, 3, 4),
- Expected: false,
- },
- "AnyPass": {
- Filter: SeccompFilter(0xf00, []SeccompSyscall{
- {
- Syscall: syscall.SYS_READ,
- Any: []SeccompConditions{
- {All: []SeccompCondition{&SeccompArgHasAnyBit{Arg: 0, Mask: 2}}},
- {All: []SeccompCondition{&SeccompArgHasAnyBit{Arg: 1, Mask: 2}}},
- {All: []SeccompCondition{&SeccompArgHasAnyBit{Arg: 2, Mask: 2}}},
- {All: []SeccompCondition{&SeccompArgHasAnyBit{Arg: 3, Mask: 2}}},
- },
- },
- {Syscall: syscall.SYS_WRITE},
- }),
- Data: seccompData(syscall.SYS_READ, 0xf00, 0, 1, 2, 3, 4),
- Expected: true,
- },
- "BadArch": {
- Filter: SeccompFilter(0xf00, []SeccompSyscall{
- {Syscall: syscall.SYS_READ},
- {Syscall: syscall.SYS_WRITE},
- }),
- Data: seccompData(syscall.SYS_MOUNT, 0xf01, 0),
- Expected: false,
- },
- "BadSyscall": {
- Filter: SeccompFilter(0xf00, []SeccompSyscall{
- {Syscall: syscall.SYS_READ},
- {Syscall: syscall.SYS_WRITE},
- }),
- Data: seccompData(syscall.SYS_MOUNT, 0xf00, 0),
- Expected: false,
- },
- } {
- t.Run(name, func(t *testing.T) {
- vm, err := bpf.NewVM(tc.Filter)
- require.NoError(t, err)
- result, err := vm.Run(tc.Data)
- require.NoError(t, err)
- if tc.Expected {
- assert.Equal(t, SECCOMP_RET_ALLOW, result)
- } else {
- assert.Equal(t, int(SECCOMP_RET_ERRNO|syscall.EPERM), result)
- }
- })
- }
-}
-
-func TestSeccompFilter_Conditions(t *testing.T) {
- for name, tc := range map[string]struct {
- Condition SeccompCondition
- Args []uint64
- Expected bool
- }{
- "ArgHasAnyBitFail": {
- Condition: SeccompArgHasAnyBit{Arg: 0, Mask: 0x0004},
- Args: []uint64{0x0400008000},
- Expected: false,
- },
- "ArgHasAnyBitPass1": {
- Condition: SeccompArgHasAnyBit{Arg: 0, Mask: 0x400000004},
- Args: []uint64{0x8000008004},
- Expected: true,
- },
- "ArgHasAnyBitPass2": {
- Condition: SeccompArgHasAnyBit{Arg: 0, Mask: 0x400000004},
- Args: []uint64{0x8400008000},
- Expected: true,
- },
- "ArgHasNoBitsFail1": {
- Condition: SeccompArgHasNoBits{Arg: 0, Mask: 0x1100000011},
- Args: []uint64{0x0000008007},
- Expected: false,
- },
- "ArgHasNoBitsFail2": {
- Condition: SeccompArgHasNoBits{Arg: 0, Mask: 0x1100000011},
- Args: []uint64{0x0700008000},
- Expected: false,
- },
- "ArgHasNoBitsPass": {
- Condition: SeccompArgHasNoBits{Arg: 0, Mask: 0x400000004},
- Args: []uint64{0x8000008000},
- Expected: true,
- },
- "ArgEqualsPass": {
- Condition: SeccompArgEquals{Arg: 0, Value: 0x123456789ABCDEF},
- Args: []uint64{0x123456789ABCDEF},
- Expected: true,
- },
- "ArgEqualsFail1": {
- Condition: SeccompArgEquals{Arg: 0, Value: 0x123456789ABCDEF},
- Args: []uint64{0x023456789ABCDEF},
- Expected: false,
- },
- "ArgEqualsFail2": {
- Condition: SeccompArgEquals{Arg: 0, Value: 0x123456789ABCDEF},
- Args: []uint64{0x123456789ABCDE0},
- Expected: false,
- },
- } {
- t.Run(name, func(t *testing.T) {
- filter := SeccompFilter(0xf00, []SeccompSyscall{
- {
- Syscall: 1,
- Any: []SeccompConditions{{All: []SeccompCondition{tc.Condition}}},
- },
- })
- vm, err := bpf.NewVM(filter)
- require.NoError(t, err)
- result, err := vm.Run(seccompData(1, 0xf00, 0, tc.Args...))
- require.NoError(t, err)
- if tc.Expected {
- assert.Equal(t, SECCOMP_RET_ALLOW, result)
- } else {
- assert.Equal(t, int(SECCOMP_RET_ERRNO|syscall.EPERM), result)
- }
- })
- }
-}
diff --git a/plugin/rpcplugin/sandbox/supervisor.go b/plugin/rpcplugin/sandbox/supervisor.go
deleted file mode 100644
index 0e63954fd..000000000
--- a/plugin/rpcplugin/sandbox/supervisor.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package sandbox
-
-import (
- "context"
- "fmt"
- "io"
- "path/filepath"
- "strings"
-
- "github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/plugin"
- "github.com/mattermost/mattermost-server/plugin/rpcplugin"
-)
-
-func SupervisorProvider(bundle *model.BundleInfo) (plugin.Supervisor, error) {
- return rpcplugin.SupervisorWithNewProcessFunc(bundle, func(ctx context.Context) (rpcplugin.Process, io.ReadWriteCloser, error) {
- executable := filepath.Clean(filepath.Join(".", bundle.Manifest.Backend.Executable))
- if strings.HasPrefix(executable, "..") {
- return nil, nil, fmt.Errorf("invalid backend executable")
- }
- return NewProcess(ctx, &Configuration{
- MountPoints: []*MountPoint{{
- Source: bundle.Path,
- Destination: "/plugin",
- ReadOnly: true,
- }},
- WorkingDirectory: "/plugin",
- }, filepath.Join("/plugin", executable))
- })
-}
diff --git a/plugin/rpcplugin/sandbox/supervisor_test.go b/plugin/rpcplugin/sandbox/supervisor_test.go
deleted file mode 100644
index 245185dd5..000000000
--- a/plugin/rpcplugin/sandbox/supervisor_test.go
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package sandbox
-
-import (
- "testing"
-
- "github.com/mattermost/mattermost-server/plugin/rpcplugin/rpcplugintest"
-)
-
-func TestSupervisorProvider(t *testing.T) {
- if err := CheckSupport(); err != nil {
- t.Skip("sandboxing not supported:", err)
- }
-
- rpcplugintest.TestSupervisorProvider(t, SupervisorProvider)
-}
diff --git a/plugin/rpcplugin/supervisor.go b/plugin/rpcplugin/supervisor.go
deleted file mode 100644
index 246747c89..000000000
--- a/plugin/rpcplugin/supervisor.go
+++ /dev/null
@@ -1,176 +0,0 @@
-// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package rpcplugin
-
-import (
- "context"
- "fmt"
- "io"
- "path/filepath"
- "strings"
- "sync/atomic"
- "time"
-
- "github.com/mattermost/mattermost-server/mlog"
- "github.com/mattermost/mattermost-server/model"
- "github.com/mattermost/mattermost-server/plugin"
-)
-
-const (
- MaxProcessRestarts = 3
-)
-
-// Supervisor implements a plugin.Supervisor that launches the plugin in a separate process and
-// communicates via RPC.
-//
-// If the plugin unexpectedly exits, the supervisor will relaunch it after a short delay, but will
-// only restart a plugin at most three times.
-type Supervisor struct {
- hooks atomic.Value
- done chan bool
- cancel context.CancelFunc
- newProcess func(context.Context) (Process, io.ReadWriteCloser, error)
- pluginId string
- pluginErr error
-}
-
-var _ plugin.Supervisor = (*Supervisor)(nil)
-
-// Starts the plugin. This method will block until the plugin is successfully launched for the first
-// time and will return an error if the plugin cannot be launched at all.
-func (s *Supervisor) Start(api plugin.API) error {
- ctx, cancel := context.WithCancel(context.Background())
- s.done = make(chan bool, 1)
- start := make(chan error, 1)
- go s.run(ctx, start, api)
-
- select {
- case <-time.After(time.Second * 3):
- cancel()
- <-s.done
- return fmt.Errorf("timed out waiting for plugin")
- case err := <-start:
- s.cancel = cancel
- return err
- }
-}
-
-// Waits for the supervisor to stop (on demand or of its own accord), returning any error that
-// triggered the supervisor to stop.
-func (s *Supervisor) Wait() error {
- <-s.done
- return s.pluginErr
-}
-
-// Stops the plugin.
-func (s *Supervisor) Stop() error {
- s.cancel()
- <-s.done
- return nil
-}
-
-// Returns the hooks used to communicate with the plugin. The hooks may change if the plugin is
-// restarted, so the return value should not be cached.
-func (s *Supervisor) Hooks() plugin.Hooks {
- return s.hooks.Load().(plugin.Hooks)
-}
-
-func (s *Supervisor) run(ctx context.Context, start chan<- error, api plugin.API) {
- defer func() {
- close(s.done)
- }()
- done := ctx.Done()
- for i := 0; i <= MaxProcessRestarts; i++ {
- s.runPlugin(ctx, start, api)
- select {
- case <-done:
- return
- default:
- start = nil
- if i < MaxProcessRestarts {
- mlog.Error("Plugin terminated unexpectedly", mlog.String("plugin_id", s.pluginId))
- time.Sleep(time.Duration((1 + i*i)) * time.Second)
- } else {
- s.pluginErr = fmt.Errorf("plugin terminated unexpectedly too many times")
- mlog.Error("Plugin shutdown", mlog.String("plugin_id", s.pluginId), mlog.Int("max_process_restarts", MaxProcessRestarts), mlog.Err(s.pluginErr))
- }
- }
- }
-}
-
-func (s *Supervisor) runPlugin(ctx context.Context, start chan<- error, api plugin.API) error {
- if start == nil {
- mlog.Debug("Restarting plugin", mlog.String("plugin_id", s.pluginId))
- }
-
- p, ipc, err := s.newProcess(ctx)
- if err != nil {
- if start != nil {
- start <- err
- }
- return err
- }
-
- muxer := NewMuxer(ipc, false)
- closeMuxer := make(chan bool, 1)
- muxerClosed := make(chan error, 1)
- go func() {
- select {
- case <-ctx.Done():
- break
- case <-closeMuxer:
- break
- }
- muxerClosed <- muxer.Close()
- }()
-
- hooks, err := ConnectMain(muxer, s.pluginId)
- if err == nil {
- err = hooks.OnActivate(api)
- }
-
- if err != nil {
- if start != nil {
- start <- err
- }
- closeMuxer <- true
- <-muxerClosed
- p.Wait()
- return err
- }
-
- s.hooks.Store(hooks)
-
- if start != nil {
- start <- nil
- }
- p.Wait()
- closeMuxer <- true
- <-muxerClosed
-
- return nil
-}
-
-func SupervisorProvider(bundle *model.BundleInfo) (plugin.Supervisor, error) {
- return SupervisorWithNewProcessFunc(bundle, func(ctx context.Context) (Process, io.ReadWriteCloser, error) {
- executable := filepath.Clean(filepath.Join(".", bundle.Manifest.Backend.Executable))
- if strings.HasPrefix(executable, "..") {
- return nil, nil, fmt.Errorf("invalid backend executable")
- }
- return NewProcess(ctx, filepath.Join(bundle.Path, executable))
- })
-}
-
-func SupervisorWithNewProcessFunc(bundle *model.BundleInfo, newProcess func(context.Context) (Process, io.ReadWriteCloser, error)) (plugin.Supervisor, error) {
- if bundle.Manifest == nil {
- return nil, fmt.Errorf("no manifest available")
- } else if bundle.Manifest.Backend == nil || bundle.Manifest.Backend.Executable == "" {
- return nil, fmt.Errorf("no backend executable specified")
- }
- executable := filepath.Clean(filepath.Join(".", bundle.Manifest.Backend.Executable))
- if strings.HasPrefix(executable, "..") {
- return nil, fmt.Errorf("invalid backend executable")
- }
- return &Supervisor{pluginId: bundle.Manifest.Id, newProcess: newProcess}, nil
-}
diff --git a/plugin/rpcplugin/supervisor_test.go b/plugin/rpcplugin/supervisor_test.go
deleted file mode 100644
index 06c1fafeb..000000000
--- a/plugin/rpcplugin/supervisor_test.go
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-package rpcplugin
-
-import (
- "testing"
-
- "github.com/mattermost/mattermost-server/plugin/rpcplugin/rpcplugintest"
-)
-
-func TestSupervisorProvider(t *testing.T) {
- rpcplugintest.TestSupervisorProvider(t, SupervisorProvider)
-}
diff --git a/plugin/supervisor.go b/plugin/supervisor.go
index f20df7040..1e1005f53 100644
--- a/plugin/supervisor.go
+++ b/plugin/supervisor.go
@@ -1,13 +1,98 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
+// See LICENSE.txt for license information.
package plugin
-// Supervisor provides the interface for an object that controls the execution of a plugin. This
-// type is only relevant to the server, and isn't used by the plugins themselves.
-type Supervisor interface {
- Start(API) error
- Wait() error
- Stop() error
- Hooks() Hooks
+import (
+ "fmt"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "github.com/hashicorp/go-plugin"
+ "github.com/mattermost/mattermost-server/mlog"
+ "github.com/mattermost/mattermost-server/model"
+)
+
+type supervisor struct {
+ pluginId string
+ client *plugin.Client
+ hooks Hooks
+ implemented [TotalHooksId]bool
+}
+
+func newSupervisor(pluginInfo *model.BundleInfo, parentLogger *mlog.Logger, apiImpl API) (*supervisor, error) {
+ supervisor := supervisor{}
+
+ wrappedLogger := pluginInfo.WrapLogger(parentLogger)
+
+ hclogAdaptedLogger := &hclogAdapter{
+ wrappedLogger: wrappedLogger.WithCallerSkip(1),
+ extrasKey: "wrapped_extras",
+ }
+
+ pluginMap := map[string]plugin.Plugin{
+ "hooks": &hooksPlugin{
+ log: wrappedLogger,
+ apiImpl: apiImpl,
+ },
+ }
+
+ executable := filepath.Clean(filepath.Join(".", pluginInfo.Manifest.Backend.Executable))
+ if strings.HasPrefix(executable, "..") {
+ return nil, fmt.Errorf("invalid backend executable")
+ }
+ executable = filepath.Join(pluginInfo.Path, executable)
+
+ supervisor.client = plugin.NewClient(&plugin.ClientConfig{
+ HandshakeConfig: handshake,
+ Plugins: pluginMap,
+ Cmd: exec.Command(executable),
+ SyncStdout: wrappedLogger.With(mlog.String("source", "plugin_stdout")).StdLogWriter(),
+ SyncStderr: wrappedLogger.With(mlog.String("source", "plugin_stderr")).StdLogWriter(),
+ Logger: hclogAdaptedLogger,
+ StartTimeout: time.Second * 3,
+ })
+
+ rpcClient, err := supervisor.client.Client()
+ if err != nil {
+ return nil, err
+ }
+
+ raw, err := rpcClient.Dispense("hooks")
+ if err != nil {
+ return nil, err
+ }
+
+ supervisor.hooks = raw.(Hooks)
+
+ if impl, err := supervisor.hooks.Implemented(); err != nil {
+ return nil, err
+ } else {
+ for _, hookName := range impl {
+ if hookId, ok := hookNameToId[hookName]; ok {
+ supervisor.implemented[hookId] = true
+ }
+ }
+ }
+
+ err = supervisor.Hooks().OnActivate()
+ if err != nil {
+ return nil, err
+ }
+
+ return &supervisor, nil
+}
+
+func (sup *supervisor) Shutdown() {
+ sup.client.Kill()
+}
+
+func (sup *supervisor) Hooks() Hooks {
+ return sup.hooks
+}
+
+func (sup *supervisor) Implements(hookId int) bool {
+ return sup.implemented[hookId]
}
diff --git a/plugin/supervisor_test.go b/plugin/supervisor_test.go
new file mode 100644
index 000000000..19d0499e5
--- /dev/null
+++ b/plugin/supervisor_test.go
@@ -0,0 +1,147 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package plugin
+
+import (
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "testing"
+
+ "github.com/mattermost/mattermost-server/mlog"
+ "github.com/mattermost/mattermost-server/model"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/mock"
+ "github.com/stretchr/testify/require"
+)
+
+func TestSupervisor(t *testing.T) {
+ for name, f := range map[string]func(*testing.T){
+ "Supervisor": testSupervisor,
+ "Supervisor_InvalidExecutablePath": testSupervisor_InvalidExecutablePath,
+ "Supervisor_NonExistentExecutablePath": testSupervisor_NonExistentExecutablePath,
+ "Supervisor_StartTimeout": testSupervisor_StartTimeout,
+ } {
+ t.Run(name, f)
+ }
+}
+
+func CompileGo(t *testing.T, sourceCode, outputPath string) {
+ dir, err := ioutil.TempDir(".", "")
+ require.NoError(t, err)
+ defer os.RemoveAll(dir)
+ require.NoError(t, ioutil.WriteFile(filepath.Join(dir, "main.go"), []byte(sourceCode), 0600))
+ cmd := exec.Command("go", "build", "-o", outputPath, "main.go")
+ cmd.Dir = dir
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ require.NoError(t, cmd.Run())
+}
+
+func testSupervisor(t *testing.T) {
+ dir, err := ioutil.TempDir("", "")
+ require.NoError(t, err)
+ defer os.RemoveAll(dir)
+
+ backend := filepath.Join(dir, "backend.exe")
+ CompileGo(t, `
+ package main
+
+ import (
+ "github.com/mattermost/mattermost-server/plugin"
+ )
+
+ type MyPlugin struct {
+ plugin.MattermostPlugin
+ }
+
+ func main() {
+ plugin.ClientMain(&MyPlugin{})
+ }
+ `, backend)
+
+ ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "backend": {"executable": "backend.exe"}}`), 0600)
+
+ bundle := model.BundleInfoForPath(dir)
+ var api MockAPI
+ api.On("LoadPluginConfiguration", mock.Anything).Return(nil)
+ log := mlog.NewLogger(&mlog.LoggerConfiguration{
+ EnableConsole: true,
+ ConsoleJson: true,
+ ConsoleLevel: "error",
+ EnableFile: false,
+ })
+ supervisor, err := newSupervisor(bundle, log, &api)
+ require.NoError(t, err)
+ supervisor.Shutdown()
+}
+
+func testSupervisor_InvalidExecutablePath(t *testing.T) {
+ dir, err := ioutil.TempDir("", "")
+ require.NoError(t, err)
+ defer os.RemoveAll(dir)
+
+ ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "backend": {"executable": "/foo/../../backend.exe"}}`), 0600)
+
+ bundle := model.BundleInfoForPath(dir)
+ log := mlog.NewLogger(&mlog.LoggerConfiguration{
+ EnableConsole: true,
+ ConsoleJson: true,
+ ConsoleLevel: "error",
+ EnableFile: false,
+ })
+ supervisor, err := newSupervisor(bundle, log, nil)
+ assert.Nil(t, supervisor)
+ assert.Error(t, err)
+}
+
+func testSupervisor_NonExistentExecutablePath(t *testing.T) {
+ dir, err := ioutil.TempDir("", "")
+ require.NoError(t, err)
+ defer os.RemoveAll(dir)
+
+ ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "backend": {"executable": "thisfileshouldnotexist"}}`), 0600)
+
+ bundle := model.BundleInfoForPath(dir)
+ log := mlog.NewLogger(&mlog.LoggerConfiguration{
+ EnableConsole: true,
+ ConsoleJson: true,
+ ConsoleLevel: "error",
+ EnableFile: false,
+ })
+ supervisor, err := newSupervisor(bundle, log, nil)
+ require.Error(t, err)
+ require.Nil(t, supervisor)
+}
+
+// If plugin development goes really wrong, let's make sure plugin activation won't block forever.
+func testSupervisor_StartTimeout(t *testing.T) {
+ dir, err := ioutil.TempDir("", "")
+ require.NoError(t, err)
+ defer os.RemoveAll(dir)
+
+ backend := filepath.Join(dir, "backend.exe")
+ CompileGo(t, `
+ package main
+
+ func main() {
+ for {
+ }
+ }
+ `, backend)
+
+ ioutil.WriteFile(filepath.Join(dir, "plugin.json"), []byte(`{"id": "foo", "backend": {"executable": "backend.exe"}}`), 0600)
+
+ bundle := model.BundleInfoForPath(dir)
+ log := mlog.NewLogger(&mlog.LoggerConfiguration{
+ EnableConsole: true,
+ ConsoleJson: true,
+ ConsoleLevel: "error",
+ EnableFile: false,
+ })
+ supervisor, err := newSupervisor(bundle, log, nil)
+ require.Error(t, err)
+ require.Nil(t, supervisor)
+}
diff --git a/plugin/valid.go b/plugin/valid.go
index 62c594a1a..5c543e673 100644
--- a/plugin/valid.go
+++ b/plugin/valid.go
@@ -9,16 +9,23 @@ import (
)
const (
- MinIdLength = 3
- MaxIdLength = 190
+ MinIdLength = 3
+ MaxIdLength = 190
+ ValidIdRegex = `^[a-zA-Z0-9-_\.]+$`
)
-var ValidId *regexp.Regexp
+// ValidId constrains the set of valid plugin identifiers:
+// ^[a-zA-Z0-9-_\.]+
+var validId *regexp.Regexp
func init() {
- ValidId = regexp.MustCompile(`^[a-zA-Z0-9-_\.]+$`)
+ validId = regexp.MustCompile(ValidIdRegex)
}
+// IsValidId verifies that the plugin id has a minimum length of 3, maximum length of 190, and
+// contains only alphanumeric characters, dashes, underscores and periods.
+//
+// These constraints are necessary since the plugin id is used as part of a filesystem path.
func IsValidId(id string) bool {
if utf8.RuneCountInString(id) < MinIdLength {
return false
@@ -28,5 +35,5 @@ func IsValidId(id string) bool {
return false
}
- return ValidId.MatchString(id)
+ return validId.MatchString(id)
}