types.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. package rcli
  2. // rcli (Remote Command-Line Interface) is a simple protocol for...
  3. // serving command-line interfaces remotely.
  4. //
  5. // rcli can be used over any transport capable of a) sending binary streams in
  6. // both directions, and b) capable of half-closing a connection. TCP and Unix sockets
  7. // are the usual suspects.
  8. import (
  9. "flag"
  10. "fmt"
  11. "github.com/dotcloud/docker/term"
  12. "io"
  13. "log"
  14. "net"
  15. "os"
  16. "reflect"
  17. "strings"
  18. )
  19. type DockerConnOptions struct {
  20. RawTerminal bool
  21. }
  22. type DockerConn interface {
  23. io.ReadWriteCloser
  24. CloseWrite() error
  25. CloseRead() error
  26. GetOptions() *DockerConnOptions
  27. SetOptionRawTerminal()
  28. Flush() error
  29. }
  30. type DockerLocalConn struct {
  31. writer io.WriteCloser
  32. savedState *term.State
  33. }
  34. func NewDockerLocalConn(w io.WriteCloser) *DockerLocalConn {
  35. return &DockerLocalConn{
  36. writer: w,
  37. }
  38. }
  39. func (c *DockerLocalConn) Read(b []byte) (int, error) {
  40. return 0, fmt.Errorf("DockerLocalConn does not implement Read()")
  41. }
  42. func (c *DockerLocalConn) Write(b []byte) (int, error) { return c.writer.Write(b) }
  43. func (c *DockerLocalConn) Close() error {
  44. if c.savedState != nil {
  45. RestoreTerminal(c.savedState)
  46. c.savedState = nil
  47. }
  48. return c.writer.Close()
  49. }
  50. func (c *DockerLocalConn) Flush() error { return nil }
  51. func (c *DockerLocalConn) CloseWrite() error { return nil }
  52. func (c *DockerLocalConn) CloseRead() error { return nil }
  53. func (c *DockerLocalConn) GetOptions() *DockerConnOptions { return nil }
  54. func (c *DockerLocalConn) SetOptionRawTerminal() {
  55. if state, err := SetRawTerminal(); err != nil {
  56. if os.Getenv("DEBUG") != "" {
  57. log.Printf("Can't set the terminal in raw mode: %s", err)
  58. }
  59. } else {
  60. c.savedState = state
  61. }
  62. }
  63. var UnknownDockerProto = fmt.Errorf("Only TCP is actually supported by Docker at the moment")
  64. func dialDocker(proto string, addr string) (DockerConn, error) {
  65. conn, err := net.Dial(proto, addr)
  66. if err != nil {
  67. return nil, err
  68. }
  69. switch i := conn.(type) {
  70. case *net.TCPConn:
  71. return NewDockerTCPConn(i, true), nil
  72. }
  73. return nil, UnknownDockerProto
  74. }
  75. func newDockerFromConn(conn net.Conn, client bool) (DockerConn, error) {
  76. switch i := conn.(type) {
  77. case *net.TCPConn:
  78. return NewDockerTCPConn(i, client), nil
  79. }
  80. return nil, UnknownDockerProto
  81. }
  82. func newDockerServerConn(conn net.Conn) (DockerConn, error) {
  83. return newDockerFromConn(conn, false)
  84. }
  85. type Service interface {
  86. Name() string
  87. Help() string
  88. }
  89. type Cmd func(io.ReadCloser, io.Writer, ...string) error
  90. type CmdMethod func(Service, io.ReadCloser, io.Writer, ...string) error
  91. // FIXME: For reverse compatibility
  92. func call(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error {
  93. return LocalCall(service, stdin, stdout, args...)
  94. }
  95. func LocalCall(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error {
  96. if len(args) == 0 {
  97. args = []string{"help"}
  98. }
  99. flags := flag.NewFlagSet("main", flag.ContinueOnError)
  100. flags.SetOutput(stdout)
  101. flags.Usage = func() { stdout.Write([]byte(service.Help())) }
  102. if err := flags.Parse(args); err != nil {
  103. return err
  104. }
  105. cmd := flags.Arg(0)
  106. log.Printf("%s\n", strings.Join(append(append([]string{service.Name()}, cmd), flags.Args()[1:]...), " "))
  107. if cmd == "" {
  108. cmd = "help"
  109. }
  110. method := getMethod(service, cmd)
  111. if method != nil {
  112. return method(stdin, stdout, flags.Args()[1:]...)
  113. }
  114. return fmt.Errorf("No such command: %s", cmd)
  115. }
  116. func getMethod(service Service, name string) Cmd {
  117. if name == "help" {
  118. return func(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  119. if len(args) == 0 {
  120. stdout.Write([]byte(service.Help()))
  121. } else {
  122. if method := getMethod(service, args[0]); method == nil {
  123. return fmt.Errorf("No such command: %s", args[0])
  124. } else {
  125. method(stdin, stdout, "--help")
  126. }
  127. }
  128. return nil
  129. }
  130. }
  131. methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:])
  132. method, exists := reflect.TypeOf(service).MethodByName(methodName)
  133. if !exists {
  134. return nil
  135. }
  136. return func(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  137. ret := method.Func.CallSlice([]reflect.Value{
  138. reflect.ValueOf(service),
  139. reflect.ValueOf(stdin),
  140. reflect.ValueOf(stdout),
  141. reflect.ValueOf(args),
  142. })[0].Interface()
  143. if ret == nil {
  144. return nil
  145. }
  146. return ret.(error)
  147. }
  148. }
  149. func Subcmd(output io.Writer, name, signature, description string) *flag.FlagSet {
  150. flags := flag.NewFlagSet(name, flag.ContinueOnError)
  151. flags.SetOutput(output)
  152. flags.Usage = func() {
  153. fmt.Fprintf(output, "\nUsage: docker %s %s\n\n%s\n\n", name, signature, description)
  154. flags.PrintDefaults()
  155. }
  156. return flags
  157. }