소스 검색

Merge remote-tracking branch 'dotcloud/master' into clean-reshuffle

Thatcher Peskens 12 년 전
부모
커밋
097531d853
7개의 변경된 파일532개의 추가작업 그리고 478개의 파일을 삭제
  1. 8 8
      README.md
  2. 151 137
      api.go
  3. 263 288
      api_test.go
  4. 8 0
      commands.go
  5. 95 43
      docs/sources/use/builder.rst
  6. 1 1
      registry.go
  7. 6 1
      server.go

+ 8 - 8
README.md

@@ -77,14 +77,14 @@ commands, the *order* in which the commands are executed expresses *dependencies
 Here's a typical docker build process:
 
 ```bash
-from	ubuntu:12.10
-run	apt-get update
-run	apt-get install python
-run	apt-get install python-pip
-run	pip install django
-run	apt-get install curl
-run	curl http://github.com/shykes/helloflask/helloflask/master.tar.gz | tar -zxv
-run	cd master && pip install -r requirements.txt
+from ubuntu:12.10
+run apt-get update
+run DEBIAN_FRONTEND=noninteractive apt-get install -q -y python
+run DEBIAN_FRONTEND=noninteractive apt-get install -q -y python-pip
+run pip install django
+run DEBIAN_FRONTEND=noninteractive apt-get install -q -y curl
+run curl -L https://github.com/shykes/helloflask/archive/master.tar.gz | tar -xzv
+run cd helloflask-master && pip install -r requirements.txt
 ```
 
 Note that Docker doesn't care *how* dependencies are built - as long as they can be built by running a unix command in a container.

+ 151 - 137
api.go

@@ -39,22 +39,28 @@ func httpError(w http.ResponseWriter, err error) {
 	}
 }
 
-func getAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func writeJson(w http.ResponseWriter, b []byte) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Write(b)
+}
+
+func getAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) 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 err
 	}
-	return b, nil
+	writeJson(w, b)
+	return nil
 }
 
