MM-11292: clean up plugins GoDoc (#9109)

* clean up plugins GoDoc:

- eliminate plugin.NewBlankContext() as unnecessary
- export ValidIdRegex as a string vs. the less readable var
- add/update various documentation strings
- hide everything by default, except where used by client plugins or the mattermost-server. The exception to this rule are the `*(Args|Returns)` structs which must be public for go-plugin, but are now prefixed with `Z_` with a warning not to use.
- include a top-level example to get plugin authors started

This is not a breaking change for existing plugins compiled against
plugins-v2.

* remove commented out ServeHTTPResponseWriter

* update examples to match developer docs

* add missing plugin/doc.go license header
Этот коммит содержится в:
Jesse Hallam
2018-07-13 10:29:50 -04:00
коммит произвёл GitHub
родитель 5ddb08dcb4
Коммит 17f211c393
20 изменённых файлов: 668 добавлений и 544 удалений

Просмотреть файл

@@ -105,7 +105,7 @@ func (a *App) ExecutePluginCommand(args *model.CommandArgs) (*model.Command, *mo
if err != nil { if err != nil {
return pc.Command, nil, model.NewAppError("ExecutePluginCommand", "model.plugin_command.error.app_error", nil, "err="+err.Error(), http.StatusInternalServerError) return pc.Command, nil, model.NewAppError("ExecutePluginCommand", "model.plugin_command.error.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
} }
response, appErr := pluginHooks.ExecuteCommand(plugin.NewBlankContext(), args) response, appErr := pluginHooks.ExecuteCommand(&plugin.Context{}, args)
return pc.Command, response, appErr return pc.Command, response, appErr
} }
} }

Просмотреть файл

@@ -52,7 +52,7 @@ func (a *App) installPlugin(pluginFile io.Reader) (*model.Manifest, *model.AppEr
} }
if !plugin.IsValidId(manifest.Id) { if !plugin.IsValidId(manifest.Id) {
return nil, model.NewAppError("installPlugin", "app.plugin.invalid_id.app_error", map[string]interface{}{"Min": plugin.MinIdLength, "Max": plugin.MaxIdLength, "Regex": plugin.ValidId.String()}, "", http.StatusBadRequest) return nil, model.NewAppError("installPlugin", "app.plugin.invalid_id.app_error", map[string]interface{}{"Min": plugin.MinIdLength, "Max": plugin.MaxIdLength, "Regex": plugin.ValidIdRegex}, "", http.StatusBadRequest)
} }
bundles, err := a.Plugins.Available() bundles, err := a.Plugins.Available()

Просмотреть файл

@@ -72,5 +72,5 @@ func (a *App) servePluginRequest(w http.ResponseWriter, r *http.Request, handler
r.URL.RawQuery = newQuery.Encode() r.URL.RawQuery = newQuery.Encode()
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/plugins/"+params["plugin_id"]) r.URL.Path = strings.TrimPrefix(r.URL.Path, "/plugins/"+params["plugin_id"])
handler(plugin.NewBlankContext(), w, r) handler(&plugin.Context{}, w, r)
} }

Просмотреть файл

