Parcourir la source

split api and server. run return exit code. import, pull and commit uses the smae endpoint. non zero status code on failure

Victor Vieux il y a 12 ans
Parent
commit
04cd20fa62
7 fichiers modifiés avec 718 ajouts et 571 suppressions
  1. 1 1
      Makefile
  2. 172 474
      api.go
  3. 84 40
      commands.go
  4. 6 3
      commands_test.go
  5. 5 51
      docker/docker.go
  6. 2 2
      runtime_test.go
  7. 448 0
      server.go

+ 1 - 1
Makefile

@@ -38,7 +38,7 @@ $(DOCKER_BIN): $(DOCKER_DIR)
 
 $(DOCKER_DIR):
 	@mkdir -p $(dir $@)
-	@if [ -h $@ ]; then rm -f $@; ln -sf $(CURDIR)/ $@; fi
+	@if [ -h $@ ]; then rm -f $@; fi; ln -sf $(CURDIR)/ $@
 	@(cd $(DOCKER_MAIN); go get $(GO_OPTIONS))
 
 whichrelease:

+ 172 - 474
api.go

@@ -5,24 +5,46 @@ import (
 	"encoding/json"
 	"fmt"
 	"github.com/gorilla/mux"
-	"io"
 	"log"
 	"net"
 	"net/http"
-	"net/url"
 	"os"
-	"runtime"
 	"strconv"
 	"strings"
 )
 
