Documentation ¶
Overview ¶
The plugin package exposes functions and helpers for communicating to plugins which are implemented as standalone binary applications.
plugin.Client fully manages the lifecycle of executing the application, connecting to it, and returning the RPC client for dispensing plugins.
plugin.Serve fully manages listeners to expose an RPC server from a binary that plugin.Client can connect to.
Index ¶
- Constants
- Variables
- func CleanupClients()
- func Discover(glob, dir string) ([]string, error)
- func Serve(opts *ServeConfig)
- func ServeMux(m ServeMuxMap)
- func TestConn(t *testing.T) (net.Conn, net.Conn)
- func TestPluginRPCConn(t *testing.T, ps map[string]Plugin) (*RPCClient, *RPCServer)
- func TestRPCConn(t *testing.T) (*rpc.Client, *rpc.Server)
- type BasicError
- type Client
- type ClientConfig
- type HandshakeConfig
- type MuxBroker
- type Plugin
- type RPCClient
- type RPCServer
- type ReattachConfig
- type ServeConfig
- type ServeMuxMap
Constants ¶
const CoreProtocolVersion = 1
CoreProtocolVersion is the ProtocolVersion of the plugin system itself. We will increment this whenever we change any protocol behavior. This will invalidate any prior plugins but will at least allow us to iterate on the core in a safe way. We will do our best to do this very infrequently.
Variables ¶
var ( // ErrProcessNotFound is returned when a client is instantiated to // reattach to an existing process and it isn't found. ErrProcessNotFound = errors.New("Reattachment process not found") )
Error types
var Killed uint32 = 0
If this is 1, then we've called CleanupClients. This can be used by plugin RPC implementations to change error behavior since you can expected network connection errors at this point. This should be read by using sync/atomic.
Functions ¶
func CleanupClients ¶
func CleanupClients()
This makes sure all the managed subprocesses are killed and properly logged. This should be called before the parent process running the plugins exits.
This must only be called _once_.
func Discover ¶
Discover discovers plugins that are in a given directory.
The directory doesn't need to be absolute. For example, "." will work fine.
This currently assumes any file matching the glob is a plugin. In the future this may be smarter about checking that a file is executable and so on.
TODO: test
func Serve ¶
func Serve(opts *ServeConfig)
Serve serves the plugins given by ServeConfig.
Serve doesn't return until the plugin is done being executed. Any errors will be outputted to the log.
This is the method that plugins should call in their main() functions.
func ServeMux ¶
func ServeMux(m ServeMuxMap)
ServeMux is like Serve, but serves multiple types of plugins determined by the argument given on the command-line.
This command doesn't return until the plugin is done being executed. Any errors are logged or output to stderr.
func TestConn ¶
TestConn is a helper function for returning a client and server net.Conn connected to each other.
func TestPluginRPCConn ¶
TestPluginRPCConn returns a plugin RPC client and server that are connected together and configured.
Types ¶
type BasicError ¶
type BasicError struct {
Message string
}
This is a type that wraps error types so that they can be messaged across RPC channels. Since "error" is an interface, we can't always gob-encode the underlying structure. This is a valid error interface implementer that we will push across.
func NewBasicError ¶
func NewBasicError(err error) *BasicError
NewBasicError is used to create a BasicError.
err is allowed to be nil.
func (*BasicError) Error ¶
func (e *BasicError) Error() string
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client handles the lifecycle of a plugin application. It launches plugins, connects to them, dispenses interface implementations, and handles killing the process.
Plugin hosts should use one Client for each plugin executable. To dispense a plugin type, use the `Client.Client` function, and then cal `Dispense`. This awkward API is mostly historical but is used to split the client that deals with subprocess management and the client that does RPC management.
See NewClient and ClientConfig for using a Client.
func NewClient ¶
func NewClient(config *ClientConfig) (c *Client)
Creates a new plugin client which manages the lifecycle of an external plugin and gets the address for the RPC connection.
The client must be cleaned up at some point by calling Kill(). If the client is a managed client (created with NewManagedClient) you can just call CleanupClients at the end of your program and they will be properly cleaned.
func (*Client) Client ¶
Client returns an RPC client for the plugin.
Subsequent calls to this will return the same RPC client.
func (*Client) Kill ¶
func (c *Client) Kill()
End the executing subprocess (if it is running) and perform any cleanup tasks necessary such as capturing any remaining logs and so on.
This method blocks until the process successfully exits.
This method can safely be called multiple times.
func (*Client) ReattachConfig ¶
func (c *Client) ReattachConfig() *ReattachConfig
ReattachConfig returns the information that must be provided to NewClient to reattach to the plugin process that this client started. This is useful for plugins that detach from their parent process.
If this returns nil then the process hasn't been started yet. Please call Start or Client before calling this.
func (*Client) Start ¶
Starts the underlying subprocess, communicating with it to negotiate a port for RPC connections, and returning the address to connect via RPC.
This method is safe to call multiple times. Subsequent calls have no effect. Once a client has been started once, it cannot be started again, even if it was killed.
type ClientConfig ¶
type ClientConfig struct { // HandshakeConfig is the configuration that must match servers. HandshakeConfig // Plugins are the plugins that can be consumed. Plugins map[string]Plugin // One of the following must be set, but not both. // // Cmd is the unstarted subprocess for starting the plugin. If this is // set, then the Client starts the plugin process on its own and connects // to it. // // Reattach is configuration for reattaching to an existing plugin process // that is already running. This isn't common. Cmd *exec.Cmd Reattach *ReattachConfig // Managed represents if the client should be managed by the // plugin package or not. If true, then by calling CleanupClients, // it will automatically be cleaned up. Otherwise, the client // user is fully responsible for making sure to Kill all plugin // clients. By default the client is _not_ managed. Managed bool // The minimum and maximum port to use for communicating with // the subprocess. If not set, this defaults to 10,000 and 25,000 // respectively. MinPort, MaxPort uint // StartTimeout is the timeout to wait for the plugin to say it // has started successfully. StartTimeout time.Duration // If non-nil, then the stderr of the client will be written to here // (as well as the log). This is the original os.Stderr of the subprocess. // This isn't the output of synced stderr. Stderr io.Writer // SyncStdout, SyncStderr can be set to override the // respective os.Std* values in the plugin. Care should be taken to // avoid races here. If these are nil, then this will automatically be // hooked up to os.Stdin, Stdout, and Stderr, respectively. // // If the default values (nil) are used, then this package will not // sync any of these streams. SyncStdout io.Writer SyncStderr io.Writer }
ClientConfig is the configuration used to initialize a new plugin client. After being used to initialize a plugin client, that configuration must not be modified again.
type HandshakeConfig ¶
type HandshakeConfig struct { // ProtocolVersion is the version that clients must match on to // agree they can communicate. This should match the ProtocolVersion // set on ClientConfig when using a plugin. ProtocolVersion uint // MagicCookieKey and value are used as a very basic verification // that a plugin is intended to be launched. This is not a security // measure, just a UX feature. If the magic cookie doesn't match, // we show human-friendly output. MagicCookieKey string MagicCookieValue string }
HandshakeConfig is the configuration used by client and servers to handshake before starting a plugin connection. This is embedded by both ServeConfig and ClientConfig.
In practice, the plugin host creates a HandshakeConfig that is exported and plugins then can easily consume it.
type MuxBroker ¶
MuxBroker is responsible for brokering multiplexed connections by unique ID.
It is used by plugins to multiplex multiple RPC connections and data streams on top of a single connection between the plugin process and the host process.
This allows a plugin to request a channel with a specific ID to connect to or accept a connection from, and the broker handles the details of holding these channels open while they're being negotiated.
The Plugin interface has access to these for both Server and Client. The broker can be used by either (optionally) to reserve and connect to new multiplexed streams. This is useful for complex args and return values, or anything else you might need a data stream for.
func (*MuxBroker) Accept ¶
Accept accepts a connection by ID.
This should not be called multiple times with the same ID at one time.
func (*MuxBroker) AcceptAndServe ¶
AcceptAndServe is used to accept a specific stream ID and immediately serve an RPC server on that stream ID. This is used to easily serve complex arguments.
The served interface is always registered to the "Plugin" name.
type Plugin ¶
type Plugin interface { // Server should return the RPC server compatible struct to serve // the methods that the Client calls over net/rpc. Server(*MuxBroker) (interface{}, error) // Client returns an interface implementation for the plugin you're // serving that communicates to the server end of the plugin. Client(*MuxBroker, *rpc.Client) (interface{}, error) }
Plugin is the interface that is implemented to serve/connect to an inteface implementation.
type RPCClient ¶
type RPCClient struct {
// contains filtered or unexported fields
}
RPCClient connects to an RPCServer over net/rpc to dispense plugin types.
func NewRPCClient ¶
NewRPCClient creates a client from an already-open connection-like value. Dial is typically used instead.
func (*RPCClient) Close ¶
Close closes the connection. The client is no longer usable after this is called.
func (*RPCClient) SyncStreams ¶
SyncStreams should be called to enable syncing of stdout, stderr with the plugin.
This will return immediately and the syncing will continue to happen in the background. You do not need to launch this in a goroutine itself.
This should never be called multiple times.
type RPCServer ¶
type RPCServer struct { Plugins map[string]Plugin // Stdout, Stderr are what this server will use instead of the // normal stdin/out/err. This is because due to the multi-process nature // of our plugin system, we can't use the normal process values so we // make our own custom one we pipe across. Stdout io.Reader Stderr io.Reader // DoneCh should be set to a non-nil channel that will be closed // when the control requests the RPC server to end. DoneCh chan<- struct{} }
RPCServer listens for network connections and then dispenses interface implementations over net/rpc.
func (*RPCServer) Accept ¶
Accept accepts connections on a listener and serves requests for each incoming connection. Accept blocks; the caller typically invokes it in a go statement.
func (*RPCServer) ServeConn ¶
func (s *RPCServer) ServeConn(conn io.ReadWriteCloser)
ServeConn runs a single connection.
ServeConn blocks, serving the connection until the client hangs up.
type ReattachConfig ¶
ReattachConfig is used to configure a client to reattach to an already-running plugin process. You can retrieve this information by calling ReattachConfig on Client.
type ServeConfig ¶
type ServeConfig struct { // HandshakeConfig is the configuration that must match clients. HandshakeConfig // Plugins are the plugins that are served. Plugins map[string]Plugin }
ServeConfig configures what sorts of plugins are served.
type ServeMuxMap ¶
type ServeMuxMap map[string]*ServeConfig
ServeMuxMap is the type that is used to configure ServeMux