소스 검색

Prototyping with a mock CLI and daemon

Solomon Hykes 12 년 전
부모
커밋
e5323e7c9a
2개의 변경된 파일274개의 추가작업 그리고 0개의 파일을 삭제
  1. 39 0
      docker/docker.go
  2. 235 0
      dockerd/dockerd.go

+ 39 - 0
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)
+}

+ 235 - 0
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
+}