-func ListenAndServe(addr string, rtime *Runtime) error {
+func hijackServer(w http.ResponseWriter) (*os.File, net.Conn, error) {
+	rwc, _, err := w.(http.Hijacker).Hijack()
+	if err != nil {
+		return nil, nil, err
+	}
+
+	file, err := rwc.(*net.TCPConn).File()
+	if err != nil {
+		return nil, rwc, err
+	}
+
+	// Flush the options to make sure the client sets the raw mode
+	rwc.Write([]byte{})
+
+	return file, rwc, 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 ListenAndServe(addr string, srv *Server) error {
 	r := mux.NewRouter()
 	log.Printf("Listening for HTTP on %s\n", addr)
 
 	r.Path("/version").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		log.Println(r.Method, r.RequestURI)
-		m := ApiVersion{VERSION, GIT_COMMIT, rtime.capabilities.MemoryLimit, rtime.capabilities.SwapLimit}
+		m := srv.DockerVersion()
 		b, err := json.Marshal(m)
 		if err != nil {
 			http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -35,16 +57,11 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 		log.Println(r.Method, r.RequestURI)
 		vars := mux.Vars(r)
 		name := vars["name"]
-		if container := rtime.Get(name); container != nil {
-			if err := container.Kill(); err != nil {
-				http.Error(w, "Error restarting container "+name+": "+err.Error(), http.StatusInternalServerError)
-				return
-			}
+		if err := srv.ContainerKill(name); err != nil {
+			httpError(w, err)
 		} else {
-			http.Error(w, "No such container: "+name, http.StatusNotFound)
-			return
+			w.WriteHeader(http.StatusOK)
 		}
-		w.WriteHeader(http.StatusOK)
 	})
 
 	r.Path("/containers/{name:.*}/export").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -52,36 +69,21 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 		vars := mux.Vars(r)
 		name := vars["name"]
 
-		if container := rtime.Get(name); container != nil {
-
-			data, err := container.Export()
-			if err != nil {
-				http.Error(w, err.Error(), http.StatusInternalServerError)
-				return
-			}
-			conn, _, err := w.(http.Hijacker).Hijack()
-			if err != nil {
-				http.Error(w, err.Error(), http.StatusInternalServerError)
-				return
-			}
-			defer conn.Close()
-			file, err := conn.(*net.TCPConn).File()
-			if err != nil {
-				http.Error(w, err.Error(), http.StatusInternalServerError)
-				return
-			}
+		file, rwc, err := hijackServer(w)
+		if file != nil {
 			defer file.Close()
-
-			fmt.Fprintln(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n")
-			// Stream the entire contents of the container (basically a volatile snapshot)
-			if _, err := io.Copy(file, data); err != nil {
-				fmt.Fprintln(file, "Error: "+err.Error())
-				return
-			}
-		} else {
-			http.Error(w, "No such container: "+name, http.StatusNotFound)
 		}
-
+		if rwc != nil {
+			defer rwc.Close()
+		}
+		if err != nil {
+			httpError(w, err)
+			return
+		}
+		fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n")
+		if err := srv.ContainerExport(name, file); err != nil {
+			fmt.Fprintln(file, "Error: "+err.Error())
+		}
 	})
 
 	r.Path("/images").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -89,61 +91,14 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 		if err := r.ParseForm(); err != nil {
 			http.Error(w, err.Error(), http.StatusInternalServerError)
 		}
-		All := r.Form.Get("all")
-		NameFilter := r.Form.Get("filter")
-		Quiet := r.Form.Get("quiet")
+		all := r.Form.Get("all")
+		filter := r.Form.Get("filter")
+		quiet := r.Form.Get("quiet")
 
-		var allImages map[string]*Image
-		var err error
-		if All == "1" {
-			allImages, err = rtime.graph.Map()
-		} else {
-			allImages, err = rtime.graph.Heads()
-		}
+		outs, err := srv.Images(all, filter, quiet)
 		if err != nil {
-			w.WriteHeader(500)
-			return
-		}
-		var outs []ApiImages = []ApiImages{} //produce [] when empty instead of 'null'
-		for name, repository := range rtime.repositories.Repositories {
-			if NameFilter != "" && name != NameFilter {
-				continue
-			}
-			for tag, id := range repository {
-				var out ApiImages
-				image, err := rtime.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 Quiet != "1" {
-					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 NameFilter == "" {
-			for id, image := range allImages {
-				var out ApiImages
-				if Quiet != "1" {
-					out.Repository = "<none>"
-					out.Tag = "<none>"
-					out.Id = TruncateId(id)
-					out.Created = image.Created.Unix()
-				} else {
-					out.Id = image.ShortId()
-				}
-				outs = append(outs, out)
-			}
+			httpError(w, err)
 		}
-
 		b, err := json.Marshal(outs)
 		if err != nil {
 			http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -154,22 +109,7 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 
 	r.Path("/info").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		log.Println(r.Method, r.RequestURI)
-		images, _ := rtime.graph.All()
-		var imgcount int
-		if images == nil {
-			imgcount = 0
-		} else {
-			imgcount = len(images)
-		}
-		var out ApiInfo
-		out.Containers = len(rtime.List())
-		out.Version = VERSION
-		out.Images = imgcount
-		if os.Getenv("DEBUG") == "1" {
-			out.Debug = true
-			out.NFd = getTotalUsedFds()
-			out.NGoroutines = runtime.NumGoroutine()
-		}
+		out := srv.DockerInfo()
 		b, err := json.Marshal(out)
 		if err != nil {
 			http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -182,22 +122,10 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 		log.Println(r.Method, r.RequestURI)
 		vars := mux.Vars(r)
 		name := vars["name"]
-
-		image, err := rtime.repositories.LookupImage(name)
+		outs, err := srv.ImageHistory(name)
 		if err != nil {
-			http.Error(w, err.Error(), http.StatusInternalServerError)
-			return
+			httpError(w, err)
 		}
-
-		var outs []ApiHistory = []ApiHistory{} //produce [] when empty instead of 'null'
-		err = image.WalkHistory(func(img *Image) error {
-			var out ApiHistory
-			out.Id = rtime.repositories.ImageName(img.ShortId())
-			out.Created = img.Created.Unix()
-			out.CreatedBy = strings.Join(img.ContainerConfig.Cmd, " ")
-			return nil
-		})
-
 		b, err := json.Marshal(outs)
 		if err != nil {
 			http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -210,25 +138,15 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 		log.Println(r.Method, r.RequestURI)
 		vars := mux.Vars(r)
 		name := vars["name"]
-
-		if container := rtime.Get(name); container != nil {
-			changes, err := container.Changes()
-			if err != nil {
-				http.Error(w, err.Error(), http.StatusInternalServerError)
-				return
-			}
-			var changesStr []string
-			for _, name := range changes {
-				changesStr = append(changesStr, name.String())
-			}
-			b, err := json.Marshal(changesStr)
-			if err != nil {
-				http.Error(w, err.Error(), http.StatusInternalServerError)
-			} else {
-				w.Write(b)
-			}
+		changesStr, err := srv.ContainerChanges(name)
+		if err != nil {
+			httpError(w, err)
+		}
+		b, err := json.Marshal(changesStr)
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
 		} else {
-			http.Error(w, "No such container: "+name, http.StatusNotFound)
+			w.Write(b)
 		}
 	})
 
@@ -237,99 +155,36 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 		if err := r.ParseForm(); err != nil {
 			http.Error(w, err.Error(), http.StatusInternalServerError)
 		}
-		privatePort := r.Form.Get("port")
 		vars := mux.Vars(r)
 		name := vars["name"]
-
-		if container := rtime.Get(name); container == nil {
-			http.Error(w, "No such container: "+name, http.StatusNotFound)
-			return
-		} else {
-			if frontend, exists := container.NetworkSettings.PortMapping[privatePort]; !exists {
-				http.Error(w, "No private port '"+privatePort+"' allocated on "+name, http.StatusInternalServerError)
-				return
-			} else {
-				b, err := json.Marshal(ApiPort{frontend})
-				if err != nil {
-					http.Error(w, err.Error(), http.StatusInternalServerError)
-				} else {
-					w.Write(b)
-				}
-			}
-		}
-	})
-
-	r.Path("/containers").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		log.Println(r.Method, r.RequestURI)
-		if err := r.ParseForm(); err != nil {
-			http.Error(w, err.Error(), http.StatusInternalServerError)
-		}
-		All := r.Form.Get("all")
-		NoTrunc := r.Form.Get("notrunc")
-		Quiet := r.Form.Get("quiet")
-		Last := r.Form.Get("n")
-		n, err := strconv.Atoi(Last)
+		out, err := srv.ContainerPort(name, r.Form.Get("port"))
 		if err != nil {
-			n = -1
-		}
-		var outs []ApiContainers = []ApiContainers{} //produce [] when empty instead of 'null'
-		for i, container := range rtime.List() {
-			if !container.State.Running && All != "1" && n == -1 {
-				continue
-			}
-			if i == n {
-				break
-			}
-			var out ApiContainers
-			out.Id = container.ShortId()
-			if Quiet != "1" {
-				command := fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " "))
-				if NoTrunc != "1" {
-					command = Trunc(command, 20)
-				}
-				out.Image = rtime.repositories.ImageName(container.Image)
-				out.Command = command
-				out.Created = container.Created.Unix()
-				out.Status = container.State.String()
-				out.Ports = container.NetworkSettings.PortMappingHuman()
-			}
-			outs = append(outs, out)
+			httpError(w, err)
 		}
-
-		b, err := json.Marshal(outs)
+		b, err := json.Marshal(ApiPort{out})
 		if err != nil {
 			http.Error(w, err.Error(), http.StatusInternalServerError)
 		} else {
 			w.Write(b)
 		}
+
 	})
 
-	r.Path("/containers/{name:.*}/commit").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+	r.Path("/containers").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		log.Println(r.Method, r.RequestURI)
-		var config Config
-		if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
-			http.Error(w, err.Error(), http.StatusInternalServerError)
-			return
-		}
-
 		if err := r.ParseForm(); err != nil {
 			http.Error(w, err.Error(), http.StatusInternalServerError)
-			return
 		}
-		vars := mux.Vars(r)
-		name := vars["name"]
-		repo := r.Form.Get("repo")
-		tag := r.Form.Get("tag")
-		author := r.Form.Get("author")
-		comment := r.Form.Get("comment")
-
-		img, err := rtime.Commit(name, repo, tag, comment, author, &config)
+		all := r.Form.Get("all")
+		notrunc := r.Form.Get("notrunc")
+		quiet := r.Form.Get("quiet")
+		n, err := strconv.Atoi(r.Form.Get("n"))
 		if err != nil {
-			http.Error(w, err.Error(), http.StatusInternalServerError)
-			return
+			n = -1
 		}
 
