浏览代码

added run (wip), fixed ps and images, added port and tag

Victor Vieux 12 年之前
父节点
当前提交
cf19be44a8
共有 5 个文件被更改,包括 327 次插入254 次删除
  1. 184 56
      api.go
  2. 10 35
      api_params.go
  3. 123 153
      commands.go
  4. 5 4
      container.go
  5. 5 6
      registry.go

+ 184 - 56
api.go

@@ -1,12 +1,14 @@
 package docker
 
 import (
-	_"bytes"
+	_ "bytes"
 	"encoding/json"
 	"fmt"
 	"github.com/gorilla/mux"
+	"io"
 	"io/ioutil"
 	"log"
+	"net"
 	"net/http"
 	"os"
 	"runtime"
@@ -20,8 +22,8 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 	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.RequestURI)
-		m := VersionOut{VERSION, GIT_COMMIT, NO_MEMORY_LIMIT}
+		log.Println(r.Method, r.RequestURI)
+		m := ApiVersion{VERSION, GIT_COMMIT, NO_MEMORY_LIMIT}
 		b, err := json.Marshal(m)
 		if err != nil {
 			http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -31,26 +33,29 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 	})
 
 	r.Path("/containers/{name:.*}/kill").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		log.Println(r.RequestURI)
+		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
-                        }
-                } else {
-                        http.Error(w, "No such container: "+name, http.StatusInternalServerError)
-                        return
-                }
-                w.WriteHeader(200)
+		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
+			}
+		} else {
+			http.Error(w, "No such container: "+name, http.StatusInternalServerError)
+			return
+		}
+		w.WriteHeader(200)
 	})
 
 	r.Path("/images").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		log.Println(r.RequestURI)
+		log.Println(r.Method, r.RequestURI)
+		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")
+		NameFilter := r.Form.Get("filter")
+		Quiet := r.Form.Get("quiet")
 
 		var allImages map[string]*Image
 		var err error
@@ -63,13 +68,13 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 			w.WriteHeader(500)
 			return
 		}
-		var outs []ImagesOut
+		var outs []ApiImages
 		for name, repository := range rtime.repositories.Repositories {
 			if NameFilter != "" && name != NameFilter {
 				continue
 			}
 			for tag, id := range repository {
-				var out ImagesOut
+				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)
@@ -90,7 +95,7 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 		// Display images which aren't part of a
 		if NameFilter == "" {
 			for id, image := range allImages {
-				var out ImagesOut
+				var out ApiImages
 				if Quiet != "true" {
 					out.Repository = "<none>"
 					out.Tag = "<none>"
@@ -112,7 +117,7 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 	})
 
 	r.Path("/info").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		log.Println(r.RequestURI)
+		log.Println(r.Method, r.RequestURI)
 		images, _ := rtime.graph.All()
 		var imgcount int
 		if images == nil {
@@ -120,7 +125,7 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 		} else {
 			imgcount = len(images)
 		}
-		var out InfoOut
+		var out ApiInfo
 		out.Containers = len(rtime.List())
 		out.Version = VERSION
 		out.Images = imgcount
@@ -138,7 +143,7 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 	})
 
 	r.Path("/images/{name:.*}/history").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		log.Println(r.RequestURI)
+		log.Println(r.Method, r.RequestURI)
 		vars := mux.Vars(r)
 		name := vars["name"]
 
@@ -147,9 +152,9 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 			http.Error(w, err.Error(), http.StatusInternalServerError)
 			return
 		}
-		var outs []HistoryOut
+		var outs []ApiHistory
 		err = image.WalkHistory(func(img *Image) error {
-			var out HistoryOut
+			var out ApiHistory
 			out.Id = rtime.repositories.ImageName(img.ShortId())
 			out.Created = HumanDuration(time.Now().Sub(img.Created)) + " ago"
 			out.CreatedBy = strings.Join(img.ContainerConfig.Cmd, " ")
@@ -165,12 +170,12 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 	})
 
 	r.Path("/containers/{name:.*}/logs").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		log.Println(r.RequestURI)
