diff --git a/api.go b/api.go index fdf87f3f15..782276740b 100644 --- a/api.go +++ b/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 = "" out.Tag = "" @@ -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() - - - 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") + 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 + return } - if err := rtime.graph.PullRepository(bufrw, in.Name, "", rtime.repositories, rtime.authConfig); err != nil { - //http.Error(w, err.Error(), http.StatusInternalServerError) + if err := rtime.graph.PullRepository(file, name, "", rtime.repositories, rtime.authConfig); err != nil { + fmt.Fprintln(file, "Error: "+err.Error()) } - return }) + 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() + + 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 + } + } + 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 + } + + 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 { diff --git a/api_params.go b/api_params.go index 8f8b332a09..0b0254d5e9 100644 --- a/api_params.go +++ b/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 diff --git a/commands.go b/commands.go index e9b0701580..2549e3823f 100644 --- a/commands.go +++ b/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) -} -*/ -/* -func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { - config, err := ParseRun(args) + 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 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") + cmd.Usage() + return nil } - if config.Tty { - stdout.SetOptionRawTerminal() - } - // 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) + +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) + } + req, err := http.NewRequest(method, path, body) 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 data != nil { + req.Header.Set("Content-Type", "application/json") + } + + dial, err := net.Dial("tcp", "0.0.0.0:4243") if err != nil { - return nil, err + return err } - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - 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 } - if resp.StatusCode != 200 { - return nil, fmt.Errorf("[error] %s", body) + if isTerminal { + if err := <-sendStdin; err != nil { + return err + } } - return body, nil + + return nil + } -func apiPostHijack(path string, data interface{}) (io.ReadCloser, 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 - } - //TODO check status code - - return resp.Body, nil -} -*/ func Subcmd(name, signature, description string) *flag.FlagSet { flags := flag.NewFlagSet(name, flag.ContinueOnError) flags.Usage = func() { diff --git a/container.go b/container.go index 96cd423a06..0f40daadf3 100644 --- a/container.go +++ b/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 { diff --git a/registry.go b/registry.go index 7d13efd88f..428db1b968 100644 --- a/registry.go +++ b/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)