Selaa lähdekoodia

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

Victor Vieux 12 vuotta sitten
vanhempi
commit
04cd20fa62
7 muutettua tiedostoa jossa 718 lisäystä ja 571 poistoa
  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
+}