-func postAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func postAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	config := &auth.AuthConfig{}
 	if err := json.NewDecoder(r.Body).Decode(config); err != nil {
-		return nil, err
+		return err
 	}
 
 	if config.Username == srv.runtime.authConfig.Username {
@@ -64,7 +70,7 @@ func postAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[stri
 	newAuthConfig := auth.NewAuthConfig(config.Username, config.Password, config.Email, srv.runtime.root)
 	status, err := auth.Login(newAuthConfig)
 	if err != nil {
-		return nil, err
+		return err
 	} else {
 		srv.runtime.graph.getHttpClient().Jar = cookiejar.NewCookieJar()
 		srv.runtime.authConfig = newAuthConfig
@@ -72,51 +78,53 @@ func postAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[stri
 	if status != "" {
 		b, err := json.Marshal(&ApiAuth{Status: status})
 		if err != nil {
-			return nil, err
+			return err
 		}
-		return b, nil
+		writeJson(w, b)
+		return nil
 	}
 	w.WriteHeader(http.StatusNoContent)
-	return nil, nil
+	return nil
 }
 
-func getVersion(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func getVersion(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	m := srv.DockerVersion()
 	b, err := json.Marshal(m)
 	if err != nil {
-		return nil, err
+		return err
 	}
-	return b, nil
+	writeJson(w, b)
+	return nil
 }
 
-func postContainersKill(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func postContainersKill(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if vars == nil {
-		return nil, fmt.Errorf("Missing parameter")
+		return fmt.Errorf("Missing parameter")
 	}
 	name := vars["name"]
 	if err := srv.ContainerKill(name); err != nil {
-		return nil, err
+		return err
 	}
 	w.WriteHeader(http.StatusNoContent)
-	return nil, nil
+	return nil
 }
 
-func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if vars == nil {
-		return nil, fmt.Errorf("Missing parameter")
+		return fmt.Errorf("Missing parameter")
 	}
 	name := vars["name"]
 
 	if err := srv.ContainerExport(name, w); err != nil {
 		Debugf("%s", err.Error())
-		//return nil, err
+		return err
 	}
-	return nil, nil
+	return nil
 }
 
-func getImagesJson(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func getImagesJson(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := parseForm(r); err != nil {
-		return nil, err
+		return err
 	}
 
 	all := r.Form.Get("all") == "1"
@@ -125,66 +133,70 @@ func getImagesJson(srv *Server, w http.ResponseWriter, r *http.Request, vars map
 
 	outs, err := srv.Images(all, only_ids, filter)
 	if err != nil {
-		return nil, err
+		return err
 	}
 	b, err := json.Marshal(outs)
 	if err != nil {
-		return nil, err
+		return err
 	}
-	return b, nil
+	writeJson(w, b)
+	return nil
 }
 
-func getImagesViz(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func getImagesViz(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := srv.ImagesViz(w); err != nil {
-		return nil, err
+		return err
 	}
-	return nil, nil
+	return nil
 }
 
-func getInfo(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func getInfo(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	out := srv.DockerInfo()
 	b, err := json.Marshal(out)
 	if err != nil {
-		return nil, err
+		return err
 	}
-	return b, nil
+	writeJson(w, b)
+	return nil
 }
 
-func getImagesHistory(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func getImagesHistory(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if vars == nil {
-		return nil, fmt.Errorf("Missing parameter")
+		return fmt.Errorf("Missing parameter")
 	}
 	name := vars["name"]
 	outs, err := srv.ImageHistory(name)
 	if err != nil {
-		return nil, err
+		return err
 	}
 	b, err := json.Marshal(outs)
 	if err != nil {
-		return nil, err
+		return err
 	}
-	return b, nil
+	writeJson(w, b)
+	return nil
 }
 
-func getContainersChanges(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func getContainersChanges(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if vars == nil {
-		return nil, fmt.Errorf("Missing parameter")
+		return fmt.Errorf("Missing parameter")
 	}
 	name := vars["name"]
 	changesStr, err := srv.ContainerChanges(name)
 	if err != nil {
-		return nil, err
+		return err
 	}
 	b, err := json.Marshal(changesStr)
 	if err != nil {
-		return nil, err
+		return err
 	}
-	return b, nil
+	writeJson(w, b)
+	return nil
 }
 
-func getContainersPs(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func getContainersPs(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := parseForm(r); err != nil {
-		return nil, err
+		return err
 	}
 	all := r.Form.Get("all") == "1"
 	trunc_cmd := r.Form.Get("trunc_cmd") != "0"
@@ -199,36 +211,37 @@ func getContainersPs(srv *Server, w http.ResponseWriter, r *http.Request, vars m
 	outs := srv.Containers(all, trunc_cmd, only_ids, n, since, before)
 	b, err := json.Marshal(outs)
 	if err != nil {
-		return nil, err
+		return err
 	}
-	return b, nil
+	writeJson(w, b)
+	return nil
 }
 
-func postImagesTag(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func postImagesTag(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := parseForm(r); err != nil {
-		return nil, err
+		return err
 	}
 	repo := r.Form.Get("repo")
 	tag := r.Form.Get("tag")
 	if vars == nil {
-		return nil, fmt.Errorf("Missing parameter")
+		return 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
+		return err
 	}
 	w.WriteHeader(http.StatusCreated)
-	return nil, nil
+	return nil
 }
 
-func postCommit(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func postCommit(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := parseForm(r); err != nil {
-		return nil, err
+		return err
 	}
-	var config Config
-	if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
+	config := &Config{}
+	if err := json.NewDecoder(r.Body).Decode(config); err != nil {
 		Debugf("%s", err.Error())
 	}
 	repo := r.Form.Get("repo")
@@ -236,22 +249,23 @@ func postCommit(srv *Server, w http.ResponseWriter, r *http.Request, vars map[st
 	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)
+	id, err := srv.ContainerCommit(container, repo, tag, author, comment, config)
 	if err != nil {
-		return nil, err
+		return err
 	}
-	b, err := json.Marshal(ApiId{id})
+	b, err := json.Marshal(&ApiId{id})
 	if err != nil {
-		return nil, err
+		return err
 	}
 	w.WriteHeader(http.StatusCreated)
-	return b, nil
+	writeJson(w, b)
+	return 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) {
+func postImagesCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := parseForm(r); err != nil {
-		return nil, err
+		return err
 	}
 
 	src := r.Form.Get("fromSrc")
@@ -261,7 +275,7 @@ func postImagesCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars
 
 	in, out, err := hijackServer(w)
 	if err != nil {
-		return nil, err
+		return err
 	}
 	defer in.Close()
 	fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
@@ -275,95 +289,96 @@ func postImagesCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars
 			fmt.Fprintf(out, "Error: %s\n", err)
 		}
 	}
-	return nil, nil
+	return nil
 }
 
-func getImagesSearch(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func getImagesSearch(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := parseForm(r); err != nil {
-		return nil, err
+		return err
 	}
 
 	term := r.Form.Get("term")
 	outs, err := srv.ImagesSearch(term)
 	if err != nil {
-		return nil, err
+		return err
 	}
 	b, err := json.Marshal(outs)
 	if err != nil {
-		return nil, err
+		return err
 	}
-	return b, nil
+	writeJson(w, b)
+	return nil
 }
 
-func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := parseForm(r); err != nil {
-		return nil, err
+		return err
 	}
 
 	url := r.Form.Get("url")
 	path := r.Form.Get("path")
 	if vars == nil {
-		return nil, fmt.Errorf("Missing parameter")
+		return fmt.Errorf("Missing parameter")
 	}
 	name := vars["name"]
 
 	in, out, err := hijackServer(w)
 	if err != nil {
-		return nil, err
+		return 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
+	return nil
 }
 
-func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := parseForm(r); err != nil {
-		return nil, err
+		return err
 	}
 
 	registry := r.Form.Get("registry")
 
 	if vars == nil {
-		return nil, fmt.Errorf("Missing parameter")
+		return fmt.Errorf("Missing parameter")
 	}
 	name := vars["name"]
 
 	in, out, err := hijackServer(w)
 	if err != nil {
-		return nil, err
+		return 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)
+		fmt.Fprintf(out, "Error: %s\n", err)
 	}
-	return nil, nil
+	return nil
 }
 
-func postBuild(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func postBuild(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	in, out, err := hijackServer(w)
 	if err != nil {
-		return nil, err
+		return 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
+	return nil
 }
 
-func postContainersCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func postContainersCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	config := &Config{}
 	if err := json.NewDecoder(r.Body).Decode(config); err != nil {
-		return nil, err
+		return err
 	}
 	id, err := srv.ContainerCreate(config)
 	if err != nil {
-		return nil, err
+		return err
 	}
 
 	out := &ApiRun{
@@ -379,75 +394,76 @@ func postContainersCreate(srv *Server, w http.ResponseWriter, r *http.Request, v
 	}
 	b, err := json.Marshal(out)
 	if err != nil {
-		return nil, err
+		return err
 	}
 	w.WriteHeader(http.StatusCreated)
-	return b, nil
+	writeJson(w, b)
+	return nil
 }
 
-func postContainersRestart(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func postContainersRestart(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := parseForm(r); err != nil {
-		return nil, err
+		return 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")
+		return fmt.Errorf("Missing parameter")
 	}
 	name := vars["name"]
 	if err := srv.ContainerRestart(name, t); err != nil {
-		return nil, err
+		return err
 	}
 	w.WriteHeader(http.StatusNoContent)
-	return nil, nil
+	return nil
 }
 
-func deleteContainers(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func deleteContainers(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := parseForm(r); err != nil {
-		return nil, err
+		return err
 	}
 	if vars == nil {
-		return nil, fmt.Errorf("Missing parameter")
+		return fmt.Errorf("Missing parameter")
 	}
 	name := vars["name"]
 	removeVolume := r.Form.Get("v") == "1"
 
 	if err := srv.ContainerDestroy(name, removeVolume); err != nil {
-		return nil, err
+		return err
 	}
 	w.WriteHeader(http.StatusNoContent)
-	return nil, nil
+	return nil
 }
 
-func deleteImages(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func deleteImages(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if vars == nil {
-		return nil, fmt.Errorf("Missing parameter")
+		return fmt.Errorf("Missing parameter")
 	}
 	name := vars["name"]
 	if err := srv.ImageDelete(name); err != nil {
-		return nil, err
+		return err
 	}
 	w.WriteHeader(http.StatusNoContent)
-	return nil, nil
+	return nil
 }
 
-func postContainersStart(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func postContainersStart(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if vars == nil {
-		return nil, fmt.Errorf("Missing parameter")
+		return fmt.Errorf("Missing parameter")
 	}
 	name := vars["name"]
 	if err := srv.ContainerStart(name); err != nil {
-		return nil, err
+		return err
 	}
 	w.WriteHeader(http.StatusNoContent)
-	return nil, nil
+	return nil
 }
 
-func postContainersStop(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func postContainersStop(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := parseForm(r); err != nil {
-		return nil, err
+		return err
 	}
 	t, err := strconv.Atoi(r.Form.Get("t"))
 	if err != nil || t < 0 {
@@ -455,36 +471,37 @@ func postContainersStop(srv *Server, w http.ResponseWriter, r *http.Request, var
 	}
 
 	if vars == nil {
-		return nil, fmt.Errorf("Missing parameter")
+		return fmt.Errorf("Missing parameter")
 	}
 	name := vars["name"]
 
 	if err := srv.ContainerStop(name, t); err != nil {
-		return nil, err
+		return err
 	}
 	w.WriteHeader(http.StatusNoContent)
-	return nil, nil
+	return nil
 }
 
-func postContainersWait(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func postContainersWait(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if vars == nil {
-		return nil, fmt.Errorf("Missing parameter")
+		return fmt.Errorf("Missing parameter")
 	}
 	name := vars["name"]
 	status, err := srv.ContainerWait(name)
 	if err != nil {
-		return nil, err
+		return err
 	}
 	b, err := json.Marshal(&ApiWait{StatusCode: status})
 	if err != nil {
-		return nil, err
+		return err
 	}
-	return b, nil
+	writeJson(w, b)
+	return nil
 }
 
-func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := parseForm(r); err != nil {
-		return nil, err
+		return err
 	}
 	logs := r.Form.Get("logs") == "1"
 	stream := r.Form.Get("stream") == "1"
@@ -492,13 +509,13 @@ func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request, v
 	stdout := r.Form.Get("stdout") == "1"
 	stderr := r.Form.Get("stderr") == "1"
 	if vars == nil {
-		return nil, fmt.Errorf("Missing parameter")
+		return fmt.Errorf("Missing parameter")
 	}
 	name := vars["name"]
 
 	in, out, err := hijackServer(w)
 	if err != nil {
-		return nil, err
+		return err
 	}
 	defer in.Close()
 
@@ -506,48 +523,50 @@ func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request, v
 	if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, in, out); err != nil {
 		fmt.Fprintf(out, "Error: %s\n", err)
 	}
-	return nil, nil
+	return nil
 }
 
-func getContainersByName(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func getContainersByName(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if vars == nil {
-		return nil, fmt.Errorf("Missing parameter")
+		return fmt.Errorf("Missing parameter")
 	}
 	name := vars["name"]
 
 	container, err := srv.ContainerInspect(name)
 	if err != nil {
-		return nil, err
+		return err
 	}
 	b, err := json.Marshal(container)
 	if err != nil {
-		return nil, err
+		return err
 	}
-	return b, nil
+	writeJson(w, b)
+	return nil
 }
 
-func getImagesByName(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) {
+func getImagesByName(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if vars == nil {
-		return nil, fmt.Errorf("Missing parameter")
+		return fmt.Errorf("Missing parameter")
 	}
 	name := vars["name"]
 
 	image, err := srv.ImageInspect(name)
 	if err != nil {
-		return nil, err
+		return err
 	}
 	b, err := json.Marshal(image)
 	if err != nil {
-		return nil, err
+		return err
 	}
-	return b, nil
+	writeJson(w, b)
+	return 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){
+	m := map[string]map[string]func(*Server, http.ResponseWriter, *http.Request, map[string]string) error{
 		"GET": {
 			"/auth":                         getAuth,
 			"/version":                      getVersion,
@@ -602,14 +621,9 @@ func ListenAndServe(addr string, srv *Server, logging bool) error {
 						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 {
+				if err := localFct(srv, w, r, mux.Vars(r)); err != nil {
 					httpError(w, err)
 				}
-				if json != nil {
-					w.Header().Set("Content-Type", "application/json")
-					w.Write(json)
-				}
 			})
 		}
 	}

+ 263 - 288
api_test.go

@@ -1,6 +1,7 @@
 package docker
 
 import (
+	"archive/tar"
 	"bufio"
 	"bytes"
 	"encoding/json"
@@ -11,7 +12,6 @@ import (
 	"net/http/httptest"
 	"os"
 	"path"
-	"strings"
 	"testing"
 	"time"
 )
@@ -43,13 +43,9 @@ func TestGetAuth(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	body, err := postAuth(srv, r, req, nil)
-	if err != nil {
+	if err := postAuth(srv, r, req, nil); err != nil {
 		t.Fatal(err)
 	}
-	if body == nil {
-		t.Fatalf("No body received\n")
-	}
 	if r.Code != http.StatusOK && r.Code != 0 {
 		t.Fatalf("%d OK or 0 expected, received %d\n", http.StatusOK, r.Code)
 	}
@@ -70,15 +66,14 @@ func TestGetVersion(t *testing.T) {
 
 	srv := &Server{runtime: runtime}
 
-	body, err := getVersion(srv, nil, nil, nil)
-	if err != nil {
+	r := httptest.NewRecorder()
+
+	if err := getVersion(srv, r, nil, nil); err != nil {
 		t.Fatal(err)
 	}
 
 	v := &ApiVersion{}
-
-	err = json.Unmarshal(body, v)
-	if err != nil {
+	if err = json.Unmarshal(r.Body.Bytes(), v); err != nil {
 		t.Fatal(err)
 	}
 	if v.Version != VERSION {
@@ -95,12 +90,14 @@ func TestGetInfo(t *testing.T) {
 
 	srv := &Server{runtime: runtime}
 
-	body, err := getInfo(srv, nil, nil, nil)
-	if err != nil {
+	r := httptest.NewRecorder()
+
+	if err := getInfo(srv, r, nil, nil); err != nil {
 		t.Fatal(err)
 	}
+
 	infos := &ApiInfo{}
-	err = json.Unmarshal(body, infos)
+	err = json.Unmarshal(r.Body.Bytes(), infos)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -124,14 +121,14 @@ func TestGetImagesJson(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	body, err := getImagesJson(srv, nil, req, nil)
-	if err != nil {
+	r := httptest.NewRecorder()
+
+	if err := getImagesJson(srv, r, req, nil); err != nil {
 		t.Fatal(err)
 	}
 
 	images := []ApiImages{}
-	err = json.Unmarshal(body, &images)
-	if err != nil {
+	if err := json.Unmarshal(r.Body.Bytes(), &images); err != nil {
 		t.Fatal(err)
 	}
 
@@ -139,24 +136,24 @@ func TestGetImagesJson(t *testing.T) {
 		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)
+	if images[0].Repository != unitTestImageName {
+		t.Errorf("Excepted image %s, %s found", unitTestImageName, images[0].Repository)
 	}
 
+	r2 := httptest.NewRecorder()
+
 	// only_ids=1&all=1
 	req2, err := http.NewRequest("GET", "/images/json?only_ids=1&all=1", nil)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	body2, err := getImagesJson(srv, nil, req2, nil)
-	if err != nil {
+	if err := getImagesJson(srv, r2, req2, nil); err != nil {
 		t.Fatal(err)
 	}
 
 	images2 := []ApiImages{}
-	err = json.Unmarshal(body2, &images2)
-	if err != nil {
+	if err := json.Unmarshal(r2.Body.Bytes(), &images2); err != nil {
 		t.Fatal(err)
 	}
 
@@ -168,24 +165,24 @@ func TestGetImagesJson(t *testing.T) {
 		t.Errorf("Excepted no image Repository, %s found", images2[0].Repository)
 	}
 
-	if images2[0].Id == "" {
-		t.Errorf("Excepted image Id, %s found", images2[0].Id)
+	if images2[0].Id != GetTestImage(runtime).ShortId() {
+		t.Errorf("Retrieved image Id differs, expected %s, received %s", GetTestImage(runtime).ShortId(), images2[0].Id)
 	}
 
+	r3 := httptest.NewRecorder()
+
 	// filter=a
 	req3, err := http.NewRequest("GET", "/images/json?filter=a", nil)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	body3, err := getImagesJson(srv, nil, req3, nil)
-	if err != nil {
+	if err := getImagesJson(srv, r3, req3, nil); err != nil {
 		t.Fatal(err)
 	}
 
 	images3 := []ApiImages{}
-	err = json.Unmarshal(body3, &images3)
-	if err != nil {
+	if err := json.Unmarshal(r3.Body.Bytes(), &images3); err != nil {
 		t.Fatal(err)
 	}
 
@@ -204,9 +201,7 @@ func TestGetImagesViz(t *testing.T) {
 	srv := &Server{runtime: runtime}
 
 	r := httptest.NewRecorder()
-
-	_, err = getImagesViz(srv, r, nil, nil)
-	if err != nil {
+	if err := getImagesViz(srv, r, nil, nil); err != nil {
 		t.Fatal(err)
 	}
 
@@ -233,20 +228,19 @@ func TestGetImagesSearch(t *testing.T) {
 
 	srv := &Server{runtime: runtime}
 
+	r := httptest.NewRecorder()
+
 	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 {
+	if err := getImagesSearch(srv, r, req, nil); err != nil {
 		t.Fatal(err)
 	}
 
 	results := []ApiSearch{}
-
-	err = json.Unmarshal(body, &results)
-	if err != nil {
+	if err := json.Unmarshal(r.Body.Bytes(), &results); err != nil {
 		t.Fatal(err)
 	}
 	if len(results) < 2 {
@@ -263,15 +257,14 @@ func TestGetImagesHistory(t *testing.T) {
 
 	srv := &Server{runtime: runtime}
 
-	body, err := getImagesHistory(srv, nil, nil, map[string]string{"name": unitTestImageName})
-	if err != nil {
+	r := httptest.NewRecorder()
+
+	if err := getImagesHistory(srv, r, nil, map[string]string{"name": unitTestImageName}); err != nil {
 		t.Fatal(err)
 	}
 
 	history := []ApiHistory{}
-
-	err = json.Unmarshal(body, &history)
-	if err != nil {
+	if err := json.Unmarshal(r.Body.Bytes(), &history); err != nil {
 		t.Fatal(err)
 	}
 	if len(history) != 1 {
@@ -288,18 +281,16 @@ func TestGetImagesByName(t *testing.T) {
 
 	srv := &Server{runtime: runtime}
 
-	body, err := getImagesByName(srv, nil, nil, map[string]string{"name": unitTestImageName})
-	if err != nil {
+	r := httptest.NewRecorder()
+	if err := getImagesByName(srv, r, nil, map[string]string{"name": unitTestImageName}); err != nil {
 		t.Fatal(err)
 	}
 
 	img := &Image{}
-
-	err = json.Unmarshal(body, img)
-	if err != nil {
+	if err := json.Unmarshal(r.Body.Bytes(), img); err != nil {
 		t.Fatal(err)
 	}
-	if img.Comment != "Imported from http://get.docker.io/images/busybox" {
+	if img.Id != GetTestImage(runtime).Id || img.Comment != "Imported from http://get.docker.io/images/busybox" {
 		t.Errorf("Error inspecting image")
 	}
 }
@@ -327,13 +318,12 @@ func TestGetContainersPs(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	body, err := getContainersPs(srv, nil, req, nil)
-	if err != nil {
+	r := httptest.NewRecorder()
+	if err := getContainersPs(srv, r, req, nil); err != nil {
 		t.Fatal(err)
 	}
 	containers := []ApiContainers{}
-	err = json.Unmarshal(body, &containers)
-	if err != nil {
+	if err := json.Unmarshal(r.Body.Bytes(), &containers); err != nil {
 		t.Fatal(err)
 	}
 	if len(containers) != 1 {
@@ -359,7 +349,7 @@ func TestGetContainersExport(t *testing.T) {
 	container, err := builder.Create(
 		&Config{
 			Image: GetTestImage(runtime).Id,
-			Cmd:   []string{"/bin/rm", "/etc/passwd"},
+			Cmd:   []string{"touch", "/test"},
 		},
 	)
 	if err != nil {
@@ -372,9 +362,7 @@ func TestGetContainersExport(t *testing.T) {
 	}
 
 	r := httptest.NewRecorder()
-
-	_, err = getContainersExport(srv, r, nil, map[string]string{"name": container.Id})
-	if err != nil {
+	if err = getContainersExport(srv, r, nil, map[string]string{"name": container.Id}); err != nil {
 		t.Fatal(err)
 	}
 
@@ -382,8 +370,22 @@ func TestGetContainersExport(t *testing.T) {
 		t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code)
 	}
 
-	if r.Body == nil {
-		t.Fatalf("Body expected, found 0")
+	found := false
+	for tarReader := tar.NewReader(r.Body); ; {
+		h, err := tarReader.Next()
+		if err != nil {
+			if err == io.EOF {
+				break
+			}
+			t.Fatal(err)
+		}
+		if h.Name == "./test" {
+			found = true
+			break
+		}
+	}
+	if !found {
+		t.Fatalf("The created test file has not been found in the exported image")
 	}
 }
 
@@ -414,12 +416,12 @@ func TestGetContainersChanges(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	body, err := getContainersChanges(srv, nil, nil, map[string]string{"name": container.Id})
-	if err != nil {
+	r := httptest.NewRecorder()
+	if err := getContainersChanges(srv, r, nil, map[string]string{"name": container.Id}); err != nil {
 		t.Fatal(err)
 	}
 	changes := []Change{}
-	if err := json.Unmarshal(body, &changes); err != nil {
+	if err := json.Unmarshal(r.Body.Bytes(), &changes); err != nil {
 		t.Fatal(err)
 	}
 
@@ -450,7 +452,7 @@ func TestGetContainersByName(t *testing.T) {
 	container, err := builder.Create(
 		&Config{
 			Image: GetTestImage(runtime).Id,
-			Cmd:   []string{"/bin/rm", "/etc/passwd"},
+			Cmd:   []string{"echo", "test"},
 		},
 	)
 	if err != nil {
@@ -458,14 +460,17 @@ func TestGetContainersByName(t *testing.T) {
 	}
 	defer runtime.Destroy(container)
 
-	body, err := getContainersByName(srv, nil, nil, map[string]string{"name": container.Id})
-	if err != nil {
+	r := httptest.NewRecorder()
+	if err := getContainersByName(srv, r, nil, map[string]string{"name": container.Id}); err != nil {
 		t.Fatal(err)
 	}
-	outContainer := Container{}
-	if err := json.Unmarshal(body, &outContainer); err != nil {
+	outContainer := &Container{}
+	if err := json.Unmarshal(r.Body.Bytes(), outContainer); err != nil {
 		t.Fatal(err)
 	}
+	if outContainer.Id != container.Id {
+		t.Fatalf("Wrong containers retrieved. Expected %s, recieved %s", container.Id, outContainer.Id)
+	}
 }
 
 func TestPostAuth(t *testing.T) {
@@ -483,14 +488,13 @@ func TestPostAuth(t *testing.T) {
 	}
 	runtime.authConfig = authConfigOrig
 
-	body, err := getAuth(srv, nil, nil, nil)
-	if err != nil {
+	r := httptest.NewRecorder()
+	if err := getAuth(srv, r, nil, nil); err != nil {
 		t.Fatal(err)
 	}
 
 	authConfig := &auth.AuthConfig{}
-	err = json.Unmarshal(body, authConfig)
-	if err != nil {
+	if err := json.Unmarshal(r.Body.Bytes(), authConfig); err != nil {
 		t.Fatal(err)
 	}
 
@@ -508,15 +512,13 @@ func TestPostCommit(t *testing.T) {
 
 	srv := &Server{runtime: runtime}
 
-	r := httptest.NewRecorder()
-
 	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"},
+			Cmd:   []string{"touch", "/test"},
 		},
 	)
 	if err != nil {
@@ -533,17 +535,21 @@ func TestPostCommit(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	body, err := postCommit(srv, r, req, nil)
-	if err != nil {
+	r := httptest.NewRecorder()
+	if err := postCommit(srv, r, req, nil); err != nil {
 		t.Fatal(err)
 	}
-
-	if body == nil {
-		t.Fatalf("Body expected, received: 0\n")
-	}
 	if r.Code != http.StatusCreated {
 		t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code)
 	}
+
+	apiId := &ApiId{}
+	if err := json.Unmarshal(r.Body.Bytes(), apiId); err != nil {
+		t.Fatal(err)
+	}
+	if _, err := runtime.graph.Get(apiId.Id); err != nil {
+		t.Fatalf("The image has not been commited")
+	}
 }
 
 func TestPostBuild(t *testing.T) {
@@ -560,20 +566,16 @@ func TestPostBuild(t *testing.T) {
 
 	c1 := make(chan struct{})
 	go func() {
+		defer close(c1)
 		r := &hijackTester{
 			ResponseRecorder: httptest.NewRecorder(),
 			in:               stdin,
 			out:              stdoutPipe,
 		}
 
-		body, err := postBuild(srv, r, nil, nil)
-		close(c1)
-		if err != nil {
+		if err := postBuild(srv, r, nil, nil); err != nil {
 			t.Fatal(err)
 		}
-		if body != nil {
-			t.Fatalf("No body expected, received: %s\n", body)
-		}
 	}()
 
 	// Acknowledge hijack
@@ -601,163 +603,169 @@ func TestPostBuild(t *testing.T) {
 }
 
 func TestPostImagesCreate(t *testing.T) {
-	runtime, err := newTestRuntime()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer nuke(runtime)
-
-	srv := &Server{runtime: runtime}
-
-	stdin, stdinPipe := io.Pipe()
-	stdout, stdoutPipe := io.Pipe()
-
-	c1 := make(chan struct{})
-	go func() {
-		r := &hijackTester{
-			ResponseRecorder: httptest.NewRecorder(),
-			in:               stdin,
-			out:              stdoutPipe,
-		}
-
-		req, err := http.NewRequest("POST", "/images/create?fromImage=docker-ut", bytes.NewReader([]byte{}))
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		body, err := postImagesCreate(srv, r, req, nil)
-		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, "Waiting for imagesCreate output", 5*time.Second, func() {
-		reader := bufio.NewReader(stdout)
-		line, err := reader.ReadString('\n')
-		if err != nil {
-			t.Fatal(err)
-		}
-		if !strings.HasPrefix(line, "Pulling repository docker-ut from") {
-			t.Fatalf("Expected Pulling repository docker-ut from..., found %s", line)
-		}
-	})
-
-	// Close pipes (client disconnects)
-	if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
-		t.Fatal(err)
-	}
-
-	// Wait for imagesCreate to finish, the client disconnected, therefore, Create finished his job
-	setTimeout(t, "Waiting for imagesCreate timed out", 10*time.Second, func() {
-		<-c1
-	})
+	// FIXME: Use the staging in order to perform tests
+
+	// runtime, err := newTestRuntime()
+	// if err != nil {
+	// 	t.Fatal(err)
+	// }
+	// defer nuke(runtime)
+
+	// srv := &Server{runtime: runtime}
+
+	// stdin, stdinPipe := io.Pipe()
+	// stdout, stdoutPipe := io.Pipe()
+
+	// c1 := make(chan struct{})
+	// go func() {
+	// 	defer close(c1)
+
+	// 	r := &hijackTester{
+	// 		ResponseRecorder: httptest.NewRecorder(),
+	// 		in:               stdin,
+	// 		out:              stdoutPipe,
+	// 	}
+
+	// 	req, err := http.NewRequest("POST", "/images/create?fromImage="+unitTestImageName, bytes.NewReader([]byte{}))
+	// 	if err != nil {
+	// 		t.Fatal(err)
+	// 	}
+
+	// 	body, err := postImagesCreate(srv, r, req, nil)
+	// 	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, "Waiting for imagesCreate output", 5*time.Second, func() {
+	// 	reader := bufio.NewReader(stdout)
+	// 	line, err := reader.ReadString('\n')
+	// 	if err != nil {
+	// 		t.Fatal(err)
+	// 	}
+	// 	if !strings.HasPrefix(line, "Pulling repository d from") {
+	// 		t.Fatalf("Expected Pulling repository docker-ut from..., found %s", line)
+	// 	}
+	// })
+
+	// // Close pipes (client disconnects)
+	// if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
+	// 	t.Fatal(err)
+	// }
+
+	// // Wait for imagesCreate to finish, the client disconnected, therefore, Create finished his job
+	// setTimeout(t, "Waiting for imagesCreate timed out", 10*time.Second, func() {
+	// 	<-c1
+	// })
 }
 
-func TestPostImagesInsert(t *testing.T) {
-	//FIXME: Implement this test (or remove this endpoint)
-	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) {
-	runtime, err := newTestRuntime()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer nuke(runtime)
-
-	srv := &Server{runtime: runtime}
-
-	stdin, stdinPipe := io.Pipe()
-	stdout, stdoutPipe := io.Pipe()
-
-	c1 := make(chan struct{})
-	go func() {
-		r := &hijackTester{
-			ResponseRecorder: httptest.NewRecorder(),
-			in:               stdin,
-			out:              stdoutPipe,
-		}
-
-		req, err := http.NewRequest("POST", "/images/docker-ut/push", bytes.NewReader([]byte{}))
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		body, err := postImagesPush(srv, r, req, map[string]string{"name": "docker-ut"})
-		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, "Waiting for imagesCreate output", 5*time.Second, func() {
-		reader := bufio.NewReader(stdout)
-		line, err := reader.ReadString('\n')
-		if err != nil {
-			t.Fatal(err)
-		}
-		if !strings.HasPrefix(line, "Processing checksum") {
-			t.Fatalf("Processing checksum..., found %s", line)
-		}
-	})
-
-	// Close pipes (client disconnects)
-	if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
-		t.Fatal(err)
-	}
-
-	// Wait for imagesPush to finish, the client disconnected, therefore, Push finished his job
-	setTimeout(t, "Waiting for imagesPush timed out", 10*time.Second, func() {
-		<-c1
-	})
+	//FIXME: Use staging in order to perform tests
+	// runtime, err := newTestRuntime()
+	// if err != nil {
+	// 	t.Fatal(err)
+	// }
+	// defer nuke(runtime)
+
+	// srv := &Server{runtime: runtime}
+
+	// stdin, stdinPipe := io.Pipe()
+	// stdout, stdoutPipe := io.Pipe()
+
+	// c1 := make(chan struct{})
+	// go func() {
+	// 	r := &hijackTester{
+	// 		ResponseRecorder: httptest.NewRecorder(),
+	// 		in:               stdin,
+	// 		out:              stdoutPipe,
+	// 	}
+
+	// 	req, err := http.NewRequest("POST", "/images/docker-ut/push", bytes.NewReader([]byte{}))
+	// 	if err != nil {
+	// 		t.Fatal(err)
+	// 	}
+
+	// 	body, err := postImagesPush(srv, r, req, map[string]string{"name": "docker-ut"})
+	// 	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, "Waiting for imagesCreate output", 5*time.Second, func() {
+	// 	reader := bufio.NewReader(stdout)
+	// 	line, err := reader.ReadString('\n')
+	// 	if err != nil {
+	// 		t.Fatal(err)
+	// 	}
+	// 	if !strings.HasPrefix(line, "Processing checksum") {
+	// 		t.Fatalf("Processing checksum..., found %s", line)
+	// 	}
+	// })
+
+	// // Close pipes (client disconnects)
+	// if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
+	// 	t.Fatal(err)
+	// }
+
+	// // Wait for imagesPush to finish, the client disconnected, therefore, Push finished his job
+	// setTimeout(t, "Waiting for imagesPush timed out", 10*time.Second, func() {
+	// 	<-c1
+	// })
 }
 
 func TestPostImagesTag(t *testing.T) {
-	runtime, err := newTestRuntime()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer nuke(runtime)
-
-	srv := &Server{runtime: runtime}
-
-	r := httptest.NewRecorder()
-
-	req, err := http.NewRequest("POST", "/images/docker-ut/tag?repo=testrepo&tag=testtag", bytes.NewReader([]byte{}))
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	body, err := postImagesTag(srv, r, req, map[string]string{"name": "docker-ut"})
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if body != nil {
-		t.Fatalf("No body expected, received: %s\n", body)
-	}
-	if r.Code != http.StatusCreated {
-		t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code)
-	}
+	// FIXME: Use staging in order to perform tests
+
+	// runtime, err := newTestRuntime()
+	// if err != nil {
+	// 	t.Fatal(err)
+	// }
+	// defer nuke(runtime)
+
+	// srv := &Server{runtime: runtime}
+
+	// r := httptest.NewRecorder()
+
+	// req, err := http.NewRequest("POST", "/images/docker-ut/tag?repo=testrepo&tag=testtag", bytes.NewReader([]byte{}))
+	// if err != nil {
+	// 	t.Fatal(err)
+	// }
+
+	// body, err := postImagesTag(srv, r, req, map[string]string{"name": "docker-ut"})
+	// if err != nil {
+	// 	t.Fatal(err)
+	// }
+
+	// if body != nil {
+	// 	t.Fatalf("No body expected, received: %s\n", body)
+	// }
+	// if r.Code != http.StatusCreated {
+	// 	t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code)
+	// }
 }
 
 func TestPostContainersCreate(t *testing.T) {
@@ -769,8 +777,6 @@ func TestPostContainersCreate(t *testing.T) {
 
 	srv := &Server{runtime: runtime}
 
-	r := httptest.NewRecorder()
-
 	configJson, err := json.Marshal(&Config{
 		Image:  GetTestImage(runtime).Id,
 		Memory: 33554432,
@@ -785,8 +791,8 @@ func TestPostContainersCreate(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	body, err := postContainersCreate(srv, r, req, nil)
-	if err != nil {
+	r := httptest.NewRecorder()
+	if err := postContainersCreate(srv, r, req, nil); err != nil {
 		t.Fatal(err)
 	}
 	if r.Code != http.StatusCreated {
@@ -794,7 +800,7 @@ func TestPostContainersCreate(t *testing.T) {
 	}
 
 	apiRun := &ApiRun{}
-	if err := json.Unmarshal(body, apiRun); err != nil {
+	if err := json.Unmarshal(r.Body.Bytes(), apiRun); err != nil {
 		t.Fatal(err)
 	}
 
@@ -849,14 +855,9 @@ func TestPostContainersKill(t *testing.T) {
 	}
 
 	r := httptest.NewRecorder()
-
-	body, err := postContainersKill(srv, r, nil, map[string]string{"name": container.Id})
-	if err != nil {
+	if err := postContainersKill(srv, r, nil, map[string]string{"name": container.Id}); 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)
 	}
@@ -897,19 +898,14 @@ func TestPostContainersRestart(t *testing.T) {
 		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 {
+	r := httptest.NewRecorder()
+	if err := postContainersRestart(srv, r, req, map[string]string{"name": container.Id}); 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)
 	}
@@ -948,14 +944,9 @@ func TestPostContainersStart(t *testing.T) {
 	defer runtime.Destroy(container)
 
 	r := httptest.NewRecorder()
-
-	body, err := postContainersStart(srv, r, nil, map[string]string{"name": container.Id})
-	if err != nil {
+	if err := postContainersStart(srv, r, nil, map[string]string{"name": container.Id}); 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)
 	}
@@ -967,7 +958,8 @@ func TestPostContainersStart(t *testing.T) {
 		t.Errorf("Container should be running")
 	}
 
-	if _, err = postContainersStart(srv, r, nil, map[string]string{"name": container.Id}); err == nil {
+	r = httptest.NewRecorder()
+	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")
 	}
 
@@ -1008,20 +1000,15 @@ func TestPostContainersStop(t *testing.T) {
 		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 {
+	r := httptest.NewRecorder()
+	if err := postContainersStop(srv, r, req, map[string]string{"name": container.Id}); 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)
 	}
@@ -1056,12 +1043,12 @@ func TestPostContainersWait(t *testing.T) {
 	}
 
 	setTimeout(t, "Wait timed out", 3*time.Second, func() {
-		body, err := postContainersWait(srv, nil, nil, map[string]string{"name": container.Id})
-		if err != nil {
+		r := httptest.NewRecorder()
+		if err := postContainersWait(srv, r, nil, map[string]string{"name": container.Id}); err != nil {
 			t.Fatal(err)
 		}
 		apiWait := &ApiWait{}
-		if err := json.Unmarshal(body, apiWait); err != nil {
+		if err := json.Unmarshal(r.Body.Bytes(), apiWait); err != nil {
 			t.Fatal(err)
 		}
 		if apiWait.StatusCode != 0 {
@@ -1106,8 +1093,7 @@ func TestPostContainersAttach(t *testing.T) {
 	// 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.
+		defer close(c1)
 
 		r := &hijackTester{
 			ResponseRecorder: httptest.NewRecorder(),
@@ -1120,14 +1106,9 @@ func TestPostContainersAttach(t *testing.T) {
 			t.Fatal(err)
 		}
 
-		body, err := postContainersAttach(srv, r, req, map[string]string{"name": container.Id})
-		close(c1)
-		if err != nil {
+		if err := postContainersAttach(srv, r, req, map[string]string{"name": container.Id}); err != nil {
 			t.Fatal(err)
 		}
-		if body != nil {
-			t.Fatalf("No body expected, received: %s\n", body)
-		}
 	}()
 
 	// Acknowledge hijack
@@ -1165,7 +1146,7 @@ func TestPostContainersAttach(t *testing.T) {
 	container.Wait()
 }
 
-// FIXME: Test deleting runnign container
+// FIXME: Test deleting running container
 // FIXME: Test deleting container with volume
 // FIXME: Test deleting volume in use by other container
 func TestDeleteContainers(t *testing.T) {
@@ -1190,20 +1171,14 @@ func TestDeleteContainers(t *testing.T) {
 		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 {
+	r := httptest.NewRecorder()
+	if err := deleteContainers(srv, r, req, map[string]string{"name": container.Id}); 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)
 	}

+ 8 - 0
commands.go

@@ -1196,6 +1196,14 @@ func stream(method, path string) error {
 		return err
 	}
 	defer resp.Body.Close()
+	if resp.StatusCode < 200 || resp.StatusCode >= 400 {
+		body, err := ioutil.ReadAll(resp.Body)
+		if err != nil {
+			return err
+		}
+		return fmt.Errorf("error: %s", body)
+	}
+
 	if _, err := io.Copy(os.Stdout, resp.Body); err != nil {
 		return err
 	}

+ 95 - 43
docs/sources/use/builder.rst

@@ -4,83 +4,120 @@ Docker Builder
 
 .. contents:: Table of Contents
 
-1. Format
+Docker Builder specifes a simple DSL which allows you to automate the steps you
+would normally manually take to create an image. Docker Build will run your 
+steps and commit them along the way, giving you a final image.
+
+1. Usage
+========
+
+To use Docker Builder, assemble the steps into a text file (commonly referred to
+as a Dockerfile) and supply this to `docker build` on STDIN, like so:
+
+    ``docker build < Dockerfile``
+
+Docker will run your steps one-by-one, committing the result if necessary, 
+before finally outputting the ID of your new image.
+
+2. Format
 =========
 
-The Docker builder format is quite simple:
+The Dockerfile format is quite simple:
 
     ``instruction arguments``
 
-The first instruction must be `FROM`
+The Instruction is not case-sensitive, however convention is for them to be 
+UPPERCASE in order to distinguish them from arguments more easily.
 
-All instruction are to be placed in a file named `Dockerfile`
+Dockerfiles are evaluated in order, therefore the first instruction must be 
+`FROM` in order to specify the base image from which you are building.
 
-In order to place comments within a Dockerfile, simply prefix the line with "`#`"
+Docker will ignore lines in Dockerfiles prefixed with "`#`", so you may add 
+comment lines. A comment marker in the rest of the line will be treated as an
+argument.
 
 2. Instructions
 ===============
 
-Docker builder comes with a set of instructions:
-
-1. FROM: Set from what image to build
-2. RUN: Execute a command
-3. INSERT: Insert a remote file (http) into the image
+Docker builder comes with a set of instructions, described below.
 
 2.1 FROM
 --------
+
     ``FROM <image>``
 
-The `FROM` instruction must be the first one in order for Builder to know from where to run commands.
+The `FROM` instruction sets the base image for subsequent instructions. As such,
+a valid Dockerfile must have it as its first instruction.
 
-`FROM` can also be used in order to build multiple images within a single Dockerfile
+`FROM` can be included multiple times within a single Dockerfile in order to 
+create multiple images. Simply make a note of the last image id output by the 
+commit before each new `FROM` command.
 
 2.2 MAINTAINER
 --------------
+
     ``MAINTAINER <name>``
 
-The `MAINTAINER` instruction allow you to set the Author field of the generated images.
-This instruction is never automatically reset.
+The `MAINTAINER` instruction allows you to set the Author field of the generated 
+images.
 
 2.3 RUN
 -------
+
     ``RUN <command>``
 
-The `RUN` instruction is the main one, it allows you to execute any commands on the `FROM` image and to save the results.
-You can use as many `RUN` as you want within a Dockerfile, the commands will be executed on the result of the previous command.
+The `RUN` instruction will execute any commands on the current image and commit
+the results. The resulting committed image will be used for the next step in the
+Dockerfile.
 
+Layering `RUN` instructions and generating commits conforms to the
+core concepts of Docker where commits are cheap and containers can be created
+from any point in an image's history, much like source control.
 
 2.4 CMD
 -------
+
     ``CMD <command>``
 
 The `CMD` instruction sets the command to be executed when running the image.
-It is equivalent to do `docker commit -run '{"Cmd": <command>}'` outside the builder.
+This is functionally equivalent to running 
+`docker commit -run '{"Cmd": <command>}'` outside the builder.
 
 .. note::
-    Do not confuse `RUN` with `CMD`. `RUN` actually run a command and save the result, `CMD` does not execute anything.
+    Don't confuse `RUN` with `CMD`. `RUN` actually runs a command and commits 
+    the result; `CMD` does not execute anything at build time, but specifies the
+    intended command for the image.
 
 2.5 EXPOSE
 ----------
+
     ``EXPOSE <port> [<port>...]``
 
-The `EXPOSE` instruction sets ports to be publicly exposed when running the image.
-This is equivalent to do `docker commit -run '{"PortSpecs": ["<port>", "<port2>"]}'` outside the builder.
+The `EXPOSE` instruction sets ports to be publicly exposed when running the 
+image. This is functionally equivalent to running 
+`docker commit -run '{"PortSpecs": ["<port>", "<port2>"]}'` outside the builder.
 
 2.6 ENV
 -------
+
     ``ENV <key> <value>``
 
-The `ENV` instruction set as environment variable `<key>` with the value `<value>`. This value will be passed to all future ``RUN`` instructions.
+The `ENV` instruction sets the environment variable `<key>` to the value 
+`<value>`. This value will be passed to all future ``RUN`` instructions. This is
+functionally equivalent to prefixing the command with `<key>=<value>`
 
 .. note::
-    The environment variables are local to the Dockerfile, they will not be set as autorun.
+    The environment variables are local to the Dockerfile, they will not persist
+    when a container is run from the resulting image.
 
 2.7 INSERT
 ----------
 
     ``INSERT <file url> <path>``
 
-The `INSERT` instruction will download the file at the given url and place it within the image at the given path.
+The `INSERT` instruction will download the file from the given url to the given
+path within the image. It is similar to `RUN curl -o <path> <url>`, assuming 
+curl was installed within the image.
 
 .. note::
     The path must include the file name.
@@ -89,42 +126,57 @@ The `INSERT` instruction will download the file at the given url and place it wi
 3. Dockerfile Examples
 ======================
 
-::
+.. code-block:: bash
 
     # Nginx
     #
     # VERSION               0.0.1
-    # DOCKER-VERSION        0.2
     
-    from      ubuntu
-    maintainer Guillaume J. Charmes "guillaume@dotcloud.com"
+    FROM      ubuntu
+    MAINTAINER Guillaume J. Charmes "guillaume@dotcloud.com"
     
     # make sure the package repository is up to date
-    run echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
-    run apt-get update
+    RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
+    RUN apt-get update
     
-    run apt-get install -y inotify-tools nginx apache2 openssh-server
-    insert https://raw.github.com/creack/docker-vps/master/nginx-wrapper.sh /usr/sbin/nginx-wrapper
+    RUN apt-get install -y inotify-tools nginx apache2 openssh-server
+    INSERT https://raw.github.com/creack/docker-vps/master/nginx-wrapper.sh /usr/sbin/nginx-wrapper
 
-::
+.. code-block:: bash
 
     # Firefox over VNC
     #
     # VERSION               0.3
-    # DOCKER-VERSION        0.2
     
-    from ubuntu
+    FROM ubuntu
     # make sure the package repository is up to date
-    run echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
-    run apt-get update
+    RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
+    RUN apt-get update
     
     # Install vnc, xvfb in order to create a 'fake' display and firefox
-    run apt-get install -y x11vnc xvfb firefox
-    run mkdir /.vnc
+    RUN apt-get install -y x11vnc xvfb firefox
+    RUN mkdir /.vnc
     # Setup a password
-    run x11vnc -storepasswd 1234 ~/.vnc/passwd
-    # Autostart firefox (might not be the best way to do it, but it does the trick)
-    run bash -c 'echo "firefox" >> /.bashrc'
+    RUN x11vnc -storepasswd 1234 ~/.vnc/passwd
+    # Autostart firefox (might not be the best way, but it does the trick)
+    RUN bash -c 'echo "firefox" >> /.bashrc'
     
-    expose 5900
-    cmd    ["x11vnc", "-forever", "-usepw", "-create"]
+    EXPOSE 5900
+    CMD    ["x11vnc", "-forever", "-usepw", "-create"]
+
+.. code-block:: bash
+
+    # Multiple images example
+    #
+    # VERSION               0.1
+
+    FROM ubuntu
+    RUN echo foo > bar
+    # Will output something like ===> 907ad6c2736f
+
+    FROM ubuntu
+    RUN echo moo > oink
+    # Will output something like ===> 695d7793cbe4
+
+    # You'll now have two images, 907ad6c2736f with /bar, and 695d7793cbe4 with
+    # /oink.

+ 1 - 1
registry.go

@@ -316,7 +316,7 @@ func (graph *Graph) PullRepository(stdout io.Writer, remote, askedTag string, re
 	err = func() error {
 		localChecksums := make(map[string]string)
 		remoteChecksums := []ImgListJson{}
-		checksumDictPth := path.Join(graph.Root, "..", "checksums")
+		checksumDictPth := path.Join(graph.Root, "checksums")
 
 		if err := json.Unmarshal(checksumsJson, &remoteChecksums); err != nil {
 			return err

+ 6 - 1
server.go

@@ -262,7 +262,12 @@ func (srv *Server) Containers(all, trunc_cmd, only_ids bool, n int, since, befor
 		displayed++
 
 		c := ApiContainers{
-			Id: container.ShortId(),
+			Id: container.Id,
+		}
+		if trunc_cmd {
+			c = ApiContainers{
+				Id: container.ShortId(),
+			}
 		}
 
 		if !only_ids {