diff --git a/docker/docker.go b/docker/docker.go new file mode 100644 index 0000000000..1fbad569fc --- /dev/null +++ b/docker/docker.go @@ -0,0 +1,39 @@ +package main + +import ( + "io" + "log" + "os" + "flag" + "net/http" + "net/url" +) + + +// 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 CallToURL(host string, cmd string, args []string) *url.URL { + qValues := make(url.Values) + for _, v := range args { + qValues.Add(ARG_URL_KEY, v) + } + return &url.URL{ + Scheme: "http", + Host: host, + Path: "/" + cmd, + RawQuery: qValues.Encode(), + } +} + + +func main() { + flag.Parse() + u := CallToURL(os.Getenv("DOCKER"), flag.Args()[0], flag.Args()[1:]) + resp, err := http.Get(u.String()) + if err != nil { + log.Fatal(err) + } + io.Copy(os.Stdout, resp.Body) +} diff --git a/dockerd/dockerd.go b/dockerd/dockerd.go new file mode 100644 index 0000000000..fef63f1e5e --- /dev/null +++ b/dockerd/dockerd.go @@ -0,0 +1,235 @@ +package main + +import ( + "errors" + "log" + "io" +// "io/ioutil" + "net/http" + "net/url" + "os/exec" + "flag" + "reflect" + "fmt" + "github.com/kr/pty" + "path" + "strings" + "time" + "math/rand" + "crypto/sha256" + "bytes" + "text/tabwriter" +) + +func (docker *Docker) CmdUsage(stdin io.ReadCloser, stdout io.Writer, args ...string) error { + fmt.Fprintf(stdout, "Usage: docker COMMAND [arg...]\n\nCommands:\n") + for _, cmd := range [][]interface{}{ + {"run", "Run a command in a container"}, + {"list", "Display a list of containers"}, + {"layers", "Display a list of layers"}, + {"download", "Download a layer from a remote location"}, + {"upload", "Upload a layer"}, + {"wait", "Wait for the state of a container to change"}, + {"stop", "Stop a running container"}, + {"logs", "Fetch the logs of a container"}, + {"export", "Extract changes to a container's filesystem into a new layer"}, + {"attach", "Attach to the standard inputs and outputs of a running container"}, + {"info", "Display system-wide information"}, + } { + fmt.Fprintf(stdout, " %-10.10s%s\n", cmd...) + } + return nil +} + +func (docker *Docker) CmdLayers(stdin io.ReadCloser, stdout io.Writer, args ...string) error { + w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0) + fmt.Fprintf(w, "ID\tSOURCE\tADDED\n") + for _, layer := range docker.layers { + fmt.Fprintf(w, "%s\t%s\t%s ago\n", layer.Id, layer.Name, time.Now().Sub(layer.Added)) + } + w.Flush() + return nil +} + +func (docker *Docker) CmdUpload(stdin io.ReadCloser, stdout io.Writer, args ...string) error { + layer := Layer{Id: randomId(), Name: args[0], Added: time.Now()} + docker.layers = append(docker.layers, layer) + fmt.Fprintf(stdout, "New layer: %s (%s)\n", layer.Id, layer.Name) + return nil +} + +func (docker *Docker) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error { + if len(docker.layers) == 0 { + return errors.New("No layers") + } + container := Container{ + Id: randomId(), + Cmd: args[0], + Args: args[1:], + Layers: docker.layers[:1], + } + docker.containers = append(docker.containers, container) + cmd := exec.Command(args[0], args[1:]...) + cmd_stdin, cmd_stdout, err := startCommand(cmd, false) + if err != nil { + return err + } + copy_out := Go(func() error { + _, err := io.Copy(stdout, cmd_stdout) + return err + }) + copy_in := Go(func() error { + //_, err := io.Copy(cmd_stdin, stdin) + cmd_stdin.Close() + stdin.Close() + //return err + return nil + }) + if err := cmd.Wait(); err != nil { + return err + } + if err := <-copy_in; err != nil { + return err + } + if err := <-copy_out; err != nil { + return err + } + return nil +} + +func startCommand(cmd *exec.Cmd, interactive bool) (io.WriteCloser, io.ReadCloser, error) { + if interactive { + term, err := pty.Start(cmd) + if err != nil { + return nil, nil, err + } + return term, term, nil + } + stdin, err := cmd.StdinPipe() + if err != nil { + return nil, nil, err + } + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, nil, err + } + if err := cmd.Start(); err != nil { + return nil, nil, err + } + return stdin, stdout, nil +} + +func (docker *Docker) CmdList(stdin io.ReadCloser, stdout io.Writer, args ...string) error { + w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0) + fmt.Fprintf(w, "ID\tCMD\tSTATUS\tLAYERS\n") + for _, container := range docker.containers { + var layers []string + for _, layer := range container.Layers { + layers = append(layers, layer.Name) + } + fmt.Fprintf(w, "%s\t%s %s\t?\t%s\n", + container.Id, + container.Cmd, + strings.Join(container.Args, " "), + strings.Join(layers, ", ")) + } + w.Flush() + return nil +} + +func main() { + rand.Seed(time.Now().UTC().UnixNano()) + flag.Parse() + if err := http.ListenAndServe(":4242", new(Docker)); err != nil { + log.Fatal(err) + } +} + +func (docker *Docker) ServeHTTP(w http.ResponseWriter, r *http.Request) { + cmd, args := URLToCall(r.URL) + method := docker.getMethod(cmd) + if method == nil { + docker.CmdUsage(r.Body, w, cmd) + } else { + err := method(r.Body, w, args...) + if err != nil { + fmt.Fprintf(w, "Error: %s\n", err) + } + } +} + + +func (docker *Docker) getMethod(name string) Cmd { + methodName := "Cmd"+strings.ToUpper(name[:1])+strings.ToLower(name[1:]) + method, exists := reflect.TypeOf(docker).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(docker), + reflect.ValueOf(stdin), + reflect.ValueOf(stdout), + reflect.ValueOf(args), + })[0].Interface() + if ret == nil { + return nil + } + return ret.(error) + } +} + +func Go(f func() error) chan error { + ch := make(chan error) + go func() { + ch <- f() + }() + return ch +} + +type Docker struct { + layers []Layer + containers []Container +} + +type Layer struct { + Id string + Name string + Added time.Time +} + +type Container struct { + Id string + Cmd string + Args []string + Layers []Layer +} + +type Cmd func(io.ReadCloser, io.Writer, ...string) error +type CmdMethod func(*Docker, io.ReadCloser, io.Writer, ...string) error + +// 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 randomBytes() io.Reader { + return bytes.NewBuffer([]byte(fmt.Sprintf("%x", rand.Int()))) +} + +func ComputeId(content io.Reader) (string, error) { + h := sha256.New() + if _, err := io.Copy(h, content); err != nil { + return "", err + } + return fmt.Sprintf("%x", h.Sum(nil)[:8]), nil +} + +func randomId() string { + id, _ := ComputeId(randomBytes()) // can't fail + return id +}