+		log.Println(r.Method, r.RequestURI)
 		vars := mux.Vars(r)
 		name := vars["name"]
 
 		if container := rtime.Get(name); container != nil {
-			var out LogsOut
+			var out ApiLogs
 
 			logStdout, err := container.ReadLog("stdout")
 			if err != nil {
@@ -210,17 +215,47 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 		}
 	})
 
+	r.Path("/containers/{name:.*}/port").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)
+		}
+		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.StatusInternalServerError)
+			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.RequestURI)
+		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")
+		NoTrunc := r.Form.Get("notrunc")
+		Quiet := r.Form.Get("quiet")
 		Last := r.Form.Get("n")
 		n, err := strconv.Atoi(Last)
 		if err != nil {
 			n = -1
 		}
-		var outs []PsOut
+		var outs []ApiContainers
 
 		for i, container := range rtime.List() {
 			if !container.State.Running && All != "true" && n == -1 {
@@ -229,7 +264,7 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 			if i == n {
 				break
 			}
-			var out PsOut
+			var out ApiContainers
 			out.Id = container.ShortId()
 			if Quiet != "true" {
 				command := fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " "))
@@ -252,41 +287,134 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 		}
 	})
 
-	r.Path("/pull").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		var in PullIn
-		//json.NewDecoder(r.Body).Decode(&in)
-		in.Name = "base"
+	r.Path("/images/{name:.*}/tag").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)
+		}
+		vars := mux.Vars(r)
+		name := vars["name"]
+		repo := r.Form.Get("repo")
+		tag := r.Form.Get("tag")
+		var force bool
+		if r.Form.Get("force") == "true" {
+			force = true
+		}
 
-		hj, ok := w.(http.Hijacker)
-		if !ok {
-			http.Error(w, "webserver doesn't support hijacking", http.StatusInternalServerError)
+		if err := rtime.repositories.Set(repo, tag, name, force); err != nil {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
 			return
 		}
-		conn, bufrw, err := hj.Hijack()
+		w.WriteHeader(200)
+	})
+
+	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
 		}
-		// Don't forget to close the connection:
 		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: application/json\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())
+		}
+	})
 
+	r.Path("/containers").Methods("POST").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
+		}
+
+		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()
 
-		if rtime.graph.LookupRemoteImage(in.Name, rtime.authConfig) {
-			if err := rtime.graph.PullImage(bufrw, in.Name, rtime.authConfig); err != nil {
-				//http.Error(w, err.Error(), http.StatusInternalServerError)
+		fmt.Fprintln(file, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n")
+		container, err := rtime.Create(&config)
+		if err != nil {
+			// If container not found, try to pull it
+			if rtime.graph.IsNotExist(err) {
+				fmt.Fprintf(file, "Image %s not found, trying to pull it from registry.\r\n", config.Image)
+				//if err = srv.CmdPull(stdin, stdout, config.Image); err != nil {
+				//	fmt.Fprintln(file, "Error: "+err.Error())
+				//	return
+				//}
+				//if container, err = srv.runtime.Create(config); err != nil {
+				//	fmt.Fprintln(file, "Error: "+err.Error())
+				//	return
+				//}
+			} else {
+				fmt.Fprintln(file, "Error: "+err.Error())
+				return
 			}
-			return 
 		}
-		if err := rtime.graph.PullRepository(bufrw, in.Name, "", rtime.repositories, rtime.authConfig); err != nil {
-			//http.Error(w, err.Error(), http.StatusInternalServerError)
+		var (
+			cStdin           io.ReadCloser
+			cStdout, cStderr io.Writer
+		)
+		if config.AttachStdin {
+			r, w := io.Pipe()
+			go func() {
+				defer w.Close()
+				defer Debugf("Closing buffered stdin pipe")
+				io.Copy(w, file)
+			}()
+			cStdin = r
+		}
+		if config.AttachStdout {
+			cStdout = file
+		}
+		if config.AttachStderr {
+			cStderr = file // FIXME: rcli can't differentiate stdout from stderr                                                         
 		}
-		return
-	})
 
+		attachErr := container.Attach(cStdin, file, cStdout, cStderr)
+		Debugf("Starting\n")
+		if err := container.Start(); err != nil {
+			fmt.Fprintln(file, "Error: "+err.Error())
+			return
+		}
+		if cStdout == nil && cStderr == nil {
+			fmt.Fprintln(file, container.ShortId())
+		}
+		Debugf("Waiting for attach to return\n")
+		<-attachErr
+		// Expecting I/O pipe error, discarding        
+
+	})
 
 	r.Path("/containers/{name:.*}/restart").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		log.Println(r.RequestURI)
