Procházet zdrojové kódy

Merge pull request #432 from dotcloud/remote-api

+ Remote API: Implement the remote API.
Guillaume J. Charmes před 12 roky
rodič
revize
26bfeb1d67

+ 618 - 0
api.go

@@ -0,0 +1,618 @@
+package docker
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/dotcloud/docker/auth"
+	"github.com/gorilla/mux"
+	"github.com/shin-/cookiejar"
+	"io"
+	"log"
+	"net/http"
+	"strconv"
+	"strings"
+)
+
+func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
+	conn, _, err := w.(http.Hijacker).Hijack()
+	if err != nil {
+		return nil, nil, err
+	}
+	// Flush the options to make sure the client sets the raw mode
+	conn.Write([]byte{})
+	return conn, conn, nil
+}
+
+//If we don't do this, POST method without Content-type (even with empty body) will fail
+func parseForm(r *http.Request) error {
+	if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
+		return err
+	}
+	return nil
+}
+
+func httpError(w http.ResponseWriter, err error) {
+	if strings.HasPrefix(err.Error(), "No such") {
+		http.Error(w, err.Error(), http.StatusNotFound)
+	} else {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+	}
+}
+
+func getAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	config := &auth.AuthConfig{
+		Username: srv.runtime.authConfig.Username,
+		Email:    srv.runtime.authConfig.Email,
+	}
+	b, err := json.Marshal(config)
+	if err != nil {
+		return nil, err
+	}
+	return b, nil
+}
+
+func postAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	config := &auth.AuthConfig{}
+	if err := json.NewDecoder(r.Body).Decode(config); err != nil {
+		return nil, err
+	}
+
+	if config.Username == srv.runtime.authConfig.Username {
+		config.Password = srv.runtime.authConfig.Password
+	}
+
+	newAuthConfig := auth.NewAuthConfig(config.Username, config.Password, config.Email, srv.runtime.root)
+	status, err := auth.Login(newAuthConfig)
+	if err != nil {
+		return nil, err
+	} else {
+		srv.runtime.graph.getHttpClient().Jar = cookiejar.NewCookieJar()
+		srv.runtime.authConfig = newAuthConfig
+	}
+	if status != "" {
+		b, err := json.Marshal(&ApiAuth{Status: status})
+		if err != nil {
+			return nil, err
+		}
+		return b, nil
+	}
+	w.WriteHeader(http.StatusNoContent)
+	return nil, nil
+}
+
+func getVersion(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	m := srv.DockerVersion()
+	b, err := json.Marshal(m)
+	if err != nil {
+		return nil, err
+	}
+	return b, nil
+}
+
+func postContainersKill(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	if vars == nil {
+		return nil, fmt.Errorf("Missing parameter")
+	}
+	name := vars["name"]
+	if err := srv.ContainerKill(name); err != nil {
+		return nil, err
+	}
+	w.WriteHeader(http.StatusNoContent)
+	return nil, nil
+}
+
+func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	if vars == nil {
+		return nil, fmt.Errorf("Missing parameter")
+	}
+	name := vars["name"]
+
+	if err := srv.ContainerExport(name, w); err != nil {
+		Debugf("%s", err.Error())
+		//return nil, err
+	}
+	return nil, nil
+}
+
+func getImagesJson(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	if err := parseForm(r); err != nil {
+		return nil, err
+	}
+
+	all := r.Form.Get("all") == "1"
+	filter := r.Form.Get("filter")
+	only_ids := r.Form.Get("only_ids") == "1"
+
+	outs, err := srv.Images(all, only_ids, filter)
+	if err != nil {
+		return nil, err
+	}
+	b, err := json.Marshal(outs)
+	if err != nil {
+		return nil, err
+	}
+	return b, nil
+}
+
+func getImagesViz(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	if err := srv.ImagesViz(w); err != nil {
+		return nil, err
+	}
+	return nil, nil
+}
+
+func getInfo(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	out := srv.DockerInfo()
+	b, err := json.Marshal(out)
+	if err != nil {
+		return nil, err
+	}
+	return b, nil
+}
+
+func getImagesHistory(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	if vars == nil {
+		return nil, fmt.Errorf("Missing parameter")
+	}
+	name := vars["name"]
+	outs, err := srv.ImageHistory(name)
+	if err != nil {
+		return nil, err
+	}
+	b, err := json.Marshal(outs)
+	if err != nil {
+		return nil, err
+	}
+	return b, nil
+}
+
+func getContainersChanges(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	if vars == nil {
+		return nil, fmt.Errorf("Missing parameter")
+	}
+	name := vars["name"]
+	changesStr, err := srv.ContainerChanges(name)
+	if err != nil {
+		return nil, err
+	}
+	b, err := json.Marshal(changesStr)
+	if err != nil {
+		return nil, err
+	}
+	return b, nil
+}
+
+func getContainersPs(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	if err := parseForm(r); err != nil {
+		return nil, err
+	}
+	all := r.Form.Get("all") == "1"
+	trunc_cmd := r.Form.Get("trunc_cmd") != "0"
+	only_ids := r.Form.Get("only_ids") == "1"
+	since := r.Form.Get("since")
+	before := r.Form.Get("before")
+	n, err := strconv.Atoi(r.Form.Get("limit"))
+	if err != nil {
+		n = -1
+	}
+
+	outs := srv.Containers(all, trunc_cmd, only_ids, n, since, before)
+	b, err := json.Marshal(outs)
+	if err != nil {
+		return nil, err
+	}
+	return b, nil
+}
+
+func postImagesTag(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	if err := parseForm(r); err != nil {
+		return nil, err
+	}
+	repo := r.Form.Get("repo")
+	tag := r.Form.Get("tag")
+	if vars == nil {
+		return nil, fmt.Errorf("Missing parameter")
+	}
+	name := vars["name"]
+	force := r.Form.Get("force") == "1"
+
+	if err := srv.ContainerTag(name, repo, tag, force); err != nil {
+		return nil, err
+	}
+	w.WriteHeader(http.StatusCreated)
+	return nil, nil
+}
+
+func postCommit(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	if err := parseForm(r); err != nil {
+		return nil, err
+	}
+	var config Config
+	if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
+		Debugf("%s", err.Error())
+	}
+	repo := r.Form.Get("repo")
+	tag := r.Form.Get("tag")
+	container := r.Form.Get("container")
+	author := r.Form.Get("author")
+	comment := r.Form.Get("comment")
+	id, err := srv.ContainerCommit(container, repo, tag, author, comment, &config)
+	if err != nil {
+		return nil, err
+	}
+	b, err := json.Marshal(ApiId{id})
+	if err != nil {
+		return nil, err
+	}
+	w.WriteHeader(http.StatusCreated)
+	return b, nil
+}
+
+// Creates an image from Pull or from Import
+func postImagesCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	if err := parseForm(r); err != nil {
+		return nil, err
+	}
+
+	src := r.Form.Get("fromSrc")
+	image := r.Form.Get("fromImage")
+	repo := r.Form.Get("repo")
+	tag := r.Form.Get("tag")
+
+	in, out, err := hijackServer(w)
+	if err != nil {
+		return nil, err
+	}
+	defer in.Close()
+	fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
+	if image != "" { //pull
+		registry := r.Form.Get("registry")
+		if err := srv.ImagePull(image, tag, registry, out); err != nil {
+			fmt.Fprintf(out, "Error: %s\n", err)
+		}
+	} else { //import
+		if err := srv.ImageImport(src, repo, tag, in, out); err != nil {
+			fmt.Fprintf(out, "Error: %s\n", err)
+		}
+	}
+	return nil, nil
+}
+
+func getImagesSearch(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	if err := parseForm(r); err != nil {
+		return nil, err
+	}
+
+	term := r.Form.Get("term")
+	outs, err := srv.ImagesSearch(term)
+	if err != nil {
+		return nil, err
+	}
+	b, err := json.Marshal(outs)
+	if err != nil {
+		return nil, err
+	}
+	return b, nil
+}
+
+func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	if err := parseForm(r); err != nil {
+		return nil, err
+	}
+
+	url := r.Form.Get("url")
+	path := r.Form.Get("path")
+	if vars == nil {
+		return nil, fmt.Errorf("Missing parameter")
+	}
+	name := vars["name"]
+
+	in, out, err := hijackServer(w)
+	if err != nil {
+		return nil, err
+	}
+	defer in.Close()
+	fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
+	if err := srv.ImageInsert(name, url, path, out); err != nil {
+		fmt.Fprintf(out, "Error: %s\n", err)
+	}
+	return nil, nil
+}
+
+func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	if err := parseForm(r); err != nil {
+		return nil, err
+	}
+
+	registry := r.Form.Get("registry")
+
+	if vars == nil {
+		return nil, fmt.Errorf("Missing parameter")
+	}
+	name := vars["name"]
+
+	in, out, err := hijackServer(w)
+	if err != nil {
+		return nil, err
+	}
+	defer in.Close()
+	fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
+	if err := srv.ImagePush(name, registry, out); err != nil {
+		fmt.Fprintln(out, "Error: %s\n", err)
+	}
+	return nil, nil
+}
+
+func postBuild(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	in, out, err := hijackServer(w)
+	if err != nil {
+		return nil, err
+	}
+	defer in.Close()
+	fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
+	if err := srv.ImageCreateFromFile(in, out); err != nil {
+		fmt.Fprintln(out, "Error: %s\n", err)
+	}
+	return nil, nil
+}
+
+func postContainersCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	config := &Config{}
+	if err := json.NewDecoder(r.Body).Decode(config); err != nil {
+		return nil, err
+	}
+	id, err := srv.ContainerCreate(config)
+	if err != nil {
+		return nil, err
+	}
+
+	out := &ApiRun{
+		Id: id,
+	}
+	if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
+		log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.")
+		out.Warnings = append(out.Warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.")
+	}
+	if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit {
+		log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.")
+		out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.")
+	}
+	b, err := json.Marshal(out)
+	if err != nil {
+		return nil, err
+	}
+	w.WriteHeader(http.StatusCreated)
+	return b, nil
+}
+
+func postContainersRestart(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	if err := parseForm(r); err != nil {
+		return nil, err
+	}
+	t, err := strconv.Atoi(r.Form.Get("t"))
+	if err != nil || t < 0 {
+		t = 10
+	}
+	if vars == nil {
+		return nil, fmt.Errorf("Missing parameter")
+	}
+	name := vars["name"]
+	if err := srv.ContainerRestart(name, t); err != nil {
+		return nil, err
+	}
+	w.WriteHeader(http.StatusNoContent)
+	return nil, nil
+}
+
+func deleteContainers(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	if err := parseForm(r); err != nil {
+		return nil, err
+	}
+	if vars == nil {
+		return nil, fmt.Errorf("Missing parameter")
+	}
+	name := vars["name"]
+	removeVolume := r.Form.Get("v") == "1"
+
+	if err := srv.ContainerDestroy(name, removeVolume); err != nil {
+		return nil, err
+	}
+	w.WriteHeader(http.StatusNoContent)
+	return nil, nil
+}
+
+func deleteImages(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	if vars == nil {
+		return nil, fmt.Errorf("Missing parameter")
+	}
+	name := vars["name"]
+	if err := srv.ImageDelete(name); err != nil {
+		return nil, err
+	}
+	w.WriteHeader(http.StatusNoContent)
+	return nil, nil
+}
+
+func postContainersStart(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	if vars == nil {
+		return nil, fmt.Errorf("Missing parameter")
+	}
+	name := vars["name"]
+	if err := srv.ContainerStart(name); err != nil {
+		return nil, err
+	}
+	w.WriteHeader(http.StatusNoContent)
+	return nil, nil
+}
+
+func postContainersStop(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	if err := parseForm(r); err != nil {
+		return nil, err
+	}
+	t, err := strconv.Atoi(r.Form.Get("t"))
+	if err != nil || t < 0 {
+		t = 10
+	}
+
+	if vars == nil {
+		return nil, fmt.Errorf("Missing parameter")
+	}
+	name := vars["name"]
+
+	if err := srv.ContainerStop(name, t); err != nil {
+		return nil, err
+	}
+	w.WriteHeader(http.StatusNoContent)
+	return nil, nil
+}
+
+func postContainersWait(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	if vars == nil {
+		return nil, fmt.Errorf("Missing parameter")
+	}
+	name := vars["name"]
+	status, err := srv.ContainerWait(name)
+	if err != nil {
+		return nil, err
+	}
+	b, err := json.Marshal(&ApiWait{StatusCode: status})
+	if err != nil {
+		return nil, err
+	}
+	return b, nil
+}
+
+func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	if err := parseForm(r); err != nil {
+		return nil, err
+	}
+	logs := r.Form.Get("logs") == "1"
+	stream := r.Form.Get("stream") == "1"
+	stdin := r.Form.Get("stdin") == "1"
+	stdout := r.Form.Get("stdout") == "1"
+	stderr := r.Form.Get("stderr") == "1"
+	if vars == nil {
+		return nil, fmt.Errorf("Missing parameter")
+	}
+	name := vars["name"]
+
+	in, out, err := hijackServer(w)
+	if err != nil {
+		return nil, err
+	}
+	defer in.Close()
+
+	fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
+	if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, in, out); err != nil {
+		fmt.Fprintf(out, "Error: %s\n", err)
+	}
+	return nil, nil
+}
+
+func getContainersByName(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	if vars == nil {
+		return nil, fmt.Errorf("Missing parameter")
+	}
+	name := vars["name"]
+
+	container, err := srv.ContainerInspect(name)
+	if err != nil {
+		return nil, err
+	}
+	b, err := json.Marshal(container)
+	if err != nil {
+		return nil, err
+	}
+	return b, nil
+}
+
+func getImagesByName(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+	if vars == nil {
+		return nil, fmt.Errorf("Missing parameter")
+	}
+	name := vars["name"]
+
+	image, err := srv.ImageInspect(name)
+	if err != nil {
+		return nil, err
+	}
+	b, err := json.Marshal(image)
+	if err != nil {
+		return nil, err
+	}
+	return b, nil
+}
+
+func ListenAndServe(addr string, srv *Server, logging bool) error {
+	r := mux.NewRouter()
+	log.Printf("Listening for HTTP on %s\n", addr)
+
+	m := map[string]map[string]func(*Server, http.ResponseWriter, *http.Request, map[string]string) ([]byte, error){
+		"GET": {
+			"/auth":                         getAuth,
+			"/version":                      getVersion,
+			"/info":                         getInfo,
+			"/images/json":                  getImagesJson,
+			"/images/viz":                   getImagesViz,
+			"/images/search":                getImagesSearch,
+			"/images/{name:.*}/history":     getImagesHistory,
+			"/images/{name:.*}/json":        getImagesByName,
+			"/containers/ps":                getContainersPs,
+			"/containers/{name:.*}/export":  getContainersExport,
+			"/containers/{name:.*}/changes": getContainersChanges,
+			"/containers/{name:.*}/json":    getContainersByName,
+		},
+		"POST": {
+			"/auth":                         postAuth,
+			"/commit":                       postCommit,
+			"/build":                        postBuild,
+			"/images/create":                postImagesCreate,
+			"/images/{name:.*}/insert":      postImagesInsert,
+			"/images/{name:.*}/push":        postImagesPush,
+			"/images/{name:.*}/tag":         postImagesTag,
+			"/containers/create":            postContainersCreate,
+			"/containers/{name:.*}/kill":    postContainersKill,
+			"/containers/{name:.*}/restart": postContainersRestart,
+			"/containers/{name:.*}/start":   postContainersStart,
+			"/containers/{name:.*}/stop":    postContainersStop,
+			"/containers/{name:.*}/wait":    postContainersWait,
+			"/containers/{name:.*}/attach":  postContainersAttach,
+		},
+		"DELETE": {
+			"/containers/{name:.*}": deleteContainers,
+			"/images/{name:.*}":     deleteImages,
+		},
+	}
+
+	for method, routes := range m {
+		for route, fct := range routes {
+			Debugf("Registering %s, %s", method, route)
+			// NOTE: scope issue, make sure the variables are local and won't be changed
+			localRoute := route
+			localMethod := method
+			localFct := fct
+			r.Path(localRoute).Methods(localMethod).HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+				Debugf("Calling %s %s", localMethod, localRoute)
+				if logging {
+					log.Println(r.Method, r.RequestURI)
+				}
+				if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
+					userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
+					if len(userAgent) == 2 && userAgent[1] != VERSION {
+						Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], VERSION)
+					}
+				}
+				json, err := localFct(srv, w, r, mux.Vars(r))
+				if err != nil {
+					httpError(w, err)
+				}
+				if json != nil {
+					w.Header().Set("Content-Type", "application/json")
+					w.Write(json)
+				}
+			})
+		}
+	}
+
+	return http.ListenAndServe(addr, r)
+}