-		b, err := json.Marshal(ApiId{img.ShortId()})
+		outs := srv.Containers(all, notrunc, quiet, n)
+		b, err := json.Marshal(outs)
 		if err != nil {
 			http.Error(w, err.Error(), http.StatusInternalServerError)
 		} else {
@@ -342,121 +197,79 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 		if err := r.ParseForm(); err != nil {
 			http.Error(w, err.Error(), http.StatusInternalServerError)
 		}
-		vars := mux.Vars(r)
-		name := vars["name"]
 		repo := r.Form.Get("repo")
 		tag := r.Form.Get("tag")
+		vars := mux.Vars(r)
+		name := vars["name"]
 		var force bool
 		if r.Form.Get("force") == "1" {
 			force = true
 		}
 
-		if err := rtime.repositories.Set(repo, tag, name, force); err != nil {
+		if err := srv.ContainerTag(name, repo, tag, force); err != nil {
 			http.Error(w, err.Error(), http.StatusInternalServerError)
 			return
 		}
 		w.WriteHeader(http.StatusCreated)
 	})
 
-	r.Path("/images/{name:.*}/pull").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		log.Println(r.Method, r.RequestURI)
-		vars := mux.Vars(r)
-		name := vars["name"]
-
-		conn, _, err := w.(http.Hijacker).Hijack()
-		if err != nil {
-			http.Error(w, err.Error(), http.StatusInternalServerError)
-			return
-		}
-		defer conn.Close()
-		file, err := conn.(*net.TCPConn).File()
-		if err != nil {
-			http.Error(w, err.Error(), http.StatusInternalServerError)
-			return
-		}
-		defer file.Close()
-
-		fmt.Fprintln(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n")
-		if rtime.graph.LookupRemoteImage(name, rtime.authConfig) {
-			if err := rtime.graph.PullImage(file, name, rtime.authConfig); err != nil {
-				fmt.Fprintln(file, "Error: "+err.Error())
-			}
-			return
-		}
-		if err := rtime.graph.PullRepository(file, name, "", rtime.repositories, rtime.authConfig); err != nil {
-			fmt.Fprintln(file, "Error: "+err.Error())
-		}
-	})
-
-	/* /!\ W.I.P /!\ */
 	r.Path("/images").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		log.Println(r.Method, r.RequestURI)
 		if err := r.ParseForm(); err != nil {
 			http.Error(w, err.Error(), http.StatusInternalServerError)
 		}
-		src := r.Form.Get("src")
+
+		src := r.Form.Get("fromSrc")
+		image := r.Form.Get("fromImage")
+		container := r.Form.Get("fromContainer")
 		repo := r.Form.Get("repo")
 		tag := r.Form.Get("tag")
 
-		var archive io.Reader
-		var resp *http.Response
+		if container != "" { //commit
+			var config Config
+			if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
+				http.Error(w, err.Error(), http.StatusInternalServerError)
+				return
+			}
+			author := r.Form.Get("author")
+			comment := r.Form.Get("comment")
 
-		conn, _, err := w.(http.Hijacker).Hijack()
-		if err != nil {
-			http.Error(w, err.Error(), http.StatusInternalServerError)
-			return
-		}
-		defer conn.Close()
-		file, err := conn.(*net.TCPConn).File()
-		if err != nil {
-			http.Error(w, err.Error(), http.StatusInternalServerError)
-			return
-		}
-		defer file.Close()
-
-		fmt.Fprintln(file, "HTTP/1.1 201 Created\r\nContent-Type: application/json\r\n\r\n")
-		if src == "-" {
-			r, w := io.Pipe()
-			go func() {
-				defer w.Close()
-				defer Debugf("Closing buffered stdin pipe")
-				io.Copy(w, file)
-			}()
-			archive = r
-		} else {
-			u, err := url.Parse(src)
+			id, err := srv.ContainerCommit(container, repo, tag, author, comment, &config)
 			if err != nil {
-				fmt.Fprintln(file, "Error: "+err.Error())
+				httpError(w, err)
 			}
-			if u.Scheme == "" {
-				u.Scheme = "http"
-				u.Host = src
-				u.Path = ""
-			}
-			fmt.Fprintln(file, "Downloading from", u)
-			// Download with curl (pretty progress bar)
-			// If curl is not available, fallback to http.Get()
-			resp, err = Download(u.String(), file)
+			b, err := json.Marshal(ApiId{id})
 			if err != nil {
 				http.Error(w, err.Error(), http.StatusInternalServerError)
-				return
+			} else {
+				w.Write(b)
 			}
-			archive = ProgressReader(resp.Body, int(resp.ContentLength), file, "Importing %v/%v (%v)")
-		}
-		img, err := rtime.graph.Create(archive, nil, "Imported from "+src, "", nil)
-		if err != nil {
-			http.Error(w, err.Error(), http.StatusInternalServerError)
-			return
-		}
-		// Optionally register the image at REPO/TAG
-		if repo != "" {
-			if err := rtime.repositories.Set(repo, tag, img.Id, true); err != nil {
-				http.Error(w, err.Error(), http.StatusInternalServerError)
+		} else if image != "" || src != "" {
+			file, rwc, err := hijackServer(w)
+			if file != nil {
+				defer file.Close()
+			}
+			if rwc != nil {
+				defer rwc.Close()
+			}
+			if err != nil {
+				httpError(w, err)
 				return
 			}
-		}
+			fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n")
 
-		fmt.Fprintln(file, img.ShortId())
+			if image != "" { //pull
+				if err := srv.ImagePull(image, file); err != nil {
+					fmt.Fprintln(file, "Error: "+err.Error())
+				}
+			} else { //import
+				if err := srv.ImageImport(src, repo, tag, file); err != nil {
+					fmt.Fprintln(file, "Error: "+err.Error())
+				}
+			}
+		} else {
+			w.WriteHeader(http.StatusNotFound)
+		}
 	})
 
 	r.Path("/containers").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -466,37 +279,19 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 			http.Error(w, err.Error(), http.StatusInternalServerError)
 			return
 		}