@@ -11,7 +11,8 @@ import (
// The API can be used to retrieve data or perform actions on behalf of the plugin. Most methods // 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. // 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 { type API interface {
// LoadPluginConfiguration loads the plugin's configuration. dest should be a pointer to a // LoadPluginConfiguration loads the plugin's configuration. dest should be a pointer to a
// struct that the configuration JSON can be unmarshalled to. // struct that the configuration JSON can be unmarshalled to.
@@ -178,7 +179,7 @@ type API interface {
LogWarn(msg string, keyValuePairs ...interface{}) LogWarn(msg string, keyValuePairs ...interface{})
} }
var Handshake = plugin.HandshakeConfig{ var handshake = plugin.HandshakeConfig{
ProtocolVersion: 1, ProtocolVersion: 1,
MagicCookieKey: "MATTERMOST_PLUGIN", MagicCookieKey: "MATTERMOST_PLUGIN",
MagicCookieValue: "Securely message teams, anywhere.", MagicCookieValue: "Securely message teams, anywhere.",

Просмотреть файл

@@ -7,8 +7,9 @@ import (
"github.com/hashicorp/go-plugin" "github.com/hashicorp/go-plugin"
) )
// Starts the serving of a Mattermost plugin over rpc or gRPC // Starts the serving of a Mattermost plugin over net/rpc. gRPC is not yet supported.
// Call this when your plugin is ready to start //
// Call this when your plugin is ready to start.
func ClientMain(pluginImplementation interface{}) { func ClientMain(pluginImplementation interface{}) {
if impl, ok := pluginImplementation.(interface { if impl, ok := pluginImplementation.(interface {
SetAPI(api API) SetAPI(api API)
@@ -21,28 +22,39 @@ func ClientMain(pluginImplementation interface{}) {
} }
pluginMap := map[string]plugin.Plugin{ pluginMap := map[string]plugin.Plugin{
"hooks": &HooksPlugin{hooks: pluginImplementation}, "hooks": &hooksPlugin{hooks: pluginImplementation},
} }
plugin.Serve(&plugin.ServeConfig{ plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: Handshake, HandshakeConfig: handshake,
Plugins: pluginMap, Plugins: pluginMap,
}) })
} }
type MattermostPlugin struct { type MattermostPlugin struct {
// API exposes the plugin api, and becomes available just prior to the OnActive hook.
API API API API
selfRef interface{} // This is so we can unmarshal into our parent 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) { func (p *MattermostPlugin) SetAPI(api API) {
p.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{}) { func (p *MattermostPlugin) SetSelfRef(ref interface{}) {
p.selfRef = ref 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 { func (p *MattermostPlugin) OnConfigurationChange() error {
if p.selfRef != nil { if p.selfRef != nil {
return p.API.LoadPluginConfiguration(p.selfRef) return p.API.LoadPluginConfiguration(p.selfRef)

Просмотреть файл

@@ -22,9 +22,9 @@ import (
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
) )
var HookNameToId map[string]int = make(map[string]int) var hookNameToId map[string]int = make(map[string]int)
type HooksRPCClient struct { type hooksRPCClient struct {
client *rpc.Client client *rpc.Client
log *mlog.Logger log *mlog.Logger
muxBroker *plugin.MuxBroker muxBroker *plugin.MuxBroker
@@ -32,33 +32,33 @@ type HooksRPCClient struct {
implemented [TotalHooksId]bool implemented [TotalHooksId]bool
} }
type HooksRPCServer struct { type hooksRPCServer struct {
impl interface{} impl interface{}
muxBroker *plugin.MuxBroker muxBroker *plugin.MuxBroker
apiRPCClient *APIRPCClient apiRPCClient *apiRPCClient
} }
// Implements hashicorp/go-plugin/plugin.Plugin interface to connect the hooks of a plugin // Implements hashicorp/go-plugin/plugin.Plugin interface to connect the hooks of a plugin
type HooksPlugin struct { type hooksPlugin struct {
hooks interface{} hooks interface{}
apiImpl API apiImpl API
log *mlog.Logger log *mlog.Logger
} }
func (p *HooksPlugin) Server(b *plugin.MuxBroker) (interface{}, error) { func (p *hooksPlugin) Server(b *plugin.MuxBroker) (interface{}, error) {
return &HooksRPCServer{impl: p.hooks, muxBroker: b}, nil return &hooksRPCServer{impl: p.hooks, muxBroker: b}, nil
} }
func (p *HooksPlugin) Client(b *plugin.MuxBroker, client *rpc.Client) (interface{}, error) { 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 return &hooksRPCClient{client: client, log: p.log, muxBroker: b, apiImpl: p.apiImpl}, nil
} }
type APIRPCClient struct { type apiRPCClient struct {
client *rpc.Client client *rpc.Client
log *mlog.Logger log *mlog.Logger
} }
type APIRPCServer struct { type apiRPCServer struct {
impl API impl API
} }
@@ -72,17 +72,17 @@ func init() {
// These enforce compile time checks to make sure types implement the 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 // If you are getting an error here, you probably need to run `make pluginapi` to
// autogenerate RPC glue code // autogenerate RPC glue code
var _ plugin.Plugin = &HooksPlugin{} var _ plugin.Plugin = &hooksPlugin{}
var _ Hooks = &HooksRPCClient{} var _ Hooks = &hooksRPCClient{}
// //
// Below are specal cases for hooks or APIs that can not be auto generated // Below are specal cases for hooks or APIs that can not be auto generated
// //
func (g *HooksRPCClient) Implemented() (impl []string, err error) { func (g *hooksRPCClient) Implemented() (impl []string, err error) {
err = g.client.Call("Plugin.Implemented", struct{}{}, &impl) err = g.client.Call("Plugin.Implemented", struct{}{}, &impl)
for _, hookName := range impl { for _, hookName := range impl {
if hookId, ok := HookNameToId[hookName]; ok { if hookId, ok := hookNameToId[hookName]; ok {
g.implemented[hookId] = true g.implemented[hookId] = true
} }
} }
@@ -90,7 +90,7 @@ func (g *HooksRPCClient) Implemented() (impl []string, err error) {
} }
// Implemented replies with the names of the hooks that are implemented. // Implemented replies with the names of the hooks that are implemented.
func (s *HooksRPCServer) Implemented(args struct{}, reply *[]string) error { func (s *hooksRPCServer) Implemented(args struct{}, reply *[]string) error {
ifaceType := reflect.TypeOf((*Hooks)(nil)).Elem() ifaceType := reflect.TypeOf((*Hooks)(nil)).Elem()
implType := reflect.TypeOf(s.impl) implType := reflect.TypeOf(s.impl)
selfType := reflect.TypeOf(s) selfType := reflect.TypeOf(s)
@@ -130,24 +130,24 @@ func (s *HooksRPCServer) Implemented(args struct{}, reply *[]string) error {
return nil return nil
} }
type OnActivateArgs struct { type Z_OnActivateArgs struct {
APIMuxId uint32 APIMuxId uint32
} }
type OnActivateReturns struct { type Z_OnActivateReturns struct {
A error A error
} }
func (g *HooksRPCClient) OnActivate() error { func (g *hooksRPCClient) OnActivate() error {
muxId := g.muxBroker.NextId() muxId := g.muxBroker.NextId()
go g.muxBroker.AcceptAndServe(muxId, &APIRPCServer{ go g.muxBroker.AcceptAndServe(muxId, &apiRPCServer{
impl: g.apiImpl, impl: g.apiImpl,
}) })
_args := &OnActivateArgs{ _args := &Z_OnActivateArgs{
APIMuxId: muxId, APIMuxId: muxId,
} }
_returns := &OnActivateReturns{} _returns := &Z_OnActivateReturns{}
if err := g.client.Call("Plugin.OnActivate", _args, _returns); err != nil { if err := g.client.Call("Plugin.OnActivate", _args, _returns); err != nil {
g.log.Error("RPC call to OnActivate plugin failed.", mlog.Err(err)) g.log.Error("RPC call to OnActivate plugin failed.", mlog.Err(err))
@@ -155,13 +155,13 @@ func (g *HooksRPCClient) OnActivate() error {
return _returns.A return _returns.A
} }
func (s *HooksRPCServer) OnActivate(args *OnActivateArgs, returns *OnActivateReturns) error { func (s *hooksRPCServer) OnActivate(args *Z_OnActivateArgs, returns *Z_OnActivateReturns) error {
connection, err := s.muxBroker.Dial(args.APIMuxId) connection, err := s.muxBroker.Dial(args.APIMuxId)
if err != nil { if err != nil {
return err return err
} }
s.apiRPCClient = &APIRPCClient{ s.apiRPCClient = &apiRPCClient{
client: rpc.NewClient(connection), client: rpc.NewClient(connection),
} }
@@ -186,23 +186,23 @@ func (s *HooksRPCServer) OnActivate(args *OnActivateArgs, returns *OnActivateRet
return nil return nil
} }
type LoadPluginConfigurationArgs struct { type Z_LoadPluginConfigurationArgsArgs struct {
} }
type LoadPluginConfigurationReturns struct { type Z_LoadPluginConfigurationArgsReturns struct {
A []byte A []byte
} }
func (g *APIRPCClient) LoadPluginConfiguration(dest interface{}) error { func (g *apiRPCClient) LoadPluginConfiguration(dest interface{}) error {
_args := &LoadPluginConfigurationArgs{} _args := &Z_LoadPluginConfigurationArgsArgs{}
_returns := &LoadPluginConfigurationReturns{} _returns := &Z_LoadPluginConfigurationArgsReturns{}
if err := g.client.Call("Plugin.LoadPluginConfiguration", _args, _returns); err != nil { if err := g.client.Call("Plugin.LoadPluginConfiguration", _args, _returns); err != nil {
g.log.Error("RPC call to LoadPluginConfiguration API failed.", mlog.Err(err)) g.log.Error("RPC call to LoadPluginConfiguration API failed.", mlog.Err(err))
} }
return json.Unmarshal(_returns.A, dest) return json.Unmarshal(_returns.A, dest)
} }
func (s *APIRPCServer) LoadPluginConfiguration(args *LoadPluginConfigurationArgs, returns *LoadPluginConfigurationReturns) error { func (s *apiRPCServer) LoadPluginConfiguration(args *Z_LoadPluginConfigurationArgsArgs, returns *Z_LoadPluginConfigurationArgsReturns) error {
var config interface{} var config interface{}
if hook, ok := s.impl.(interface { if hook, ok := s.impl.(interface {
LoadPluginConfiguration(dest interface{}) error LoadPluginConfiguration(dest interface{}) error
@@ -220,17 +220,17 @@ func (s *APIRPCServer) LoadPluginConfiguration(args *LoadPluginConfigurationArgs
} }
func init() { func init() {
HookNameToId["ServeHTTP"] = ServeHTTPId hookNameToId["ServeHTTP"] = ServeHTTPId
} }
type ServeHTTPArgs struct { type Z_ServeHTTPArgs struct {
ResponseWriterStream uint32 ResponseWriterStream uint32
Request *http.Request Request *http.Request
Context *Context Context *Context
RequestBodyStream uint32 RequestBodyStream uint32
} }
func (g *HooksRPCClient) ServeHTTP(c *Context, w http.ResponseWriter, r *http.Request) { func (g *hooksRPCClient) ServeHTTP(c *Context, w http.ResponseWriter, r *http.Request) {
if !g.implemented[ServeHTTPId] { if !g.implemented[ServeHTTPId] {
http.NotFound(w, r) http.NotFound(w, r)
return return
@@ -247,7 +247,7 @@ func (g *HooksRPCClient) ServeHTTP(c *Context, w http.ResponseWriter, r *http.Re
defer connection.Close() defer connection.Close()
rpcServer := rpc.NewServer() rpcServer := rpc.NewServer()
if err := rpcServer.RegisterName("Plugin", &HTTPResponseWriterRPCServer{w: w}); err != nil { 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)) g.log.Error("Plugin failed to ServeHTTP, coulden't register RPC name", mlog.Err(err))
http.Error(w, "500 internal server error", http.StatusInternalServerError) http.Error(w, "500 internal server error", http.StatusInternalServerError)
return return
@@ -266,7 +266,7 @@ func (g *HooksRPCClient) ServeHTTP(c *Context, w http.ResponseWriter, r *http.Re
return return
} }
defer bodyConnection.Close() defer bodyConnection.Close()
ServeIOReader(r.Body, bodyConnection) serveIOReader(r.Body, bodyConnection)
}() }()
} }
@@ -282,7 +282,7 @@ func (g *HooksRPCClient) ServeHTTP(c *Context, w http.ResponseWriter, r *http.Re
RequestURI: r.RequestURI, RequestURI: r.RequestURI,
} }
if err := g.client.Call("Plugin.ServeHTTP", ServeHTTPArgs{ if err := g.client.Call("Plugin.ServeHTTP", Z_ServeHTTPArgs{
Context: c, Context: c,
ResponseWriterStream: serveHTTPStreamId, ResponseWriterStream: serveHTTPStreamId,
Request: forwardedRequest, Request: forwardedRequest,
@@ -294,13 +294,13 @@ func (g *HooksRPCClient) ServeHTTP(c *Context, w http.ResponseWriter, r *http.Re
return return
} }
func (s *HooksRPCServer) ServeHTTP(args *ServeHTTPArgs, returns *struct{}) error { func (s *hooksRPCServer) ServeHTTP(args *Z_ServeHTTPArgs, returns *struct{}) error {
connection, err := s.muxBroker.Dial(args.ResponseWriterStream) connection, err := s.muxBroker.Dial(args.ResponseWriterStream)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote response writer stream, error: %v", err.Error()) fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote response writer stream, error: %v", err.Error())
return err return err
} }
w := ConnectHTTPResponseWriter(connection) w := connectHTTPResponseWriter(connection)
defer w.Close() defer w.Close()
r := args.Request r := args.Request
@@ -310,7 +310,7 @@ func (s *HooksRPCServer) ServeHTTP(args *ServeHTTPArgs, returns *struct{}) error
fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote request body stream, error: %v", err.Error()) fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote request body stream, error: %v", err.Error())
return err return err
} }
r.Body = ConnectIOReader(connection) r.Body = connectIOReader(connection)
} else { } else {
r.Body = ioutil.NopCloser(&bytes.Buffer{}) r.Body = ioutil.NopCloser(&bytes.Buffer{})
} }

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@@ -3,9 +3,8 @@
package plugin 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 { type Context struct {
} }
func NewBlankContext() *Context {
return &Context{}
}

9
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

Просмотреть файл

@@ -14,31 +14,37 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
type APIImplCreatorFunc func(*model.Manifest) API type apiImplCreatorFunc func(*model.Manifest) API
type SupervisorCreatorFunc func(*model.BundleInfo, *mlog.Logger, API) (*Supervisor, error) type supervisorCreatorFunc func(*model.BundleInfo, *mlog.Logger, API) (*supervisor, error)
// Hooks will be the hooks API for the plugin // multiPluginHookRunnerFunc is a callback function to invoke as part of RunMultiPluginHook.
// Return value should be true if we should continue calling more plugins //
type MultliPluginHookRunnerFunc func(hooks Hooks) bool // Return false to stop the hook from iterating to subsequent plugins.
type multiPluginHookRunnerFunc func(hooks Hooks) bool
type ActivePlugin struct { type activePlugin struct {
BundleInfo *model.BundleInfo BundleInfo *model.BundleInfo
State int State int
Supervisor *Supervisor
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 { type Environment struct {
activePlugins map[string]ActivePlugin activePlugins map[string]activePlugin
mutex sync.RWMutex mutex sync.RWMutex
logger *mlog.Logger logger *mlog.Logger
newAPIImpl APIImplCreatorFunc newAPIImpl apiImplCreatorFunc
pluginDir string pluginDir string
webappPluginDir string webappPluginDir string
} }
func NewEnvironment(newAPIImpl APIImplCreatorFunc, pluginDir string, webappPluginDir string, logger *mlog.Logger) (*Environment, error) { func NewEnvironment(newAPIImpl apiImplCreatorFunc, pluginDir string, webappPluginDir string, logger *mlog.Logger) (*Environment, error) {
return &Environment{ return &Environment{
activePlugins: make(map[string]ActivePlugin), activePlugins: make(map[string]activePlugin),
logger: logger, logger: logger,
newAPIImpl: newAPIImpl, newAPIImpl: newAPIImpl,
pluginDir: pluginDir, pluginDir: pluginDir,
@@ -53,7 +59,7 @@ func NewEnvironment(newAPIImpl APIImplCreatorFunc, pluginDir string, webappPlugi
// parsed). // parsed).
// //
// Plugins are found non-recursively and paths beginning with a dot are always ignored. // Plugins are found non-recursively and paths beginning with a dot are always ignored.
func ScanSearchPath(path string) ([]*model.BundleInfo, error) { func scanSearchPath(path string) ([]*model.BundleInfo, error) {
files, err := ioutil.ReadDir(path) files, err := ioutil.ReadDir(path)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -72,7 +78,7 @@ func ScanSearchPath(path string) ([]*model.BundleInfo, error) {
// Returns a list of all plugins within the environment. // Returns a list of all plugins within the environment.
func (env *Environment) Available() ([]*model.BundleInfo, error) { func (env *Environment) Available() ([]*model.BundleInfo, error) {
return ScanSearchPath(env.pluginDir) return scanSearchPath(env.pluginDir)
} }
// Returns a list of all currently active plugins within the environment. // Returns a list of all currently active plugins within the environment.
@@ -88,12 +94,13 @@ func (env *Environment) Active() []*model.BundleInfo {
return activePlugins return activePlugins
} }
// IsActive returns true if the plugin with the given id is active.
func (env *Environment) IsActive(id string) bool { func (env *Environment) IsActive(id string) bool {
_, ok := env.activePlugins[id] _, ok := env.activePlugins[id]
return ok return ok
} }
// Returns a list of plugin statuses reprensenting the state of every plugin // Statuses returns a list of plugin statuses representing the state of every plugin
func (env *Environment) Statuses() (model.PluginStatuses, error) { func (env *Environment) Statuses() (model.PluginStatuses, error) {
env.mutex.RLock() env.mutex.RLock()
defer env.mutex.RUnlock() defer env.mutex.RUnlock()
@@ -130,6 +137,7 @@ func (env *Environment) Statuses() (model.PluginStatuses, error) {
return pluginStatuses, nil return pluginStatuses, nil
} }
// Activate activates the plugin with the given id.
func (env *Environment) Activate(id string) (reterr error) { func (env *Environment) Activate(id string) (reterr error) {
env.mutex.Lock() env.mutex.Lock()
defer env.mutex.Unlock() defer env.mutex.Unlock()
@@ -156,7 +164,7 @@ func (env *Environment) Activate(id string) (reterr error) {
return fmt.Errorf("plugin not found: %v", id) return fmt.Errorf("plugin not found: %v", id)
} }
activePlugin := ActivePlugin{BundleInfo: pluginInfo} activePlugin := activePlugin{BundleInfo: pluginInfo}
defer func() { defer func() {
if reterr == nil { if reterr == nil {
activePlugin.State = model.PluginStateRunning activePlugin.State = model.PluginStateRunning
@@ -185,11 +193,11 @@ func (env *Environment) Activate(id string) (reterr error) {
} }
if pluginInfo.Manifest.Backend != nil { if pluginInfo.Manifest.Backend != nil {
supervisor, err := NewSupervisor(pluginInfo, env.logger, env.newAPIImpl(pluginInfo.Manifest)) supervisor, err := newSupervisor(pluginInfo, env.logger, env.newAPIImpl(pluginInfo.Manifest))
if err != nil { if err != nil {
return errors.Wrapf(err, "unable to start plugin: %v", id) return errors.Wrapf(err, "unable to start plugin: %v", id)
} }
activePlugin.Supervisor = supervisor activePlugin.supervisor = supervisor
} }
return nil return nil
@@ -204,56 +212,59 @@ func (env *Environment) Deactivate(id string) {
return return
} else { } else {
delete(env.activePlugins, id) delete(env.activePlugins, id)
if activePlugin.Supervisor != nil { if activePlugin.supervisor != nil {
if err := activePlugin.Supervisor.Hooks().OnDeactivate(); err != 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)) env.logger.Error("Plugin OnDeactivate() error", mlog.String("plugin_id", activePlugin.BundleInfo.Manifest.Id), mlog.Err(err))
} }
activePlugin.Supervisor.Shutdown() activePlugin.supervisor.Shutdown()
} }
} }
} }
// Deactivates all plugins and gracefully shuts down the environment. // Shutdown deactivates all plugins and gracefully shuts down the environment.
func (env *Environment) Shutdown() { func (env *Environment) Shutdown() {
env.mutex.Lock() env.mutex.Lock()
defer env.mutex.Unlock() defer env.mutex.Unlock()
for _, activePlugin := range env.activePlugins { for _, activePlugin := range env.activePlugins {
if activePlugin.Supervisor != nil { if activePlugin.supervisor != nil {
if err := activePlugin.Supervisor.Hooks().OnDeactivate(); err != 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)) env.logger.Error("Plugin OnDeactivate() error", mlog.String("plugin_id", activePlugin.BundleInfo.Manifest.Id), mlog.Err(err))
} }
activePlugin.Supervisor.Shutdown() activePlugin.supervisor.Shutdown()
} }
} }
env.activePlugins = make(map[string]ActivePlugin) env.activePlugins = make(map[string]activePlugin)
return return
} }
// Returns the hooks API for the plugin ID specified // HooksForPlugin returns the hooks API for the plugin with the given id.
// You should probably use RunMultiPluginHook instead. //
// Consider using RunMultiPluginHook instead.
func (env *Environment) HooksForPlugin(id string) (Hooks, error) { func (env *Environment) HooksForPlugin(id string) (Hooks, error) {
env.mutex.RLock() env.mutex.RLock()
defer env.mutex.RUnlock() defer env.mutex.RUnlock()
if plug, ok := env.activePlugins[id]; ok && plug.Supervisor != nil { if plug, ok := env.activePlugins[id]; ok && plug.supervisor != nil {
return plug.Supervisor.Hooks(), nil return plug.supervisor.Hooks(), nil
} }
return nil, fmt.Errorf("plugin not found: %v", id) return nil, fmt.Errorf("plugin not found: %v", id)
} }
// Calls hookRunnerFunc with the hooks for each active plugin that implments the given HookId // RunMultiPluginHook invokes hookRunnerFunc for each plugin that implements the given hookId.
// If hookRunnerFunc returns false, then iteration will not continue. //
func (env *Environment) RunMultiPluginHook(hookRunnerFunc MultliPluginHookRunnerFunc, mustImplement int) { // 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() env.mutex.RLock()
defer env.mutex.RUnlock() defer env.mutex.RUnlock()
for _, activePlugin := range env.activePlugins { for _, activePlugin := range env.activePlugins {
if activePlugin.Supervisor == nil || !activePlugin.Supervisor.Implements(mustImplement) { if activePlugin.supervisor == nil || !activePlugin.supervisor.Implements(hookId) {
continue continue
} }
if !hookRunnerFunc(activePlugin.Supervisor.Hooks()) { if !hookRunnerFunc(activePlugin.supervisor.Hooks()) {
break break
} }
} }

20
plugin/example_hello_world_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,20 @@
package plugin_test
import (
"fmt"
"net/http"
"github.com/mattermost/mattermost-server/plugin"
)
type HelloWorldPlugin struct{}
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() {
plugin.ClientMain(&HelloWorldPlugin{})
}

71
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{})
}

Просмотреть файл

@@ -12,12 +12,12 @@ import (
"github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/mlog"
) )
type HclogAdapter struct { type hclogAdapter struct {
wrappedLogger *mlog.Logger wrappedLogger *mlog.Logger
extrasKey string extrasKey string
} }
func (h *HclogAdapter) Trace(msg string, args ...interface{}) { func (h *hclogAdapter) Trace(msg string, args ...interface{}) {
extras := strings.TrimSpace(fmt.Sprint(args...)) extras := strings.TrimSpace(fmt.Sprint(args...))
if extras != "" { if extras != "" {
h.wrappedLogger.Debug(msg, mlog.String(h.extrasKey, extras)) h.wrappedLogger.Debug(msg, mlog.String(h.extrasKey, extras))
@@ -26,7 +26,7 @@ func (h *HclogAdapter) Trace(msg string, args ...interface{}) {
} }
} }
func (h *HclogAdapter) Debug(msg string, args ...interface{}) { func (h *hclogAdapter) Debug(msg string, args ...interface{}) {
extras := strings.TrimSpace(fmt.Sprint(args...)) extras := strings.TrimSpace(fmt.Sprint(args...))
if extras != "" { if extras != "" {
h.wrappedLogger.Debug(msg, mlog.String(h.extrasKey, extras)) h.wrappedLogger.Debug(msg, mlog.String(h.extrasKey, extras))
@@ -35,7 +35,7 @@ func (h *HclogAdapter) Debug(msg string, args ...interface{}) {
} }
} }
func (h *HclogAdapter) Info(msg string, args ...interface{}) { func (h *hclogAdapter) Info(msg string, args ...interface{}) {
extras := strings.TrimSpace(fmt.Sprint(args...)) extras := strings.TrimSpace(fmt.Sprint(args...))
if extras != "" { if extras != "" {
h.wrappedLogger.Info(msg, mlog.String(h.extrasKey, extras)) h.wrappedLogger.Info(msg, mlog.String(h.extrasKey, extras))
@@ -44,7 +44,7 @@ func (h *HclogAdapter) Info(msg string, args ...interface{}) {
} }
} }
func (h *HclogAdapter) Warn(msg string, args ...interface{}) { func (h *hclogAdapter) Warn(msg string, args ...interface{}) {
extras := strings.TrimSpace(fmt.Sprint(args...)) extras := strings.TrimSpace(fmt.Sprint(args...))
if extras != "" { if extras != "" {
h.wrappedLogger.Warn(msg, mlog.String(h.extrasKey, extras)) h.wrappedLogger.Warn(msg, mlog.String(h.extrasKey, extras))
@@ -53,7 +53,7 @@ func (h *HclogAdapter) Warn(msg string, args ...interface{}) {
} }
} }
func (h *HclogAdapter) Error(msg string, args ...interface{}) { func (h *hclogAdapter) Error(msg string, args ...interface{}) {
extras := strings.TrimSpace(fmt.Sprint(args...)) extras := strings.TrimSpace(fmt.Sprint(args...))
if extras != "" { if extras != "" {
h.wrappedLogger.Error(msg, mlog.String(h.extrasKey, extras)) h.wrappedLogger.Error(msg, mlog.String(h.extrasKey, extras))
@@ -62,38 +62,38 @@ func (h *HclogAdapter) Error(msg string, args ...interface{}) {
} }
} }
func (h *HclogAdapter) IsTrace() bool { func (h *hclogAdapter) IsTrace() bool {
return false return false
} }
func (h *HclogAdapter) IsDebug() bool { func (h *hclogAdapter) IsDebug() bool {
return true return true
} }
func (h *HclogAdapter) IsInfo() bool { func (h *hclogAdapter) IsInfo() bool {
return true return true
} }
func (h *HclogAdapter) IsWarn() bool { func (h *hclogAdapter) IsWarn() bool {
return true return true
} }
func (h *HclogAdapter) IsError() bool { func (h *hclogAdapter) IsError() bool {
return true return true
} }
func (h *HclogAdapter) With(args ...interface{}) hclog.Logger { func (h *hclogAdapter) With(args ...interface{}) hclog.Logger {
return h return h
} }
func (h *HclogAdapter) Named(name string) hclog.Logger { func (h *hclogAdapter) Named(name string) hclog.Logger {
return h return h
} }
func (h *HclogAdapter) ResetNamed(name string) hclog.Logger { func (h *hclogAdapter) ResetNamed(name string) hclog.Logger {
return h return h
} }
func (h *HclogAdapter) StandardLogger(opts *hclog.StandardLoggerOptions) *log.Logger { func (h *hclogAdapter) StandardLogger(opts *hclog.StandardLoggerOptions) *log.Logger {
return h.wrappedLogger.StdLog() return h.wrappedLogger.StdLog()
} }

Просмотреть файл

@@ -9,8 +9,10 @@ import (
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
) )
// These assignments are part of the wire protocol. You can add more, but should not change existing // These assignments are part of the wire protocol used to trigger hook events in plugins.
// assignments. Follow the naming convention of <HookName>Id as the autogenerated glue code depends on that. //
// 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 ( const (
OnActivateId = 0 OnActivateId = 0
OnDeactivateId = 1 OnDeactivateId = 1
@@ -29,15 +31,16 @@ const (
TotalHooksId = iota TotalHooksId = iota
) )
// Methods from the Hooks interface can be used by a plugin to respond to events. Methods are likely // Hooks describes the methods a plugin may implement to automatically receive the corresponding
// to be added over time, and plugins are not expected to implement all of them. Instead, plugins // event.
// 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. // 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 { type Hooks interface {
// OnActivate is invoked when the plugin is activated. // OnActivate is invoked when the plugin is activated.
OnActivate() error OnActivate() error
// Implemented returns a list of hooks that are implmented by the plugin. // 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. // Plugins do not need to provide an implementation. Any given will be ignored.
Implemented() ([]string, error) Implemented() ([]string, error)

Просмотреть файл

@@ -9,26 +9,26 @@ import (
"net/rpc" "net/rpc"
) )
type HTTPResponseWriterRPCServer struct { type httpResponseWriterRPCServer struct {
w http.ResponseWriter w http.ResponseWriter
} }
func (w *HTTPResponseWriterRPCServer) Header(args struct{}, reply *http.Header) error { func (w *httpResponseWriterRPCServer) Header(args struct{}, reply *http.Header) error {
*reply = w.w.Header() *reply = w.w.Header()
return nil return nil
} }
func (w *HTTPResponseWriterRPCServer) Write(args []byte, reply *struct{}) error { func (w *httpResponseWriterRPCServer) Write(args []byte, reply *struct{}) error {
_, err := w.w.Write(args) _, err := w.w.Write(args)
return err return err
} }
func (w *HTTPResponseWriterRPCServer) WriteHeader(args int, reply *struct{}) error { func (w *httpResponseWriterRPCServer) WriteHeader(args int, reply *struct{}) error {
w.w.WriteHeader(args) w.w.WriteHeader(args)
return nil return nil
} }
func (w *HTTPResponseWriterRPCServer) SyncHeader(args http.Header, reply *struct{}) error { func (w *httpResponseWriterRPCServer) SyncHeader(args http.Header, reply *struct{}) error {
dest := w.w.Header() dest := w.w.Header()
for k := range dest { for k := range dest {
if _, ok := args[k]; !ok { if _, ok := args[k]; !ok {
@@ -41,29 +41,21 @@ func (w *HTTPResponseWriterRPCServer) SyncHeader(args http.Header, reply *struct
return nil return nil
} }
func ServeHTTPResponseWriter(w http.ResponseWriter, conn io.ReadWriteCloser) { type httpResponseWriterRPCClient struct {
server := rpc.NewServer()
server.Register(&HTTPResponseWriterRPCServer{
w: w,
})
server.ServeConn(conn)
}
type HTTPResponseWriterRPCClient struct {
client *rpc.Client client *rpc.Client
header http.Header header http.Header
} }
var _ http.ResponseWriter = (*HTTPResponseWriterRPCClient)(nil) var _ http.ResponseWriter = (*httpResponseWriterRPCClient)(nil)
func (w *HTTPResponseWriterRPCClient) Header() http.Header { func (w *httpResponseWriterRPCClient) Header() http.Header {
if w.header == nil { if w.header == nil {
w.client.Call("Plugin.Header", struct{}{}, &w.header) w.client.Call("Plugin.Header", struct{}{}, &w.header)
} }
return w.header return w.header
} }
func (w *HTTPResponseWriterRPCClient) Write(b []byte) (int, error) { func (w *httpResponseWriterRPCClient) Write(b []byte) (int, error) {
if err := w.client.Call("Plugin.SyncHeader", w.header, nil); err != nil { if err := w.client.Call("Plugin.SyncHeader", w.header, nil); err != nil {
return 0, err return 0, err
} }
@@ -73,19 +65,19 @@ func (w *HTTPResponseWriterRPCClient) Write(b []byte) (int, error) {
return len(b), nil return len(b), nil
} }
func (w *HTTPResponseWriterRPCClient) WriteHeader(statusCode int) { func (w *httpResponseWriterRPCClient) WriteHeader(statusCode int) {
if err := w.client.Call("Plugin.SyncHeader", w.header, nil); err != nil { if err := w.client.Call("Plugin.SyncHeader", w.header, nil); err != nil {
return return
} }
w.client.Call("Plugin.WriteHeader", statusCode, nil) w.client.Call("Plugin.WriteHeader", statusCode, nil)
} }
func (h *HTTPResponseWriterRPCClient) Close() error { func (h *httpResponseWriterRPCClient) Close() error {
return h.client.Close() return h.client.Close()
} }
func ConnectHTTPResponseWriter(conn io.ReadWriteCloser) *HTTPResponseWriterRPCClient { func connectHTTPResponseWriter(conn io.ReadWriteCloser) *httpResponseWriterRPCClient {
return &HTTPResponseWriterRPCClient{ return &httpResponseWriterRPCClient{
client: rpc.NewClient(conn), client: rpc.NewClient(conn),
} }
} }

Просмотреть файл

@@ -210,20 +210,20 @@ package plugin
{{range .HooksMethods}} {{range .HooksMethods}}
func init() { func init() {
HookNameToId["{{.Name}}"] = {{.Name}}Id hookNameToId["{{.Name}}"] = {{.Name}}Id
} }
type {{.Name}}Args struct { type {{.Name | obscure}}Args struct {
{{structStyle .Params}} {{structStyle .Params}}
} }
type {{.Name}}Returns struct { type {{.Name | obscure}}Returns struct {
{{structStyle .Return}} {{structStyle .Return}}
} }
func (g *HooksRPCClient) {{.Name}}{{funcStyle .Params}} {{funcStyle .Return}} { func (g *hooksRPCClient) {{.Name}}{{funcStyle .Params}} {{funcStyle .Return}} {
_args := &{{.Name}}Args{ {{valuesOnly .Params}} } _args := &{{.Name | obscure}}Args{ {{valuesOnly .Params}} }
_returns := &{{.Name}}Returns{} _returns := &{{.Name | obscure}}Returns{}
if g.implemented[{{.Name}}Id] { if g.implemented[{{.Name}}Id] {
if err := g.client.Call("Plugin.{{.Name}}", _args, _returns); err != nil { if err := g.client.Call("Plugin.{{.Name}}", _args, _returns); err != nil {
g.log.Error("RPC call {{.Name}} to plugin failed.", mlog.Err(err)) g.log.Error("RPC call {{.Name}} to plugin failed.", mlog.Err(err))
@@ -232,7 +232,7 @@ func (g *HooksRPCClient) {{.Name}}{{funcStyle .Params}} {{funcStyle .Return}} {
return {{destruct "_returns." .Return}} return {{destruct "_returns." .Return}}
} }
func (s *HooksRPCServer) {{.Name}}(args *{{.Name}}Args, returns *{{.Name}}Returns) error { func (s *hooksRPCServer) {{.Name}}(args *{{.Name | obscure}}Args, returns *{{.Name | obscure}}Returns) error {
if hook, ok := s.impl.(interface { if hook, ok := s.impl.(interface {
{{.Name}}{{funcStyle .Params}} {{funcStyle .Return}} {{.Name}}{{funcStyle .Params}} {{funcStyle .Return}}
}); ok { }); ok {
@@ -246,24 +246,24 @@ func (s *HooksRPCServer) {{.Name}}(args *{{.Name}}Args, returns *{{.Name}}Return
{{range .APIMethods}} {{range .APIMethods}}
type {{.Name}}Args struct { type {{.Name | obscure}}Args struct {
{{structStyle .Params}} {{structStyle .Params}}
} }
type {{.Name}}Returns struct { type {{.Name | obscure}}Returns struct {
{{structStyle .Return}} {{structStyle .Return}}
} }
func (g *APIRPCClient) {{.Name}}{{funcStyle .Params}} {{funcStyle .Return}} { func (g *apiRPCClient) {{.Name}}{{funcStyle .Params}} {{funcStyle .Return}} {
_args := &{{.Name}}Args{ {{valuesOnly .Params}} } _args := &{{.Name | obscure}}Args{ {{valuesOnly .Params}} }
_returns := &{{.Name}}Returns{} _returns := &{{.Name | obscure}}Returns{}
if err := g.client.Call("Plugin.{{.Name}}", _args, _returns); err != nil { if err := g.client.Call("Plugin.{{.Name}}", _args, _returns); err != nil {
g.log.Error("RPC call to {{.Name}} API failed.", mlog.Err(err)) g.log.Error("RPC call to {{.Name}} API failed.", mlog.Err(err))
} }
return {{destruct "_returns." .Return}} return {{destruct "_returns." .Return}}
} }
func (s *APIRPCServer) {{.Name}}(args *{{.Name}}Args, returns *{{.Name}}Returns) error { func (s *apiRPCServer) {{.Name}}(args *{{.Name | obscure}}Args, returns *{{.Name | obscure}}Returns) error {
if hook, ok := s.impl.(interface { if hook, ok := s.impl.(interface {
{{.Name}}{{funcStyle .Params}} {{funcStyle .Return}} {{.Name}}{{funcStyle .Params}} {{funcStyle .Return}}
}); ok { }); ok {
@@ -295,6 +295,9 @@ func generateGlue(info *PluginInterfaceInfo) {
"destruct": func(structPrefix string, fields *ast.FieldList) string { "destruct": func(structPrefix string, fields *ast.FieldList) string {
return FieldListDestruct(structPrefix, fields, info.FileSet) return FieldListDestruct(structPrefix, fields, info.FileSet)
}, },
"obscure": func(name string) string {
return "Z_" + name
},
} }
hooksTemplate, err := template.New("hooks").Funcs(templateFunctions).Parse(hooksTemplate) hooksTemplate, err := template.New("hooks").Funcs(templateFunctions).Parse(hooksTemplate)

Просмотреть файл

@@ -22,15 +22,11 @@ func (rwc *rwc) Close() (err error) {
return return
} }
func NewReadWriteCloser(r io.ReadCloser, w io.WriteCloser) io.ReadWriteCloser { type remoteIOReader struct {
return &rwc{r, w}
}
type RemoteIOReader struct {
conn io.ReadWriteCloser conn io.ReadWriteCloser
} }
func (r *RemoteIOReader) Read(b []byte) (int, error) { func (r *remoteIOReader) Read(b []byte) (int, error) {
var buf [10]byte var buf [10]byte
n := binary.PutVarint(buf[:], int64(len(b))) n := binary.PutVarint(buf[:], int64(len(b)))
if _, err := r.conn.Write(buf[:n]); err != nil { 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) return r.conn.Read(b)
} }
func (r *RemoteIOReader) Close() error { func (r *remoteIOReader) Close() error {
return r.conn.Close() return r.conn.Close()
} }
func ConnectIOReader(conn io.ReadWriteCloser) io.ReadCloser { func connectIOReader(conn io.ReadWriteCloser) io.ReadCloser {
return &RemoteIOReader{conn} return &remoteIOReader{conn}
} }
func ServeIOReader(r io.Reader, conn io.ReadWriteCloser) { func serveIOReader(r io.Reader, conn io.ReadWriteCloser) {
cr := bufio.NewReader(conn) cr := bufio.NewReader(conn)
defer conn.Close() defer conn.Close()
buf := make([]byte, 32*1024) buf := make([]byte, 32*1024)

Просмотреть файл

@@ -15,25 +15,25 @@ import (
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
) )
type Supervisor struct { type supervisor struct {
pluginId string pluginId string
client *plugin.Client client *plugin.Client
hooks Hooks hooks Hooks
implemented [TotalHooksId]bool implemented [TotalHooksId]bool
} }
func NewSupervisor(pluginInfo *model.BundleInfo, parentLogger *mlog.Logger, apiImpl API) (*Supervisor, error) { func newSupervisor(pluginInfo *model.BundleInfo, parentLogger *mlog.Logger, apiImpl API) (*supervisor, error) {
supervisor := Supervisor{} supervisor := supervisor{}
wrappedLogger := pluginInfo.WrapLogger(parentLogger) wrappedLogger := pluginInfo.WrapLogger(parentLogger)
hclogAdaptedLogger := &HclogAdapter{ hclogAdaptedLogger := &hclogAdapter{
wrappedLogger: wrappedLogger.WithCallerSkip(1), wrappedLogger: wrappedLogger.WithCallerSkip(1),
extrasKey: "wrapped_extras", extrasKey: "wrapped_extras",
} }
pluginMap := map[string]plugin.Plugin{ pluginMap := map[string]plugin.Plugin{
"hooks": &HooksPlugin{ "hooks": &hooksPlugin{
log: wrappedLogger, log: wrappedLogger,
apiImpl: apiImpl, apiImpl: apiImpl,
}, },
@@ -46,7 +46,7 @@ func NewSupervisor(pluginInfo *model.BundleInfo, parentLogger *mlog.Logger, apiI
executable = filepath.Join(pluginInfo.Path, executable) executable = filepath.Join(pluginInfo.Path, executable)
supervisor.client = plugin.NewClient(&plugin.ClientConfig{ supervisor.client = plugin.NewClient(&plugin.ClientConfig{
HandshakeConfig: Handshake, HandshakeConfig: handshake,
Plugins: pluginMap, Plugins: pluginMap,
Cmd: exec.Command(executable), Cmd: exec.Command(executable),
SyncStdout: wrappedLogger.With(mlog.String("source", "plugin_stdout")).StdLogWriter(), SyncStdout: wrappedLogger.With(mlog.String("source", "plugin_stdout")).StdLogWriter(),
@@ -71,7 +71,7 @@ func NewSupervisor(pluginInfo *model.BundleInfo, parentLogger *mlog.Logger, apiI
return nil, err return nil, err
} else { } else {
for _, hookName := range impl { for _, hookName := range impl {
if hookId, ok := HookNameToId[hookName]; ok { if hookId, ok := hookNameToId[hookName]; ok {
supervisor.implemented[hookId] = true supervisor.implemented[hookId] = true
} }
} }
@@ -85,14 +85,14 @@ func NewSupervisor(pluginInfo *model.BundleInfo, parentLogger *mlog.Logger, apiI
return &supervisor, nil return &supervisor, nil
} }
func (sup *Supervisor) Shutdown() { func (sup *supervisor) Shutdown() {
sup.client.Kill() sup.client.Kill()
} }
func (sup *Supervisor) Hooks() Hooks { func (sup *supervisor) Hooks() Hooks {
return sup.hooks return sup.hooks
} }
func (sup *Supervisor) Implements(hookId int) bool { func (sup *supervisor) Implements(hookId int) bool {
return sup.implemented[hookId] return sup.implemented[hookId]
} }

Просмотреть файл

@@ -73,7 +73,7 @@ func testSupervisor(t *testing.T) {
ConsoleLevel: "error", ConsoleLevel: "error",
EnableFile: false, EnableFile: false,
}) })
supervisor, err := NewSupervisor(bundle, log, &api) supervisor, err := newSupervisor(bundle, log, &api)
require.NoError(t, err) require.NoError(t, err)
supervisor.Shutdown() supervisor.Shutdown()
} }
@@ -92,7 +92,7 @@ func testSupervisor_InvalidExecutablePath(t *testing.T) {
ConsoleLevel: "error", ConsoleLevel: "error",
EnableFile: false, EnableFile: false,
}) })
supervisor, err := NewSupervisor(bundle, log, nil) supervisor, err := newSupervisor(bundle, log, nil)
assert.Nil(t, supervisor) assert.Nil(t, supervisor)
assert.Error(t, err) assert.Error(t, err)
} }
@@ -111,7 +111,7 @@ func testSupervisor_NonExistentExecutablePath(t *testing.T) {
ConsoleLevel: "error", ConsoleLevel: "error",
EnableFile: false, EnableFile: false,
}) })
supervisor, err := NewSupervisor(bundle, log, nil) supervisor, err := newSupervisor(bundle, log, nil)
require.Error(t, err) require.Error(t, err)
require.Nil(t, supervisor) require.Nil(t, supervisor)
} }
@@ -141,7 +141,7 @@ func testSupervisor_StartTimeout(t *testing.T) {
ConsoleLevel: "error", ConsoleLevel: "error",
EnableFile: false, EnableFile: false,
}) })
supervisor, err := NewSupervisor(bundle, log, nil) supervisor, err := newSupervisor(bundle, log, nil)
require.Error(t, err) require.Error(t, err)
require.Nil(t, supervisor) require.Nil(t, supervisor)
} }

Просмотреть файл

@@ -11,14 +11,21 @@ import (
const ( const (
MinIdLength = 3 MinIdLength = 3
MaxIdLength = 190 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() { 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 { func IsValidId(id string) bool {
if utf8.RuneCountInString(id) < MinIdLength { if utf8.RuneCountInString(id) < MinIdLength {
return false return false
@@ -28,5 +35,5 @@ func IsValidId(id string) bool {
return false return false
} }
return ValidId.MatchString(id) return validId.MatchString(id)
} }