+ 66 - 0
api_params.go

@@ -0,0 +1,66 @@
+package docker
+
+type ApiHistory struct {
+	Id        string
+	Created   int64
+	CreatedBy string
+}
+
+type ApiImages struct {
+	Repository string `json:",omitempty"`
+	Tag        string `json:",omitempty"`
+	Id         string
+	Created    int64 `json:",omitempty"`
+}
+
+type ApiInfo struct {
+	Containers  int
+	Version     string
+	Images      int
+	Debug       bool
+	GoVersion   string
+	NFd         int `json:",omitempty"`
+	NGoroutines int `json:",omitempty"`
+}
+
+type ApiContainers struct {
+	Id      string
+	Image   string `json:",omitempty"`
+	Command string `json:",omitempty"`
+	Created int64  `json:",omitempty"`
+	Status  string `json:",omitempty"`
+	Ports   string `json:",omitempty"`
+}
+
+type ApiSearch struct {
+	Name        string
+	Description string
+}
+
+type ApiId struct {
+	Id string
+}
+
+type ApiRun struct {
+	Id       string
+	Warnings []string
+}
+
+type ApiPort struct {
+	Port string
+}
+
+type ApiVersion struct {
+	Version     string
+	GitCommit   string
+	MemoryLimit bool
+	SwapLimit   bool
+}
+
+type ApiWait struct {
+	StatusCode int
+}
+
+type ApiAuth struct {
+	Status string
+}

+ 877 - 0
api_test.go