-		var memoryW, swapW bool
-
-		if config.Memory > 0 && !rtime.capabilities.MemoryLimit {
-			memoryW = true
-			log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.")
-			config.Memory = 0
-		}
-
-		if config.Memory > 0 && !rtime.capabilities.SwapLimit {
-			swapW = true
-			log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.")
-			config.MemorySwap = -1
-		}
-		container, err := rtime.Create(&config)
+		id, memoryW, swapW, err := srv.ContainerCreate(config)
 		if err != nil {
-			if rtime.graph.IsNotExist(err) {
-				http.Error(w, "No such image: "+config.Image, http.StatusNotFound)
-			} else {
-				http.Error(w, err.Error(), http.StatusInternalServerError)
-			}
+			httpError(w, err)
 			return
 		}
 		var out ApiRun
-		out.Id = container.ShortId()
+		out.Id = id
 		if memoryW {
 			out.Warnings = append(out.Warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.")
 		}
 		if swapW {
 			out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.")
 		}
-
 		b, err := json.Marshal(out)
 		if err != nil {
 			http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -516,66 +311,44 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 		}
 		vars := mux.Vars(r)
 		name := vars["name"]
-		if container := rtime.Get(name); container != nil {
-			if err := container.Restart(t); err != nil {
-				http.Error(w, "Error restarting container "+name+": "+err.Error(), http.StatusInternalServerError)
-				return
-			}
+		if err := srv.ContainerRestart(name, t); err != nil {
+			httpError(w, err)
 		} else {
-			http.Error(w, "No such container: "+name, http.StatusNotFound)
-			return
+			w.WriteHeader(http.StatusOK)
 		}
-		w.WriteHeader(http.StatusOK)
 	})
 
 	r.Path("/containers/{name:.*}").Methods("DELETE").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		log.Println(r.Method, r.RequestURI)
 		vars := mux.Vars(r)
 		name := vars["name"]
-		if container := rtime.Get(name); container != nil {
-			if err := rtime.Destroy(container); err != nil {
-				http.Error(w, "Error destroying container "+name+": "+err.Error(), http.StatusInternalServerError)
-				return
-			}
+		if err := srv.ContainerDestroy(name); err != nil {
+			httpError(w, err)
 		} else {
-			http.Error(w, "No such container: "+name, http.StatusNotFound)
-			return
+			w.WriteHeader(http.StatusOK)
 		}
-		w.WriteHeader(http.StatusOK)
 	})
 
 	r.Path("/images/{name:.*}").Methods("DELETE").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		log.Println(r.Method, r.RequestURI)
 		vars := mux.Vars(r)
 		name := vars["name"]
-
-		img, err := rtime.repositories.LookupImage(name)
-		if err != nil {
-			http.Error(w, "No such image: "+name, http.StatusNotFound)
-			return
+		if err := srv.ImageDelete(name); err != nil {
+			httpError(w, err)
 		} else {
-			if err := rtime.graph.Delete(img.Id); err != nil {
-				http.Error(w, "Error deleting image "+name+": "+err.Error(), http.StatusInternalServerError)
-				return
-			}
+			w.WriteHeader(http.StatusOK)
 		}
-		w.WriteHeader(http.StatusOK)
 	})
 
 	r.Path("/containers/{name:.*}/start").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		log.Println(r.Method, r.RequestURI)
 		vars := mux.Vars(r)
 		name := vars["name"]
-		if container := rtime.Get(name); container != nil {
-			if err := container.Start(); err != nil {
-				http.Error(w, "Error starting container "+name+": "+err.Error(), http.StatusInternalServerError)
-				return
-			}
+		if err := srv.ContainerStart(name); err != nil {
+			httpError(w, err)
 		} else {
-			http.Error(w, "No such container: "+name, http.StatusNotFound)
-			return
+			w.WriteHeader(http.StatusOK)
 		}
-		w.WriteHeader(http.StatusOK)
 	})
 
 	r.Path("/containers/{name:.*}/stop").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -589,33 +362,27 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 		}
 		vars := mux.Vars(r)
 		name := vars["name"]
-		if container := rtime.Get(name); container != nil {
-			if err := container.Stop(t); err != nil {
-				http.Error(w, "Error stopping container "+name+": "+err.Error(), http.StatusInternalServerError)
-				return
-			}
+
+		if err := srv.ContainerStop(name, t); err != nil {
+			httpError(w, err)
 		} else {
-			http.Error(w, "No such container: "+name, http.StatusNotFound)
-			return
+			w.WriteHeader(http.StatusOK)
 		}
-		w.WriteHeader(http.StatusOK)
 	})
 
 	r.Path("/containers/{name:.*}/wait").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		log.Println(r.Method, r.RequestURI)
 		vars := mux.Vars(r)
 		name := vars["name"]
-		if container := rtime.Get(name); container != nil {
-			b, err := json.Marshal(ApiWait{container.Wait()})
-			if err != nil {
-				http.Error(w, err.Error(), http.StatusInternalServerError)
-			} else {
-				w.Write(b)
-			}
-			return
+		status, err := srv.ContainerWait(name)
+		if err != nil {
+			httpError(w, err)
+		}
+		b, err := json.Marshal(ApiWait{status})
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
 		} else {
-			http.Error(w, "No such container: "+name, http.StatusNotFound)
-			return
+			w.Write(b)
 		}
 	})
 
@@ -632,90 +399,21 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 		vars := mux.Vars(r)
 		name := vars["name"]
 
