rcli/types.go
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
 }