f3ffba7a |
package rcli
|
abb7b81b |
// rcli (Remote Command-Line Interface) is a simple protocol for...
// serving command-line interfaces remotely.
//
// rcli can be used over any transport capable of a) sending binary streams in
// both directions, and b) capable of half-closing a connection. TCP and Unix sockets
// are the usual suspects.
|
f3ffba7a |
import ( |
c59fff42 |
"flag" |
f3ffba7a |
"fmt" |
246eed52 |
"github.com/dotcloud/docker/term" |
f3ffba7a |
"io"
"log" |
7d0ab385 |
"net" |
246eed52 |
"os" |
c59fff42 |
"reflect" |
f3ffba7a |
"strings"
)
|
7d0ab385 |
type DockerConnOptions struct {
RawTerminal bool
}
type DockerConn interface {
io.ReadWriteCloser
CloseWrite() error
CloseRead() error
GetOptions() *DockerConnOptions
SetOptionRawTerminal() |
c83393a5 |
Flush() error |
7d0ab385 |
}
|
246eed52 |
type DockerLocalConn struct { |
e6e9c1cd |
writer io.WriteCloser |
246eed52 |
savedState *term.State
}
|
e6e9c1cd |
func NewDockerLocalConn(w io.WriteCloser) *DockerLocalConn {
return &DockerLocalConn{
writer: w,
} |
246eed52 |
}
|
e6e9c1cd |
func (c *DockerLocalConn) Read(b []byte) (int, error) {
return 0, fmt.Errorf("DockerLocalConn does not implement Read()")
} |
246eed52 |
|
e6e9c1cd |
func (c *DockerLocalConn) Write(b []byte) (int, error) { return c.writer.Write(b) } |
246eed52 |
func (c *DockerLocalConn) Close() error {
if c.savedState != nil {
RestoreTerminal(c.savedState)
c.savedState = nil
} |
e6e9c1cd |
return c.writer.Close() |
246eed52 |
}
|
c83393a5 |
func (c *DockerLocalConn) Flush() error { return nil }
|
246eed52 |
func (c *DockerLocalConn) CloseWrite() error { return nil }
func (c *DockerLocalConn) CloseRead() error { return nil }
func (c *DockerLocalConn) GetOptions() *DockerConnOptions { return nil }
func (c *DockerLocalConn) SetOptionRawTerminal() {
if state, err := SetRawTerminal(); err != nil { |
b71b226c |
if os.Getenv("DEBUG") != "" {
log.Printf("Can't set the terminal in raw mode: %s", err)
} |
246eed52 |
} else {
c.savedState = state
}
}
|
b71b226c |
var UnknownDockerProto = fmt.Errorf("Only TCP is actually supported by Docker at the moment") |
7d0ab385 |
func dialDocker(proto string, addr string) (DockerConn, error) {
conn, err := net.Dial(proto, addr)
if err != nil {
return nil, err
}
switch i := conn.(type) {
case *net.TCPConn:
return NewDockerTCPConn(i, true), nil
}
return nil, UnknownDockerProto
}
func newDockerFromConn(conn net.Conn, client bool) (DockerConn, error) {
switch i := conn.(type) {
case *net.TCPConn:
return NewDockerTCPConn(i, client), nil
}
return nil, UnknownDockerProto
}
func newDockerServerConn(conn net.Conn) (DockerConn, error) {
return newDockerFromConn(conn, false)
}
|
f3ffba7a |
type Service interface {
Name() string
Help() string
}
type Cmd func(io.ReadCloser, io.Writer, ...string) error
type CmdMethod func(Service, io.ReadCloser, io.Writer, ...string) error
|
745edc49 |
// FIXME: For reverse compatibility |
7d0ab385 |
func call(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error { |
745edc49 |
return LocalCall(service, stdin, stdout, args...)
}
|
7d0ab385 |
func LocalCall(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error { |
178e126a |
if len(args) == 0 {
args = []string{"help"}
} |
f3ffba7a |
flags := flag.NewFlagSet("main", flag.ContinueOnError)
flags.SetOutput(stdout)
flags.Usage = func() { stdout.Write([]byte(service.Help())) }
if err := flags.Parse(args); err != nil {
return err
}
cmd := flags.Arg(0)
log.Printf("%s\n", strings.Join(append(append([]string{service.Name()}, cmd), flags.Args()[1:]...), " "))
if cmd == "" {
cmd = "help"
}
method := getMethod(service, cmd)
if method != nil { |
178e126a |
return method(stdin, stdout, flags.Args()[1:]...) |
f3ffba7a |
} |
b71b226c |
return fmt.Errorf("No such command: %s", cmd) |
f3ffba7a |
}
func getMethod(service Service, name string) Cmd {
if name == "help" {
return func(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
if len(args) == 0 {
stdout.Write([]byte(service.Help()))
} else {
if method := getMethod(service, args[0]); method == nil { |
b71b226c |
return fmt.Errorf("No such command: %s", args[0]) |
f3ffba7a |
} else {
method(stdin, stdout, "--help")
}
}
return nil
}
} |
c59fff42 |
methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:]) |
f3ffba7a |
method, exists := reflect.TypeOf(service).MethodByName(methodName)
if !exists {
return nil
}
return func(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
ret := method.Func.CallSlice([]reflect.Value{
reflect.ValueOf(service),
reflect.ValueOf(stdin),
reflect.ValueOf(stdout),
reflect.ValueOf(args),
})[0].Interface()
if ret == nil {
return nil
}
return ret.(error)
}
}
func Subcmd(output io.Writer, name, signature, description string) *flag.FlagSet {
flags := flag.NewFlagSet(name, flag.ContinueOnError)
flags.SetOutput(output)
flags.Usage = func() {
fmt.Fprintf(output, "\nUsage: docker %s %s\n\n%s\n\n", name, signature, description)
flags.PrintDefaults()
}
return flags
} |