-		if container := rtime.Get(name); container != nil {
-			conn, _, err := w.(http.Hijacker).Hijack()
-			if err != nil {
-				http.Error(w, err.Error(), http.StatusInternalServerError)
-				return
-			}
-			defer conn.Close()
-			file, err := conn.(*net.TCPConn).File()
-			if err != nil {
-				http.Error(w, err.Error(), http.StatusInternalServerError)
-				return
-			}
+		file, rwc, err := hijackServer(w)
+		if file != nil {
 			defer file.Close()
+		}
+		if rwc != nil {
+			defer rwc.Close()
+		}
+		if err != nil {
+			httpError(w, err)
+			return
+		}
 
-			// Flush the options to make sure the client sets the raw mode
-			conn.Write([]byte{})
-
-			fmt.Fprintln(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n")
-			//logs
-			if logs == "1" {
-				if stdout == "1" {
-					cLog, err := container.ReadLog("stdout")
-					if err != nil {
-						Debugf(err.Error())
-					} else if _, err := io.Copy(file, cLog); err != nil {
-						Debugf(err.Error())
-					}
-				}
-				if stderr == "1" {
-					cLog, err := container.ReadLog("stderr")
-					if err != nil {
-						Debugf(err.Error())
-					} else if _, err := io.Copy(file, cLog); err != nil {
-						Debugf(err.Error())
-					}
-				}
-			}
-
-			//stream
-			if stream == "1" {
-
-				if container.State.Ghost {
-					fmt.Fprintf(file, "error: Impossible to attach to a ghost container")
-					return
-				}
-
-				if container.Config.Tty {
-					oldState, err := SetRawTerminal()
-					if err != nil {
-						if os.Getenv("DEBUG") != "" {
-							log.Printf("Can't set the terminal in raw mode: %s", err)
-						}
-					} else {
-						defer RestoreTerminal(oldState)
-					}
-
-				}
-				var (
-					cStdin           io.ReadCloser
-					cStdout, cStderr io.Writer
-					cStdinCloser     io.Closer
-				)
-
-				if stdin == "1" {
-					r, w := io.Pipe()
-					go func() {
-						defer w.Close()
-						defer Debugf("Closing buffered stdin pipe")
-						io.Copy(w, file)
-					}()
-					cStdin = r
-					cStdinCloser = file
-				}
-				if stdout == "1" {
-					cStdout = file
-				}
-				if stderr == "1" {
-					cStderr = file
-				}
-
-				<-container.Attach(cStdin, cStdinCloser, cStdout, cStderr)
-			}
-		} else {
-			http.Error(w, "No such container: "+name, http.StatusNotFound)
+		fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n")
+		if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, file); err != nil {
+			fmt.Fprintln(file, "Error: "+err.Error())
 		}
 	})
 
@@ -724,16 +422,16 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 		vars := mux.Vars(r)
 		name := vars["name"]
 
-		if container := rtime.Get(name); container != nil {
-			b, err := json.Marshal(container)
-			if err != nil {
-				http.Error(w, err.Error(), http.StatusInternalServerError)
-			} else {
-				w.Write(b)
-			}
-			return
+		container, err := srv.ContainerInspect(name)
+		if err != nil {
+			httpError(w, err)
+		}
+		b, err := json.Marshal(container)
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+		} else {
+			w.Write(b)
 		}
-		http.Error(w, "No such container: "+name, http.StatusNotFound)
 	})
 
 	r.Path("/images/{name:.*}").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -741,16 +439,16 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 		vars := mux.Vars(r)
 		name := vars["name"]
 
-		if image, err := rtime.repositories.LookupImage(name); err == nil && image != nil {
-			b, err := json.Marshal(image)
-			if err != nil {
-				http.Error(w, err.Error(), http.StatusInternalServerError)
-			} else {
-				w.Write(b)
-			}
-			return
+		image, err := srv.ImageInspect(name)
+		if err != nil {
+			httpError(w, err)
+		}
+		b, err := json.Marshal(image)
+		if err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+		} else {
+			w.Write(b)
 		}
-		http.Error(w, "No such image: "+name, http.StatusNotFound)
 	})
 
 	return http.ListenAndServe(addr, r)

+ 84 - 40
commands.go

@@ -24,9 +24,30 @@ var (
 	GIT_COMMIT string
 )
 