+		log.Println(r.Method, r.RequestURI)
 		vars := mux.Vars(r)
 		name := vars["name"]
 		if container := rtime.Get(name); container != nil {
@@ -302,7 +430,7 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 	})
 
 	r.Path("/containers/{name:.*}").Methods("DELETE").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		log.Println(r.RequestURI)
+		log.Println(r.Method, r.RequestURI)
 		vars := mux.Vars(r)
 		name := vars["name"]
 		if container := rtime.Get(name); container != nil {
@@ -318,10 +446,10 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 	})
 
 	r.Path("/images/{name:.*}").Methods("DELETE").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		log.Println(r.RequestURI)
+		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.StatusInternalServerError)
@@ -336,7 +464,7 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 	})
 
 	r.Path("/containers/{name:.*}/start").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		log.Println(r.RequestURI)
+		log.Println(r.Method, r.RequestURI)
 		vars := mux.Vars(r)
 		name := vars["name"]
 		if container := rtime.Get(name); container != nil {
@@ -352,7 +480,7 @@ func ListenAndServe(addr string, rtime *Runtime) error {
 	})
 
 	r.Path("/containers/{name:.*}/stop").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		log.Println(r.RequestURI)
+		log.Println(r.Method, r.RequestURI)
 		vars := mux.Vars(r)
 		name := vars["name"]
 		if container := rtime.Get(name); container != nil {

+ 10 - 35
api_params.go

@@ -1,33 +1,19 @@
 package docker
 
-type SimpleMessage struct {
-	Message string
-}
-
-type HistoryIn struct {
-	Name string
-}
-
-type HistoryOut struct {
+type ApiHistory struct {
 	Id        string
 	Created   string
 	CreatedBy string
 }
 
-type ImagesIn struct {
-	NameFilter string
-	Quiet      bool
-	All        bool
-}
-
-type ImagesOut struct {
+type ApiImages struct {
 	Repository string `json:",omitempty"`
 	Tag        string `json:",omitempty"`
 	Id         string
 	Created    string `json:",omitempty"`
 }
 
-type InfoOut struct {
+type ApiInfo struct {
 	Containers  int
 	Version     string
 	Images      int
@@ -36,14 +22,7 @@ type InfoOut struct {
 	NGoroutines int `json:",omitempty"`
 }
 
-type PsIn struct {
-	Quiet bool
-	All   bool
-	Full  bool
-	Last  int
-}
-
-type PsOut struct {
+type ApiContainers struct {
 	Id      string
 	Image   string `json:",omitempty"`
 	Command string `json:",omitempty"`
@@ -51,20 +30,16 @@ type PsOut struct {
 	Status  string `json:",omitempty"`
 }
 
-type PullIn struct {
-	Name string
-}
-
-type LogsIn struct {
-	Name string
-}
-
-type LogsOut struct {
+type ApiLogs struct {
 	Stdout string
 	Stderr string
 }
 
-type VersionOut struct {
+type ApiPort struct {
+	Port string
+}
+
+type ApiVersion struct {
 	Version             string
 	GitCommit           string
 	MemoryLimitDisabled bool

+ 123 - 153
commands.go

@@ -1,13 +1,15 @@
 package docker
 
 import (
-	_"bytes"
+	"bytes"
 	"encoding/json"
 	"flag"
 	"fmt"
-	_"io"
+	"io"
 	"io/ioutil"
+	"net"
 	"net/http"
+	"net/http/httputil"
 	"net/url"
 	"os"
 	"strconv"
@@ -29,10 +31,14 @@ func ParseCommands(args []string) error {
 		"history": CmdHistory,
 		"kill":    CmdKill,
 		"logs":    CmdLogs,
+		"port":    CmdPort,
 		"ps":      CmdPs,
+		"pull":    CmdPull,
 		"restart": CmdRestart,
 		"rm":      CmdRm,
 		"rmi":     CmdRmi,
+		"run":     CmdRun,
+		"tag":     CmdTag,
 		"start":   CmdStart,
 		"stop":    CmdStop,
 		"version": CmdVersion,
@@ -41,7 +47,7 @@ func ParseCommands(args []string) error {
 	if len(args) > 0 {
 		cmd, exists := cmds[args[0]]
 		if !exists {
-			//TODO display commend not found
+			fmt.Println("Error: Command not found:", args[0])
 			return cmdHelp(args)
 		}
 		return cmd(args[1:])
@@ -64,17 +70,17 @@ func cmdHelp(args []string) error {
 		{"kill", "Kill a running container"},
 		//		{"login", "Register or Login to the docker registry server"},
 		{"logs", "Fetch the logs of a container"},
-		//		{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
+		{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
 		{"ps", "List containers"},
-		//		{"pull", "Pull an image or a repository from the docker registry server"},
+		{"pull", "Pull an image or a repository from the docker registry server"},
 		//		{"push", "Push an image or a repository to the docker registry server"},
 		{"restart", "Restart a running container"},
 		{"rm", "Remove a container"},
 		{"rmi", "Remove an image"},
-		//		{"run", "Run a command in a new container"},
+		{"run", "Run a command in a new container"},
 		{"start", "Start a stopped container"},
 		{"stop", "Stop a running container"},
-		//		{"tag", "Tag an image into a repository"},
+		{"tag", "Tag an image into a repository"},
 		{"version", "Show the docker version information"},
 		//		{"wait", "Block until a container stops, then print its exit code"},
 	} {
@@ -206,7 +212,6 @@ func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string
 }
 */
 
-
 // 'docker version': show version information
 func CmdVersion(args []string) error {
 	cmd := Subcmd("version", "", "Show the docker version information.")
@@ -218,12 +223,12 @@ func CmdVersion(args []string) error {
 		return nil
 	}
 
-	body, err := call("GET", "version")
+	body, err := call("GET", "/version")
 	if err != nil {
 		return err
 	}
 
-	var out VersionOut
+	var out ApiVersion
 	err = json.Unmarshal(body, &out)
 	if err != nil {
 		return err
@@ -248,12 +253,12 @@ func CmdInfo(args []string) error {
 		return nil
 	}
 
-	body, err := call("GET", "info")
+	body, err := call("GET", "/info")
 	if err != nil {
 		return err
 	}
 
-	var out InfoOut
+	var out ApiInfo
 	err = json.Unmarshal(body, &out)
 	if err != nil {
 		return err
@@ -277,7 +282,7 @@ func CmdStop(args []string) error {
 	}
 
 	for _, name := range args {
-		_, err := call("GET", "/containers/"+name+"/stop")
+		_, err := call("POST", "/containers/"+name+"/stop")
 		if err != nil {
 			fmt.Printf("%s", err)
 		} else {
@@ -298,7 +303,7 @@ func CmdRestart(args []string) error {
 	}
 
 	for _, name := range args {
-		_, err := call("GET", "/containers/"+name+"/restart")
+		_, err := call("POST", "/containers/"+name+"/restart")
 		if err != nil {
 			fmt.Printf("%s", err)
 		} else {
@@ -319,7 +324,7 @@ func CmdStart(args []string) error {
 	}
 
 	for _, name := range args {
-		_, err := call("GET", "/containers/"+name+"/start")
+		_, err := call("POST", "/containers/"+name+"/start")
 		if err != nil {
 			fmt.Printf("%s", err)
 		} else {
@@ -366,9 +371,8 @@ func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...str
 }
 */
 
-/*
-func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
-	cmd := rcli.Subcmd(stdout, "port", "CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT")
+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
 	}
@@ -376,20 +380,21 @@ func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string
 		cmd.Usage()
 		return nil
 	}
-	name := cmd.Arg(0)
-	privatePort := cmd.Arg(1)
-	if container := srv.runtime.Get(name); container == nil {
-		return fmt.Errorf("No such container: %s", name)
-	} else {
-		if frontend, exists := container.NetworkSettings.PortMapping[privatePort]; !exists {
-			return fmt.Errorf("No private port '%s' allocated on %s", privatePort, name)
-		} else {
-			fmt.Fprintln(stdout, frontend)
-		}
+	v := url.Values{}
+	v.Set("port", cmd.Arg(1))
+	body, err := call("GET", "/containers/"+cmd.Arg(0)+"/port?"+v.Encode())
+	if err != nil {
+		return err
+	}
+
+	var out ApiPort
+	err = json.Unmarshal(body, &out)
+	if err != nil {
+		return err
 	}
+	fmt.Println(out.Port)
 	return nil
 }
-*/
 
 // 'docker rmi IMAGE' removes all images with the name IMAGE
 func CmdRmi(args []string) error {
@@ -423,12 +428,12 @@ func CmdHistory(args []string) error {
 		return nil
 	}
 
-	body, err := call("GET", "images/"+cmd.Arg(0)+"/history")
+	body, err := call("GET", "/images/"+cmd.Arg(0)+"/history")
 	if err != nil {
 		return err
 	}
 
-	var outs []HistoryOut
+	var outs []ApiHistory
 	err = json.Unmarshal(body, &outs)
 	if err != nil {
 		return err
@@ -599,38 +604,29 @@ func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...
 }
 */
 
-/*
-func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
-	cmd := rcli.Subcmd(stdout, "pull", "NAME", "Pull an image or a repository from the registry")
+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
 	}
-	remote := cmd.Arg(0)
-	if remote == "" {
+
+	if cmd.NArg() != 1 {
 		cmd.Usage()
 		return nil
 	}
 
-	// FIXME: CmdPull should be a wrapper around Runtime.Pull()
-	if srv.runtime.graph.LookupRemoteImage(remote, srv.runtime.authConfig) {
-	//	if err := srv.runtime.graph.PullImage(stdout, remote, srv.runtime.authConfig); err != nil {
-	//		return err
-	//	}
-		return nil
+	if err := callStream("POST", "/images/"+cmd.Arg(0)+"/pull", nil, false); err != nil {
+		return err
 	}
-	// FIXME: Allow pull repo:tag
-	//if err := srv.runtime.graph.PullRepository(stdout, remote, "", srv.runtime.repositories, srv.runtime.authConfig); err != nil {
-	//	return err
-	//}
+
 	return nil
 }
-*/
 
 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")
-	
+
 	if err := cmd.Parse(args); err != nil {
 		return nil
 	}
@@ -649,12 +645,12 @@ func CmdImages(args []string) error {
 		v.Set("all", "true")
 	}
 
-	body, err := call("GET", "images?"+v.Encode())
+	body, err := call("GET", "/images?"+v.Encode())
 	if err != nil {
 		return err
 	}
 
-	var outs []ImagesOut
+	var outs []ApiImages
 	err = json.Unmarshal(body, &outs)
 	if err != nil {
 		return err
@@ -705,13 +701,13 @@ func CmdPs(args []string) error {
 	if *last != -1 {
 		v.Set("n", strconv.Itoa(*last))
 	}
-	
-	body, err := call("GET", "containers?"+v.Encode())
+
+	body, err := call("GET", "/containers?"+v.Encode())
 	if err != nil {
 		return err
 	}
 
-	var outs []PsOut
+	var outs []ApiContainers
 	err = json.Unmarshal(body, &outs)
 	if err != nil {
 		return err
@@ -723,7 +719,7 @@ func CmdPs(args []string) error {
 
 	for _, out := range outs {
 		if !*quiet {
-			fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", out.Id, out.Image, out.Command, out.Status, out.Created)
+			fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", out.Id, out.Image, out.Command, out.Status, out.Created)
 		} else {
 			fmt.Fprintln(w, out.Id)
 		}
@@ -818,12 +814,12 @@ func CmdLogs(args []string) error {
 		cmd.Usage()
 		return nil
 	}
-	body, err := call("GET", "containers/"+cmd.Arg(0)+"/logs")
+	body, err := call("GET", "/containers/"+cmd.Arg(0)+"/logs")
 	if err != nil {
 		return err
 	}
 
-	var out LogsOut
+	var out ApiLogs
 	err = json.Unmarshal(body, &out)
 	if err != nil {
 		return err
@@ -915,98 +911,56 @@ func (opts AttachOpts) Get(val string) bool {
 	return false
 }
 
-
-/*
-func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
-	cmd := rcli.Subcmd(stdout, "tag", "[OPTIONS] IMAGE REPOSITORY [TAG]", "Tag an image into a repository")
+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 {
 		return nil
 	}
-	if cmd.NArg() < 2 {
+	if cmd.NArg() != 2 && cmd.NArg() != 3 {
 		cmd.Usage()
 		return nil
 	}
-	return srv.runtime.repositories.Set(cmd.Arg(1), cmd.Arg(2), cmd.Arg(0), *force)
+
+	v := url.Values{}
+	v.Set("repo", cmd.Arg(1))
+	if cmd.NArg() == 3 {
+		v.Set("tag", cmd.Arg(2))
+	}
+
+	if *force {
+		v.Set("force", "true")
+	}
+
+	if err := callStream("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil, false); err != nil {
+		return err
+	}
+	return nil
 }
-*/
 
-/*
-func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
-	config, err := ParseRun(args)
+func CmdRun(args []string) error {
+	fmt.Println("CmdRun")
+	config, cmd, err := ParseRun(args)
 	if err != nil {
 		return err
 	}
 	if config.Image == "" {
-		fmt.Fprintln(stdout, "Error: Image not specified")
-		return fmt.Errorf("Image not specified")
+		cmd.Usage()
+		return nil
 	}
 	if len(config.Cmd) == 0 {
-		fmt.Fprintln(stdout, "Error: Command not specified")
-		return fmt.Errorf("Command not specified")
-	}
-
-	if config.Tty {
-		stdout.SetOptionRawTerminal()
+		cmd.Usage()
+		return nil
 	}
-	// Flush the options to make sure the client sets the raw mode
-	// or tell the client there is no options
-	stdout.Flush()
 
-	// Create new container
-	container, err := srv.runtime.Create(config)
-	if err != nil {
-		// If container not found, try to pull it
-		if srv.runtime.graph.IsNotExist(err) {
-			fmt.Fprintf(stdout, "Image %s not found, trying to pull it from registry.\r\n", config.Image)
-			if err = srv.CmdPull(stdin, stdout, config.Image); err != nil {
-				return err
-			}
-			if container, err = srv.runtime.Create(config); err != nil {
-				return err
-			}
-		} else {
-			return err
-		}
-	}
-	var (
-		cStdin           io.ReadCloser
-		cStdout, cStderr io.Writer
-	)
-	if config.AttachStdin {
-		r, w := io.Pipe()
-		go func() {
-			defer w.Close()
-			defer Debugf("Closing buffered stdin pipe")
-			io.Copy(w, stdin)
-		}()
-		cStdin = r
-	}
-	if config.AttachStdout {
-		cStdout = stdout
-	}
-	if config.AttachStderr {
-		cStderr = stdout // FIXME: rcli can't differentiate stdout from stderr
-	}
-
-	attachErr := container.Attach(cStdin, stdin, cStdout, cStderr)
-	Debugf("Starting\n")
-	if err := container.Start(); err != nil {
+	if err := callStream("POST", "/containers", *config, config.Tty); err != nil {
 		return err
 	}
-	if cStdout == nil && cStderr == nil {
-		fmt.Fprintln(stdout, container.ShortId())
-	}
-	Debugf("Waiting for attach to return\n")
-	<-attachErr
-	// Expecting I/O pipe error, discarding
 	return nil
- }
- */
+}
 
-	
 func call(method, path string) ([]byte, error) {
-	req, err := http.NewRequest(method, "http://0.0.0.0:4243/" + path, nil)
+	req, err := http.NewRequest(method, "http://0.0.0.0:4243"+path, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -1025,43 +979,59 @@ func call(method, path string) ([]byte, error) {
 	return body, nil
 
 }
-/*
-func apiPost(path string, data interface{}) ([]byte, error) {
-	buf, err := json.Marshal(data)
-	if err != nil {
-		return nil, err
-	}
-	dataBuf := bytes.NewBuffer(buf)
-	resp, err := http.Post("http://0.0.0.0:4243/"+path, "application/json", dataBuf)
-	if err != nil {
-		return nil, err
+
+func callStream(method, path string, data interface{}, isTerminal bool) error {
+	var body io.Reader
+	if data != nil {
+		buf, err := json.Marshal(data)
+		if err != nil {
+			return err
+		}
+		body = bytes.NewBuffer(buf)
 	}
-	defer resp.Body.Close()
-	body, err := ioutil.ReadAll(resp.Body)
+	req, err := http.NewRequest(method, path, body)
 	if err != nil {
-		return nil, err
+		return err
 	}
-	if resp.StatusCode != 200 {
-		return nil, fmt.Errorf("[error] %s", body)
+
+	if data != nil {
+		req.Header.Set("Content-Type", "application/json")
 	}
-	return body, nil
-}
 
-func apiPostHijack(path string, data interface{}) (io.ReadCloser, error) {
-	buf, err := json.Marshal(data)
+	dial, err := net.Dial("tcp", "0.0.0.0:4243")
 	if err != nil {
-		return nil, err
+		return err
 	}
-	dataBuf := bytes.NewBuffer(buf)
-	resp, err := http.Post("http://0.0.0.0:4243/"+path, "application/json", dataBuf)
-	if err != nil {
-		return nil, err
+	clientconn := httputil.NewClientConn(dial, nil)
+	clientconn.Do(req)
+	defer clientconn.Close()
+
+	rwc, _ := clientconn.Hijack()
+	defer rwc.Close()
+
+	receiveStdout := Go(func() error {
+		_, err := io.Copy(os.Stdout, rwc)
+		return err
+	})
+	sendStdin := Go(func() error {
+		_, err := io.Copy(rwc, os.Stdin)
+		rwc.Close()
+		return err
+	})
+
+	if err := <-receiveStdout; err != nil {
+		return err
 	}
-	//TODO check status code
+	if isTerminal {
+		if err := <-sendStdin; err != nil {
+			return err
+		}
+	}
+
+	return nil
 
-	return resp.Body, nil
 }
-*/
+
 func Subcmd(name, signature, description string) *flag.FlagSet {
 	flags := flag.NewFlagSet(name, flag.ContinueOnError)
 	flags.Usage = func() {

+ 5 - 4
container.go

@@ -2,6 +2,7 @@ package docker
 
 import (
 	"encoding/json"
+	"flag"
 	"fmt"
 	"github.com/kr/pty"
 	"io"
@@ -65,7 +66,7 @@ type Config struct {
 	Image        string // Name of the image as it was passed by the operator (eg. could be symbolic)
 }
 
-func ParseRun(args []string) (*Config, error) {
+func ParseRun(args []string) (*Config, *flag.FlagSet, error) {
 	cmd := Subcmd("run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container")
 	if len(args) > 0 && args[0] != "--help" {
 		cmd.SetOutput(ioutil.Discard)
@@ -95,10 +96,10 @@ func ParseRun(args []string) (*Config, error) {
 	cmd.Var(&flDns, "dns", "Set custom dns servers")
 
 	if err := cmd.Parse(args); err != nil {
-		return nil, err
+		return nil, cmd, err
 	}
 	if *flDetach && len(flAttach) > 0 {
-		return nil, fmt.Errorf("Conflicting options: -a and -d")
+		return nil, cmd, fmt.Errorf("Conflicting options: -a and -d")
 	}
 	// If neither -d or -a are set, attach to everything by default
 	if len(flAttach) == 0 && !*flDetach {
@@ -138,7 +139,7 @@ func ParseRun(args []string) (*Config, error) {
 	if config.OpenStdin && config.AttachStdin {
 		config.StdinOnce = true
 	}
-	return config, nil
+	return config, cmd, nil
 }
 
 type NetworkSettings struct {

+ 5 - 6
registry.go

@@ -1,7 +1,6 @@
 package docker
 
 import (
-	"bufio"
 	"encoding/json"
 	"fmt"
 	"github.com/dotcloud/docker/auth"
@@ -95,10 +94,10 @@ func (graph *Graph) LookupRemoteImage(imgId string, authConfig *auth.AuthConfig)
 
 // Retrieve an image from the Registry.
 // Returns the Image object as well as the layer as an Archive (io.Reader)
-func (graph *Graph) getRemoteImage(stdout *bufio.ReadWriter, imgId string, authConfig *auth.AuthConfig) (*Image, Archive, error) {
+func (graph *Graph) getRemoteImage(stdout io.Writer, imgId string, authConfig *auth.AuthConfig) (*Image, Archive, error) {
 	client := &http.Client{}
 
-	fmt.Fprintf(stdout, "Pulling %s metadata\r\n", imgId);stdout.Flush()
+	fmt.Fprintf(stdout, "Pulling %s metadata\r\n", imgId)
 	// Get the Json
 	req, err := http.NewRequest("GET", REGISTRY_ENDPOINT+"/images/"+imgId+"/json", nil)
 	if err != nil {
@@ -126,7 +125,7 @@ func (graph *Graph) getRemoteImage(stdout *bufio.ReadWriter, imgId string, authC
 	img.Id = imgId
 
 	// Get the layer
-	fmt.Fprintf(stdout, "Pulling %s fs layer\r\n", imgId);stdout.Flush()
+	fmt.Fprintf(stdout, "Pulling %s fs layer\r\n", imgId)
 	req, err = http.NewRequest("GET", REGISTRY_ENDPOINT+"/images/"+imgId+"/layer", nil)
 	if err != nil {
 		return nil, nil, fmt.Errorf("Error while getting from the server: %s\n", err)
@@ -139,7 +138,7 @@ func (graph *Graph) getRemoteImage(stdout *bufio.ReadWriter, imgId string, authC
 	return img, ProgressReader(res.Body, int(res.ContentLength), stdout), nil
 }
 
-func (graph *Graph) PullImage(stdout *bufio.ReadWriter, imgId string, authConfig *auth.AuthConfig) error {
+func (graph *Graph) PullImage(stdout io.Writer, imgId string, authConfig *auth.AuthConfig) error {
 	history, err := graph.getRemoteHistory(imgId, authConfig)
 	if err != nil {
 		return err
@@ -162,7 +161,7 @@ func (graph *Graph) PullImage(stdout *bufio.ReadWriter, imgId string, authConfig
 }
 
 // FIXME: Handle the askedTag parameter
-func (graph *Graph) PullRepository(stdout *bufio.ReadWriter, remote, askedTag string, repositories *TagStore, authConfig *auth.AuthConfig) error {
+func (graph *Graph) PullRepository(stdout io.Writer, remote, askedTag string, repositories *TagStore, authConfig *auth.AuthConfig) error {
 	client := &http.Client{}
 
 	fmt.Fprintf(stdout, "Pulling repository %s\r\n", remote)