Factored out docker/rcli (remote cli protocol), docker/fake (mocking utilities) and docker/future (real utilities which don't yet fit in the core)

This commit is contained in:
Solomon Hykes
2013-01-24 20:01:32 -08:00
parent 553ca56d7e
commit f3ffba7afe
7 changed files with 344 additions and 255 deletions
+41
View File
@@ -0,0 +1,41 @@
package rcli
import (
"net/http"
"net/url"
"path"
"fmt"
)
// Use this key to encode an RPC call into an URL,
// eg. domain.tld/path/to/method?q=get_user&q=gordon
const ARG_URL_KEY = "q"
func URLToCall(u *url.URL) (method string, args []string) {
return path.Base(u.Path), u.Query()[ARG_URL_KEY]
}
func ListenAndServeHTTP(addr string, service Service) error {
return http.ListenAndServe(addr, http.HandlerFunc(
func (w http.ResponseWriter, r *http.Request) {
cmd, args := URLToCall(r.URL)
if err := call(service, r.Body, &AutoFlush{w}, append([]string{cmd}, args...)...); err != nil {
fmt.Fprintf(w, "Error: " + err.Error() + "\n")
}
}))
}
type AutoFlush struct {
http.ResponseWriter
}
func (w *AutoFlush) Write(data []byte) (int, error) {
ret, err := w.ResponseWriter.Write(data)
if flusher, ok := w.ResponseWriter.(http.Flusher); ok {
flusher.Flush()
}
return ret, err
}
+62
View File
@@ -0,0 +1,62 @@
package rcli
import (
"io"
"io/ioutil"
"net"
"log"
"fmt"
"encoding/json"
"bufio"
)
func CallTCP(addr string, args ...string) (io.ReadWriteCloser, error) {
cmd, err := json.Marshal(args)
if err != nil {
return nil, err
}
conn, err := net.Dial("tcp", addr)
if err != nil {
return nil, err
}
if _, err := fmt.Fprintln(conn, string(cmd)); err != nil {
return nil, err
}
return conn, nil
}
func ListenAndServeTCP(addr string, service Service) error {
listener, err := net.Listen("tcp", addr)
if err != nil {
return err
}
defer listener.Close()
for {
if conn, err := listener.Accept(); err != nil {
return err
} else {
go func() {
if err := Serve(conn, service); err != nil {
log.Printf("Error: " + err.Error() + "\n")
fmt.Fprintf(conn, "Error: " + err.Error() + "\n")
}
conn.Close()
}()
}
}
return nil
}
func Serve(conn io.ReadWriter, service Service) error {
r := bufio.NewReader(conn)
var args []string
if line, err := r.ReadString('\n'); err != nil {
return err
} else if err := json.Unmarshal([]byte(line), &args); err != nil {
return err
} else {
return call(service, ioutil.NopCloser(r), conn, args...)
}
return nil
}
+84
View File
@@ -0,0 +1,84 @@
package rcli
import (
"fmt"
"io"
"reflect"
"flag"
"log"
"strings"
"errors"
)
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
func call(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error {
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 {
return method(stdin, stdout, args[1:]...)
}
return errors.New("No such command: " + cmd)
}
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 {
return errors.New("No such command: " + args[0])
} else {
method(stdin, stdout, "--help")
}
}
return nil
}
}
methodName := "Cmd"+strings.ToUpper(name[:1])+strings.ToLower(name[1:])
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
}