-func ParseCommands(args []string) error {
+func checkRemoteVersion() error {
+	body, _, err := call("GET", "/version", nil)
+	if err != nil {
+		return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
+	}
+
+	var out ApiVersion
+	err = json.Unmarshal(body, &out)
+	if err != nil {
+		return err
+	}
+	if out.Version != VERSION {
+		fmt.Fprintf(os.Stderr, "Warning: client and server don't have the same version (client: %s, server: %)", VERSION, out.Version)
+	}
+	return nil
+}
+
+func ParseCommands(args ...string) error {
+
+	if err := checkRemoteVersion(); err != nil {
+		return err
+	}
 
-	cmds := map[string]func(args []string) error{
+	cmds := map[string]func(args ...string) error{
 		"attach":  CmdAttach,
 		"commit":  CmdCommit,
 		"diff":    CmdDiff,
@@ -34,7 +55,7 @@ func ParseCommands(args []string) error {
 		"images":  CmdImages,
 		"info":    CmdInfo,
 		"inspect": CmdInspect,
-		//"import":  CmdImport,
+		"import":  CmdImport,
 		"history": CmdHistory,
 		"kill":    CmdKill,
 		"logs":    CmdLogs,
@@ -56,14 +77,14 @@ func ParseCommands(args []string) error {
 		cmd, exists := cmds[args[0]]
 		if !exists {
 			fmt.Println("Error: Command not found:", args[0])
-			return cmdHelp(args)
+			return cmdHelp(args...)
 		}
-		return cmd(args[1:])
+		return cmd(args[1:]...)
 	}
-	return cmdHelp(args)
+	return cmdHelp(args...)
 }
 
-func cmdHelp(args []string) error {
+func cmdHelp(args ...string) error {
 	help := "Usage: docker COMMAND [arg...]\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n"
 	for _, cmd := range [][]string{
 		{"attach", "Attach to a running container"},
@@ -72,7 +93,7 @@ func cmdHelp(args []string) error {
 		{"export", "Stream the contents of a container as a tar archive"},
 		{"history", "Show the history of an image"},
 		{"images", "List images"},
-		//{"import", "Create a new filesystem image from the contents of a tarball"},
+		{"import", "Create a new filesystem image from the contents of a tarball"},
 		{"info", "Display system-wide information"},
 		{"inspect", "Return low-level information on a container/image"},
 		{"kill", "Kill a running container"},
@@ -199,7 +220,7 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args ..
 */
 
 // 'docker wait': block until a container stops
-func CmdWait(args []string) error {
+func CmdWait(args ...string) error {
 	cmd := Subcmd("wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -225,7 +246,7 @@ func CmdWait(args []string) error {
 }
 
 // 'docker version': show version information
-func CmdVersion(args []string) error {
+func CmdVersion(args ...string) error {
 	cmd := Subcmd("version", "", "Show the docker version information.")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -258,7 +279,7 @@ func CmdVersion(args []string) error {
 }
 
 // 'docker info': display system-wide information.
-func CmdInfo(args []string) error {
+func CmdInfo(args ...string) error {
 	cmd := Subcmd("info", "", "Display system-wide information")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -286,7 +307,7 @@ func CmdInfo(args []string) error {
 	return nil
 }
 
-func CmdStop(args []string) error {
+func CmdStop(args ...string) error {
 	cmd := Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container")
 	nSeconds := cmd.Int("t", 10, "wait t seconds before killing the container")
 	if err := cmd.Parse(args); err != nil {
@@ -311,7 +332,7 @@ func CmdStop(args []string) error {
 	return nil
 }
 
-func CmdRestart(args []string) error {
+func CmdRestart(args ...string) error {
 	cmd := Subcmd("restart", "[OPTIONS] CONTAINER [CONTAINER...]", "Restart a running container")
 	nSeconds := cmd.Int("t", 10, "wait t seconds before killing the container")
 	if err := cmd.Parse(args); err != nil {
@@ -336,7 +357,7 @@ func CmdRestart(args []string) error {
 	return nil
 }
 
-func CmdStart(args []string) error {
+func CmdStart(args ...string) error {
 	cmd := Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -357,7 +378,7 @@ func CmdStart(args []string) error {
 	return nil
 }
 
-func CmdInspect(args []string) error {
+func CmdInspect(args ...string) error {
 	cmd := Subcmd("inspect", "CONTAINER|IMAGE", "Return low-level information on a container/image")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -377,7 +398,7 @@ func CmdInspect(args []string) error {
 	return nil
 }
 
-func CmdPort(args []string) error {
+func CmdPort(args ...string) error {
 	cmd := Subcmd("port", "CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -403,7 +424,7 @@ func CmdPort(args []string) error {
 }
 
 // 'docker rmi IMAGE' removes all images with the name IMAGE
-func CmdRmi(args []string) error {
+func CmdRmi(args ...string) error {
 	cmd := Subcmd("rmi", "IMAGE [IMAGE...]", "Remove an image")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -424,7 +445,7 @@ func CmdRmi(args []string) error {
 	return nil
 }
 
-func CmdHistory(args []string) error {
+func CmdHistory(args ...string) error {
 	cmd := Subcmd("history", "IMAGE", "Show the history of an image")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -454,7 +475,7 @@ func CmdHistory(args []string) error {
 	return nil
 }
 
-func CmdRm(args []string) error {
+func CmdRm(args ...string) error {
 	cmd := Subcmd("rm", "CONTAINER [CONTAINER...]", "Remove a container")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -476,7 +497,7 @@ func CmdRm(args []string) error {
 }
 
 // 'docker kill NAME' kills a running container
-func CmdKill(args []string) error {
+func CmdKill(args ...string) error {
 	cmd := Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -498,7 +519,7 @@ func CmdKill(args []string) error {
 }
 
 /* /!\ W.I.P /!\ */
-func CmdImport(args []string) error {
+func CmdImport(args ...string) error {
 	cmd := Subcmd("import", "URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball")
 
 	if err := cmd.Parse(args); err != nil {
@@ -512,7 +533,7 @@ func CmdImport(args []string) error {
 	v := url.Values{}
 	v.Set("repo", repository)
 	v.Set("tag", tag)
-	v.Set("src", src)
+	v.Set("fromSrc", src)
 
 	err := hijack("POST", "/images?"+v.Encode(), false)
 	if err != nil {
@@ -582,7 +603,7 @@ func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...
 }
 */
 
-func CmdPull(args []string) error {
+func CmdPull(args ...string) error {
 	cmd := Subcmd("pull", "NAME", "Pull an image or a repository from the registry")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -592,15 +613,17 @@ func CmdPull(args []string) error {
 		cmd.Usage()
 		return nil
 	}
+	v := url.Values{}
+	v.Set("fromImage", cmd.Arg(0))
 
-	if err := hijack("POST", "/images/"+cmd.Arg(0)+"/pull", false); err != nil {
+	if err := hijack("POST", "/images?"+v.Encode(), false); err != nil {
 		return err
 	}
 
 	return nil
 }
 
-func CmdImages(args []string) error {
+func CmdImages(args ...string) error {
 	cmd := Subcmd("images", "[OPTIONS] [NAME]", "List images")
 	quiet := cmd.Bool("q", false, "only show numeric IDs")
 	all := cmd.Bool("a", false, "show all images")
@@ -652,7 +675,7 @@ func CmdImages(args []string) error {
 	return nil
 }
 
-func CmdPs(args []string) error {
+func CmdPs(args ...string) error {
 	cmd := Subcmd("ps", "[OPTIONS]", "List containers")
 	quiet := cmd.Bool("q", false, "Only display numeric IDs")
 	all := cmd.Bool("a", false, "Show all containers. Only running containers are shown by default.")
@@ -709,7 +732,7 @@ func CmdPs(args []string) error {
 	return nil
 }
 
-func CmdCommit(args []string) error {
+func CmdCommit(args ...string) error {
 	cmd := Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]", "Create a new image from a container's changes")
 	flComment := cmd.String("m", "", "Commit message")
 	flAuthor := cmd.String("author", "", "Author (eg. \"John Hannibal Smith <hannibal@a-team.com>\"")
@@ -724,6 +747,7 @@ func CmdCommit(args []string) error {
 	}
 
 	v := url.Values{}
+	v.Set("fromContainer", name)
 	v.Set("repo", repository)
 	v.Set("tag", tag)
 	v.Set("comment", *flComment)
@@ -736,7 +760,7 @@ func CmdCommit(args []string) error {
 		}
 	}
 
-	body, _, err := call("POST", "/containers/"+name+"/commit?"+v.Encode(), config)
+	body, _, err := call("POST", "/images?"+v.Encode(), config)
 	if err != nil {
 		return err
 	}
@@ -751,7 +775,7 @@ func CmdCommit(args []string) error {
 	return nil
 }
 
-func CmdExport(args []string) error {
+func CmdExport(args ...string) error {
 	cmd := Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -768,7 +792,7 @@ func CmdExport(args []string) error {
 	return nil
 }
 
-func CmdDiff(args []string) error {
+func CmdDiff(args ...string) error {
 	cmd := Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -794,7 +818,7 @@ func CmdDiff(args []string) error {
 	return nil
 }
 
-func CmdLogs(args []string) error {
+func CmdLogs(args ...string) error {
 	cmd := Subcmd("logs", "CONTAINER", "Fetch the logs of a container")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -815,7 +839,7 @@ func CmdLogs(args []string) error {
 	return nil
 }
 
-func CmdAttach(args []string) error {
+func CmdAttach(args ...string) error {
 	cmd := Subcmd("attach", "CONTAINER", "Attach to a running container")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -906,7 +930,7 @@ func (opts AttachOpts) Get(val string) bool {
 	return false
 }
 
-func CmdTag(args []string) error {
+func CmdTag(args ...string) error {
 	cmd := Subcmd("tag", "[OPTIONS] IMAGE REPOSITORY [TAG]", "Tag an image into a repository")
 	force := cmd.Bool("f", false, "Force")
 	if err := cmd.Parse(args); err != nil {
@@ -933,7 +957,7 @@ func CmdTag(args []string) error {
 	return nil
 }
 
-func CmdRun(args []string) error {
+func CmdRun(args ...string) error {
 	config, cmd, err := ParseRun(args)
 	if err != nil {
 		return err
@@ -952,11 +976,18 @@ func CmdRun(args []string) error {
 
 	//if image not found try to pull it
 	if statusCode == 404 {
-		err = hijack("POST", "/images/"+config.Image+"/pull", false)
+		v := url.Values{}
+		v.Set("fromImage", config.Image)
+		err = hijack("POST", "/images?"+v.Encode(), false)
 		if err != nil {
 			return err
 		}
 		body, _, err = call("POST", "/containers", *config)
+		if err != nil {
+			return err
+		}
+		return nil
+
 	}
 	if err != nil {
 		return err
@@ -1001,6 +1032,18 @@ func CmdRun(args []string) error {
 		if err := hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty); err != nil {
 			return err
 		}
+		body, _, err := call("POST", "/containers/"+out.Id+"/wait", nil)
+		if err != nil {
+			fmt.Printf("%s", err)
+		} else {
+			var out ApiWait
+			err = json.Unmarshal(body, &out)
+			if err != nil {
+				return err
+			}
+			os.Exit(out.StatusCode)
+		}
+
 	} else {
 		fmt.Println(out.Id)
 	}
@@ -1054,9 +1097,14 @@ func hijack(method, path string, setRawTerminal bool) error {
 	clientconn.Do(req)
 	defer clientconn.Close()
 
-	rwc, _ := clientconn.Hijack()
+	rwc, br := clientconn.Hijack()
 	defer rwc.Close()
 
+	receiveStdout := Go(func() error {
+		_, err := io.Copy(os.Stdout, br)
+		return err
+	})
+
 	if setRawTerminal && term.IsTerminal(int(os.Stdin.Fd())) && os.Getenv("NORAW") == "" {
 		if oldState, err := SetRawTerminal(); err != nil {
 			return err
@@ -1065,10 +1113,6 @@ func hijack(method, path string, setRawTerminal bool) error {
 		}
 	}
 
-	receiveStdout := Go(func() error {
-		_, err := io.Copy(os.Stdout, rwc)
-		return err
-	})
 	sendStdin := Go(func() error {
 		_, err := io.Copy(rwc, os.Stdin)
 		rwc.Close()

+ 6 - 3
commands_test.go

@@ -1,16 +1,17 @@
 package docker
 
 import (
-	"bufio"
+	/*"bufio"
 	"fmt"
 	"github.com/dotcloud/docker/rcli"
 	"io"
 	"io/ioutil"
-	"strings"
+	"strings"*/
 	"testing"
 	"time"
 )
 
+/*TODO
 func closeWrap(args ...io.Closer) error {
 	e := false
 	ret := fmt.Errorf("Error closing elements")
@@ -25,7 +26,7 @@ func closeWrap(args ...io.Closer) error {
 	}
 	return nil
 }
-
+*/
 func setTimeout(t *testing.T, msg string, d time.Duration, f func()) {
 	c := make(chan bool)
 
@@ -43,6 +44,7 @@ func setTimeout(t *testing.T, msg string, d time.Duration, f func()) {
 	}
 }
 
+/*TODO
 func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error {
 	for i := 0; i < count; i++ {
 		if _, err := w.Write([]byte(input)); err != nil {
@@ -396,3 +398,4 @@ func TestAttachDisconnect(t *testing.T) {
 	cStdin.Close()
 	container.Wait()
 }
+*/

+ 5 - 51
docker/docker.go

@@ -4,14 +4,10 @@ import (
 	"flag"
 	"fmt"
 	"github.com/dotcloud/docker"
-	"github.com/dotcloud/docker/rcli"
-	"github.com/dotcloud/docker/term"
-	"io"
 	"io/ioutil"
 	"log"
 	"os"
 	"os/signal"
-	"runtime"
 	"strconv"
 	"syscall"
 )
@@ -49,10 +45,12 @@ func main() {
 		}
 		if err := daemon(*pidfile, *flAutoRestart); err != nil {
 			log.Fatal(err)
+			os.Exit(-1)
 		}
 	} else {
-		if err := docker.ParseCommands(flag.Args()); err != nil {
+		if err := docker.ParseCommands(flag.Args()...); err != nil {
 			log.Fatal(err)
+			os.Exit(-1)
 		}
 	}
 }
@@ -99,54 +97,10 @@ func daemon(pidfile string, autoRestart bool) error {
 		os.Exit(0)
 	}()
 
-	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 := docker.NewRuntime(autoRestart)
+	server, err := docker.NewServer(autoRestart)
 	if err != nil {
 		return err
 	}
 
-	return docker.ListenAndServe("0.0.0.0:4243", runtime)
-}
-
-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)
 }

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

+ 448 - 0
server.go

@@ -0,0 +1,448 @@
+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, file *os.File) 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(file, data); err != nil {
+			return err
+		}
+		return nil
+	}
+	return fmt.Errorf("No such container: %s", name)
+}
+
+func (srv *Server) Images(all, filter, quiet string) ([]ApiImages, error) {
+	var allImages map[string]*Image
+	var err error
+	if all == "1" {
+		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 quiet != "1" {
+				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 quiet != "1" {
+				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
+	if os.Getenv("DEBUG") == "1" {
+		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) ([]string, error) {
+	if container := srv.runtime.Get(name); container != nil {
+		changes, err := container.Changes()
+		if err != nil {
+			return nil, err
+		}
+		var changesStr []string
+		for _, name := range changes {
+			changesStr = append(changesStr, name.String())
+		}
+		return changesStr, nil
+	}
+	return nil, fmt.Errorf("No such container: %s", name)
+}
+
+func (srv *Server) ContainerPort(name, privatePort string) (string, error) {
+	if container := srv.runtime.Get(name); container != nil {
+		if frontend, exists := container.NetworkSettings.PortMapping[privatePort]; exists {
+			return frontend, nil
+		}
+		return "", fmt.Errorf("No private port '%s' allocated on %s", privatePort, name)
+	}
+	return "", fmt.Errorf("No such container: %s", name)
+}
+
+func (srv *Server) Containers(all, notrunc, quiet string, n int) []ApiContainers {
+	var outs []ApiContainers = []ApiContainers{} //produce [] when empty instead of 'null'
+	for i, container := range srv.runtime.List() {
+		if !container.State.Running && all != "1" && n == -1 {
+			continue
+		}
+		if i == n {
+			break
+		}
+		var out ApiContainers
+		out.Id = container.ShortId()
+		if quiet != "1" {
+			command := fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " "))
+			if notrunc != "1" {
+				command = Trunc(command, 20)
+			}
+			out.Image = srv.runtime.repositories.ImageName(container.Image)
+			out.Command = command
+			out.Created = container.Created.Unix()
+			out.Status = container.State.String()
+			out.Ports = container.NetworkSettings.PortMappingHuman()
+		}
+		outs = append(outs, out)
+	}
+	return outs
+}
+
+func (srv *Server) ContainerCommit(name, repo, tag, author, comment string, config *Config) (string, error) {
+	img, err := srv.runtime.Commit(name, 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 string, file *os.File) error {
+	if srv.runtime.graph.LookupRemoteImage(name, srv.runtime.authConfig) {
+		if err := srv.runtime.graph.PullImage(file, name, srv.runtime.authConfig); err != nil {
+			return err
+		}
+	}
+	if err := srv.runtime.graph.PullRepository(file, name, "", srv.runtime.repositories, srv.runtime.authConfig); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (srv *Server) ImageImport(src, repo, tag string, file *os.File) error {
+	var archive io.Reader
+	var resp *http.Response
+
+	if src == "-" {
+		r, w := io.Pipe()
+		go func() {
+			defer w.Close()
+			defer Debugf("Closing buffered stdin pipe")
+			io.Copy(w, file)
+		}()
+		archive = r
+	} else {
+		u, err := url.Parse(src)
+		if err != nil {
+			fmt.Fprintln(file, "Error: "+err.Error())
+		}
+		if u.Scheme == "" {
+			u.Scheme = "http"
+			u.Host = src
+			u.Path = ""
+		}
+		fmt.Fprintln(file, "Downloading from", u)
+		// Download with curl (pretty progress bar)                                                                            
+		// If curl is not available, fallback to http.Get()                                                                    
+		resp, err = Download(u.String(), file)
+		if err != nil {
+			return err
+		}
+		archive = ProgressReader(resp.Body, int(resp.ContentLength), file, "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(file, img.ShortId())
+	return nil
+}
+
+func (srv *Server) ContainerCreate(config Config) (string, bool, bool, error) {
+	var memoryW, swapW bool
+
+	if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
+		memoryW = true
+		log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.")
+		config.Memory = 0
+	}
+
+	if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit {
+		swapW = true
+		log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.")
+		config.MemorySwap = -1
+	}
+	container, err := srv.runtime.Create(&config)
+	if err != nil {
+		if srv.runtime.graph.IsNotExist(err) {
+			return "", false, false, fmt.Errorf("No such image: %s", config.Image)
+		}
+		return "", false, false, err
+	}
+	return container.ShortId(), memoryW, swapW, 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) error {
+	if container := srv.runtime.Get(name); container != nil {
+		if err := srv.runtime.Destroy(container); err != nil {
+			return fmt.Errorf("Error destroying container %s: %s", name, err.Error())
+		}
+	} 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 deleteing 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, logs, stream, stdin, stdout, stderr string, file *os.File) error {
+	if container := srv.runtime.Get(name); container != nil {
+		//logs
+		if logs == "1" {
+			if stdout == "1" {
+				cLog, err := container.ReadLog("stdout")
+				if err != nil {
+					Debugf(err.Error())
+				} else if _, err := io.Copy(file, cLog); err != nil {
+					Debugf(err.Error())
+				}
+			}
+			if stderr == "1" {
+				cLog, err := container.ReadLog("stderr")
+				if err != nil {
+					Debugf(err.Error())
+				} else if _, err := io.Copy(file, cLog); err != nil {
+					Debugf(err.Error())
+				}
+			}
+		}
+
+		//stream
+		if stream == "1" {
+			if container.State.Ghost {
+				return fmt.Errorf("Impossible to attach to a ghost container")
+			}
+
+			if container.Config.Tty {
+				oldState, err := SetRawTerminal()
+				if err != nil {
+					if os.Getenv("DEBUG") != "" {
+						log.Printf("Can't set the terminal in raw mode: %s", err)
+					}
+				} else {
+					defer RestoreTerminal(oldState)
+				}
+			}
+			var (
+				cStdin           io.ReadCloser
+				cStdout, cStderr io.Writer
+				cStdinCloser     io.Closer
+			)
+
+			if stdin == "1" {
+				r, w := io.Pipe()
+				go func() {
+					defer w.Close()
+					defer Debugf("Closing buffered stdin pipe")
+					io.Copy(w, file)
+				}()
+				cStdin = r
+				cStdinCloser = file
+			}
+			if stdout == "1" {
+				cStdout = file
+			}
+			if stderr == "1" {
+				cStderr = file
+			}
+
+			<-container.Attach(cStdin, cStdinCloser, cStdout, cStderr)
+		}
+	} else {
+		return fmt.Errorf("No such container: %s", name)
+	}
+	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
+}