@@ -0,0 +1,877 @@
+package docker
+
+import (
+	"bufio"
+	"bytes"
+	"encoding/json"
+	"github.com/dotcloud/docker/auth"
+	"io"
+	"net"
+	"net/http"
+	"net/http/httptest"
+	"os"
+	"path"
+	"testing"
+	"time"
+)
+
+func TestGetAuth(t *testing.T) {
+	runtime, err := newTestRuntime()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer nuke(runtime)
+
+	srv := &Server{runtime: runtime}
+
+	r := httptest.NewRecorder()
+
+	authConfig := &auth.AuthConfig{
+		Username: "utest",
+		Password: "utest",
+		Email:    "utest@yopmail.com",
+	}
+
+	authConfigJson, err := json.Marshal(authConfig)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	req, err := http.NewRequest("POST", "/auth", bytes.NewReader(authConfigJson))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	body, err := postAuth(srv, r, req, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if body == nil {
+		t.Fatalf("No body received\n")
+	}
+	if r.Code != http.StatusOK {
+		t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code)
+	}
+
+	if runtime.authConfig.Username != authConfig.Username ||
+		runtime.authConfig.Password != authConfig.Password ||
+		runtime.authConfig.Email != authConfig.Email {
+		t.Fatalf("The auth configuration hasn't been set correctly")
+	}
+}
+
+func TestGetVersion(t *testing.T) {
+	runtime, err := newTestRuntime()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer nuke(runtime)
+
+	srv := &Server{runtime: runtime}
+
+	body, err := getVersion(srv, nil, nil, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	v := &ApiVersion{}
+
+	err = json.Unmarshal(body, v)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if v.Version != VERSION {
+		t.Errorf("Excepted version %s, %s found", VERSION, v.Version)
+	}
+}
+
+func TestGetInfo(t *testing.T) {
+	runtime, err := newTestRuntime()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer nuke(runtime)
+
+	srv := &Server{runtime: runtime}
+
+	body, err := getInfo(srv, nil, nil, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	infos := &ApiInfo{}
+	err = json.Unmarshal(body, infos)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if infos.Version != VERSION {
+		t.Errorf("Excepted version %s, %s found", VERSION, infos.Version)
+	}
+}
+
+func TestGetImagesJson(t *testing.T) {
+	runtime, err := newTestRuntime()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer nuke(runtime)
+
+	srv := &Server{runtime: runtime}
+
+	// FIXME: Do more tests with filter
+	req, err := http.NewRequest("GET", "/images/json?quiet=0&all=0", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	body, err := getImagesJson(srv, nil, req, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	images := []ApiImages{}
+	err = json.Unmarshal(body, &images)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if len(images) != 1 {
+		t.Errorf("Excepted 1 image, %d found", len(images))
+	}
+
+	if images[0].Repository != "docker-ut" {
+		t.Errorf("Excepted image docker-ut, %s found", images[0].Repository)
+	}
+}
+
+func TestGetImagesViz(t *testing.T) {
+	//FIXME: Implement this test (or remove this endpoint)
+	t.Log("Test not implemented")
+}
+
+func TestGetImagesSearch(t *testing.T) {
+	runtime, err := newTestRuntime()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer nuke(runtime)
+
+	srv := &Server{runtime: runtime}
+
+	req, err := http.NewRequest("GET", "/images/search?term=redis", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	body, err := getImagesSearch(srv, nil, req, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	results := []ApiSearch{}
+
+	err = json.Unmarshal(body, &results)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(results) < 2 {
+		t.Errorf("Excepted at least 2 lines, %d found", len(results))
+	}
+}
+
+func TestGetImagesHistory(t *testing.T) {
+	runtime, err := newTestRuntime()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer nuke(runtime)
+
+	srv := &Server{runtime: runtime}
+
+	body, err := getImagesHistory(srv, nil, nil, map[string]string{"name": unitTestImageName})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	history := []ApiHistory{}
+
+	err = json.Unmarshal(body, &history)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(history) != 1 {
+		t.Errorf("Excepted 1 line, %d found", len(history))
+	}
+}
+
+func TestGetImagesByName(t *testing.T) {
+	runtime, err := newTestRuntime()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer nuke(runtime)
+
+	srv := &Server{runtime: runtime}
+
+	body, err := getImagesByName(srv, nil, nil, map[string]string{"name": unitTestImageName})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	img := &Image{}
+
+	err = json.Unmarshal(body, img)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if img.Comment != "Imported from http://get.docker.io/images/busybox" {
+		t.Errorf("Error inspecting image")
+	}
+}
+
+func TestGetContainersPs(t *testing.T) {
+	runtime, err := newTestRuntime()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer nuke(runtime)
+
+	srv := &Server{runtime: runtime}
+
+	container, err := NewBuilder(runtime).Create(&Config{
+		Image: GetTestImage(runtime).Id,
+		Cmd:   []string{"echo", "test"},
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer runtime.Destroy(container)
+
+	req, err := http.NewRequest("GET", "/containers?quiet=1&all=1", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	body, err := getContainersPs(srv, nil, req, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	containers := []ApiContainers{}
+	err = json.Unmarshal(body, &containers)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(containers) != 1 {
+		t.Fatalf("Excepted %d container, %d found", 1, len(containers))
+	}
+	if containers[0].Id != container.ShortId() {
+		t.Fatalf("Container ID mismatch. Expected: %s, received: %s\n", container.ShortId(), containers[0].Id)
+	}
+}
+
+func TestGetContainersExport(t *testing.T) {
+	//FIXME: Implement this test
+	t.Log("Test not implemented")
+}
+
+func TestGetContainersChanges(t *testing.T) {
+	runtime, err := newTestRuntime()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer nuke(runtime)
+
+	srv := &Server{runtime: runtime}
+
+	builder := NewBuilder(runtime)
+
+	// Create a container and remove a file
+	container, err := builder.Create(
+		&Config{
+			Image: GetTestImage(runtime).Id,
+			Cmd:   []string{"/bin/rm", "/etc/passwd"},
+		},
+	)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer runtime.Destroy(container)
+
+	if err := container.Run(); err != nil {
+		t.Fatal(err)
+	}
+
+	body, err := getContainersChanges(srv, nil, nil, map[string]string{"name": container.Id})
+	if err != nil {
+		t.Fatal(err)
+	}
+	changes := []Change{}
+	if err := json.Unmarshal(body, &changes); err != nil {
+		t.Fatal(err)
+	}
+
+	// Check the changelog
+	success := false
+	for _, elem := range changes {
+		if elem.Path == "/etc/passwd" && elem.Kind == 2 {
+			success = true
+		}
+	}
+	if !success {
+		t.Fatalf("/etc/passwd as been removed but is not present in the diff")
+	}
+}
+
+func TestGetContainersByName(t *testing.T) {
+	//FIXME: Implement this test
+	t.Log("Test not implemented")
+}
+
+func TestPostAuth(t *testing.T) {
+	runtime, err := newTestRuntime()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer nuke(runtime)
+
+	srv := &Server{runtime: runtime}
+
+	authConfigOrig := &auth.AuthConfig{
+		Username: "utest",
+		Email:    "utest@yopmail.com",
+	}
+	runtime.authConfig = authConfigOrig
+
+	body, err := getAuth(srv, nil, nil, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	authConfig := &auth.AuthConfig{}
+	err = json.Unmarshal(body, authConfig)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if authConfig.Username != authConfigOrig.Username || authConfig.Email != authConfigOrig.Email {
+		t.Errorf("The retrieve auth mismatch with the one set.")
+	}
+}
+
+func TestPostCommit(t *testing.T) {
+	//FIXME: Implement this test
+	t.Log("Test not implemented")
+}
+
+func TestPostBuild(t *testing.T) {
+	//FIXME: Implement this test
+	t.Log("Test not implemented")
+}
+
+func TestPostImagesCreate(t *testing.T) {
+	//FIXME: Implement this test
+	t.Log("Test not implemented")
+}
+
+func TestPostImagesInsert(t *testing.T) {
+	//FIXME: Implement this test (or remove this endpoint)
+	t.Log("Test not implemented")
+}
+
+func TestPostImagesPush(t *testing.T) {
+	//FIXME: Implement this test
+	t.Log("Test not implemented")
+}
+
+func TestPostImagesTag(t *testing.T) {
+	//FIXME: Implement this test
+	t.Log("Test not implemented")
+}
+
+func TestPostContainersCreate(t *testing.T) {
+	runtime, err := newTestRuntime()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer nuke(runtime)
+
+	srv := &Server{runtime: runtime}
+
+	r := httptest.NewRecorder()
+
+	configJson, err := json.Marshal(&Config{
+		Image:  GetTestImage(runtime).Id,
+		Memory: 33554432,
+		Cmd:    []string{"touch", "/test"},
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	req, err := http.NewRequest("POST", "/containers/create", bytes.NewReader(configJson))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	body, err := postContainersCreate(srv, r, req, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if r.Code != http.StatusCreated {
+		t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code)
+	}
+
+	apiRun := &ApiRun{}
+	if err := json.Unmarshal(body, apiRun); err != nil {
+		t.Fatal(err)
+	}
+
+	container := srv.runtime.Get(apiRun.Id)
+	if container == nil {
+		t.Fatalf("Container not created")
+	}
+
+	if err := container.Run(); err != nil {
+		t.Fatal(err)
+	}
+
+	if _, err := os.Stat(path.Join(container.rwPath(), "test")); err != nil {
+		if os.IsNotExist(err) {
+			Debugf("Err: %s", err)
+			t.Fatalf("The test file has not been created")
+		}
+		t.Fatal(err)
+	}
+}
+
+func TestPostContainersKill(t *testing.T) {
+	runtime, err := newTestRuntime()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer nuke(runtime)
+
+	srv := &Server{runtime: runtime}
+
+	container, err := NewBuilder(runtime).Create(
+		&Config{
+			Image:     GetTestImage(runtime).Id,
+			Cmd:       []string{"/bin/cat"},
+			OpenStdin: true,
+		},
+	)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer runtime.Destroy(container)
+
+	if err := container.Start(); err != nil {
+		t.Fatal(err)
+	}
+
+	// Give some time to the process to start
+	container.WaitTimeout(500 * time.Millisecond)
+
+	if !container.State.Running {
+		t.Errorf("Container should be running")
+	}
+
+	r := httptest.NewRecorder()
+
+	body, err := postContainersKill(srv, r, nil, map[string]string{"name": container.Id})
+	if err != nil {
+		t.Fatal(err)
+	}
+	if body != nil {
+		t.Fatalf("No body expected, received: %s\n", body)
+	}
+	if r.Code != http.StatusNoContent {
+		t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code)
+	}
+	if container.State.Running {
+		t.Fatalf("The container hasn't been killed")
+	}
+}
+
+func TestPostContainersRestart(t *testing.T) {
+	runtime, err := newTestRuntime()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer nuke(runtime)
+
+	srv := &Server{runtime: runtime}
+
+	container, err := NewBuilder(runtime).Create(
+		&Config{
+			Image:     GetTestImage(runtime).Id,
+			Cmd:       []string{"/bin/cat"},
+			OpenStdin: true,
+		},
+	)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer runtime.Destroy(container)
+
+	if err := container.Start(); err != nil {
+		t.Fatal(err)
+	}
+
+	// Give some time to the process to start
+	container.WaitTimeout(500 * time.Millisecond)
+
+	if !container.State.Running {
+		t.Errorf("Container should be running")
+	}
+
+	r := httptest.NewRecorder()
+
+	req, err := http.NewRequest("POST", "/containers/"+container.Id+"/restart?t=1", bytes.NewReader([]byte{}))
+	if err != nil {
+		t.Fatal(err)
+	}
+	body, err := postContainersRestart(srv, r, req, map[string]string{"name": container.Id})
+	if err != nil {
+		t.Fatal(err)
+	}
+	if body != nil {
+		t.Fatalf("No body expected, received: %s\n", body)
+	}
+	if r.Code != http.StatusNoContent {
+		t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code)
+	}
+
+	// Give some time to the process to restart
+	container.WaitTimeout(500 * time.Millisecond)
+
+	if !container.State.Running {
+		t.Fatalf("Container should be running")
+	}
+
+	if err := container.Kill(); err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestPostContainersStart(t *testing.T) {
+	runtime, err := newTestRuntime()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer nuke(runtime)
+
+	srv := &Server{runtime: runtime}
+
+	container, err := NewBuilder(runtime).Create(
+		&Config{
+			Image:     GetTestImage(runtime).Id,
+			Cmd:       []string{"/bin/cat"},
+			OpenStdin: true,
+		},
+	)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer runtime.Destroy(container)
+
+	r := httptest.NewRecorder()
+
+	body, err := postContainersStart(srv, r, nil, map[string]string{"name": container.Id})
+	if err != nil {
+		t.Fatal(err)
+	}
+	if body != nil {
+		t.Fatalf("No body expected, received: %s\n", body)
+	}
+	if r.Code != http.StatusNoContent {
+		t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code)
+	}
+
+	// Give some time to the process to start
+	container.WaitTimeout(500 * time.Millisecond)
+
+	if !container.State.Running {
+		t.Errorf("Container should be running")
+	}
+
+	if _, err = postContainersStart(srv, r, nil, map[string]string{"name": container.Id}); err == nil {
+		t.Fatalf("A running containter should be able to be started")
+	}
+
+	if err := container.Kill(); err != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestPostContainersStop(t *testing.T) {
+	runtime, err := newTestRuntime()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer nuke(runtime)
+
+	srv := &Server{runtime: runtime}
+
+	container, err := NewBuilder(runtime).Create(
+		&Config{
+			Image:     GetTestImage(runtime).Id,
+			Cmd:       []string{"/bin/cat"},
+			OpenStdin: true,
+		},
+	)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer runtime.Destroy(container)
+
+	if err := container.Start(); err != nil {
+		t.Fatal(err)
+	}
+
+	// Give some time to the process to start
+	container.WaitTimeout(500 * time.Millisecond)
+
+	if !container.State.Running {
+		t.Errorf("Container should be running")
+	}
+
+	r := httptest.NewRecorder()
+
+	// Note: as it is a POST request, it requires a body.
+	req, err := http.NewRequest("POST", "/containers/"+container.Id+"/stop?t=1", bytes.NewReader([]byte{}))
+	if err != nil {
+		t.Fatal(err)
+	}
+	body, err := postContainersStop(srv, r, req, map[string]string{"name": container.Id})
+	if err != nil {
+		t.Fatal(err)
+	}
+	if body != nil {
+		t.Fatalf("No body expected, received: %s\n", body)
+	}
+	if r.Code != http.StatusNoContent {
+		t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code)
+	}
+	if container.State.Running {
+		t.Fatalf("The container hasn't been stopped")
+	}
+}
+
+func TestPostContainersWait(t *testing.T) {
+	runtime, err := newTestRuntime()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer nuke(runtime)
+
+	srv := &Server{runtime: runtime}
+
+	container, err := NewBuilder(runtime).Create(
+		&Config{
+			Image:     GetTestImage(runtime).Id,
+			Cmd:       []string{"/bin/sleep", "1"},
+			OpenStdin: true,
+		},
+	)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer runtime.Destroy(container)
+
+	if err := container.Start(); err != nil {
+		t.Fatal(err)
+	}
+
+	setTimeout(t, "Wait timed out", 3*time.Second, func() {
+		body, err := postContainersWait(srv, nil, nil, map[string]string{"name": container.Id})
+		if err != nil {
+			t.Fatal(err)
+		}
+		apiWait := &ApiWait{}
+		if err := json.Unmarshal(body, apiWait); err != nil {
+			t.Fatal(err)
+		}
+		if apiWait.StatusCode != 0 {
+			t.Fatalf("Non zero exit code for sleep: %d\n", apiWait.StatusCode)
+		}
+	})
+
+	if container.State.Running {
+		t.Fatalf("The container should be stopped after wait")
+	}
+}
+
+func TestPostContainersAttach(t *testing.T) {
+	runtime, err := newTestRuntime()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer nuke(runtime)
+
+	srv := &Server{runtime: runtime}
+
+	container, err := NewBuilder(runtime).Create(
+		&Config{
+			Image:     GetTestImage(runtime).Id,
+			Cmd:       []string{"/bin/cat"},
+			OpenStdin: true,
+		},
+	)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer runtime.Destroy(container)
+
+	// Start the process
+	if err := container.Start(); err != nil {
+		t.Fatal(err)
+	}
+
+	stdin, stdinPipe := io.Pipe()
+	stdout, stdoutPipe := io.Pipe()
+
+	// Attach to it
+	c1 := make(chan struct{})
+	go func() {
+		// We're simulating a disconnect so the return value doesn't matter. What matters is the
+		// fact that CmdAttach returns.
+
+		r := &hijackTester{
+			ResponseRecorder: httptest.NewRecorder(),
+			in:               stdin,
+			out:              stdoutPipe,
+		}
+
+		req, err := http.NewRequest("POST", "/containers/"+container.Id+"/attach?stream=1&stdin=1&stdout=1&stderr=1", bytes.NewReader([]byte{}))
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		body, err := postContainersAttach(srv, r, req, map[string]string{"name": container.Id})
+		close(c1)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if body != nil {
+			t.Fatalf("No body expected, received: %s\n", body)
+		}
+	}()
+
+	// Acknowledge hijack
+	setTimeout(t, "hijack acknowledge timed out", 2*time.Second, func() {
+		stdout.Read([]byte{})
+		stdout.Read(make([]byte, 4096))
+	})
+
+	setTimeout(t, "read/write assertion timed out", 2*time.Second, func() {
+		if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
+			t.Fatal(err)
+		}
+	})
+
+	// Close pipes (client disconnects)
+	if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
+		t.Fatal(err)
+	}
+
+	// Wait for attach to finish, the client disconnected, therefore, Attach finished his job
+	setTimeout(t, "Waiting for CmdAttach timed out", 2*time.Second, func() {
+		<-c1
+	})
+
+	// We closed stdin, expect /bin/cat to still be running
+	// Wait a little bit to make sure container.monitor() did his thing
+	err = container.WaitTimeout(500 * time.Millisecond)
+	if err == nil || !container.State.Running {
+		t.Fatalf("/bin/cat is not running after closing stdin")
+	}
+
+	// Try to avoid the timeoout in destroy. Best effort, don't check error
+	cStdin, _ := container.StdinPipe()
+	cStdin.Close()
+	container.Wait()
+}
+
+// FIXME: Test deleting runnign container
+// FIXME: Test deleting container with volume
+// FIXME: Test deleting volume in use by other container
+func TestDeleteContainers(t *testing.T) {
+	runtime, err := newTestRuntime()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer nuke(runtime)
+
+	srv := &Server{runtime: runtime}
+
+	container, err := NewBuilder(runtime).Create(&Config{
+		Image: GetTestImage(runtime).Id,
+		Cmd:   []string{"touch", "/test"},
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer runtime.Destroy(container)
+
+	if err := container.Run(); err != nil {
+		t.Fatal(err)
+	}
+
+	r := httptest.NewRecorder()
+
+	req, err := http.NewRequest("DELETE", "/containers/"+container.Id, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	body, err := deleteContainers(srv, r, req, map[string]string{"name": container.Id})
+	if err != nil {
+		t.Fatal(err)
+	}
+	if body != nil {
+		t.Fatalf("No body expected, received: %s\n", body)
+	}
+	if r.Code != http.StatusNoContent {
+		t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code)
+	}
+
+	if c := runtime.Get(container.Id); c != nil {
+		t.Fatalf("The container as not been deleted")
+	}
+
+	if _, err := os.Stat(path.Join(container.rwPath(), "test")); err == nil {
+		t.Fatalf("The test file has not been deleted")
+	}
+}
+
+func TestDeleteImages(t *testing.T) {
+	//FIXME: Implement this test
+	t.Log("Test not implemented")
+}
+
+// Mocked types for tests
+type NopConn struct {
+	io.ReadCloser
+	io.Writer
+}
+
+func (c *NopConn) LocalAddr() net.Addr                { return nil }
+func (c *NopConn) RemoteAddr() net.Addr               { return nil }
+func (c *NopConn) SetDeadline(t time.Time) error      { return nil }
+func (c *NopConn) SetReadDeadline(t time.Time) error  { return nil }
+func (c *NopConn) SetWriteDeadline(t time.Time) error { return nil }
+
+type hijackTester struct {
+	*httptest.ResponseRecorder
+	in  io.ReadCloser
+	out io.Writer
+}
+
+func (t *hijackTester) Hijack() (net.Conn, *bufio.ReadWriter, error) {
+	bufrw := bufio.NewReadWriter(bufio.NewReader(t.in), bufio.NewWriter(t.out))
+	conn := &NopConn{
+		ReadCloser: t.in,
+		Writer:     t.out,
+	}
+	return conn, bufrw, nil
+}

+ 2 - 2
builder.go

@@ -266,7 +266,7 @@ func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) (*Image, e
 			if image == nil {
 				return nil, fmt.Errorf("Please provide a source image with `from` prior to run")
 			}
-			config, err := ParseRun([]string{image.Id, "/bin/sh", "-c", arguments}, nil, builder.runtime.capabilities)
+			config, _, err := ParseRun([]string{image.Id, "/bin/sh", "-c", arguments}, builder.runtime.capabilities)
 			if err != nil {
 				return nil, err
 			}
@@ -413,7 +413,7 @@ func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) (*Image, e
 			}
 			defer file.Body.Close()
 
-			config, err := ParseRun([]string{base.Id, "echo", "insert", sourceUrl, destPath}, nil, builder.runtime.capabilities)
+			config, _, err := ParseRun([]string{base.Id, "echo", "insert", sourceUrl, destPath}, builder.runtime.capabilities)
 			if err != nil {
 				return nil, err
 			}

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 349 - 409
commands.go


+ 3 - 2
commands_test.go

@@ -3,9 +3,8 @@ package docker
 import (
 	"bufio"
 	"fmt"
-	"github.com/dotcloud/docker/rcli"
 	"io"
-	"io/ioutil"
+	_ "io/ioutil"
 	"strings"
 	"testing"
 	"time"
@@ -59,6 +58,7 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error
 	return nil
 }
 
+/*TODO
 func cmdWait(srv *Server, container *Container) error {
 	stdout, stdoutPipe := io.Pipe()
 
@@ -467,3 +467,4 @@ func TestAttachDisconnect(t *testing.T) {
 	cStdin.Close()
 	container.Wait()
 }
+*/

+ 10 - 10
container.go

@@ -2,8 +2,8 @@ package docker
 
 import (
 	"encoding/json"
+	"flag"
 	"fmt"
-	"github.com/dotcloud/docker/rcli"
 	"github.com/kr/pty"
 	"io"
 	"io/ioutil"
@@ -71,8 +71,8 @@ type Config struct {
 	VolumesFrom  string
 }
 
-func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Config, error) {
-	cmd := rcli.Subcmd(stdout, "run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container")
+func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet, error) {
+	cmd := Subcmd("run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container")
 	if len(args) > 0 && args[0] != "--help" {
 		cmd.SetOutput(ioutil.Discard)
 	}
@@ -86,8 +86,8 @@ func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Con
 	flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
 	flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)")
 
-	if *flMemory > 0 && !capabilities.MemoryLimit {
-		fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
+	if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit {
+		//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
 		*flMemory = 0
 	}
 
@@ -106,10 +106,10 @@ func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Con
 	flVolumesFrom := cmd.String("volumes-from", "", "Mount volumes from the specified container")
 
 	if err := cmd.Parse(args); err != nil {
-		return nil, err
+		return nil, cmd, err
 	}
 	if *flDetach && len(flAttach) > 0 {
-		return nil, fmt.Errorf("Conflicting options: -a and -d")
+		return nil, cmd, fmt.Errorf("Conflicting options: -a and -d")
 	}
 	// If neither -d or -a are set, attach to everything by default
 	if len(flAttach) == 0 && !*flDetach {
@@ -148,8 +148,8 @@ func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Con
 		VolumesFrom:  *flVolumesFrom,
 	}
 
-	if *flMemory > 0 && !capabilities.SwapLimit {
-		fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
+	if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
+		//fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
 		config.MemorySwap = -1
 	}
 
@@ -157,7 +157,7 @@ func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Con
 	if config.OpenStdin && config.AttachStdin {
 		config.StdinOnce = true
 	}
-	return config, nil
+	return config, cmd, nil
 }
 
 type NetworkSettings struct {

+ 5 - 1
container_test.go

@@ -399,6 +399,11 @@ func TestStart(t *testing.T) {
 	}
 	defer runtime.Destroy(container)
 
+	cStdin, err := container.StdinPipe()
+	if err != nil {
+		t.Fatal(err)
+	}
+
 	if err := container.Start(); err != nil {
 		t.Fatal(err)
 	}
@@ -414,7 +419,6 @@ func TestStart(t *testing.T) {
 	}
 
 	// Try to avoid the timeoout in destroy. Best effort, don't check error
-	cStdin, _ := container.StdinPipe()
 	cStdin.Close()
 	container.WaitTimeout(2 * time.Second)
 }

+ 5 - 46
docker/docker.go

@@ -4,9 +4,6 @@ import (
 	"flag"
 	"fmt"
 	"github.com/dotcloud/docker"
-	"github.com/dotcloud/docker/rcli"
-	"github.com/dotcloud/docker/term"
-	"io"
 	"io/ioutil"
 	"log"
 	"os"
@@ -48,10 +45,12 @@ func main() {
 		}
 		if err := daemon(*pidfile, *flAutoRestart); err != nil {
 			log.Fatal(err)
+			os.Exit(-1)
 		}
 	} else {
-		if err := runCommand(flag.Args()); err != nil {
+		if err := docker.ParseCommands(flag.Args()...); err != nil {
 			log.Fatal(err)
+			os.Exit(-1)
 		}
 	}
 }
@@ -98,50 +97,10 @@ func daemon(pidfile string, autoRestart bool) error {
 		os.Exit(0)
 	}()
 
-	service, err := docker.NewServer(autoRestart)
+	server, err := docker.NewServer(autoRestart)
 	if err != nil {
 		return err
 	}
-	return rcli.ListenAndServe("tcp", "127.0.0.1:4242", service)
-}
 
-func runCommand(args []string) error {
-	// FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose
-	// CloseWrite(), which we need to cleanly signal that stdin is closed without
-	// closing the connection.
-	// See http://code.google.com/p/go/issues/detail?id=3345
-	if conn, err := rcli.Call("tcp", "127.0.0.1:4242", args...); err == nil {
-		options := conn.GetOptions()
-		if options.RawTerminal &&
-			term.IsTerminal(int(os.Stdin.Fd())) &&
-			os.Getenv("NORAW") == "" {
-			if oldState, err := rcli.SetRawTerminal(); err != nil {
-				return err
-			} else {
-				defer rcli.RestoreTerminal(oldState)
-			}
-		}
-		receiveStdout := docker.Go(func() error {
-			_, err := io.Copy(os.Stdout, conn)
-			return err
-		})
-		sendStdin := docker.Go(func() error {
-			_, err := io.Copy(conn, os.Stdin)
-			if err := conn.CloseWrite(); err != nil {
-				log.Printf("Couldn't send EOF: " + err.Error())
-			}
-			return err
-		})
-		if err := <-receiveStdout; err != nil {
-			return err
-		}
-		if !term.IsTerminal(int(os.Stdin.Fd())) {
-			if err := <-sendStdin; err != nil {
-				return err
-			}
-		}
-	} else {
-		return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
-	}
-	return nil
+	return docker.ListenAndServe("0.0.0.0:4243", server, true)
 }

+ 0 - 0
docs/sources/.nojekyll


+ 1 - 0
docs/sources/CNAME

@@ -0,0 +1 @@
+docker.io

+ 1 - 1
docs/sources/commandline/index.rst

@@ -13,4 +13,4 @@ Contents:
 
   basics
   workingwithrepository
-  cli
+  cli

+ 2 - 0
docs/sources/dotcloud.yml

@@ -0,0 +1,2 @@
+www:
+  type: static

+ 210 - 0
docs/sources/gettingstarted/index.html

@@ -0,0 +1,210 @@
+<!DOCTYPE html>
+<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
+<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
+<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
+<!--[if gt IE 8]><!-->
+<html class="no-js" xmlns="http://www.w3.org/1999/html" xmlns="http://www.w3.org/1999/html"> <!--<![endif]-->
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <title>Docker - the Linux container runtime</title>
+
+    <meta name="description" content="Docker encapsulates heterogeneous payloads in standard containers">
+    <meta name="viewport" content="width=device-width">
+
+    <!-- twitter bootstrap -->
+    <link rel="stylesheet" href="../_static/css/bootstrap.min.css">
+    <link rel="stylesheet" href="../_static/css/bootstrap-responsive.min.css">
+
+    <!-- main style file -->
+    <link rel="stylesheet" href="../_static/css/main.css">
+
+    <!-- vendor scripts -->
+    <script src="../_static/js/vendor/jquery-1.9.1.min.js" type="text/javascript" ></script>
+    <script src="../_static/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js" type="text/javascript" ></script>
+
+</head>
+
+
+<body>
+
+<div class="navbar navbar-fixed-top">
+    <div class="navbar-dotcloud">
+        <div class="container" style="text-align: center;">
+
+            <div style="float: right" class="pull-right">
+                <ul class="nav">
+                    <li><a href="../">Introduction</a></li>
+                    <li class="active"><a href="./">Getting started</a></li>
+                    <li class=""><a href="http://docs.docker.io/en/latest/concepts/containers/">Documentation</a></li>
+                </ul>
+
+                <div class="social links" style="float: right; margin-top: 14px; margin-left: 12px">
+                    <a class="twitter" href="http://twitter.com/getdocker">Twitter</a>
+                    <a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
+                </div>
+            </div>
+
+            <div style="margin-left: -12px; float: left;">
+                <a href="../index.html"><img style="margin-top: 12px; height: 38px" src="../_static/img/docker-letters-logo.gif"></a>
+            </div>
+        </div>
+    </div>
+</div>
+
+
+<div class="container">
+    <div class="row">
+        <div class="span12 titlebar"><h1 class="pageheader">GETTING STARTED</h1>
+        </div>
+    </div>
+
+</div>
+
+<div class="container">
+    <div class="alert alert-info">
+        <strong>Docker is still under heavy development.</strong> It should not yet be used in production. Check <a href="http://github.com/dotcloud/docker">the repo</a> for recent progress.
+    </div>
+    <div class="row">
+        <div class="span6">
+            <section class="contentblock">
+                <h2>
+                    <a name="installing-on-ubuntu-1204-and-1210" class="anchor" href="#installing-on-ubuntu-1204-and-1210"><span class="mini-icon mini-icon-link"></span>
+                    </a>Installing on Ubuntu</h2>
+
+                    <p><strong>Requirements</strong></p>
+                    <ul>
+                        <li>Ubuntu 12.04 (LTS) (64-bit)</li>
+                        <li> or Ubuntu 12.10 (quantal) (64-bit)</li>
+                    </ul>
+                <ol>
+                    <li>
+                    <p><strong>Install dependencies</strong></p>
+                    The linux-image-extra package is only needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module.
+                    <pre>sudo apt-get install linux-image-extra-`uname -r`</pre>
+
+
+                    </li>
+                    <li>
+                        <p><strong>Install Docker</strong></p>
+                        <p>Add the Ubuntu PPA (Personal Package Archive) sources to your apt sources list, update and install.</p>
+                        <p>You may see some warnings that the GPG keys cannot be verified.</p>
+                        <div class="highlight">
+                            <pre>sudo sh -c "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >> /etc/apt/sources.list"</pre>
+                            <pre>sudo apt-get update</pre>
+                            <pre>sudo apt-get install lxc-docker</pre>
+                        </div>
+
+
+                    </li>
+
+                    <li>
+                        <p><strong>Run!</strong></p>
+
+                        <div class="highlight">
+                            <pre>docker run -i -t ubuntu /bin/bash</pre>
+                        </div>
+                    </li>
+                    Continue with the <a href="http://docs.docker.io/en/latest/examples/hello_world/">Hello world</a> example.
+                </ol>
+            </section>
+
+            <section class="contentblock">
+                <h2>Contributing to Docker</h2>
+
+                <p>Want to hack on Docker? Awesome! We have some <a href="http://docs.docker.io/en/latest/contributing/contributing/">instructions to get you started</a>. They are probably not perfect, please let us know if anything feels wrong or incomplete.</p>
+            </section>
+
+        </div>
+        <div class="span6">
+            <section class="contentblock">
+                <h2>Quick install on other operating systems</h2>
+                <p><strong>For other operating systems we recommend and provide a streamlined install with virtualbox,
+                    vagrant and an Ubuntu virtual machine.</strong></p>
+
+                <ul>
+                    <li><a href="http://docs.docker.io/en/latest/installation/vagrant/">Mac OS X and other linuxes</a></li>
+                    <li><a href="http://docs.docker.io/en/latest/installation/windows/">Windows</a></li>
+                </ul>
+
+            </section>
+
+            <section class="contentblock">
+                <h2>More resources</h2>
+                <ul>
+                    <li><a href="irc://chat.freenode.net#docker">IRC: docker on freenode</a></li>
+                    <li><a href="http://www.github.com/dotcloud/docker">Github</a></li>
+                    <li><a href="http://stackoverflow.com/tags/docker/">Ask questions on Stackoverflow</a></li>
+                    <li><a href="http://twitter.com/getdocker/">Join the conversation on Twitter</a></li>
+                </ul>
+            </section>
+
+
+            <section class="contentblock">
+                <div id="wufoo-z7x3p3">
+                    Fill out my <a href="http://dotclouddocker.wufoo.com/forms/z7x3p3">online form</a>.
+                </div>
+                <script type="text/javascript">var z7x3p3;(function(d, t) {
+                    var s = d.createElement(t), options = {
+                        'userName':'dotclouddocker',
+                        'formHash':'z7x3p3',
+                        'autoResize':true,
+                        'height':'577',
+                        'async':true,
+                        'header':'show'};
+                    s.src = ('https:' == d.location.protocol ? 'https://' : 'http://') + 'wufoo.com/scripts/embed/form.js';
+                    s.onload = s.onreadystatechange = function() {
+                        var rs = this.readyState; if (rs) if (rs != 'complete') if (rs != 'loaded') return;
+                        try { z7x3p3 = new WufooForm();z7x3p3.initialize(options);z7x3p3.display(); } catch (e) {}};
+                    var scr = d.getElementsByTagName(t)[0], par = scr.parentNode; par.insertBefore(s, scr);
+                })(document, 'script');</script>
+            </section>
+
+        </div>
+    </div>
+</div>
+
+
+<div class="container">
+    <footer id="footer" class="footer">
+        <div class="row">
+            <div class="span12 social">
+
+                Docker is a project by <a href="http://www.dotcloud.com">dotCloud</a>
+
+            </div>
+        </div>
+
+        <div class="row">
+            <div class="emptyspace" style="height: 40px">
+
+            </div>
+        </div>
+
+    </footer>
+</div>
+
+
+<!-- bootstrap javascipts -->
+<script src="../_static/js/vendor/bootstrap.min.js" type="text/javascript"></script>
+
+<!-- Google analytics -->
+<script type="text/javascript">
+
+    var _gaq = _gaq || [];
+    _gaq.push(['_setAccount', 'UA-6096819-11']);
+    _gaq.push(['_setDomainName', 'docker.io']);
+    _gaq.push(['_setAllowLinker', true]);
+    _gaq.push(['_trackPageview']);
+
+    (function() {
+        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+    })();
+
+</script>
+
+
+</body>
+</html>

+ 314 - 0
docs/sources/index.html

@@ -0,0 +1,314 @@
+<!DOCTYPE html>
+<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
+<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
+<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
+<!--[if gt IE 8]><!-->
+<html class="no-js" xmlns="http://www.w3.org/1999/html"> <!--<![endif]-->
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <meta name="google-site-verification" content="UxV66EKuPe87dgnH1sbrldrx6VsoWMrx5NjwkgUFxXI" />
+    <title>Docker - the Linux container engine</title>
+
+    <meta name="description" content="Docker encapsulates heterogeneous payloads in standard containers">
+    <meta name="viewport" content="width=device-width">
+
+    <!-- twitter bootstrap -->
+    <link rel="stylesheet" href="_static/css/bootstrap.min.css">
+    <link rel="stylesheet" href="_static/css/bootstrap-responsive.min.css">
+
+    <!-- main style file -->
+    <link rel="stylesheet" href="_static/css/main.css">
+
+    <!-- vendor scripts -->
+    <script src="_static/js/vendor/jquery-1.9.1.min.js" type="text/javascript" ></script>
+    <script src="_static/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js" type="text/javascript" ></script>
+
+    <style>
+        .indexlabel {
+            float: left;
+            width: 150px;
+            display: block;
+            padding: 10px 20px 10px;
+            font-size: 20px;
+            font-weight: 200;
+            background-color: #a30000;
+            color: white;
+            height: 22px;
+        }
+        .searchbutton {
+            font-size: 20px;
+            height: 40px;
+        }
+
+        .debug {
+            border: 1px red dotted;
+        }
+
+    </style>
+
+</head>
+
+
+<body>
+
+<div class="navbar navbar-fixed-top">
+    <div class="navbar-dotcloud">
+        <div class="container" style="text-align: center;">
+
+            <div class="pull-right" >
+                <ul class="nav">
+                    <li class="active"><a href="./">Introduction</a></li>
+                    <li ><a href="gettingstarted/">Getting started</a></li>
+                    <li class=""><a href="http://docs.docker.io/en/latest/concepts/containers/">Documentation</a></li>
+                </ul>
+
+                <div class="social links" style="float: right; margin-top: 14px; margin-left: 12px">
+                    <a class="twitter" href="http://twitter.com/getdocker">Twitter</a>
+                    <a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+
+<div class="container" style="margin-top: 30px;">
+    <div class="row">
+
+        <div class="span12">
+            <section class="contentblock header">
+
+                <div class="span5" style="margin-bottom: 15px;">
+                    <div style="text-align: center;" >
+                        <img src="_static/img/docker_letters_500px.png">
+
+                        <h2>The Linux container engine</h2>
+                    </div>
+
+                    <div style="display: block; text-align: center; margin-top: 20px;">
+
+                        <h5>
+                            Docker is an open-source engine which automates the deployment of applications as highly portable, self-sufficient containers which are independent of hardware, language, framework, packaging system and hosting provider.
+                        </h5>
+
+                    </div>
+
+
+                    <div style="display: block; text-align: center; margin-top: 30px;">
+                        <a class="btn btn-custom btn-large" href="gettingstarted/">Let's get started</a>
+                    </div>
+
+                </div>
+
+                <div class="span6" >
+                    <div class="js-video" >
+                        <iframe width="600" height="360" src="http://www.youtube.com/embed/wW9CAH9nSLs?feature=player_detailpage&rel=0&modestbranding=1&start=11" frameborder="0" allowfullscreen></iframe>
+                    </div>
+                </div>
+
+                <br style="clear: both"/>
+            </section>
+        </div>
+    </div>
+</div>
+
+<div class="container">
+    <div class="row">
+
+        <div class="span6">
+            <section class="contentblock">
+                <h4>Heterogeneous payloads</h4>
+                <p>Any combination of binaries, libraries, configuration files, scripts, virtualenvs, jars, gems, tarballs, you name it. No more juggling between domain-specific tools. Docker can deploy and run them all.</p>
+                <h4>Any server</h4>
+                <p>Docker can run on any x64 machine with a modern linux kernel - whether it's a laptop, a bare metal server or a VM. This makes it perfect for multi-cloud deployments.</p>
+                <h4>Isolation</h4>
+                <p>Docker isolates processes from each other and from the underlying host, using lightweight containers.</p>
+                <h4>Repeatability</h4>
+                <p>Because each container is isolated in its own filesystem, they behave the same regardless of where, when, and alongside what they run.</p>
+            </section>
+        </div>
+        <div class="span6">
+            <section class="contentblock">
+                <h1>New! Docker Index</h1>
+                On the Docker Index you can find and explore pre-made container images. It allows you to share your images and download them.
+
+                <br><br>
+                <a href="https://index.docker.io" target="_blank">
+                    <div class="indexlabel">
+                        DOCKER index
+                    </div>
+                </a>
+                &nbsp;
+                <input type="button" class="searchbutton" type="submit" value="Search images"
+                       onClick="window.open('https://index.docker.io')" />
+
+            </section>
+            <section class="contentblock">
+                <div id="wufoo-z7x3p3">
+                    Fill out my <a href="http://dotclouddocker.wufoo.com/forms/z7x3p3">online form</a>.
+                </div>
+                <script type="text/javascript">var z7x3p3;(function(d, t) {
+                    var s = d.createElement(t), options = {
+                        'userName':'dotclouddocker',
+                        'formHash':'z7x3p3',
+                        'autoResize':true,
+                        'height':'577',
+                        'async':true,
+                        'header':'show'};
+                    s.src = ('https:' == d.location.protocol ? 'https://' : 'http://') + 'wufoo.com/scripts/embed/form.js';
+                    s.onload = s.onreadystatechange = function() {
+                        var rs = this.readyState; if (rs) if (rs != 'complete') if (rs != 'loaded') return;
+                        try { z7x3p3 = new WufooForm();z7x3p3.initialize(options);z7x3p3.display(); } catch (e) {}};
+                    var scr = d.getElementsByTagName(t)[0], par = scr.parentNode; par.insertBefore(s, scr);
+                })(document, 'script');</script>
+            </section>
+        </div>
+    </div>
+
+</div>
+
+<style>
+    .twitterblock {
+        min-height: 75px;
+    }
+
+    .twitterblock img {
+        float: left;
+        margin-right: 10px;
+    }
+
+</style>
+
+
+<div class="container">
+    <div class="row">
+        <div class="span6">
+            <section class="contentblock twitterblock">
+                <img src="https://twimg0-a.akamaihd.net/profile_images/2491994496/rbevyyq6ykp6bnoby2je_bigger.jpeg">
+                <em>John Willis @botchagalupe:</em> IMHO docker is to paas what chef was to Iaas 4 years ago
+            </section>
+        </div>
+        <div class="span6">
+            <section class="contentblock twitterblock">
+                <img src="https://twimg0-a.akamaihd.net/profile_images/3348427561/9d7f08f1e103a16c8debd169301b9944_bigger.jpeg">
+                <em>John Feminella ‏@superninjarobot:</em> So, @getdocker is pure excellence. If you've ever wished for arbitrary, PaaS-agnostic, lxc/aufs Linux containers, this is your jam!
+            </section>
+        </div>
+    </div>
+    <div class="row">
+        <div class="span6">
+            <section class="contentblock twitterblock">
+                <img src="https://si0.twimg.com/profile_images/3408403010/4496ccdd14e9b7285eca04c31a740207_bigger.jpeg">
+                <em>David Romulan ‏@destructuring:</em> I haven't had this much fun since AWS
+            </section>
+        </div>
+        <div class="span6">
+            <section class="contentblock twitterblock">
+                <img src="https://si0.twimg.com/profile_images/780893320/My_Avatar_bigger.jpg">
+                <em>Ricardo Gladwell ‏@rgladwell:</em> wow @getdocker is either amazing or totally stupid
+            </section>
+        </div>
+
+    </div>
+</div>
+
+<div class="container">
+    <div class="row">
+        <div class="span6">
+
+            <section class="contentblock">
+
+                <h2>Notable features</h2>
+
+                <ul>
+                    <li>Filesystem isolation: each process container runs in a completely separate root filesystem.</li>
+                    <li>Resource isolation: system resources like cpu and memory can be allocated differently to each process container, using cgroups.</li>
+                    <li>Network isolation: each process container runs in its own network namespace, with a virtual interface and IP address of its own.</li>
+                    <li>Copy-on-write: root filesystems are created using copy-on-write, which makes deployment extremeley fast, memory-cheap and disk-cheap.</li>
+                    <li>Logging: the standard streams (stdout/stderr/stdin) of each process container is collected and logged for real-time or batch retrieval.</li>
+                    <li>Change management: changes to a container's filesystem can be committed into a new image and re-used to create more containers. No templating or manual configuration required.</li>
+                    <li>Interactive shell: docker can allocate a pseudo-tty and attach to the standard input of any container, for example to run a throwaway interactive shell.</li>
+                </ul>
+
+                <h2>Under the hood</h2>
+
+                <p>Under the hood, Docker is built on the following components:</p>
+
+                <ul>
+                    <li>The <a href="http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c">cgroup</a> and <a href="http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part">namespacing</a> capabilities of the Linux kernel;</li>
+                    <li><a href="http://aufs.sourceforge.net/aufs.html">AUFS</a>, a powerful union filesystem with copy-on-write capabilities;</li>
+                    <li>The <a href="http://golang.org">Go</a> programming language;</li>
+                    <li><a href="http://lxc.sourceforge.net/">lxc</a>, a set of convenience scripts to simplify the creation of linux containers.</li>
+                </ul>
+
+                <h2>Who started it</h2>
+                <p>
+                    Docker is an open-source implementation of the deployment engine which powers <a href="http://dotcloud.com">dotCloud</a>, a popular Platform-as-a-Service.</p>
+
+                <p>It benefits directly from the experience accumulated over several years of large-scale operation and support of hundreds of thousands
+                    of applications and databases.
+                </p>
+
+            </section>
+        </div>
+
+        <div class="span6">
+
+
+            <section class="contentblock">
+                <h3 id="twitter">Twitter</h3>
+                <a class="twitter-timeline" href="https://twitter.com/getdocker" data-widget-id="312730839718957056">Tweets by @getdocker</a>
+                <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
+            </section>
+
+        </div>
+    </div>
+
+</div> <!-- end container -->
+
+
+<div class="container">
+    <footer id="footer" class="footer">
+        <div class="row">
+            <div class="span12">
+
+                Docker is a project by <a href="http://www.dotcloud.com">dotCloud</a>
+
+            </div>
+        </div>
+
+        <div class="row">
+            <div class="emptyspace" style="height: 40px">
+
+            </div>
+        </div>
+
+    </footer>
+</div>
+
+
+
+<!-- bootstrap javascipts -->
+<script src="_static/js/vendor/bootstrap.min.js" type="text/javascript"></script>
+
+<!-- Google analytics -->
+<script type="text/javascript">
+
+    var _gaq = _gaq || [];
+    _gaq.push(['_setAccount', 'UA-6096819-11']);
+    _gaq.push(['_setDomainName', 'docker.io']);
+    _gaq.push(['_setAllowLinker', true]);
+    _gaq.push(['_trackPageview']);
+
+    (function() {
+        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+    })();
+
+</script>
+
+
+</body>
+</html>

+ 1 - 0
docs/sources/index.rst

@@ -18,6 +18,7 @@ This documentation has the following resources:
    registry/index
    index/index
    builder/index
+   remote-api/index
    faq
 
 

+ 6 - 0
docs/sources/nginx.conf

@@ -0,0 +1,6 @@
+
+# rule to redirect original links created when hosted on github pages
+rewrite ^/documentation/(.*).html http://docs.docker.io/en/latest/$1/ permanent;
+
+# rewrite the stuff which was on the current page
+rewrite ^/gettingstarted.html$ /gettingstarted/ permanent;

+ 1005 - 0
docs/sources/remote-api/api.rst

@@ -0,0 +1,1005 @@
+=================
+Docker Remote API
+=================
+
+.. contents:: Table of Contents
+
+1. Brief introduction
+=====================
+
+- The Remote API is replacing rcli
+- Default port in the docker deamon is 4243 
+- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection in hijacked to transport stdout stdin and stderr
+
+2. Endpoints
+============
+
+2.1 Containers
+--------------
+
+List containers
+***************
+
+.. http:get:: /containers/ps
+
+	List containers
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   GET /containers/ps?trunc_cmd=0&all=1&only_ids=0&before=8dfafdbc3a40 HTTP/1.1
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/json
+	   
+	   [
+		{
+			"Id": "8dfafdbc3a40",
+			"Image": "base:latest",
+			"Command": "echo 1",
+			"Created": 1367854155,
+			"Status": "Exit 0"
+		},
+		{
+			"Id": "9cd87474be90",
+			"Image": "base:latest",
+			"Command": "echo 222222",
+			"Created": 1367854155,
+			"Status": "Exit 0"
+		},
+		{
+			"Id": "3176a2479c92",
+			"Image": "base:latest",
+			"Command": "echo 3333333333333333",
+			"Created": 1367854154,
+			"Status": "Exit 0"
+		},
+		{
+			"Id": "4cb07b47f9fb",
+			"Image": "base:latest",
+			"Command": "echo 444444444444444444444444444444444",
+			"Created": 1367854152,
+			"Status": "Exit 0"
+		}
+	   ]
+ 
+	:query only_ids: 1 or 0, Only display numeric IDs. Default 0
+	:query all: 1 or 0, Show all containers. Only running containers are shown by default
+	:query trunc_cmd: 1 or 0, Truncate output. Output is truncated by default  
+	:query limit: Show ``limit`` last created containers, include non-running ones.
+	:query since: Show only containers created since Id, include non-running ones.
+	:query before: Show only containers created before Id, include non-running ones.
+	:statuscode 200: no error
+	:statuscode 500: server error
+
+
+Create a container
+******************
+
+.. http:post:: /containers/create
+
+	Create a container
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   POST /containers/create HTTP/1.1
+	   Content-Type: application/json
+
+	   {
+		"Hostname":"",
+		"User":"",
+		"Memory":0,
+		"MemorySwap":0,
+		"AttachStdin":false,
+		"AttachStdout":true,
+		"AttachStderr":true,
+		"PortSpecs":null,
+		"Tty":false,
+		"OpenStdin":false,
+		"StdinOnce":false,
+		"Env":null,
+		"Cmd":[
+			"date"
+		],
+		"Dns":null,
+		"Image":"base",
+		"Volumes":{},
+		"VolumesFrom":""
+	   }
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 201 OK
+	   
+	   {
+		"Id":"e90e34656806"
+		"Warnings":[]
+	   }
+	
+	:jsonparam config: the container's configuration
+	:statuscode 201: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Inspect a container
+*******************
+
+.. http:get:: /containers/(id)/json
+
+	Return low-level information on the container ``id``
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   GET /containers/4fa6e0f0c678/json HTTP/1.1
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   {
+			"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2",
+			"Created": "2013-05-07T14:51:42.041847+02:00",
+			"Path": "date",
+			"Args": [],
+			"Config": {
+				"Hostname": "4fa6e0f0c678",
+				"User": "",
+				"Memory": 0,
+				"MemorySwap": 0,
+				"AttachStdin": false,
+				"AttachStdout": true,
+				"AttachStderr": true,
+				"PortSpecs": null,
+				"Tty": false,
+				"OpenStdin": false,
+				"StdinOnce": false,
+				"Env": null,
+				"Cmd": [
+					"date"
+				],
+				"Dns": null,
+				"Image": "base",
+				"Volumes": {},
+				"VolumesFrom": ""
+			},
+			"State": {
+				"Running": false,
+				"Pid": 0,
+				"ExitCode": 0,
+				"StartedAt": "2013-05-07T14:51:42.087658+02:01360",
+				"Ghost": false
+			},
+			"Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
+			"NetworkSettings": {
+				"IpAddress": "",
+				"IpPrefixLen": 0,
+				"Gateway": "",
+				"Bridge": "",
+				"PortMapping": null
+			},
+			"SysInitPath": "/home/kitty/go/src/github.com/dotcloud/docker/bin/docker",
+			"ResolvConfPath": "/etc/resolv.conf",
+			"Volumes": {}
+	   }
+
+	:statuscode 200: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Inspect changes on a container's filesystem
+*******************************************
+
+.. http:get:: /containers/(id)/changes
+
+	Inspect changes on container ``id`` 's filesystem
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   GET /containers/4fa6e0f0c678/changes HTTP/1.1
+
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/json
+	   
+	   [
+		{
+			"Path":"/dev",
+			"Kind":0
+		},
+		{
+			"Path":"/dev/kmsg",
+			"Kind":1
+		},
+		{
+			"Path":"/test",
+			"Kind":1
+		}
+	   ]
+
+	:statuscode 200: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Export a container
+******************
+
+.. http:get:: /containers/(id)/export
+
+	Export the contents of container ``id``
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   GET /containers/4fa6e0f0c678/export HTTP/1.1
+
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/octet-stream
+	   
+	   {{ STREAM }}
+
+	:statuscode 200: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Start a container
+*****************
+
+.. http:post:: /containers/(id)/start
+
+	Start the container ``id``
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   POST /containers/e90e34656806/start HTTP/1.1
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   	
+	:statuscode 200: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Stop a contaier
+***************
+
+.. http:post:: /containers/(id)/stop
+
+	Stop the container ``id``
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   POST /containers/e90e34656806/stop?t=5 HTTP/1.1
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 204 OK
+	   	
+	:query t: number of seconds to wait before killing the container
+	:statuscode 204: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Restart a container
+*******************
+
+.. http:post:: /containers/(id)/restart
+
+	Restart the container ``id``
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   POST /containers/e90e34656806/restart?t=5 HTTP/1.1
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 204 OK
+	   	
+	:query t: number of seconds to wait before killing the container
+	:statuscode 204: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Kill a container
+****************
+
+.. http:post:: /containers/(id)/kill
+
+	Kill the container ``id``
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   POST /containers/e90e34656806/kill HTTP/1.1
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 204 OK
+	   	
+	:statuscode 204: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Attach to a container
+*********************
+
+.. http:post:: /containers/(id)/attach
+
+	Stop the container ``id``
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   POST /containers/16253994b7c4/attach?logs=1&stream=0&stdout=1 HTTP/1.1
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/vnd.docker.raw-stream
+
+	   {{ STREAM }}
+	   	
+	:query logs: 1 or 0, return logs. Default 0
+	:query stream: 1 or 0, return stream. Default 0
+	:query stdin: 1 or 0, if stream=1, attach to stdin. Default 0
+	:query stdout: 1 or 0, if logs=1, return stdout log, if stream=1, attach to stdout. Default 0
+	:query stderr: 1 or 0, if logs=1, return stderr log, if stream=1, attach to stderr. Default 0
+	:statuscode 200: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Wait a container
+****************
+
+.. http:post:: /containers/(id)/wait
+
+	Block until container ``id`` stops, then returns the exit code
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   POST /containers/16253994b7c4/wait HTTP/1.1
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   {"StatusCode":0}
+	   	
+	:statuscode 200: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Remove a container
+*******************
+
+.. http:delete:: /containers/(id)
+
+	Remove the container ``id`` from the filesystem
+
+	**Example request**:
+
+        .. sourcecode:: http
+
+           DELETE /containers/16253994b7c4?v=1 HTTP/1.1
+
+        **Example response**:
+
+        .. sourcecode:: http
+
+	   HTTP/1.1 204 OK
+
+	:query v: 1 or 0, Remove the volumes associated to the container. Default 0
+        :statuscode 204: no error
+        :statuscode 404: no such container
+        :statuscode 500: server error
+
+
+2.2 Images
+----------
+
+List Images
+***********
+
+.. http:get:: /images/(format)
+
+	List images ``format`` could be json or viz (json default)
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   GET /images/json?all=0&only_ids=0 HTTP/1.1
+
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/json
+	   
+	   [
+		{
+			"Repository":"base",
+			"Tag":"ubuntu-12.10",
+			"Id":"b750fe79269d",
+			"Created":1364102658
+		},
+		{
+			"Repository":"base",
+			"Tag":"ubuntu-quantal",
+			"Id":"b750fe79269d",
+			"Created":1364102658
+		}
+	   ]
+
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   GET /images/viz HTTP/1.1
+
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: text/plain
+
+	   digraph docker {
+	   "d82cbacda43a" -> "074be284591f"
+	   "1496068ca813" -> "08306dc45919"
+	   "08306dc45919" -> "0e7893146ac2"
+	   "b750fe79269d" -> "1496068ca813"
+	   base -> "27cf78414709" [style=invis]
+	   "f71189fff3de" -> "9a33b36209ed"
+	   "27cf78414709" -> "b750fe79269d"
+	   "0e7893146ac2" -> "d6434d954665"
+	   "d6434d954665" -> "d82cbacda43a"
+	   base -> "e9aa60c60128" [style=invis]
+	   "074be284591f" -> "f71189fff3de"
+	   "b750fe79269d" [label="b750fe79269d\nbase",shape=box,fillcolor="paleturquoise",style="filled,rounded"];
+	   "e9aa60c60128" [label="e9aa60c60128\nbase2",shape=box,fillcolor="paleturquoise",style="filled,rounded"];
+	   "9a33b36209ed" [label="9a33b36209ed\ntest",shape=box,fillcolor="paleturquoise",style="filled,rounded"];
+	   base [style=invisible]
+	   }
+ 
+	:query only_ids: 1 or 0, Only display numeric IDs. Default 0
+	:query all: 1 or 0, Show all containers. Only running containers are shown by default
+	:statuscode 200: no error
+	:statuscode 500: server error
+
+
+Create an image
+***************
+
+.. http:post:: /images/create
+
+	Create an image, either by pull it from the registry or by importing it
+
+	**Example request**:
+
+        .. sourcecode:: http
+
+           POST /images/create?fromImage=base HTTP/1.1
+
+        **Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+	   Content-Type: application/vnd.docker.raw-stream
+
+	   {{ STREAM }}
+
+        :query fromImage: name of the image to pull
+	:query fromSrc: source to import, - means stdin
+        :query repo: repository
+	:query tag: tag
+	:query registry: the registry to pull from
+        :statuscode 200: no error
+        :statuscode 500: server error
+
+
+Insert a file in a image
+************************
+
+.. http:post:: /images/(name)/insert
+
+	Insert a file from ``url`` in the image ``name`` at ``path``
+
+	**Example request**:
+
+        .. sourcecode:: http
+
+           POST /images/test/insert?path=/usr&url=myurl HTTP/1.1
+
+	**Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+
+	   {{ STREAM }}
+
+	:statuscode 200: no error
+        :statuscode 500: server error
+
+
+Inspect an image
+****************
+
+.. http:get:: /images/(name)/json
+
+	Return low-level information on the image ``name``
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   GET /images/base/json HTTP/1.1
+
+	**Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   {
+		"id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
+		"parent":"27cf784147099545",
+		"created":"2013-03-23T22:24:18.818426-07:00",
+		"container":"3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0",
+		"container_config":
+			{
+				"Hostname":"",
+				"User":"",
+				"Memory":0,
+				"MemorySwap":0,
+				"AttachStdin":false,
+				"AttachStdout":false,
+				"AttachStderr":false,
+				"PortSpecs":null,
+				"Tty":true,
+				"OpenStdin":true,
+				"StdinOnce":false,
+				"Env":null,
+				"Cmd": ["/bin/bash"]
+				,"Dns":null,
+				"Image":"base",
+				"Volumes":null,
+				"VolumesFrom":""
+			}
+	   }
+
+	:statuscode 200: no error
+	:statuscode 404: no such image
+        :statuscode 500: server error
+
+
+Get the history of an image
+***************************
+
+.. http:get:: /images/(name)/history
+
+        Return the history of the image ``name``
+
+        **Example request**:
+
+        .. sourcecode:: http
+
+           GET /images/base/history HTTP/1.1
+
+        **Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   [
+		{
+			"Id":"b750fe79269d",
+			"Created":1364102658,
+			"CreatedBy":"/bin/bash"
+		},
+		{
+			"Id":"27cf78414709",
+			"Created":1364068391,
+			"CreatedBy":""
+		}
+	   ]
+
+        :statuscode 200: no error
+        :statuscode 404: no such image
+        :statuscode 500: server error
+
+
+Push an image on the registry
+*****************************
+
+.. http:post:: /images/(name)/push
+
+	Push the image ``name`` on the registry
+
+	 **Example request**:
+
+	 .. sourcecode:: http
+
+	    POST /images/test/push HTTP/1.1
+
+	 **Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+	   Content-Type: application/vnd.docker.raw-stream
+
+	   {{ STREAM }}
+
+	:query registry: the registry you wan to push, optional
+	:statuscode 200: no error
+        :statuscode 404: no such image
+        :statuscode 500: server error
+
+
+Tag an image into a repository
+******************************
+
+.. http:post:: /images/(name)/tag
+
+	Tag the image ``name`` into a repository
+
+        **Example request**:
+
+        .. sourcecode:: http
+			
+	   POST /images/test/tag?repo=myrepo&force=0 HTTP/1.1
+
+	**Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+
+	:query repo: The repository to tag in
+	:query force: 1 or 0, default 0
+	:statuscode 200: no error
+	:statuscode 404: no such image
+        :statuscode 500: server error
+
+
+Remove an image
+***************
+
+.. http:delete:: /images/(name)
+
+	Remove the image ``name`` from the filesystem 
+	
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   DELETE /images/test HTTP/1.1
+
+	**Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 204 OK
+
+	:statuscode 204: no error
+        :statuscode 404: no such image
+        :statuscode 500: server error
+
+
+Search images
+*************
+
+.. http:get:: /images/search
+
+	Search for an image in the docker index
+	
+	**Example request**:
+
+        .. sourcecode:: http
+
+           GET /images/search?term=sshd HTTP/1.1
+
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/json
+	   
+	   [
+		{
+			"Name":"cespare/sshd",
+			"Description":""
+		},
+		{
+			"Name":"johnfuller/sshd",
+			"Description":""
+		},
+		{
+			"Name":"dhrp/mongodb-sshd",
+			"Description":""
+		}
+	   ]
+
+	   :query term: term to search
+	   :statuscode 200: no error
+	   :statuscode 500: server error
+
+
+2.3 Misc
+--------
+
+Build an image from Dockerfile via stdin
+****************************************
+
+.. http:post:: /build
+
+	Build an image from Dockerfile via stdin
+
+	**Example request**:
+
+        .. sourcecode:: http
+
+           POST /build HTTP/1.1
+	   
+	   {{ STREAM }}
+
+	**Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+	   
+	   {{ STREAM }}
+
+	:statuscode 200: no error
+        :statuscode 500: server error
+
+
+Get default username and email
+******************************
+
+.. http:get:: /auth
+
+	Get the default username and email
+
+	**Example request**:
+
+        .. sourcecode:: http
+
+           GET /auth HTTP/1.1
+
+        **Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   {
+		"username":"hannibal",
+		"email":"hannibal@a-team.com"
+	   }
+
+        :statuscode 200: no error
+        :statuscode 500: server error
+
+
+Set auth configuration
+**********************
+
+.. http:post:: /auth
+
+        Get the default username and email
+
+        **Example request**:
+
+        .. sourcecode:: http
+
+           POST /auth HTTP/1.1
+	   Content-Type: application/json
+
+	   {
+		"username":"hannibal",
+		"password:"xxxx",
+		"email":"hannibal@a-team.com"
+	   }
+
+        **Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+
+        :statuscode 200: no error
+        :statuscode 204: no error
+        :statuscode 500: server error
+
+
+Display system-wide information
+*******************************
+
+.. http:get:: /info
+
+	Display system-wide information
+	
+	**Example request**:
+
+        .. sourcecode:: http
+
+           GET /info HTTP/1.1
+
+        **Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   {
+		"Containers":11,
+		"Version":"0.2.2",
+		"Images":16,
+		"GoVersion":"go1.0.3",
+		"Debug":false
+	   }
+
+        :statuscode 200: no error
+        :statuscode 500: server error
+
+
+Show the docker version information
+***********************************
+
+.. http:get:: /version
+
+	Show the docker version information
+
+	**Example request**:
+
+        .. sourcecode:: http
+
+           GET /version HTTP/1.1
+
+        **Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+	   Content-Type: application/json
+	   
+	   {
+		"Version":"0.2.2",
+		"GitCommit":"5a2a5cc+CHANGES",
+		"MemoryLimit":true,
+		"SwapLimit":false
+	   }
+
+        :statuscode 200: no error
+	:statuscode 500: server error
+
+
+Create a new image from a container's changes
+*********************************************
+
+.. http:post:: /commit
+
+	Create a new image from a container's changes
+
+	**Example request**:
+
+        .. sourcecode:: http
+
+           POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
+
+        **Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 201 OK
+	   Content-Type: application/vnd.docker.raw-stream
+
+           {"Id":"596069db4bf5"}
+
+	:query container: source container
+	:query repo: repository
+	:query tag: tag
+	:query m: commit message
+	:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
+	:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
+        :statuscode 201: no error
+	:statuscode 404: no such container
+        :statuscode 500: server error
+
+
+3. Going further
+================
+
+3.1 Inside 'docker run'
+-----------------------
+
+Here are the steps of 'docker run' :
+
+* Create the container
+* If the status code is 404, it means the image doesn't exists:
+        * Try to pull it
+        * Then retry to create the container
+* Start the container
+* If you are not in detached mode:
+        * Attach to the container, using logs=1 (to have stdout and stderr from the container's start) and stream=1
+* If in detached mode or only stdin is attached:
+	* Display the container's id
+
+
+3.2 Hijacking
+-------------
+
+In this first version of the API, some of the endpoints, like /attach, /pull or /push uses hijacking to transport stdin,
+stdout and stderr on the same socket. This might change in the future.

+ 15 - 0
docs/sources/remote-api/index.rst

@@ -0,0 +1,15 @@
+:title: docker Remote API documentation
+:description: Documentation for docker Remote API
+:keywords: docker, rest, api, http
+
+
+
+Remote API
+==========
+
+Contents:
+
+.. toctree::
+   :maxdepth: 2
+
+   api

+ 0 - 169
rcli/tcp.go

@@ -1,169 +0,0 @@
-package rcli
-
-import (
-	"bufio"
-	"bytes"
-	"encoding/json"
-	"fmt"
-	"io"
-	"io/ioutil"
-	"log"
-	"net"
-)
-
-// Note: the globals are here to avoid import cycle
-// FIXME: Handle debug levels mode?
-var DEBUG_FLAG bool = false
-var CLIENT_SOCKET io.Writer = nil
-
-type DockerTCPConn struct {
-	conn       *net.TCPConn
-	options    *DockerConnOptions
-	optionsBuf *[]byte
-	handshaked bool
-	client     bool
-}
-
-func NewDockerTCPConn(conn *net.TCPConn, client bool) *DockerTCPConn {
-	return &DockerTCPConn{
-		conn:    conn,
-		options: &DockerConnOptions{},
-		client:  client,
-	}
-}
-
-func (c *DockerTCPConn) SetOptionRawTerminal() {
-	c.options.RawTerminal = true
-}
-
-func (c *DockerTCPConn) GetOptions() *DockerConnOptions {
-	if c.client && !c.handshaked {
-		// Attempt to parse options encoded as a JSON dict and store
-		// the reminder of what we read from the socket in a buffer.
-		//
-		// bufio (and its ReadBytes method) would have been nice here,
-		// but if json.Unmarshal() fails (which will happen if we speak
-		// to a version of docker that doesn't send any option), then
-		// we can't put the data back in it for the next Read().
-		c.handshaked = true
-		buf := make([]byte, 4096)
-		if n, _ := c.conn.Read(buf); n > 0 {
-			buf = buf[:n]
-			if nl := bytes.IndexByte(buf, '\n'); nl != -1 {
-				if err := json.Unmarshal(buf[:nl], c.options); err == nil {
-					buf = buf[nl+1:]
-				}
-			}
-			c.optionsBuf = &buf
-		}
-	}
-
-	return c.options
-}
-
-func (c *DockerTCPConn) Read(b []byte) (int, error) {
-	if c.optionsBuf != nil {
-		// Consume what we buffered in GetOptions() first:
-		optionsBuf := *c.optionsBuf
-		optionsBuflen := len(optionsBuf)
-		copied := copy(b, optionsBuf)
-		if copied < optionsBuflen {
-			optionsBuf = optionsBuf[copied:]
-			c.optionsBuf = &optionsBuf
-			return copied, nil
-		}
-		c.optionsBuf = nil
-		return copied, nil
-	}
-	return c.conn.Read(b)
-}
-
-func (c *DockerTCPConn) Write(b []byte) (int, error) {
-	optionsLen := 0
-	if !c.client && !c.handshaked {
-		c.handshaked = true
-		options, _ := json.Marshal(c.options)
-		options = append(options, '\n')
-		if optionsLen, err := c.conn.Write(options); err != nil {
-			return optionsLen, err
-		}
-	}
-	n, err := c.conn.Write(b)
-	return n + optionsLen, err
-}
-
-func (c *DockerTCPConn) Flush() error {
-	_, err := c.Write([]byte{})
-	return err
-}
-
-func (c *DockerTCPConn) Close() error { return c.conn.Close() }
-
-func (c *DockerTCPConn) CloseWrite() error { return c.conn.CloseWrite() }
-
-func (c *DockerTCPConn) CloseRead() error { return c.conn.CloseRead() }
-
-// Connect to a remote endpoint using protocol `proto` and address `addr`,
-// issue a single call, and return the result.
-// `proto` may be "tcp", "unix", etc. See the `net` package for available protocols.
-func Call(proto, addr string, args ...string) (DockerConn, error) {
-	cmd, err := json.Marshal(args)
-	if err != nil {
-		return nil, err
-	}
-	conn, err := dialDocker(proto, addr)
-	if err != nil {
-		return nil, err
-	}
-	if _, err := fmt.Fprintln(conn, string(cmd)); err != nil {
-		return nil, err
-	}
-	return conn, nil
-}
-
-// Listen on `addr`, using protocol `proto`, for incoming rcli calls,
-// and pass them to `service`.
-func ListenAndServe(proto, addr string, service Service) error {
-	listener, err := net.Listen(proto, addr)
-	if err != nil {
-		return err
-	}
-	log.Printf("Listening for RCLI/%s on %s\n", proto, addr)
-	defer listener.Close()
-	for {
-		if conn, err := listener.Accept(); err != nil {
-			return err
-		} else {
-			conn, err := newDockerServerConn(conn)
-			if err != nil {
-				return err
-			}
-			go func(conn DockerConn) {
-				defer conn.Close()
-				if DEBUG_FLAG {
-					CLIENT_SOCKET = conn
-				}
-				if err := Serve(conn, service); err != nil {
-					log.Println("Error:", err.Error())
-					fmt.Fprintln(conn, "Error:", err.Error())
-				}
-			}(conn)
-		}
-	}
-	return nil
-}
-
-// Parse an rcli call on a new connection, and pass it to `service` if it
-// is valid.
-func Serve(conn DockerConn, 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
-}

+ 0 - 181
rcli/types.go

@@ -1,181 +0,0 @@
-package rcli
-
-// rcli (Remote Command-Line Interface) is a simple protocol for...
-// serving command-line interfaces remotely.
-//
-// rcli can be used over any transport capable of a) sending binary streams in
-// both directions, and b) capable of half-closing a connection. TCP and Unix sockets
-// are the usual suspects.
-
-import (
-	"flag"
-	"fmt"
-	"github.com/dotcloud/docker/term"
-	"io"
-	"log"
-	"net"
-	"os"
-	"reflect"
-	"strings"
-)
-
-type DockerConnOptions struct {
-	RawTerminal bool
-}
-
-type DockerConn interface {
-	io.ReadWriteCloser
-	CloseWrite() error
-	CloseRead() error
-	GetOptions() *DockerConnOptions
-	SetOptionRawTerminal()
-	Flush() error
-}
-
-type DockerLocalConn struct {
-	writer     io.WriteCloser
-	savedState *term.State
-}
-
-func NewDockerLocalConn(w io.WriteCloser) *DockerLocalConn {
-	return &DockerLocalConn{
-		writer: w,
-	}
-}
-
-func (c *DockerLocalConn) Read(b []byte) (int, error) {
-	return 0, fmt.Errorf("DockerLocalConn does not implement Read()")
-}
-
-func (c *DockerLocalConn) Write(b []byte) (int, error) { return c.writer.Write(b) }
-
-func (c *DockerLocalConn) Close() error {
-	if c.savedState != nil {
-		RestoreTerminal(c.savedState)
-		c.savedState = nil
-	}
-	return c.writer.Close()
-}
-
-func (c *DockerLocalConn) Flush() error { return nil }
-
-func (c *DockerLocalConn) CloseWrite() error { return nil }
-
-func (c *DockerLocalConn) CloseRead() error { return nil }
-
-func (c *DockerLocalConn) GetOptions() *DockerConnOptions { return nil }
-
-func (c *DockerLocalConn) SetOptionRawTerminal() {
-	if state, err := SetRawTerminal(); err != nil {
-		if os.Getenv("DEBUG") != "" {
-			log.Printf("Can't set the terminal in raw mode: %s", err)
-		}
-	} else {
-		c.savedState = state
-	}
-}
-
-var UnknownDockerProto = fmt.Errorf("Only TCP is actually supported by Docker at the moment")
-
-func dialDocker(proto string, addr string) (DockerConn, error) {
-	conn, err := net.Dial(proto, addr)
-	if err != nil {
-		return nil, err
-	}
-	switch i := conn.(type) {
-	case *net.TCPConn:
-		return NewDockerTCPConn(i, true), nil
-	}
-	return nil, UnknownDockerProto
-}
-
-func newDockerFromConn(conn net.Conn, client bool) (DockerConn, error) {
-	switch i := conn.(type) {
-	case *net.TCPConn:
-		return NewDockerTCPConn(i, client), nil
-	}
-	return nil, UnknownDockerProto
-}
-
-func newDockerServerConn(conn net.Conn) (DockerConn, error) {
-	return newDockerFromConn(conn, false)
-}
-
-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
-
-// FIXME: For reverse compatibility
-func call(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error {
-	return LocalCall(service, stdin, stdout, args...)
-}
-
-func LocalCall(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error {
-	if len(args) == 0 {
-		args = []string{"help"}
-	}
-	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, flags.Args()[1:]...)
-	}
-	return fmt.Errorf("No such command: %s", 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 fmt.Errorf("No such command: %s", 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
-}

+ 0 - 27
rcli/utils.go

@@ -1,27 +0,0 @@
-package rcli
-
-import (
-	"github.com/dotcloud/docker/term"
-	"os"
-	"os/signal"
-)
-
-//FIXME: move these function to utils.go (in rcli to avoid import loop)
-func SetRawTerminal() (*term.State, error) {
-	oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
-	if err != nil {
-		return nil, err
-	}
-	c := make(chan os.Signal, 1)
-	signal.Notify(c, os.Interrupt)
-	go func() {
-		_ = <-c
-		term.Restore(int(os.Stdin.Fd()), oldState)
-		os.Exit(0)
-	}()
-	return oldState, err
-}
-
-func RestoreTerminal(state *term.State) {
-	term.Restore(int(os.Stdin.Fd()), state)
-}

+ 1 - 1
runtime.go

@@ -187,7 +187,7 @@ func (runtime *Runtime) Destroy(container *Container) error {
 		return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.Id)
 	}
 
-	if err := container.Stop(10); err != nil {
+	if err := container.Stop(3); err != nil {
 		return err
 	}
 	if mounted, err := container.Mounted(); err != nil {

+ 2 - 2
runtime_test.go

@@ -2,7 +2,6 @@ package docker
 
 import (
 	"fmt"
-	"github.com/dotcloud/docker/rcli"
 	"io"
 	"io/ioutil"
 	"net"
@@ -67,12 +66,13 @@ func init() {
 	if err != nil {
 		panic(err)
 	}
+
 	// Create the "Server"
 	srv := &Server{
 		runtime: runtime,
 	}
 	// Retrieve the Image
-	if err := srv.CmdPull(os.Stdin, rcli.NewDockerLocalConn(os.Stdout), unitTestImageName); err != nil {
+	if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout); err != nil {
 		panic(err)
 	}
 }

+ 591 - 0
server.go

@@ -0,0 +1,591 @@
+package docker
+
+import (
+	"fmt"
+	"io"
+	"log"
+	"net/http"
+	"net/url"
+	"os"
+	"runtime"
+	"strings"
+)
+
+func (srv *Server) DockerVersion() ApiVersion {
+	return ApiVersion{VERSION, GIT_COMMIT, srv.runtime.capabilities.MemoryLimit, srv.runtime.capabilities.SwapLimit}
+}
+
+func (srv *Server) ContainerKill(name string) error {
+	if container := srv.runtime.Get(name); container != nil {
+		if err := container.Kill(); err != nil {
+			return fmt.Errorf("Error restarting container %s: %s", name, err.Error())
+		}
+	} else {
+		return fmt.Errorf("No such container: %s", name)
+	}
+	return nil
+}
+
+func (srv *Server) ContainerExport(name string, out io.Writer) error {
+	if container := srv.runtime.Get(name); container != nil {
+
+		data, err := container.Export()
+		if err != nil {
+			return err
+		}
+
+		// Stream the entire contents of the container (basically a volatile snapshot)
+		if _, err := io.Copy(out, data); err != nil {
+			return err
+		}
+		return nil
+	}
+	return fmt.Errorf("No such container: %s", name)
+}
+
+func (srv *Server) ImagesSearch(term string) ([]ApiSearch, error) {
+	results, err := srv.runtime.graph.SearchRepositories(nil, term)
+	if err != nil {
+		return nil, err
+	}
+
+	var outs []ApiSearch
+	for _, repo := range results.Results {
+		var out ApiSearch
+		out.Description = repo["description"]
+		if len(out.Description) > 45 {
+			out.Description = Trunc(out.Description, 42) + "..."
+		}
+		out.Name = repo["name"]
+		outs = append(outs, out)
+	}
+	return outs, nil
+}
+
+func (srv *Server) ImageInsert(name, url, path string, out io.Writer) error {
+	img, err := srv.runtime.repositories.LookupImage(name)
+	if err != nil {
+		return err
+	}
+
+	file, err := Download(url, out)
+	if err != nil {
+		return err
+	}
+	defer file.Body.Close()
+
+	config, _, err := ParseRun([]string{img.Id, "echo", "insert", url, path}, srv.runtime.capabilities)
+	if err != nil {
+		return err
+	}
+
+	b := NewBuilder(srv.runtime)
+	c, err := b.Create(config)
+	if err != nil {
+		return err
+	}
+
+	if err := c.Inject(ProgressReader(file.Body, int(file.ContentLength), out, "Downloading %v/%v (%v)"), path); err != nil {
+		return err
+	}
+	// FIXME: Handle custom repo, tag comment, author
+	img, err = b.Commit(c, "", "", img.Comment, img.Author, nil)
+	if err != nil {
+		return err
+	}
+	fmt.Fprintf(out, "%s\n", img.Id)
+	return nil
+}
+
+func (srv *Server) ImagesViz(out io.Writer) error {
+	images, _ := srv.runtime.graph.All()
+	if images == nil {
+		return nil
+	}
+	out.Write([]byte("digraph docker {\n"))
+
+	var (
+		parentImage *Image
+		err         error
+	)
+	for _, image := range images {
+		parentImage, err = image.GetParent()
+		if err != nil {
+			return fmt.Errorf("Error while getting parent image: %v", err)
+		}
+		if parentImage != nil {
+			out.Write([]byte(" \"" + parentImage.ShortId() + "\" -> \"" + image.ShortId() + "\"\n"))
+		} else {
+			out.Write([]byte(" base -> \"" + image.ShortId() + "\" [style=invis]\n"))
+		}
+	}
+
+	reporefs := make(map[string][]string)
+
+	for name, repository := range srv.runtime.repositories.Repositories {
+		for tag, id := range repository {
+			reporefs[TruncateId(id)] = append(reporefs[TruncateId(id)], fmt.Sprintf("%s:%s", name, tag))
+		}
+	}
+
+	for id, repos := range reporefs {
+		out.Write([]byte(" \"" + id + "\" [label=\"" + id + "\\n" + strings.Join(repos, "\\n") + "\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n"))
+	}
+	out.Write([]byte(" base [style=invisible]\n}\n"))
+	return nil
+}
+
+func (srv *Server) Images(all, only_ids bool, filter string) ([]ApiImages, error) {
+	var allImages map[string]*Image
+	var err error
+	if all {
+		allImages, err = srv.runtime.graph.Map()
+	} else {
+		allImages, err = srv.runtime.graph.Heads()
+	}
+	if err != nil {
+		return nil, err
+	}
+	var outs []ApiImages = []ApiImages{} //produce [] when empty instead of 'null'
+	for name, repository := range srv.runtime.repositories.Repositories {
+		if filter != "" && name != filter {
+			continue
+		}
+		for tag, id := range repository {
+			var out ApiImages
+			image, err := srv.runtime.graph.Get(id)
+			if err != nil {
+				log.Printf("Warning: couldn't load %s from %s/%s: %s", id, name, tag, err)
+				continue
+			}
+			delete(allImages, id)
+			if !only_ids {
+				out.Repository = name
+				out.Tag = tag
+				out.Id = TruncateId(id)
+				out.Created = image.Created.Unix()
+			} else {
+				out.Id = image.ShortId()
+			}
+			outs = append(outs, out)
+		}
+	}
+	// Display images which aren't part of a
+	if filter == "" {
+		for id, image := range allImages {
+			var out ApiImages
+			if !only_ids {
+				out.Repository = "<none>"
+				out.Tag = "<none>"
+				out.Id = TruncateId(id)
+				out.Created = image.Created.Unix()
+			} else {
+				out.Id = image.ShortId()
+			}
+			outs = append(outs, out)
+		}
+	}
+	return outs, nil
+}
+
+func (srv *Server) DockerInfo() ApiInfo {
+	images, _ := srv.runtime.graph.All()
+	var imgcount int
+	if images == nil {
+		imgcount = 0
+	} else {
+		imgcount = len(images)
+	}
+	var out ApiInfo
+	out.Containers = len(srv.runtime.List())
+	out.Version = VERSION
+	out.Images = imgcount
+	out.GoVersion = runtime.Version()
+	if os.Getenv("DEBUG") != "" {
+		out.Debug = true
+		out.NFd = getTotalUsedFds()
+		out.NGoroutines = runtime.NumGoroutine()
+	}
+	return out
+}
+
+func (srv *Server) ImageHistory(name string) ([]ApiHistory, error) {
+	image, err := srv.runtime.repositories.LookupImage(name)
+	if err != nil {
+		return nil, err
+	}
+
+	var outs []ApiHistory = []ApiHistory{} //produce [] when empty instead of 'null'
+	err = image.WalkHistory(func(img *Image) error {
+		var out ApiHistory
+		out.Id = srv.runtime.repositories.ImageName(img.ShortId())
+		out.Created = img.Created.Unix()
+		out.CreatedBy = strings.Join(img.ContainerConfig.Cmd, " ")
+		outs = append(outs, out)
+		return nil
+	})
+	return outs, nil
+
+}
+
+func (srv *Server) ContainerChanges(name string) ([]Change, error) {
+	if container := srv.runtime.Get(name); container != nil {
+		return container.Changes()
+	}
+	return nil, fmt.Errorf("No such container: %s", name)
+}
+
+func (srv *Server) Containers(all, trunc_cmd, only_ids bool, n int, since, before string) []ApiContainers {
+	var foundBefore bool
+	var displayed int
+	retContainers := []ApiContainers{}
+
+	for _, container := range srv.runtime.List() {
+		if !container.State.Running && !all && n == -1 && since == "" && before == "" {
+			continue
+		}
+		if before != "" {
+			if container.ShortId() == before {
+				foundBefore = true
+				continue
+			}
+			if !foundBefore {
+				continue
+			}
+		}
+		if displayed == n {
+			break
+		}
+		if container.ShortId() == since {
+			break
+		}
+		displayed++
+
+		c := ApiContainers{
+			Id: container.ShortId(),
+		}
+
+		if !only_ids {
+			command := fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " "))
+			if trunc_cmd {
+				command = Trunc(command, 20)
+			}
+			c.Image = srv.runtime.repositories.ImageName(container.Image)
+			c.Command = command
+			c.Created = container.Created.Unix()
+			c.Status = container.State.String()
+			c.Ports = container.NetworkSettings.PortMappingHuman()
+		}
+		retContainers = append(retContainers, c)
+	}
+	return retContainers
+}
+
+func (srv *Server) ContainerCommit(name, repo, tag, author, comment string, config *Config) (string, error) {
+	container := srv.runtime.Get(name)
+	if container == nil {
+		return "", fmt.Errorf("No such container: %s", name)
+	}
+	img, err := NewBuilder(srv.runtime).Commit(container, repo, tag, comment, author, config)
+	if err != nil {
+		return "", err
+	}
+	return img.ShortId(), err
+}
+
+func (srv *Server) ContainerTag(name, repo, tag string, force bool) error {
+	if err := srv.runtime.repositories.Set(repo, tag, name, force); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (srv *Server) ImagePull(name, tag, registry string, out io.Writer) error {
+	if registry != "" {
+		if err := srv.runtime.graph.PullImage(out, name, registry, nil); err != nil {
+			return err
+		}
+		return nil
+	}
+	if err := srv.runtime.graph.PullRepository(out, name, tag, srv.runtime.repositories, srv.runtime.authConfig); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (srv *Server) ImagePush(name, registry string, out io.Writer) error {
+	img, err := srv.runtime.graph.Get(name)
+	if err != nil {
+		Debugf("The push refers to a repository [%s] (len: %d)\n", name, len(srv.runtime.repositories.Repositories[name]))
+		// If it fails, try to get the repository
+		if localRepo, exists := srv.runtime.repositories.Repositories[name]; exists {
+			if err := srv.runtime.graph.PushRepository(out, name, localRepo, srv.runtime.authConfig); err != nil {
+				return err
+			}
+			return nil
+		}
+
+		return err
+	}
+	err = srv.runtime.graph.PushImage(out, img, registry, nil)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Writer) error {
+	var archive io.Reader
+	var resp *http.Response
+
+	if src == "-" {
+		archive = in
+	} else {
+		u, err := url.Parse(src)
+		if err != nil {
+			fmt.Fprintf(out, "Error: %s\n", err)
+		}
+		if u.Scheme == "" {
+			u.Scheme = "http"
+			u.Host = src
+			u.Path = ""
+		}
+		fmt.Fprintln(out, "Downloading from", u)
+		// Download with curl (pretty progress bar)
+		// If curl is not available, fallback to http.Get()
+		resp, err = Download(u.String(), out)
+		if err != nil {
+			return err
+		}
+		archive = ProgressReader(resp.Body, int(resp.ContentLength), out, "Importing %v/%v (%v)")
+	}
+	img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil)
+	if err != nil {
+		return err
+	}
+	// Optionally register the image at REPO/TAG
+	if repo != "" {
+		if err := srv.runtime.repositories.Set(repo, tag, img.Id, true); err != nil {
+			return err
+		}
+	}
+	fmt.Fprintln(out, img.ShortId())
+	return nil
+}
+
+func (srv *Server) ContainerCreate(config *Config) (string, error) {
+
+	if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
+		config.Memory = 0
+	}
+
+	if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit {
+		config.MemorySwap = -1
+	}
+	b := NewBuilder(srv.runtime)
+	container, err := b.Create(config)
+	if err != nil {
+		if srv.runtime.graph.IsNotExist(err) {
+			return "", fmt.Errorf("No such image: %s", config.Image)
+		}
+		return "", err
+	}
+	return container.ShortId(), nil
+}
+
+func (srv *Server) ImageCreateFromFile(dockerfile io.Reader, out io.Writer) error {
+	img, err := NewBuilder(srv.runtime).Build(dockerfile, out)
+	if err != nil {
+		return err
+	}
+	fmt.Fprintf(out, "%s\n", img.ShortId())
+	return nil
+}
+
+func (srv *Server) ContainerRestart(name string, t int) error {
+	if container := srv.runtime.Get(name); container != nil {
+		if err := container.Restart(t); err != nil {
+			return fmt.Errorf("Error restarting container %s: %s", name, err.Error())
+		}
+	} else {
+		return fmt.Errorf("No such container: %s", name)
+	}
+	return nil
+}
+
+func (srv *Server) ContainerDestroy(name string, removeVolume bool) error {
+
+	if container := srv.runtime.Get(name); container != nil {
+		volumes := make(map[string]struct{})
+		// Store all the deleted containers volumes
+		for _, volumeId := range container.Volumes {
+			volumes[volumeId] = struct{}{}
+		}
+		if err := srv.runtime.Destroy(container); err != nil {
+			return fmt.Errorf("Error destroying container %s: %s", name, err.Error())
+		}
+
+		if removeVolume {
+			// Retrieve all volumes from all remaining containers
+			usedVolumes := make(map[string]*Container)
+			for _, container := range srv.runtime.List() {
+				for _, containerVolumeId := range container.Volumes {
+					usedVolumes[containerVolumeId] = container
+				}
+			}
+
+			for volumeId := range volumes {
+				// If the requested volu
+				if c, exists := usedVolumes[volumeId]; exists {
+					log.Printf("The volume %s is used by the container %s. Impossible to remove it. Skipping.\n", volumeId, c.Id)
+					continue
+				}
+				if err := srv.runtime.volumes.Delete(volumeId); err != nil {
+					return err
+				}
+			}
+		}
+	} else {
+		return fmt.Errorf("No such container: %s", name)
+	}
+	return nil
+}
+
+func (srv *Server) ImageDelete(name string) error {
+	img, err := srv.runtime.repositories.LookupImage(name)
+	if err != nil {
+		return fmt.Errorf("No such image: %s", name)
+	} else {
+		if err := srv.runtime.graph.Delete(img.Id); err != nil {
+			return fmt.Errorf("Error deleting image %s: %s", name, err.Error())
+		}
+	}
+	return nil
+}
+
+func (srv *Server) ContainerStart(name string) error {
+	if container := srv.runtime.Get(name); container != nil {
+		if err := container.Start(); err != nil {
+			return fmt.Errorf("Error starting container %s: %s", name, err.Error())
+		}
+	} else {
+		return fmt.Errorf("No such container: %s", name)
+	}
+	return nil
+}
+
+func (srv *Server) ContainerStop(name string, t int) error {
+	if container := srv.runtime.Get(name); container != nil {
+		if err := container.Stop(t); err != nil {
+			return fmt.Errorf("Error stopping container %s: %s", name, err.Error())
+		}
+	} else {
+		return fmt.Errorf("No such container: %s", name)
+	}
+	return nil
+}
+
+func (srv *Server) ContainerWait(name string) (int, error) {
+	if container := srv.runtime.Get(name); container != nil {
+		return container.Wait(), nil
+	}
+	return 0, fmt.Errorf("No such container: %s", name)
+}
+
+func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, stderr bool, in io.ReadCloser, out io.Writer) error {
+	container := srv.runtime.Get(name)
+	if container == nil {
+		return fmt.Errorf("No such container: %s", name)
+	}
+
+	//logs
+	if logs {
+		if stdout {
+			cLog, err := container.ReadLog("stdout")
+			if err != nil {
+				Debugf(err.Error())
+			} else if _, err := io.Copy(out, cLog); err != nil {
+				Debugf(err.Error())
+			}
+		}
+		if stderr {
+			cLog, err := container.ReadLog("stderr")
+			if err != nil {
+				Debugf(err.Error())
+			} else if _, err := io.Copy(out, cLog); err != nil {
+				Debugf(err.Error())
+			}
+		}
+	}
+
+	//stream
+	if stream {
+		if container.State.Ghost {
+			return fmt.Errorf("Impossible to attach to a ghost container")
+		}
+
+		var (
+			cStdin           io.ReadCloser
+			cStdout, cStderr io.Writer
+			cStdinCloser     io.Closer
+		)
+
+		if stdin {
+			r, w := io.Pipe()
+			go func() {
+				defer w.Close()
+				defer Debugf("Closing buffered stdin pipe")
+				io.Copy(w, in)
+			}()
+			cStdin = r
+			cStdinCloser = in
+		}
+		if stdout {
+			cStdout = out
+		}
+		if stderr {
+			cStderr = out
+		}
+
+		<-container.Attach(cStdin, cStdinCloser, cStdout, cStderr)
+
+		// If we are in stdinonce mode, wait for the process to end
+		// otherwise, simply return
+		if container.Config.StdinOnce && !container.Config.Tty {
+			container.Wait()
+		}
+	}
+	return nil
+}
+
+func (srv *Server) ContainerInspect(name string) (*Container, error) {
+	if container := srv.runtime.Get(name); container != nil {
+		return container, nil
+	}
+	return nil, fmt.Errorf("No such container: %s", name)
+}
+
+func (srv *Server) ImageInspect(name string) (*Image, error) {
+	if image, err := srv.runtime.repositories.LookupImage(name); err == nil && image != nil {
+		return image, nil
+	}
+	return nil, fmt.Errorf("No such image: %s", name)
+}
+
+func NewServer(autoRestart bool) (*Server, error) {
+	if runtime.GOARCH != "amd64" {
+		log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH)
+	}
+	runtime, err := NewRuntime(autoRestart)
+	if err != nil {
+		return nil, err
+	}
+	srv := &Server{
+		runtime: runtime,
+	}
+	return srv, nil
+}
+
+type Server struct {
+	runtime *Runtime
+}

+ 96 - 0
server_test.go

@@ -0,0 +1,96 @@
+package docker
+
+import (
+	"testing"
+)
+
+func TestCreateRm(t *testing.T) {
+	runtime, err := newTestRuntime()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer nuke(runtime)
+
+	srv := &Server{runtime: runtime}
+
+	config, _, err := ParseRun([]string{GetTestImage(runtime).Id, "echo test"}, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	id, err := srv.ContainerCreate(config)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if len(runtime.List()) != 1 {
+		t.Errorf("Expected 1 container, %v found", len(runtime.List()))
+	}
+
+	if err = srv.ContainerDestroy(id, true); err != nil {
+		t.Fatal(err)
+	}
+
+	if len(runtime.List()) != 0 {
+		t.Errorf("Expected 0 container, %v found", len(runtime.List()))
+	}
+
+}
+
+func TestCreateStartRestartStopStartKillRm(t *testing.T) {
+	runtime, err := newTestRuntime()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer nuke(runtime)
+
+	srv := &Server{runtime: runtime}
+
+	config, _, err := ParseRun([]string{GetTestImage(runtime).Id, "/bin/cat"}, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	id, err := srv.ContainerCreate(config)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if len(runtime.List()) != 1 {
+		t.Errorf("Expected 1 container, %v found", len(runtime.List()))
+	}
+
+	err = srv.ContainerStart(id)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	err = srv.ContainerRestart(id, 1)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	err = srv.ContainerStop(id, 1)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	err = srv.ContainerStart(id)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	err = srv.ContainerKill(id)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if err = srv.ContainerDestroy(id, true); err != nil {
+		t.Fatal(err)
+	}
+
+	if len(runtime.List()) != 0 {
+		t.Errorf("Expected 0 container, %v found", len(runtime.List()))
+	}
+
+}

+ 21 - 4
utils.go

@@ -6,13 +6,14 @@ import (
 	"encoding/hex"
 	"errors"
 	"fmt"
-	"github.com/dotcloud/docker/rcli"
+	"github.com/dotcloud/docker/term"
 	"index/suffixarray"
 	"io"
 	"io/ioutil"
 	"net/http"
 	"os"
 	"os/exec"
+	"os/signal"
 	"path/filepath"
 	"runtime"
 	"strings"
@@ -58,9 +59,6 @@ func Debugf(format string, a ...interface{}) {
 		}
 
 		fmt.Fprintf(os.Stderr, fmt.Sprintf("[debug] %s:%d %s\n", file, line, format), a...)
-		if rcli.CLIENT_SOCKET != nil {
-			fmt.Fprintf(rcli.CLIENT_SOCKET, fmt.Sprintf("[debug] %s:%d %s\n", file, line, format), a...)
-		}
 	}
 }
 
@@ -404,6 +402,25 @@ func CopyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error)
 	return written, err
 }
 
+func SetRawTerminal() (*term.State, error) {
+	oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
+	if err != nil {
+		return nil, err
+	}
+	c := make(chan os.Signal, 1)
+	signal.Notify(c, os.Interrupt)
+	go func() {
+		_ = <-c
+		term.Restore(int(os.Stdin.Fd()), oldState)
+		os.Exit(0)
+	}()
+	return oldState, err
+}
+
+func RestoreTerminal(state *term.State) {
+	term.Restore(int(os.Stdin.Fd()), state)
+}
+
 func HashData(src io.Reader) (string, error) {
 	h := sha256.New()
 	if _, err := io.Copy(h, src); err != nil {

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů