Created
June 29, 2020 23:45
-
-
Save matthewmueller/98fc622b071dd22f8ccc7ed67fb88365 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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