2013-01-24 23:01:32 -05:00
|
|
|
package rcli
|
|
|
|
|
2013-02-12 11:42:15 -05:00
|
|
|
// 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.
|
|
|
|
|
2013-01-24 23:01:32 -05:00
|
|
|
import (
|
2013-03-09 22:44:09 -05:00
|
|
|
"flag"
|
2013-01-24 23:01:32 -05:00
|
|
|
"fmt"
|
2013-04-04 21:36:34 -04:00
|
|
|
"github.com/dotcloud/docker/term"
|
2013-01-24 23:01:32 -05:00
|
|
|
"io"
|
|
|
|
"log"
|
2013-04-03 15:25:19 -04:00
|
|
|
"net"
|
2013-04-04 21:36:34 -04:00
|
|
|
"os"
|
2013-03-09 22:44:09 -05:00
|
|
|
"reflect"
|
2013-01-24 23:01:32 -05:00
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2013-04-03 15:25:19 -04:00
|
|
|
type DockerConnOptions struct {
|
|
|
|
RawTerminal bool
|
|
|
|
}
|
|
|
|
|
|
|
|
type DockerConn interface {
|
|
|
|
io.ReadWriteCloser
|
|
|
|
CloseWrite() error
|
|
|
|
CloseRead() error
|
|
|
|
GetOptions() *DockerConnOptions
|
|
|
|
SetOptionRawTerminal()
|
2013-04-05 23:08:31 -04:00
|
|
|
Flush() error
|
2013-04-03 15:25:19 -04:00
|
|
|
}
|
|
|
|
|
2013-04-04 21:36:34 -04:00
|
|
|
type DockerLocalConn struct {
|
2013-04-04 21:52:03 -04:00
|
|
|
writer io.WriteCloser
|
2013-04-04 21:36:34 -04:00
|
|
|
savedState *term.State
|
|
|
|
}
|
|
|
|
|
2013-04-04 21:52:03 -04:00
|
|
|
func NewDockerLocalConn(w io.WriteCloser) *DockerLocalConn {
|
|
|
|
return &DockerLocalConn{
|
|
|
|
writer: w,
|
|
|
|
}
|
2013-04-04 21:36:34 -04:00
|
|
|
}
|
|
|
|
|
2013-04-04 21:52:03 -04:00
|
|
|
func (c *DockerLocalConn) Read(b []byte) (int, error) {
|
|
|
|
return 0, fmt.Errorf("DockerLocalConn does not implement Read()")
|
|
|
|
}
|
2013-04-04 21:36:34 -04:00
|
|
|
|
2013-04-04 21:52:03 -04:00
|
|
|
func (c *DockerLocalConn) Write(b []byte) (int, error) { return c.writer.Write(b) }
|
2013-04-04 21:36:34 -04:00
|
|
|
|
|
|
|
func (c *DockerLocalConn) Close() error {
|
|
|
|
if c.savedState != nil {
|
|
|
|
RestoreTerminal(c.savedState)
|
|
|
|
c.savedState = nil
|
|
|
|
}
|
2013-04-04 21:52:03 -04:00
|
|
|
return c.writer.Close()
|
2013-04-04 21:36:34 -04:00
|
|
|
}
|
|
|
|
|
2013-04-05 23:08:31 -04:00
|
|
|
func (c *DockerLocalConn) Flush() error { return nil }
|
|
|
|
|
2013-04-04 21:36:34 -04:00
|
|
|
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 {
|
2013-04-04 22:21:36 -04:00
|
|
|
if os.Getenv("DEBUG") != "" {
|
|
|
|
log.Printf("Can't set the terminal in raw mode: %s", err)
|
|
|
|
}
|
2013-04-04 21:36:34 -04:00
|
|
|
} else {
|
|
|
|
c.savedState = state
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-04-04 22:21:36 -04:00
|
|
|
var UnknownDockerProto = fmt.Errorf("Only TCP is actually supported by Docker at the moment")
|
2013-04-03 15:25:19 -04:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2013-01-24 23:01:32 -05:00
|
|
|
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
|
|
|
|
|
2013-03-13 03:29:40 -04:00
|
|
|
// FIXME: For reverse compatibility
|
2013-04-03 15:25:19 -04:00
|
|
|
func call(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error {
|
2013-03-13 03:29:40 -04:00
|
|
|
return LocalCall(service, stdin, stdout, args...)
|
|
|
|
}
|
|
|
|
|
2013-04-03 15:25:19 -04:00
|
|
|
func LocalCall(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error {
|
2013-02-12 12:10:47 -05:00
|
|
|
if len(args) == 0 {
|
|
|
|
args = []string{"help"}
|
|
|
|
}
|
2013-01-24 23:01:32 -05:00
|
|
|
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 {
|
2013-02-12 12:10:47 -05:00
|
|
|
return method(stdin, stdout, flags.Args()[1:]...)
|
2013-01-24 23:01:32 -05:00
|
|
|
}
|
2013-04-04 22:21:36 -04:00
|
|
|
return fmt.Errorf("No such command: %s", cmd)
|
2013-01-24 23:01:32 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2013-04-04 22:21:36 -04:00
|
|
|
return fmt.Errorf("No such command: %s", args[0])
|
2013-01-24 23:01:32 -05:00
|
|
|
} else {
|
|
|
|
method(stdin, stdout, "--help")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
2013-03-09 22:44:09 -05:00
|
|
|
methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:])
|
2013-01-24 23:01:32 -05:00
|
|
|
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
|
|
|
|
}
|