types.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  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. "errors"
  10. "flag"
  11. "fmt"
  12. "io"
  13. "log"
  14. "net"
  15. "reflect"
  16. "strings"
  17. )
  18. type DockerConnOptions struct {
  19. RawTerminal bool
  20. }
  21. type DockerConn interface {
  22. io.ReadWriteCloser
  23. CloseWrite() error
  24. CloseRead() error
  25. GetOptions() *DockerConnOptions
  26. SetOptionRawTerminal()
  27. }
  28. var UnknownDockerProto = errors.New("Only TCP is actually supported by Docker at the moment")
  29. func dialDocker(proto string, addr string) (DockerConn, error) {
  30. conn, err := net.Dial(proto, addr)
  31. if err != nil {
  32. return nil, err
  33. }
  34. switch i := conn.(type) {
  35. case *net.TCPConn:
  36. return NewDockerTCPConn(i, true), nil
  37. }
  38. return nil, UnknownDockerProto
  39. }
  40. func newDockerFromConn(conn net.Conn, client bool) (DockerConn, error) {
  41. switch i := conn.(type) {
  42. case *net.TCPConn:
  43. return NewDockerTCPConn(i, client), nil
  44. }
  45. return nil, UnknownDockerProto
  46. }
  47. func newDockerServerConn(conn net.Conn) (DockerConn, error) {
  48. return newDockerFromConn(conn, false)
  49. }
  50. type Service interface {
  51. Name() string
  52. Help() string
  53. }
  54. type Cmd func(io.ReadCloser, io.Writer, ...string) error
  55. type CmdMethod func(Service, io.ReadCloser, io.Writer, ...string) error
  56. // FIXME: For reverse compatibility
  57. func call(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error {
  58. return LocalCall(service, stdin, stdout, args...)
  59. }
  60. func LocalCall(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error {
  61. if len(args) == 0 {
  62. args = []string{"help"}
  63. }
  64. flags := flag.NewFlagSet("main", flag.ContinueOnError)
  65. flags.SetOutput(stdout)
  66. flags.Usage = func() { stdout.Write([]byte(service.Help())) }
  67. if err := flags.Parse(args); err != nil {
  68. return err
  69. }
  70. cmd := flags.Arg(0)
  71. log.Printf("%s\n", strings.Join(append(append([]string{service.Name()}, cmd), flags.Args()[1:]...), " "))
  72. if cmd == "" {
  73. cmd = "help"
  74. }
  75. method := getMethod(service, cmd)
  76. if method != nil {
  77. return method(stdin, stdout, flags.Args()[1:]...)
  78. }
  79. return errors.New("No such command: " + cmd)
  80. }
  81. func getMethod(service Service, name string) Cmd {
  82. if name == "help" {
  83. return func(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  84. if len(args) == 0 {
  85. stdout.Write([]byte(service.Help()))
  86. } else {
  87. if method := getMethod(service, args[0]); method == nil {
  88. return errors.New("No such command: " + args[0])
  89. } else {
  90. method(stdin, stdout, "--help")
  91. }
  92. }
  93. return nil
  94. }
  95. }
  96. methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:])
  97. method, exists := reflect.TypeOf(service).MethodByName(methodName)
  98. if !exists {
  99. return nil
  100. }
  101. return func(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
  102. ret := method.Func.CallSlice([]reflect.Value{
  103. reflect.ValueOf(service),
  104. reflect.ValueOf(stdin),
  105. reflect.ValueOf(stdout),
  106. reflect.ValueOf(args),
  107. })[0].Interface()
  108. if ret == nil {
  109. return nil
  110. }
  111. return ret.(error)
  112. }
  113. }
  114. func Subcmd(output io.Writer, name, signature, description string) *flag.FlagSet {
  115. flags := flag.NewFlagSet(name, flag.ContinueOnError)
  116. flags.SetOutput(output)
  117. flags.Usage = func() {
  118. fmt.Fprintf(output, "\nUsage: docker %s %s\n\n%s\n\n", name, signature, description)
  119. flags.PrintDefaults()
  120. }
  121. return flags
  122. }