Skip to content

Instantly share code, notes, and snippets.

@matthewmueller
Created June 29, 2020 23:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save matthewmueller/98fc622b071dd22f8ccc7ed67fb88365 to your computer and use it in GitHub Desktop.
Save matthewmueller/98fc622b071dd22f8ccc7ed67fb88365 to your computer and use it in GitHub Desktop.
package plugin
import (
"fmt"
"io"
"os"
"os/exec"
"strconv"
)
// Plugin struct
type Plugin struct {
Name string
Short string
Long string
Usage string
Examples []string
Options map[string]string
Commands map[string]string
}
// Conn interface
type Conn interface {
io.ReadWriteCloser
}
// Pipe creates a bi-directional in-memory pipe
// between two plugins (usually host <-> plugin)
func Pipe() (h Conn, p Conn) {
r1, w1 := io.Pipe()
r2, w2 := io.Pipe()
return &plugin{r2, w1}, &plugin{r1, w2}
}
type host struct {
rc io.ReadCloser
wc io.WriteCloser
cmd *exec.Cmd
}
var _ Conn = (*host)(nil)
func (h *host) Read(p []byte) (int, error) {
return h.rc.Read(p)
}
func (h *host) Write(p []byte) (int, error) {
return h.wc.Write(p)
}
func (h *host) Close() error {
h.rc.Close()
h.wc.Close()
return h.cmd.Wait()
}
// Start starts up a command and returns the connection
// TODO: support passing initialization args through
func Start(cmd string) (Conn, error) {
r1, w1, err := os.Pipe()
if err != nil {
return nil, err
}
r2, w2, err := os.Pipe()
if err != nil {
return nil, err
}
command := exec.Command(cmd)
// TODO: prefix with plugin name
command.Stdin = os.Stdin
command.Stdout = os.Stdout
command.Stderr = os.Stderr
// TODO: not sure what the general practice is here
// do we specify both or is just the first one fine?
command.Env = append([]string{"FD=3"}, os.Environ()...)
command.ExtraFiles = []*os.File{r1, w2}
if err := command.Start(); err != nil {
return nil, err
}
return &host{r2, w1, command}, nil
}
type plugin struct {
rc io.ReadCloser
wc io.WriteCloser
}
func (pl *plugin) Read(p []byte) (int, error) {
return pl.rc.Read(p)
}
func (pl *plugin) Write(p []byte) (int, error) {
return pl.wc.Write(p)
}
func (pl *plugin) Close() error {
pl.rc.Close()
pl.wc.Close()
return nil
}
// Serve a connection
func Serve(name string) (Conn, error) {
fdEnv := os.Getenv("FD")
if fdEnv == "" {
return nil, fmt.Errorf("%s shouldn't be called directly", name)
}
fd, err := strconv.Atoi(fdEnv)
if err != nil {
return nil, fmt.Errorf("%s file descriptor env is invalid", name)
}
reader := os.NewFile(uintptr(fd), "pipe")
writer := os.NewFile(uintptr(fd+1), "pipe")
return &plugin{reader, writer}, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment