From 38e2d00199db5dabe9972ff9c3153ac6b63266f2 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 10 Apr 2013 19:48:21 -0700 Subject: [PATCH 01/69] Skeleton of http API --- api.go | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ commands.go | 2 ++ docker/docker.go | 3 +++ 3 files changed, 68 insertions(+) create mode 100644 api.go diff --git a/api.go b/api.go new file mode 100644 index 000000000..853f7abbd --- /dev/null +++ b/api.go @@ -0,0 +1,63 @@ +package docker + +import ( + "github.com/gorilla/mux" + "net/http" + _"encoding/json" +) + + +type RestEndpoint struct { + *mux.Router + runtime *Runtime +} + +func NewRestEndpoint(runtime *Runtime) *RestEndpoint { + endpoint := &RestEndpoint{ + Router: mux.NewRouter(), + runtime: runtime, + } + endpoint.Path("/images").Methods("GET").HandlerFunc(endpoint.GetImages) + endpoint.Path("/images").Methods("POST").HandlerFunc(endpoint.PostImages) + endpoint.Path("/images/{id}").Methods("GET").HandlerFunc(endpoint.GetImage) + endpoint.Path("/images/{id}").Methods("DELETE").HandlerFunc(endpoint.DeleteImage) + endpoint.Path("/containers").Methods("GET").HandlerFunc(endpoint.GetContainers) + endpoint.Path("/containers").Methods("POST").HandlerFunc(endpoint.PostContainers) + endpoint.Path("/containers/{id}").Methods("GET").HandlerFunc(endpoint.GetContainer) + endpoint.Path("/containers/{id}").Methods("DELETE").HandlerFunc(endpoint.DeleteContainer) + return endpoint +} + +func (ep *RestEndpoint) GetImages(w http.ResponseWriter, r *http.Response) { + +} + +func (ep *RestEndpoint) PostImages(w http.ResponseWriter, r *http.Response) { + +} + +func (ep *RestEndpoint) GetImage(w http.ResponseWriter, r *http.Response) { + +} + +func (ep *RestEndpoint) DeleteImage(w http.ResponseWriter, r *http.Response) { + +} + +func (ep *RestEndpoint) GetContainers(w http.ResponseWriter, r *http.Response) { + +} + +func (ep *RestEndpoint) PostContainers(w http.ResponseWriter, r *http.Response) { + +} + +func (ep *RestEndpoint) GetContainer(w http.ResponseWriter, r *http.Response) { + +} + +func (ep *RestEndpoint) DeleteContainer(w http.ResponseWriter, r *http.Response) { + +} + + diff --git a/commands.go b/commands.go index 20d6b45c9..722d4414b 100644 --- a/commands.go +++ b/commands.go @@ -959,10 +959,12 @@ func NewServer() (*Server, error) { } srv := &Server{ runtime: runtime, + restEndpoint: NewRestEndpoint(runtime), } return srv, nil } type Server struct { runtime *Runtime + restEndpoint *RestEndpoint } diff --git a/docker/docker.go b/docker/docker.go index 1b1c21990..73ea726d5 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -52,6 +52,9 @@ func daemon() error { if err != nil { return err } + if err := http.ListenAndServe("0.0.0.0:4243", service.restEndpoint); err != nil { + return err + } return rcli.ListenAndServe("tcp", "127.0.0.1:4242", service) } From a11b31399b51a3d5bfd0696e13439196915bbbbb Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 10 Apr 2013 19:48:21 -0700 Subject: [PATCH 02/69] Skeleton of http API --- api.go | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ commands.go | 2 ++ docker/docker.go | 3 +++ 3 files changed, 68 insertions(+) create mode 100644 api.go diff --git a/api.go b/api.go new file mode 100644 index 000000000..853f7abbd --- /dev/null +++ b/api.go @@ -0,0 +1,63 @@ +package docker + +import ( + "github.com/gorilla/mux" + "net/http" + _"encoding/json" +) + + +type RestEndpoint struct { + *mux.Router + runtime *Runtime +} + +func NewRestEndpoint(runtime *Runtime) *RestEndpoint { + endpoint := &RestEndpoint{ + Router: mux.NewRouter(), + runtime: runtime, + } + endpoint.Path("/images").Methods("GET").HandlerFunc(endpoint.GetImages) + endpoint.Path("/images").Methods("POST").HandlerFunc(endpoint.PostImages) + endpoint.Path("/images/{id}").Methods("GET").HandlerFunc(endpoint.GetImage) + endpoint.Path("/images/{id}").Methods("DELETE").HandlerFunc(endpoint.DeleteImage) + endpoint.Path("/containers").Methods("GET").HandlerFunc(endpoint.GetContainers) + endpoint.Path("/containers").Methods("POST").HandlerFunc(endpoint.PostContainers) + endpoint.Path("/containers/{id}").Methods("GET").HandlerFunc(endpoint.GetContainer) + endpoint.Path("/containers/{id}").Methods("DELETE").HandlerFunc(endpoint.DeleteContainer) + return endpoint +} + +func (ep *RestEndpoint) GetImages(w http.ResponseWriter, r *http.Response) { + +} + +func (ep *RestEndpoint) PostImages(w http.ResponseWriter, r *http.Response) { + +} + +func (ep *RestEndpoint) GetImage(w http.ResponseWriter, r *http.Response) { + +} + +func (ep *RestEndpoint) DeleteImage(w http.ResponseWriter, r *http.Response) { + +} + +func (ep *RestEndpoint) GetContainers(w http.ResponseWriter, r *http.Response) { + +} + +func (ep *RestEndpoint) PostContainers(w http.ResponseWriter, r *http.Response) { + +} + +func (ep *RestEndpoint) GetContainer(w http.ResponseWriter, r *http.Response) { + +} + +func (ep *RestEndpoint) DeleteContainer(w http.ResponseWriter, r *http.Response) { + +} + + diff --git a/commands.go b/commands.go index 82a0ce103..10890ad92 100644 --- a/commands.go +++ b/commands.go @@ -986,10 +986,12 @@ func NewServer() (*Server, error) { } srv := &Server{ runtime: runtime, + restEndpoint: NewRestEndpoint(runtime), } return srv, nil } type Server struct { runtime *Runtime + restEndpoint *RestEndpoint } diff --git a/docker/docker.go b/docker/docker.go index 83c47c6f1..693ddd72f 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -99,6 +99,9 @@ func daemon(pidfile string) error { if err != nil { return err } + if err := http.ListenAndServe("0.0.0.0:4243", service.restEndpoint); err != nil { + return err + } return rcli.ListenAndServe("tcp", "127.0.0.1:4242", service) } From c0d5d5969bb9c0bfe79e7a11977697e50cc185ae Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 18 Apr 2013 03:13:43 +0200 Subject: [PATCH 03/69] skeleton remote API, only version working (wip) --- api.go | 73 ++++++++++--------------------------- api_params.go | 7 ++++ commands.go | 15 -------- commands2.go | 95 ++++++++++++++++++++++++++++++++++++++++++++++++ docker/docker.go | 14 ++++--- 5 files changed, 130 insertions(+), 74 deletions(-) create mode 100644 api_params.go create mode 100644 commands2.go diff --git a/api.go b/api.go index 853f7abbd..353a93288 100644 --- a/api.go +++ b/api.go @@ -1,63 +1,30 @@ package docker import ( + "encoding/json" + "log" "github.com/gorilla/mux" "net/http" - _"encoding/json" ) +func ListenAndServe(addr string, runtime *Runtime) error { + r := mux.NewRouter() + log.Printf("Listening for HTTP on %s\n", addr) -type RestEndpoint struct { - *mux.Router - runtime *Runtime + r.Path("/version").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + m := VersionOut{VERSION, GIT_COMMIT, NO_MEMORY_LIMIT} + b, err := json.Marshal(m) + if err != nil { + w.WriteHeader(500) + } else { + w.Write(b) + } + }) + + r.Path("/images").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + //TODO use runtime + }) + + return http.ListenAndServe(addr, r) } -func NewRestEndpoint(runtime *Runtime) *RestEndpoint { - endpoint := &RestEndpoint{ - Router: mux.NewRouter(), - runtime: runtime, - } - endpoint.Path("/images").Methods("GET").HandlerFunc(endpoint.GetImages) - endpoint.Path("/images").Methods("POST").HandlerFunc(endpoint.PostImages) - endpoint.Path("/images/{id}").Methods("GET").HandlerFunc(endpoint.GetImage) - endpoint.Path("/images/{id}").Methods("DELETE").HandlerFunc(endpoint.DeleteImage) - endpoint.Path("/containers").Methods("GET").HandlerFunc(endpoint.GetContainers) - endpoint.Path("/containers").Methods("POST").HandlerFunc(endpoint.PostContainers) - endpoint.Path("/containers/{id}").Methods("GET").HandlerFunc(endpoint.GetContainer) - endpoint.Path("/containers/{id}").Methods("DELETE").HandlerFunc(endpoint.DeleteContainer) - return endpoint -} - -func (ep *RestEndpoint) GetImages(w http.ResponseWriter, r *http.Response) { - -} - -func (ep *RestEndpoint) PostImages(w http.ResponseWriter, r *http.Response) { - -} - -func (ep *RestEndpoint) GetImage(w http.ResponseWriter, r *http.Response) { - -} - -func (ep *RestEndpoint) DeleteImage(w http.ResponseWriter, r *http.Response) { - -} - -func (ep *RestEndpoint) GetContainers(w http.ResponseWriter, r *http.Response) { - -} - -func (ep *RestEndpoint) PostContainers(w http.ResponseWriter, r *http.Response) { - -} - -func (ep *RestEndpoint) GetContainer(w http.ResponseWriter, r *http.Response) { - -} - -func (ep *RestEndpoint) DeleteContainer(w http.ResponseWriter, r *http.Response) { - -} - - diff --git a/api_params.go b/api_params.go new file mode 100644 index 000000000..277074964 --- /dev/null +++ b/api_params.go @@ -0,0 +1,7 @@ +package docker + +type VersionOut struct { + Version string + GitCommit string + MemoryLimitDisabled bool +} diff --git a/commands.go b/commands.go index 10890ad92..b30f9690e 100644 --- a/commands.go +++ b/commands.go @@ -976,22 +976,7 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...s return nil } -func NewServer() (*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() - if err != nil { - return nil, err - } - srv := &Server{ - runtime: runtime, - restEndpoint: NewRestEndpoint(runtime), - } - return srv, nil -} type Server struct { runtime *Runtime - restEndpoint *RestEndpoint } diff --git a/commands2.go b/commands2.go new file mode 100644 index 000000000..014a4bbe6 --- /dev/null +++ b/commands2.go @@ -0,0 +1,95 @@ +package docker + +import ( + "fmt" + "io/ioutil" + "net/http" + "encoding/json" +) + +func ParseCommands(args []string) error { + + cmds := map[string]func(args []string) error { + "version":cmdVersion, + } + + if len(args) > 0 { + cmd, exists := cmds[args[0]] + if !exists { + //TODO display commend not found + return cmdHelp(args) + } + return cmd(args) + } + return cmdHelp(args) +} + +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"}, +// {"commit", "Create a new image from a container's changes"}, +// {"diff", "Inspect changes on a container's filesystem"}, +// {"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"}, +// {"info", "Display system-wide information"}, +// {"inspect", "Return low-level information on a container"}, +// {"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"}, +// {"ps", "List containers"}, +// {"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"}, +// {"start", "Start a stopped container"}, +// {"stop", "Stop a running container"}, +// {"tag", "Tag an image into a repository"}, + {"version", "Show the docker version information"}, +// {"wait", "Block until a container stops, then print its exit code"}, + } { + help += fmt.Sprintf(" %-10.10s%s\n", cmd[0], cmd[1]) + } + fmt.Println(help) + return nil +} + +func cmdVersion(args []string) error { + body, err := apiCall("version") + if err != nil { + return err + } + + var out VersionOut + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + fmt.Println("Version:", out.Version) + fmt.Println("Git Commit:", out.GitCommit) + if out.MemoryLimitDisabled { + fmt.Println("Memory limit disabled") + } + + return nil +} + +func apiCall(path string) ([]byte, error) { + resp, err := http.Get("http://0.0.0.0:4243/" + path) + if err != nil { + return nil,err + } + //TODO check status code + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + return body, nil + +} \ No newline at end of file diff --git a/docker/docker.go b/docker/docker.go index 693ddd72f..4639921d5 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -10,6 +10,7 @@ import ( "log" "os" "os/signal" + "runtime" "syscall" ) @@ -52,7 +53,7 @@ func main() { log.Fatal(err) } } else { - if err := runCommand(flag.Args()); err != nil { + if err := docker.ParseCommands(flag.Args()); err != nil { log.Fatal(err) } } @@ -95,14 +96,15 @@ func daemon(pidfile string) error { os.Exit(0) }() - service, err := docker.NewServer() + 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() if err != nil { return err } - if err := http.ListenAndServe("0.0.0.0:4243", service.restEndpoint); err != nil { - return err - } - return rcli.ListenAndServe("tcp", "127.0.0.1:4242", service) + + return docker.ListenAndServe("0.0.0.0:4243", runtime) } func runCommand(args []string) error { From 79e910580604339a74c1577061d8b207ae8c2ee1 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 18 Apr 2013 18:56:22 +0200 Subject: [PATCH 04/69] added kill and images(wip) --- api.go | 100 +++++++++++++++++++++++- api_params.go | 23 +++++- commands.go | 1 - commands2.go | 205 ++++++++++++++++++++++++++++++++++++++------------ 4 files changed, 273 insertions(+), 56 deletions(-) diff --git a/api.go b/api.go index 353a93288..4e2750b33 100644 --- a/api.go +++ b/api.go @@ -2,9 +2,11 @@ package docker import ( "encoding/json" - "log" + _ "fmt" "github.com/gorilla/mux" + "log" "net/http" + "time" ) func ListenAndServe(addr string, runtime *Runtime) error { @@ -21,10 +23,102 @@ func ListenAndServe(addr string, runtime *Runtime) error { } }) + r.Path("/kill").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var ids []string + if err := json.NewDecoder(r.Body).Decode(&ids); err != nil { + w.WriteHeader(500) + return + } + + var ret SimpleMessage + for _, name := range ids { + container := runtime.Get(name) + if container == nil { + ret.Message = "No such container: " + name + "\n" + break + } + if err := container.Kill(); err != nil { + ret.Message = ret.Message + "Error killing container " + name + ": " + err.Error() + "\n" + } + } + if ret.Message == "" { + w.WriteHeader(200) + } else { + w.WriteHeader(500) + } + + b, err := json.Marshal(ret) + if err != nil { + w.WriteHeader(500) + } else { + w.Write(b) + } + + }) + r.Path("/images").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - //TODO use runtime + var in ImagesIn + json.NewDecoder(r.Body).Decode(&in) + + var allImages map[string]*Image + var err error + if in.All { + allImages, err = runtime.graph.Map() + } else { + allImages, err = runtime.graph.Heads() + } + if err != nil { + w.WriteHeader(500) + return + } + var outs []ImagesOut + for name, repository := range runtime.repositories.Repositories { + if in.NameFilter != "" && name != in.NameFilter { + continue + } + for tag, id := range repository { + var out ImagesOut + image, err := 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 !in.Quiet { + out.Repository = name + out.Tag = tag + out.Id = TruncateId(id) + out.Created = HumanDuration(time.Now().Sub(image.Created)) + " ago" + } else { + out.Id = image.ShortId() + } + outs = append(outs, out) + } + } + // Display images which aren't part of a + if in.NameFilter == "" { + for id, image := range allImages { + var out ImagesOut + if !in.Quiet { + out.Repository = "" + out.Tag = "" + out.Id = TruncateId(id) + out.Created = HumanDuration(time.Now().Sub(image.Created)) + " ago" + } else { + out.Id = image.ShortId() + } + outs = append(outs, out) + } + } + + b, err := json.Marshal(outs) + if err != nil { + w.WriteHeader(500) + } else { + w.Write(b) + } + }) return http.ListenAndServe(addr, r) } - diff --git a/api_params.go b/api_params.go index 277074964..f882b353d 100644 --- a/api_params.go +++ b/api_params.go @@ -1,7 +1,24 @@ package docker +type SimpleMessage struct { + Message string +} + +type ImagesIn struct { + NameFilter string + Quiet bool + All bool +} + +type ImagesOut struct { + Repository string `json:",omitempty"` + Tag string `json:",omitempty"` + Id string + Created string `json:",omitempty"` +} + type VersionOut struct { - Version string - GitCommit string - MemoryLimitDisabled bool + Version string + GitCommit string + MemoryLimitDisabled bool } diff --git a/commands.go b/commands.go index b30f9690e..59bd87d57 100644 --- a/commands.go +++ b/commands.go @@ -976,7 +976,6 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...s return nil } - type Server struct { runtime *Runtime } diff --git a/commands2.go b/commands2.go index 014a4bbe6..a2bdedeec 100644 --- a/commands2.go +++ b/commands2.go @@ -1,25 +1,31 @@ package docker import ( + "bytes" + "encoding/json" + "flag" "fmt" "io/ioutil" "net/http" - "encoding/json" + "os" + "text/tabwriter" ) func ParseCommands(args []string) error { - - cmds := map[string]func(args []string) error { - "version":cmdVersion, + + cmds := map[string]func(args []string) error{ + "images": cmdImages, + "kill": cmdKill, + "version": cmdVersion, } if len(args) > 0 { - cmd, exists := cmds[args[0]] + cmd, exists := cmds[args[0]] if !exists { //TODO display commend not found return cmdHelp(args) } - return cmd(args) + return cmd(args[1:]) } return cmdHelp(args) } @@ -27,31 +33,31 @@ func ParseCommands(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"}, -// {"commit", "Create a new image from a container's changes"}, -// {"diff", "Inspect changes on a container's filesystem"}, -// {"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"}, -// {"info", "Display system-wide information"}, -// {"inspect", "Return low-level information on a container"}, -// {"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"}, -// {"ps", "List containers"}, -// {"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"}, -// {"start", "Start a stopped container"}, -// {"stop", "Stop a running container"}, -// {"tag", "Tag an image into a repository"}, + // {"attach", "Attach to a running container"}, + // {"commit", "Create a new image from a container's changes"}, + // {"diff", "Inspect changes on a container's filesystem"}, + // {"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"}, + // {"info", "Display system-wide information"}, + // {"inspect", "Return low-level information on a container"}, + {"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"}, + // {"ps", "List containers"}, + // {"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"}, + // {"start", "Start a stopped container"}, + // {"stop", "Stop a running container"}, + // {"tag", "Tag an image into a repository"}, {"version", "Show the docker version information"}, -// {"wait", "Block until a container stops, then print its exit code"}, + // {"wait", "Block until a container stops, then print its exit code"}, } { help += fmt.Sprintf(" %-10.10s%s\n", cmd[0], cmd[1]) } @@ -59,8 +65,80 @@ func cmdHelp(args []string) error { return nil } -func cmdVersion(args []string) error { - body, err := apiCall("version") +func cmdImages(args []string) error { + cmd := subcmd("images", "[OPTIONS] [NAME]", "List images") + quiet := cmd.Bool("q", false, "only show numeric IDs") + flAll := cmd.Bool("a", false, "show all images") + if err := cmd.Parse(args); err != nil { + return nil + } + if cmd.NArg() > 1 { + cmd.Usage() + return nil + } + var nameFilter string + if cmd.NArg() == 1 { + nameFilter = cmd.Arg(0) + } + + in := ImagesIn{nameFilter, *quiet, *flAll} + + body, err := apiPost("images", in) + if err != nil { + return err + } + + var outs []ImagesOut + err = json.Unmarshal(body, &outs) + if err != nil { + return err + } + w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) + if !*quiet { + fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED") + } + + for _, out := range outs { + if !*quiet { + fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", out.Repository, out.Tag, out.Id, out.Created) + } else { + fmt.Fprintln(w, out.Id) + } + } + + if !*quiet { + w.Flush() + } + return nil + +} + +func cmdKill(args []string) error { + cmd := subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container") + if err := cmd.Parse(args); err != nil { + return nil + } + if cmd.NArg() < 1 { + cmd.Usage() + return nil + } + + body, err := apiPost("kill", args) + if err != nil { + return err + } + + var out SimpleMessage + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + fmt.Print(out.Message) + return nil +} + +func cmdVersion(_ []string) error { + body, err := apiGet("version") if err != nil { return err } @@ -68,28 +146,57 @@ func cmdVersion(args []string) error { var out VersionOut err = json.Unmarshal(body, &out) if err != nil { - return err - } + return err + } fmt.Println("Version:", out.Version) - fmt.Println("Git Commit:", out.GitCommit) - if out.MemoryLimitDisabled { - fmt.Println("Memory limit disabled") - } + fmt.Println("Git Commit:", out.GitCommit) + if out.MemoryLimitDisabled { + fmt.Println("Memory limit disabled") + } return nil } -func apiCall(path string) ([]byte, error) { - resp, err := http.Get("http://0.0.0.0:4243/" + path) - if err != nil { - return nil,err - } +func apiGet(path string) ([]byte, error) { + resp, err := http.Get("http://0.0.0.0:4243/" + path) + if err != nil { + return nil, err + } //TODO check status code - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } return body, nil -} \ No newline at end of file +} + +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 + } + //TODO check status code + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + return body, nil + +} + +func subcmd(name, signature, description string) *flag.FlagSet { + flags := flag.NewFlagSet(name, flag.ContinueOnError) + flags.Usage = func() { + fmt.Printf("\nUsage: docker %s %s\n\n%s\n\n", name, signature, description) + flags.PrintDefaults() + } + return flags +} From b295239de20f15620ee4149fcc7c13ce461cdaac Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 19 Apr 2013 15:24:37 +0200 Subject: [PATCH 05/69] added: info, history, logs, ps, start, stop, restart, rm, rmi --- api.go | 323 ++++++++++++++++++++++++++++++++++++++++-- api_params.go | 43 ++++++ commands.go | 2 +- commands2.go | 378 ++++++++++++++++++++++++++++++++++++++++++++++---- container.go | 7 +- 5 files changed, 714 insertions(+), 39 deletions(-) diff --git a/api.go b/api.go index 4e2750b33..948b48d8b 100644 --- a/api.go +++ b/api.go @@ -2,14 +2,18 @@ package docker import ( "encoding/json" - _ "fmt" + "fmt" "github.com/gorilla/mux" + "io/ioutil" "log" "net/http" + "os" + "runtime" + "strings" "time" ) -func ListenAndServe(addr string, runtime *Runtime) error { +func ListenAndServe(addr string, rtime *Runtime) error { r := mux.NewRouter() log.Printf("Listening for HTTP on %s\n", addr) @@ -32,7 +36,7 @@ func ListenAndServe(addr string, runtime *Runtime) error { var ret SimpleMessage for _, name := range ids { - container := runtime.Get(name) + container := rtime.Get(name) if container == nil { ret.Message = "No such container: " + name + "\n" break @@ -63,22 +67,22 @@ func ListenAndServe(addr string, runtime *Runtime) error { var allImages map[string]*Image var err error if in.All { - allImages, err = runtime.graph.Map() + allImages, err = rtime.graph.Map() } else { - allImages, err = runtime.graph.Heads() + allImages, err = rtime.graph.Heads() } if err != nil { w.WriteHeader(500) return } var outs []ImagesOut - for name, repository := range runtime.repositories.Repositories { + for name, repository := range rtime.repositories.Repositories { if in.NameFilter != "" && name != in.NameFilter { continue } for tag, id := range repository { var out ImagesOut - image, err := runtime.graph.Get(id) + 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 @@ -113,7 +117,310 @@ func ListenAndServe(addr string, runtime *Runtime) error { b, err := json.Marshal(outs) if err != nil { - w.WriteHeader(500) + http.Error(w, err.Error(), http.StatusInternalServerError) + } else { + w.Write(b) + } + + }) + + r.Path("/info").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + images, _ := rtime.graph.All() + var imgcount int + if images == nil { + imgcount = 0 + } else { + imgcount = len(images) + } + var out InfoOut + 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() + } + b, err := json.Marshal(out) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } else { + w.Write(b) + } + }) + + r.Path("/history").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(r.RequestURI) + + var in HistoryIn + json.NewDecoder(r.Body).Decode(&in) + + image, err := rtime.repositories.LookupImage(in.Name) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + var outs []HistoryOut + err = image.WalkHistory(func(img *Image) error { + var out HistoryOut + out.Id = rtime.repositories.ImageName(img.ShortId()) + out.Created = HumanDuration(time.Now().Sub(img.Created)) + " ago" + out.CreatedBy = strings.Join(img.ContainerConfig.Cmd, " ") + return nil + }) + + b, err := json.Marshal(outs) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } else { + w.Write(b) + } + + }) + + r.Path("/logs").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + var in LogsIn + json.NewDecoder(r.Body).Decode(&in) + + if container := rtime.Get(in.Name); container != nil { + var out LogsOut + + logStdout, err := container.ReadLog("stdout") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + logStderr, err := container.ReadLog("stderr") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + stdout, errStdout := ioutil.ReadAll(logStdout) + if errStdout != nil { + http.Error(w, errStdout.Error(), http.StatusInternalServerError) + return + } else { + out.Stdout = fmt.Sprintf("%s", stdout) + } + stderr, errStderr := ioutil.ReadAll(logStderr) + if errStderr != nil { + http.Error(w, errStderr.Error(), http.StatusInternalServerError) + return + } else { + out.Stderr = fmt.Sprintf("%s", stderr) + } + + b, err := json.Marshal(out) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } else { + w.Write(b) + } + + } else { + http.Error(w, "No such container: "+in.Name, http.StatusInternalServerError) + } + }) + + r.Path("/ps").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var in PsIn + json.NewDecoder(r.Body).Decode(&in) + + var outs []PsOut + + for i, container := range rtime.List() { + if !container.State.Running && !in.All && in.Last == -1 { + continue + } + if i == in.Last { + break + } + var out PsOut + out.Id = container.ShortId() + if !in.Quiet { + command := fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")) + if !in.Full { + command = Trunc(command, 20) + } + out.Image = rtime.repositories.ImageName(container.Image) + out.Command = command + out.Created = HumanDuration(time.Now().Sub(container.Created)) + " ago" + out.Status = container.State.String() + } + outs = append(outs, out) + } + + b, err := json.Marshal(outs) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } else { + w.Write(b) + } + + }) + + r.Path("/restart").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var ins, outs []string + json.NewDecoder(r.Body).Decode(&ins) + + for _, name := range ins { + if container := rtime.Get(name); container != nil { + if err := container.Restart(); err != nil { + http.Error(w, "Error restaring container "+name+": "+err.Error(), http.StatusInternalServerError) + return + } + outs = append(outs, container.ShortId()) + } else { + http.Error(w, "No such container: "+name, http.StatusInternalServerError) + return + } + } + b, err := json.Marshal(outs) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } else { + w.Write(b) + } + }) + + r.Path("/rm").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var ins, outs []string + json.NewDecoder(r.Body).Decode(&ins) + + for _, name := range ins { + 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 + } + outs = append(outs, container.ShortId()) + } else { + http.Error(w, "No such container: "+name, http.StatusInternalServerError) + return + } + } + b, err := json.Marshal(outs) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } else { + w.Write(b) + } + + }) + + r.Path("/rmi").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var ins, outs []string + json.NewDecoder(r.Body).Decode(&ins) + + for _, name := range ins { + img, err := rtime.repositories.LookupImage(name) + if err != nil { + http.Error(w, "No such image: "+name, http.StatusInternalServerError) + return + } else { + if err := rtime.graph.Delete(img.Id); err != nil { + http.Error(w, "Error deleting image "+name+": "+err.Error(), http.StatusInternalServerError) + return + } + outs = append(outs, img.ShortId()) + } + } + b, err := json.Marshal(outs) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } else { + w.Write(b) + } + + }) + + r.Path("/run").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + var config Config + json.NewDecoder(r.Body).Decode(&config) + + hj, ok := w.(http.Hijacker) + if !ok { + http.Error(w, "webserver doesn't support hijacking", http.StatusInternalServerError) + return + } + conn, bufrw, err := hj.Hijack() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer conn.Close() + + //TODO config.Tty + + // Create new container + container, err := rtime.Create(&config) + if err != nil { + // If container not found, try to pull it + if rtime.graph.IsNotExist(err) { + bufrw.WriteString("Image " + config.Image + " not found, trying to pull it from registry.\r\n") + bufrw.Flush() + //TODO if err = srv.CmdPull(stdin, stdout, config.Image); err != nil { + //return err + //} + if container, err = rtime.Create(&config); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } else { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } + container = container + }) + + r.Path("/start").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var ins, outs []string + json.NewDecoder(r.Body).Decode(&ins) + + for _, name := range ins { + 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 + } + outs = append(outs, container.ShortId()) + } else { + http.Error(w, "No such container: "+name, http.StatusInternalServerError) + return + } + } + b, err := json.Marshal(outs) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } else { + w.Write(b) + } + + }) + + r.Path("/stop").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var ins, outs []string + json.NewDecoder(r.Body).Decode(&ins) + + for _, name := range ins { + if container := rtime.Get(name); container != nil { + if err := container.Stop(); err != nil { + http.Error(w, "Error stopping container "+name+": "+err.Error(), http.StatusInternalServerError) + return + } + outs = append(outs, container.ShortId()) + } else { + http.Error(w, "No such container: "+name, http.StatusInternalServerError) + return + } + } + b, err := json.Marshal(outs) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) } else { w.Write(b) } diff --git a/api_params.go b/api_params.go index f882b353d..4a2bbee13 100644 --- a/api_params.go +++ b/api_params.go @@ -4,6 +4,16 @@ type SimpleMessage struct { Message string } +type HistoryIn struct { + Name string +} + +type HistoryOut struct { + Id string + Created string + CreatedBy string +} + type ImagesIn struct { NameFilter string Quiet bool @@ -17,6 +27,39 @@ type ImagesOut struct { Created string `json:",omitempty"` } +type InfoOut struct { + Containers int + Version string + Images int + Debug bool + NFd int `json:",omitempty"` + NGoroutines int `json:",omitempty"` +} + +type PsIn struct { + Quiet bool + All bool + Full bool + Last int +} + +type PsOut struct { + Id string + Image string `json:",omitempty"` + Command string `json:",omitempty"` + Created string `json:",omitempty"` + Status string `json:",omitempty"` +} + +type LogsIn struct { + Name string +} + +type LogsOut struct { + Stdout string + Stderr string +} + type VersionOut struct { Version string GitCommit string diff --git a/commands.go b/commands.go index 59bd87d57..f7361b1a1 100644 --- a/commands.go +++ b/commands.go @@ -906,7 +906,7 @@ func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string) } func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { - config, err := ParseRun(args, stdout) + config, err := ParseRun(args) if err != nil { return err } diff --git a/commands2.go b/commands2.go index a2bdedeec..02056f543 100644 --- a/commands2.go +++ b/commands2.go @@ -5,6 +5,7 @@ import ( "encoding/json" "flag" "fmt" + "io" "io/ioutil" "net/http" "os" @@ -15,7 +16,17 @@ func ParseCommands(args []string) error { cmds := map[string]func(args []string) error{ "images": cmdImages, + "info": cmdInfo, + "history": cmdHistory, "kill": cmdKill, + "logs": cmdLogs, + "ps": cmdPs, + "restart": cmdRestart, + "rm": cmdRm, + "rmi": cmdRmi, + "run": cmdRun, + "start": cmdStart, + "stop": cmdStop, "version": cmdVersion, } @@ -37,24 +48,24 @@ func cmdHelp(args []string) error { // {"commit", "Create a new image from a container's changes"}, // {"diff", "Inspect changes on a container's filesystem"}, // {"export", "Stream the contents of a container as a tar archive"}, - // {"history", "Show the history of an image"}, + {"history", "Show the history of an image"}, {"images", "List images"}, // {"import", "Create a new filesystem image from the contents of a tarball"}, - // {"info", "Display system-wide information"}, + {"info", "Display system-wide information"}, // {"inspect", "Return low-level information on a container"}, {"kill", "Kill a running container"}, // {"login", "Register or Login to the docker registry server"}, - // {"logs", "Fetch the logs of a container"}, + {"logs", "Fetch the logs of a container"}, // {"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"}, - // {"ps", "List containers"}, + {"ps", "List containers"}, // {"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"}, - // {"start", "Start a stopped container"}, - // {"stop", "Stop a running container"}, + {"restart", "Restart a running container"}, + {"rm", "Remove a container"}, + {"rmi", "Remove an image"}, + {"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"}, {"version", "Show the docker version information"}, // {"wait", "Block until a container stops, then print its exit code"}, @@ -66,9 +77,10 @@ func cmdHelp(args []string) error { } func cmdImages(args []string) error { - cmd := subcmd("images", "[OPTIONS] [NAME]", "List images") - quiet := cmd.Bool("q", false, "only show numeric IDs") - flAll := cmd.Bool("a", false, "show all images") + cmd := Subcmd("images", "[OPTIONS] [NAME]", "List images") + var in ImagesIn + cmd.BoolVar(&in.Quiet, "q", false, "only show numeric IDs") + cmd.BoolVar(&in.All, "a", false, "show all images") if err := cmd.Parse(args); err != nil { return nil } @@ -76,13 +88,10 @@ func cmdImages(args []string) error { cmd.Usage() return nil } - var nameFilter string if cmd.NArg() == 1 { - nameFilter = cmd.Arg(0) + in.NameFilter = cmd.Arg(0) } - in := ImagesIn{nameFilter, *quiet, *flAll} - body, err := apiPost("images", in) if err != nil { return err @@ -94,27 +103,86 @@ func cmdImages(args []string) error { return err } w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) - if !*quiet { + if !in.Quiet { fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED") } for _, out := range outs { - if !*quiet { + if !in.Quiet { fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", out.Repository, out.Tag, out.Id, out.Created) } else { fmt.Fprintln(w, out.Id) } } - if !*quiet { + if !in.Quiet { w.Flush() } return nil } +func cmdInfo(args []string) error { + cmd := Subcmd("info", "", "Display system-wide information") + if err := cmd.Parse(args); err != nil { + return nil + } + if cmd.NArg() > 0 { + cmd.Usage() + return nil + } + + body, err := apiGet("info") + if err != nil { + return err + } + + var out InfoOut + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + fmt.Printf("containers: %d\nversion: %s\nimages: %d\n", out.Containers, out.Version, out.Images) + if out.Debug { + fmt.Println("debug mode enabled") + fmt.Printf("fds: %d\ngoroutines: %d\n", out.NFd, out.NGoroutines) + } + return nil + +} + +func cmdHistory(args []string) error { + cmd := Subcmd("history", "IMAGE", "Show the history of an image") + if err := cmd.Parse(args); err != nil { + return nil + } + if cmd.NArg() != 1 { + cmd.Usage() + return nil + } + + body, err := apiPost("history", HistoryIn{cmd.Arg(0)}) + if err != nil { + return err + } + + var outs []HistoryOut + err = json.Unmarshal(body, &outs) + if err != nil { + return err + } + w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) + fmt.Fprintln(w, "ID\tCREATED\tCREATED BY") + + for _, out := range outs { + fmt.Fprintf(w, "%s\t%s\t%s\n", out.Id, out.Created, out.CreatedBy) + } + w.Flush() + return nil +} + func cmdKill(args []string) error { - cmd := subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container") + cmd := Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container") if err := cmd.Parse(args); err != nil { return nil } @@ -137,7 +205,247 @@ func cmdKill(args []string) error { return nil } -func cmdVersion(_ []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 + } + if cmd.NArg() != 1 { + cmd.Usage() + return nil + } + body, err := apiPost("logs", LogsIn{cmd.Arg(0)}) + if err != nil { + return err + } + + var out LogsOut + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + fmt.Fprintln(os.Stdout, out.Stdout) + fmt.Fprintln(os.Stderr, out.Stderr) + + return nil +} + +func cmdPs(args []string) error { + cmd := Subcmd("ps", "[OPTIONS]", "List containers") + var in PsIn + cmd.BoolVar(&in.Quiet, "q", false, "Only display numeric IDs") + cmd.BoolVar(&in.All, "a", false, "Show all containers. Only running containers are shown by default.") + cmd.BoolVar(&in.Full, "notrunc", false, "Don't truncate output") + nLatest := cmd.Bool("l", false, "Show only the latest created container, include non-running ones.") + cmd.IntVar(&in.Last, "n", -1, "Show n last created containers, include non-running ones.") + if err := cmd.Parse(args); err != nil { + return nil + } + if in.Last == -1 && *nLatest { + in.Last = 1 + } + + body, err := apiPost("ps", in) + if err != nil { + return err + } + + var outs []PsOut + err = json.Unmarshal(body, &outs) + if err != nil { + return err + } + w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) + if !in.Quiet { + fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS") + } + + for _, out := range outs { + if !in.Quiet { + fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", out.Id, out.Image, out.Command, out.Status, out.Created) + } else { + fmt.Fprintln(w, out.Id) + } + } + + if !in.Quiet { + w.Flush() + } + return nil +} + +func cmdRestart(args []string) error { + cmd := Subcmd("restart", "CONTAINER [CONTAINER...]", "Restart a running container") + if err := cmd.Parse(args); err != nil { + return nil + } + if cmd.NArg() < 1 { + cmd.Usage() + return nil + } + + body, err := apiPost("restart", cmd.Args()) + if err != nil { + return err + } + + var out []string + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + for _, name := range out { + fmt.Println(name) + } + return nil +} + +func cmdRm(args []string) error { + cmd := Subcmd("rm", "CONTAINER [CONTAINER...]", "Remove a container") + if err := cmd.Parse(args); err != nil { + return nil + } + if cmd.NArg() < 1 { + cmd.Usage() + return nil + } + + body, err := apiPost("rm", cmd.Args()) + if err != nil { + return err + } + + var out []string + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + for _, name := range out { + fmt.Println(name) + } + return nil +} + +func cmdRmi(args []string) error { + cmd := Subcmd("rmi", "IMAGE [IMAGE...]", "Remove an image") + if err := cmd.Parse(args); err != nil { + return nil + } + if cmd.NArg() < 1 { + cmd.Usage() + return nil + } + + body, err := apiPost("rmi", cmd.Args()) + if err != nil { + return err + } + + var out []string + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + for _, name := range out { + fmt.Println(name) + } + return nil +} + +func cmdRun(args []string) error { + config, err := ParseRun(args) + if err != nil { + return err + } + if config.Image == "" { + fmt.Println("Error: Image not specified") + return fmt.Errorf("Image not specified") + } + if len(config.Cmd) == 0 { + fmt.Println("Error: Command not specified") + return fmt.Errorf("Command not specified") + } + + body, err := apiPostHijack("run", config) + if err != nil { + return err + } + defer body.Close() + + /*str, err2 := ioutil.ReadAll(body) + if err2 != nil { + return err2 + } + fmt.Println(str)*/ + return nil + +} + +func cmdStart(args []string) error { + cmd := Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container") + if err := cmd.Parse(args); err != nil { + return nil + } + if cmd.NArg() < 1 { + cmd.Usage() + return nil + } + + body, err := apiPost("start", cmd.Args()) + if err != nil { + return err + } + + var out []string + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + for _, name := range out { + fmt.Println(name) + } + + return nil + +} + +func cmdStop(args []string) error { + cmd := Subcmd("stop", "CONTAINER [CONTAINER...]", "Restart a running container") + if err := cmd.Parse(args); err != nil { + return nil + } + if cmd.NArg() < 1 { + cmd.Usage() + return nil + } + + body, err := apiPost("stop", cmd.Args()) + if err != nil { + return err + } + + var out []string + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + for _, name := range out { + fmt.Println(name) + } + return nil + +} + +func cmdVersion(args []string) error { + cmd := Subcmd("version", "", "Show the docker version information.") + if err := cmd.Parse(args); err != nil { + return nil + } + if cmd.NArg() > 0 { + cmd.Usage() + return nil + } + body, err := apiGet("version") if err != nil { return err @@ -162,12 +470,14 @@ func apiGet(path string) ([]byte, error) { if err != nil { return nil, err } - //TODO check status code defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } + if resp.StatusCode != 200 { + return nil, fmt.Errorf("error: %s", body) + } return body, nil } @@ -182,17 +492,33 @@ func apiPost(path string, data interface{}) ([]byte, error) { if err != nil { return nil, err } - //TODO check status code defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } + if resp.StatusCode != 200 { + return nil, fmt.Errorf("[error] %s", body) + } return body, nil - } -func subcmd(name, signature, description string) *flag.FlagSet { +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() { fmt.Printf("\nUsage: docker %s %s\n\n%s\n\n", name, signature, description) diff --git a/container.go b/container.go index 74706a407..96cd423a0 100644 --- a/container.go +++ b/container.go @@ -3,7 +3,6 @@ package docker import ( "encoding/json" "fmt" - "github.com/dotcloud/docker/rcli" "github.com/kr/pty" "io" "io/ioutil" @@ -66,8 +65,8 @@ type Config struct { Image string // Name of the image as it was passed by the operator (eg. could be symbolic) } -func ParseRun(args []string, stdout io.Writer) (*Config, error) { - cmd := rcli.Subcmd(stdout, "run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container") +func ParseRun(args []string) (*Config, 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) } @@ -82,7 +81,7 @@ func ParseRun(args []string, stdout io.Writer) (*Config, error) { flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)") if *flMemory > 0 && NO_MEMORY_LIMIT { - fmt.Fprintf(stdout, "WARNING: This version of docker has been compiled without memory limit support. Discarding -m.") + fmt.Println("WARNING: This version of docker has been compiled without memory limit support. Discarding -m.") *flMemory = 0 } From 1aa7f1392de665a6c8f751f53e1f7bb7bb6bf4f3 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 22 Apr 2013 18:17:47 +0200 Subject: [PATCH 06/69] restify api --- api.go | 324 ++++++++++------------------ commands.go | 585 +++++++++++++++++++++++++++++---------------------- commands2.go | 528 ---------------------------------------------- 3 files changed, 453 insertions(+), 984 deletions(-) delete mode 100644 commands2.go diff --git a/api.go b/api.go index 948b48d8b..f5c6acf53 100644 --- a/api.go +++ b/api.go @@ -9,6 +9,7 @@ import ( "net/http" "os" "runtime" + "strconv" "strings" "time" ) @@ -17,56 +18,42 @@ func ListenAndServe(addr string, rtime *Runtime) error { r := mux.NewRouter() log.Printf("Listening for HTTP on %s\n", addr) - r.Path("/version").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + 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} b, err := json.Marshal(m) if err != nil { - w.WriteHeader(500) + http.Error(w, err.Error(), http.StatusInternalServerError) } else { w.Write(b) } }) - r.Path("/kill").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var ids []string - if err := json.NewDecoder(r.Body).Decode(&ids); err != nil { - w.WriteHeader(500) - return - } - - var ret SimpleMessage - for _, name := range ids { - container := rtime.Get(name) - if container == nil { - ret.Message = "No such container: " + name + "\n" - break - } - if err := container.Kill(); err != nil { - ret.Message = ret.Message + "Error killing container " + name + ": " + err.Error() + "\n" - } - } - if ret.Message == "" { - w.WriteHeader(200) - } else { - w.WriteHeader(500) - } - - b, err := json.Marshal(ret) - if err != nil { - w.WriteHeader(500) - } else { - w.Write(b) - } - + r.Path("/containers/{name:.*}/kill").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(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) }) - r.Path("/images").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var in ImagesIn - json.NewDecoder(r.Body).Decode(&in) + r.Path("/images").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(r.RequestURI) + All := r.Form.Get("all") + NameFilter := r.Form.Get("filter") + Quiet := r.Form.Get("quiet") var allImages map[string]*Image var err error - if in.All { + if All == "true" { allImages, err = rtime.graph.Map() } else { allImages, err = rtime.graph.Heads() @@ -77,7 +64,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { } var outs []ImagesOut for name, repository := range rtime.repositories.Repositories { - if in.NameFilter != "" && name != in.NameFilter { + if NameFilter != "" && name != NameFilter { continue } for tag, id := range repository { @@ -88,7 +75,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { continue } delete(allImages, id) - if !in.Quiet { + if Quiet != "true" { out.Repository = name out.Tag = tag out.Id = TruncateId(id) @@ -100,10 +87,10 @@ func ListenAndServe(addr string, rtime *Runtime) error { } } // Display images which aren't part of a - if in.NameFilter == "" { + if NameFilter == "" { for id, image := range allImages { var out ImagesOut - if !in.Quiet { + if Quiet != "true" { out.Repository = "" out.Tag = "" out.Id = TruncateId(id) @@ -121,10 +108,10 @@ func ListenAndServe(addr string, rtime *Runtime) error { } else { w.Write(b) } - }) - r.Path("/info").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + r.Path("/info").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(r.RequestURI) images, _ := rtime.graph.All() var imgcount int if images == nil { @@ -149,13 +136,12 @@ func ListenAndServe(addr string, rtime *Runtime) error { } }) - r.Path("/history").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + r.Path("/images/{name:.*}/history").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println(r.RequestURI) + vars := mux.Vars(r) + name := vars["name"] - var in HistoryIn - json.NewDecoder(r.Body).Decode(&in) - - image, err := rtime.repositories.LookupImage(in.Name) + image, err := rtime.repositories.LookupImage(name) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -175,15 +161,14 @@ func ListenAndServe(addr string, rtime *Runtime) error { } else { w.Write(b) } - }) - r.Path("/logs").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + r.Path("/containers/{name:.*}/logs").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(r.RequestURI) + vars := mux.Vars(r) + name := vars["name"] - var in LogsIn - json.NewDecoder(r.Body).Decode(&in) - - if container := rtime.Get(in.Name); container != nil { + if container := rtime.Get(name); container != nil { var out LogsOut logStdout, err := container.ReadLog("stdout") @@ -220,28 +205,34 @@ func ListenAndServe(addr string, rtime *Runtime) error { } } else { - http.Error(w, "No such container: "+in.Name, http.StatusInternalServerError) + http.Error(w, "No such container: "+name, http.StatusInternalServerError) } }) - r.Path("/ps").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var in PsIn - json.NewDecoder(r.Body).Decode(&in) - + r.Path("/containers").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(r.RequestURI) + 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) + if err != nil { + n = -1 + } var outs []PsOut for i, container := range rtime.List() { - if !container.State.Running && !in.All && in.Last == -1 { + if !container.State.Running && All != "true" && n == -1 { continue } - if i == in.Last { + if i == n { break } var out PsOut out.Id = container.ShortId() - if !in.Quiet { + if Quiet != "true" { command := fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")) - if !in.Full { + if NoTrunc != "true" { command = Trunc(command, 20) } out.Image = rtime.repositories.ImageName(container.Image) @@ -258,173 +249,88 @@ func ListenAndServe(addr string, rtime *Runtime) error { } else { w.Write(b) } - }) - r.Path("/restart").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var ins, outs []string - json.NewDecoder(r.Body).Decode(&ins) - - for _, name := range ins { - if container := rtime.Get(name); container != nil { - if err := container.Restart(); err != nil { - http.Error(w, "Error restaring container "+name+": "+err.Error(), http.StatusInternalServerError) - return - } - outs = append(outs, container.ShortId()) - } else { - http.Error(w, "No such container: "+name, http.StatusInternalServerError) + r.Path("/containers/{name:.*}/restart").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(r.RequestURI) + vars := mux.Vars(r) + name := vars["name"] + if container := rtime.Get(name); container != nil { + if err := container.Restart(); err != nil { + http.Error(w, "Error restarting container "+name+": "+err.Error(), http.StatusInternalServerError) return } - } - b, err := json.Marshal(outs) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) } else { - w.Write(b) - } - }) - - r.Path("/rm").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var ins, outs []string - json.NewDecoder(r.Body).Decode(&ins) - - for _, name := range ins { - 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 - } - outs = append(outs, container.ShortId()) - } else { - http.Error(w, "No such container: "+name, http.StatusInternalServerError) - return - } - } - b, err := json.Marshal(outs) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } else { - w.Write(b) - } - - }) - - r.Path("/rmi").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var ins, outs []string - json.NewDecoder(r.Body).Decode(&ins) - - for _, name := range ins { - img, err := rtime.repositories.LookupImage(name) - if err != nil { - http.Error(w, "No such image: "+name, http.StatusInternalServerError) - return - } else { - if err := rtime.graph.Delete(img.Id); err != nil { - http.Error(w, "Error deleting image "+name+": "+err.Error(), http.StatusInternalServerError) - return - } - outs = append(outs, img.ShortId()) - } - } - b, err := json.Marshal(outs) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } else { - w.Write(b) - } - - }) - - r.Path("/run").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - - var config Config - json.NewDecoder(r.Body).Decode(&config) - - hj, ok := w.(http.Hijacker) - if !ok { - http.Error(w, "webserver doesn't support hijacking", http.StatusInternalServerError) + http.Error(w, "No such container: "+name, http.StatusInternalServerError) return } - conn, bufrw, err := hj.Hijack() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + w.WriteHeader(200) + }) + + r.Path("/containers/{name:.*}").Methods("DELETE").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(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 + } + } else { + http.Error(w, "No such container: "+name, http.StatusInternalServerError) return } - defer conn.Close() - - //TODO config.Tty - - // Create new container - container, err := rtime.Create(&config) - if err != nil { - // If container not found, try to pull it - if rtime.graph.IsNotExist(err) { - bufrw.WriteString("Image " + config.Image + " not found, trying to pull it from registry.\r\n") - bufrw.Flush() - //TODO if err = srv.CmdPull(stdin, stdout, config.Image); err != nil { - //return err - //} - if container, err = rtime.Create(&config); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - } else { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - } - container = container + w.WriteHeader(200) }) - r.Path("/start").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var ins, outs []string - json.NewDecoder(r.Body).Decode(&ins) - - for _, name := range ins { - 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 - } - outs = append(outs, container.ShortId()) - } else { - http.Error(w, "No such container: "+name, http.StatusInternalServerError) + r.Path("/images/{name:.*}").Methods("DELETE").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(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) + return + } else { + if err := rtime.graph.Delete(img.Id); err != nil { + http.Error(w, "Error deleting image "+name+": "+err.Error(), http.StatusInternalServerError) return } } - b, err := json.Marshal(outs) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } else { - w.Write(b) - } - + w.WriteHeader(200) }) - r.Path("/stop").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var ins, outs []string - json.NewDecoder(r.Body).Decode(&ins) - - for _, name := range ins { - if container := rtime.Get(name); container != nil { - if err := container.Stop(); err != nil { - http.Error(w, "Error stopping container "+name+": "+err.Error(), http.StatusInternalServerError) - return - } - outs = append(outs, container.ShortId()) - } else { - http.Error(w, "No such container: "+name, http.StatusInternalServerError) + r.Path("/containers/{name:.*}/start").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(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 } - } - b, err := json.Marshal(outs) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) } else { - w.Write(b) + http.Error(w, "No such container: "+name, http.StatusInternalServerError) + return } + w.WriteHeader(200) + }) + r.Path("/containers/{name:.*}/stop").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(r.RequestURI) + vars := mux.Vars(r) + name := vars["name"] + if container := rtime.Get(name); container != nil { + if err := container.Stop(); err != nil { + http.Error(w, "Error stopping container "+name+": "+err.Error(), http.StatusInternalServerError) + return + } + } else { + http.Error(w, "No such container: "+name, http.StatusInternalServerError) + return + } + w.WriteHeader(200) }) return http.ListenAndServe(addr, r) diff --git a/commands.go b/commands.go index f7361b1a1..8bcbec551 100644 --- a/commands.go +++ b/commands.go @@ -1,21 +1,17 @@ package docker import ( - "bytes" + _"bytes" "encoding/json" + "flag" "fmt" - "github.com/dotcloud/docker/auth" - "github.com/dotcloud/docker/rcli" - "io" - "log" + _"io" + "io/ioutil" "net/http" "net/url" - "runtime" + "os" "strconv" - "strings" "text/tabwriter" - "time" - "unicode" ) const VERSION = "0.1.4" @@ -25,45 +21,70 @@ var ( NO_MEMORY_LIMIT bool ) -func (srv *Server) Name() string { - return "docker" +func ParseCommands(args []string) error { + + cmds := map[string]func(args []string) error{ + "images": CmdImages, + "info": CmdInfo, + "history": CmdHistory, + "kill": CmdKill, + "logs": CmdLogs, + "ps": CmdPs, + "restart": CmdRestart, + "rm": CmdRm, + "rmi": CmdRmi, + "start": CmdStart, + "stop": CmdStop, + "version": CmdVersion, + } + + if len(args) > 0 { + cmd, exists := cmds[args[0]] + if !exists { + //TODO display commend not found + return cmdHelp(args) + } + return cmd(args[1:]) + } + return cmdHelp(args) } -// FIXME: Stop violating DRY by repeating usage here and in Subcmd declarations -func (srv *Server) Help() string { +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"}, - {"commit", "Create a new image from a container's changes"}, - {"diff", "Inspect changes on a container's filesystem"}, - {"export", "Stream the contents of a container as a tar archive"}, + // {"attach", "Attach to a running container"}, + // {"commit", "Create a new image from a container's changes"}, + // {"diff", "Inspect changes on a container's filesystem"}, + // {"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"}, + // {"inspect", "Return low-level information on a container"}, {"kill", "Kill a running container"}, - {"login", "Register or Login to the docker registry server"}, + // {"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"}, - {"push", "Push an image or a repository to 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"}, + // {"wait", "Block until a container stops, then print its exit code"}, } { help += fmt.Sprintf(" %-10.10s%s\n", cmd[0], cmd[1]) } - return help + fmt.Println(help) + return nil } +/* // 'docker login': login / register a user to registry service. func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { // Read a line on raw terminal with support for simple backspace @@ -161,7 +182,9 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args .. } return nil } +*/ +/* // 'docker wait': block until a container stops func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.") @@ -181,27 +204,12 @@ func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string } return nil } +*/ + // 'docker version': show version information -func (srv *Server) CmdVersion(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - fmt.Fprintf(stdout, "Version:%s\n", VERSION) - fmt.Fprintf(stdout, "Git Commit:%s\n", GIT_COMMIT) - if NO_MEMORY_LIMIT { - fmt.Fprintf(stdout, "Memory limit disabled\n") - } - return nil -} - -// 'docker info': display system-wide information. -func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - images, _ := srv.runtime.graph.All() - var imgcount int - if images == nil { - imgcount = 0 - } else { - imgcount = len(images) - } - cmd := rcli.Subcmd(stdout, "info", "", "Display system-wide information.") +func CmdVersion(args []string) error { + cmd := Subcmd("version", "", "Show the docker version information.") if err := cmd.Parse(args); err != nil { return nil } @@ -209,21 +217,57 @@ func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string cmd.Usage() return nil } - fmt.Fprintf(stdout, "containers: %d\nversion: %s\nimages: %d\n", - len(srv.runtime.List()), - VERSION, - imgcount) - if !rcli.DEBUG_FLAG { - return nil + body, err := call("GET", "version") + if err != nil { + return err } - fmt.Fprintln(stdout, "debug mode enabled") - fmt.Fprintf(stdout, "fds: %d\ngoroutines: %d\n", getTotalUsedFds(), runtime.NumGoroutine()) + + var out VersionOut + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + fmt.Println("Version:", out.Version) + fmt.Println("Git Commit:", out.GitCommit) + if out.MemoryLimitDisabled { + fmt.Println("Memory limit disabled") + } + return nil } -func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "stop", "CONTAINER [CONTAINER...]", "Stop a running container") +// 'docker info': display system-wide information. +func CmdInfo(args []string) error { + cmd := Subcmd("info", "", "Display system-wide information") + if err := cmd.Parse(args); err != nil { + return nil + } + if cmd.NArg() > 0 { + cmd.Usage() + return nil + } + + body, err := call("GET", "info") + if err != nil { + return err + } + + var out InfoOut + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + fmt.Printf("containers: %d\nversion: %s\nimages: %d\n", out.Containers, out.Version, out.Images) + if out.Debug { + fmt.Println("debug mode enabled") + fmt.Printf("fds: %d\ngoroutines: %d\n", out.NFd, out.NGoroutines) + } + return nil +} + +func CmdStop(args []string) error { + cmd := Subcmd("stop", "CONTAINER [CONTAINER...]", "Stop a running container") if err := cmd.Parse(args); err != nil { return nil } @@ -231,21 +275,20 @@ func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string cmd.Usage() return nil } - for _, name := range cmd.Args() { - if container := srv.runtime.Get(name); container != nil { - if err := container.Stop(); err != nil { - return err - } - fmt.Fprintln(stdout, container.ShortId()) + + for _, name := range args { + _, err := call("GET", "/containers/"+name+"/stop") + if err != nil { + fmt.Printf("%s", err) } else { - return fmt.Errorf("No such container: %s", name) + fmt.Println(name) } } return nil } -func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "restart", "CONTAINER [CONTAINER...]", "Restart a running container") +func CmdRestart(args []string) error { + cmd := Subcmd("restart", "CONTAINER [CONTAINER...]", "Restart a running container") if err := cmd.Parse(args); err != nil { return nil } @@ -253,21 +296,20 @@ func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...str cmd.Usage() return nil } - for _, name := range cmd.Args() { - if container := srv.runtime.Get(name); container != nil { - if err := container.Restart(); err != nil { - return err - } - fmt.Fprintln(stdout, container.ShortId()) + + for _, name := range args { + _, err := call("GET", "/containers/"+name+"/restart") + if err != nil { + fmt.Printf("%s", err) } else { - return fmt.Errorf("No such container: %s", name) + fmt.Println(name) } } return nil } -func (srv *Server) CmdStart(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "start", "CONTAINER [CONTAINER...]", "Start a stopped container") +func CmdStart(args []string) error { + cmd := Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container") if err := cmd.Parse(args); err != nil { return nil } @@ -275,19 +317,19 @@ func (srv *Server) CmdStart(stdin io.ReadCloser, stdout io.Writer, args ...strin cmd.Usage() return nil } - for _, name := range cmd.Args() { - if container := srv.runtime.Get(name); container != nil { - if err := container.Start(); err != nil { - return err - } - fmt.Fprintln(stdout, container.ShortId()) + + for _, name := range args { + _, err := call("GET", "/containers/"+name+"/start") + if err != nil { + fmt.Printf("%s", err) } else { - return fmt.Errorf("No such container: %s", name) + fmt.Println(name) } } return nil } +/* func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "inspect", "CONTAINER", "Return low-level information on a container") if err := cmd.Parse(args); err != nil { @@ -322,7 +364,9 @@ func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...str stdout.Write([]byte{'\n'}) return nil } +*/ +/* 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") if err := cmd.Parse(args); err != nil { @@ -345,10 +389,11 @@ func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string } return nil } +*/ // 'docker rmi IMAGE' removes all images with the name IMAGE -func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string) (err error) { - cmd := rcli.Subcmd(stdout, "rmimage", "IMAGE [IMAGE...]", "Remove an image") +func CmdRmi(args []string) error { + cmd := Subcmd("rmi", "IMAGE [IMAGE...]", "Remove an image") if err := cmd.Parse(args); err != nil { return nil } @@ -356,20 +401,20 @@ func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string) cmd.Usage() return nil } - for _, name := range cmd.Args() { - img, err := srv.runtime.repositories.LookupImage(name) + + for _, name := range args { + _, err := call("DELETE", "/images/"+name) if err != nil { - return err - } - if err := srv.runtime.graph.Delete(img.Id); err != nil { - return err + fmt.Printf("%s", err) + } else { + fmt.Println(name) } } return nil } -func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "history", "IMAGE", "Show the history of an image") +func CmdHistory(args []string) error { + cmd := Subcmd("history", "IMAGE", "Show the history of an image") if err := cmd.Parse(args); err != nil { return nil } @@ -377,25 +422,29 @@ func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...str cmd.Usage() return nil } - image, err := srv.runtime.repositories.LookupImage(cmd.Arg(0)) + + body, err := call("GET", "images/"+cmd.Arg(0)+"/history") if err != nil { return err } - w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0) - defer w.Flush() + + var outs []HistoryOut + err = json.Unmarshal(body, &outs) + if err != nil { + return err + } + w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) fmt.Fprintln(w, "ID\tCREATED\tCREATED BY") - return image.WalkHistory(func(img *Image) error { - fmt.Fprintf(w, "%s\t%s\t%s\n", - srv.runtime.repositories.ImageName(img.ShortId()), - HumanDuration(time.Now().Sub(img.Created))+" ago", - strings.Join(img.ContainerConfig.Cmd, " "), - ) - return nil - }) + + for _, out := range outs { + fmt.Fprintf(w, "%s\t%s\t%s\n", out.Id, out.Created, out.CreatedBy) + } + w.Flush() + return nil } -func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "rm", "CONTAINER [CONTAINER...]", "Remove a container") +func CmdRm(args []string) error { + cmd := Subcmd("rm", "CONTAINER [CONTAINER...]", "Remove a container") if err := cmd.Parse(args); err != nil { return nil } @@ -403,21 +452,21 @@ func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) cmd.Usage() return nil } - for _, name := range cmd.Args() { - container := srv.runtime.Get(name) - if container == nil { - return fmt.Errorf("No such container: %s", name) - } - if err := srv.runtime.Destroy(container); err != nil { - fmt.Fprintln(stdout, "Error destroying container "+name+": "+err.Error()) + + for _, name := range args { + _, err := call("DELETE", "/containers/"+name) + if err != nil { + fmt.Printf("%s", err) + } else { + fmt.Println(name) } } return nil } // 'docker kill NAME' kills a running container -func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "kill", "CONTAINER [CONTAINER...]", "Kill a running container") +func CmdKill(args []string) error { + cmd := Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container") if err := cmd.Parse(args); err != nil { return nil } @@ -425,18 +474,19 @@ func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string cmd.Usage() return nil } - for _, name := range cmd.Args() { - container := srv.runtime.Get(name) - if container == nil { - return fmt.Errorf("No such container: %s", name) - } - if err := container.Kill(); err != nil { - fmt.Fprintln(stdout, "Error killing container "+name+": "+err.Error()) + + for _, name := range args { + _, err := call("POST", "/containers/"+name+"/kill") + if err != nil { + fmt.Printf("%s", err) + } else { + fmt.Println(name) } } return nil } +/* func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { stdout.Flush() cmd := rcli.Subcmd(stdout, "import", "URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball") @@ -486,7 +536,9 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args . fmt.Fprintln(stdout, img.ShortId()) return nil } +*/ +/* func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { cmd := rcli.Subcmd(stdout, "push", "NAME", "Push an image or a repository to the registry") if err := cmd.Parse(args); err != nil { @@ -545,7 +597,9 @@ func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ... } return nil } +*/ +/* 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") if err := cmd.Parse(args); err != nil { @@ -570,12 +624,13 @@ func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string } return nil } +*/ -func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "images", "[OPTIONS] [NAME]", "List images") - //limit := cmd.Int("l", 0, "Only show the N most recent versions of each image") +func CmdImages(args []string) error { + cmd := Subcmd("images", "[OPTIONS] [NAME]", "List images") quiet := cmd.Bool("q", false, "only show numeric IDs") - flAll := cmd.Bool("a", false, "show all images") + all := cmd.Bool("a", false, "show all images") + if err := cmd.Parse(args); err != nil { return nil } @@ -583,137 +638,104 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri cmd.Usage() return nil } - var nameFilter string + v := url.Values{} if cmd.NArg() == 1 { - nameFilter = cmd.Arg(0) + v.Set("filter", cmd.Arg(0)) } - w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0) - if !*quiet { - fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED") + if *quiet { + v.Set("quiet", "true") } - var allImages map[string]*Image - var err error - if *flAll { - allImages, err = srv.runtime.graph.Map() - } else { - allImages, err = srv.runtime.graph.Heads() + if *all { + v.Set("all", "true") } + + body, err := call("GET", "images?"+v.Encode()) if err != nil { return err } - for name, repository := range srv.runtime.repositories.Repositories { - if nameFilter != "" && name != nameFilter { - continue - } - for tag, id := range repository { - 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 { - for idx, field := range []string{ - /* REPOSITORY */ name, - /* TAG */ tag, - /* ID */ TruncateId(id), - /* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago", - } { - if idx == 0 { - w.Write([]byte(field)) - } else { - w.Write([]byte("\t" + field)) - } - } - w.Write([]byte{'\n'}) - } else { - stdout.Write([]byte(image.ShortId() + "\n")) - } - } + + var outs []ImagesOut + err = json.Unmarshal(body, &outs) + if err != nil { + return err } - // Display images which aren't part of a - if nameFilter == "" { - for id, image := range allImages { - if !*quiet { - for idx, field := range []string{ - /* REPOSITORY */ "", - /* TAG */ "", - /* ID */ TruncateId(id), - /* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago", - } { - if idx == 0 { - w.Write([]byte(field)) - } else { - w.Write([]byte("\t" + field)) - } - } - w.Write([]byte{'\n'}) - } else { - stdout.Write([]byte(image.ShortId() + "\n")) - } + w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) + if !*quiet { + fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED") + } + + for _, out := range outs { + if !*quiet { + fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", out.Repository, out.Tag, out.Id, out.Created) + } else { + fmt.Fprintln(w, out.Id) } } + if !*quiet { w.Flush() } return nil } -func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, - "ps", "[OPTIONS]", "List containers") +func CmdPs(args []string) error { + cmd := Subcmd("ps", "[OPTIONS]", "List containers") quiet := cmd.Bool("q", false, "Only display numeric IDs") - flAll := cmd.Bool("a", false, "Show all containers. Only running containers are shown by default.") - flFull := cmd.Bool("notrunc", false, "Don't truncate output") - latest := cmd.Bool("l", false, "Show only the latest created container, include non-running ones.") - nLast := cmd.Int("n", -1, "Show n last created containers, include non-running ones.") + all := cmd.Bool("a", false, "Show all containers. Only running containers are shown by default.") + noTrunc := cmd.Bool("notrunc", false, "Don't truncate output") + nLatest := cmd.Bool("l", false, "Show only the latest created container, include non-running ones.") + last := cmd.Int("n", -1, "Show n last created containers, include non-running ones.") + if err := cmd.Parse(args); err != nil { return nil } - if *nLast == -1 && *latest { - *nLast = 1 + v := url.Values{} + if *last == -1 && *nLatest { + *last = 1 } - w := tabwriter.NewWriter(stdout, 12, 1, 3, ' ', 0) + if *quiet { + v.Set("quiet", "true") + } + if *all { + v.Set("all", "true") + } + if *noTrunc { + v.Set("notrunc", "true") + } + if *last != -1 { + v.Set("n", strconv.Itoa(*last)) + } + + body, err := call("GET", "containers?"+v.Encode()) + if err != nil { + return err + } + + var outs []PsOut + err = json.Unmarshal(body, &outs) + if err != nil { + return err + } + w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) if !*quiet { - fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tCOMMENT") + fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS") } - for i, container := range srv.runtime.List() { - if !container.State.Running && !*flAll && *nLast == -1 { - continue - } - if i == *nLast { - break - } + + for _, out := range outs { if !*quiet { - command := fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")) - if !*flFull { - command = Trunc(command, 20) - } - for idx, field := range []string{ - /* ID */ container.ShortId(), - /* IMAGE */ srv.runtime.repositories.ImageName(container.Image), - /* COMMAND */ command, - /* CREATED */ HumanDuration(time.Now().Sub(container.Created)) + " ago", - /* STATUS */ container.State.String(), - /* COMMENT */ "", - } { - if idx == 0 { - w.Write([]byte(field)) - } else { - w.Write([]byte("\t" + field)) - } - } - w.Write([]byte{'\n'}) + fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", out.Id, out.Image, out.Command, out.Status, out.Created) } else { - stdout.Write([]byte(container.ShortId() + "\n")) + fmt.Fprintln(w, out.Id) } } + if !*quiet { w.Flush() } return nil } +/* func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]", @@ -734,7 +756,9 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri fmt.Fprintln(stdout, img.ShortId()) return nil } +*/ +/* func (srv *Server) CmdExport(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "export", "CONTAINER", @@ -756,7 +780,9 @@ func (srv *Server) CmdExport(stdin io.ReadCloser, stdout io.Writer, args ...stri } return fmt.Errorf("No such container: %s", name) } +*/ +/* func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "diff", "CONTAINER", @@ -781,9 +807,10 @@ func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string } return nil } +*/ -func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "logs", "CONTAINER", "Fetch the logs of a container") +func CmdLogs(args []string) error { + cmd := Subcmd("logs", "CONTAINER", "Fetch the logs of a container") if err := cmd.Parse(args); err != nil { return nil } @@ -791,29 +818,23 @@ func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string cmd.Usage() return nil } - name := cmd.Arg(0) - if container := srv.runtime.Get(name); container != nil { - logStdout, err := container.ReadLog("stdout") - if err != nil { - return err - } - logStderr, err := container.ReadLog("stderr") - if err != nil { - return err - } - // FIXME: Interpolate stdout and stderr instead of concatenating them - // FIXME: Differentiate stdout and stderr in the remote protocol - if _, err := io.Copy(stdout, logStdout); err != nil { - return err - } - if _, err := io.Copy(stdout, logStderr); err != nil { - return err - } - return nil + body, err := call("GET", "containers/"+cmd.Arg(0)+"/logs") + if err != nil { + return err } - return fmt.Errorf("No such container: %s", cmd.Arg(0)) + + var out LogsOut + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + fmt.Fprintln(os.Stdout, out.Stdout) + fmt.Fprintln(os.Stderr, out.Stderr) + + return nil } +/* func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { cmd := rcli.Subcmd(stdout, "attach", "CONTAINER", "Attach to a running container") if err := cmd.Parse(args); err != nil { @@ -836,7 +857,8 @@ func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout rcli.DockerConn, args . stdout.Flush() return <-container.Attach(stdin, nil, stdout, stdout) } - +*/ +/* // Ports type - Used to parse multiple -p flags type ports []int @@ -852,6 +874,7 @@ func (p *ports) Set(value string) error { *p = append(*p, port) return nil } +*/ // ListOpts type type ListOpts []string @@ -892,6 +915,8 @@ 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") force := cmd.Bool("f", false, "Force") @@ -904,7 +929,9 @@ func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string) } 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) if err != nil { @@ -974,8 +1001,72 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...s <-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) + if err != nil { + return nil, err + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + if resp.StatusCode != 200 { + return nil, fmt.Errorf("error: %s", body) + } + 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 + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + if resp.StatusCode != 200 { + return nil, fmt.Errorf("[error] %s", body) + } + return body, nil } -type Server struct { - runtime *Runtime +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() { + fmt.Printf("\nUsage: docker %s %s\n\n%s\n\n", name, signature, description) + flags.PrintDefaults() + } + return flags } diff --git a/commands2.go b/commands2.go deleted file mode 100644 index 02056f543..000000000 --- a/commands2.go +++ /dev/null @@ -1,528 +0,0 @@ -package docker - -import ( - "bytes" - "encoding/json" - "flag" - "fmt" - "io" - "io/ioutil" - "net/http" - "os" - "text/tabwriter" -) - -func ParseCommands(args []string) error { - - cmds := map[string]func(args []string) error{ - "images": cmdImages, - "info": cmdInfo, - "history": cmdHistory, - "kill": cmdKill, - "logs": cmdLogs, - "ps": cmdPs, - "restart": cmdRestart, - "rm": cmdRm, - "rmi": cmdRmi, - "run": cmdRun, - "start": cmdStart, - "stop": cmdStop, - "version": cmdVersion, - } - - if len(args) > 0 { - cmd, exists := cmds[args[0]] - if !exists { - //TODO display commend not found - return cmdHelp(args) - } - return cmd(args[1:]) - } - return cmdHelp(args) -} - -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"}, - // {"commit", "Create a new image from a container's changes"}, - // {"diff", "Inspect changes on a container's filesystem"}, - // {"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"}, - {"info", "Display system-wide information"}, - // {"inspect", "Return low-level information on a container"}, - {"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"}, - {"ps", "List containers"}, - // {"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"}, - {"start", "Start a stopped container"}, - {"stop", "Stop a running container"}, - // {"tag", "Tag an image into a repository"}, - {"version", "Show the docker version information"}, - // {"wait", "Block until a container stops, then print its exit code"}, - } { - help += fmt.Sprintf(" %-10.10s%s\n", cmd[0], cmd[1]) - } - fmt.Println(help) - return nil -} - -func cmdImages(args []string) error { - cmd := Subcmd("images", "[OPTIONS] [NAME]", "List images") - var in ImagesIn - cmd.BoolVar(&in.Quiet, "q", false, "only show numeric IDs") - cmd.BoolVar(&in.All, "a", false, "show all images") - if err := cmd.Parse(args); err != nil { - return nil - } - if cmd.NArg() > 1 { - cmd.Usage() - return nil - } - if cmd.NArg() == 1 { - in.NameFilter = cmd.Arg(0) - } - - body, err := apiPost("images", in) - if err != nil { - return err - } - - var outs []ImagesOut - err = json.Unmarshal(body, &outs) - if err != nil { - return err - } - w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) - if !in.Quiet { - fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED") - } - - for _, out := range outs { - if !in.Quiet { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", out.Repository, out.Tag, out.Id, out.Created) - } else { - fmt.Fprintln(w, out.Id) - } - } - - if !in.Quiet { - w.Flush() - } - return nil - -} - -func cmdInfo(args []string) error { - cmd := Subcmd("info", "", "Display system-wide information") - if err := cmd.Parse(args); err != nil { - return nil - } - if cmd.NArg() > 0 { - cmd.Usage() - return nil - } - - body, err := apiGet("info") - if err != nil { - return err - } - - var out InfoOut - err = json.Unmarshal(body, &out) - if err != nil { - return err - } - fmt.Printf("containers: %d\nversion: %s\nimages: %d\n", out.Containers, out.Version, out.Images) - if out.Debug { - fmt.Println("debug mode enabled") - fmt.Printf("fds: %d\ngoroutines: %d\n", out.NFd, out.NGoroutines) - } - return nil - -} - -func cmdHistory(args []string) error { - cmd := Subcmd("history", "IMAGE", "Show the history of an image") - if err := cmd.Parse(args); err != nil { - return nil - } - if cmd.NArg() != 1 { - cmd.Usage() - return nil - } - - body, err := apiPost("history", HistoryIn{cmd.Arg(0)}) - if err != nil { - return err - } - - var outs []HistoryOut - err = json.Unmarshal(body, &outs) - if err != nil { - return err - } - w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) - fmt.Fprintln(w, "ID\tCREATED\tCREATED BY") - - for _, out := range outs { - fmt.Fprintf(w, "%s\t%s\t%s\n", out.Id, out.Created, out.CreatedBy) - } - w.Flush() - return nil -} - -func cmdKill(args []string) error { - cmd := Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container") - if err := cmd.Parse(args); err != nil { - return nil - } - if cmd.NArg() < 1 { - cmd.Usage() - return nil - } - - body, err := apiPost("kill", args) - if err != nil { - return err - } - - var out SimpleMessage - err = json.Unmarshal(body, &out) - if err != nil { - return err - } - fmt.Print(out.Message) - return nil -} - -func cmdLogs(args []string) error { - cmd := Subcmd("logs", "CONTAINER", "Fetch the logs of a container") - if err := cmd.Parse(args); err != nil { - return nil - } - if cmd.NArg() != 1 { - cmd.Usage() - return nil - } - body, err := apiPost("logs", LogsIn{cmd.Arg(0)}) - if err != nil { - return err - } - - var out LogsOut - err = json.Unmarshal(body, &out) - if err != nil { - return err - } - fmt.Fprintln(os.Stdout, out.Stdout) - fmt.Fprintln(os.Stderr, out.Stderr) - - return nil -} - -func cmdPs(args []string) error { - cmd := Subcmd("ps", "[OPTIONS]", "List containers") - var in PsIn - cmd.BoolVar(&in.Quiet, "q", false, "Only display numeric IDs") - cmd.BoolVar(&in.All, "a", false, "Show all containers. Only running containers are shown by default.") - cmd.BoolVar(&in.Full, "notrunc", false, "Don't truncate output") - nLatest := cmd.Bool("l", false, "Show only the latest created container, include non-running ones.") - cmd.IntVar(&in.Last, "n", -1, "Show n last created containers, include non-running ones.") - if err := cmd.Parse(args); err != nil { - return nil - } - if in.Last == -1 && *nLatest { - in.Last = 1 - } - - body, err := apiPost("ps", in) - if err != nil { - return err - } - - var outs []PsOut - err = json.Unmarshal(body, &outs) - if err != nil { - return err - } - w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) - if !in.Quiet { - fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS") - } - - for _, out := range outs { - if !in.Quiet { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", out.Id, out.Image, out.Command, out.Status, out.Created) - } else { - fmt.Fprintln(w, out.Id) - } - } - - if !in.Quiet { - w.Flush() - } - return nil -} - -func cmdRestart(args []string) error { - cmd := Subcmd("restart", "CONTAINER [CONTAINER...]", "Restart a running container") - if err := cmd.Parse(args); err != nil { - return nil - } - if cmd.NArg() < 1 { - cmd.Usage() - return nil - } - - body, err := apiPost("restart", cmd.Args()) - if err != nil { - return err - } - - var out []string - err = json.Unmarshal(body, &out) - if err != nil { - return err - } - for _, name := range out { - fmt.Println(name) - } - return nil -} - -func cmdRm(args []string) error { - cmd := Subcmd("rm", "CONTAINER [CONTAINER...]", "Remove a container") - if err := cmd.Parse(args); err != nil { - return nil - } - if cmd.NArg() < 1 { - cmd.Usage() - return nil - } - - body, err := apiPost("rm", cmd.Args()) - if err != nil { - return err - } - - var out []string - err = json.Unmarshal(body, &out) - if err != nil { - return err - } - for _, name := range out { - fmt.Println(name) - } - return nil -} - -func cmdRmi(args []string) error { - cmd := Subcmd("rmi", "IMAGE [IMAGE...]", "Remove an image") - if err := cmd.Parse(args); err != nil { - return nil - } - if cmd.NArg() < 1 { - cmd.Usage() - return nil - } - - body, err := apiPost("rmi", cmd.Args()) - if err != nil { - return err - } - - var out []string - err = json.Unmarshal(body, &out) - if err != nil { - return err - } - for _, name := range out { - fmt.Println(name) - } - return nil -} - -func cmdRun(args []string) error { - config, err := ParseRun(args) - if err != nil { - return err - } - if config.Image == "" { - fmt.Println("Error: Image not specified") - return fmt.Errorf("Image not specified") - } - if len(config.Cmd) == 0 { - fmt.Println("Error: Command not specified") - return fmt.Errorf("Command not specified") - } - - body, err := apiPostHijack("run", config) - if err != nil { - return err - } - defer body.Close() - - /*str, err2 := ioutil.ReadAll(body) - if err2 != nil { - return err2 - } - fmt.Println(str)*/ - return nil - -} - -func cmdStart(args []string) error { - cmd := Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container") - if err := cmd.Parse(args); err != nil { - return nil - } - if cmd.NArg() < 1 { - cmd.Usage() - return nil - } - - body, err := apiPost("start", cmd.Args()) - if err != nil { - return err - } - - var out []string - err = json.Unmarshal(body, &out) - if err != nil { - return err - } - for _, name := range out { - fmt.Println(name) - } - - return nil - -} - -func cmdStop(args []string) error { - cmd := Subcmd("stop", "CONTAINER [CONTAINER...]", "Restart a running container") - if err := cmd.Parse(args); err != nil { - return nil - } - if cmd.NArg() < 1 { - cmd.Usage() - return nil - } - - body, err := apiPost("stop", cmd.Args()) - if err != nil { - return err - } - - var out []string - err = json.Unmarshal(body, &out) - if err != nil { - return err - } - for _, name := range out { - fmt.Println(name) - } - return nil - -} - -func cmdVersion(args []string) error { - cmd := Subcmd("version", "", "Show the docker version information.") - if err := cmd.Parse(args); err != nil { - return nil - } - if cmd.NArg() > 0 { - cmd.Usage() - return nil - } - - body, err := apiGet("version") - if err != nil { - return err - } - - var out VersionOut - err = json.Unmarshal(body, &out) - if err != nil { - return err - } - fmt.Println("Version:", out.Version) - fmt.Println("Git Commit:", out.GitCommit) - if out.MemoryLimitDisabled { - fmt.Println("Memory limit disabled") - } - - return nil -} - -func apiGet(path string) ([]byte, error) { - resp, err := http.Get("http://0.0.0.0:4243/" + path) - if err != nil { - return nil, err - } - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - if resp.StatusCode != 200 { - return nil, fmt.Errorf("error: %s", body) - } - 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 - } - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - if resp.StatusCode != 200 { - return nil, fmt.Errorf("[error] %s", body) - } - return body, 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() { - fmt.Printf("\nUsage: docker %s %s\n\n%s\n\n", name, signature, description) - flags.PrintDefaults() - } - return flags -} From 6ce475dbdf97fd5e721a150c58d92d6819e5decb Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 22 Apr 2013 23:37:22 +0200 Subject: [PATCH 07/69] added push hijack (wip) --- api.go | 34 ++++++++++++++++++++++++++++++++++ api_params.go | 4 ++++ commands.go | 12 ++++++------ registry.go | 11 ++++++----- 4 files changed, 50 insertions(+), 11 deletions(-) diff --git a/api.go b/api.go index f5c6acf53..fdf87f3f1 100644 --- a/api.go +++ b/api.go @@ -1,6 +1,7 @@ package docker import ( + _"bytes" "encoding/json" "fmt" "github.com/gorilla/mux" @@ -251,6 +252,39 @@ 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" + + hj, ok := w.(http.Hijacker) + if !ok { + http.Error(w, "webserver doesn't support hijacking", http.StatusInternalServerError) + return + } + conn, bufrw, err := hj.Hijack() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + // Don't forget to close the connection: + defer conn.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) + } + return + } + if err := rtime.graph.PullRepository(bufrw, in.Name, "", rtime.repositories, rtime.authConfig); err != nil { + //http.Error(w, err.Error(), http.StatusInternalServerError) + } + return + }) + + r.Path("/containers/{name:.*}/restart").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println(r.RequestURI) vars := mux.Vars(r) diff --git a/api_params.go b/api_params.go index 4a2bbee13..8f8b332a0 100644 --- a/api_params.go +++ b/api_params.go @@ -51,6 +51,10 @@ type PsOut struct { Status string `json:",omitempty"` } +type PullIn struct { + Name string +} + type LogsIn struct { Name string } diff --git a/commands.go b/commands.go index 8bcbec551..e9b070158 100644 --- a/commands.go +++ b/commands.go @@ -613,15 +613,15 @@ func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string // 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 - } + // if err := srv.runtime.graph.PullImage(stdout, remote, srv.runtime.authConfig); err != nil { + // return err + // } return nil } // FIXME: Allow pull repo:tag - if err := srv.runtime.graph.PullRepository(stdout, remote, "", srv.runtime.repositories, srv.runtime.authConfig); err != nil { - return err - } + //if err := srv.runtime.graph.PullRepository(stdout, remote, "", srv.runtime.repositories, srv.runtime.authConfig); err != nil { + // return err + //} return nil } */ diff --git a/registry.go b/registry.go index 428db1b96..7d13efd88 100644 --- a/registry.go +++ b/registry.go @@ -1,6 +1,7 @@ package docker import ( + "bufio" "encoding/json" "fmt" "github.com/dotcloud/docker/auth" @@ -94,10 +95,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 io.Writer, imgId string, authConfig *auth.AuthConfig) (*Image, Archive, error) { +func (graph *Graph) getRemoteImage(stdout *bufio.ReadWriter, imgId string, authConfig *auth.AuthConfig) (*Image, Archive, error) { client := &http.Client{} - fmt.Fprintf(stdout, "Pulling %s metadata\r\n", imgId) + fmt.Fprintf(stdout, "Pulling %s metadata\r\n", imgId);stdout.Flush() // Get the Json req, err := http.NewRequest("GET", REGISTRY_ENDPOINT+"/images/"+imgId+"/json", nil) if err != nil { @@ -125,7 +126,7 @@ func (graph *Graph) getRemoteImage(stdout io.Writer, imgId string, authConfig *a img.Id = imgId // Get the layer - fmt.Fprintf(stdout, "Pulling %s fs layer\r\n", imgId) + fmt.Fprintf(stdout, "Pulling %s fs layer\r\n", imgId);stdout.Flush() 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) @@ -138,7 +139,7 @@ func (graph *Graph) getRemoteImage(stdout io.Writer, imgId string, authConfig *a return img, ProgressReader(res.Body, int(res.ContentLength), stdout), nil } -func (graph *Graph) PullImage(stdout io.Writer, imgId string, authConfig *auth.AuthConfig) error { +func (graph *Graph) PullImage(stdout *bufio.ReadWriter, imgId string, authConfig *auth.AuthConfig) error { history, err := graph.getRemoteHistory(imgId, authConfig) if err != nil { return err @@ -161,7 +162,7 @@ func (graph *Graph) PullImage(stdout io.Writer, imgId string, authConfig *auth.A } // FIXME: Handle the askedTag parameter -func (graph *Graph) PullRepository(stdout io.Writer, remote, askedTag string, repositories *TagStore, authConfig *auth.AuthConfig) error { +func (graph *Graph) PullRepository(stdout *bufio.ReadWriter, remote, askedTag string, repositories *TagStore, authConfig *auth.AuthConfig) error { client := &http.Client{} fmt.Fprintf(stdout, "Pulling repository %s\r\n", remote) From cf19be44a8eb9b790ba3bef29f851dc65cb61c3c Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 23 Apr 2013 18:20:53 +0200 Subject: [PATCH 08/69] added run (wip), fixed ps and images, added port and tag --- api.go | 242 +++++++++++++++++++++++++++++++++---------- api_params.go | 45 ++------ commands.go | 282 ++++++++++++++++++++++---------------------------- container.go | 9 +- registry.go | 11 +- 5 files changed, 331 insertions(+), 258 deletions(-) diff --git a/api.go b/api.go index fdf87f3f1..782276740 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 8f8b332a0..0b0254d5e 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 e9b070158..2549e3823 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 96cd423a0..0f40daadf 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 7d13efd88..428db1b96 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) From 1e357c69690f5325d92ae7c41e396711865382f0 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 24 Apr 2013 14:01:40 +0200 Subject: [PATCH 09/69] changed not found errors to 404, added inspect, wait and diff --- api.go | 94 +++++++++++++++++++++++++++++++++++++++++++++----- api_params.go | 4 +++ commands.go | 95 ++++++++++++++++++++++++--------------------------- 3 files changed, 135 insertions(+), 58 deletions(-) diff --git a/api.go b/api.go index 782276740..776a0e725 100644 --- a/api.go +++ b/api.go @@ -42,12 +42,46 @@ func ListenAndServe(addr string, rtime *Runtime) error { return } } else { - http.Error(w, "No such container: "+name, http.StatusInternalServerError) + http.Error(w, "No such container: "+name, http.StatusNotFound) return } w.WriteHeader(200) }) + r.Path("/images/{name:.*}").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(r.Method, r.RequestURI) + 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 + } + http.Error(w, "No such image: "+name, http.StatusNotFound) + }) + + r.Path("/containers/{name:.*}").Methods("GET").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(container) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } else { + w.Write(b) + } + return + } + http.Error(w, "No such container: "+name, http.StatusNotFound) + }) + r.Path("/images").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println(r.Method, r.RequestURI) if err := r.ParseForm(); err != nil { @@ -211,7 +245,33 @@ func ListenAndServe(addr string, rtime *Runtime) error { } } else { - http.Error(w, "No such container: "+name, http.StatusInternalServerError) + http.Error(w, "No such container: "+name, http.StatusNotFound) + } + }) + + r.Path("/containers/{name:.*}/changes").Methods("GET").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 { + 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) + } + } else { + http.Error(w, "No such container: "+name, http.StatusNotFound) } }) @@ -225,7 +285,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { name := vars["name"] if container := rtime.Get(name); container == nil { - http.Error(w, "No such container: "+name, http.StatusInternalServerError) + http.Error(w, "No such container: "+name, http.StatusNotFound) return } else { if frontend, exists := container.NetworkSettings.PortMapping[privatePort]; !exists { @@ -423,7 +483,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { return } } else { - http.Error(w, "No such container: "+name, http.StatusInternalServerError) + http.Error(w, "No such container: "+name, http.StatusNotFound) return } w.WriteHeader(200) @@ -439,7 +499,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { return } } else { - http.Error(w, "No such container: "+name, http.StatusInternalServerError) + http.Error(w, "No such container: "+name, http.StatusNotFound) return } w.WriteHeader(200) @@ -452,7 +512,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { img, err := rtime.repositories.LookupImage(name) if err != nil { - http.Error(w, "No such image: "+name, http.StatusInternalServerError) + http.Error(w, "No such image: "+name, http.StatusNotFound) return } else { if err := rtime.graph.Delete(img.Id); err != nil { @@ -473,7 +533,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { return } } else { - http.Error(w, "No such container: "+name, http.StatusInternalServerError) + http.Error(w, "No such container: "+name, http.StatusNotFound) return } w.WriteHeader(200) @@ -489,11 +549,29 @@ func ListenAndServe(addr string, rtime *Runtime) error { return } } else { - http.Error(w, "No such container: "+name, http.StatusInternalServerError) + http.Error(w, "No such container: "+name, http.StatusNotFound) return } w.WriteHeader(200) }) + 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 + } else { + http.Error(w, "No such container: "+name, http.StatusNotFound) + return + } + }) + return http.ListenAndServe(addr, r) } diff --git a/api_params.go b/api_params.go index 0b0254d5e..cd6c77d1b 100644 --- a/api_params.go +++ b/api_params.go @@ -44,3 +44,7 @@ type ApiVersion struct { GitCommit string MemoryLimitDisabled bool } + +type ApiWait struct { + StatusCode int +} diff --git a/commands.go b/commands.go index 2549e3823..3065bc541 100644 --- a/commands.go +++ b/commands.go @@ -26,8 +26,10 @@ var ( func ParseCommands(args []string) error { cmds := map[string]func(args []string) error{ + "diff": CmdDiff, "images": CmdImages, "info": CmdInfo, + "inspect": CmdInspect, "history": CmdHistory, "kill": CmdKill, "logs": CmdLogs, @@ -42,6 +44,7 @@ func ParseCommands(args []string) error { "start": CmdStart, "stop": CmdStop, "version": CmdVersion, + "wait": CmdWait, } if len(args) > 0 { @@ -60,13 +63,13 @@ func cmdHelp(args []string) error { for _, cmd := range [][]string{ // {"attach", "Attach to a running container"}, // {"commit", "Create a new image from a container's changes"}, - // {"diff", "Inspect changes on a container's filesystem"}, + {"diff", "Inspect changes on a container's filesystem"}, // {"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"}, {"info", "Display system-wide information"}, - // {"inspect", "Return low-level information on a container"}, + {"inspect", "Return low-level information on a container/image"}, {"kill", "Kill a running container"}, // {"login", "Register or Login to the docker registry server"}, {"logs", "Fetch the logs of a container"}, @@ -82,7 +85,7 @@ func cmdHelp(args []string) error { {"stop", "Stop a running container"}, {"tag", "Tag an image into a repository"}, {"version", "Show the docker version information"}, - // {"wait", "Block until a container stops, then print its exit code"}, + {"wait", "Block until a container stops, then print its exit code"}, } { help += fmt.Sprintf(" %-10.10s%s\n", cmd[0], cmd[1]) } @@ -190,10 +193,9 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args .. } */ -/* // 'docker wait': block until a container stops -func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.") +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 } @@ -202,15 +204,20 @@ func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string return nil } for _, name := range cmd.Args() { - if container := srv.runtime.Get(name); container != nil { - fmt.Fprintln(stdout, container.Wait()) + body, err := call("POST", "/containers/"+name+"/wait") + if err != nil { + fmt.Printf("%s", err) } else { - return fmt.Errorf("No such container: %s", name) + var out ApiWait + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + fmt.Println(out.StatusCode) } } return nil } -*/ // 'docker version': show version information func CmdVersion(args []string) error { @@ -334,42 +341,31 @@ func CmdStart(args []string) error { return nil } -/* -func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "inspect", "CONTAINER", "Return low-level information on a container") +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 } - if cmd.NArg() < 1 { + if cmd.NArg() != 1 { cmd.Usage() return nil } - name := cmd.Arg(0) var obj interface{} - if container := srv.runtime.Get(name); container != nil { - obj = container - } else if image, err := srv.runtime.repositories.LookupImage(name); err == nil && image != nil { - obj = image - } else { - // No output means the object does not exist - // (easier to script since stdout and stderr are not differentiated atm) - return nil + var err error + obj, err = call("GET", "/containers/"+cmd.Arg(0)) + if err != nil { + obj, err = call("GET", "/images/"+cmd.Arg(0)) + if err != nil { + return err + } } - data, err := json.Marshal(obj) + b, err := json.MarshalIndent(obj, "", " ") if err != nil { return err } - indented := new(bytes.Buffer) - if err = json.Indent(indented, data, "", " "); err != nil { - return err - } - if _, err := io.Copy(stdout, indented); err != nil { - return err - } - stdout.Write([]byte{'\n'}) + fmt.Printf("%s\n", b) return nil } -*/ func CmdPort(args []string) error { cmd := Subcmd("port", "CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT") @@ -778,32 +774,31 @@ func (srv *Server) CmdExport(stdin io.ReadCloser, stdout io.Writer, args ...stri } */ -/* -func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, - "diff", "CONTAINER", - "Inspect changes on a container's filesystem") +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 } - if cmd.NArg() < 1 { + if cmd.NArg() != 1 { cmd.Usage() return nil } - if container := srv.runtime.Get(cmd.Arg(0)); container == nil { - return fmt.Errorf("No such container") - } else { - changes, err := container.Changes() - if err != nil { - return err - } - for _, change := range changes { - fmt.Fprintln(stdout, change.String()) - } + + body, err := call("GET", "/containers/"+cmd.Arg(0)+"/changes") + if err != nil { + return err + } + + var changes []string + err = json.Unmarshal(body, &changes) + if err != nil { + return err + } + for _, change := range changes { + fmt.Println(change) } return nil } -*/ func CmdLogs(args []string) error { cmd := Subcmd("logs", "CONTAINER", "Fetch the logs of a container") From 79512b2a8025184d647de61cdd6a19ebadd17a49 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 24 Apr 2013 16:06:03 +0200 Subject: [PATCH 10/69] added commit --- api.go | 26 ++++++++++++++++++++++++++ api_params.go | 4 ++++ commands.go | 34 +++++++++++++++++++++++----------- 3 files changed, 53 insertions(+), 11 deletions(-) diff --git a/api.go b/api.go index 776a0e725..e5e0052d4 100644 --- a/api.go +++ b/api.go @@ -347,6 +347,32 @@ func ListenAndServe(addr string, rtime *Runtime) error { } }) + r.Path("/containers/{name:.*}/commit").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) + return + } + vars := mux.Vars(r) + name := vars["name"] + repo := r.Form.Get("repo") + tag := r.Form.Get("tag") + comment := r.Form.Get("comment") + + img, err := rtime.Commit(name, repo, tag, comment) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + b, err := json.Marshal(ApiCommit{img.ShortId()}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } else { + w.Write(b) + } + }) + 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 { diff --git a/api_params.go b/api_params.go index cd6c77d1b..ed19fd869 100644 --- a/api_params.go +++ b/api_params.go @@ -30,6 +30,10 @@ type ApiContainers struct { Status string `json:",omitempty"` } +type ApiCommit struct { + Id string +} + type ApiLogs struct { Stdout string Stderr string diff --git a/commands.go b/commands.go index 3065bc541..8ccba086e 100644 --- a/commands.go +++ b/commands.go @@ -26,6 +26,7 @@ var ( func ParseCommands(args []string) error { cmds := map[string]func(args []string) error{ + "commit": CmdCommit, "diff": CmdDiff, "images": CmdImages, "info": CmdInfo, @@ -62,7 +63,7 @@ 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"}, - // {"commit", "Create a new image from a container's changes"}, + {"commit", "Create a new image from a container's changes"}, {"diff", "Inspect changes on a container's filesystem"}, // {"export", "Stream the contents of a container as a tar archive"}, {"history", "Show the history of an image"}, @@ -727,28 +728,36 @@ func CmdPs(args []string) error { return nil } -/* -func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, - "commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]", - "Create a new image from a container's changes") +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") if err := cmd.Parse(args); err != nil { return nil } - containerName, repository, tag := cmd.Arg(0), cmd.Arg(1), cmd.Arg(2) - if containerName == "" { + name, repository, tag := cmd.Arg(0), cmd.Arg(1), cmd.Arg(2) + if name == "" { cmd.Usage() return nil } - img, err := srv.runtime.Commit(containerName, repository, tag, *flComment) + v := url.Values{} + v.Set("repo", repository) + v.Set("tag", tag) + v.Set("comment", *flComment) + + body, err := call("POST", "/containers/"+name+"/commit?"+v.Encode()) if err != nil { return err } - fmt.Fprintln(stdout, img.ShortId()) + + var out ApiCommit + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + + fmt.Println(out.Id) return nil } -*/ /* func (srv *Server) CmdExport(stdin io.ReadCloser, stdout io.Writer, args ...string) error { @@ -959,6 +968,9 @@ func call(method, path string) ([]byte, error) { if err != nil { return nil, err } + if method == "POST" { + req.Header.Set("Content-Type", "plain/text") + } resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err From c7bbe7ca797ad2f4eb0fff3d58e8eaaa72756036 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 24 Apr 2013 16:32:51 +0200 Subject: [PATCH 11/69] added export --- api.go | 37 +++++++++++++++++++++++++++++++++++++ commands.go | 42 +++++++++++++++++++++++------------------- 2 files changed, 60 insertions(+), 19 deletions(-) diff --git a/api.go b/api.go index e5e0052d4..1b9507dd6 100644 --- a/api.go +++ b/api.go @@ -65,6 +65,43 @@ func ListenAndServe(addr string, rtime *Runtime) error { http.Error(w, "No such image: "+name, http.StatusNotFound) }) + r.Path("/containers/{name:.*}/export").Methods("GET").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 { + + 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 + } + defer file.Close() + + fmt.Fprintln(file, "HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\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) + } + + }) + r.Path("/containers/{name:.*}").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println(r.Method, r.RequestURI) vars := mux.Vars(r) diff --git a/commands.go b/commands.go index 8ccba086e..7d07e897c 100644 --- a/commands.go +++ b/commands.go @@ -28,6 +28,7 @@ func ParseCommands(args []string) error { cmds := map[string]func(args []string) error{ "commit": CmdCommit, "diff": CmdDiff, + "export": CmdExport, "images": CmdImages, "info": CmdInfo, "inspect": CmdInspect, @@ -65,7 +66,7 @@ func cmdHelp(args []string) error { // {"attach", "Attach to a running container"}, {"commit", "Create a new image from a container's changes"}, {"diff", "Inspect changes on a container's filesystem"}, - // {"export", "Stream the contents of a container as a tar archive"}, + {"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"}, @@ -759,29 +760,22 @@ func CmdCommit(args []string) error { return nil } -/* -func (srv *Server) CmdExport(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, - "export", "CONTAINER", - "Export the contents of a filesystem as a tar archive") +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 } - name := cmd.Arg(0) - 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(stdout, data); err != nil { - return err - } + + if cmd.NArg() != 1 { + cmd.Usage() return nil } - return fmt.Errorf("No such container: %s", name) + + if err := callStream("GET", "/containers/"+cmd.Arg(0)+"/export", nil, false); err != nil { + return err + } + return nil } -*/ func CmdDiff(args []string) error { cmd := Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem") @@ -1010,8 +1004,18 @@ func callStream(method, path string, data interface{}, isTerminal bool) error { return err } clientconn := httputil.NewClientConn(dial, nil) - clientconn.Do(req) + resp, err := clientconn.Do(req) defer clientconn.Close() + if err != nil { + return err + } + if resp.StatusCode != 200 { + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + return fmt.Errorf("error: %s", body) + } rwc, _ := clientconn.Hijack() defer rwc.Close() From 75c0dc9526766719f6e528c48e63001596042071 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 24 Apr 2013 18:50:26 +0200 Subject: [PATCH 12/69] fixed inspect --- commands.go | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/commands.go b/commands.go index 7d07e897c..e9458d43b 100644 --- a/commands.go +++ b/commands.go @@ -352,20 +352,14 @@ func CmdInspect(args []string) error { cmd.Usage() return nil } - var obj interface{} - var err error - obj, err = call("GET", "/containers/"+cmd.Arg(0)) + obj, err := call("GET", "/containers/"+cmd.Arg(0)) if err != nil { obj, err = call("GET", "/images/"+cmd.Arg(0)) if err != nil { return err } } - b, err := json.MarshalIndent(obj, "", " ") - if err != nil { - return err - } - fmt.Printf("%s\n", b) + fmt.Printf("%s\n", obj) return nil } @@ -1004,18 +998,8 @@ func callStream(method, path string, data interface{}, isTerminal bool) error { return err } clientconn := httputil.NewClientConn(dial, nil) - resp, err := clientconn.Do(req) + clientconn.Do(req) defer clientconn.Close() - if err != nil { - return err - } - if resp.StatusCode != 200 { - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return err - } - return fmt.Errorf("error: %s", body) - } rwc, _ := clientconn.Hijack() defer rwc.Close() From a48eb4dff869631221854bb85380c15bf7b9d0eb Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 26 Apr 2013 15:08:33 +0200 Subject: [PATCH 13/69] run now try to pull if unknown image --- api.go | 88 ++++++++++++++++++++++++++++++++++++++--------------- commands.go | 36 +++++++++------------- 2 files changed, 78 insertions(+), 46 deletions(-) diff --git a/api.go b/api.go index 1b9507dd6..3de2479a4 100644 --- a/api.go +++ b/api.go @@ -130,7 +130,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { var allImages map[string]*Image var err error - if All == "true" { + if All == "1" { allImages, err = rtime.graph.Map() } else { allImages, err = rtime.graph.Heads() @@ -152,7 +152,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { continue } delete(allImages, id) - if Quiet != "true" { + if Quiet != "1" { out.Repository = name out.Tag = tag out.Id = TruncateId(id) @@ -167,7 +167,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { if NameFilter == "" { for id, image := range allImages { var out ApiImages - if Quiet != "true" { + if Quiet != "1" { out.Repository = "" out.Tag = "" out.Id = TruncateId(id) @@ -355,7 +355,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { var outs []ApiContainers for i, container := range rtime.List() { - if !container.State.Running && All != "true" && n == -1 { + if !container.State.Running && All != "1" && n == -1 { continue } if i == n { @@ -363,9 +363,9 @@ func ListenAndServe(addr string, rtime *Runtime) error { } var out ApiContainers out.Id = container.ShortId() - if Quiet != "true" { + if Quiet != "1" { command := fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")) - if NoTrunc != "true" { + if NoTrunc != "1" { command = Trunc(command, 20) } out.Image = rtime.repositories.ImageName(container.Image) @@ -420,7 +420,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { repo := r.Form.Get("repo") tag := r.Form.Get("tag") var force bool - if r.Form.Get("force") == "true" { + if r.Form.Get("force") == "1" { force = true } @@ -451,14 +451,14 @@ func ListenAndServe(addr string, rtime *Runtime) error { 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()) - } + if err := rtime.graph.PullImage(file, name, rtime.authConfig); err != nil { + fmt.Fprintln(file, "Error: "+err.Error()) + } + return nil + } + 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) { @@ -488,14 +488,19 @@ func ListenAndServe(addr string, rtime *Runtime) error { // 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 - //} + 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 + } + } else if err := rtime.graph.PullRepository(file, name, "", rtime.repositories, rtime.authConfig); err != nil { + fmt.Fprintln(file, "Error: "+err.Error()) + return + } + if container, err = rtime.Create(&config); err != nil { + fmt.Fprintln(file, "Error: "+err.Error()) + return + } } else { fmt.Fprintln(file, "Error: "+err.Error()) return @@ -532,8 +537,43 @@ func ListenAndServe(addr string, rtime *Runtime) error { } Debugf("Waiting for attach to return\n") <-attachErr - // Expecting I/O pipe error, discarding + // Expecting I/O pipe error, discarding + }) + r.Path("/containers/{name:.*}/attach").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 { + 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") + r, w := io.Pipe() + go func() { + defer w.Close() + defer Debugf("Closing buffered stdin pipe") + io.Copy(w, file) + }() + cStdin := r + + <-container.Attach(cStdin, nil, file, file) + // Expecting I/O pipe error, discarding + + } else { + http.Error(w, "No such container: "+name, http.StatusNotFound) + return + } }) r.Path("/containers/{name:.*}/restart").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/commands.go b/commands.go index e9458d43b..e39c03f24 100644 --- a/commands.go +++ b/commands.go @@ -26,6 +26,7 @@ var ( func ParseCommands(args []string) error { cmds := map[string]func(args []string) error{ + "attach": CmdAttach, "commit": CmdCommit, "diff": CmdDiff, "export": CmdExport, @@ -63,7 +64,7 @@ func ParseCommands(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"}, + {"attach", "Attach to a running container"}, {"commit", "Create a new image from a container's changes"}, {"diff", "Inspect changes on a container's filesystem"}, {"export", "Stream the contents of a container as a tar archive"}, @@ -631,10 +632,10 @@ func CmdImages(args []string) error { v.Set("filter", cmd.Arg(0)) } if *quiet { - v.Set("quiet", "true") + v.Set("quiet", "1") } if *all { - v.Set("all", "true") + v.Set("all", "1") } body, err := call("GET", "/images?"+v.Encode()) @@ -682,13 +683,13 @@ func CmdPs(args []string) error { *last = 1 } if *quiet { - v.Set("quiet", "true") + v.Set("quiet", "1") } if *all { - v.Set("all", "true") + v.Set("all", "1") } if *noTrunc { - v.Set("notrunc", "true") + v.Set("notrunc", "1") } if *last != -1 { v.Set("n", strconv.Itoa(*last)) @@ -822,9 +823,8 @@ func CmdLogs(args []string) error { return nil } -/* -func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { - cmd := rcli.Subcmd(stdout, "attach", "CONTAINER", "Attach to a running container") +func CmdAttach(args []string) error { + cmd := Subcmd("attach", "CONTAINER", "Attach to a running container") if err := cmd.Parse(args); err != nil { return nil } @@ -832,20 +832,13 @@ func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout rcli.DockerConn, args . cmd.Usage() return nil } - name := cmd.Arg(0) - container := srv.runtime.Get(name) - if container == nil { - return fmt.Errorf("No such container: %s", name) - } - if container.Config.Tty { - stdout.SetOptionRawTerminal() + if err := callStream("POST", "/containers/"+cmd.Arg(0)+"/attach", nil, true); err != nil { + return err } - // Flush the options to make sure the client sets the raw mode - stdout.Flush() - return <-container.Attach(stdin, nil, stdout, stdout) + return nil } -*/ + /* // Ports type - Used to parse multiple -p flags type ports []int @@ -921,7 +914,7 @@ func CmdTag(args []string) error { } if *force { - v.Set("force", "true") + v.Set("force", "1") } if err := callStream("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil, false); err != nil { @@ -931,7 +924,6 @@ func CmdTag(args []string) error { } func CmdRun(args []string) error { - fmt.Println("CmdRun") config, cmd, err := ParseRun(args) if err != nil { return err From 30cb4b351f3513a6ebec89183def0647346b5708 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 26 Apr 2013 15:08:33 +0200 Subject: [PATCH 14/69] run now try to pull if unknown image --- api.go | 88 ++++++++++++++++++++++++++++++++++++++--------------- commands.go | 36 +++++++++------------- 2 files changed, 78 insertions(+), 46 deletions(-) diff --git a/api.go b/api.go index 1b9507dd6..81b4ff963 100644 --- a/api.go +++ b/api.go @@ -130,7 +130,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { var allImages map[string]*Image var err error - if All == "true" { + if All == "1" { allImages, err = rtime.graph.Map() } else { allImages, err = rtime.graph.Heads() @@ -152,7 +152,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { continue } delete(allImages, id) - if Quiet != "true" { + if Quiet != "1" { out.Repository = name out.Tag = tag out.Id = TruncateId(id) @@ -167,7 +167,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { if NameFilter == "" { for id, image := range allImages { var out ApiImages - if Quiet != "true" { + if Quiet != "1" { out.Repository = "" out.Tag = "" out.Id = TruncateId(id) @@ -355,7 +355,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { var outs []ApiContainers for i, container := range rtime.List() { - if !container.State.Running && All != "true" && n == -1 { + if !container.State.Running && All != "1" && n == -1 { continue } if i == n { @@ -363,9 +363,9 @@ func ListenAndServe(addr string, rtime *Runtime) error { } var out ApiContainers out.Id = container.ShortId() - if Quiet != "true" { + if Quiet != "1" { command := fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")) - if NoTrunc != "true" { + if NoTrunc != "1" { command = Trunc(command, 20) } out.Image = rtime.repositories.ImageName(container.Image) @@ -420,7 +420,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { repo := r.Form.Get("repo") tag := r.Form.Get("tag") var force bool - if r.Form.Get("force") == "true" { + if r.Form.Get("force") == "1" { force = true } @@ -451,14 +451,14 @@ func ListenAndServe(addr string, rtime *Runtime) error { 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()) - } + 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) { @@ -488,14 +488,19 @@ func ListenAndServe(addr string, rtime *Runtime) error { // 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 - //} + if rtime.graph.LookupRemoteImage(config.Image, rtime.authConfig) { + if err := rtime.graph.PullImage(file, config.Image, rtime.authConfig); err != nil { + fmt.Fprintln(file, "Error: "+err.Error()) + return + } + } else if err := rtime.graph.PullRepository(file, config.Image, "", rtime.repositories, rtime.authConfig); err != nil { + fmt.Fprintln(file, "Error: "+err.Error()) + return + } + if container, err = rtime.Create(&config); err != nil { + fmt.Fprintln(file, "Error: "+err.Error()) + return + } } else { fmt.Fprintln(file, "Error: "+err.Error()) return @@ -532,8 +537,43 @@ func ListenAndServe(addr string, rtime *Runtime) error { } Debugf("Waiting for attach to return\n") <-attachErr - // Expecting I/O pipe error, discarding + // Expecting I/O pipe error, discarding + }) + r.Path("/containers/{name:.*}/attach").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 { + 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") + r, w := io.Pipe() + go func() { + defer w.Close() + defer Debugf("Closing buffered stdin pipe") + io.Copy(w, file) + }() + cStdin := r + + <-container.Attach(cStdin, nil, file, file) + // Expecting I/O pipe error, discarding + + } else { + http.Error(w, "No such container: "+name, http.StatusNotFound) + return + } }) r.Path("/containers/{name:.*}/restart").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/commands.go b/commands.go index e9458d43b..e39c03f24 100644 --- a/commands.go +++ b/commands.go @@ -26,6 +26,7 @@ var ( func ParseCommands(args []string) error { cmds := map[string]func(args []string) error{ + "attach": CmdAttach, "commit": CmdCommit, "diff": CmdDiff, "export": CmdExport, @@ -63,7 +64,7 @@ func ParseCommands(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"}, + {"attach", "Attach to a running container"}, {"commit", "Create a new image from a container's changes"}, {"diff", "Inspect changes on a container's filesystem"}, {"export", "Stream the contents of a container as a tar archive"}, @@ -631,10 +632,10 @@ func CmdImages(args []string) error { v.Set("filter", cmd.Arg(0)) } if *quiet { - v.Set("quiet", "true") + v.Set("quiet", "1") } if *all { - v.Set("all", "true") + v.Set("all", "1") } body, err := call("GET", "/images?"+v.Encode()) @@ -682,13 +683,13 @@ func CmdPs(args []string) error { *last = 1 } if *quiet { - v.Set("quiet", "true") + v.Set("quiet", "1") } if *all { - v.Set("all", "true") + v.Set("all", "1") } if *noTrunc { - v.Set("notrunc", "true") + v.Set("notrunc", "1") } if *last != -1 { v.Set("n", strconv.Itoa(*last)) @@ -822,9 +823,8 @@ func CmdLogs(args []string) error { return nil } -/* -func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { - cmd := rcli.Subcmd(stdout, "attach", "CONTAINER", "Attach to a running container") +func CmdAttach(args []string) error { + cmd := Subcmd("attach", "CONTAINER", "Attach to a running container") if err := cmd.Parse(args); err != nil { return nil } @@ -832,20 +832,13 @@ func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout rcli.DockerConn, args . cmd.Usage() return nil } - name := cmd.Arg(0) - container := srv.runtime.Get(name) - if container == nil { - return fmt.Errorf("No such container: %s", name) - } - if container.Config.Tty { - stdout.SetOptionRawTerminal() + if err := callStream("POST", "/containers/"+cmd.Arg(0)+"/attach", nil, true); err != nil { + return err } - // Flush the options to make sure the client sets the raw mode - stdout.Flush() - return <-container.Attach(stdin, nil, stdout, stdout) + return nil } -*/ + /* // Ports type - Used to parse multiple -p flags type ports []int @@ -921,7 +914,7 @@ func CmdTag(args []string) error { } if *force { - v.Set("force", "true") + v.Set("force", "1") } if err := callStream("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil, false); err != nil { @@ -931,7 +924,6 @@ func CmdTag(args []string) error { } func CmdRun(args []string) error { - fmt.Println("CmdRun") config, cmd, err := ParseRun(args) if err != nil { return err From e5104a4cb4fe68ecd17b5a01ce295c64f8107b42 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 29 Apr 2013 15:12:18 +0200 Subject: [PATCH 15/69] working tty --- api.go | 38 ++++++++++++++++++++++++++++---------- commands.go | 13 +++++++++++-- utils.go | 21 +++++++++++++++++++++ 3 files changed, 60 insertions(+), 12 deletions(-) diff --git a/api.go b/api.go index 81b4ff963..e66dc1e64 100644 --- a/api.go +++ b/api.go @@ -451,14 +451,14 @@ func ListenAndServe(addr string, rtime *Runtime) error { 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()) - } + 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) { @@ -482,6 +482,18 @@ func ListenAndServe(addr string, rtime *Runtime) error { } defer file.Close() + if 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) + } + + } + 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 { @@ -493,7 +505,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { fmt.Fprintln(file, "Error: "+err.Error()) return } - } else if err := rtime.graph.PullRepository(file, config.Image, "", rtime.repositories, rtime.authConfig); err != nil { + } else if err := rtime.graph.PullRepository(file, config.Image, "", rtime.repositories, rtime.authConfig); err != nil { fmt.Fprintln(file, "Error: "+err.Error()) return } @@ -523,7 +535,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { cStdout = file } if config.AttachStderr { - cStderr = file // FIXME: rcli can't differentiate stdout from stderr + cStderr = file // FIXME: api can't differentiate stdout from stderr } attachErr := container.Attach(cStdin, file, cStdout, cStderr) @@ -538,6 +550,12 @@ func ListenAndServe(addr string, rtime *Runtime) error { Debugf("Waiting for attach to return\n") <-attachErr // Expecting I/O pipe error, discarding + + // If we are in stdinonce mode, wait for the process to end + // otherwise, simply return + if config.StdinOnce && !config.Tty { + container.Wait() + } }) r.Path("/containers/{name:.*}/attach").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/commands.go b/commands.go index e39c03f24..c934fe6bb 100644 --- a/commands.go +++ b/commands.go @@ -5,6 +5,7 @@ import ( "encoding/json" "flag" "fmt" + "github.com/dotcloud/docker/term" "io" "io/ioutil" "net" @@ -967,7 +968,7 @@ func call(method, path string) ([]byte, error) { } -func callStream(method, path string, data interface{}, isTerminal bool) error { +func callStream(method, path string, data interface{}, setRawTerminal bool) error { var body io.Reader if data != nil { buf, err := json.Marshal(data) @@ -996,6 +997,14 @@ func callStream(method, path string, data interface{}, isTerminal bool) error { rwc, _ := clientconn.Hijack() defer rwc.Close() + if setRawTerminal && term.IsTerminal(int(os.Stdin.Fd())) && os.Getenv("NORAW") == "" { + if oldState, err := SetRawTerminal(); err != nil { + return err + } else { + defer RestoreTerminal(oldState) + } + } + receiveStdout := Go(func() error { _, err := io.Copy(os.Stdout, rwc) return err @@ -1009,7 +1018,7 @@ func callStream(method, path string, data interface{}, isTerminal bool) error { if err := <-receiveStdout; err != nil { return err } - if isTerminal { + if !term.IsTerminal(int(os.Stdin.Fd())) { if err := <-sendStdin; err != nil { return err } diff --git a/utils.go b/utils.go index 68e12b20b..5ff1ad7a0 100644 --- a/utils.go +++ b/utils.go @@ -5,12 +5,14 @@ import ( "errors" "fmt" "github.com/dotcloud/docker/rcli" + "github.com/dotcloud/docker/term" "index/suffixarray" "io" "io/ioutil" "net/http" "os" "os/exec" + "os/signal" "path/filepath" "runtime" "strings" @@ -384,3 +386,22 @@ func CopyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) } return written, err } + +func SetRawTerminal() (*term.State, error) { + oldState, err := term.MakeRaw(int(os.Stdin.Fd())) + if err != nil { + return nil, err + } + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + go func() { + _ = <-c + term.Restore(int(os.Stdin.Fd()), oldState) + os.Exit(0) + }() + return oldState, err +} + +func RestoreTerminal(state *term.State) { + term.Restore(int(os.Stdin.Fd()), state) +} From 131c6ab3e6461eb1a985a8eb0a64b59e1b990810 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 29 Apr 2013 17:46:41 +0200 Subject: [PATCH 16/69] more accurate http errors, attach handle tty correctly now --- api.go | 104 ++++++++++++++++++++++++++++++---------------------- commands.go | 13 ++++++- 2 files changed, 73 insertions(+), 44 deletions(-) diff --git a/api.go b/api.go index e66dc1e64..3bcb423d5 100644 --- a/api.go +++ b/api.go @@ -45,24 +45,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { http.Error(w, "No such container: "+name, http.StatusNotFound) return } - w.WriteHeader(200) - }) - - r.Path("/images/{name:.*}").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.Method, r.RequestURI) - 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 - } - http.Error(w, "No such image: "+name, http.StatusNotFound) + w.WriteHeader(http.StatusOK) }) r.Path("/containers/{name:.*}/export").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -102,23 +85,6 @@ func ListenAndServe(addr string, rtime *Runtime) error { }) - r.Path("/containers/{name:.*}").Methods("GET").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(container) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } else { - w.Write(b) - } - return - } - http.Error(w, "No such container: "+name, http.StatusNotFound) - }) - r.Path("/images").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println(r.Method, r.RequestURI) if err := r.ParseForm(); err != nil { @@ -428,7 +394,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { http.Error(w, err.Error(), http.StatusInternalServerError) return } - w.WriteHeader(200) + w.WriteHeader(http.StatusCreated) }) r.Path("/images/{name:.*}/pull").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -491,10 +457,13 @@ func ListenAndServe(addr string, rtime *Runtime) error { } else { defer RestoreTerminal(oldState) } - } - fmt.Fprintln(file, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n") + // Flush the options to make sure the client sets the raw mode + // or tell the client there is no options + conn.Write([]byte{}) + + fmt.Fprintln(file, "HTTP/1.1 201 Created\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 @@ -576,6 +545,21 @@ func ListenAndServe(addr string, rtime *Runtime) error { } defer file.Close() + 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) + } + + } + + // 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: application/json\r\n\r\n") r, w := io.Pipe() go func() { @@ -607,7 +591,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { http.Error(w, "No such container: "+name, http.StatusNotFound) return } - w.WriteHeader(200) + w.WriteHeader(http.StatusOK) }) r.Path("/containers/{name:.*}").Methods("DELETE").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -623,7 +607,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { http.Error(w, "No such container: "+name, http.StatusNotFound) return } - w.WriteHeader(200) + w.WriteHeader(http.StatusOK) }) r.Path("/images/{name:.*}").Methods("DELETE").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -641,7 +625,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { return } } - w.WriteHeader(200) + w.WriteHeader(http.StatusOK) }) r.Path("/containers/{name:.*}/start").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -657,7 +641,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { http.Error(w, "No such container: "+name, http.StatusNotFound) return } - w.WriteHeader(200) + w.WriteHeader(http.StatusOK) }) r.Path("/containers/{name:.*}/stop").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -673,7 +657,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { http.Error(w, "No such container: "+name, http.StatusNotFound) return } - w.WriteHeader(200) + w.WriteHeader(http.StatusOK) }) r.Path("/containers/{name:.*}/wait").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -694,5 +678,39 @@ func ListenAndServe(addr string, rtime *Runtime) error { } }) + r.Path("/containers/{name:.*}").Methods("GET").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(container) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } else { + w.Write(b) + } + return + } + http.Error(w, "No such container: "+name, http.StatusNotFound) + }) + + r.Path("/images/{name:.*}").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(r.Method, r.RequestURI) + 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 + } + http.Error(w, "No such image: "+name, http.StatusNotFound) + }) + return http.ListenAndServe(addr, r) } diff --git a/commands.go b/commands.go index c934fe6bb..f8a5d2a68 100644 --- a/commands.go +++ b/commands.go @@ -834,7 +834,18 @@ func CmdAttach(args []string) error { return nil } - if err := callStream("POST", "/containers/"+cmd.Arg(0)+"/attach", nil, true); err != nil { + body, err := call("GET", "/containers/"+cmd.Arg(0)) + if err != nil { + return err + } + + var container Container + err = json.Unmarshal(body, &container) + if err != nil { + return err + } + + if err := callStream("POST", "/containers/"+cmd.Arg(0)+"/attach", nil, container.Config.Tty); err != nil { return err } return nil From 36b968bb09b72366b21b74439eeba463317d2320 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 30 Apr 2013 17:04:31 +0200 Subject: [PATCH 17/69] [] instead fon null, timetsamps and wip import --- api.go | 89 +++++++++++++++++++++++++++++++++++++++++++++------ api_params.go | 6 ++-- commands.go | 58 +++++++++------------------------ 3 files changed, 99 insertions(+), 54 deletions(-) diff --git a/api.go b/api.go index 3bcb423d5..7e0c8fc24 100644 --- a/api.go +++ b/api.go @@ -10,11 +10,11 @@ import ( "log" "net" "net/http" + "net/url" "os" "runtime" "strconv" "strings" - "time" ) func ListenAndServe(addr string, rtime *Runtime) error { @@ -105,7 +105,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { w.WriteHeader(500) return } - var outs []ApiImages + var outs []ApiImages = []ApiImages{} //produce [] when empty instead of 'null' for name, repository := range rtime.repositories.Repositories { if NameFilter != "" && name != NameFilter { continue @@ -122,7 +122,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { out.Repository = name out.Tag = tag out.Id = TruncateId(id) - out.Created = HumanDuration(time.Now().Sub(image.Created)) + " ago" + out.Created = image.Created.Unix() } else { out.Id = image.ShortId() } @@ -137,7 +137,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { out.Repository = "" out.Tag = "" out.Id = TruncateId(id) - out.Created = HumanDuration(time.Now().Sub(image.Created)) + " ago" + out.Created = image.Created.Unix() } else { out.Id = image.ShortId() } @@ -189,11 +189,12 @@ func ListenAndServe(addr string, rtime *Runtime) error { http.Error(w, err.Error(), http.StatusInternalServerError) return } - var outs []ApiHistory + + 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 = HumanDuration(time.Now().Sub(img.Created)) + " ago" + out.Created = img.Created.Unix() out.CreatedBy = strings.Join(img.ContainerConfig.Cmd, " ") return nil }) @@ -318,8 +319,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { if err != nil { n = -1 } - var outs []ApiContainers - + 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 @@ -336,7 +336,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { } out.Image = rtime.repositories.ImageName(container.Image) out.Command = command - out.Created = HumanDuration(time.Now().Sub(container.Created)) + " ago" + out.Created = container.Created.Unix() out.Status = container.State.String() } outs = append(outs, out) @@ -427,6 +427,77 @@ func ListenAndServe(addr string, rtime *Runtime) 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") + repo := r.Form.Get("repo") + tag := r.Form.Get("tag") + + var archive io.Reader + var resp *http.Response + + 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) + 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 { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + archive = ProgressReader(resp.Body, int(resp.ContentLength), file) + } + img, err := rtime.graph.Create(archive, nil, "Imported from "+src) + 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) + return + } + } + + fmt.Fprintln(file, img.ShortId()) + }) + r.Path("/containers").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println(r.Method, r.RequestURI) var config Config diff --git a/api_params.go b/api_params.go index ed19fd869..837b5a642 100644 --- a/api_params.go +++ b/api_params.go @@ -2,7 +2,7 @@ package docker type ApiHistory struct { Id string - Created string + Created int64 CreatedBy string } @@ -10,7 +10,7 @@ type ApiImages struct { Repository string `json:",omitempty"` Tag string `json:",omitempty"` Id string - Created string `json:",omitempty"` + Created int64 `json:",omitempty"` } type ApiInfo struct { @@ -26,7 +26,7 @@ type ApiContainers struct { Id string Image string `json:",omitempty"` Command string `json:",omitempty"` - Created string `json:",omitempty"` + Created int64 `json:",omitempty"` Status string `json:",omitempty"` } diff --git a/commands.go b/commands.go index f8a5d2a68..963689598 100644 --- a/commands.go +++ b/commands.go @@ -15,6 +15,7 @@ import ( "os" "strconv" "text/tabwriter" + "time" ) const VERSION = "0.1.4" @@ -34,6 +35,7 @@ func ParseCommands(args []string) error { "images": CmdImages, "info": CmdInfo, "inspect": CmdInspect, + //"import": CmdImport, "history": CmdHistory, "kill": CmdKill, "logs": CmdLogs, @@ -71,7 +73,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"}, @@ -436,7 +438,7 @@ func CmdHistory(args []string) error { fmt.Fprintln(w, "ID\tCREATED\tCREATED BY") for _, out := range outs { - fmt.Fprintf(w, "%s\t%s\t%s\n", out.Id, out.Created, out.CreatedBy) + fmt.Fprintf(w, "%s\t%s ago\t%s\n", out.Id, HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.CreatedBy) } w.Flush() return nil @@ -485,12 +487,9 @@ func CmdKill(args []string) error { return nil } -/* -func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { - stdout.Flush() - cmd := rcli.Subcmd(stdout, "import", "URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball") - var archive io.Reader - var resp *http.Response +/* /!\ W.I.P /!\ */ +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 { return nil @@ -499,43 +498,18 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args . cmd.Usage() return nil } - src := cmd.Arg(0) - if src == "-" { - archive = stdin - } else { - u, err := url.Parse(src) - if err != nil { - return err - } - if u.Scheme == "" { - u.Scheme = "http" - u.Host = src - u.Path = "" - } - fmt.Fprintln(stdout, "Downloading from", u) - // Download with curl (pretty progress bar) - // If curl is not available, fallback to http.Get() - resp, err = Download(u.String(), stdout) - if err != nil { - return err - } - archive = ProgressReader(resp.Body, int(resp.ContentLength), stdout) - } - img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src) + src, repository, tag := cmd.Arg(0), cmd.Arg(1), cmd.Arg(2) + v := url.Values{} + v.Set("repo", repository) + v.Set("tag", tag) + v.Set("src", src) + + err := callStream("POST", "/images?"+v.Encode(), nil, false) if err != nil { return err } - // Optionally register the image at REPO/TAG - if repository := cmd.Arg(1); repository != "" { - tag := cmd.Arg(2) // Repository will handle an empty tag properly - if err := srv.runtime.repositories.Set(repository, tag, img.Id, true); err != nil { - return err - } - } - fmt.Fprintln(stdout, img.ShortId()) return nil } -*/ /* func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { @@ -656,7 +630,7 @@ func CmdImages(args []string) error { for _, out := range outs { if !*quiet { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", out.Repository, out.Tag, out.Id, out.Created) + fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\n", out.Repository, out.Tag, out.Id, HumanDuration(time.Now().Sub(time.Unix(out.Created, 0)))) } else { fmt.Fprintln(w, out.Id) } @@ -713,7 +687,7 @@ func CmdPs(args []string) error { for _, out := range outs { if !*quiet { - fmt.Fprintf(w, "%s\t%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 ago\n", out.Id, out.Image, out.Command, out.Status, HumanDuration(time.Now().Sub(time.Unix(out.Created, 0)))) } else { fmt.Fprintln(w, out.Id) } From a4bcf7e1ac19dbf4c2e4f65af4addc1bfc47d567 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 2 May 2013 05:07:06 +0200 Subject: [PATCH 18/69] refactoring run/attach/logs --- api.go | 283 ++++++++++++++++++-------------------------------- api_params.go | 7 +- commands.go | 174 ++++++++++++++++++++----------- 3 files changed, 217 insertions(+), 247 deletions(-) diff --git a/api.go b/api.go index 7e0c8fc24..b574bab4d 100644 --- a/api.go +++ b/api.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/gorilla/mux" "io" - "io/ioutil" "log" "net" "net/http" @@ -73,7 +72,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { } defer file.Close() - fmt.Fprintln(file, "HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\r\n\r\n") + 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()) @@ -207,52 +206,6 @@ 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.Method, r.RequestURI) - vars := mux.Vars(r) - name := vars["name"] - - if container := rtime.Get(name); container != nil { - var out ApiLogs - - logStdout, err := container.ReadLog("stdout") - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - logStderr, err := container.ReadLog("stderr") - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - stdout, errStdout := ioutil.ReadAll(logStdout) - if errStdout != nil { - http.Error(w, errStdout.Error(), http.StatusInternalServerError) - return - } else { - out.Stdout = fmt.Sprintf("%s", stdout) - } - stderr, errStderr := ioutil.ReadAll(logStderr) - if errStderr != nil { - http.Error(w, errStderr.Error(), http.StatusInternalServerError) - return - } else { - out.Stderr = fmt.Sprintf("%s", stderr) - } - - b, err := json.Marshal(out) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } else { - w.Write(b) - } - - } else { - http.Error(w, "No such container: "+name, http.StatusNotFound) - } - }) - r.Path("/containers/{name:.*}/changes").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println(r.Method, r.RequestURI) vars := mux.Vars(r) @@ -368,7 +321,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { return } - b, err := json.Marshal(ApiCommit{img.ShortId()}) + b, err := json.Marshal(ApiId{img.ShortId()}) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } else { @@ -415,7 +368,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { } defer file.Close() - fmt.Fprintln(file, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n") + 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()) @@ -506,146 +459,21 @@ func ListenAndServe(addr string, rtime *Runtime) error { 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 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) - } - } - - // Flush the options to make sure the client sets the raw mode - // or tell the client there is no options - conn.Write([]byte{}) - - fmt.Fprintln(file, "HTTP/1.1 201 Created\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 rtime.graph.LookupRemoteImage(config.Image, rtime.authConfig) { - if err := rtime.graph.PullImage(file, config.Image, rtime.authConfig); err != nil { - fmt.Fprintln(file, "Error: "+err.Error()) - return - } - } else if err := rtime.graph.PullRepository(file, config.Image, "", rtime.repositories, rtime.authConfig); err != nil { - fmt.Fprintln(file, "Error: "+err.Error()) - return - } - if container, err = rtime.Create(&config); err != nil { - fmt.Fprintln(file, "Error: "+err.Error()) - return - } + http.Error(w, "No such image: "+config.Image, http.StatusNotFound) } else { - fmt.Fprintln(file, "Error: "+err.Error()) - return + 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: api 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 - - // If we are in stdinonce mode, wait for the process to end - // otherwise, simply return - if config.StdinOnce && !config.Tty { - container.Wait() - } - }) - - r.Path("/containers/{name:.*}/attach").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 { - 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 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) - } - - } - - // 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: application/json\r\n\r\n") - r, w := io.Pipe() - go func() { - defer w.Close() - defer Debugf("Closing buffered stdin pipe") - io.Copy(w, file) - }() - cStdin := r - - <-container.Attach(cStdin, nil, file, file) - // Expecting I/O pipe error, discarding + b, err := json.Marshal(ApiId{container.ShortId()}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) } else { - http.Error(w, "No such container: "+name, http.StatusNotFound) - return + w.Write(b) } }) @@ -749,6 +577,99 @@ func ListenAndServe(addr string, rtime *Runtime) error { } }) + r.Path("/containers/{name:.*}/attach").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) + } + logs := r.Form.Get("logs") + stream := r.Form.Get("stream") + stdin := r.Form.Get("stdin") + stdout := r.Form.Get("stdout") + stderr := r.Form.Get("stderr") + 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 + } + defer file.Close() + 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) + } + + } + // 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" { + 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) + } + }) + r.Path("/containers/{name:.*}").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println(r.Method, r.RequestURI) vars := mux.Vars(r) diff --git a/api_params.go b/api_params.go index 837b5a642..0d3a6c08c 100644 --- a/api_params.go +++ b/api_params.go @@ -30,15 +30,10 @@ type ApiContainers struct { Status string `json:",omitempty"` } -type ApiCommit struct { +type ApiId struct { Id string } -type ApiLogs struct { - Stdout string - Stderr string -} - type ApiPort struct { Port string } diff --git a/commands.go b/commands.go index 963689598..4fcf3dcd3 100644 --- a/commands.go +++ b/commands.go @@ -210,7 +210,7 @@ func CmdWait(args []string) error { return nil } for _, name := range cmd.Args() { - body, err := call("POST", "/containers/"+name+"/wait") + body, _, err := call("POST", "/containers/"+name+"/wait", nil) if err != nil { fmt.Printf("%s", err) } else { @@ -236,7 +236,7 @@ func CmdVersion(args []string) error { return nil } - body, err := call("GET", "/version") + body, _, err := call("GET", "/version", nil) if err != nil { return err } @@ -266,7 +266,7 @@ func CmdInfo(args []string) error { return nil } - body, err := call("GET", "/info") + body, _, err := call("GET", "/info", nil) if err != nil { return err } @@ -295,7 +295,7 @@ func CmdStop(args []string) error { } for _, name := range args { - _, err := call("POST", "/containers/"+name+"/stop") + _, _, err := call("POST", "/containers/"+name+"/stop", nil) if err != nil { fmt.Printf("%s", err) } else { @@ -316,7 +316,7 @@ func CmdRestart(args []string) error { } for _, name := range args { - _, err := call("POST", "/containers/"+name+"/restart") + _, _, err := call("POST", "/containers/"+name+"/restart", nil) if err != nil { fmt.Printf("%s", err) } else { @@ -337,7 +337,7 @@ func CmdStart(args []string) error { } for _, name := range args { - _, err := call("POST", "/containers/"+name+"/start") + _, _, err := call("POST", "/containers/"+name+"/start", nil) if err != nil { fmt.Printf("%s", err) } else { @@ -356,9 +356,9 @@ func CmdInspect(args []string) error { cmd.Usage() return nil } - obj, err := call("GET", "/containers/"+cmd.Arg(0)) + obj, _, err := call("GET", "/containers/"+cmd.Arg(0), nil) if err != nil { - obj, err = call("GET", "/images/"+cmd.Arg(0)) + obj, _, err = call("GET", "/images/"+cmd.Arg(0), nil) if err != nil { return err } @@ -378,7 +378,7 @@ func CmdPort(args []string) error { } v := url.Values{} v.Set("port", cmd.Arg(1)) - body, err := call("GET", "/containers/"+cmd.Arg(0)+"/port?"+v.Encode()) + body, _, err := call("GET", "/containers/"+cmd.Arg(0)+"/port?"+v.Encode(), nil) if err != nil { return err } @@ -404,7 +404,7 @@ func CmdRmi(args []string) error { } for _, name := range args { - _, err := call("DELETE", "/images/"+name) + _, _, err := call("DELETE", "/images/"+name, nil) if err != nil { fmt.Printf("%s", err) } else { @@ -424,7 +424,7 @@ 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", nil) if err != nil { return err } @@ -455,7 +455,7 @@ func CmdRm(args []string) error { } for _, name := range args { - _, err := call("DELETE", "/containers/"+name) + _, _, err := call("DELETE", "/containers/"+name, nil) if err != nil { fmt.Printf("%s", err) } else { @@ -477,7 +477,7 @@ func CmdKill(args []string) error { } for _, name := range args { - _, err := call("POST", "/containers/"+name+"/kill") + _, _, err := call("POST", "/containers/"+name+"/kill", nil) if err != nil { fmt.Printf("%s", err) } else { @@ -504,7 +504,7 @@ func CmdImport(args []string) error { v.Set("tag", tag) v.Set("src", src) - err := callStream("POST", "/images?"+v.Encode(), nil, false) + err := hijack("POST", "/images?"+v.Encode(), false) if err != nil { return err } @@ -583,7 +583,7 @@ func CmdPull(args []string) error { return nil } - if err := callStream("POST", "/images/"+cmd.Arg(0)+"/pull", nil, false); err != nil { + if err := hijack("POST", "/images/"+cmd.Arg(0)+"/pull", false); err != nil { return err } @@ -613,7 +613,7 @@ func CmdImages(args []string) error { v.Set("all", "1") } - body, err := call("GET", "/images?"+v.Encode()) + body, _, err := call("GET", "/images?"+v.Encode(), nil) if err != nil { return err } @@ -670,7 +670,7 @@ func CmdPs(args []string) error { v.Set("n", strconv.Itoa(*last)) } - body, err := call("GET", "/containers?"+v.Encode()) + body, _, err := call("GET", "/containers?"+v.Encode(), nil) if err != nil { return err } @@ -715,12 +715,12 @@ func CmdCommit(args []string) error { v.Set("tag", tag) v.Set("comment", *flComment) - body, err := call("POST", "/containers/"+name+"/commit?"+v.Encode()) + body, _, err := call("POST", "/containers/"+name+"/commit?"+v.Encode(), nil) if err != nil { return err } - var out ApiCommit + var out ApiId err = json.Unmarshal(body, &out) if err != nil { return err @@ -741,7 +741,7 @@ func CmdExport(args []string) error { return nil } - if err := callStream("GET", "/containers/"+cmd.Arg(0)+"/export", nil, false); err != nil { + if err := hijack("GET", "/containers/"+cmd.Arg(0)+"/export", false); err != nil { return err } return nil @@ -757,7 +757,7 @@ func CmdDiff(args []string) error { return nil } - body, err := call("GET", "/containers/"+cmd.Arg(0)+"/changes") + body, _, err := call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil) if err != nil { return err } @@ -782,19 +782,15 @@ func CmdLogs(args []string) error { cmd.Usage() return nil } - body, err := call("GET", "/containers/"+cmd.Arg(0)+"/logs") - if err != nil { + + v := url.Values{} + v.Set("logs", "1") + v.Set("stdout", "1") + v.Set("stderr", "1") + + if err := hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), false); err != nil { return err } - - var out ApiLogs - err = json.Unmarshal(body, &out) - if err != nil { - return err - } - fmt.Fprintln(os.Stdout, out.Stdout) - fmt.Fprintln(os.Stderr, out.Stderr) - return nil } @@ -808,7 +804,7 @@ func CmdAttach(args []string) error { return nil } - body, err := call("GET", "/containers/"+cmd.Arg(0)) + body, _, err := call("GET", "/containers/"+cmd.Arg(0), nil) if err != nil { return err } @@ -819,7 +815,14 @@ func CmdAttach(args []string) error { return err } - if err := callStream("POST", "/containers/"+cmd.Arg(0)+"/attach", nil, container.Config.Tty); err != nil { + v := url.Values{} + v.Set("logs", "1") + v.Set("stream", "1") + v.Set("stdout", "1") + v.Set("stderr", "1") + v.Set("stdin", "1") + + if err := hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty); err != nil { return err } return nil @@ -903,7 +906,7 @@ func CmdTag(args []string) error { v.Set("force", "1") } - if err := callStream("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil, false); err != nil { + if _, _, err := call("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil); err != nil { return err } return nil @@ -923,54 +926,105 @@ func CmdRun(args []string) error { return nil } - if err := callStream("POST", "/containers", *config, config.Tty); err != nil { + //create the container + body, statusCode, err := call("POST", "/containers", *config) + + //if image not found try to pull it + if statusCode == 404 { + err = hijack("POST", "/images/"+config.Image+"/pull", false) + if err != nil { + return err + } + body, _, err = call("POST", "/containers", *config) + } + if err != nil { return err } + + var out ApiId + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + + v := url.Values{} + v.Set("logs", "1") + v.Set("stream", "1") + + if config.AttachStdin { + v.Set("stdin", "1") + } + if config.AttachStdout { + v.Set("stdout", "1") + } + if config.AttachStderr { + v.Set("stderr", "1") + + } + /* + attach := Go(func() error { + err := hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty) + return err + })*/ + + //start the container + _, _, err = call("POST", "/containers/"+out.Id+"/start", nil) + if err != nil { + return err + } + + if err := hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty); err != nil { + return err + } + + /* + if err := <-attach; err != nil { + return err + } + */ + return nil } -func call(method, path string) ([]byte, error) { - req, err := http.NewRequest(method, "http://0.0.0.0:4243"+path, nil) - if err != nil { - return nil, err +func call(method, path string, data interface{}) ([]byte, int, error) { + var params io.Reader + if data != nil { + buf, err := json.Marshal(data) + if err != nil { + return nil, -1, err + } + params = bytes.NewBuffer(buf) } - if method == "POST" { + req, err := http.NewRequest(method, "http://0.0.0.0:4243"+path, params) + if err != nil { + return nil, -1, err + } + if data != nil { + req.Header.Set("Content-Type", "application/json") + } else if method == "POST" { req.Header.Set("Content-Type", "plain/text") } resp, err := http.DefaultClient.Do(req) if err != nil { - return nil, err + return nil, -1, err } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { - return nil, err + return nil, -1, err } if resp.StatusCode != 200 { - return nil, fmt.Errorf("error: %s", body) + return nil, resp.StatusCode, fmt.Errorf("error: %s", body) } - return body, nil + return body, resp.StatusCode, nil } -func callStream(method, path string, data interface{}, setRawTerminal 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) +func hijack(method, path string, setRawTerminal bool) error { + req, err := http.NewRequest(method, path, nil) if err != nil { return err } - - if data != nil { - req.Header.Set("Content-Type", "application/json") - } - dial, err := net.Dial("tcp", "0.0.0.0:4243") if err != nil { return err From c6963da54e1aa550d58419a8275967060866bd6d Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 3 May 2013 17:51:11 +0200 Subject: [PATCH 19/69] makefile from master --- Makefile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 67b805b83..f35362d2c 100644 --- a/Makefile +++ b/Makefile @@ -33,17 +33,20 @@ all: $(DOCKER_BIN) $(DOCKER_BIN): $(DOCKER_DIR) @mkdir -p $(dir $@) - @(cd $(DOCKER_MAIN); go get $(GO_OPTIONS); go build $(GO_OPTIONS) $(BUILD_OPTIONS) -o $@) + @(cd $(DOCKER_MAIN); go build $(GO_OPTIONS) $(BUILD_OPTIONS) -o $@) @echo $(DOCKER_BIN_RELATIVE) is created. $(DOCKER_DIR): @mkdir -p $(dir $@) - @ln -sf $(CURDIR)/ $@ + @if [ -h $@ ]; then rm -f $@; ln -sf $(CURDIR)/ $@; fi + @(cd $(DOCKER_MAIN); go get $(GO_OPTIONS)) whichrelease: echo $(RELEASE_VERSION) release: $(BINRELEASE) + s3cmd -P put $(BINRELEASE) s3://get.docker.io/builds/`uname -s`/`uname -m`/docker-$(RELEASE_VERSION).tgz + srcrelease: $(SRCRELEASE) deps: $(DOCKER_DIR) From 04cd20fa627635d30ea315d0d4e281e10e8d293c Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 6 May 2013 11:31:22 +0200 Subject: [PATCH 20/69] split api and server. run return exit code. import, pull and commit uses the smae endpoint. non zero status code on failure --- Makefile | 2 +- api.go | 662 +++++++++++++---------------------------------- commands.go | 124 ++++++--- commands_test.go | 9 +- docker/docker.go | 56 +--- runtime_test.go | 4 +- server.go | 448 ++++++++++++++++++++++++++++++++ 7 files changed, 726 insertions(+), 579 deletions(-) create mode 100644 server.go diff --git a/Makefile b/Makefile index f35362d2c..bae7d6490 100644 --- a/Makefile +++ b/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: diff --git a/api.go b/api.go index df442ab39..decd273e2 100644 --- a/api.go +++ b/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 + httpError(w, err) } - 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 = "" - out.Tag = "" - out.Id = TruncateId(id) - out.Created = image.Created.Unix() - } else { - out.Id = image.ShortId() - } - outs = append(outs, out) - } - } - 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,26 +155,19 @@ 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) - } - } + out, err := srv.ContainerPort(name, r.Form.Get("port")) + if err != nil { + httpError(w, err) } + b, err := json.Marshal(ApiPort{out}) + 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) { @@ -264,38 +175,15 @@ 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") - NoTrunc := r.Form.Get("notrunc") - Quiet := r.Form.Get("quiet") - Last := r.Form.Get("n") - n, err := strconv.Atoi(Last) + 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 { 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) - } + outs := srv.Containers(all, notrunc, quiet, n) b, err := json.Marshal(outs) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -304,159 +192,84 @@ func ListenAndServe(addr string, rtime *Runtime) error { } }) - r.Path("/containers/{name:.*}/commit").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 - } - - 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) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - b, err := json.Marshal(ApiId{img.ShortId()}) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } else { - w.Write(b) - } - }) - 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") + 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() + id, err := srv.ContainerCommit(container, repo, tag, author, comment, &config) + if err != nil { + httpError(w, err) + } + b, err := json.Marshal(ApiId{id}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } else { + w.Write(b) + } + } 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, "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 + 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 { - 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 { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - archive = ProgressReader(resp.Body, int(resp.ContentLength), file, "Importing %v/%v (%v)") + w.WriteHeader(http.StatusNotFound) } - 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) - return - } - } - - fmt.Fprintln(file, img.ShortId()) }) 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) diff --git a/commands.go b/commands.go index d847ed365..87c6dcc55 100644 --- a/commands.go +++ b/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?") + } - cmds := map[string]func(args []string) error{ + 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{ "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 \"") @@ -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() diff --git a/commands_test.go b/commands_test.go index 83b480d52..784ef4196 100644 --- a/commands_test.go +++ b/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() } +*/ diff --git a/docker/docker.go b/docker/docker.go index 80d424be3..7cfc39c03 100644 --- a/docker/docker.go +++ b/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) } diff --git a/runtime_test.go b/runtime_test.go index e9be838c0..d9e504e1a 100644 --- a/runtime_test.go +++ b/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) } } diff --git a/server.go b/server.go new file mode 100644 index 000000000..ddf1f849e --- /dev/null +++ b/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 = "" + out.Tag = "" + 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 +} From f37399d22baaec0b04de42e4712d50f3edd80444 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 6 May 2013 13:34:31 +0200 Subject: [PATCH 21/69] added login and push --- api.go | 68 ++++++++++++++++++++++ api_params.go | 4 ++ commands.go | 152 +++++++++++++++++++++++++++----------------------- server.go | 21 +++++++ utils.go | 34 +++++++++++ 5 files changed, 209 insertions(+), 70 deletions(-) diff --git a/api.go b/api.go index 738d8d563..23a63a4b3 100644 --- a/api.go +++ b/api.go @@ -4,6 +4,7 @@ import ( _ "bytes" "encoding/json" "fmt" + "github.com/dotcloud/docker/auth" "github.com/gorilla/mux" "log" "net" @@ -42,6 +43,51 @@ func ListenAndServe(addr string, srv *Server) error { r := mux.NewRouter() log.Printf("Listening for HTTP on %s\n", addr) + r.Path("/auth").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(r.Method, r.RequestURI) + var out auth.AuthConfig + out.Username = srv.runtime.authConfig.Username + out.Email = srv.runtime.authConfig.Email + b, err := json.Marshal(out) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } else { + w.Write(b) + } + }) + + r.Path("/auth").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(r.Method, r.RequestURI) + var config auth.AuthConfig + if err := json.NewDecoder(r.Body).Decode(&config); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if config.Username == srv.runtime.authConfig.Username { + config.Password = srv.runtime.authConfig.Password + } + + newAuthConfig := auth.NewAuthConfig(config.Username, config.Password, config.Email, srv.runtime.root) + status, err := auth.Login(newAuthConfig) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } else { + srv.runtime.authConfig = newAuthConfig + } + if status != "" { + b, err := json.Marshal(ApiAuth{status}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } else { + w.Write(b) + } + } else { + w.WriteHeader(http.StatusOK) + } + }) + r.Path("/version").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println(r.Method, r.RequestURI) m := srv.DockerVersion() @@ -272,6 +318,28 @@ func ListenAndServe(addr string, srv *Server) error { } }) + r.Path("/images/{name:*.}/push").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(r.Method, r.RequestURI) + vars := mux.Vars(r) + name := vars["name"] + + 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") + if err := srv.ImagePush(name, file); 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 diff --git a/api_params.go b/api_params.go index 0b46f8c72..b86265b17 100644 --- a/api_params.go +++ b/api_params.go @@ -54,3 +54,7 @@ type ApiVersion struct { type ApiWait struct { StatusCode int } + +type ApiAuth struct { + Status string +} diff --git a/commands.go b/commands.go index 0305c115a..168c04bc2 100644 --- a/commands.go +++ b/commands.go @@ -5,6 +5,7 @@ import ( "encoding/json" "flag" "fmt" + "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/term" "io" "io/ioutil" @@ -15,8 +16,10 @@ import ( "os" "path/filepath" "strconv" + "strings" "text/tabwriter" "time" + "unicode" ) const VERSION = "0.2.2" @@ -59,10 +62,12 @@ func ParseCommands(args ...string) error { "import": CmdImport, "history": CmdHistory, "kill": CmdKill, + "login": CmdLogin, "logs": CmdLogs, "port": CmdPort, "ps": CmdPs, "pull": CmdPull, + "push": CmdPush, "restart": CmdRestart, "rm": CmdRm, "rmi": CmdRmi, @@ -98,12 +103,12 @@ func cmdHelp(args ...string) error { {"info", "Display system-wide information"}, {"inspect", "Return low-level information on a container/image"}, {"kill", "Kill a running container"}, - // {"login", "Register or Login to the docker registry server"}, + {"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"}, {"ps", "List containers"}, {"pull", "Pull an image or a repository from the docker registry server"}, - // {"push", "Push an image or a repository to 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"}, @@ -120,17 +125,8 @@ func cmdHelp(args ...string) error { return nil } -/* // 'docker login': login / register a user to registry service. -func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { - // Read a line on raw terminal with support for simple backspace - // sequences and echo. - // - // This function is necessary because the login command must be done in a - // raw terminal for two reasons: - // - we have to read a password (without echoing it); - // - the rcli "protocol" only supports cannonical and raw modes and you - // can't tune it once the command as been started. +func CmdLogin(args ...string) error { var readStringOnRawTerminal = func(stdin io.Reader, stdout io.Writer, echo bool) string { char := make([]byte, 1) buffer := make([]byte, 64) @@ -173,52 +169,74 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args .. return readStringOnRawTerminal(stdin, stdout, false) } - stdout.SetOptionRawTerminal() + oldState, err := SetRawTerminal() + if err != nil { + return err + } else { + defer RestoreTerminal(oldState) + } - cmd := rcli.Subcmd(stdout, "login", "", "Register or Login to the docker registry server") + cmd := Subcmd("login", "", "Register or Login to the docker registry server") if err := cmd.Parse(args); err != nil { return nil } + body, _, err := call("GET", "/auth", nil) + if err != nil { + return err + } + + var out auth.AuthConfig + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + var username string var password string var email string - fmt.Fprint(stdout, "Username (", srv.runtime.authConfig.Username, "): ") - username = readAndEchoString(stdin, stdout) + fmt.Print("Username (", out.Username, "): ") + username = readAndEchoString(os.Stdin, os.Stdout) if username == "" { - username = srv.runtime.authConfig.Username + username = out.Username } - if username != srv.runtime.authConfig.Username { - fmt.Fprint(stdout, "Password: ") - password = readString(stdin, stdout) + if username != out.Username { + fmt.Print("Password: ") + password = readString(os.Stdin, os.Stdout) if password == "" { return fmt.Errorf("Error : Password Required") } - fmt.Fprint(stdout, "Email (", srv.runtime.authConfig.Email, "): ") - email = readAndEchoString(stdin, stdout) + fmt.Print("Email (", out.Email, "): ") + email = readAndEchoString(os.Stdin, os.Stdout) if email == "" { - email = srv.runtime.authConfig.Email + email = out.Email } } else { - password = srv.runtime.authConfig.Password - email = srv.runtime.authConfig.Email + email = out.Email } - newAuthConfig := auth.NewAuthConfig(username, password, email, srv.runtime.root) - status, err := auth.Login(newAuthConfig) + + out.Username = username + out.Password = password + out.Email = email + + body, _, err = call("POST", "/auth", out) if err != nil { - fmt.Fprintf(stdout, "Error: %s\r\n", err) - } else { - srv.runtime.authConfig = newAuthConfig + return err } - if status != "" { - fmt.Fprint(stdout, status) + + var out2 ApiAuth + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + if out2.Status != "" { + fmt.Print(out2.Status) } return nil } -*/ // 'docker wait': block until a container stops func CmdWait(args ...string) error { @@ -547,66 +565,60 @@ func CmdImport(args ...string) error { return nil } -/* -func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { - cmd := rcli.Subcmd(stdout, "push", "NAME", "Push an image or a repository to the registry") +func CmdPush(args ...string) error { + cmd := Subcmd("push", "NAME", "Push an image or a repository to the registry") if err := cmd.Parse(args); err != nil { return nil } - local := cmd.Arg(0) + name := cmd.Arg(0) - if local == "" { + if name == "" { cmd.Usage() return nil } + body, _, err := call("GET", "/auth", nil) + if err != nil { + return err + } + + var out auth.AuthConfig + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + // If the login failed, abort - if srv.runtime.authConfig == nil || srv.runtime.authConfig.Username == "" { - if err := srv.CmdLogin(stdin, stdout, args...); err != nil { + if out.Username == "" { + if err := CmdLogin(args...); err != nil { return err } - if srv.runtime.authConfig == nil || srv.runtime.authConfig.Username == "" { + + body, _, err = call("GET", "/auth", nil) + if err != nil { + return err + } + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + + if out.Username == "" { return fmt.Errorf("Please login prior to push. ('docker login')") } } - var remote string - - tmp := strings.SplitN(local, "/", 2) - if len(tmp) == 1 { + if len(strings.SplitN(name, "/", 2)) == 1 { return fmt.Errorf( "Impossible to push a \"root\" repository. Please rename your repository in / (ex: %s/%s)", - srv.runtime.authConfig.Username, local) - } else { - remote = local + out.Username, name) } - Debugf("Pushing [%s] to [%s]\n", local, remote) - - // Try to get the image - // FIXME: Handle lookup - // FIXME: Also push the tags in case of ./docker push myrepo:mytag - // img, err := srv.runtime.LookupImage(cmd.Arg(0)) - img, err := srv.runtime.graph.Get(local) - if err != nil { - Debugf("The push refers to a repository [%s] (len: %d)\n", local, len(srv.runtime.repositories.Repositories[local])) - // If it fails, try to get the repository - if localRepo, exists := srv.runtime.repositories.Repositories[local]; exists { - if err := srv.runtime.graph.PushRepository(stdout, remote, localRepo, srv.runtime.authConfig); err != nil { - return err - } - return nil - } - - return err - } - err = srv.runtime.graph.PushImage(stdout, img, srv.runtime.authConfig) - if err != nil { + if err := hijack("POST", "/images"+name+"/pull", false); err != nil { return err } return nil } -*/ func CmdPull(args ...string) error { cmd := Subcmd("pull", "NAME", "Pull an image or a repository from the registry") diff --git a/server.go b/server.go index 4d6feb3f7..7ea71d1be 100644 --- a/server.go +++ b/server.go @@ -214,6 +214,27 @@ func (srv *Server) ImagePull(name string, file *os.File) error { return nil } +func (srv *Server) ImagePush(name string, file *os.File) error { + img, err := srv.runtime.graph.Get(name) + if err != nil { + Debugf("The push refers to a repository [%s] (len: %d)\n", name, len(srv.runtime.repositories.Repositories[name])) + // If it fails, try to get the repository + if localRepo, exists := srv.runtime.repositories.Repositories[name]; exists { + if err := srv.runtime.graph.PushRepository(file, name, localRepo, srv.runtime.authConfig); err != nil { + return err + } + return nil + } + + return err + } + err = srv.runtime.graph.PushImage(file, img, srv.runtime.authConfig) + if 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 diff --git a/utils.go b/utils.go index 40f67f719..8f269df40 100644 --- a/utils.go +++ b/utils.go @@ -477,3 +477,37 @@ func FindCgroupMountpoint(cgroupType string) (string, error) { return "", fmt.Errorf("cgroup mountpoint not found for %s", cgroupType) } + +/* +func ReadStringOnTerminal(prompt string) (string, error) { + fmt.Print(prompt) + in := bufio.NewReader(os.Stdin); + return in.ReadString('\n'); +} + +func ReadPasswdOnTerminal(prompt string) (string, error) { + fmt.Print(prompt); + const stty_arg0 = "/bin/stty"; + stty_argv_e_off := []string{"stty","-echo"}; + stty_argv_e_on := []string{"stty","echo"}; + const exec_cwdir = ""; + fd := []*os.File{os.Stdin,os.Stdout,os.Stderr}; + pid, err := os.StartProcess(stty_arg0,stty_argv_e_off,nil,exec_cwdir,fd); + if err != nil { + return "", err + } + rd := bufio.NewReader(os.Stdin); + os.Wait(pid,0); + line, err := rd.ReadString('\n'); + if err != nil { + return "", err + } + passwd := strings.TrimSpace(line) + pid, e := os.StartProcess(stty_arg0,stty_argv_e_on,nil,exec_cwdir,fd); + if e =! nil { + return "", err + } + os.Wait(pid,0) + return passwd, err +} +*/ From 5a2a5ccdaf70fba20c89912153fec19eba35e7e6 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 6 May 2013 16:59:33 +0200 Subject: [PATCH 22/69] removed rcli completly --- commands.go | 23 ------- rcli/tcp.go | 169 --------------------------------------------- rcli/types.go | 181 ------------------------------------------------- rcli/utils.go | 27 -------- server_test.go | 96 ++++++++++++++++++++++++++ utils.go | 4 -- 6 files changed, 96 insertions(+), 404 deletions(-) delete mode 100644 rcli/tcp.go delete mode 100644 rcli/types.go delete mode 100644 rcli/utils.go create mode 100644 server_test.go diff --git a/commands.go b/commands.go index 168c04bc2..3103f5276 100644 --- a/commands.go +++ b/commands.go @@ -890,24 +890,6 @@ func CmdAttach(args ...string) error { return nil } -/* -// Ports type - Used to parse multiple -p flags -type ports []int - -func (p *ports) String() string { - return fmt.Sprint(*p) -} - -func (p *ports) Set(value string) error { - port, err := strconv.Atoi(value) - if err != nil { - return fmt.Errorf("Invalid port: %v", value) - } - *p = append(*p, port) - return nil -} -*/ - // ListOpts type type ListOpts []string @@ -1053,11 +1035,6 @@ func CmdRun(args ...string) error { v.Set("stderr", "1") } - /* - attach := Go(func() error { - err := hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty) - return err - })*/ //start the container _, _, err = call("POST", "/containers/"+out.Id+"/start", nil) diff --git a/rcli/tcp.go b/rcli/tcp.go deleted file mode 100644 index cf111cdf7..000000000 --- a/rcli/tcp.go +++ /dev/null @@ -1,169 +0,0 @@ -package rcli - -import ( - "bufio" - "bytes" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "log" - "net" -) - -// Note: the globals are here to avoid import cycle -// FIXME: Handle debug levels mode? -var DEBUG_FLAG bool = false -var CLIENT_SOCKET io.Writer = nil - -type DockerTCPConn struct { - conn *net.TCPConn - options *DockerConnOptions - optionsBuf *[]byte - handshaked bool - client bool -} - -func NewDockerTCPConn(conn *net.TCPConn, client bool) *DockerTCPConn { - return &DockerTCPConn{ - conn: conn, - options: &DockerConnOptions{}, - client: client, - } -} - -func (c *DockerTCPConn) SetOptionRawTerminal() { - c.options.RawTerminal = true -} - -func (c *DockerTCPConn) GetOptions() *DockerConnOptions { - if c.client && !c.handshaked { - // Attempt to parse options encoded as a JSON dict and store - // the reminder of what we read from the socket in a buffer. - // - // bufio (and its ReadBytes method) would have been nice here, - // but if json.Unmarshal() fails (which will happen if we speak - // to a version of docker that doesn't send any option), then - // we can't put the data back in it for the next Read(). - c.handshaked = true - buf := make([]byte, 4096) - if n, _ := c.conn.Read(buf); n > 0 { - buf = buf[:n] - if nl := bytes.IndexByte(buf, '\n'); nl != -1 { - if err := json.Unmarshal(buf[:nl], c.options); err == nil { - buf = buf[nl+1:] - } - } - c.optionsBuf = &buf - } - } - - return c.options -} - -func (c *DockerTCPConn) Read(b []byte) (int, error) { - if c.optionsBuf != nil { - // Consume what we buffered in GetOptions() first: - optionsBuf := *c.optionsBuf - optionsBuflen := len(optionsBuf) - copied := copy(b, optionsBuf) - if copied < optionsBuflen { - optionsBuf = optionsBuf[copied:] - c.optionsBuf = &optionsBuf - return copied, nil - } - c.optionsBuf = nil - return copied, nil - } - return c.conn.Read(b) -} - -func (c *DockerTCPConn) Write(b []byte) (int, error) { - optionsLen := 0 - if !c.client && !c.handshaked { - c.handshaked = true - options, _ := json.Marshal(c.options) - options = append(options, '\n') - if optionsLen, err := c.conn.Write(options); err != nil { - return optionsLen, err - } - } - n, err := c.conn.Write(b) - return n + optionsLen, err -} - -func (c *DockerTCPConn) Flush() error { - _, err := c.Write([]byte{}) - return err -} - -func (c *DockerTCPConn) Close() error { return c.conn.Close() } - -func (c *DockerTCPConn) CloseWrite() error { return c.conn.CloseWrite() } - -func (c *DockerTCPConn) CloseRead() error { return c.conn.CloseRead() } - -// Connect to a remote endpoint using protocol `proto` and address `addr`, -// issue a single call, and return the result. -// `proto` may be "tcp", "unix", etc. See the `net` package for available protocols. -func Call(proto, addr string, args ...string) (DockerConn, error) { - cmd, err := json.Marshal(args) - if err != nil { - return nil, err - } - conn, err := dialDocker(proto, addr) - if err != nil { - return nil, err - } - if _, err := fmt.Fprintln(conn, string(cmd)); err != nil { - return nil, err - } - return conn, nil -} - -// Listen on `addr`, using protocol `proto`, for incoming rcli calls, -// and pass them to `service`. -func ListenAndServe(proto, addr string, service Service) error { - listener, err := net.Listen(proto, addr) - if err != nil { - return err - } - log.Printf("Listening for RCLI/%s on %s\n", proto, addr) - defer listener.Close() - for { - if conn, err := listener.Accept(); err != nil { - return err - } else { - conn, err := newDockerServerConn(conn) - if err != nil { - return err - } - go func(conn DockerConn) { - defer conn.Close() - if DEBUG_FLAG { - CLIENT_SOCKET = conn - } - if err := Serve(conn, service); err != nil { - log.Println("Error:", err.Error()) - fmt.Fprintln(conn, "Error:", err.Error()) - } - }(conn) - } - } - return nil -} - -// Parse an rcli call on a new connection, and pass it to `service` if it -// is valid. -func Serve(conn DockerConn, service Service) error { - r := bufio.NewReader(conn) - var args []string - if line, err := r.ReadString('\n'); err != nil { - return err - } else if err := json.Unmarshal([]byte(line), &args); err != nil { - return err - } else { - return call(service, ioutil.NopCloser(r), conn, args...) - } - return nil -} diff --git a/rcli/types.go b/rcli/types.go deleted file mode 100644 index 38f4a8c00..000000000 --- a/rcli/types.go +++ /dev/null @@ -1,181 +0,0 @@ -package rcli - -// rcli (Remote Command-Line Interface) is a simple protocol for... -// serving command-line interfaces remotely. -// -// rcli can be used over any transport capable of a) sending binary streams in -// both directions, and b) capable of half-closing a connection. TCP and Unix sockets -// are the usual suspects. - -import ( - "flag" - "fmt" - "github.com/dotcloud/docker/term" - "io" - "log" - "net" - "os" - "reflect" - "strings" -) - -type DockerConnOptions struct { - RawTerminal bool -} - -type DockerConn interface { - io.ReadWriteCloser - CloseWrite() error - CloseRead() error - GetOptions() *DockerConnOptions - SetOptionRawTerminal() - Flush() error -} - -type DockerLocalConn struct { - writer io.WriteCloser - savedState *term.State -} - -func NewDockerLocalConn(w io.WriteCloser) *DockerLocalConn { - return &DockerLocalConn{ - writer: w, - } -} - -func (c *DockerLocalConn) Read(b []byte) (int, error) { - return 0, fmt.Errorf("DockerLocalConn does not implement Read()") -} - -func (c *DockerLocalConn) Write(b []byte) (int, error) { return c.writer.Write(b) } - -func (c *DockerLocalConn) Close() error { - if c.savedState != nil { - RestoreTerminal(c.savedState) - c.savedState = nil - } - return c.writer.Close() -} - -func (c *DockerLocalConn) Flush() error { return nil } - -func (c *DockerLocalConn) CloseWrite() error { return nil } - -func (c *DockerLocalConn) CloseRead() error { return nil } - -func (c *DockerLocalConn) GetOptions() *DockerConnOptions { return nil } - -func (c *DockerLocalConn) SetOptionRawTerminal() { - if state, err := SetRawTerminal(); err != nil { - if os.Getenv("DEBUG") != "" { - log.Printf("Can't set the terminal in raw mode: %s", err) - } - } else { - c.savedState = state - } -} - -var UnknownDockerProto = fmt.Errorf("Only TCP is actually supported by Docker at the moment") - -func dialDocker(proto string, addr string) (DockerConn, error) { - conn, err := net.Dial(proto, addr) - if err != nil { - return nil, err - } - switch i := conn.(type) { - case *net.TCPConn: - return NewDockerTCPConn(i, true), nil - } - return nil, UnknownDockerProto -} - -func newDockerFromConn(conn net.Conn, client bool) (DockerConn, error) { - switch i := conn.(type) { - case *net.TCPConn: - return NewDockerTCPConn(i, client), nil - } - return nil, UnknownDockerProto -} - -func newDockerServerConn(conn net.Conn) (DockerConn, error) { - return newDockerFromConn(conn, false) -} - -type Service interface { - Name() string - Help() string -} - -type Cmd func(io.ReadCloser, io.Writer, ...string) error -type CmdMethod func(Service, io.ReadCloser, io.Writer, ...string) error - -// FIXME: For reverse compatibility -func call(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error { - return LocalCall(service, stdin, stdout, args...) -} - -func LocalCall(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error { - if len(args) == 0 { - args = []string{"help"} - } - flags := flag.NewFlagSet("main", flag.ContinueOnError) - flags.SetOutput(stdout) - flags.Usage = func() { stdout.Write([]byte(service.Help())) } - if err := flags.Parse(args); err != nil { - return err - } - cmd := flags.Arg(0) - log.Printf("%s\n", strings.Join(append(append([]string{service.Name()}, cmd), flags.Args()[1:]...), " ")) - if cmd == "" { - cmd = "help" - } - method := getMethod(service, cmd) - if method != nil { - return method(stdin, stdout, flags.Args()[1:]...) - } - return fmt.Errorf("No such command: %s", cmd) -} - -func getMethod(service Service, name string) Cmd { - if name == "help" { - return func(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - if len(args) == 0 { - stdout.Write([]byte(service.Help())) - } else { - if method := getMethod(service, args[0]); method == nil { - return fmt.Errorf("No such command: %s", args[0]) - } else { - method(stdin, stdout, "--help") - } - } - return nil - } - } - methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:]) - method, exists := reflect.TypeOf(service).MethodByName(methodName) - if !exists { - return nil - } - return func(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - ret := method.Func.CallSlice([]reflect.Value{ - reflect.ValueOf(service), - reflect.ValueOf(stdin), - reflect.ValueOf(stdout), - reflect.ValueOf(args), - })[0].Interface() - if ret == nil { - return nil - } - return ret.(error) - } -} - -func Subcmd(output io.Writer, name, signature, description string) *flag.FlagSet { - flags := flag.NewFlagSet(name, flag.ContinueOnError) - flags.SetOutput(output) - flags.Usage = func() { - fmt.Fprintf(output, "\nUsage: docker %s %s\n\n%s\n\n", name, signature, description) - flags.PrintDefaults() - } - return flags -} diff --git a/rcli/utils.go b/rcli/utils.go deleted file mode 100644 index dbd579ffc..000000000 --- a/rcli/utils.go +++ /dev/null @@ -1,27 +0,0 @@ -package rcli - -import ( - "github.com/dotcloud/docker/term" - "os" - "os/signal" -) - -//FIXME: move these function to utils.go (in rcli to avoid import loop) -func SetRawTerminal() (*term.State, error) { - oldState, err := term.MakeRaw(int(os.Stdin.Fd())) - if err != nil { - return nil, err - } - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - go func() { - _ = <-c - term.Restore(int(os.Stdin.Fd()), oldState) - os.Exit(0) - }() - return oldState, err -} - -func RestoreTerminal(state *term.State) { - term.Restore(int(os.Stdin.Fd()), state) -} diff --git a/server_test.go b/server_test.go new file mode 100644 index 000000000..fe10cfd35 --- /dev/null +++ b/server_test.go @@ -0,0 +1,96 @@ +package docker + +import ( + "testing" +) + +func TestCreateRm(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + config, _, err := ParseRun([]string{GetTestImage(runtime).Id, "echo test"}) + if err != nil { + t.Fatal(err) + } + + id, _, _, err := srv.ContainerCreate(*config) + if err != nil { + t.Fatal(err) + } + + if len(runtime.List()) != 1 { + t.Errorf("Expected 1 container, %v found", len(runtime.List())) + } + + if err = srv.ContainerDestroy(id, true); err != nil { + t.Fatal(err) + } + + if len(runtime.List()) != 0 { + t.Errorf("Expected 0 container, %v found", len(runtime.List())) + } + +} + +func TestCreateStartRestartStopStartKillRm(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + config, _, err := ParseRun([]string{GetTestImage(runtime).Id, "/bin/cat"}) + if err != nil { + t.Fatal(err) + } + + id, _, _, err := srv.ContainerCreate(*config) + if err != nil { + t.Fatal(err) + } + + if len(runtime.List()) != 1 { + t.Errorf("Expected 1 container, %v found", len(runtime.List())) + } + + err = srv.ContainerStart(id) + if err != nil { + t.Fatal(err) + } + + err = srv.ContainerRestart(id, 1) + if err != nil { + t.Fatal(err) + } + + err = srv.ContainerStop(id, 1) + if err != nil { + t.Fatal(err) + } + + err = srv.ContainerStart(id) + if err != nil { + t.Fatal(err) + } + + err = srv.ContainerKill(id) + if err != nil { + t.Fatal(err) + } + + if err = srv.ContainerDestroy(id, true); err != nil { + t.Fatal(err) + } + + if len(runtime.List()) != 0 { + t.Errorf("Expected 0 container, %v found", len(runtime.List())) + } + +} diff --git a/utils.go b/utils.go index 8f269df40..b5258fd49 100644 --- a/utils.go +++ b/utils.go @@ -4,7 +4,6 @@ import ( "bytes" "errors" "fmt" - "github.com/dotcloud/docker/rcli" "github.com/dotcloud/docker/term" "index/suffixarray" "io" @@ -58,9 +57,6 @@ func Debugf(format string, a ...interface{}) { } fmt.Fprintf(os.Stderr, fmt.Sprintf("[debug] %s:%d %s\n", file, line, format), a...) - if rcli.CLIENT_SOCKET != nil { - fmt.Fprintf(rcli.CLIENT_SOCKET, fmt.Sprintf("[debug] %s:%d %s\n", file, line, format), a...) - } } } From 0b6c79b303547e38c758c16da2db1a430296953e Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 7 May 2013 17:19:41 +0200 Subject: [PATCH 23/69] first draft of the doc, split commit and fix some issues in spi.go --- api.go | 120 ++-- commands.go | 8 +- docs/sources/conf.py | 2 +- docs/sources/index.rst | 3 +- docs/sources/remote-api/api.rst | 878 ++++++++++++++++++++++++++++++ docs/sources/remote-api/index.rst | 15 + 6 files changed, 977 insertions(+), 49 deletions(-) create mode 100644 docs/sources/remote-api/api.rst create mode 100644 docs/sources/remote-api/index.rst diff --git a/api.go b/api.go index 23a63a4b3..11c722f50 100644 --- a/api.go +++ b/api.go @@ -52,6 +52,7 @@ func ListenAndServe(addr string, srv *Server) error { if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } else { + w.Header().Set("Content-Type", "application/json") w.Write(b) } }) @@ -81,6 +82,7 @@ func ListenAndServe(addr string, srv *Server) error { if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } else { + w.Header().Set("Content-Type", "application/json") w.Write(b) } } else { @@ -95,6 +97,7 @@ func ListenAndServe(addr string, srv *Server) error { if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } else { + w.Header().Set("Content-Type", "application/json") w.Write(b) } }) @@ -136,6 +139,7 @@ func ListenAndServe(addr string, srv *Server) error { log.Println(r.Method, r.RequestURI) if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) + return } all := r.Form.Get("all") filter := r.Form.Get("filter") @@ -144,11 +148,13 @@ func ListenAndServe(addr string, srv *Server) error { outs, err := srv.Images(all, filter, quiet) if err != nil { httpError(w, err) + return } b, err := json.Marshal(outs) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } else { + w.Header().Set("Content-Type", "application/json") w.Write(b) } }) @@ -160,6 +166,7 @@ func ListenAndServe(addr string, srv *Server) error { if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } else { + w.Header().Set("Content-Type", "application/json") w.Write(b) } }) @@ -171,11 +178,13 @@ func ListenAndServe(addr string, srv *Server) error { outs, err := srv.ImageHistory(name) if err != nil { httpError(w, err) + return } b, err := json.Marshal(outs) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } else { + w.Header().Set("Content-Type", "application/json") w.Write(b) } }) @@ -187,11 +196,13 @@ func ListenAndServe(addr string, srv *Server) error { changesStr, err := srv.ContainerChanges(name) if err != nil { httpError(w, err) + return } b, err := json.Marshal(changesStr) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } else { + w.Header().Set("Content-Type", "application/json") w.Write(b) } }) @@ -200,17 +211,20 @@ func ListenAndServe(addr string, srv *Server) error { log.Println(r.Method, r.RequestURI) if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) + return } vars := mux.Vars(r) name := vars["name"] out, err := srv.ContainerPort(name, r.Form.Get("port")) if err != nil { httpError(w, err) + return } b, err := json.Marshal(ApiPort{out}) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } else { + w.Header().Set("Content-Type", "application/json") w.Write(b) } @@ -220,6 +234,7 @@ func ListenAndServe(addr string, srv *Server) error { log.Println(r.Method, r.RequestURI) if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) + return } all := r.Form.Get("all") notrunc := r.Form.Get("notrunc") @@ -234,6 +249,7 @@ func ListenAndServe(addr string, srv *Server) error { if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } else { + w.Header().Set("Content-Type", "application/json") w.Write(b) } }) @@ -242,6 +258,7 @@ func ListenAndServe(addr string, srv *Server) error { log.Println(r.Method, r.RequestURI) if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) + return } repo := r.Form.Get("repo") tag := r.Form.Get("tag") @@ -259,62 +276,68 @@ func ListenAndServe(addr string, srv *Server) error { w.WriteHeader(http.StatusCreated) }) + r.Path("/commit").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) + return + } + var config Config + if err := json.NewDecoder(r.Body).Decode(&config); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + repo := r.Form.Get("repo") + tag := r.Form.Get("tag") + container := r.Form.Get("container") + author := r.Form.Get("author") + comment := r.Form.Get("comment") + id, err := srv.ContainerCommit(container, repo, tag, author, comment, &config) + if err != nil { + httpError(w, err) + return + } + b, err := json.Marshal(ApiId{id}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } else { + w.Header().Set("Content-Type", "application/json") + w.Write(b) + } + }) + 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) + return } 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") - if container != "" { //commit - var config Config - if err := json.NewDecoder(r.Body).Decode(&config); 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 + } + fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") + if image != "" { //pull + if err := srv.ImagePull(image, file); err != nil { + fmt.Fprintln(file, "Error: "+err.Error()) } - author := r.Form.Get("author") - comment := r.Form.Get("comment") - - id, err := srv.ContainerCommit(container, repo, tag, author, comment, &config) - if err != nil { - httpError(w, err) + } else { //import + if err := srv.ImageImport(src, repo, tag, file); err != nil { + fmt.Fprintln(file, "Error: "+err.Error()) } - b, err := json.Marshal(ApiId{id}) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } else { - w.Write(b) - } - } 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") - - 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) } }) @@ -364,6 +387,7 @@ func ListenAndServe(addr string, srv *Server) error { if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } else { + w.Header().Set("Content-Type", "application/json") w.Write(b) } }) @@ -372,6 +396,7 @@ func ListenAndServe(addr string, srv *Server) error { log.Println(r.Method, r.RequestURI) if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) + return } t, err := strconv.Atoi(r.Form.Get("t")) if err != nil || t < 0 { @@ -390,6 +415,7 @@ func ListenAndServe(addr string, srv *Server) error { log.Println(r.Method, r.RequestURI) if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) + return } vars := mux.Vars(r) name := vars["name"] @@ -430,6 +456,7 @@ func ListenAndServe(addr string, srv *Server) error { log.Println(r.Method, r.RequestURI) if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) + return } t, err := strconv.Atoi(r.Form.Get("t")) if err != nil || t < 0 { @@ -452,11 +479,13 @@ func ListenAndServe(addr string, srv *Server) error { status, err := srv.ContainerWait(name) if err != nil { httpError(w, err) + return } b, err := json.Marshal(ApiWait{status}) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } else { + w.Header().Set("Content-Type", "application/json") w.Write(b) } }) @@ -465,6 +494,7 @@ func ListenAndServe(addr string, srv *Server) error { log.Println(r.Method, r.RequestURI) if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) + return } logs := r.Form.Get("logs") stream := r.Form.Get("stream") @@ -500,11 +530,13 @@ func ListenAndServe(addr string, srv *Server) error { container, err := srv.ContainerInspect(name) if err != nil { httpError(w, err) + return } b, err := json.Marshal(container) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } else { + w.Header().Set("Content-Type", "application/json") w.Write(b) } }) @@ -517,11 +549,13 @@ func ListenAndServe(addr string, srv *Server) error { image, err := srv.ImageInspect(name) if err != nil { httpError(w, err) + return } b, err := json.Marshal(image) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } else { + w.Header().Set("Content-Type", "application/json") w.Write(b) } }) diff --git a/commands.go b/commands.go index 3103f5276..510ab849c 100644 --- a/commands.go +++ b/commands.go @@ -541,7 +541,6 @@ func CmdKill(args ...string) error { return nil } -/* /!\ W.I.P /!\ */ func CmdImport(args ...string) error { cmd := Subcmd("import", "URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball") @@ -614,7 +613,7 @@ func CmdPush(args ...string) error { out.Username, name) } - if err := hijack("POST", "/images"+name+"/pull", false); err != nil { + if err := hijack("POST", "/images"+name+"/push", false); err != nil { return err } return nil @@ -764,7 +763,7 @@ func CmdCommit(args ...string) error { } v := url.Values{} - v.Set("fromContainer", name) + v.Set("container", name) v.Set("repo", repository) v.Set("tag", tag) v.Set("comment", *flComment) @@ -777,7 +776,7 @@ func CmdCommit(args ...string) error { } } - body, _, err := call("POST", "/images?"+v.Encode(), config) + body, _, err := call("POST", "/commit?"+v.Encode(), config) if err != nil { return err } @@ -1072,6 +1071,7 @@ func call(method, path string, data interface{}) ([]byte, int, error) { } params = bytes.NewBuffer(buf) } + req, err := http.NewRequest(method, "http://0.0.0.0:4243"+path, params) if err != nil { return nil, -1, err diff --git a/docs/sources/conf.py b/docs/sources/conf.py index c05c7d10a..4c54d8bb6 100644 --- a/docs/sources/conf.py +++ b/docs/sources/conf.py @@ -25,7 +25,7 @@ import sys, os # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [] +extensions = ['sphinxcontrib.httpdomain'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/docs/sources/index.rst b/docs/sources/index.rst index 4e724a0cd..bbd879334 100644 --- a/docs/sources/index.rst +++ b/docs/sources/index.rst @@ -16,7 +16,8 @@ This documentation has the following resources: contributing/index commandline/index registry/index + remote-api/index faq -.. image:: http://www.docker.io/_static/lego_docker.jpg \ No newline at end of file +.. image:: http://www.docker.io/_static/lego_docker.jpg diff --git a/docs/sources/remote-api/api.rst b/docs/sources/remote-api/api.rst new file mode 100644 index 000000000..361289010 --- /dev/null +++ b/docs/sources/remote-api/api.rst @@ -0,0 +1,878 @@ +================= +Docker Remote API +================= + +.. contents:: Table of Contents + +1. Brief introduction +===================== + +- The Remote API is replacing rcli +- Default port in the docker deamon is 4243 +- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection in hijacked to transport stdout stdin and stderr + +2. Endpoints +============ + +2.1 Containers +-------------- + +List containers +*************** + +.. http:get:: /containers + + List containers + + **Example request**: + + .. sourcecode:: http + + GET /containers?notrunc=1&all=1&quiet=0 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + [ + { + "Id": "8dfafdbc3a40", + "Image": "base:latest", + "Command": "echo 1", + "Created": 1367854155, + "Status": "Exit 0" + }, + { + "Id": "9cd87474be90", + "Image": "base:latest", + "Command": "echo 2", + "Created": 1367854155, + "Status": "Exit 0" + }, + { + "Id": "3176a2479c92", + "Image": "base:latest", + "Command": "echo 3", + "Created": 1367854154, + "Status": "Exit 0" + }, + { + "Id": "4cb07b47f9fb", + "Image": "base:latest", + "Command": "echo 4", + "Created": 1367854152, + "Status": "Exit 0" + } + ] + + :query quiet: 1 or 0, Only display numeric IDs. Not quiet by default + :query all: 1 or 0, Show all containers. Only running containers are shown by default + :query notrunc: 1 or 0, Don't truncate output. Output is truncated by default + :query n: limit number, Show n last created containers, include non-running ones. + :statuscode 200: no error + :statuscode 500: server error + + +Create a container +****************** + +.. http:post:: /containers + + Create a container + + **Example request**: + + .. sourcecode:: http + + POST /containers HTTP/1.1 + + { + "Hostname":"", + "User":"", + "Memory":0, + "MemorySwap":0, + "AttachStdin":false, + "AttachStdout":true, + "AttachStderr":true, + "PortSpecs":null, + "Tty":false, + "OpenStdin":false, + "StdinOnce":false, + "Env":null, + "Cmd":[ + "date" + ], + "Dns":null, + "Image":"base", + "Volumes":{}, + "VolumesFrom":"" + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + { + "Id":"e90e34656806" + "Warnings":[] + } + + :jsonparam config: the container's configuration + :statuscode 200: no error + :statuscode 400: no such container + :statuscode 500: server error + + +Inspect a container +******************* + +.. http:get:: /containers/(id) + + Return low-level information on the container ``id`` + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + { + "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2", + "Created": "2013-05-07T14:51:42.041847+02:00", + "Path": "date", + "Args": [], + "Config": { + "Hostname": "4fa6e0f0c678", + "User": "", + "Memory": 0, + "MemorySwap": 0, + "AttachStdin": false, + "AttachStdout": true, + "AttachStderr": true, + "PortSpecs": null, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": null, + "Cmd": [ + "date" + ], + "Dns": null, + "Image": "base", + "Volumes": {}, + "VolumesFrom": "" + }, + "State": { + "Running": false, + "Pid": 0, + "ExitCode": 0, + "StartedAt": "2013-05-07T14:51:42.087658+02:01360", + "Ghost": false + }, + "Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "NetworkSettings": { + "IpAddress": "", + "IpPrefixLen": 0, + "Gateway": "", + "Bridge": "", + "PortMapping": null + }, + "SysInitPath": "/home/kitty/go/src/github.com/dotcloud/docker/bin/docker", + "ResolvConfPath": "/etc/resolv.conf", + "Volumes": {} + } + + :statuscode 200: no error + :statuscode 400: no such container + :statuscode 500: server error + + +Inspect changes on a container's filesystem +******************************************* + +.. http:get:: /containers/(id)/changes + + Inspect changes on container ``id`` 's filesystem + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/changes HTTP/1.1 + + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + [ + "C /dev", + "A /dev/kmsg" + ] + + :statuscode 200: no error + :statuscode 400: no such container + :statuscode 500: server error + + +Export a container +****************** + +.. http:get:: /containers/(id)/export + + Export the contents of container ``id`` + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/export HTTP/1.1 + + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: raw-stream-hijack + + {{ STREAM }} + + :statuscode 200: no error + :statuscode 400: no such container + :statuscode 500: server error + + +Map container's private ports +***************************** + +.. http:get:: /containers/(id)/port + + Map a private port of container ``id`` + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/port?port=80 HTTP/1.1 + + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + {"Port":"80"} + + :query port: the container private port you want to get + :statuscode 200: no error + :statuscode 400: no such container + :statuscode 500: server error + + +Start a container +***************** + +.. http:post:: /containers/(id)/start + + Start the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/e90e34656806/start HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :statuscode 200: no error + :statuscode 400: no such container + :statuscode 500: server error + + +Stop a contaier +*************** + +.. http:post:: /containers/(id)/stop + + Stop the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/e90e34656806/stop?t=5 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :query t: number of seconds to wait before killing the container + :statuscode 200: no error + :statuscode 400: no such container + :statuscode 500: server error + + +Restart a container +******************* + +.. http:post:: /containers/(id)/restart + + Restart the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/e90e34656806/restart?t=5 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :query t: number of seconds to wait before killing the container + :statuscode 200: no error + :statuscode 400: no such container + :statuscode 500: server error + + +Kill a container +**************** + +.. http:post:: /containers/(id)/kill + + Kill the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/e90e34656806/kill HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :statuscode 200: no error + :statuscode 400: no such container + :statuscode 500: server error + + +Attach to a container +********************* + +.. http:post:: /containers/(id)/attach + + Stop the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/16253994b7c4/attach?logs=1&stream=0&stdout=1 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: raw-stream-hijack + + {{ STREAM }} + + :query logs: 1 or 0, return logs. Default 0 + :query stream: 1 or 0, return stream. Default 0 + :query stdin: 1 or 0, if stream=1, attach to stdin. Default 0 + :query stdout: 1 or 0, if logs=1, return stdout log, if stream=1, attach to stdout. Default 0 + :query stderr: 1 or 0, if logs=1, return stderr log, if stream=1, attach to stderr. Default 0 + :statuscode 200: no error + :statuscode 400: no such container + :statuscode 500: server error + + +Wait a container +**************** + +.. http:post:: /containers/(id)/wait + + Block until container ``id`` stops, then returns the exit code + + **Example request**: + + .. sourcecode:: http + + POST /containers/16253994b7c4/wait HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + {"StatusCode":0} + + :statuscode 200: no error + :statuscode 400: no such container + :statuscode 500: server error + + +Remove a container +******************* + +.. http:delete:: /container/(id) + + Remove the container ``id`` from the filesystem + + **Example request**: + + .. sourcecode:: http + + DELETE /containers/16253994b7c4?v=1 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :query v: 1 or 0, Remove the volumes associated to the container. Default 0 + :statuscode 200: no error + :statuscode 400: no such container + :statuscode 500: server error + + +2.2 Images +---------- + +List Images +*********** + +.. http:get:: /images + + List images + + **Example request**: + + .. sourcecode:: http + + GET /images?all=0&quiet=0 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + [ + { + "Repository":"base", + "Tag":"ubuntu-12.10", + "Id":"b750fe79269d", + "Created":1364102658 + }, + { + "Repository":"base", + "Tag":"ubuntu-quantal", + "Id":"b750fe79269d", + "Created":1364102658 + } + ] + + :query quiet: 1 or 0, Only display numeric IDs. Not quiet by default + :query all: 1 or 0, Show all containers. Only running containers are shown by default + :statuscode 200: no error + :statuscode 500: server error + + +Create an image +*************** + +.. http:post:: /images + + Create an image, either by pull it from the registry or by importing it + + **Example request**: + + .. sourcecode:: http + + POST /images?fromImage=base HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: raw-stream-hijack + + {{ STREAM }} + + :query fromImage: name of the image to pull + :query fromSrc: source to import, - means stdin + :query repo: repository + :query tag: tag + :statuscode 200: no error + :statuscode 500: server error + +Inspect an image +**************** + +.. http:get:: /images/(name) + + Return low-level information on the image ``name`` + + **Example request**: + + .. sourcecode:: http + + GET /images/base HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + { + "id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "parent":"27cf784147099545", + "created":"2013-03-23T22:24:18.818426-07:00", + "container":"3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0", + "container_config": + { + "Hostname":"", + "User":"", + "Memory":0, + "MemorySwap":0, + "AttachStdin":false, + "AttachStdout":false, + "AttachStderr":false, + "PortSpecs":null, + "Tty":true, + "OpenStdin":true, + "StdinOnce":false, + "Env":null, + "Cmd": ["/bin/bash"] + ,"Dns":null, + "Image":"base", + "Volumes":null, + "VolumesFrom":"" + } + } + + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 500: server error + + +Get the history of an image +*************************** + +.. http:get:: /images/(name) + + Return the history of the image ``name`` + + **Example request**: + + .. sourcecode:: http + + GET /images/base/history HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + [ + { + "Id":"b750fe79269d", + "Created":1364102658, + "CreatedBy":"/bin/bash" + }, + { + "Id":"27cf78414709", + "Created":1364068391, + "CreatedBy":"" + } + ] + + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 500: server error + + +Push an image on the registry +***************************** + +.. http:post:: /images/(name)/push + + Push the image ``name`` on the registry + + **Example request**: + + .. sourcecode:: http + + POST /images/test/push HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: raw-stream-hijack + + {{ STREAM }} + + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 500: server error + + +Tag an image into a repository +****************************** + +.. http:post:: /images/(name)/tag + + Tag the image ``name`` into a repository + + **Example request**: + + .. sourcecode:: http + + POST /images/test/tag?repo=myrepo&force=0 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :query repo: The repository to tag in + :query force: 1 or 0, default 0 + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 500: server error + + +Remove an image +*************** + +.. http:delete:: /images/(name) + + Remove the image ``name`` from the filesystem + + **Example request**: + + .. sourcecode:: http + + DELETE /images/test HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 500: server error + + +2.3 Misc +-------- + +Get default username and email +****************************** + +.. http:get:: /auth + + Get the default username and email + + **Example request**: + + .. sourcecode:: http + + GET /auth HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + { + "username":"hannibal", + "email":"hannibal@a-team.com" + } + + :statuscode 200: no error + :statuscode 500: server error + + +Set auth configuration +********************** + +.. http:post:: /auth + + Get the default username and email + + **Example request**: + + .. sourcecode:: http + + POST /auth HTTP/1.1 + + { + "username":"hannibal", + "password:"xxxx", + "email":"hannibal@a-team.com" + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :statuscode 200: no error + :statuscode 500: server error + + +Display system-wide information +******************************* + +.. http:get:: /info + + Display system-wide information + + **Example request**: + + .. sourcecode:: http + + GET /info HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + { + "Containers":11, + "Version":"0.2.2", + "Images":16, + "Debug":false + } + + :statuscode 200: no error + :statuscode 500: server error + +Show the docker version information +*********************************** + +.. http:get:: /version + + Show the docker version information + + **Example request**: + + .. sourcecode:: http + + GET /version HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + { + "Version":"0.2.2", + "GitCommit":"5a2a5cc+CHANGES", + "MemoryLimit":true, + "SwapLimit":false + } + + :statuscode 200: no error + :statuscode 500: server error + + +Create a new image from a container's changes +********************************************* + +.. http:post:: /commit + + Create a new image from a container's changes + + **Example request**: + + .. sourcecode:: http + + POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: raw-stream-hijack + + {{ STREAM }} + + :query container: source container + :query repo: repository + :query tag: tag + :query m: commit message + :query author: author (eg. "John Hannibal Smith ") + :query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]}) + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +3. Going further +================ + +3.1 Inside 'docker run' +----------------------- + +Here are the steps of 'docker run' : + +* Create the container +* If the status code is 404, it means the image doesn't exists: + * Try to pull it + * Then retry to create the container +* Start the container +* If you are not in detached mode: + * Attach to the container, using logs=1 (to have stdout and stderr from the container's start) and stream=1 + * Call /wait to get the exit code and exit with it + + +3.2 Hijacking +------------- + +In this first version of the API, some of the endpoints, like /attach, /pull or /push uses hijacking to transport stdin, +stdout and stderr on the same socket. This might change in the future. diff --git a/docs/sources/remote-api/index.rst b/docs/sources/remote-api/index.rst new file mode 100644 index 000000000..5b3b790b5 --- /dev/null +++ b/docs/sources/remote-api/index.rst @@ -0,0 +1,15 @@ +:title: docker Remote API documentation +:description: Documentation for docker Remote API +:keywords: docker, rest, api, http + + + +Remote API +========== + +Contents: + +.. toctree:: + :maxdepth: 2 + + api From 40794113750a3d21e784e2521ae517f23a081135 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 7 May 2013 20:59:04 +0200 Subject: [PATCH 24/69] fix run no parameter --- api.go | 18 +++++++++--------- api_params.go | 2 +- commands.go | 32 ++++++++++++++------------------ server.go | 12 ++++++------ 4 files changed, 30 insertions(+), 34 deletions(-) diff --git a/api.go b/api.go index 073a46952..91b61c82a 100644 --- a/api.go +++ b/api.go @@ -368,9 +368,9 @@ func ListenAndServe(addr string, srv *Server) error { r.Path("/images/search").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) - return - } + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } term := r.Form.Get("term") outs, err := srv.ImagesSearch(term) @@ -379,12 +379,12 @@ func ListenAndServe(addr string, srv *Server) error { return } b, err := json.Marshal(outs) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) - } + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } else { + w.Header().Set("Content-Type", "application/json") + w.Write(b) + } }) r.Path("/images/{name:*.}/insert").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/api_params.go b/api_params.go index d57637abd..c727b2b29 100644 --- a/api_params.go +++ b/api_params.go @@ -32,7 +32,7 @@ type ApiContainers struct { } type ApiSearch struct { - Name string + Name string Description string } diff --git a/commands.go b/commands.go index d693bfc85..2af25440d 100644 --- a/commands.go +++ b/commands.go @@ -53,7 +53,7 @@ func ParseCommands(args ...string) error { cmds := map[string]func(args ...string) error{ "attach": CmdAttach, - "build": CmdBuild, + "build": CmdBuild, "commit": CmdCommit, "diff": CmdDiff, "export": CmdExport, @@ -75,7 +75,7 @@ func ParseCommands(args ...string) error { "rmi": CmdRmi, "run": CmdRun, "tag": CmdTag, - "search": CmdSearch, + "search": CmdSearch, "start": CmdStart, "stop": CmdStop, "version": CmdVersion, @@ -960,19 +960,19 @@ func CmdSearch(args ...string) error { cmd.Usage() return nil } - - v := url.Values{} - v.Set("term", cmd.Arg(0)) - body, _, err := call("GET", "/images/search?"+v.Encode(), nil) - if err != nil { - return err - } - var outs []ApiSearch - err = json.Unmarshal(body, &outs) - if err != nil { - return err - } + v := url.Values{} + v.Set("term", cmd.Arg(0)) + body, _, err := call("GET", "/images/search?"+v.Encode(), nil) + if err != nil { + return err + } + + var outs []ApiSearch + err = json.Unmarshal(body, &outs) + if err != nil { + return err + } fmt.Printf("Found %d results matching your query (\"%s\")\n", len(outs), cmd.Arg(0)) w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) fmt.Fprintf(w, "NAME\tDESCRIPTION\n") @@ -1080,10 +1080,6 @@ func CmdRun(args ...string) error { cmd.Usage() return nil } - if len(config.Cmd) == 0 { - cmd.Usage() - return nil - } //create the container body, statusCode, err := call("POST", "/containers", *config) diff --git a/server.go b/server.go index 00d5c7349..df81706aa 100644 --- a/server.go +++ b/server.go @@ -46,16 +46,16 @@ func (srv *Server) ContainerExport(name string, file *os.File) error { func (srv *Server) ImagesSearch(term string) ([]ApiSearch, error) { results, err := srv.runtime.graph.SearchRepositories(nil, term) if err != nil { - return nil, err - } - + return nil, err + } + var outs []ApiSearch for _, repo := range results.Results { var out ApiSearch out.Description = repo["description"] - if len(out.Description) > 45 { - out.Description = Trunc(out.Description, 42) + "..." - } + if len(out.Description) > 45 { + out.Description = Trunc(out.Description, 42) + "..." + } out.Name = repo["name"] outs = append(outs, out) } From ac0e27699c0dd9306ae0edabeff5d462399c8e74 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 7 May 2013 21:36:24 +0200 Subject: [PATCH 25/69] display id on run -s stdin --- commands.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/commands.go b/commands.go index 2af25440d..1f4c3acfc 100644 --- a/commands.go +++ b/commands.go @@ -1132,6 +1132,7 @@ func CmdRun(args ...string) error { if err != nil { return err } + var status int if config.AttachStdin || config.AttachStdout || config.AttachStderr { if err := hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty); err != nil { return err @@ -1145,12 +1146,17 @@ func CmdRun(args ...string) error { if err != nil { return err } - os.Exit(out.StatusCode) + status = out.StatusCode } + } - } else { + if !config.AttachStdout && !config.AttachStderr { fmt.Println(out.Id) } + + if status != 0 { + os.Exit(status) + } return nil } From a5b765a769736171d05692aff3abdf3f02dfe5be Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 7 May 2013 22:15:21 +0200 Subject: [PATCH 26/69] remove useless wait in run --- commands.go | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/commands.go b/commands.go index 1f4c3acfc..3f151a9d5 100644 --- a/commands.go +++ b/commands.go @@ -1132,31 +1132,16 @@ func CmdRun(args ...string) error { if err != nil { return err } - var status int + if config.AttachStdin || config.AttachStdout || config.AttachStderr { 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 - } - status = out.StatusCode - } } if !config.AttachStdout && !config.AttachStderr { fmt.Println(out.Id) } - - if status != 0 { - os.Exit(status) - } return nil } @@ -1227,7 +1212,9 @@ func hijack(method, path string, setRawTerminal bool) error { sendStdin := Go(func() error { _, err := io.Copy(rwc, os.Stdin) - rwc.Close() + if err := rwc.(*net.TCPConn).CloseWrite(); err != nil { + fmt.Fprintf(os.Stderr, "Couldn't send EOF: " + err.Error()) + } return err }) From 4d30a32c68c199a1e10e68729feb519b74da2961 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 7 May 2013 23:15:42 +0200 Subject: [PATCH 27/69] removed RAW mode on server --- server.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/server.go b/server.go index df81706aa..a2525a9b9 100644 --- a/server.go +++ b/server.go @@ -530,16 +530,6 @@ func (srv *Server) ContainerAttach(name, logs, stream, stdin, stdout, stderr str 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 @@ -564,6 +554,12 @@ func (srv *Server) ContainerAttach(name, logs, stream, stdin, stdout, stderr str } <-container.Attach(cStdin, cStdinCloser, cStdout, cStderr) + + // If we are in stdinonce mode, wait for the process to end + // otherwise, simply return + if container.Config.StdinOnce && !container.Config.Tty { + container.Wait() + } } } else { return fmt.Errorf("No such container: %s", name) From a0880edc6337054725abc9e8338550bc5258f4ab Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 7 May 2013 23:56:45 +0200 Subject: [PATCH 28/69] removed useless buffered pipe in import --- commands.go | 2 +- server.go | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/commands.go b/commands.go index 3f151a9d5..3020459f0 100644 --- a/commands.go +++ b/commands.go @@ -1213,7 +1213,7 @@ func hijack(method, path string, setRawTerminal bool) error { sendStdin := Go(func() error { _, err := io.Copy(rwc, os.Stdin) if err := rwc.(*net.TCPConn).CloseWrite(); err != nil { - fmt.Fprintf(os.Stderr, "Couldn't send EOF: " + err.Error()) + fmt.Fprintf(os.Stderr, "Couldn't send EOF: "+err.Error()) } return err }) diff --git a/server.go b/server.go index a2525a9b9..788e6bdf0 100644 --- a/server.go +++ b/server.go @@ -338,13 +338,7 @@ func (srv *Server) ImageImport(src, repo, tag string, file *os.File) error { 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 + archive = file } else { u, err := url.Parse(src) if err != nil { From b56b2da5c5ac9c65c07d33844a4c2ceeb36bbf6a Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 7 May 2013 16:33:12 -0700 Subject: [PATCH 29/69] Refactor api.go to use a factory with named functions --- api.go | 1340 ++++++++++++++++++++++++++++----------------------- commands.go | 5 +- server.go | 8 +- 3 files changed, 733 insertions(+), 620 deletions(-) diff --git a/api.go b/api.go index 91b61c82a..2f00e300d 100644 --- a/api.go +++ b/api.go @@ -40,626 +40,736 @@ func httpError(w http.ResponseWriter, err error) { } } +func getAuth(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + var out auth.AuthConfig + out.Username = srv.runtime.authConfig.Username + out.Email = srv.runtime.authConfig.Email + b, err := json.Marshal(out) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } else { + w.Header().Set("Content-Type", "application/json") + w.Write(b) + } + return nil +} + +func postAuth(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + var config auth.AuthConfig + if err := json.NewDecoder(r.Body).Decode(&config); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } + + if config.Username == srv.runtime.authConfig.Username { + config.Password = srv.runtime.authConfig.Password + } + + newAuthConfig := auth.NewAuthConfig(config.Username, config.Password, config.Email, srv.runtime.root) + status, err := auth.Login(newAuthConfig) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } else { + srv.runtime.graph.getHttpClient().Jar = cookiejar.NewCookieJar() + srv.runtime.authConfig = newAuthConfig + } + if status != "" { + b, err := json.Marshal(ApiAuth{status}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } else { + w.Header().Set("Content-Type", "application/json") + w.Write(b) + } + } else { + w.WriteHeader(http.StatusOK) + } + return nil +} + +func getVersion(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + m := srv.DockerVersion() + b, err := json.Marshal(m) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } else { + w.Header().Set("Content-Type", "application/json") + w.Write(b) + } + return nil +} + +func postContainersKill(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + vars := mux.Vars(r) + name := vars["name"] + if err := srv.ContainerKill(name); err != nil { + httpError(w, err) + return err + } else { + w.WriteHeader(http.StatusOK) + } + return nil +} + +func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + vars := mux.Vars(r) + name := vars["name"] + + file, rwc, err := hijackServer(w) + if file != nil { + defer file.Close() + } + if rwc != nil { + defer rwc.Close() + } + if err != nil { + httpError(w, err) + return err + } + 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.Fprintf(file, "Error: %s\n", err) + return err + } + return nil +} + +func getImages(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } + + viz := r.Form.Get("viz") + if viz == "1" { + file, rwc, err := hijackServer(w) + if file != nil { + defer file.Close() + } + if rwc != nil { + defer rwc.Close() + } + if err != nil { + httpError(w, err) + return err + } + fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") + if err := srv.ImagesViz(file); err != nil { + fmt.Fprintf(file, "Error: %s\n", err) + } + return nil + } + + all := r.Form.Get("all") + filter := r.Form.Get("filter") + quiet := r.Form.Get("quiet") + + outs, err := srv.Images(all, filter, quiet) + if err != nil { + httpError(w, err) + return err + } + b, err := json.Marshal(outs) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } else { + w.Header().Set("Content-Type", "application/json") + w.Write(b) + } + return nil +} + +func getInfo(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + out := srv.DockerInfo() + b, err := json.Marshal(out) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } else { + w.Header().Set("Content-Type", "application/json") + w.Write(b) + } + return nil +} + +func getImagesHistory(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + vars := mux.Vars(r) + name := vars["name"] + outs, err := srv.ImageHistory(name) + if err != nil { + httpError(w, err) + return err + } + b, err := json.Marshal(outs) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } else { + w.Header().Set("Content-Type", "application/json") + w.Write(b) + } + return nil +} + +func getContainersChanges(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + vars := mux.Vars(r) + name := vars["name"] + changesStr, err := srv.ContainerChanges(name) + if err != nil { + httpError(w, err) + return err + } + b, err := json.Marshal(changesStr) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } else { + w.Header().Set("Content-Type", "application/json") + w.Write(b) + } + return nil +} + +func getContainersPort(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } + vars := mux.Vars(r) + name := vars["name"] + out, err := srv.ContainerPort(name, r.Form.Get("port")) + if err != nil { + httpError(w, err) + return err + } + b, err := json.Marshal(ApiPort{out}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } else { + w.Header().Set("Content-Type", "application/json") + w.Write(b) + } + return nil +} + +func getContainers(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } + 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 { + n = -1 + } + + outs := srv.Containers(all, notrunc, quiet, n) + b, err := json.Marshal(outs) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } else { + w.Header().Set("Content-Type", "application/json") + w.Write(b) + } + return nil +} + +func postImagesTag(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } + 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 := srv.ContainerTag(name, repo, tag, force); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } + w.WriteHeader(http.StatusCreated) + return nil +} + +func postCommit(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } + var config Config + if err := json.NewDecoder(r.Body).Decode(&config); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } + repo := r.Form.Get("repo") + tag := r.Form.Get("tag") + container := r.Form.Get("container") + author := r.Form.Get("author") + comment := r.Form.Get("comment") + id, err := srv.ContainerCommit(container, repo, tag, author, comment, &config) + if err != nil { + httpError(w, err) + return err + } + b, err := json.Marshal(ApiId{id}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } else { + w.Header().Set("Content-Type", "application/json") + w.Write(b) + } + return nil +} + +func postImages(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } + + src := r.Form.Get("fromSrc") + image := r.Form.Get("fromImage") + repo := r.Form.Get("repo") + tag := r.Form.Get("tag") + + file, rwc, err := hijackServer(w) + if file != nil { + defer file.Close() + } + if rwc != nil { + defer rwc.Close() + } + if err != nil { + httpError(w, err) + return err + } + fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") + if image != "" { //pull + registry := r.Form.Get("registry") + if err := srv.ImagePull(image, tag, registry, file); err != nil { + fmt.Fprintf(file, "Error: %s\n", err) + return err + } + } else { //import + if err := srv.ImageImport(src, repo, tag, file); err != nil { + fmt.Fprintf(file, "Error: %s\n", err) + return err + } + } + return nil +} + +func getImagesSearch(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } + + term := r.Form.Get("term") + outs, err := srv.ImagesSearch(term) + if err != nil { + httpError(w, err) + return err + } + b, err := json.Marshal(outs) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } else { + w.Header().Set("Content-Type", "application/json") + w.Write(b) + } + return nil +} + +func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } + + url := r.Form.Get("url") + path := r.Form.Get("path") + vars := mux.Vars(r) + name := vars["name"] + + file, rwc, err := hijackServer(w) + if file != nil { + defer file.Close() + } + if rwc != nil { + defer rwc.Close() + } + if err != nil { + httpError(w, err) + return err + } + fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") + if err := srv.ImageInsert(name, url, path, file); err != nil { + fmt.Fprintln(file, "Error: "+err.Error()) + } + return nil +} + +func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } + + registry := r.Form.Get("registry") + + vars := mux.Vars(r) + name := vars["name"] + + file, rwc, err := hijackServer(w) + if file != nil { + defer file.Close() + } + if rwc != nil { + defer rwc.Close() + } + if err != nil { + httpError(w, err) + return err + } + fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") + if err := srv.ImagePush(name, registry, file); err != nil { + fmt.Fprintln(file, "Error: "+err.Error()) + } + return nil +} + +func postBuild(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + + file, rwc, err := hijackServer(w) + if file != nil { + defer file.Close() + } + if rwc != nil { + defer rwc.Close() + } + if err != nil { + httpError(w, err) + return err + } + fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") + if err := srv.ImageCreateFormFile(file); err != nil { + fmt.Fprintln(file, "Error: "+err.Error()) + } + return nil +} + +func postContainers(srv *Server, w http.ResponseWriter, r *http.Request) error { + 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 err + } + id, memoryW, swapW, err := srv.ContainerCreate(config) + if err != nil { + httpError(w, err) + return err + } + var out ApiRun + 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) + return err + } else { + w.Header().Set("Content-Type", "application/json") + w.Write(b) + } + return nil +} + +func postContainersRestart(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } + t, err := strconv.Atoi(r.Form.Get("t")) + if err != nil || t < 0 { + t = 10 + } + vars := mux.Vars(r) + name := vars["name"] + if err := srv.ContainerRestart(name, t); err != nil { + httpError(w, err) + return err + } else { + w.WriteHeader(http.StatusOK) + } + return nil +} + +func deleteContainers(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } + vars := mux.Vars(r) + name := vars["name"] + var v bool + if r.Form.Get("v") == "1" { + v = true + } + if err := srv.ContainerDestroy(name, v); err != nil { + httpError(w, err) + return err + } else { + w.WriteHeader(http.StatusOK) + } + return nil +} + +func deleteImages(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + vars := mux.Vars(r) + name := vars["name"] + if err := srv.ImageDelete(name); err != nil { + httpError(w, err) + return err + } else { + w.WriteHeader(http.StatusOK) + } + return nil +} + +func postContainersStart(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + vars := mux.Vars(r) + name := vars["name"] + if err := srv.ContainerStart(name); err != nil { + httpError(w, err) + return err + } else { + w.WriteHeader(http.StatusOK) + } + return nil +} + +func postContainersStop(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } + t, err := strconv.Atoi(r.Form.Get("t")) + if err != nil || t < 0 { + t = 10 + } + vars := mux.Vars(r) + name := vars["name"] + + if err := srv.ContainerStop(name, t); err != nil { + httpError(w, err) + return err + } else { + w.WriteHeader(http.StatusOK) + } + return nil +} + +func postContainersWait(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + vars := mux.Vars(r) + name := vars["name"] + status, err := srv.ContainerWait(name) + if err != nil { + httpError(w, err) + return err + } + b, err := json.Marshal(ApiWait{status}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } else { + w.Header().Set("Content-Type", "application/json") + w.Write(b) + } + return nil +} + +func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } + logs := r.Form.Get("logs") + stream := r.Form.Get("stream") + stdin := r.Form.Get("stdin") + stdout := r.Form.Get("stdout") + stderr := r.Form.Get("stderr") + vars := mux.Vars(r) + name := vars["name"] + + file, rwc, err := hijackServer(w) + if file != nil { + defer file.Close() + } + if rwc != nil { + defer rwc.Close() + } + if err != nil { + httpError(w, err) + return err + } + + 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.Fprintf(file, "Error: %s\n", err) + return err + } + return nil +} + +func getContainersByName(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + vars := mux.Vars(r) + name := vars["name"] + + container, err := srv.ContainerInspect(name) + if err != nil { + httpError(w, err) + return err + } + b, err := json.Marshal(container) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } else { + w.Header().Set("Content-Type", "application/json") + w.Write(b) + } + return nil +} + +func getImagesByName(srv *Server, w http.ResponseWriter, r *http.Request) error { + log.Println(r.Method, r.RequestURI) + vars := mux.Vars(r) + name := vars["name"] + + image, err := srv.ImageInspect(name) + if err != nil { + httpError(w, err) + return err + } + b, err := json.Marshal(image) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } else { + w.Header().Set("Content-Type", "application/json") + w.Write(b) + } + return nil +} + +func wrap(fct func(*Server, http.ResponseWriter, *http.Request) error, w http.ResponseWriter, r *http.Request, srv *Server, method, route string) { + if err := fct(srv, w, r); err != nil { + Debugf("Error: %s %s: %s", method, route, err) + } +} + func ListenAndServe(addr string, srv *Server) error { r := mux.NewRouter() log.Printf("Listening for HTTP on %s\n", addr) - r.Path("/auth").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.Method, r.RequestURI) - var out auth.AuthConfig - out.Username = srv.runtime.authConfig.Username - out.Email = srv.runtime.authConfig.Email - b, err := json.Marshal(out) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) - } - }) + m := map[string]map[string]func(*Server, http.ResponseWriter, *http.Request) error{ + "GET": { + "/auth": getAuth, + "/version": getVersion, + "/containers/{name:.*}/export": getContainersExport, + "/images": getImages, + "/info": getInfo, + "/images/{name:.*}/history": getImagesHistory, + "/containers/{name:.*}/changes": getContainersChanges, + "/containers/{name:.*}/port": getContainersPort, + "/containers": getContainers, + "/images/search": getImagesSearch, + "/containers/{name:.*}": getContainersByName, + "/images/{name:.*}": getImagesByName, + }, + "POST": { + "/auth": postAuth, + "/containers/{name:.*}/kill": postContainersKill, + "/images/{name:.*}/tag": postImagesTag, + "/commit": postCommit, + "/images": postImages, + "/images/{name:*.}/insert": postImagesInsert, + "/images/{name:*.}/push": postImagesPush, + "/postBuild": postBuild, + "/postContainers": postContainers, + "/containers/{name:.*}/restart": postContainersRestart, + "/containers/{name:.*}/start": postContainersStart, + "/containers/{name:.*}/stop": postContainersStop, + "/containers/{name:.*}/wait": postContainersWait, + "/containers/{name:.*}/attach": postContainersAttach, + }, + "DELETE": { + "/containers/{name:.*}": deleteContainers, + "/images/{name:.*}": deleteImages, + }, + } - r.Path("/auth").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.Method, r.RequestURI) - var config auth.AuthConfig - if err := json.NewDecoder(r.Body).Decode(&config); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - if config.Username == srv.runtime.authConfig.Username { - config.Password = srv.runtime.authConfig.Password - } - - newAuthConfig := auth.NewAuthConfig(config.Username, config.Password, config.Email, srv.runtime.root) - status, err := auth.Login(newAuthConfig) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } else { - srv.runtime.graph.getHttpClient().Jar = cookiejar.NewCookieJar() - srv.runtime.authConfig = newAuthConfig - } - if status != "" { - b, err := json.Marshal(ApiAuth{status}) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) - } - } else { - w.WriteHeader(http.StatusOK) - } - }) - - r.Path("/version").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.Method, r.RequestURI) - m := srv.DockerVersion() - b, err := json.Marshal(m) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) - } - }) - - r.Path("/containers/{name:.*}/kill").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.Method, r.RequestURI) - vars := mux.Vars(r) - name := vars["name"] - if err := srv.ContainerKill(name); err != nil { - httpError(w, err) - } else { - w.WriteHeader(http.StatusOK) - } - }) - - r.Path("/containers/{name:.*}/export").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.Method, r.RequestURI) - vars := mux.Vars(r) - name := vars["name"] - - 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") - 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) { - log.Println(r.Method, r.RequestURI) - if err := r.ParseForm(); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - viz := r.Form.Get("viz") - if viz == "1" { - 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") - if err := srv.ImagesViz(file); err != nil { - fmt.Fprintln(file, "Error: "+err.Error()) - } - return - } - - all := r.Form.Get("all") - filter := r.Form.Get("filter") - quiet := r.Form.Get("quiet") - - outs, err := srv.Images(all, filter, quiet) - if err != nil { - httpError(w, err) - return - } - b, err := json.Marshal(outs) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) - } - }) - - r.Path("/info").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.Method, r.RequestURI) - out := srv.DockerInfo() - b, err := json.Marshal(out) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) - } - }) - - r.Path("/images/{name:.*}/history").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.Method, r.RequestURI) - vars := mux.Vars(r) - name := vars["name"] - outs, err := srv.ImageHistory(name) - if err != nil { - httpError(w, err) - return - } - b, err := json.Marshal(outs) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) - } - }) - - r.Path("/containers/{name:.*}/changes").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.Method, r.RequestURI) - vars := mux.Vars(r) - name := vars["name"] - changesStr, err := srv.ContainerChanges(name) - if err != nil { - httpError(w, err) - return - } - b, err := json.Marshal(changesStr) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) - } - }) - - 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) - return - } - vars := mux.Vars(r) - name := vars["name"] - out, err := srv.ContainerPort(name, r.Form.Get("port")) - if err != nil { - httpError(w, err) - return - } - b, err := json.Marshal(ApiPort{out}) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } else { - w.Header().Set("Content-Type", "application/json") - 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) - return - } - 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 { - n = -1 - } - - outs := srv.Containers(all, notrunc, quiet, n) - b, err := json.Marshal(outs) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) - } - }) - - 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) - return - } - 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 := srv.ContainerTag(name, repo, tag, force); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - w.WriteHeader(http.StatusCreated) - }) - - r.Path("/commit").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) - return - } - var config Config - if err := json.NewDecoder(r.Body).Decode(&config); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - repo := r.Form.Get("repo") - tag := r.Form.Get("tag") - container := r.Form.Get("container") - author := r.Form.Get("author") - comment := r.Form.Get("comment") - id, err := srv.ContainerCommit(container, repo, tag, author, comment, &config) - if err != nil { - httpError(w, err) - return - } - b, err := json.Marshal(ApiId{id}) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) - } - }) - - 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) - return - } - - src := r.Form.Get("fromSrc") - image := r.Form.Get("fromImage") - repo := r.Form.Get("repo") - tag := r.Form.Get("tag") - - 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") - if image != "" { //pull - registry := r.Form.Get("registry") - if err := srv.ImagePull(image, tag, registry, 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()) - } - } - }) - - r.Path("/images/search").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) - return - } - - term := r.Form.Get("term") - outs, err := srv.ImagesSearch(term) - if err != nil { - httpError(w, err) - return - } - b, err := json.Marshal(outs) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) - } - }) - - r.Path("/images/{name:*.}/insert").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) - return - } - - url := r.Form.Get("url") - path := r.Form.Get("path") - vars := mux.Vars(r) - name := vars["name"] - - 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") - if err := srv.ImageInsert(name, url, path, file); err != nil { - fmt.Fprintln(file, "Error: "+err.Error()) - } - }) - r.Path("/images/{name:*.}/push").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) - return - } - - registry := r.Form.Get("registry") - - vars := mux.Vars(r) - name := vars["name"] - - 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") - if err := srv.ImagePush(name, registry, file); err != nil { - fmt.Fprintln(file, "Error: "+err.Error()) - } - }) - - r.Path("/build").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.Method, r.RequestURI) - - 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") - if err := srv.ImageCreateFormFile(file); 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 - } - id, memoryW, swapW, err := srv.ContainerCreate(config) - if err != nil { - httpError(w, err) - return - } - var out ApiRun - 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) - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) - } - }) - - r.Path("/containers/{name:.*}/restart").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) - return - } - t, err := strconv.Atoi(r.Form.Get("t")) - if err != nil || t < 0 { - t = 10 - } - vars := mux.Vars(r) - name := vars["name"] - if err := srv.ContainerRestart(name, t); err != nil { - httpError(w, err) - } else { - w.WriteHeader(http.StatusOK) - } - }) - - r.Path("/containers/{name:.*}").Methods("DELETE").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) - return - } - vars := mux.Vars(r) - name := vars["name"] - var v bool - if r.Form.Get("v") == "1" { - v = true - } - if err := srv.ContainerDestroy(name, v); err != nil { - httpError(w, err) - } else { - 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"] - if err := srv.ImageDelete(name); err != nil { - httpError(w, err) - } else { - 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 err := srv.ContainerStart(name); err != nil { - httpError(w, err) - } else { - w.WriteHeader(http.StatusOK) - } - }) - - r.Path("/containers/{name:.*}/stop").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) - return - } - t, err := strconv.Atoi(r.Form.Get("t")) - if err != nil || t < 0 { - t = 10 - } - vars := mux.Vars(r) - name := vars["name"] - - if err := srv.ContainerStop(name, t); err != nil { - httpError(w, err) - } else { - 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"] - status, err := srv.ContainerWait(name) - if err != nil { - httpError(w, err) - return - } - b, err := json.Marshal(ApiWait{status}) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) - } - }) - - r.Path("/containers/{name:.*}/attach").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) - return - } - logs := r.Form.Get("logs") - stream := r.Form.Get("stream") - stdin := r.Form.Get("stdin") - stdout := r.Form.Get("stdout") - stderr := r.Form.Get("stderr") - vars := mux.Vars(r) - name := vars["name"] - - 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") - if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, file); err != nil { - fmt.Fprintln(file, "Error: "+err.Error()) - } - }) - - r.Path("/containers/{name:.*}").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.Method, r.RequestURI) - vars := mux.Vars(r) - name := vars["name"] - - container, err := srv.ContainerInspect(name) - if err != nil { - httpError(w, err) - return - } - b, err := json.Marshal(container) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) - } - }) - - r.Path("/images/{name:.*}").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.Method, r.RequestURI) - vars := mux.Vars(r) - name := vars["name"] - - image, err := srv.ImageInspect(name) - if err != nil { - httpError(w, err) - return - } - b, err := json.Marshal(image) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) - } - }) + for method, routes := range m { + for route, fct := range routes { + Debugf("Registering %s, %s", method, route) + // NOTE: scope issue, make sure the variables are local and won't be changed + localRoute := route + localMethod := method + localFct := fct + r.Path(localRoute).Methods(localMethod).HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + Debugf("Calling %s %s", localMethod, localRoute) + localFct(srv, w, r) + }) + } + } return http.ListenAndServe(addr, r) } diff --git a/commands.go b/commands.go index 3020459f0..2ada46ff9 100644 --- a/commands.go +++ b/commands.go @@ -35,8 +35,10 @@ func checkRemoteVersion() error { } var out ApiVersion + err = json.Unmarshal(body, &out) if err != nil { + Debugf("Error unmarshal: body: %s, err: %s\n", body, err) return err } if out.Version != VERSION { @@ -323,6 +325,7 @@ func CmdVersion(args ...string) error { var out ApiVersion err = json.Unmarshal(body, &out) if err != nil { + Debugf("Error unmarshal: body: %s, err: %s\n", body, err) return err } fmt.Println("Version:", out.Version) @@ -1213,7 +1216,7 @@ func hijack(method, path string, setRawTerminal bool) error { sendStdin := Go(func() error { _, err := io.Copy(rwc, os.Stdin) if err := rwc.(*net.TCPConn).CloseWrite(); err != nil { - fmt.Fprintf(os.Stderr, "Couldn't send EOF: "+err.Error()) + fmt.Fprintf(os.Stderr, "Couldn't send EOF: %s\n", err) } return err }) diff --git a/server.go b/server.go index 788e6bdf0..b22e49440 100644 --- a/server.go +++ b/server.go @@ -316,7 +316,7 @@ func (srv *Server) ImagePush(name, registry string, file *os.File) error { img, err := srv.runtime.graph.Get(name) if err != nil { Debugf("The push refers to a repository [%s] (len: %d)\n", name, len(srv.runtime.repositories.Repositories[name])) - // If it fails, try to get the repository + // If it fails, try to get the repository if localRepo, exists := srv.runtime.repositories.Repositories[name]; exists { if err := srv.runtime.graph.PushRepository(file, name, localRepo, srv.runtime.authConfig); err != nil { return err @@ -350,8 +350,8 @@ func (srv *Server) ImageImport(src, repo, tag string, file *os.File) error { u.Path = "" } fmt.Fprintln(file, "Downloading from", u) - // Download with curl (pretty progress bar) - // If curl is not available, fallback to http.Get() + // 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 @@ -362,7 +362,7 @@ func (srv *Server) ImageImport(src, repo, tag string, file *os.File) error { if err != nil { return err } - // Optionally register the image at REPO/TAG + // Optionally register the image at REPO/TAG if repo != "" { if err := srv.runtime.repositories.Set(repo, tag, img.Id, true); err != nil { return err From 279db68b4600551706dd1185ad1b05b374e9e05c Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 7 May 2013 16:36:49 -0700 Subject: [PATCH 30/69] Use Fprintf instead of Fprintln --- api.go | 9 ++++++--- server.go | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/api.go b/api.go index 2f00e300d..23ccd403b 100644 --- a/api.go +++ b/api.go @@ -437,7 +437,8 @@ func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request) error } fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") if err := srv.ImageInsert(name, url, path, file); err != nil { - fmt.Fprintln(file, "Error: "+err.Error()) + fmt.Fprintf(file, "Error: %s\n", err) + return err } return nil } @@ -467,7 +468,8 @@ func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request) error { } fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") if err := srv.ImagePush(name, registry, file); err != nil { - fmt.Fprintln(file, "Error: "+err.Error()) + fmt.Fprintln(file, "Error: $s\n", err) + return err } return nil } @@ -488,7 +490,8 @@ func postBuild(srv *Server, w http.ResponseWriter, r *http.Request) error { } fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") if err := srv.ImageCreateFormFile(file); err != nil { - fmt.Fprintln(file, "Error: "+err.Error()) + fmt.Fprintln(file, "Error: %s\n", err) + return err } return nil } diff --git a/server.go b/server.go index b22e49440..7399a399d 100644 --- a/server.go +++ b/server.go @@ -342,7 +342,7 @@ func (srv *Server) ImageImport(src, repo, tag string, file *os.File) error { } else { u, err := url.Parse(src) if err != nil { - fmt.Fprintln(file, "Error: "+err.Error()) + fmt.Fprintf(file, "Error: %s\n", err) } if u.Scheme == "" { u.Scheme = "http" From ab96da8eb2c81a90e5c081da26ee49c3af2f1ca5 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 7 May 2013 16:47:43 -0700 Subject: [PATCH 31/69] Use bool instead of string for flags --- api.go | 37 ++++++++++++++++--------------------- server.go | 34 +++++++++++++++++----------------- 2 files changed, 33 insertions(+), 38 deletions(-) diff --git a/api.go b/api.go index 23ccd403b..8eb160a14 100644 --- a/api.go +++ b/api.go @@ -150,8 +150,8 @@ func getImages(srv *Server, w http.ResponseWriter, r *http.Request) error { return err } - viz := r.Form.Get("viz") - if viz == "1" { + viz := r.Form.Get("viz") == "1" + if viz { file, rwc, err := hijackServer(w) if file != nil { defer file.Close() @@ -170,11 +170,11 @@ func getImages(srv *Server, w http.ResponseWriter, r *http.Request) error { return nil } - all := r.Form.Get("all") + all := r.Form.Get("all") == "1" filter := r.Form.Get("filter") - quiet := r.Form.Get("quiet") + quiet := r.Form.Get("quiet") == "1" - outs, err := srv.Images(all, filter, quiet) + outs, err := srv.Images(all, quiet, filter) if err != nil { httpError(w, err) return err @@ -274,9 +274,9 @@ func getContainers(srv *Server, w http.ResponseWriter, r *http.Request) error { http.Error(w, err.Error(), http.StatusInternalServerError) return err } - all := r.Form.Get("all") - notrunc := r.Form.Get("notrunc") - quiet := r.Form.Get("quiet") + all := r.Form.Get("all") == "1" + notrunc := r.Form.Get("notrunc") == "1" + quiet := r.Form.Get("quiet") == "1" n, err := strconv.Atoi(r.Form.Get("n")) if err != nil { n = -1 @@ -304,10 +304,7 @@ func postImagesTag(srv *Server, w http.ResponseWriter, r *http.Request) error { tag := r.Form.Get("tag") vars := mux.Vars(r) name := vars["name"] - var force bool - if r.Form.Get("force") == "1" { - force = true - } + force := r.Form.Get("force") == "1" if err := srv.ContainerTag(name, repo, tag, force); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -556,10 +553,8 @@ func deleteContainers(srv *Server, w http.ResponseWriter, r *http.Request) error } vars := mux.Vars(r) name := vars["name"] - var v bool - if r.Form.Get("v") == "1" { - v = true - } + v := r.Form.Get("v") == "1" + if err := srv.ContainerDestroy(name, v); err != nil { httpError(w, err) return err @@ -643,11 +638,11 @@ func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request) e http.Error(w, err.Error(), http.StatusInternalServerError) return err } - logs := r.Form.Get("logs") - stream := r.Form.Get("stream") - stdin := r.Form.Get("stdin") - stdout := r.Form.Get("stdout") - stderr := r.Form.Get("stderr") + logs := r.Form.Get("logs") == "1" + stream := r.Form.Get("stream") == "1" + stdin := r.Form.Get("stdin") == "1" + stdout := r.Form.Get("stdout") == "1" + stderr := r.Form.Get("stderr") == "1" vars := mux.Vars(r) name := vars["name"] diff --git a/server.go b/server.go index 7399a399d..1d5eaa850 100644 --- a/server.go +++ b/server.go @@ -136,10 +136,10 @@ func (srv *Server) ImagesViz(file *os.File) error { return nil } -func (srv *Server) Images(all, filter, quiet string) ([]ApiImages, error) { +func (srv *Server) Images(all, quiet bool, filter string) ([]ApiImages, error) { var allImages map[string]*Image var err error - if all == "1" { + if all { allImages, err = srv.runtime.graph.Map() } else { allImages, err = srv.runtime.graph.Heads() @@ -160,7 +160,7 @@ func (srv *Server) Images(all, filter, quiet string) ([]ApiImages, error) { continue } delete(allImages, id) - if quiet != "1" { + if !quiet { out.Repository = name out.Tag = tag out.Id = TruncateId(id) @@ -175,7 +175,7 @@ func (srv *Server) Images(all, filter, quiet string) ([]ApiImages, error) { if filter == "" { for id, image := range allImages { var out ApiImages - if quiet != "1" { + if !quiet { out.Repository = "" out.Tag = "" out.Id = TruncateId(id) @@ -201,7 +201,7 @@ func (srv *Server) DockerInfo() ApiInfo { out.Containers = len(srv.runtime.List()) out.Version = VERSION out.Images = imgcount - if os.Getenv("DEBUG") == "1" { + if os.Getenv("DEBUG") != "" { out.Debug = true out.NFd = getTotalUsedFds() out.NGoroutines = runtime.NumGoroutine() @@ -253,10 +253,10 @@ func (srv *Server) ContainerPort(name, privatePort string) (string, error) { return "", fmt.Errorf("No such container: %s", name) } -func (srv *Server) Containers(all, notrunc, quiet string, n int) []ApiContainers { +func (srv *Server) Containers(all, notrunc, quiet bool, 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 { + if !container.State.Running && !all && n == -1 { continue } if i == n { @@ -264,9 +264,9 @@ func (srv *Server) Containers(all, notrunc, quiet string, n int) []ApiContainers } var out ApiContainers out.Id = container.ShortId() - if quiet != "1" { + if !quiet { command := fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")) - if notrunc != "1" { + if !notrunc { command = Trunc(command, 20) } out.Image = srv.runtime.repositories.ImageName(container.Image) @@ -496,11 +496,11 @@ func (srv *Server) ContainerWait(name string) (int, error) { return 0, fmt.Errorf("No such container: %s", name) } -func (srv *Server) ContainerAttach(name, logs, stream, stdin, stdout, stderr string, file *os.File) error { +func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, stderr bool, file *os.File) error { if container := srv.runtime.Get(name); container != nil { //logs - if logs == "1" { - if stdout == "1" { + if logs { + if stdout { cLog, err := container.ReadLog("stdout") if err != nil { Debugf(err.Error()) @@ -508,7 +508,7 @@ func (srv *Server) ContainerAttach(name, logs, stream, stdin, stdout, stderr str Debugf(err.Error()) } } - if stderr == "1" { + if stderr { cLog, err := container.ReadLog("stderr") if err != nil { Debugf(err.Error()) @@ -519,7 +519,7 @@ func (srv *Server) ContainerAttach(name, logs, stream, stdin, stdout, stderr str } //stream - if stream == "1" { + if stream { if container.State.Ghost { return fmt.Errorf("Impossible to attach to a ghost container") } @@ -530,7 +530,7 @@ func (srv *Server) ContainerAttach(name, logs, stream, stdin, stdout, stderr str cStdinCloser io.Closer ) - if stdin == "1" { + if stdin { r, w := io.Pipe() go func() { defer w.Close() @@ -540,10 +540,10 @@ func (srv *Server) ContainerAttach(name, logs, stream, stdin, stdout, stderr str cStdin = r cStdinCloser = file } - if stdout == "1" { + if stdout { cStdout = file } - if stderr == "1" { + if stderr { cStderr = file } From 891c5202ead662d9c8a3147f691e719106e0f20a Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 7 May 2013 17:27:09 -0700 Subject: [PATCH 32/69] Factorize api.go --- api.go | 381 ++++++++++++++++++++------------------------------------- 1 file changed, 131 insertions(+), 250 deletions(-) diff --git a/api.go b/api.go index 8eb160a14..ea66fce9c 100644 --- a/api.go +++ b/api.go @@ -40,28 +40,22 @@ func httpError(w http.ResponseWriter, err error) { } } -func getAuth(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func getAuth(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { var out auth.AuthConfig out.Username = srv.runtime.authConfig.Username out.Email = srv.runtime.authConfig.Email b, err := json.Marshal(out) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) + return nil, err } - return nil + return b, nil } -func postAuth(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func postAuth(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { var config auth.AuthConfig if err := json.NewDecoder(r.Body).Decode(&config); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) - return err + return nil, err } if config.Username == srv.runtime.authConfig.Username { @@ -71,8 +65,7 @@ func postAuth(srv *Server, w http.ResponseWriter, r *http.Request) error { newAuthConfig := auth.NewAuthConfig(config.Username, config.Password, config.Email, srv.runtime.root) status, err := auth.Login(newAuthConfig) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err + return nil, err } else { srv.runtime.graph.getHttpClient().Jar = cookiejar.NewCookieJar() srv.runtime.authConfig = newAuthConfig @@ -80,47 +73,36 @@ func postAuth(srv *Server, w http.ResponseWriter, r *http.Request) error { if status != "" { b, err := json.Marshal(ApiAuth{status}) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) + return nil, err + return b, nil } } else { w.WriteHeader(http.StatusOK) } - return nil + return nil, nil } -func getVersion(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func getVersion(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { m := srv.DockerVersion() b, err := json.Marshal(m) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) + return nil, err } - return nil + return b, nil } -func postContainersKill(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func postContainersKill(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { vars := mux.Vars(r) name := vars["name"] if err := srv.ContainerKill(name); err != nil { - httpError(w, err) - return err + return nil, err } else { w.WriteHeader(http.StatusOK) } - return nil + return nil, nil } -func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { vars := mux.Vars(r) name := vars["name"] @@ -132,22 +114,19 @@ func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request) er defer rwc.Close() } if err != nil { - httpError(w, err) - return err + return nil, err } 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.Fprintf(file, "Error: %s\n", err) - return err + return nil, err } - return nil + return nil, nil } -func getImages(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func getImages(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { if err := r.ParseForm(); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err + return nil, err } viz := r.Form.Get("viz") == "1" @@ -160,14 +139,13 @@ func getImages(srv *Server, w http.ResponseWriter, r *http.Request) error { defer rwc.Close() } if err != nil { - httpError(w, err) - return err + return nil, err } fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") if err := srv.ImagesViz(file); err != nil { fmt.Fprintf(file, "Error: %s\n", err) } - return nil + return nil, nil } all := r.Form.Get("all") == "1" @@ -176,103 +154,72 @@ func getImages(srv *Server, w http.ResponseWriter, r *http.Request) error { outs, err := srv.Images(all, quiet, filter) if err != nil { - httpError(w, err) - return err + return nil, err } b, err := json.Marshal(outs) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) + return nil, err } - return nil + return b, nil } -func getInfo(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func getInfo(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { out := srv.DockerInfo() b, err := json.Marshal(out) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) + return nil, err } - return nil + return b, nil } -func getImagesHistory(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func getImagesHistory(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { vars := mux.Vars(r) name := vars["name"] outs, err := srv.ImageHistory(name) if err != nil { - httpError(w, err) - return err + return nil, err } b, err := json.Marshal(outs) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) + return nil, err } - return nil + return b, nil } -func getContainersChanges(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func getContainersChanges(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { vars := mux.Vars(r) name := vars["name"] changesStr, err := srv.ContainerChanges(name) if err != nil { - httpError(w, err) - return err + return nil, err } b, err := json.Marshal(changesStr) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) + return nil, err } - return nil + return b, nil } -func getContainersPort(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func getContainersPort(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { if err := r.ParseForm(); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err + return nil, err } vars := mux.Vars(r) name := vars["name"] out, err := srv.ContainerPort(name, r.Form.Get("port")) if err != nil { - httpError(w, err) - return err + return nil, err } b, err := json.Marshal(ApiPort{out}) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) + return nil, err } - return nil + return b, nil } -func getContainers(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func getContainers(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { if err := r.ParseForm(); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err + return nil, err } all := r.Form.Get("all") == "1" notrunc := r.Form.Get("notrunc") == "1" @@ -285,20 +232,14 @@ func getContainers(srv *Server, w http.ResponseWriter, r *http.Request) error { outs := srv.Containers(all, notrunc, quiet, n) b, err := json.Marshal(outs) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) + return nil, err } - return nil + return b, nil } -func postImagesTag(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func postImagesTag(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { if err := r.ParseForm(); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err + return nil, err } repo := r.Form.Get("repo") tag := r.Form.Get("tag") @@ -307,23 +248,19 @@ func postImagesTag(srv *Server, w http.ResponseWriter, r *http.Request) error { force := r.Form.Get("force") == "1" if err := srv.ContainerTag(name, repo, tag, force); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err + return nil, err } w.WriteHeader(http.StatusCreated) - return nil + return nil, nil } -func postCommit(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func postCommit(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { if err := r.ParseForm(); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err + return nil, err } var config Config if err := json.NewDecoder(r.Body).Decode(&config); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err + return nil, err } repo := r.Form.Get("repo") tag := r.Form.Get("tag") @@ -332,25 +269,18 @@ func postCommit(srv *Server, w http.ResponseWriter, r *http.Request) error { comment := r.Form.Get("comment") id, err := srv.ContainerCommit(container, repo, tag, author, comment, &config) if err != nil { - httpError(w, err) - return err + return nil, err } b, err := json.Marshal(ApiId{id}) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) + return nil, err } - return nil + return b, nil } -func postImages(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func postImages(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { if err := r.ParseForm(); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err + return nil, err } src := r.Form.Get("fromSrc") @@ -366,54 +296,44 @@ func postImages(srv *Server, w http.ResponseWriter, r *http.Request) error { defer rwc.Close() } if err != nil { - httpError(w, err) - return err + return nil, err } fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") if image != "" { //pull registry := r.Form.Get("registry") if err := srv.ImagePull(image, tag, registry, file); err != nil { fmt.Fprintf(file, "Error: %s\n", err) - return err + return nil, err } } else { //import if err := srv.ImageImport(src, repo, tag, file); err != nil { fmt.Fprintf(file, "Error: %s\n", err) - return err + return nil, err } } - return nil + return nil, nil } -func getImagesSearch(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func getImagesSearch(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { if err := r.ParseForm(); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err + return nil, err } term := r.Form.Get("term") outs, err := srv.ImagesSearch(term) if err != nil { - httpError(w, err) - return err + return nil, err } b, err := json.Marshal(outs) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) + return nil, err } - return nil + return b, nil } -func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { if err := r.ParseForm(); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err + return nil, err } url := r.Form.Get("url") @@ -429,22 +349,19 @@ func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request) error defer rwc.Close() } if err != nil { - httpError(w, err) - return err + return nil, err } fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") if err := srv.ImageInsert(name, url, path, file); err != nil { fmt.Fprintf(file, "Error: %s\n", err) - return err + return nil, err } - return nil + return nil, nil } -func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { if err := r.ParseForm(); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err + return nil, err } registry := r.Form.Get("registry") @@ -460,19 +377,17 @@ func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request) error { defer rwc.Close() } if err != nil { - httpError(w, err) - return err + return nil, err } fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") if err := srv.ImagePush(name, registry, file); err != nil { fmt.Fprintln(file, "Error: $s\n", err) - return err + return nil, err } - return nil + return nil, nil } -func postBuild(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func postBuild(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { file, rwc, err := hijackServer(w) if file != nil { @@ -482,28 +397,24 @@ func postBuild(srv *Server, w http.ResponseWriter, r *http.Request) error { defer rwc.Close() } if err != nil { - httpError(w, err) - return err + return nil, err } fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") if err := srv.ImageCreateFormFile(file); err != nil { fmt.Fprintln(file, "Error: %s\n", err) - return err + return nil, err } - return nil + return nil, nil } -func postContainers(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func postContainers(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { var config Config if err := json.NewDecoder(r.Body).Decode(&config); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err + return nil, err } id, memoryW, swapW, err := srv.ContainerCreate(config) if err != nil { - httpError(w, err) - return err + return nil, err } var out ApiRun out.Id = id @@ -515,20 +426,14 @@ func postContainers(srv *Server, w http.ResponseWriter, r *http.Request) error { } b, err := json.Marshal(out) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) + return nil, err } - return nil + return b, nil } -func postContainersRestart(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func postContainersRestart(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { if err := r.ParseForm(); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err + return nil, err } t, err := strconv.Atoi(r.Form.Get("t")) if err != nil || t < 0 { @@ -537,64 +442,54 @@ func postContainersRestart(srv *Server, w http.ResponseWriter, r *http.Request) vars := mux.Vars(r) name := vars["name"] if err := srv.ContainerRestart(name, t); err != nil { - httpError(w, err) - return err + return nil, err } else { w.WriteHeader(http.StatusOK) } - return nil + return nil, nil } -func deleteContainers(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func deleteContainers(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { if err := r.ParseForm(); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err + return nil, err } vars := mux.Vars(r) name := vars["name"] v := r.Form.Get("v") == "1" if err := srv.ContainerDestroy(name, v); err != nil { - httpError(w, err) - return err + return nil, err } else { w.WriteHeader(http.StatusOK) } - return nil + return nil, nil } -func deleteImages(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func deleteImages(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { vars := mux.Vars(r) name := vars["name"] if err := srv.ImageDelete(name); err != nil { - httpError(w, err) - return err + return nil, err } else { w.WriteHeader(http.StatusOK) } - return nil + return nil, nil } -func postContainersStart(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func postContainersStart(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { vars := mux.Vars(r) name := vars["name"] if err := srv.ContainerStart(name); err != nil { - httpError(w, err) - return err + return nil, err } else { w.WriteHeader(http.StatusOK) } - return nil + return nil, nil } -func postContainersStop(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func postContainersStop(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { if err := r.ParseForm(); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err + return nil, err } t, err := strconv.Atoi(r.Form.Get("t")) if err != nil || t < 0 { @@ -604,39 +499,30 @@ func postContainersStop(srv *Server, w http.ResponseWriter, r *http.Request) err name := vars["name"] if err := srv.ContainerStop(name, t); err != nil { - httpError(w, err) - return err + return nil, err } else { w.WriteHeader(http.StatusOK) } - return nil + return nil, nil } -func postContainersWait(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func postContainersWait(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { vars := mux.Vars(r) name := vars["name"] status, err := srv.ContainerWait(name) if err != nil { - httpError(w, err) - return err + return nil, err } b, err := json.Marshal(ApiWait{status}) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) + return nil, err } - return nil + return b, nil } -func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { if err := r.ParseForm(); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err + return nil, err } logs := r.Form.Get("logs") == "1" stream := r.Form.Get("stream") == "1" @@ -654,58 +540,45 @@ func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request) e defer rwc.Close() } if err != nil { - httpError(w, err) - return err + return nil, err } 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.Fprintf(file, "Error: %s\n", err) - return err + return nil, err } - return nil + return nil, nil } -func getContainersByName(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func getContainersByName(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { vars := mux.Vars(r) name := vars["name"] container, err := srv.ContainerInspect(name) if err != nil { - httpError(w, err) - return err + return nil, err } b, err := json.Marshal(container) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) + return nil, err } - return nil + return b, nil } -func getImagesByName(srv *Server, w http.ResponseWriter, r *http.Request) error { - log.Println(r.Method, r.RequestURI) +func getImagesByName(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { vars := mux.Vars(r) name := vars["name"] image, err := srv.ImageInspect(name) if err != nil { - httpError(w, err) - return err + return nil, err } b, err := json.Marshal(image) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return err - } else { - w.Header().Set("Content-Type", "application/json") - w.Write(b) + return nil, err } - return nil + return b, nil } func wrap(fct func(*Server, http.ResponseWriter, *http.Request) error, w http.ResponseWriter, r *http.Request, srv *Server, method, route string) { @@ -718,7 +591,7 @@ func ListenAndServe(addr string, srv *Server) error { r := mux.NewRouter() log.Printf("Listening for HTTP on %s\n", addr) - m := map[string]map[string]func(*Server, http.ResponseWriter, *http.Request) error{ + m := map[string]map[string]func(*Server, http.ResponseWriter, *http.Request) ([]byte, error){ "GET": { "/auth": getAuth, "/version": getVersion, @@ -764,7 +637,15 @@ func ListenAndServe(addr string, srv *Server) error { localFct := fct r.Path(localRoute).Methods(localMethod).HandlerFunc(func(w http.ResponseWriter, r *http.Request) { Debugf("Calling %s %s", localMethod, localRoute) - localFct(srv, w, r) + log.Println(r.Method, r.RequestURI) + json, err := localFct(srv, w, r) + if err != nil { + httpError(w, err) + } + if json != nil { + w.Header().Set("Content-Type", "application/json") + w.Write(json) + } }) } } From 755604a2bdcaa47ad1ef827be021c9997002d311 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 7 May 2013 17:35:33 -0700 Subject: [PATCH 33/69] Fix routes in api.go --- api.go | 4 ++-- commands.go | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/api.go b/api.go index ea66fce9c..b77a60af4 100644 --- a/api.go +++ b/api.go @@ -614,8 +614,8 @@ func ListenAndServe(addr string, srv *Server) error { "/images": postImages, "/images/{name:*.}/insert": postImagesInsert, "/images/{name:*.}/push": postImagesPush, - "/postBuild": postBuild, - "/postContainers": postContainers, + "/build": postBuild, + "/containers": postContainers, "/containers/{name:.*}/restart": postContainersRestart, "/containers/{name:.*}/start": postContainersStart, "/containers/{name:.*}/stop": postContainersStop, diff --git a/commands.go b/commands.go index 2ada46ff9..9a3bfaab9 100644 --- a/commands.go +++ b/commands.go @@ -1086,7 +1086,6 @@ func CmdRun(args ...string) error { //create the container body, statusCode, err := call("POST", "/containers", *config) - //if image not found try to pull it if statusCode == 404 { v := url.Values{} From 57cfe72e8c27dc3f499b15a679d16dee85eb7a3e Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 7 May 2013 18:06:49 -0700 Subject: [PATCH 34/69] Replace os.File with io.ReadCloser and io.Writer --- api.go | 129 ++++++++++++++++++------------------------------------ server.go | 78 ++++++++++++++++----------------- 2 files changed, 81 insertions(+), 126 deletions(-) diff --git a/api.go b/api.go index b77a60af4..b95b1c26d 100644 --- a/api.go +++ b/api.go @@ -1,35 +1,26 @@ package docker import ( - _ "bytes" "encoding/json" "fmt" "github.com/dotcloud/docker/auth" "github.com/gorilla/mux" "github.com/shin-/cookiejar" + "io" "log" - "net" "net/http" - "os" "strconv" "strings" ) -func hijackServer(w http.ResponseWriter) (*os.File, net.Conn, error) { - rwc, _, err := w.(http.Hijacker).Hijack() +func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { + conn, _, 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 + conn.Write([]byte{}) + return conn, conn, nil } func httpError(w http.ResponseWriter, err error) { @@ -106,19 +97,14 @@ func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request) ([ vars := mux.Vars(r) name := vars["name"] - file, rwc, err := hijackServer(w) - if file != nil { - defer file.Close() - } - if rwc != nil { - defer rwc.Close() - } + in, out, err := hijackServer(w) if err != nil { + defer in.Close() return nil, err } - 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.Fprintf(file, "Error: %s\n", err) + fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") + if err := srv.ContainerExport(name, out); err != nil { + fmt.Fprintf(out, "Error: %s\n", err) return nil, err } return nil, nil @@ -131,19 +117,14 @@ func getImages(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, err viz := r.Form.Get("viz") == "1" if viz { - file, rwc, err := hijackServer(w) - if file != nil { - defer file.Close() - } - if rwc != nil { - defer rwc.Close() - } + in, out, err := hijackServer(w) if err != nil { + defer in.Close() return nil, err } - fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") - if err := srv.ImagesViz(file); err != nil { - fmt.Fprintf(file, "Error: %s\n", err) + fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") + if err := srv.ImagesViz(out); err != nil { + fmt.Fprintf(out, "Error: %s\n", err) } return nil, nil } @@ -288,26 +269,21 @@ func postImages(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, er repo := r.Form.Get("repo") tag := r.Form.Get("tag") - file, rwc, err := hijackServer(w) - if file != nil { - defer file.Close() - } - if rwc != nil { - defer rwc.Close() - } + in, out, err := hijackServer(w) if err != nil { + defer in.Close() return nil, err } - fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") + fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") if image != "" { //pull registry := r.Form.Get("registry") - if err := srv.ImagePull(image, tag, registry, file); err != nil { - fmt.Fprintf(file, "Error: %s\n", err) + if err := srv.ImagePull(image, tag, registry, out); err != nil { + fmt.Fprintf(out, "Error: %s\n", err) return nil, err } } else { //import - if err := srv.ImageImport(src, repo, tag, file); err != nil { - fmt.Fprintf(file, "Error: %s\n", err) + if err := srv.ImageImport(src, repo, tag, in, out); err != nil { + fmt.Fprintf(out, "Error: %s\n", err) return nil, err } } @@ -341,19 +317,14 @@ func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request) ([]by vars := mux.Vars(r) name := vars["name"] - file, rwc, err := hijackServer(w) - if file != nil { - defer file.Close() - } - if rwc != nil { - defer rwc.Close() - } + in, out, err := hijackServer(w) if err != nil { + defer in.Close() return nil, err } - fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") - if err := srv.ImageInsert(name, url, path, file); err != nil { - fmt.Fprintf(file, "Error: %s\n", err) + fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") + if err := srv.ImageInsert(name, url, path, out); err != nil { + fmt.Fprintf(out, "Error: %s\n", err) return nil, err } return nil, nil @@ -369,39 +340,28 @@ func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte vars := mux.Vars(r) name := vars["name"] - file, rwc, err := hijackServer(w) - if file != nil { - defer file.Close() - } - if rwc != nil { - defer rwc.Close() - } + in, out, err := hijackServer(w) if err != nil { + defer in.Close() return nil, err } - fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") - if err := srv.ImagePush(name, registry, file); err != nil { - fmt.Fprintln(file, "Error: $s\n", err) + fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") + if err := srv.ImagePush(name, registry, out); err != nil { + fmt.Fprintln(out, "Error: %s\n", err) return nil, err } return nil, nil } func postBuild(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { - - file, rwc, err := hijackServer(w) - if file != nil { - defer file.Close() - } - if rwc != nil { - defer rwc.Close() - } + in, out, err := hijackServer(w) if err != nil { + defer in.Close() return nil, err } - fmt.Fprintf(file, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") - if err := srv.ImageCreateFormFile(file); err != nil { - fmt.Fprintln(file, "Error: %s\n", err) + fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") + if err := srv.ImageCreateFromFile(in, out); err != nil { + fmt.Fprintln(out, "Error: %s\n", err) return nil, err } return nil, nil @@ -532,20 +492,15 @@ func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request) ( vars := mux.Vars(r) name := vars["name"] - file, rwc, err := hijackServer(w) - if file != nil { - defer file.Close() - } - if rwc != nil { - defer rwc.Close() - } + in, out, err := hijackServer(w) if err != nil { + defer in.Close() return nil, err } - 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.Fprintf(file, "Error: %s\n", err) + fmt.Fprintf(out, "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, in, out); err != nil { + fmt.Fprintf(out, "Error: %s\n", err) return nil, err } return nil, nil diff --git a/server.go b/server.go index 1d5eaa850..5fb419113 100644 --- a/server.go +++ b/server.go @@ -26,7 +26,7 @@ func (srv *Server) ContainerKill(name string) error { return nil } -func (srv *Server) ContainerExport(name string, file *os.File) error { +func (srv *Server) ContainerExport(name string, out io.Writer) error { if container := srv.runtime.Get(name); container != nil { data, err := container.Export() @@ -35,7 +35,7 @@ func (srv *Server) ContainerExport(name string, file *os.File) error { } // Stream the entire contents of the container (basically a volatile snapshot) - if _, err := io.Copy(file, data); err != nil { + if _, err := io.Copy(out, data); err != nil { return err } return nil @@ -62,13 +62,13 @@ func (srv *Server) ImagesSearch(term string) ([]ApiSearch, error) { return outs, nil } -func (srv *Server) ImageInsert(name, url, path string, stdout *os.File) error { +func (srv *Server) ImageInsert(name, url, path string, out io.Writer) error { img, err := srv.runtime.repositories.LookupImage(name) if err != nil { return err } - file, err := Download(url, stdout) + file, err := Download(url, out) if err != nil { return err } @@ -85,7 +85,7 @@ func (srv *Server) ImageInsert(name, url, path string, stdout *os.File) error { return err } - if err := c.Inject(ProgressReader(file.Body, int(file.ContentLength), stdout, "Downloading %v/%v (%v)"), path); err != nil { + if err := c.Inject(ProgressReader(file.Body, int(file.ContentLength), out, "Downloading %v/%v (%v)"), path); err != nil { return err } // FIXME: Handle custom repo, tag comment, author @@ -93,29 +93,31 @@ func (srv *Server) ImageInsert(name, url, path string, stdout *os.File) error { if err != nil { return err } - fmt.Fprintf(stdout, "%s\n", img.Id) + fmt.Fprintf(out, "%s\n", img.Id) return nil } -func (srv *Server) ImagesViz(file *os.File) error { +func (srv *Server) ImagesViz(out io.Writer) error { images, _ := srv.runtime.graph.All() if images == nil { return nil } - fmt.Fprintf(file, "digraph docker {\n") + fmt.Fprintf(out, "digraph docker {\n") - var parentImage *Image - var err error + var ( + parentImage *Image + err error + ) for _, image := range images { parentImage, err = image.GetParent() if err != nil { return fmt.Errorf("Error while getting parent image: %v", err) } if parentImage != nil { - fmt.Fprintf(file, " \"%s\" -> \"%s\"\n", parentImage.ShortId(), image.ShortId()) + fmt.Fprintf(out, " \"%s\" -> \"%s\"\n", parentImage.ShortId(), image.ShortId()) } else { - fmt.Fprintf(file, " base -> \"%s\" [style=invis]\n", image.ShortId()) + fmt.Fprintf(out, " base -> \"%s\" [style=invis]\n", image.ShortId()) } } @@ -128,11 +130,9 @@ func (srv *Server) ImagesViz(file *os.File) error { } for id, repos := range reporefs { - fmt.Fprintf(file, " \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n", id, id, strings.Join(repos, "\\n")) + fmt.Fprintf(out, " \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n", id, id, strings.Join(repos, "\\n")) } - - fmt.Fprintf(file, " base [style=invisible]\n") - fmt.Fprintf(file, "}\n") + fmt.Fprintf(out, " base [style=invisible]\n}\n") return nil } @@ -299,26 +299,26 @@ func (srv *Server) ContainerTag(name, repo, tag string, force bool) error { return nil } -func (srv *Server) ImagePull(name, tag, registry string, file *os.File) error { +func (srv *Server) ImagePull(name, tag, registry string, out io.Writer) error { if registry != "" { - if err := srv.runtime.graph.PullImage(file, name, registry, nil); err != nil { + if err := srv.runtime.graph.PullImage(out, name, registry, nil); err != nil { return err } return nil } - if err := srv.runtime.graph.PullRepository(file, name, tag, srv.runtime.repositories, srv.runtime.authConfig); err != nil { + if err := srv.runtime.graph.PullRepository(out, name, tag, srv.runtime.repositories, srv.runtime.authConfig); err != nil { return err } return nil } -func (srv *Server) ImagePush(name, registry string, file *os.File) error { +func (srv *Server) ImagePush(name, registry string, out io.Writer) error { img, err := srv.runtime.graph.Get(name) if err != nil { Debugf("The push refers to a repository [%s] (len: %d)\n", name, len(srv.runtime.repositories.Repositories[name])) // If it fails, try to get the repository if localRepo, exists := srv.runtime.repositories.Repositories[name]; exists { - if err := srv.runtime.graph.PushRepository(file, name, localRepo, srv.runtime.authConfig); err != nil { + if err := srv.runtime.graph.PushRepository(out, name, localRepo, srv.runtime.authConfig); err != nil { return err } return nil @@ -326,37 +326,37 @@ func (srv *Server) ImagePush(name, registry string, file *os.File) error { return err } - err = srv.runtime.graph.PushImage(file, img, registry, nil) + err = srv.runtime.graph.PushImage(out, img, registry, nil) if err != nil { return err } return nil } -func (srv *Server) ImageImport(src, repo, tag string, file *os.File) error { +func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Writer) error { var archive io.Reader var resp *http.Response if src == "-" { - archive = file + archive = in } else { u, err := url.Parse(src) if err != nil { - fmt.Fprintf(file, "Error: %s\n", err) + fmt.Fprintf(out, "Error: %s\n", err) } if u.Scheme == "" { u.Scheme = "http" u.Host = src u.Path = "" } - fmt.Fprintln(file, "Downloading from", u) + fmt.Fprintln(out, "Downloading from", u) // Download with curl (pretty progress bar) // If curl is not available, fallback to http.Get() - resp, err = Download(u.String(), file) + resp, err = Download(u.String(), out) if err != nil { return err } - archive = ProgressReader(resp.Body, int(resp.ContentLength), file, "Importing %v/%v (%v)") + archive = ProgressReader(resp.Body, int(resp.ContentLength), out, "Importing %v/%v (%v)") } img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil) if err != nil { @@ -368,7 +368,7 @@ func (srv *Server) ImageImport(src, repo, tag string, file *os.File) error { return err } } - fmt.Fprintln(file, img.ShortId()) + fmt.Fprintln(out, img.ShortId()) return nil } @@ -397,12 +397,12 @@ func (srv *Server) ContainerCreate(config Config) (string, bool, bool, error) { return container.ShortId(), memoryW, swapW, nil } -func (srv *Server) ImageCreateFormFile(file *os.File) error { - img, err := NewBuilder(srv.runtime).Build(file, file) +func (srv *Server) ImageCreateFromFile(dockerfile io.Reader, out io.Writer) error { + img, err := NewBuilder(srv.runtime).Build(dockerfile, out) if err != nil { return err } - fmt.Fprintf(file, "%s\n", img.ShortId()) + fmt.Fprintf(out, "%s\n", img.ShortId()) return nil } @@ -496,7 +496,7 @@ func (srv *Server) ContainerWait(name string) (int, error) { return 0, fmt.Errorf("No such container: %s", name) } -func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, stderr bool, file *os.File) error { +func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, stderr bool, in io.ReadCloser, out io.Writer) error { if container := srv.runtime.Get(name); container != nil { //logs if logs { @@ -504,7 +504,7 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std cLog, err := container.ReadLog("stdout") if err != nil { Debugf(err.Error()) - } else if _, err := io.Copy(file, cLog); err != nil { + } else if _, err := io.Copy(out, cLog); err != nil { Debugf(err.Error()) } } @@ -512,7 +512,7 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std cLog, err := container.ReadLog("stderr") if err != nil { Debugf(err.Error()) - } else if _, err := io.Copy(file, cLog); err != nil { + } else if _, err := io.Copy(out, cLog); err != nil { Debugf(err.Error()) } } @@ -535,16 +535,16 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std go func() { defer w.Close() defer Debugf("Closing buffered stdin pipe") - io.Copy(w, file) + io.Copy(w, in) }() cStdin = r - cStdinCloser = file + cStdinCloser = in } if stdout { - cStdout = file + cStdout = out } if stderr { - cStderr = file + cStderr = out } <-container.Attach(cStdin, cStdinCloser, cStdout, cStderr) From 2ac4e662f1f507d8c1d45141f7c8e128ba96ff9d Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 7 May 2013 18:15:33 -0700 Subject: [PATCH 35/69] Small fix --- api.go | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/api.go b/api.go index b95b1c26d..c24b969c3 100644 --- a/api.go +++ b/api.go @@ -67,9 +67,8 @@ func postAuth(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, erro return nil, err return b, nil } - } else { - w.WriteHeader(http.StatusOK) } + w.WriteHeader(http.StatusOK) return nil, nil } @@ -87,9 +86,8 @@ func postContainersKill(srv *Server, w http.ResponseWriter, r *http.Request) ([] name := vars["name"] if err := srv.ContainerKill(name); err != nil { return nil, err - } else { - w.WriteHeader(http.StatusOK) } + w.WriteHeader(http.StatusOK) return nil, nil } @@ -403,9 +401,8 @@ func postContainersRestart(srv *Server, w http.ResponseWriter, r *http.Request) name := vars["name"] if err := srv.ContainerRestart(name, t); err != nil { return nil, err - } else { - w.WriteHeader(http.StatusOK) } + w.WriteHeader(http.StatusOK) return nil, nil } @@ -419,9 +416,8 @@ func deleteContainers(srv *Server, w http.ResponseWriter, r *http.Request) ([]by if err := srv.ContainerDestroy(name, v); err != nil { return nil, err - } else { - w.WriteHeader(http.StatusOK) } + w.WriteHeader(http.StatusOK) return nil, nil } @@ -430,9 +426,8 @@ func deleteImages(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, name := vars["name"] if err := srv.ImageDelete(name); err != nil { return nil, err - } else { - w.WriteHeader(http.StatusOK) } + w.WriteHeader(http.StatusOK) return nil, nil } @@ -441,9 +436,8 @@ func postContainersStart(srv *Server, w http.ResponseWriter, r *http.Request) ([ name := vars["name"] if err := srv.ContainerStart(name); err != nil { return nil, err - } else { - w.WriteHeader(http.StatusOK) } + w.WriteHeader(http.StatusOK) return nil, nil } @@ -460,9 +454,8 @@ func postContainersStop(srv *Server, w http.ResponseWriter, r *http.Request) ([] if err := srv.ContainerStop(name, t); err != nil { return nil, err - } else { - w.WriteHeader(http.StatusOK) } + w.WriteHeader(http.StatusOK) return nil, nil } @@ -536,12 +529,6 @@ func getImagesByName(srv *Server, w http.ResponseWriter, r *http.Request) ([]byt return b, nil } -func wrap(fct func(*Server, http.ResponseWriter, *http.Request) error, w http.ResponseWriter, r *http.Request, srv *Server, method, route string) { - if err := fct(srv, w, r); err != nil { - Debugf("Error: %s %s: %s", method, route, err) - } -} - func ListenAndServe(addr string, srv *Server) error { r := mux.NewRouter() log.Printf("Listening for HTTP on %s\n", addr) From cacc7e564acfcb7c28c2526e29714057fe793b2a Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 7 May 2013 23:32:17 -0700 Subject: [PATCH 36/69] Fix non exiting client issue --- api.go | 14 +++---- commands.go | 1 - server.go | 118 ++++++++++++++++++++++++++-------------------------- 3 files changed, 66 insertions(+), 67 deletions(-) diff --git a/api.go b/api.go index c24b969c3..ea7a6f146 100644 --- a/api.go +++ b/api.go @@ -97,9 +97,9 @@ func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request) ([ in, out, err := hijackServer(w) if err != nil { - defer in.Close() return nil, err } + defer in.Close() fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") if err := srv.ContainerExport(name, out); err != nil { fmt.Fprintf(out, "Error: %s\n", err) @@ -117,9 +117,9 @@ func getImages(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, err if viz { in, out, err := hijackServer(w) if err != nil { - defer in.Close() return nil, err } + defer in.Close() fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") if err := srv.ImagesViz(out); err != nil { fmt.Fprintf(out, "Error: %s\n", err) @@ -269,9 +269,9 @@ func postImages(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, er in, out, err := hijackServer(w) if err != nil { - defer in.Close() return nil, err } + defer in.Close() fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") if image != "" { //pull registry := r.Form.Get("registry") @@ -317,9 +317,9 @@ func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request) ([]by in, out, err := hijackServer(w) if err != nil { - defer in.Close() return nil, err } + defer in.Close() fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") if err := srv.ImageInsert(name, url, path, out); err != nil { fmt.Fprintf(out, "Error: %s\n", err) @@ -340,9 +340,9 @@ func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte in, out, err := hijackServer(w) if err != nil { - defer in.Close() return nil, err } + defer in.Close() fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") if err := srv.ImagePush(name, registry, out); err != nil { fmt.Fprintln(out, "Error: %s\n", err) @@ -354,9 +354,9 @@ func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte func postBuild(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { in, out, err := hijackServer(w) if err != nil { - defer in.Close() return nil, err } + defer in.Close() fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") if err := srv.ImageCreateFromFile(in, out); err != nil { fmt.Fprintln(out, "Error: %s\n", err) @@ -487,9 +487,9 @@ func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request) ( in, out, err := hijackServer(w) if err != nil { - defer in.Close() return nil, err } + defer in.Close() fmt.Fprintf(out, "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, in, out); err != nil { diff --git a/commands.go b/commands.go index 9a3bfaab9..a19a37d5f 100644 --- a/commands.go +++ b/commands.go @@ -1140,7 +1140,6 @@ func CmdRun(args ...string) error { return err } } - if !config.AttachStdout && !config.AttachStderr { fmt.Println(out.Id) } diff --git a/server.go b/server.go index 5fb419113..7f91bf019 100644 --- a/server.go +++ b/server.go @@ -497,67 +497,67 @@ func (srv *Server) ContainerWait(name string) (int, error) { } func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, stderr bool, in io.ReadCloser, out io.Writer) error { - if container := srv.runtime.Get(name); container != nil { - //logs - if logs { - if stdout { - cLog, err := container.ReadLog("stdout") - if err != nil { - Debugf(err.Error()) - } else if _, err := io.Copy(out, cLog); err != nil { - Debugf(err.Error()) - } - } - if stderr { - cLog, err := container.ReadLog("stderr") - if err != nil { - Debugf(err.Error()) - } else if _, err := io.Copy(out, cLog); err != nil { - Debugf(err.Error()) - } - } - } - - //stream - if stream { - if container.State.Ghost { - return fmt.Errorf("Impossible to attach to a ghost container") - } - - var ( - cStdin io.ReadCloser - cStdout, cStderr io.Writer - cStdinCloser io.Closer - ) - - if stdin { - r, w := io.Pipe() - go func() { - defer w.Close() - defer Debugf("Closing buffered stdin pipe") - io.Copy(w, in) - }() - cStdin = r - cStdinCloser = in - } - if stdout { - cStdout = out - } - if stderr { - cStderr = out - } - - <-container.Attach(cStdin, cStdinCloser, cStdout, cStderr) - - // If we are in stdinonce mode, wait for the process to end - // otherwise, simply return - if container.Config.StdinOnce && !container.Config.Tty { - container.Wait() - } - } - } else { + container := srv.runtime.Get(name) + if container == nil { return fmt.Errorf("No such container: %s", name) } + //logs + if logs { + if stdout { + cLog, err := container.ReadLog("stdout") + if err != nil { + Debugf(err.Error()) + } else if _, err := io.Copy(out, cLog); err != nil { + Debugf(err.Error()) + } + } + if stderr { + cLog, err := container.ReadLog("stderr") + if err != nil { + Debugf(err.Error()) + } else if _, err := io.Copy(out, cLog); err != nil { + Debugf(err.Error()) + } + } + } + + //stream + if stream { + if container.State.Ghost { + return fmt.Errorf("Impossible to attach to a ghost container") + } + + var ( + cStdin io.ReadCloser + cStdout, cStderr io.Writer + cStdinCloser io.Closer + ) + + if stdin { + r, w := io.Pipe() + go func() { + defer w.Close() + defer Debugf("Closing buffered stdin pipe") + io.Copy(w, in) + }() + cStdin = r + cStdinCloser = in + } + if stdout { + cStdout = out + } + if stderr { + cStderr = out + } + + <-container.Attach(cStdin, cStdinCloser, cStdout, cStderr) + + // If we are in stdinonce mode, wait for the process to end + // otherwise, simply return + if container.Config.StdinOnce && !container.Config.Tty { + container.Wait() + } + } return nil } From 60ddcaa15d61c75bec78edeb72999daa7077dd0e Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 8 May 2013 17:35:50 +0200 Subject: [PATCH 37/69] changes 2 endpoints to avoid confusion, changed some parameters, fix doc, add api unit tests --- api.go | 18 +-- api_test.go | 240 ++++++++++++++++++++++++++++++++ commands.go | 16 +-- docs/sources/remote-api/api.rst | 158 +++++++++++++++++---- server.go | 26 ++-- 5 files changed, 396 insertions(+), 62 deletions(-) create mode 100644 api_test.go diff --git a/api.go b/api.go index ea7a6f146..167ed1ad0 100644 --- a/api.go +++ b/api.go @@ -129,9 +129,9 @@ func getImages(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, err all := r.Form.Get("all") == "1" filter := r.Form.Get("filter") - quiet := r.Form.Get("quiet") == "1" + only_ids := r.Form.Get("only_ids") == "1" - outs, err := srv.Images(all, quiet, filter) + outs, err := srv.Images(all, only_ids, filter) if err != nil { return nil, err } @@ -201,14 +201,14 @@ func getContainers(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, return nil, err } all := r.Form.Get("all") == "1" - notrunc := r.Form.Get("notrunc") == "1" - quiet := r.Form.Get("quiet") == "1" - n, err := strconv.Atoi(r.Form.Get("n")) + trunc_cmd := r.Form.Get("trunc_cmd") != "0" + only_ids := r.Form.Get("only_ids") == "1" + n, err := strconv.Atoi(r.Form.Get("limit")) if err != nil { n = -1 } - outs := srv.Containers(all, notrunc, quiet, n) + outs := srv.Containers(all, trunc_cmd, only_ids, n) b, err := json.Marshal(outs) if err != nil { return nil, err @@ -540,13 +540,13 @@ func ListenAndServe(addr string, srv *Server) error { "/containers/{name:.*}/export": getContainersExport, "/images": getImages, "/info": getInfo, + "/images/search": getImagesSearch, "/images/{name:.*}/history": getImagesHistory, "/containers/{name:.*}/changes": getContainersChanges, "/containers/{name:.*}/port": getContainersPort, "/containers": getContainers, - "/images/search": getImagesSearch, - "/containers/{name:.*}": getContainersByName, - "/images/{name:.*}": getImagesByName, + "/images/{name:.*}/json": getImagesByName, + "/containers/{name:.*}/json": getContainersByName, }, "POST": { "/auth": postAuth, diff --git a/api_test.go b/api_test.go new file mode 100644 index 000000000..d92eacf51 --- /dev/null +++ b/api_test.go @@ -0,0 +1,240 @@ +package docker + +import ( + "encoding/json" + "github.com/dotcloud/docker/auth" + "testing" +) + +func init() { + // Make it our Store root + runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false) + if err != nil { + panic(err) + } + + // Create the "Server" + srv := &Server{ + runtime: runtime, + } + go ListenAndServe("0.0.0.0:4243", srv) + +} + +func TestAuth(t *testing.T) { + var out auth.AuthConfig + + out.Username = "utest" + out.Password = "utest" + out.Email = "utest@yopmail.com" + + _, _, err := call("POST", "/auth", out) + if err != nil { + t.Fatal(err) + } + + out.Username = "" + out.Password = "" + out.Email = "" + + body, _, err := call("GET", "/auth", nil) + if err != nil { + t.Fatal(err) + } + + err = json.Unmarshal(body, &out) + if err != nil { + t.Fatal(err) + } + + if out.Username != "utest" { + t.Errorf("Expected username to be utest, %s found", out.Username) + } +} + +func TestVersion(t *testing.T) { + body, _, err := call("GET", "/version", nil) + if err != nil { + t.Fatal(err) + } + var out ApiVersion + err = json.Unmarshal(body, &out) + if err != nil { + t.Fatal(err) + } + if out.Version != VERSION { + t.Errorf("Excepted version %s, %s found", VERSION, out.Version) + } +} + +func TestImages(t *testing.T) { + body, _, err := call("GET", "/images?quiet=0&all=0", nil) + if err != nil { + t.Fatal(err) + } + var outs []ApiImages + err = json.Unmarshal(body, &outs) + if err != nil { + t.Fatal(err) + } + + if len(outs) != 1 { + t.Errorf("Excepted 1 image, %d found", len(outs)) + } + + if outs[0].Repository != "docker-ut" { + t.Errorf("Excepted image docker-ut, %s found", outs[0].Repository) + } +} + +func TestInfo(t *testing.T) { + body, _, err := call("GET", "/info", nil) + if err != nil { + t.Fatal(err) + } + var out ApiInfo + err = json.Unmarshal(body, &out) + if err != nil { + t.Fatal(err) + } + if out.Version != VERSION { + t.Errorf("Excepted version %s, %s found", VERSION, out.Version) + } +} + +func TestHistory(t *testing.T) { + body, _, err := call("GET", "/images/"+unitTestImageName+"/history", nil) + if err != nil { + t.Fatal(err) + } + var outs []ApiHistory + err = json.Unmarshal(body, &outs) + if err != nil { + t.Fatal(err) + } + if len(outs) != 1 { + t.Errorf("Excepted 1 line, %d found", len(outs)) + } +} + +func TestImagesSearch(t *testing.T) { + body, _, err := call("GET", "/images/search?term=redis", nil) + if err != nil { + t.Fatal(err) + } + var outs []ApiSearch + err = json.Unmarshal(body, &outs) + if err != nil { + t.Fatal(err) + } + if len(outs) < 2 { + t.Errorf("Excepted at least 2 lines, %d found", len(outs)) + } +} + +func TestGetImage(t *testing.T) { + obj, _, err := call("GET", "/images/"+unitTestImageName+"/json", nil) + if err != nil { + t.Fatal(err) + } + var out Image + err = json.Unmarshal(obj, &out) + if err != nil { + t.Fatal(err) + } + if out.Comment != "Imported from http://get.docker.io/images/busybox" { + t.Errorf("Error inspecting image") + } +} + +func TestCreateListStartStopRestartKillWaitDelete(t *testing.T) { + containers := testListContainers(t, -1) + for _, container := range containers { + testDeleteContainer(t, container.Id) + } + testCreateContainer(t) + id := testListContainers(t, 1)[0].Id + testContainerStart(t, id) + testContainerStop(t, id) + testContainerRestart(t, id) + testContainerKill(t, id) + testContainerWait(t, id) + testDeleteContainer(t, id) + testListContainers(t, 0) +} + +func testCreateContainer(t *testing.T) { + config, _, err := ParseRun([]string{unitTestImageName, "touch test"}, nil) + if err != nil { + t.Fatal(err) + } + _, _, err = call("POST", "/containers", *config) + if err != nil { + t.Fatal(err) + } +} + +func testListContainers(t *testing.T, expected int) []ApiContainers { + body, _, err := call("GET", "/containers?quiet=1&all=1", nil) + if err != nil { + t.Fatal(err) + } + var outs []ApiContainers + err = json.Unmarshal(body, &outs) + if err != nil { + t.Fatal(err) + } + if expected >= 0 && len(outs) != expected { + t.Errorf("Excepted %d container, %d found", expected, len(outs)) + } + return outs +} + +func testContainerStart(t *testing.T, id string) { + _, _, err := call("POST", "/containers/"+id+"/start", nil) + if err != nil { + t.Fatal(err) + } +} + +func testContainerRestart(t *testing.T, id string) { + _, _, err := call("POST", "/containers/"+id+"/restart?t=1", nil) + if err != nil { + t.Fatal(err) + } +} + +func testContainerStop(t *testing.T, id string) { + _, _, err := call("POST", "/containers/"+id+"/stop?t=1", nil) + if err != nil { + t.Fatal(err) + } +} + +func testContainerKill(t *testing.T, id string) { + _, _, err := call("POST", "/containers/"+id+"/kill", nil) + if err != nil { + t.Fatal(err) + } +} + +func testContainerWait(t *testing.T, id string) { + _, _, err := call("POST", "/containers/"+id+"/wait", nil) + if err != nil { + t.Fatal(err) + } +} + +func testDeleteContainer(t *testing.T, id string) { + _, _, err := call("DELETE", "/containers/"+id, nil) + if err != nil { + t.Fatal(err) + } +} + +func testContainerChanges(t *testing.T, id string) { + _, _, err := call("GET", "/containers/"+id+"/changes", nil) + if err != nil { + t.Fatal(err) + } +} diff --git a/commands.go b/commands.go index a19a37d5f..96960455d 100644 --- a/commands.go +++ b/commands.go @@ -449,9 +449,9 @@ func CmdInspect(args ...string) error { cmd.Usage() return nil } - obj, _, err := call("GET", "/containers/"+cmd.Arg(0), nil) + obj, _, err := call("GET", "/containers/"+cmd.Arg(0)+"/json", nil) if err != nil { - obj, _, err = call("GET", "/images/"+cmd.Arg(0), nil) + obj, _, err = call("GET", "/images/"+cmd.Arg(0)+"/json", nil) if err != nil { return err } @@ -720,7 +720,7 @@ func CmdImages(args ...string) error { v.Set("filter", cmd.Arg(0)) } if *quiet { - v.Set("quiet", "1") + v.Set("only_ids", "1") } if *all { v.Set("all", "1") @@ -773,16 +773,16 @@ func CmdPs(args ...string) error { *last = 1 } if *quiet { - v.Set("quiet", "1") + v.Set("only_ids", "1") } if *all { v.Set("all", "1") } if *noTrunc { - v.Set("notrunc", "1") + v.Set("trunc_cmd", "0") } if *last != -1 { - v.Set("n", strconv.Itoa(*last)) + v.Set("limit", strconv.Itoa(*last)) } body, _, err := call("GET", "/containers?"+v.Encode(), nil) @@ -888,13 +888,13 @@ func CmdDiff(args ...string) error { return err } - var changes []string + var changes []Change err = json.Unmarshal(body, &changes) if err != nil { return err } for _, change := range changes { - fmt.Println(change) + fmt.Println(change.String()) } return nil } diff --git a/docs/sources/remote-api/api.rst b/docs/sources/remote-api/api.rst index 210e289f3..1a430aa01 100644 --- a/docs/sources/remote-api/api.rst +++ b/docs/sources/remote-api/api.rst @@ -28,7 +28,7 @@ List containers .. sourcecode:: http - GET /containers?notrunc=1&all=1&quiet=0 HTTP/1.1 + GET /containers?trunc_cmd=0&all=1&only_ids=0 HTTP/1.1 **Example response**: @@ -47,30 +47,30 @@ List containers { "Id": "9cd87474be90", "Image": "base:latest", - "Command": "echo 2", + "Command": "echo 222222", "Created": 1367854155, "Status": "Exit 0" }, { "Id": "3176a2479c92", "Image": "base:latest", - "Command": "echo 3", + "Command": "echo 3333333333333333", "Created": 1367854154, "Status": "Exit 0" }, { "Id": "4cb07b47f9fb", "Image": "base:latest", - "Command": "echo 4", + "Command": "echo 444444444444444444444444444444444", "Created": 1367854152, "Status": "Exit 0" } ] - :query quiet: 1 or 0, Only display numeric IDs. Not quiet by default + :query only_ids: 1 or 0, Only display numeric IDs. Default 0 :query all: 1 or 0, Show all containers. Only running containers are shown by default - :query notrunc: 1 or 0, Don't truncate output. Output is truncated by default - :query n: limit number, Show n last created containers, include non-running ones. + :query trunc_cmd: 1 or 0, Truncate output. Output is truncated by default + :query limit: Show ``limit`` last created containers, include non-running ones. :statuscode 200: no error :statuscode 500: server error @@ -123,14 +123,14 @@ Create a container :jsonparam config: the container's configuration :statuscode 200: no error - :statuscode 400: no such container + :statuscode 404: no such container :statuscode 500: server error Inspect a container ******************* -.. http:get:: /containers/(id) +.. http:get:: /containers/(id)/json Return low-level information on the container ``id`` @@ -138,7 +138,7 @@ Inspect a container .. sourcecode:: http - GET /containers/4fa6e0f0c678 HTTP/1.1 + GET /containers/4fa6e0f0c678/json HTTP/1.1 **Example response**: @@ -193,7 +193,7 @@ Inspect a container } :statuscode 200: no error - :statuscode 400: no such container + :statuscode 404: no such container :statuscode 500: server error @@ -218,12 +218,22 @@ Inspect changes on a container's filesystem HTTP/1.1 200 OK [ - "C /dev", - "A /dev/kmsg" + { + "Path":"/dev", + "Kind":0 + }, + { + "Path":"/dev/kmsg", + "Kind":1 + }, + { + "Path":"/test", + "Kind":1 + } ] :statuscode 200: no error - :statuscode 400: no such container + :statuscode 404: no such container :statuscode 500: server error @@ -251,7 +261,7 @@ Export a container {{ STREAM }} :statuscode 200: no error - :statuscode 400: no such container + :statuscode 404: no such container :statuscode 500: server error @@ -279,7 +289,7 @@ Map container's private ports :query port: the container private port you want to get :statuscode 200: no error - :statuscode 400: no such container + :statuscode 404: no such container :statuscode 500: server error @@ -303,7 +313,7 @@ Start a container HTTP/1.1 200 OK :statuscode 200: no error - :statuscode 400: no such container + :statuscode 404: no such container :statuscode 500: server error @@ -328,7 +338,7 @@ Stop a contaier :query t: number of seconds to wait before killing the container :statuscode 200: no error - :statuscode 400: no such container + :statuscode 404: no such container :statuscode 500: server error @@ -353,7 +363,7 @@ Restart a container :query t: number of seconds to wait before killing the container :statuscode 200: no error - :statuscode 400: no such container + :statuscode 404: no such container :statuscode 500: server error @@ -377,7 +387,7 @@ Kill a container HTTP/1.1 200 OK :statuscode 200: no error - :statuscode 400: no such container + :statuscode 404: no such container :statuscode 500: server error @@ -409,7 +419,7 @@ Attach to a container :query stdout: 1 or 0, if logs=1, return stdout log, if stream=1, attach to stdout. Default 0 :query stderr: 1 or 0, if logs=1, return stderr log, if stream=1, attach to stderr. Default 0 :statuscode 200: no error - :statuscode 400: no such container + :statuscode 404: no such container :statuscode 500: server error @@ -435,7 +445,7 @@ Wait a container {"StatusCode":0} :statuscode 200: no error - :statuscode 400: no such container + :statuscode 404: no such container :statuscode 500: server error @@ -460,7 +470,7 @@ Remove a container :query v: 1 or 0, Remove the volumes associated to the container. Default 0 :statuscode 200: no error - :statuscode 400: no such container + :statuscode 404: no such container :statuscode 500: server error @@ -478,7 +488,7 @@ List Images .. sourcecode:: http - GET /images?all=0&quiet=0 HTTP/1.1 + GET /images?all=0&only_ids=0 HTTP/1.1 **Example response**: @@ -501,7 +511,7 @@ List Images } ] - :query quiet: 1 or 0, Only display numeric IDs. Not quiet by default + :query only_ids: 1 or 0, Only display numeric IDs. Default 0 :query all: 1 or 0, Show all containers. Only running containers are shown by default :statuscode 200: no error :statuscode 500: server error @@ -537,10 +547,36 @@ Create an image :statuscode 200: no error :statuscode 500: server error + +Insert a file in a image +************************ + +.. http:post:: /images/(name)/insert + + Insert a file from ``url`` in the image ``name`` at ``path`` + + **Example request**: + + .. sourcecode:: http + + POST /images/test/insert?path=/usr&url=myurl HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + {{ STREAM }} + + :statuscode 200: no error + :statuscode 500: server error + + Inspect an image **************** -.. http:get:: /images/(name) +.. http:get:: /images/(name)/json Return low-level information on the image ``name`` @@ -548,7 +584,7 @@ Inspect an image .. sourcecode:: http - GET /images/base HTTP/1.1 + GET /images/base/json HTTP/1.1 **Example response**: @@ -703,9 +739,73 @@ Remove an image :statuscode 500: server error +Search images +************* + +.. http:get:: /images/search + + Search for an image in the docker index + + **Example request**: + + .. sourcecode:: http + + GET /images/search?term=sshd HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + [ + { + "Name":"cespare/sshd", + "Description":"" + }, + { + "Name":"johnfuller/sshd", + "Description":"" + }, + { + "Name":"dhrp/mongodb-sshd", + "Description":"" + } + ] + + :query term: term to search + :statuscode 200: no error + :statuscode 500: server error + + 2.3 Misc -------- +Build an image from Dockerfile via stdin +**************************************** + +.. http:post:: /build + + Build an image from Dockerfile via stdin + + **Example request**: + + .. sourcecode:: http + + POST /build HTTP/1.1 + + {{ STREAM }} + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + {{ STREAM }} + + :statuscode 200: no error + :statuscode 500: server error + + Get default username and email ****************************** @@ -792,6 +892,7 @@ Display system-wide information :statuscode 200: no error :statuscode 500: server error + Show the docker version information *********************************** @@ -870,7 +971,8 @@ Here are the steps of 'docker run' : * Start the container * If you are not in detached mode: * Attach to the container, using logs=1 (to have stdout and stderr from the container's start) and stream=1 - * Call /wait to get the exit code and exit with it +* If in detached mode or only stdin is attached: + * Display the container's id 3.2 Hijacking diff --git a/server.go b/server.go index 7f91bf019..7ba3b68f8 100644 --- a/server.go +++ b/server.go @@ -136,7 +136,7 @@ func (srv *Server) ImagesViz(out io.Writer) error { return nil } -func (srv *Server) Images(all, quiet bool, filter string) ([]ApiImages, error) { +func (srv *Server) Images(all, only_ids bool, filter string) ([]ApiImages, error) { var allImages map[string]*Image var err error if all { @@ -160,7 +160,7 @@ func (srv *Server) Images(all, quiet bool, filter string) ([]ApiImages, error) { continue } delete(allImages, id) - if !quiet { + if !only_ids { out.Repository = name out.Tag = tag out.Id = TruncateId(id) @@ -175,7 +175,7 @@ func (srv *Server) Images(all, quiet bool, filter string) ([]ApiImages, error) { if filter == "" { for id, image := range allImages { var out ApiImages - if !quiet { + if !only_ids { out.Repository = "" out.Tag = "" out.Id = TruncateId(id) @@ -228,17 +228,9 @@ func (srv *Server) ImageHistory(name string) ([]ApiHistory, error) { } -func (srv *Server) ContainerChanges(name string) ([]string, error) { +func (srv *Server) ContainerChanges(name string) ([]Change, 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 container.Changes() } return nil, fmt.Errorf("No such container: %s", name) } @@ -253,7 +245,7 @@ func (srv *Server) ContainerPort(name, privatePort string) (string, error) { return "", fmt.Errorf("No such container: %s", name) } -func (srv *Server) Containers(all, notrunc, quiet bool, n int) []ApiContainers { +func (srv *Server) Containers(all, trunc_cmd, only_ids bool, 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 && n == -1 { @@ -264,9 +256,9 @@ func (srv *Server) Containers(all, notrunc, quiet bool, n int) []ApiContainers { } var out ApiContainers out.Id = container.ShortId() - if !quiet { + if !only_ids { command := fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")) - if !notrunc { + if trunc_cmd { command = Trunc(command, 20) } out.Image = srv.runtime.repositories.ImageName(container.Image) @@ -461,7 +453,7 @@ func (srv *Server) ImageDelete(name string) error { 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 fmt.Errorf("Error deleting image %s: %s", name, err.Error()) } } return nil From 075e1ebb0e835c3591059131bec6dedaf042e227 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 8 May 2013 18:06:43 +0200 Subject: [PATCH 38/69] remove useless port endpoint --- api.go | 18 ------------------ commands.go | 15 +++++++++------ docs/sources/remote-api/api.rst | 28 ---------------------------- server.go | 10 ---------- 4 files changed, 9 insertions(+), 62 deletions(-) diff --git a/api.go b/api.go index 167ed1ad0..136cd52d7 100644 --- a/api.go +++ b/api.go @@ -179,23 +179,6 @@ func getContainersChanges(srv *Server, w http.ResponseWriter, r *http.Request) ( return b, nil } -func getContainersPort(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { - if err := r.ParseForm(); err != nil { - return nil, err - } - vars := mux.Vars(r) - name := vars["name"] - out, err := srv.ContainerPort(name, r.Form.Get("port")) - if err != nil { - return nil, err - } - b, err := json.Marshal(ApiPort{out}) - if err != nil { - return nil, err - } - return b, nil -} - func getContainers(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { if err := r.ParseForm(); err != nil { return nil, err @@ -543,7 +526,6 @@ func ListenAndServe(addr string, srv *Server) error { "/images/search": getImagesSearch, "/images/{name:.*}/history": getImagesHistory, "/containers/{name:.*}/changes": getContainersChanges, - "/containers/{name:.*}/port": getContainersPort, "/containers": getContainers, "/images/{name:.*}/json": getImagesByName, "/containers/{name:.*}/json": getContainersByName, diff --git a/commands.go b/commands.go index 96960455d..02e02d6ce 100644 --- a/commands.go +++ b/commands.go @@ -469,19 +469,22 @@ func CmdPort(args ...string) error { cmd.Usage() return nil } - v := url.Values{} - v.Set("port", cmd.Arg(1)) - body, _, err := call("GET", "/containers/"+cmd.Arg(0)+"/port?"+v.Encode(), nil) + + body, _, err := call("GET", "/containers/"+cmd.Arg(0)+"/json", nil) if err != nil { return err } - - var out ApiPort + var out Container err = json.Unmarshal(body, &out) if err != nil { return err } - fmt.Println(out.Port) + + if frontend, exists := out.NetworkSettings.PortMapping[cmd.Arg(1)]; exists { + fmt.Println(frontend) + } else { + return fmt.Errorf("error: No private port '%s' allocated on %s", cmd.Arg(1), cmd.Arg(0)) + } return nil } diff --git a/docs/sources/remote-api/api.rst b/docs/sources/remote-api/api.rst index 1a430aa01..25007af12 100644 --- a/docs/sources/remote-api/api.rst +++ b/docs/sources/remote-api/api.rst @@ -265,34 +265,6 @@ Export a container :statuscode 500: server error -Map container's private ports -***************************** - -.. http:get:: /containers/(id)/port - - Map a private port of container ``id`` - - **Example request**: - - .. sourcecode:: http - - GET /containers/4fa6e0f0c678/port?port=80 HTTP/1.1 - - - **Example response**: - - .. sourcecode:: http - - HTTP/1.1 200 OK - - {"Port":"80"} - - :query port: the container private port you want to get - :statuscode 200: no error - :statuscode 404: no such container - :statuscode 500: server error - - Start a container ***************** diff --git a/server.go b/server.go index 7ba3b68f8..f57d8e6f0 100644 --- a/server.go +++ b/server.go @@ -235,16 +235,6 @@ func (srv *Server) ContainerChanges(name string) ([]Change, error) { 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, trunc_cmd, only_ids bool, n int) []ApiContainers { var outs []ApiContainers = []ApiContainers{} //produce [] when empty instead of 'null' for i, container := range srv.runtime.List() { From bc3fa506e93c3e54078b9e62f5466bf4a1048704 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 8 May 2013 18:28:11 +0200 Subject: [PATCH 39/69] added pagination on ps --- api.go | 4 +++- commands.go | 8 ++++++++ docs/sources/remote-api/api.rst | 4 +++- server.go | 23 +++++++++++++++++++---- 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/api.go b/api.go index 136cd52d7..6d14f1f17 100644 --- a/api.go +++ b/api.go @@ -186,12 +186,14 @@ func getContainers(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, all := r.Form.Get("all") == "1" trunc_cmd := r.Form.Get("trunc_cmd") != "0" only_ids := r.Form.Get("only_ids") == "1" + since := r.Form.Get("since") + before := r.Form.Get("before") n, err := strconv.Atoi(r.Form.Get("limit")) if err != nil { n = -1 } - outs := srv.Containers(all, trunc_cmd, only_ids, n) + outs := srv.Containers(all, trunc_cmd, only_ids, n, since, before) b, err := json.Marshal(outs) if err != nil { return nil, err diff --git a/commands.go b/commands.go index 02e02d6ce..81575c7b1 100644 --- a/commands.go +++ b/commands.go @@ -766,6 +766,8 @@ func CmdPs(args ...string) error { all := cmd.Bool("a", false, "Show all containers. Only running containers are shown by default.") noTrunc := cmd.Bool("notrunc", false, "Don't truncate output") nLatest := cmd.Bool("l", false, "Show only the latest created container, include non-running ones.") + since := cmd.String("sinceId", "", "Show only containers created since Id, include non-running ones.") + before := cmd.String("beforeId", "", "Show only container created before Id, include non-running ones.") last := cmd.Int("n", -1, "Show n last created containers, include non-running ones.") if err := cmd.Parse(args); err != nil { @@ -787,6 +789,12 @@ func CmdPs(args ...string) error { if *last != -1 { v.Set("limit", strconv.Itoa(*last)) } + if *since != "" { + v.Set("since", *since) + } + if *before != "" { + v.Set("before", *before) + } body, _, err := call("GET", "/containers?"+v.Encode(), nil) if err != nil { diff --git a/docs/sources/remote-api/api.rst b/docs/sources/remote-api/api.rst index 25007af12..eb26cf97a 100644 --- a/docs/sources/remote-api/api.rst +++ b/docs/sources/remote-api/api.rst @@ -28,7 +28,7 @@ List containers .. sourcecode:: http - GET /containers?trunc_cmd=0&all=1&only_ids=0 HTTP/1.1 + GET /containers?trunc_cmd=0&all=1&only_ids=0&before=8dfafdbc3a40 HTTP/1.1 **Example response**: @@ -71,6 +71,8 @@ List containers :query all: 1 or 0, Show all containers. Only running containers are shown by default :query trunc_cmd: 1 or 0, Truncate output. Output is truncated by default :query limit: Show ``limit`` last created containers, include non-running ones. + :query since: Show only containers created since Id, include non-running ones. + :query before: Show only containers created before Id, include non-running ones. :statuscode 200: no error :statuscode 500: server error diff --git a/server.go b/server.go index f57d8e6f0..ffc66d622 100644 --- a/server.go +++ b/server.go @@ -235,15 +235,30 @@ func (srv *Server) ContainerChanges(name string) ([]Change, error) { return nil, fmt.Errorf("No such container: %s", name) } -func (srv *Server) Containers(all, trunc_cmd, only_ids bool, n int) []ApiContainers { +func (srv *Server) Containers(all, trunc_cmd, only_ids bool, n int, since, before string) []ApiContainers { var outs []ApiContainers = []ApiContainers{} //produce [] when empty instead of 'null' - for i, container := range srv.runtime.List() { - if !container.State.Running && !all && n == -1 { + var foundBefore bool + var displayed int + for _, container := range srv.runtime.List() { + if !container.State.Running && !all && n == -1 && since == "" && before == "" { continue } - if i == n { + if before != "" { + if container.ShortId() == before { + foundBefore = true + continue + } + if !foundBefore { + continue + } + } + if displayed == n { break } + if container.ShortId() == since { + break + } + displayed += 1 var out ApiContainers out.Id = container.ShortId() if !only_ids { From 4a1e0d321ec8ef622673971deff0d191d198cc31 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 8 May 2013 18:36:37 +0200 Subject: [PATCH 40/69] change content-type and small fix in run --- api.go | 14 +++++++------- commands.go | 1 - 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/api.go b/api.go index 6d14f1f17..a6022a105 100644 --- a/api.go +++ b/api.go @@ -100,7 +100,7 @@ func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request) ([ return nil, err } defer in.Close() - fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") + fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") if err := srv.ContainerExport(name, out); err != nil { fmt.Fprintf(out, "Error: %s\n", err) return nil, err @@ -120,7 +120,7 @@ func getImages(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, err return nil, err } defer in.Close() - fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") + fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") if err := srv.ImagesViz(out); err != nil { fmt.Fprintf(out, "Error: %s\n", err) } @@ -257,7 +257,7 @@ func postImages(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, er return nil, err } defer in.Close() - fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") + fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") if image != "" { //pull registry := r.Form.Get("registry") if err := srv.ImagePull(image, tag, registry, out); err != nil { @@ -305,7 +305,7 @@ func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request) ([]by return nil, err } defer in.Close() - fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") + fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") if err := srv.ImageInsert(name, url, path, out); err != nil { fmt.Fprintf(out, "Error: %s\n", err) return nil, err @@ -328,7 +328,7 @@ func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte return nil, err } defer in.Close() - fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") + fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") if err := srv.ImagePush(name, registry, out); err != nil { fmt.Fprintln(out, "Error: %s\n", err) return nil, err @@ -342,7 +342,7 @@ func postBuild(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, err return nil, err } defer in.Close() - fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") + fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") if err := srv.ImageCreateFromFile(in, out); err != nil { fmt.Fprintln(out, "Error: %s\n", err) return nil, err @@ -476,7 +476,7 @@ func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request) ( } defer in.Close() - fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: raw-stream-hijack\r\n\r\n") + fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, in, out); err != nil { fmt.Fprintf(out, "Error: %s\n", err) return nil, err diff --git a/commands.go b/commands.go index 81575c7b1..167834397 100644 --- a/commands.go +++ b/commands.go @@ -1109,7 +1109,6 @@ func CmdRun(args ...string) error { if err != nil { return err } - return nil } if err != nil { return err From 954ecac388bbee5cf00d22260287ed01bf5d11a4 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 8 May 2013 18:52:01 +0200 Subject: [PATCH 41/69] fix doc and empty content-type --- api.go | 32 ++++++++++++++++++++------------ docs/sources/remote-api/api.rst | 29 ++++++++++++++++++++++------- 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/api.go b/api.go index a6022a105..3d6484964 100644 --- a/api.go +++ b/api.go @@ -23,6 +23,14 @@ func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { return conn, conn, nil } +//If we don't do this, POST method without Content-type (even with empty body) will fail +func parseForm(r *http.Request) error { + if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") { + return err + } + return nil +} + func httpError(w http.ResponseWriter, err error) { if strings.HasPrefix(err.Error(), "No such") { http.Error(w, err.Error(), http.StatusNotFound) @@ -109,7 +117,7 @@ func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request) ([ } func getImages(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { - if err := r.ParseForm(); err != nil { + if err := parseForm(r); err != nil { return nil, err } @@ -180,7 +188,7 @@ func getContainersChanges(srv *Server, w http.ResponseWriter, r *http.Request) ( } func getContainers(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { - if err := r.ParseForm(); err != nil { + if err := parseForm(r); err != nil { return nil, err } all := r.Form.Get("all") == "1" @@ -202,7 +210,7 @@ func getContainers(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, } func postImagesTag(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { - if err := r.ParseForm(); err != nil { + if err := parseForm(r); err != nil { return nil, err } repo := r.Form.Get("repo") @@ -219,7 +227,7 @@ func postImagesTag(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, } func postCommit(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { - if err := r.ParseForm(); err != nil { + if err := parseForm(r); err != nil { return nil, err } var config Config @@ -243,7 +251,7 @@ func postCommit(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, er } func postImages(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { - if err := r.ParseForm(); err != nil { + if err := parseForm(r); err != nil { return nil, err } @@ -274,7 +282,7 @@ func postImages(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, er } func getImagesSearch(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { - if err := r.ParseForm(); err != nil { + if err := parseForm(r); err != nil { return nil, err } @@ -291,7 +299,7 @@ func getImagesSearch(srv *Server, w http.ResponseWriter, r *http.Request) ([]byt } func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { - if err := r.ParseForm(); err != nil { + if err := parseForm(r); err != nil { return nil, err } @@ -314,7 +322,7 @@ func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request) ([]by } func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { - if err := r.ParseForm(); err != nil { + if err := parseForm(r); err != nil { return nil, err } @@ -375,7 +383,7 @@ func postContainers(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte } func postContainersRestart(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { - if err := r.ParseForm(); err != nil { + if err := parseForm(r); err != nil { return nil, err } t, err := strconv.Atoi(r.Form.Get("t")) @@ -392,7 +400,7 @@ func postContainersRestart(srv *Server, w http.ResponseWriter, r *http.Request) } func deleteContainers(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { - if err := r.ParseForm(); err != nil { + if err := parseForm(r); err != nil { return nil, err } vars := mux.Vars(r) @@ -427,7 +435,7 @@ func postContainersStart(srv *Server, w http.ResponseWriter, r *http.Request) ([ } func postContainersStop(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { - if err := r.ParseForm(); err != nil { + if err := parseForm(r); err != nil { return nil, err } t, err := strconv.Atoi(r.Form.Get("t")) @@ -459,7 +467,7 @@ func postContainersWait(srv *Server, w http.ResponseWriter, r *http.Request) ([] } func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { - if err := r.ParseForm(); err != nil { + if err := parseForm(r); err != nil { return nil, err } logs := r.Form.Get("logs") == "1" diff --git a/docs/sources/remote-api/api.rst b/docs/sources/remote-api/api.rst index eb26cf97a..aa8c90bf9 100644 --- a/docs/sources/remote-api/api.rst +++ b/docs/sources/remote-api/api.rst @@ -35,6 +35,7 @@ List containers .. sourcecode:: http HTTP/1.1 200 OK + Content-Type: application/json [ { @@ -89,7 +90,8 @@ Create a container .. sourcecode:: http POST /containers HTTP/1.1 - + Content-Type: application/json + { "Hostname":"", "User":"", @@ -147,7 +149,8 @@ Inspect a container .. sourcecode:: http HTTP/1.1 200 OK - + Content-Type: application/json + { "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2", "Created": "2013-05-07T14:51:42.041847+02:00", @@ -218,6 +221,7 @@ Inspect changes on a container's filesystem .. sourcecode:: http HTTP/1.1 200 OK + Content-Type: application/json [ { @@ -258,7 +262,7 @@ Export a container .. sourcecode:: http HTTP/1.1 200 OK - Content-Type: raw-stream-hijack + Content-Type: application/vnd.docker.raw-stream {{ STREAM }} @@ -383,7 +387,7 @@ Attach to a container .. sourcecode:: http HTTP/1.1 200 OK - Content-Type: raw-stream-hijack + Content-Type: application/vnd.docker.raw-stream {{ STREAM }} @@ -415,6 +419,7 @@ Wait a container .. sourcecode:: http HTTP/1.1 200 OK + Content-Type: application/json {"StatusCode":0} @@ -469,6 +474,7 @@ List Images .. sourcecode:: http HTTP/1.1 200 OK + Content-Type: application/json [ { @@ -509,7 +515,7 @@ Create an image .. sourcecode:: http HTTP/1.1 200 OK - Content-Type: raw-stream-hijack + Content-Type: application/vnd.docker.raw-stream {{ STREAM }} @@ -565,6 +571,7 @@ Inspect an image .. sourcecode:: http HTTP/1.1 200 OK + Content-Type: application/json { "id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", @@ -616,6 +623,7 @@ Get the history of an image .. sourcecode:: http HTTP/1.1 200 OK + Content-Type: application/json [ { @@ -653,7 +661,7 @@ Push an image on the registry .. sourcecode:: http HTTP/1.1 200 OK - Content-Type: raw-stream-hijack + Content-Type: application/vnd.docker.raw-stream {{ STREAM }} @@ -729,6 +737,9 @@ Search images **Example response**: .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json [ { @@ -798,6 +809,7 @@ Get default username and email .. sourcecode:: http HTTP/1.1 200 OK + Content-Type: application/json { "username":"hannibal", @@ -820,6 +832,7 @@ Set auth configuration .. sourcecode:: http POST /auth HTTP/1.1 + Content-Type: application/json { "username":"hannibal", @@ -855,6 +868,7 @@ Display system-wide information .. sourcecode:: http HTTP/1.1 200 OK + Content-Type: application/json { "Containers":11, @@ -885,6 +899,7 @@ Show the docker version information .. sourcecode:: http HTTP/1.1 200 OK + Content-Type: application/json { "Version":"0.2.2", @@ -915,7 +930,7 @@ Create a new image from a container's changes .. sourcecode:: http HTTP/1.1 200 OK - Content-Type: raw-stream-hijack + Content-Type: application/vnd.docker.raw-stream {{ STREAM }} From bf605fcfc77aeb1ef5d8c00072b6c2c1f988ebcd Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 8 May 2013 19:21:52 +0200 Subject: [PATCH 42/69] fix commit without run parameter --- api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.go b/api.go index 3d6484964..6f6e5dfc7 100644 --- a/api.go +++ b/api.go @@ -232,7 +232,7 @@ func postCommit(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, er } var config Config if err := json.NewDecoder(r.Body).Decode(&config); err != nil { - return nil, err + Debugf("%s", err.Error()) } repo := r.Form.Get("repo") tag := r.Form.Get("tag") From 1d42cbaa21826e1594911742b8ead439da11882e Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 8 May 2013 23:19:24 +0200 Subject: [PATCH 43/69] removed useless returns --- api.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/api.go b/api.go index 6f6e5dfc7..cc82f2f37 100644 --- a/api.go +++ b/api.go @@ -111,7 +111,6 @@ func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request) ([ fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") if err := srv.ContainerExport(name, out); err != nil { fmt.Fprintf(out, "Error: %s\n", err) - return nil, err } return nil, nil } @@ -270,12 +269,10 @@ func postImages(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, er registry := r.Form.Get("registry") if err := srv.ImagePull(image, tag, registry, out); err != nil { fmt.Fprintf(out, "Error: %s\n", err) - return nil, err } } else { //import if err := srv.ImageImport(src, repo, tag, in, out); err != nil { fmt.Fprintf(out, "Error: %s\n", err) - return nil, err } } return nil, nil @@ -316,7 +313,6 @@ func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request) ([]by fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") if err := srv.ImageInsert(name, url, path, out); err != nil { fmt.Fprintf(out, "Error: %s\n", err) - return nil, err } return nil, nil } @@ -339,7 +335,6 @@ func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") if err := srv.ImagePush(name, registry, out); err != nil { fmt.Fprintln(out, "Error: %s\n", err) - return nil, err } return nil, nil } @@ -353,7 +348,6 @@ func postBuild(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, err fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") if err := srv.ImageCreateFromFile(in, out); err != nil { fmt.Fprintln(out, "Error: %s\n", err) - return nil, err } return nil, nil } @@ -487,7 +481,6 @@ func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request) ( fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, in, out); err != nil { fmt.Fprintf(out, "Error: %s\n", err) - return nil, err } return nil, nil } From 24c785bc062c19488a47d9ed9ed2799a3c6783f8 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 8 May 2013 23:57:14 +0200 Subject: [PATCH 44/69] fix login --- api.go | 3 +-- commands.go | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api.go b/api.go index cc82f2f37..771242fb2 100644 --- a/api.go +++ b/api.go @@ -53,7 +53,6 @@ func getAuth(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error func postAuth(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { var config auth.AuthConfig if err := json.NewDecoder(r.Body).Decode(&config); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) return nil, err } @@ -73,8 +72,8 @@ func postAuth(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, erro b, err := json.Marshal(ApiAuth{status}) if err != nil { return nil, err - return b, nil } + return b, nil } w.WriteHeader(http.StatusOK) return nil, nil diff --git a/commands.go b/commands.go index 167834397..a6aa1c46c 100644 --- a/commands.go +++ b/commands.go @@ -270,11 +270,12 @@ func CmdLogin(args ...string) error { } var out2 ApiAuth - err = json.Unmarshal(body, &out) + err = json.Unmarshal(body, &out2) if err != nil { return err } if out2.Status != "" { + RestoreTerminal(oldState) fmt.Print(out2.Status) } return nil From 842cb8909e1194a4650db6c6c4d1891ac2fd5a88 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 9 May 2013 03:46:39 +0200 Subject: [PATCH 45/69] pretty print json in inspect --- commands.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/commands.go b/commands.go index b2db3b32d..65ae50e4c 100644 --- a/commands.go +++ b/commands.go @@ -457,7 +457,14 @@ func CmdInspect(args ...string) error { return err } } - fmt.Printf("%s\n", obj) + + indented := new(bytes.Buffer) + if err = json.Indent(indented, obj, "", " "); err != nil { + return err + } + if _, err := io.Copy(os.Stdout, indented); err != nil { + return err + } return nil } From 4576e11121581c92a436a53ad347bccbfc921a9b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 9 May 2013 17:54:41 +0200 Subject: [PATCH 46/69] fix attach and remove cli doc --- commands.go | 3 +- docs/sources/commandline/cli.rst | 55 ------------------ docs/sources/commandline/command/attach.rst | 9 --- docs/sources/commandline/command/build.rst | 9 --- docs/sources/commandline/command/commit.rst | 27 --------- docs/sources/commandline/command/diff.rst | 9 --- docs/sources/commandline/command/export.rst | 9 --- docs/sources/commandline/command/history.rst | 9 --- docs/sources/commandline/command/images.rst | 22 ------- .../command/images/docker_images.gif | Bin 35785 -> 0 bytes docs/sources/commandline/command/import.rst | 9 --- docs/sources/commandline/command/info.rst | 9 --- docs/sources/commandline/command/inspect.rst | 9 --- docs/sources/commandline/command/kill.rst | 9 --- docs/sources/commandline/command/login.rst | 9 --- docs/sources/commandline/command/logs.rst | 9 --- docs/sources/commandline/command/port.rst | 9 --- docs/sources/commandline/command/ps.rst | 13 ----- docs/sources/commandline/command/pull.rst | 9 --- docs/sources/commandline/command/push.rst | 9 --- docs/sources/commandline/command/restart.rst | 9 --- docs/sources/commandline/command/rm.rst | 9 --- docs/sources/commandline/command/rmi.rst | 9 --- docs/sources/commandline/command/run.rst | 22 ------- docs/sources/commandline/command/search.rst | 10 ---- docs/sources/commandline/command/start.rst | 9 --- docs/sources/commandline/command/stop.rst | 9 --- docs/sources/commandline/command/tag.rst | 11 ---- docs/sources/commandline/command/version.rst | 3 - docs/sources/commandline/command/wait.rst | 9 --- docs/sources/commandline/index.rst | 3 +- 31 files changed, 2 insertions(+), 347 deletions(-) delete mode 100644 docs/sources/commandline/cli.rst delete mode 100644 docs/sources/commandline/command/attach.rst delete mode 100644 docs/sources/commandline/command/build.rst delete mode 100644 docs/sources/commandline/command/commit.rst delete mode 100644 docs/sources/commandline/command/diff.rst delete mode 100644 docs/sources/commandline/command/export.rst delete mode 100644 docs/sources/commandline/command/history.rst delete mode 100644 docs/sources/commandline/command/images.rst delete mode 100644 docs/sources/commandline/command/images/docker_images.gif delete mode 100644 docs/sources/commandline/command/import.rst delete mode 100644 docs/sources/commandline/command/info.rst delete mode 100644 docs/sources/commandline/command/inspect.rst delete mode 100644 docs/sources/commandline/command/kill.rst delete mode 100644 docs/sources/commandline/command/login.rst delete mode 100644 docs/sources/commandline/command/logs.rst delete mode 100644 docs/sources/commandline/command/port.rst delete mode 100644 docs/sources/commandline/command/ps.rst delete mode 100644 docs/sources/commandline/command/pull.rst delete mode 100644 docs/sources/commandline/command/push.rst delete mode 100644 docs/sources/commandline/command/restart.rst delete mode 100644 docs/sources/commandline/command/rm.rst delete mode 100644 docs/sources/commandline/command/rmi.rst delete mode 100644 docs/sources/commandline/command/run.rst delete mode 100644 docs/sources/commandline/command/search.rst delete mode 100644 docs/sources/commandline/command/start.rst delete mode 100644 docs/sources/commandline/command/stop.rst delete mode 100644 docs/sources/commandline/command/tag.rst delete mode 100644 docs/sources/commandline/command/version.rst delete mode 100644 docs/sources/commandline/command/wait.rst diff --git a/commands.go b/commands.go index 65ae50e4c..c08c20b86 100644 --- a/commands.go +++ b/commands.go @@ -949,7 +949,7 @@ func CmdAttach(args ...string) error { return nil } - body, _, err := call("GET", "/containers/"+cmd.Arg(0), nil) + body, _, err := call("GET", "/containers/"+cmd.Arg(0)+"/json", nil) if err != nil { return err } @@ -961,7 +961,6 @@ func CmdAttach(args ...string) error { } v := url.Values{} - v.Set("logs", "1") v.Set("stream", "1") v.Set("stdout", "1") v.Set("stderr", "1") diff --git a/docs/sources/commandline/cli.rst b/docs/sources/commandline/cli.rst deleted file mode 100644 index 47ecb79e6..000000000 --- a/docs/sources/commandline/cli.rst +++ /dev/null @@ -1,55 +0,0 @@ -:title: Command Line Interface -:description: Docker's CLI command description and usage -:keywords: Docker, Docker documentation, CLI, command line - -.. _cli: - -Command Line Interface -====================== - -Docker Usage -~~~~~~~~~~~~ - -To list available commands, either run ``docker`` with no parameters or execute -``docker help``:: - - $ docker - Usage: docker COMMAND [arg...] - - A self-sufficient runtime for linux containers. - - ... - -Available Commands -~~~~~~~~~~~~~~~~~~ - -.. toctree:: - :maxdepth: 1 - - command/attach - command/build - command/commit - command/diff - command/export - command/history - command/images - command/import - command/info - command/inspect - command/kill - command/login - command/logs - command/port - command/ps - command/pull - command/push - command/restart - command/rm - command/rmi - command/run - command/search - command/start - command/stop - command/tag - command/version - command/wait diff --git a/docs/sources/commandline/command/attach.rst b/docs/sources/commandline/command/attach.rst deleted file mode 100644 index ac9a84c0c..000000000 --- a/docs/sources/commandline/command/attach.rst +++ /dev/null @@ -1,9 +0,0 @@ -=========================================== -``attach`` -- Attach to a running container -=========================================== - -:: - - Usage: docker attach CONTAINER - - Attach to a running container diff --git a/docs/sources/commandline/command/build.rst b/docs/sources/commandline/command/build.rst deleted file mode 100644 index 6415f11f7..000000000 --- a/docs/sources/commandline/command/build.rst +++ /dev/null @@ -1,9 +0,0 @@ -=========================================== -``build`` -- Build a container from Dockerfile via stdin -=========================================== - -:: - - Usage: docker build - - Example: cat Dockerfile | docker build - - Build a new image from the Dockerfile passed via stdin diff --git a/docs/sources/commandline/command/commit.rst b/docs/sources/commandline/command/commit.rst deleted file mode 100644 index c73f8d189..000000000 --- a/docs/sources/commandline/command/commit.rst +++ /dev/null @@ -1,27 +0,0 @@ -=========================================================== -``commit`` -- Create a new image from a container's changes -=========================================================== - -:: - - Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY [TAG]] - - Create a new image from a container's changes - - -m="": Commit message - -author="": Author (eg. "John Hannibal Smith " - -run="": Config automatically applied when the image is run. "+`(ex: {"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}') - -Full -run example:: - - {"Hostname": "", - "User": "", - "Memory": 0, - "MemorySwap": 0, - "PortSpecs": ["22", "80", "443"], - "Tty": true, - "OpenStdin": true, - "StdinOnce": true, - "Env": ["FOO=BAR", "FOO2=BAR2"], - "Cmd": ["cat", "-e", "/etc/resolv.conf"], - "Dns": ["8.8.8.8", "8.8.4.4"]} diff --git a/docs/sources/commandline/command/diff.rst b/docs/sources/commandline/command/diff.rst deleted file mode 100644 index 301da6c49..000000000 --- a/docs/sources/commandline/command/diff.rst +++ /dev/null @@ -1,9 +0,0 @@ -======================================================= -``diff`` -- Inspect changes on a container's filesystem -======================================================= - -:: - - Usage: docker diff CONTAINER [OPTIONS] - - Inspect changes on a container's filesystem diff --git a/docs/sources/commandline/command/export.rst b/docs/sources/commandline/command/export.rst deleted file mode 100644 index 88ecd2fd5..000000000 --- a/docs/sources/commandline/command/export.rst +++ /dev/null @@ -1,9 +0,0 @@ -================================================================= -``export`` -- Stream the contents of a container as a tar archive -================================================================= - -:: - - Usage: docker export CONTAINER - - Export the contents of a filesystem as a tar archive diff --git a/docs/sources/commandline/command/history.rst b/docs/sources/commandline/command/history.rst deleted file mode 100644 index 92fad3b48..000000000 --- a/docs/sources/commandline/command/history.rst +++ /dev/null @@ -1,9 +0,0 @@ -=========================================== -``history`` -- Show the history of an image -=========================================== - -:: - - Usage: docker history [OPTIONS] IMAGE - - Show the history of an image diff --git a/docs/sources/commandline/command/images.rst b/docs/sources/commandline/command/images.rst deleted file mode 100644 index 5bcfe817f..000000000 --- a/docs/sources/commandline/command/images.rst +++ /dev/null @@ -1,22 +0,0 @@ -========================= -``images`` -- List images -========================= - -:: - - Usage: docker images [OPTIONS] [NAME] - - List images - - -a=false: show all images - -q=false: only show numeric IDs - -viz=false: output in graphviz format - -Displaying images visually --------------------------- - -:: - - docker images -viz | dot -Tpng -o docker.png - -.. image:: images/docker_images.gif diff --git a/docs/sources/commandline/command/images/docker_images.gif b/docs/sources/commandline/command/images/docker_images.gif deleted file mode 100644 index 5894ca270e002758b8f332141e00356e42868880..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35785 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 Date: Thu, 9 May 2013 20:24:49 +0200 Subject: [PATCH 47/69] display warning on the server in debug in version don't match --- api.go | 7 +++++++ commands.go | 27 ++++----------------------- 2 files changed, 11 insertions(+), 23 deletions(-) diff --git a/api.go b/api.go index 771242fb2..c661cc22b 100644 --- a/api.go +++ b/api.go @@ -564,6 +564,13 @@ func ListenAndServe(addr string, srv *Server) error { r.Path(localRoute).Methods(localMethod).HandlerFunc(func(w http.ResponseWriter, r *http.Request) { Debugf("Calling %s %s", localMethod, localRoute) log.Println(r.Method, r.RequestURI) + + if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") { + userAgent := strings.Split(r.Header.Get("User-Agent"), "/") + if len(userAgent) == 2 && userAgent[1] != VERSION { + Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], VERSION) + } + } json, err := localFct(srv, w, r) if err != nil { httpError(w, err) diff --git a/commands.go b/commands.go index c08c20b86..b63d23213 100644 --- a/commands.go +++ b/commands.go @@ -28,31 +28,8 @@ var ( GIT_COMMIT string ) -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 { - Debugf("Error unmarshal: body: %s, err: %s\n", body, err) - 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{ "attach": CmdAttach, "build": CmdBuild, @@ -1177,6 +1154,7 @@ func call(method, path string, data interface{}) ([]byte, int, error) { if err != nil { return nil, -1, err } + req.Header.Set("User-Agent", "Docker-Client/"+VERSION) if data != nil { req.Header.Set("Content-Type", "application/json") } else if method == "POST" { @@ -1184,6 +1162,9 @@ func call(method, path string, data interface{}) ([]byte, int, error) { } resp, err := http.DefaultClient.Do(req) if err != nil { + if strings.Contains(err.Error(), "connection refused") { + return nil, -1, fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?") + } return nil, -1, err } defer resp.Body.Close() From 0bcc5d3bee82d3cd7e51b88005c2105d3f399150 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 9 May 2013 20:36:08 +0200 Subject: [PATCH 48/69] re-add previously deleted doc files --- docs/sources/commandline/index.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/sources/commandline/index.rst b/docs/sources/commandline/index.rst index e6897a747..72290fa7a 100644 --- a/docs/sources/commandline/index.rst +++ b/docs/sources/commandline/index.rst @@ -9,7 +9,8 @@ Commands Contents: .. toctree:: - :maxdepth: 2 + :maxdepth: 3 basics workingwithrepository + cli From 0862183c867d5f738184afb80fc4d1f2356364c6 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 9 May 2013 21:42:29 +0200 Subject: [PATCH 49/69] fix status code and error detection --- api.go | 16 +++++++++------- commands.go | 3 +-- docs/sources/remote-api/api.rst | 31 ++++++++++++++++--------------- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/api.go b/api.go index c661cc22b..7cbc1ce6b 100644 --- a/api.go +++ b/api.go @@ -75,7 +75,7 @@ func postAuth(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, erro } return b, nil } - w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusNoContent) return nil, nil } @@ -94,7 +94,7 @@ func postContainersKill(srv *Server, w http.ResponseWriter, r *http.Request) ([] if err := srv.ContainerKill(name); err != nil { return nil, err } - w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusNoContent) return nil, nil } @@ -245,6 +245,7 @@ func postCommit(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, er if err != nil { return nil, err } + w.WriteHeader(http.StatusCreated) return b, nil } @@ -372,6 +373,7 @@ func postContainers(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte if err != nil { return nil, err } + w.WriteHeader(http.StatusCreated) return b, nil } @@ -388,7 +390,7 @@ func postContainersRestart(srv *Server, w http.ResponseWriter, r *http.Request) if err := srv.ContainerRestart(name, t); err != nil { return nil, err } - w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusNoContent) return nil, nil } @@ -403,7 +405,7 @@ func deleteContainers(srv *Server, w http.ResponseWriter, r *http.Request) ([]by if err := srv.ContainerDestroy(name, v); err != nil { return nil, err } - w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusNoContent) return nil, nil } @@ -413,7 +415,7 @@ func deleteImages(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, if err := srv.ImageDelete(name); err != nil { return nil, err } - w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusNoContent) return nil, nil } @@ -423,7 +425,7 @@ func postContainersStart(srv *Server, w http.ResponseWriter, r *http.Request) ([ if err := srv.ContainerStart(name); err != nil { return nil, err } - w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusNoContent) return nil, nil } @@ -441,7 +443,7 @@ func postContainersStop(srv *Server, w http.ResponseWriter, r *http.Request) ([] if err := srv.ContainerStop(name, t); err != nil { return nil, err } - w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusNoContent) return nil, nil } diff --git a/commands.go b/commands.go index b63d23213..1068907d0 100644 --- a/commands.go +++ b/commands.go @@ -1172,11 +1172,10 @@ func call(method, path string, data interface{}) ([]byte, int, error) { if err != nil { return nil, -1, err } - if resp.StatusCode != 200 { + if resp.StatusCode < 200 || resp.StatusCode >= 400 { return nil, resp.StatusCode, fmt.Errorf("error: %s", body) } return body, resp.StatusCode, nil - } func hijack(method, path string, setRawTerminal bool) error { diff --git a/docs/sources/remote-api/api.rst b/docs/sources/remote-api/api.rst index 124e57616..c5ec081c8 100644 --- a/docs/sources/remote-api/api.rst +++ b/docs/sources/remote-api/api.rst @@ -118,7 +118,7 @@ Create a container .. sourcecode:: http - HTTP/1.1 200 OK + HTTP/1.1 201 OK { "Id":"e90e34656806" @@ -126,7 +126,7 @@ Create a container } :jsonparam config: the container's configuration - :statuscode 200: no error + :statuscode 201: no error :statuscode 404: no such container :statuscode 500: server error @@ -312,10 +312,10 @@ Stop a contaier .. sourcecode:: http - HTTP/1.1 200 OK + HTTP/1.1 204 OK :query t: number of seconds to wait before killing the container - :statuscode 200: no error + :statuscode 204: no error :statuscode 404: no such container :statuscode 500: server error @@ -337,10 +337,10 @@ Restart a container .. sourcecode:: http - HTTP/1.1 200 OK + HTTP/1.1 204 OK :query t: number of seconds to wait before killing the container - :statuscode 200: no error + :statuscode 204: no error :statuscode 404: no such container :statuscode 500: server error @@ -362,9 +362,9 @@ Kill a container .. sourcecode:: http - HTTP/1.1 200 OK + HTTP/1.1 204 OK - :statuscode 200: no error + :statuscode 204: no error :statuscode 404: no such container :statuscode 500: server error @@ -445,10 +445,10 @@ Remove a container .. sourcecode:: http - HTTP/1.1 200 OK + HTTP/1.1 204 OK :query v: 1 or 0, Remove the volumes associated to the container. Default 0 - :statuscode 200: no error + :statuscode 204: no error :statuscode 404: no such container :statuscode 500: server error @@ -714,9 +714,9 @@ Remove an image .. sourcecode:: http - HTTP/1.1 200 OK + HTTP/1.1 204 OK - :statuscode 200: no error + :statuscode 204: no error :statuscode 404: no such image :statuscode 500: server error @@ -847,6 +847,7 @@ Set auth configuration HTTP/1.1 200 OK :statuscode 200: no error + :statuscode 204: no error :statuscode 500: server error @@ -930,10 +931,10 @@ Create a new image from a container's changes .. sourcecode:: http - HTTP/1.1 200 OK + HTTP/1.1 201 OK Content-Type: application/vnd.docker.raw-stream - {{ STREAM }} + {"Id":"596069db4bf5"} :query container: source container :query repo: repository @@ -941,7 +942,7 @@ Create a new image from a container's changes :query m: commit message :query author: author (eg. "John Hannibal Smith ") :query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]}) - :statuscode 200: no error + :statuscode 201: no error :statuscode 404: no such container :statuscode 500: server error From 0ecf5e245dac26a81fa51a189419df29ccefc79a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 9 May 2013 23:10:26 +0200 Subject: [PATCH 50/69] removed hijack on viz --- api.go | 42 ++++++++++++++++----------------- commands.go | 4 +++- docs/sources/remote-api/api.rst | 39 +++++++++++++++++++++++++++--- server.go | 11 ++++----- 4 files changed, 64 insertions(+), 32 deletions(-) diff --git a/api.go b/api.go index 7cbc1ce6b..d387bce61 100644 --- a/api.go +++ b/api.go @@ -115,37 +115,34 @@ func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request) ([ } func getImages(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { + vars := mux.Vars(r) + format := vars["format"] + if err := parseForm(r); err != nil { return nil, err } - viz := r.Form.Get("viz") == "1" - if viz { - in, out, err := hijackServer(w) + if format == "viz" { + if err := srv.ImagesViz(w); err != nil { + return nil, err + } + return nil, nil + } else if format == "" || format == "json" { + all := r.Form.Get("all") == "1" + filter := r.Form.Get("filter") + only_ids := r.Form.Get("only_ids") == "1" + + outs, err := srv.Images(all, only_ids, filter) if err != nil { return nil, err } - defer in.Close() - fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") - if err := srv.ImagesViz(out); err != nil { - fmt.Fprintf(out, "Error: %s\n", err) + b, err := json.Marshal(outs) + if err != nil { + return nil, err } - return nil, nil + return b, nil } - - all := r.Form.Get("all") == "1" - filter := r.Form.Get("filter") - only_ids := r.Form.Get("only_ids") == "1" - - outs, err := srv.Images(all, only_ids, filter) - if err != nil { - return nil, err - } - b, err := json.Marshal(outs) - if err != nil { - return nil, err - } - return b, nil + return nil, fmt.Errorf("No such format: %s", format) } func getInfo(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { @@ -526,6 +523,7 @@ func ListenAndServe(addr string, srv *Server) error { "/version": getVersion, "/containers/{name:.*}/export": getContainersExport, "/images": getImages, + "/images/{format}": getImages, "/info": getInfo, "/images/search": getImagesSearch, "/images/{name:.*}/history": getImagesHistory, diff --git a/commands.go b/commands.go index 1068907d0..44eddd2af 100644 --- a/commands.go +++ b/commands.go @@ -699,9 +699,11 @@ func CmdImages(args ...string) error { } if *flViz { - if err := hijack("GET", "/images?viz=1", false); err != nil { + body, _, err := call("GET", "/images/viz", false) + if err != nil { return err } + fmt.Printf("%s", body) } else { v := url.Values{} if cmd.NArg() == 1 { diff --git a/docs/sources/remote-api/api.rst b/docs/sources/remote-api/api.rst index c5ec081c8..350d6c589 100644 --- a/docs/sources/remote-api/api.rst +++ b/docs/sources/remote-api/api.rst @@ -460,15 +460,16 @@ List Images *********** .. http:get:: /images +.. http:get:: /images/(format) - List images + List images ``format`` could be json or viz (json default) **Example request**: .. sourcecode:: http - GET /images?all=0&only_ids=0 HTTP/1.1 - + GET /images/json?all=0&only_ids=0 HTTP/1.1 + **Example response**: .. sourcecode:: http @@ -490,6 +491,38 @@ List Images "Created":1364102658 } ] + + + **Example request**: + + .. sourcecode:: http + + GET /images/viz HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: text/plain + + digraph docker { + "d82cbacda43a" -> "074be284591f" + "1496068ca813" -> "08306dc45919" + "08306dc45919" -> "0e7893146ac2" + "b750fe79269d" -> "1496068ca813" + base -> "27cf78414709" [style=invis] + "f71189fff3de" -> "9a33b36209ed" + "27cf78414709" -> "b750fe79269d" + "0e7893146ac2" -> "d6434d954665" + "d6434d954665" -> "d82cbacda43a" + base -> "e9aa60c60128" [style=invis] + "074be284591f" -> "f71189fff3de" + "b750fe79269d" [label="b750fe79269d\nbase",shape=box,fillcolor="paleturquoise",style="filled,rounded"]; + "e9aa60c60128" [label="e9aa60c60128\nbase2",shape=box,fillcolor="paleturquoise",style="filled,rounded"]; + "9a33b36209ed" [label="9a33b36209ed\ntest",shape=box,fillcolor="paleturquoise",style="filled,rounded"]; + base [style=invisible] + } :query only_ids: 1 or 0, Only display numeric IDs. Default 0 :query all: 1 or 0, Show all containers. Only running containers are shown by default diff --git a/server.go b/server.go index bf312b347..75cafb7a1 100644 --- a/server.go +++ b/server.go @@ -102,8 +102,7 @@ func (srv *Server) ImagesViz(out io.Writer) error { if images == nil { return nil } - - fmt.Fprintf(out, "digraph docker {\n") + out.Write([]byte("digraph docker {\n")) var ( parentImage *Image @@ -115,9 +114,9 @@ func (srv *Server) ImagesViz(out io.Writer) error { return fmt.Errorf("Error while getting parent image: %v", err) } if parentImage != nil { - fmt.Fprintf(out, " \"%s\" -> \"%s\"\n", parentImage.ShortId(), image.ShortId()) + out.Write([]byte(" \"" + parentImage.ShortId() + "\" -> \"" + image.ShortId() + "\"\n")) } else { - fmt.Fprintf(out, " base -> \"%s\" [style=invis]\n", image.ShortId()) + out.Write([]byte(" base -> \"" + image.ShortId() + "\" [style=invis]\n")) } } @@ -130,9 +129,9 @@ func (srv *Server) ImagesViz(out io.Writer) error { } for id, repos := range reporefs { - fmt.Fprintf(out, " \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n", id, id, strings.Join(repos, "\\n")) + out.Write([]byte(" \"" + id + "\" [label=\"" + id + "\\n" + strings.Join(repos, "\\n") + "\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n")) } - fmt.Fprintf(out, " base [style=invisible]\n}\n") + out.Write([]byte(" base [style=invisible]\n}\n")) return nil } From 93dc2c331e198a7988a672902967143f0ca7ad19 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 9 May 2013 23:28:03 +0200 Subject: [PATCH 51/69] removed hijack in export --- api.go | 11 +++-------- commands.go | 25 ++++++++++++++++++++++++- docs/sources/remote-api/api.rst | 2 +- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/api.go b/api.go index d387bce61..6929bbd6e 100644 --- a/api.go +++ b/api.go @@ -102,14 +102,9 @@ func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request) ([ vars := mux.Vars(r) name := vars["name"] - in, out, err := hijackServer(w) - if err != nil { - return nil, err - } - defer in.Close() - fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") - if err := srv.ContainerExport(name, out); err != nil { - fmt.Fprintf(out, "Error: %s\n", err) + if err := srv.ContainerExport(name, w); err != nil { + Debugf("%s", err.Error()) + //return nil, err } return nil, nil } diff --git a/commands.go b/commands.go index 44eddd2af..63a1f3ed3 100644 --- a/commands.go +++ b/commands.go @@ -865,7 +865,7 @@ func CmdExport(args ...string) error { return nil } - if err := hijack("GET", "/containers/"+cmd.Arg(0)+"/export", false); err != nil { + if err := stream("GET", "/containers/"+cmd.Arg(0)+"/export"); err != nil { return err } return nil @@ -1180,6 +1180,29 @@ func call(method, path string, data interface{}) ([]byte, int, error) { return body, resp.StatusCode, nil } +func stream(method, path string) error { + req, err := http.NewRequest(method, "http://0.0.0.0:4243"+path, nil) + if err != nil { + return err + } + req.Header.Set("User-Agent", "Docker-Client/"+VERSION) + if method == "POST" { + req.Header.Set("Content-Type", "plain/text") + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + if strings.Contains(err.Error(), "connection refused") { + return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?") + } + return err + } + defer resp.Body.Close() + if _, err := io.Copy(os.Stdout, resp.Body); err != nil { + return err + } + return nil +} + func hijack(method, path string, setRawTerminal bool) error { req, err := http.NewRequest(method, path, nil) if err != nil { diff --git a/docs/sources/remote-api/api.rst b/docs/sources/remote-api/api.rst index 350d6c589..0e9ded548 100644 --- a/docs/sources/remote-api/api.rst +++ b/docs/sources/remote-api/api.rst @@ -262,7 +262,7 @@ Export a container .. sourcecode:: http HTTP/1.1 200 OK - Content-Type: application/vnd.docker.raw-stream + Content-Type: application/octet-stream {{ STREAM }} From c423a790d629b13c2eb1bc553beb3f61e30b3888 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 9 May 2013 23:52:12 +0200 Subject: [PATCH 52/69] fixed issue with viz --- api.go | 50 +++++++++++++++++++++++------------------------- api_test.go | 2 +- docker/docker.go | 2 +- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/api.go b/api.go index 6929bbd6e..c62fd82f5 100644 --- a/api.go +++ b/api.go @@ -110,34 +110,30 @@ func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request) ([ } func getImages(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { - vars := mux.Vars(r) - format := vars["format"] - if err := parseForm(r); err != nil { return nil, err } - if format == "viz" { - if err := srv.ImagesViz(w); err != nil { - return nil, err - } - return nil, nil - } else if format == "" || format == "json" { - all := r.Form.Get("all") == "1" - filter := r.Form.Get("filter") - only_ids := r.Form.Get("only_ids") == "1" + all := r.Form.Get("all") == "1" + filter := r.Form.Get("filter") + only_ids := r.Form.Get("only_ids") == "1" - outs, err := srv.Images(all, only_ids, filter) - if err != nil { - return nil, err - } - b, err := json.Marshal(outs) - if err != nil { - return nil, err - } - return b, nil + outs, err := srv.Images(all, only_ids, filter) + if err != nil { + return nil, err } - return nil, fmt.Errorf("No such format: %s", format) + b, err := json.Marshal(outs) + if err != nil { + return nil, err + } + return b, nil +} + +func getImagesViz(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { + if err := srv.ImagesViz(w); err != nil { + return nil, err + } + return nil, nil } func getInfo(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { @@ -508,7 +504,7 @@ func getImagesByName(srv *Server, w http.ResponseWriter, r *http.Request) ([]byt return b, nil } -func ListenAndServe(addr string, srv *Server) error { +func ListenAndServe(addr string, srv *Server, logging bool) error { r := mux.NewRouter() log.Printf("Listening for HTTP on %s\n", addr) @@ -518,7 +514,8 @@ func ListenAndServe(addr string, srv *Server) error { "/version": getVersion, "/containers/{name:.*}/export": getContainersExport, "/images": getImages, - "/images/{format}": getImages, + "/images/json": getImages, + "/images/viz": getImagesViz, "/info": getInfo, "/images/search": getImagesSearch, "/images/{name:.*}/history": getImagesHistory, @@ -558,8 +555,9 @@ func ListenAndServe(addr string, srv *Server) error { localFct := fct r.Path(localRoute).Methods(localMethod).HandlerFunc(func(w http.ResponseWriter, r *http.Request) { Debugf("Calling %s %s", localMethod, localRoute) - log.Println(r.Method, r.RequestURI) - + if logging { + log.Println(r.Method, r.RequestURI) + } if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") { userAgent := strings.Split(r.Header.Get("User-Agent"), "/") if len(userAgent) == 2 && userAgent[1] != VERSION { diff --git a/api_test.go b/api_test.go index d92eacf51..d8c15a301 100644 --- a/api_test.go +++ b/api_test.go @@ -17,7 +17,7 @@ func init() { srv := &Server{ runtime: runtime, } - go ListenAndServe("0.0.0.0:4243", srv) + go ListenAndServe("0.0.0.0:4243", srv, false) } diff --git a/docker/docker.go b/docker/docker.go index 7cfc39c03..778326a81 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -102,5 +102,5 @@ func daemon(pidfile string, autoRestart bool) error { return err } - return docker.ListenAndServe("0.0.0.0:4243", server) + return docker.ListenAndServe("0.0.0.0:4243", server, true) } From b2b59ddb10ea0bc9d7f0f39cea5f1d8742d1e83e Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 9 May 2013 15:47:06 -0700 Subject: [PATCH 53/69] Remove ListenAndServe from unit tests --- api_test.go | 414 +++++++++++++++++++++++++++++----------------------- 1 file changed, 228 insertions(+), 186 deletions(-) diff --git a/api_test.go b/api_test.go index d8c15a301..1b46d3ee9 100644 --- a/api_test.go +++ b/api_test.go @@ -1,240 +1,282 @@ package docker import ( + "bytes" "encoding/json" "github.com/dotcloud/docker/auth" + "net/http" + "net/http/httptest" "testing" ) -func init() { - // Make it our Store root - runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false) - if err != nil { - panic(err) - } +// func init() { +// // Make it our Store root +// runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false) +// if err != nil { +// panic(err) +// } - // Create the "Server" - srv := &Server{ - runtime: runtime, - } - go ListenAndServe("0.0.0.0:4243", srv, false) - -} +// // Create the "Server" +// srv := &Server{ +// runtime: runtime, +// } +// go ListenAndServe("0.0.0.0:4243", srv, false) +// } func TestAuth(t *testing.T) { - var out auth.AuthConfig + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) - out.Username = "utest" - out.Password = "utest" - out.Email = "utest@yopmail.com" + srv := &Server{runtime: runtime} - _, _, err := call("POST", "/auth", out) + r := httptest.NewRecorder() + + authConfig := &auth.AuthConfig{ + Username: "utest", + Password: "utest", + Email: "utest@yopmail.com", + } + + authConfigJson, err := json.Marshal(authConfig) if err != nil { t.Fatal(err) } - out.Username = "" - out.Password = "" - out.Email = "" - - body, _, err := call("GET", "/auth", nil) + req, err := http.NewRequest("POST", "/auth", bytes.NewReader(authConfigJson)) if err != nil { t.Fatal(err) } - err = json.Unmarshal(body, &out) + body, err := postAuth(srv, r, req) + if err != nil { + t.Fatal(err) + } + if body != nil { + t.Fatalf("No body expected, received: %s\n", body) + } + + if r.Code != http.StatusNoContent { + t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) + } + + authConfig = &auth.AuthConfig{} + + req, err = http.NewRequest("GET", "/auth", nil) if err != nil { t.Fatal(err) } - if out.Username != "utest" { - t.Errorf("Expected username to be utest, %s found", out.Username) + body, err = getAuth(srv, nil, req) + if err != nil { + t.Fatal(err) + } + + err = json.Unmarshal(body, authConfig) + if err != nil { + t.Fatal(err) + } + + if authConfig.Username != "utest" { + t.Errorf("Expected username to be utest, %s found", authConfig.Username) } } func TestVersion(t *testing.T) { - body, _, err := call("GET", "/version", nil) + runtime, err := newTestRuntime() if err != nil { t.Fatal(err) } - var out ApiVersion - err = json.Unmarshal(body, &out) + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + body, err := getVersion(srv, nil, nil) if err != nil { t.Fatal(err) } - if out.Version != VERSION { - t.Errorf("Excepted version %s, %s found", VERSION, out.Version) + + v := &ApiVersion{} + + err = json.Unmarshal(body, v) + if err != nil { + t.Fatal(err) + } + if v.Version != VERSION { + t.Errorf("Excepted version %s, %s found", VERSION, v.Version) } } -func TestImages(t *testing.T) { - body, _, err := call("GET", "/images?quiet=0&all=0", nil) - if err != nil { - t.Fatal(err) - } - var outs []ApiImages - err = json.Unmarshal(body, &outs) - if err != nil { - t.Fatal(err) - } +// func TestImages(t *testing.T) { +// body, _, err := call("GET", "/images?quiet=0&all=0", nil) +// if err != nil { +// t.Fatal(err) +// } +// var outs []ApiImages +// err = json.Unmarshal(body, &outs) +// if err != nil { +// t.Fatal(err) +// } - if len(outs) != 1 { - t.Errorf("Excepted 1 image, %d found", len(outs)) - } +// if len(outs) != 1 { +// t.Errorf("Excepted 1 image, %d found", len(outs)) +// } - if outs[0].Repository != "docker-ut" { - t.Errorf("Excepted image docker-ut, %s found", outs[0].Repository) - } -} +// if outs[0].Repository != "docker-ut" { +// t.Errorf("Excepted image docker-ut, %s found", outs[0].Repository) +// } +// } -func TestInfo(t *testing.T) { - body, _, err := call("GET", "/info", nil) - if err != nil { - t.Fatal(err) - } - var out ApiInfo - err = json.Unmarshal(body, &out) - if err != nil { - t.Fatal(err) - } - if out.Version != VERSION { - t.Errorf("Excepted version %s, %s found", VERSION, out.Version) - } -} +// func TestInfo(t *testing.T) { +// body, _, err := call("GET", "/info", nil) +// if err != nil { +// t.Fatal(err) +// } +// var out ApiInfo +// err = json.Unmarshal(body, &out) +// if err != nil { +// t.Fatal(err) +// } +// if out.Version != VERSION { +// t.Errorf("Excepted version %s, %s found", VERSION, out.Version) +// } +// } -func TestHistory(t *testing.T) { - body, _, err := call("GET", "/images/"+unitTestImageName+"/history", nil) - if err != nil { - t.Fatal(err) - } - var outs []ApiHistory - err = json.Unmarshal(body, &outs) - if err != nil { - t.Fatal(err) - } - if len(outs) != 1 { - t.Errorf("Excepted 1 line, %d found", len(outs)) - } -} +// func TestHistory(t *testing.T) { +// body, _, err := call("GET", "/images/"+unitTestImageName+"/history", nil) +// if err != nil { +// t.Fatal(err) +// } +// var outs []ApiHistory +// err = json.Unmarshal(body, &outs) +// if err != nil { +// t.Fatal(err) +// } +// if len(outs) != 1 { +// t.Errorf("Excepted 1 line, %d found", len(outs)) +// } +// } -func TestImagesSearch(t *testing.T) { - body, _, err := call("GET", "/images/search?term=redis", nil) - if err != nil { - t.Fatal(err) - } - var outs []ApiSearch - err = json.Unmarshal(body, &outs) - if err != nil { - t.Fatal(err) - } - if len(outs) < 2 { - t.Errorf("Excepted at least 2 lines, %d found", len(outs)) - } -} +// func TestImagesSearch(t *testing.T) { +// body, _, err := call("GET", "/images/search?term=redis", nil) +// if err != nil { +// t.Fatal(err) +// } +// var outs []ApiSearch +// err = json.Unmarshal(body, &outs) +// if err != nil { +// t.Fatal(err) +// } +// if len(outs) < 2 { +// t.Errorf("Excepted at least 2 lines, %d found", len(outs)) +// } +// } -func TestGetImage(t *testing.T) { - obj, _, err := call("GET", "/images/"+unitTestImageName+"/json", nil) - if err != nil { - t.Fatal(err) - } - var out Image - err = json.Unmarshal(obj, &out) - if err != nil { - t.Fatal(err) - } - if out.Comment != "Imported from http://get.docker.io/images/busybox" { - t.Errorf("Error inspecting image") - } -} +// func TestGetImage(t *testing.T) { +// obj, _, err := call("GET", "/images/"+unitTestImageName+"/json", nil) +// if err != nil { +// t.Fatal(err) +// } +// var out Image +// err = json.Unmarshal(obj, &out) +// if err != nil { +// t.Fatal(err) +// } +// if out.Comment != "Imported from http://get.docker.io/images/busybox" { +// t.Errorf("Error inspecting image") +// } +// } -func TestCreateListStartStopRestartKillWaitDelete(t *testing.T) { - containers := testListContainers(t, -1) - for _, container := range containers { - testDeleteContainer(t, container.Id) - } - testCreateContainer(t) - id := testListContainers(t, 1)[0].Id - testContainerStart(t, id) - testContainerStop(t, id) - testContainerRestart(t, id) - testContainerKill(t, id) - testContainerWait(t, id) - testDeleteContainer(t, id) - testListContainers(t, 0) -} +// func TestCreateListStartStopRestartKillWaitDelete(t *testing.T) { +// containers := testListContainers(t, -1) +// for _, container := range containers { +// testDeleteContainer(t, container.Id) +// } +// testCreateContainer(t) +// id := testListContainers(t, 1)[0].Id +// testContainerStart(t, id) +// testContainerStop(t, id) +// testContainerRestart(t, id) +// testContainerKill(t, id) +// testContainerWait(t, id) +// testDeleteContainer(t, id) +// testListContainers(t, 0) +// } -func testCreateContainer(t *testing.T) { - config, _, err := ParseRun([]string{unitTestImageName, "touch test"}, nil) - if err != nil { - t.Fatal(err) - } - _, _, err = call("POST", "/containers", *config) - if err != nil { - t.Fatal(err) - } -} +// func testCreateContainer(t *testing.T) { +// config, _, err := ParseRun([]string{unitTestImageName, "touch test"}, nil) +// if err != nil { +// t.Fatal(err) +// } +// _, _, err = call("POST", "/containers", *config) +// if err != nil { +// t.Fatal(err) +// } +// } -func testListContainers(t *testing.T, expected int) []ApiContainers { - body, _, err := call("GET", "/containers?quiet=1&all=1", nil) - if err != nil { - t.Fatal(err) - } - var outs []ApiContainers - err = json.Unmarshal(body, &outs) - if err != nil { - t.Fatal(err) - } - if expected >= 0 && len(outs) != expected { - t.Errorf("Excepted %d container, %d found", expected, len(outs)) - } - return outs -} +// func testListContainers(t *testing.T, expected int) []ApiContainers { +// body, _, err := call("GET", "/containers?quiet=1&all=1", nil) +// if err != nil { +// t.Fatal(err) +// } +// var outs []ApiContainers +// err = json.Unmarshal(body, &outs) +// if err != nil { +// t.Fatal(err) +// } +// if expected >= 0 && len(outs) != expected { +// t.Errorf("Excepted %d container, %d found", expected, len(outs)) +// } +// return outs +// } -func testContainerStart(t *testing.T, id string) { - _, _, err := call("POST", "/containers/"+id+"/start", nil) - if err != nil { - t.Fatal(err) - } -} +// func testContainerStart(t *testing.T, id string) { +// _, _, err := call("POST", "/containers/"+id+"/start", nil) +// if err != nil { +// t.Fatal(err) +// } +// } -func testContainerRestart(t *testing.T, id string) { - _, _, err := call("POST", "/containers/"+id+"/restart?t=1", nil) - if err != nil { - t.Fatal(err) - } -} +// func testContainerRestart(t *testing.T, id string) { +// _, _, err := call("POST", "/containers/"+id+"/restart?t=1", nil) +// if err != nil { +// t.Fatal(err) +// } +// } -func testContainerStop(t *testing.T, id string) { - _, _, err := call("POST", "/containers/"+id+"/stop?t=1", nil) - if err != nil { - t.Fatal(err) - } -} +// func testContainerStop(t *testing.T, id string) { +// _, _, err := call("POST", "/containers/"+id+"/stop?t=1", nil) +// if err != nil { +// t.Fatal(err) +// } +// } -func testContainerKill(t *testing.T, id string) { - _, _, err := call("POST", "/containers/"+id+"/kill", nil) - if err != nil { - t.Fatal(err) - } -} +// func testContainerKill(t *testing.T, id string) { +// _, _, err := call("POST", "/containers/"+id+"/kill", nil) +// if err != nil { +// t.Fatal(err) +// } +// } -func testContainerWait(t *testing.T, id string) { - _, _, err := call("POST", "/containers/"+id+"/wait", nil) - if err != nil { - t.Fatal(err) - } -} +// func testContainerWait(t *testing.T, id string) { +// _, _, err := call("POST", "/containers/"+id+"/wait", nil) +// if err != nil { +// t.Fatal(err) +// } +// } -func testDeleteContainer(t *testing.T, id string) { - _, _, err := call("DELETE", "/containers/"+id, nil) - if err != nil { - t.Fatal(err) - } -} +// func testDeleteContainer(t *testing.T, id string) { +// _, _, err := call("DELETE", "/containers/"+id, nil) +// if err != nil { +// t.Fatal(err) +// } +// } -func testContainerChanges(t *testing.T, id string) { - _, _, err := call("GET", "/containers/"+id+"/changes", nil) - if err != nil { - t.Fatal(err) - } -} +// func testContainerChanges(t *testing.T, id string) { +// _, _, err := call("GET", "/containers/"+id+"/changes", nil) +// if err != nil { +// t.Fatal(err) +// } +// } From 7e8b413bcf9d61819e46dda2fd567dcd379ce663 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 9 May 2013 16:20:06 -0700 Subject: [PATCH 54/69] Implement the some unit tests without listenandserve --- api_test.go | 115 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 78 insertions(+), 37 deletions(-) diff --git a/api_test.go b/api_test.go index 1b46d3ee9..9f2613455 100644 --- a/api_test.go +++ b/api_test.go @@ -54,12 +54,11 @@ func TestAuth(t *testing.T) { if err != nil { t.Fatal(err) } - if body != nil { - t.Fatalf("No body expected, received: %s\n", body) + if body == nil { + t.Fatalf("No body received\n") } - - if r.Code != http.StatusNoContent { - t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) + if r.Code != http.StatusOK { + t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) } authConfig = &auth.AuthConfig{} @@ -109,43 +108,85 @@ func TestVersion(t *testing.T) { } } -// func TestImages(t *testing.T) { -// body, _, err := call("GET", "/images?quiet=0&all=0", nil) -// if err != nil { -// t.Fatal(err) -// } -// var outs []ApiImages -// err = json.Unmarshal(body, &outs) -// if err != nil { -// t.Fatal(err) -// } +func TestImages(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) -// if len(outs) != 1 { -// t.Errorf("Excepted 1 image, %d found", len(outs)) -// } + srv := &Server{runtime: runtime} -// if outs[0].Repository != "docker-ut" { -// t.Errorf("Excepted image docker-ut, %s found", outs[0].Repository) -// } -// } + // FIXME: Do more tests with filter + req, err := http.NewRequest("GET", "/images?quiet=0&all=0", nil) + if err != nil { + t.Fatal(err) + } -// func TestInfo(t *testing.T) { -// body, _, err := call("GET", "/info", nil) -// if err != nil { -// t.Fatal(err) -// } -// var out ApiInfo -// err = json.Unmarshal(body, &out) -// if err != nil { -// t.Fatal(err) -// } -// if out.Version != VERSION { -// t.Errorf("Excepted version %s, %s found", VERSION, out.Version) -// } -// } + body, err := getImages(srv, nil, req) + if err != nil { + t.Fatal(err) + } + + images := []ApiImages{} + err = json.Unmarshal(body, &images) + if err != nil { + t.Fatal(err) + } + + if len(images) != 1 { + t.Errorf("Excepted 1 image, %d found", len(images)) + } + + if images[0].Repository != "docker-ut" { + t.Errorf("Excepted image docker-ut, %s found", images[0].Repository) + } +} + +func TestInfo(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + body, err := getInfo(srv, nil, nil) + if err != nil { + t.Fatal(err) + } + infos := &ApiInfo{} + err = json.Unmarshal(body, infos) + if err != nil { + t.Fatal(err) + } + if infos.Version != VERSION { + t.Errorf("Excepted version %s, %s found", VERSION, infos.Version) + } +} // func TestHistory(t *testing.T) { -// body, _, err := call("GET", "/images/"+unitTestImageName+"/history", nil) +// runtime, err := newTestRuntime() +// if err != nil { +// t.Fatal(err) +// } +// defer nuke(runtime) + +// srv := &Server{runtime: runtime} + +// req, err := http.NewRequest("GET", "/images/"+unitTestImageName+"/history", nil) +// if err != nil { +// t.Fatal(err) +// } + +// router := mux.NewRouter() +// router.Path("/images/{name:.*}/history") +// vars := mux.Vars(req) +// router. +// vars["name"] = unitTestImageName + +// body, err := getImagesHistory(srv, nil, req) // if err != nil { // t.Fatal(err) // } From 3115c5a2252c2c053774d91e0270076800de3305 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 10 May 2013 01:20:07 +0200 Subject: [PATCH 55/69] private tests api fixed --- api_test.go | 287 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 220 insertions(+), 67 deletions(-) diff --git a/api_test.go b/api_test.go index 1b46d3ee9..88c93512a 100644 --- a/api_test.go +++ b/api_test.go @@ -205,78 +205,231 @@ func TestVersion(t *testing.T) { // testListContainers(t, 0) // } -// func testCreateContainer(t *testing.T) { -// config, _, err := ParseRun([]string{unitTestImageName, "touch test"}, nil) -// if err != nil { -// t.Fatal(err) -// } -// _, _, err = call("POST", "/containers", *config) -// if err != nil { -// t.Fatal(err) -// } -// } +func testCreateContainer(t *testing.T) { -// func testListContainers(t *testing.T, expected int) []ApiContainers { -// body, _, err := call("GET", "/containers?quiet=1&all=1", nil) -// if err != nil { -// t.Fatal(err) -// } -// var outs []ApiContainers -// err = json.Unmarshal(body, &outs) -// if err != nil { -// t.Fatal(err) -// } -// if expected >= 0 && len(outs) != expected { -// t.Errorf("Excepted %d container, %d found", expected, len(outs)) -// } -// return outs -// } + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) -// func testContainerStart(t *testing.T, id string) { -// _, _, err := call("POST", "/containers/"+id+"/start", nil) -// if err != nil { -// t.Fatal(err) -// } -// } + srv := &Server{runtime: runtime} -// func testContainerRestart(t *testing.T, id string) { -// _, _, err := call("POST", "/containers/"+id+"/restart?t=1", nil) -// if err != nil { -// t.Fatal(err) -// } -// } + r := httptest.NewRecorder() -// func testContainerStop(t *testing.T, id string) { -// _, _, err := call("POST", "/containers/"+id+"/stop?t=1", nil) -// if err != nil { -// t.Fatal(err) -// } -// } + config, _, err := ParseRun([]string{unitTestImageName, "touch test"}, nil) + if err != nil { + t.Fatal(err) + } -// func testContainerKill(t *testing.T, id string) { -// _, _, err := call("POST", "/containers/"+id+"/kill", nil) -// if err != nil { -// t.Fatal(err) -// } -// } + configJson, err := json.Marshal(config) + if err != nil { + t.Fatal(err) + } -// func testContainerWait(t *testing.T, id string) { -// _, _, err := call("POST", "/containers/"+id+"/wait", nil) -// if err != nil { -// t.Fatal(err) -// } -// } + req, err := http.NewRequest("POST", "/containers", bytes.NewReader(configJson)) + if err != nil { + t.Fatal(err) + } -// func testDeleteContainer(t *testing.T, id string) { -// _, _, err := call("DELETE", "/containers/"+id, nil) -// if err != nil { -// t.Fatal(err) -// } -// } + body, err := postContainers(srv, r, req) + if err != nil { + t.Fatal(err) + } -// func testContainerChanges(t *testing.T, id string) { -// _, _, err := call("GET", "/containers/"+id+"/changes", nil) -// if err != nil { -// t.Fatal(err) -// } -// } + if body == nil { + t.Fatalf("Body expected, received: nil\n") + } + + if r.Code != http.StatusCreated { + t.Fatalf("%d Created expected, received %d\n", http.StatusNoContent, r.Code) + } +} + +func testListContainers(t *testing.T, srv *Server, expected int) []ApiContainers { + + r := httptest.NewRecorder() + + req, err := http.NewRequest("GET", "/containers?quiet=1&all=1", nil) + if err != nil { + t.Fatal(err) + } + + body, err := getContainers(srv, r, req) + if err != nil { + t.Fatal(err) + } + + var outs []ApiContainers + err = json.Unmarshal(body, &outs) + if err != nil { + t.Fatal(err) + } + if expected >= 0 && len(outs) != expected { + t.Errorf("Excepted %d container, %d found", expected, len(outs)) + } + return outs +} + +func testContainerStart(t *testing.T, srv *Server, id string) { + + r := httptest.NewRecorder() + + req, err := http.NewRequest("POST", "/containers/"+id+"/start", nil) + if err != nil { + t.Fatal(err) + } + + body, err := postContainersStart(srv, r, req) + if err != nil { + t.Fatal(err) + } + + if body != nil { + t.Fatalf("No body expected, received: %s\n", body) + } + + if r.Code != http.StatusNoContent { + t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) + } +} + +func testContainerRestart(t *testing.T, srv *Server, id string) { + + r := httptest.NewRecorder() + + req, err := http.NewRequest("POST", "/containers/"+id+"/restart?t=1", nil) + if err != nil { + t.Fatal(err) + } + + body, err := postContainersRestart(srv, r, req) + if err != nil { + t.Fatal(err) + } + + if body != nil { + t.Fatalf("No body expected, received: %s\n", body) + } + + if r.Code != http.StatusNoContent { + t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) + } +} + +func testContainerStop(t *testing.T, srv *Server, id string) { + + r := httptest.NewRecorder() + + req, err := http.NewRequest("POST", "/containers/"+id+"/stop?t=1", nil) + if err != nil { + t.Fatal(err) + } + + body, err := postContainersStop(srv, r, req) + if err != nil { + t.Fatal(err) + } + + if body != nil { + t.Fatalf("No body expected, received: %s\n", body) + } + + if r.Code != http.StatusNoContent { + t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) + } +} + +func testContainerKill(t *testing.T, srv *Server, id string) { + + r := httptest.NewRecorder() + + req, err := http.NewRequest("POST", "/containers/"+id+"/kill", nil) + if err != nil { + t.Fatal(err) + } + + body, err := postContainersKill(srv, r, req) + if err != nil { + t.Fatal(err) + } + + if body != nil { + t.Fatalf("No body expected, received: %s\n", body) + } + + if r.Code != http.StatusNoContent { + t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) + } +} + +func testContainerWait(t *testing.T, srv *Server, id string) { + + r := httptest.NewRecorder() + + req, err := http.NewRequest("POST", "/containers/"+id+"/wait", nil) + req.Header.Set("Content-Type", "plain/text") + + if err != nil { + t.Fatal(err) + } + + body, err := postContainersWait(srv, r, req) + if err != nil { + t.Fatal(err) + } + + if body == nil { + t.Fatalf("Body expected, received: nil\n") + } + + if r.Code != http.StatusOK { + t.Fatalf("%d OK expected, received %d\n", http.StatusNoContent, r.Code) + } +} + +func testDeleteContainer(t *testing.T, srv *Server, id string) { + + r := httptest.NewRecorder() + + req, err := http.NewRequest("DELETE", "/containers/"+id, nil) + if err != nil { + t.Fatal(err) + } + + body, err := deleteContainers(srv, r, req) + if err != nil { + t.Fatal(err) + } + + if body != nil { + t.Fatalf("No body expected, received: %s\n", body) + } + + if r.Code != http.StatusNoContent { + t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) + } +} + +func testContainerChanges(t *testing.T, srv *Server, id string) { + + r := httptest.NewRecorder() + + req, err := http.NewRequest("GET", "/containers/"+id+"/changes", nil) + if err != nil { + t.Fatal(err) + } + + body, err := getContainersChanges(srv, r, req) + if err != nil { + t.Fatal(err) + } + + if body == nil { + t.Fatalf("Body expected, received: nil\n") + } + + if r.Code != http.StatusOK { + t.Fatalf("%d OK expected, received %d\n", http.StatusNoContent, r.Code) + } +} From add73641e6a84cabac82b7ee7a2d72d5d78f1cf4 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 10 May 2013 01:23:50 +0200 Subject: [PATCH 56/69] fixed private caller --- api_test.go | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/api_test.go b/api_test.go index b2fd252ff..1d9963d8d 100644 --- a/api_test.go +++ b/api_test.go @@ -230,23 +230,7 @@ func TestInfo(t *testing.T) { // } // } -// func TestCreateListStartStopRestartKillWaitDelete(t *testing.T) { -// containers := testListContainers(t, -1) -// for _, container := range containers { -// testDeleteContainer(t, container.Id) -// } -// testCreateContainer(t) -// id := testListContainers(t, 1)[0].Id -// testContainerStart(t, id) -// testContainerStop(t, id) -// testContainerRestart(t, id) -// testContainerKill(t, id) -// testContainerWait(t, id) -// testDeleteContainer(t, id) -// testListContainers(t, 0) -// } - -func testCreateContainer(t *testing.T) { +func TestCreateListStartStopRestartKillWaitDelete(t *testing.T) { runtime, err := newTestRuntime() if err != nil { @@ -256,6 +240,23 @@ func testCreateContainer(t *testing.T) { srv := &Server{runtime: runtime} + containers := testListContainers(t, srv, -1) + for _, container := range containers { + testDeleteContainer(t, srv, container.Id) + } + testCreateContainer(t, srv) + id := testListContainers(t, srv, 1)[0].Id + testContainerStart(t, srv, id) + testContainerStop(t, srv, id) + testContainerRestart(t, srv, id) + testContainerKill(t, srv, id) + testContainerWait(t, srv, id) + testDeleteContainer(t, srv, id) + testListContainers(t, srv, 0) +} + +func testCreateContainer(t *testing.T, srv *Server) { + r := httptest.NewRecorder() config, _, err := ParseRun([]string{unitTestImageName, "touch test"}, nil) From ff67da9c867d82192020e3840bd588acc959bcd3 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 9 May 2013 16:28:47 -0700 Subject: [PATCH 57/69] Add the variable maps to the Api functions --- api.go | 125 +++++++++++++++++++++++++++++++++------------------- api_test.go | 28 ++++++------ 2 files changed, 93 insertions(+), 60 deletions(-) diff --git a/api.go b/api.go index c62fd82f5..4fe74a505 100644 --- a/api.go +++ b/api.go @@ -39,7 +39,7 @@ func httpError(w http.ResponseWriter, err error) { } } -func getAuth(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { +func getAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { var out auth.AuthConfig out.Username = srv.runtime.authConfig.Username out.Email = srv.runtime.authConfig.Email @@ -50,7 +50,7 @@ func getAuth(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error return b, nil } -func postAuth(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { +func postAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { var config auth.AuthConfig if err := json.NewDecoder(r.Body).Decode(&config); err != nil { return nil, err @@ -79,7 +79,7 @@ func postAuth(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, erro return nil, nil } -func getVersion(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { +func getVersion(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { m := srv.DockerVersion() b, err := json.Marshal(m) if err != nil { @@ -88,8 +88,10 @@ func getVersion(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, er return b, nil } -func postContainersKill(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { - vars := mux.Vars(r) +func postContainersKill(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { + if vars == nil { + return nil, fmt.Errorf("Missing parameter") + } name := vars["name"] if err := srv.ContainerKill(name); err != nil { return nil, err @@ -98,8 +100,10 @@ func postContainersKill(srv *Server, w http.ResponseWriter, r *http.Request) ([] return nil, nil } -func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { - vars := mux.Vars(r) +func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { + if vars == nil { + return nil, fmt.Errorf("Missing parameter") + } name := vars["name"] if err := srv.ContainerExport(name, w); err != nil { @@ -109,7 +113,7 @@ func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request) ([ return nil, nil } -func getImages(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { +func getImages(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { if err := parseForm(r); err != nil { return nil, err } @@ -129,14 +133,14 @@ func getImages(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, err return b, nil } -func getImagesViz(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { +func getImagesViz(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { if err := srv.ImagesViz(w); err != nil { return nil, err } return nil, nil } -func getInfo(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { +func getInfo(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { out := srv.DockerInfo() b, err := json.Marshal(out) if err != nil { @@ -145,9 +149,12 @@ func getInfo(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error return b, nil } -func getImagesHistory(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { - vars := mux.Vars(r) +func getImagesHistory(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { + if vars == nil { + return nil, fmt.Errorf("Missing parameter") + } name := vars["name"] + log.Printf("----------> %s\n", name) outs, err := srv.ImageHistory(name) if err != nil { return nil, err @@ -159,8 +166,10 @@ func getImagesHistory(srv *Server, w http.ResponseWriter, r *http.Request) ([]by return b, nil } -func getContainersChanges(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { - vars := mux.Vars(r) +func getContainersChanges(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { + if vars == nil { + return nil, fmt.Errorf("Missing parameter") + } name := vars["name"] changesStr, err := srv.ContainerChanges(name) if err != nil { @@ -173,7 +182,7 @@ func getContainersChanges(srv *Server, w http.ResponseWriter, r *http.Request) ( return b, nil } -func getContainers(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { +func getContainers(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { if err := parseForm(r); err != nil { return nil, err } @@ -195,13 +204,15 @@ func getContainers(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, return b, nil } -func postImagesTag(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { +func postImagesTag(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { if err := parseForm(r); err != nil { return nil, err } repo := r.Form.Get("repo") tag := r.Form.Get("tag") - vars := mux.Vars(r) + if vars == nil { + return nil, fmt.Errorf("Missing parameter") + } name := vars["name"] force := r.Form.Get("force") == "1" @@ -212,7 +223,7 @@ func postImagesTag(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, return nil, nil } -func postCommit(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { +func postCommit(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { if err := parseForm(r); err != nil { return nil, err } @@ -237,7 +248,7 @@ func postCommit(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, er return b, nil } -func postImages(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { +func postImages(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { if err := parseForm(r); err != nil { return nil, err } @@ -266,7 +277,7 @@ func postImages(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, er return nil, nil } -func getImagesSearch(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { +func getImagesSearch(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { if err := parseForm(r); err != nil { return nil, err } @@ -283,14 +294,16 @@ func getImagesSearch(srv *Server, w http.ResponseWriter, r *http.Request) ([]byt return b, nil } -func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { +func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { if err := parseForm(r); err != nil { return nil, err } url := r.Form.Get("url") path := r.Form.Get("path") - vars := mux.Vars(r) + if vars == nil { + return nil, fmt.Errorf("Missing parameter") + } name := vars["name"] in, out, err := hijackServer(w) @@ -305,14 +318,16 @@ func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request) ([]by return nil, nil } -func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { +func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { if err := parseForm(r); err != nil { return nil, err } registry := r.Form.Get("registry") - vars := mux.Vars(r) + if vars == nil { + return nil, fmt.Errorf("Missing parameter") + } name := vars["name"] in, out, err := hijackServer(w) @@ -327,7 +342,7 @@ func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte return nil, nil } -func postBuild(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { +func postBuild(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { in, out, err := hijackServer(w) if err != nil { return nil, err @@ -340,7 +355,7 @@ func postBuild(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, err return nil, nil } -func postContainers(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { +func postContainers(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { var config Config if err := json.NewDecoder(r.Body).Decode(&config); err != nil { return nil, err @@ -365,7 +380,7 @@ func postContainers(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte return b, nil } -func postContainersRestart(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { +func postContainersRestart(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { if err := parseForm(r); err != nil { return nil, err } @@ -373,7 +388,9 @@ func postContainersRestart(srv *Server, w http.ResponseWriter, r *http.Request) if err != nil || t < 0 { t = 10 } - vars := mux.Vars(r) + if vars == nil { + return nil, fmt.Errorf("Missing parameter") + } name := vars["name"] if err := srv.ContainerRestart(name, t); err != nil { return nil, err @@ -382,11 +399,13 @@ func postContainersRestart(srv *Server, w http.ResponseWriter, r *http.Request) return nil, nil } -func deleteContainers(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { +func deleteContainers(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { if err := parseForm(r); err != nil { return nil, err } - vars := mux.Vars(r) + if vars == nil { + return nil, fmt.Errorf("Missing parameter") + } name := vars["name"] v := r.Form.Get("v") == "1" @@ -397,8 +416,10 @@ func deleteContainers(srv *Server, w http.ResponseWriter, r *http.Request) ([]by return nil, nil } -func deleteImages(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { - vars := mux.Vars(r) +func deleteImages(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { + if vars == nil { + return nil, fmt.Errorf("Missing parameter") + } name := vars["name"] if err := srv.ImageDelete(name); err != nil { return nil, err @@ -407,8 +428,10 @@ func deleteImages(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, return nil, nil } -func postContainersStart(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { - vars := mux.Vars(r) +func postContainersStart(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { + if vars == nil { + return nil, fmt.Errorf("Missing parameter") + } name := vars["name"] if err := srv.ContainerStart(name); err != nil { return nil, err @@ -417,7 +440,7 @@ func postContainersStart(srv *Server, w http.ResponseWriter, r *http.Request) ([ return nil, nil } -func postContainersStop(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { +func postContainersStop(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { if err := parseForm(r); err != nil { return nil, err } @@ -425,7 +448,9 @@ func postContainersStop(srv *Server, w http.ResponseWriter, r *http.Request) ([] if err != nil || t < 0 { t = 10 } - vars := mux.Vars(r) + if vars == nil { + return nil, fmt.Errorf("Missing parameter") + } name := vars["name"] if err := srv.ContainerStop(name, t); err != nil { @@ -435,8 +460,10 @@ func postContainersStop(srv *Server, w http.ResponseWriter, r *http.Request) ([] return nil, nil } -func postContainersWait(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { - vars := mux.Vars(r) +func postContainersWait(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { + if vars == nil { + return nil, fmt.Errorf("Missing parameter") + } name := vars["name"] status, err := srv.ContainerWait(name) if err != nil { @@ -449,7 +476,7 @@ func postContainersWait(srv *Server, w http.ResponseWriter, r *http.Request) ([] return b, nil } -func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { +func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { if err := parseForm(r); err != nil { return nil, err } @@ -458,7 +485,9 @@ func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request) ( stdin := r.Form.Get("stdin") == "1" stdout := r.Form.Get("stdout") == "1" stderr := r.Form.Get("stderr") == "1" - vars := mux.Vars(r) + if vars == nil { + return nil, fmt.Errorf("Missing parameter") + } name := vars["name"] in, out, err := hijackServer(w) @@ -474,8 +503,10 @@ func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request) ( return nil, nil } -func getContainersByName(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { - vars := mux.Vars(r) +func getContainersByName(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { + if vars == nil { + return nil, fmt.Errorf("Missing parameter") + } name := vars["name"] container, err := srv.ContainerInspect(name) @@ -489,8 +520,10 @@ func getContainersByName(srv *Server, w http.ResponseWriter, r *http.Request) ([ return b, nil } -func getImagesByName(srv *Server, w http.ResponseWriter, r *http.Request) ([]byte, error) { - vars := mux.Vars(r) +func getImagesByName(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { + if vars == nil { + return nil, fmt.Errorf("Missing parameter") + } name := vars["name"] image, err := srv.ImageInspect(name) @@ -508,7 +541,7 @@ func ListenAndServe(addr string, srv *Server, logging bool) error { r := mux.NewRouter() log.Printf("Listening for HTTP on %s\n", addr) - m := map[string]map[string]func(*Server, http.ResponseWriter, *http.Request) ([]byte, error){ + m := map[string]map[string]func(*Server, http.ResponseWriter, *http.Request, map[string]string) ([]byte, error){ "GET": { "/auth": getAuth, "/version": getVersion, @@ -564,7 +597,7 @@ func ListenAndServe(addr string, srv *Server, logging bool) error { Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], VERSION) } } - json, err := localFct(srv, w, r) + json, err := localFct(srv, w, r, mux.Vars(r)) if err != nil { httpError(w, err) } diff --git a/api_test.go b/api_test.go index 1d9963d8d..8cafbf534 100644 --- a/api_test.go +++ b/api_test.go @@ -50,7 +50,7 @@ func TestAuth(t *testing.T) { t.Fatal(err) } - body, err := postAuth(srv, r, req) + body, err := postAuth(srv, r, req, nil) if err != nil { t.Fatal(err) } @@ -68,7 +68,7 @@ func TestAuth(t *testing.T) { t.Fatal(err) } - body, err = getAuth(srv, nil, req) + body, err = getAuth(srv, nil, req, nil) if err != nil { t.Fatal(err) } @@ -92,7 +92,7 @@ func TestVersion(t *testing.T) { srv := &Server{runtime: runtime} - body, err := getVersion(srv, nil, nil) + body, err := getVersion(srv, nil, nil, nil) if err != nil { t.Fatal(err) } @@ -123,7 +123,7 @@ func TestImages(t *testing.T) { t.Fatal(err) } - body, err := getImages(srv, nil, req) + body, err := getImages(srv, nil, req, nil) if err != nil { t.Fatal(err) } @@ -152,7 +152,7 @@ func TestInfo(t *testing.T) { srv := &Server{runtime: runtime} - body, err := getInfo(srv, nil, nil) + body, err := getInfo(srv, nil, nil, nil) if err != nil { t.Fatal(err) } @@ -274,7 +274,7 @@ func testCreateContainer(t *testing.T, srv *Server) { t.Fatal(err) } - body, err := postContainers(srv, r, req) + body, err := postContainers(srv, r, req, nil) if err != nil { t.Fatal(err) } @@ -297,7 +297,7 @@ func testListContainers(t *testing.T, srv *Server, expected int) []ApiContainers t.Fatal(err) } - body, err := getContainers(srv, r, req) + body, err := getContainers(srv, r, req, nil) if err != nil { t.Fatal(err) } @@ -322,7 +322,7 @@ func testContainerStart(t *testing.T, srv *Server, id string) { t.Fatal(err) } - body, err := postContainersStart(srv, r, req) + body, err := postContainersStart(srv, r, req, nil) if err != nil { t.Fatal(err) } @@ -345,7 +345,7 @@ func testContainerRestart(t *testing.T, srv *Server, id string) { t.Fatal(err) } - body, err := postContainersRestart(srv, r, req) + body, err := postContainersRestart(srv, r, req, nil) if err != nil { t.Fatal(err) } @@ -368,7 +368,7 @@ func testContainerStop(t *testing.T, srv *Server, id string) { t.Fatal(err) } - body, err := postContainersStop(srv, r, req) + body, err := postContainersStop(srv, r, req, nil) if err != nil { t.Fatal(err) } @@ -391,7 +391,7 @@ func testContainerKill(t *testing.T, srv *Server, id string) { t.Fatal(err) } - body, err := postContainersKill(srv, r, req) + body, err := postContainersKill(srv, r, req, nil) if err != nil { t.Fatal(err) } @@ -416,7 +416,7 @@ func testContainerWait(t *testing.T, srv *Server, id string) { t.Fatal(err) } - body, err := postContainersWait(srv, r, req) + body, err := postContainersWait(srv, r, req, nil) if err != nil { t.Fatal(err) } @@ -439,7 +439,7 @@ func testDeleteContainer(t *testing.T, srv *Server, id string) { t.Fatal(err) } - body, err := deleteContainers(srv, r, req) + body, err := deleteContainers(srv, r, req, nil) if err != nil { t.Fatal(err) } @@ -462,7 +462,7 @@ func testContainerChanges(t *testing.T, srv *Server, id string) { t.Fatal(err) } - body, err := getContainersChanges(srv, r, req) + body, err := getContainersChanges(srv, r, req, nil) if err != nil { t.Fatal(err) } From 152ebeea4327c3240e211b96b5858063f0ee468b Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 9 May 2013 17:50:56 -0700 Subject: [PATCH 58/69] Change API route for containers/ and images/ in order to avoid conflict --- api.go | 47 +++++++++++++++++++++++++---------------------- api_test.go | 24 +++++++----------------- server.go | 41 ++++++++++++++++++++--------------------- server_test.go | 4 ++-- 4 files changed, 54 insertions(+), 62 deletions(-) diff --git a/api.go b/api.go index 4fe74a505..f3b0f4bcc 100644 --- a/api.go +++ b/api.go @@ -154,7 +154,6 @@ func getImagesHistory(srv *Server, w http.ResponseWriter, r *http.Request, vars return nil, fmt.Errorf("Missing parameter") } name := vars["name"] - log.Printf("----------> %s\n", name) outs, err := srv.ImageHistory(name) if err != nil { return nil, err @@ -182,7 +181,7 @@ func getContainersChanges(srv *Server, w http.ResponseWriter, r *http.Request, v return b, nil } -func getContainers(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { +func getContainersPs(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { if err := parseForm(r); err != nil { return nil, err } @@ -248,7 +247,8 @@ func postCommit(srv *Server, w http.ResponseWriter, r *http.Request, vars map[st return b, nil } -func postImages(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { +// Creates an image from Pull or from Import +func postImagesCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { if err := parseForm(r); err != nil { return nil, err } @@ -355,21 +355,25 @@ func postBuild(srv *Server, w http.ResponseWriter, r *http.Request, vars map[str return nil, nil } -func postContainers(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { - var config Config - if err := json.NewDecoder(r.Body).Decode(&config); err != nil { +func postContainersCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { + config := &Config{} + if err := json.NewDecoder(r.Body).Decode(config); err != nil { return nil, err } - id, memoryW, swapW, err := srv.ContainerCreate(config) + id, err := srv.ContainerCreate(config) if err != nil { return nil, err } - var out ApiRun - out.Id = id - if memoryW { + + out := &ApiRun{ + Id: id, + } + if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit { + log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.") out.Warnings = append(out.Warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.") } - if swapW { + if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit { + log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.") out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.") } b, err := json.Marshal(out) @@ -545,28 +549,27 @@ func ListenAndServe(addr string, srv *Server, logging bool) error { "GET": { "/auth": getAuth, "/version": getVersion, - "/containers/{name:.*}/export": getContainersExport, - "/images": getImages, + "/info": getInfo, "/images/json": getImages, "/images/viz": getImagesViz, - "/info": getInfo, "/images/search": getImagesSearch, "/images/{name:.*}/history": getImagesHistory, - "/containers/{name:.*}/changes": getContainersChanges, - "/containers": getContainers, "/images/{name:.*}/json": getImagesByName, + "/containers/ps": getContainersPs, + "/containers/{name:.*}/export": getContainersExport, + "/containers/{name:.*}/changes": getContainersChanges, "/containers/{name:.*}/json": getContainersByName, }, "POST": { - "/auth": postAuth, - "/containers/{name:.*}/kill": postContainersKill, - "/images/{name:.*}/tag": postImagesTag, + "/auth": postAuth, "/commit": postCommit, - "/images": postImages, + "/build": postBuild, + "/images/create": postImagesCreate, "/images/{name:*.}/insert": postImagesInsert, "/images/{name:*.}/push": postImagesPush, - "/build": postBuild, - "/containers": postContainers, + "/images/{name:.*}/tag": postImagesTag, + "/containers/create": postContainersCreate, + "/containers/{name:.*}/kill": postContainersKill, "/containers/{name:.*}/restart": postContainersRestart, "/containers/{name:.*}/start": postContainersStart, "/containers/{name:.*}/stop": postContainersStop, diff --git a/api_test.go b/api_test.go index 8cafbf534..e840d1a88 100644 --- a/api_test.go +++ b/api_test.go @@ -9,20 +9,6 @@ import ( "testing" ) -// func init() { -// // Make it our Store root -// runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false) -// if err != nil { -// panic(err) -// } - -// // Create the "Server" -// srv := &Server{ -// runtime: runtime, -// } -// go ListenAndServe("0.0.0.0:4243", srv, false) -// } - func TestAuth(t *testing.T) { runtime, err := newTestRuntime() if err != nil { @@ -108,7 +94,11 @@ func TestVersion(t *testing.T) { } } -func TestImages(t *testing.T) { +func TestContainersExport(t *testing.T) { + //FIXME: Implement this test +} + +func TestGetImages(t *testing.T) { runtime, err := newTestRuntime() if err != nil { t.Fatal(err) @@ -274,7 +264,7 @@ func testCreateContainer(t *testing.T, srv *Server) { t.Fatal(err) } - body, err := postContainers(srv, r, req, nil) + body, err := postContainersCreate(srv, r, req, nil) if err != nil { t.Fatal(err) } @@ -297,7 +287,7 @@ func testListContainers(t *testing.T, srv *Server, expected int) []ApiContainers t.Fatal(err) } - body, err := getContainers(srv, r, req, nil) + body, err := getContainersPs(srv, r, req, nil) if err != nil { t.Fatal(err) } diff --git a/server.go b/server.go index 75cafb7a1..baf7657c9 100644 --- a/server.go +++ b/server.go @@ -236,9 +236,10 @@ func (srv *Server) ContainerChanges(name string) ([]Change, error) { } func (srv *Server) Containers(all, trunc_cmd, only_ids bool, n int, since, before string) []ApiContainers { - var outs []ApiContainers = []ApiContainers{} //produce [] when empty instead of 'null' var foundBefore bool var displayed int + retContainers := []ApiContainers{} + for _, container := range srv.runtime.List() { if !container.State.Running && !all && n == -1 && since == "" && before == "" { continue @@ -258,23 +259,26 @@ func (srv *Server) Containers(all, trunc_cmd, only_ids bool, n int, since, befor if container.ShortId() == since { break } - displayed += 1 - var out ApiContainers - out.Id = container.ShortId() + displayed++ + + c := ApiContainers{ + Id: container.ShortId(), + } + if !only_ids { command := fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")) if trunc_cmd { 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() + c.Image = srv.runtime.repositories.ImageName(container.Image) + c.Command = command + c.Created = container.Created.Unix() + c.Status = container.State.String() + c.Ports = container.NetworkSettings.PortMappingHuman() } - outs = append(outs, out) + retContainers = append(retContainers, c) } - return outs + return retContainers } func (srv *Server) ContainerCommit(name, repo, tag, author, comment string, config *Config) (string, error) { @@ -369,29 +373,24 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write return nil } -func (srv *Server) ContainerCreate(config Config) (string, bool, bool, error) { - var memoryW, swapW bool +func (srv *Server) ContainerCreate(config *Config) (string, error) { 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 } b := NewBuilder(srv.runtime) - container, err := b.Create(&config) + container, err := b.Create(config) if err != nil { if srv.runtime.graph.IsNotExist(err) { - return "", false, false, fmt.Errorf("No such image: %s", config.Image) + return "", fmt.Errorf("No such image: %s", config.Image) } - return "", false, false, err + return "", err } - return container.ShortId(), memoryW, swapW, nil + return container.ShortId(), nil } func (srv *Server) ImageCreateFromFile(dockerfile io.Reader, out io.Writer) error { diff --git a/server_test.go b/server_test.go index af72cba47..7b9025286 100644 --- a/server_test.go +++ b/server_test.go @@ -18,7 +18,7 @@ func TestCreateRm(t *testing.T) { t.Fatal(err) } - id, _, _, err := srv.ContainerCreate(*config) + id, err := srv.ContainerCreate(config) if err != nil { t.Fatal(err) } @@ -51,7 +51,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) { t.Fatal(err) } - id, _, _, err := srv.ContainerCreate(*config) + id, err := srv.ContainerCreate(config) if err != nil { t.Fatal(err) } From ede0793d94e0bafcddc65b1fcb683169accffea3 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 9 May 2013 17:51:27 -0700 Subject: [PATCH 59/69] Add unit tests for remote api --- api_test.go | 130 +++++++++++++++++++++++++++++----------------------- 1 file changed, 73 insertions(+), 57 deletions(-) diff --git a/api_test.go b/api_test.go index e840d1a88..66c90ad10 100644 --- a/api_test.go +++ b/api_test.go @@ -156,69 +156,85 @@ func TestInfo(t *testing.T) { } } -// func TestHistory(t *testing.T) { -// runtime, err := newTestRuntime() -// if err != nil { -// t.Fatal(err) -// } -// defer nuke(runtime) +func TestHistory(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) -// srv := &Server{runtime: runtime} + srv := &Server{runtime: runtime} -// req, err := http.NewRequest("GET", "/images/"+unitTestImageName+"/history", nil) -// if err != nil { -// t.Fatal(err) -// } + body, err := getImagesHistory(srv, nil, nil, map[string]string{"name": unitTestImageName}) + if err != nil { + t.Fatal(err) + } -// router := mux.NewRouter() -// router.Path("/images/{name:.*}/history") -// vars := mux.Vars(req) -// router. -// vars["name"] = unitTestImageName + history := []ApiHistory{} -// body, err := getImagesHistory(srv, nil, req) -// if err != nil { -// t.Fatal(err) -// } -// var outs []ApiHistory -// err = json.Unmarshal(body, &outs) -// if err != nil { -// t.Fatal(err) -// } -// if len(outs) != 1 { -// t.Errorf("Excepted 1 line, %d found", len(outs)) -// } -// } + err = json.Unmarshal(body, &history) + if err != nil { + t.Fatal(err) + } + if len(history) != 1 { + t.Errorf("Excepted 1 line, %d found", len(history)) + } +} -// func TestImagesSearch(t *testing.T) { -// body, _, err := call("GET", "/images/search?term=redis", nil) -// if err != nil { -// t.Fatal(err) -// } -// var outs []ApiSearch -// err = json.Unmarshal(body, &outs) -// if err != nil { -// t.Fatal(err) -// } -// if len(outs) < 2 { -// t.Errorf("Excepted at least 2 lines, %d found", len(outs)) -// } -// } +func TestImagesSearch(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) -// func TestGetImage(t *testing.T) { -// obj, _, err := call("GET", "/images/"+unitTestImageName+"/json", nil) -// if err != nil { -// t.Fatal(err) -// } -// var out Image -// err = json.Unmarshal(obj, &out) -// if err != nil { -// t.Fatal(err) -// } -// if out.Comment != "Imported from http://get.docker.io/images/busybox" { -// t.Errorf("Error inspecting image") -// } -// } + srv := &Server{runtime: runtime} + + req, err := http.NewRequest("GET", "/images/search?term=redis", nil) + if err != nil { + t.Fatal(err) + } + + body, err := getImagesSearch(srv, nil, req, nil) + if err != nil { + t.Fatal(err) + } + + results := []ApiSearch{} + + err = json.Unmarshal(body, &results) + if err != nil { + t.Fatal(err) + } + if len(results) < 2 { + t.Errorf("Excepted at least 2 lines, %d found", len(results)) + } +} + +func TestGetImage(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + body, err := getImagesByName(srv, nil, nil, map[string]string{"name": unitTestImageName}) + if err != nil { + t.Fatal(err) + } + + img := &Image{} + + err = json.Unmarshal(body, img) + if err != nil { + t.Fatal(err) + } + if img.Comment != "Imported from http://get.docker.io/images/busybox" { + t.Errorf("Error inspecting image") + } +} func TestCreateListStartStopRestartKillWaitDelete(t *testing.T) { From 2a303dab8594420b6b7908183a73c5d744b1e66b Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 9 May 2013 19:19:24 -0700 Subject: [PATCH 60/69] Add unit tests --- api.go | 1 + api_test.go | 228 ++++++++++++++++++++++++++++++++++------------ container_test.go | 6 +- 3 files changed, 174 insertions(+), 61 deletions(-) diff --git a/api.go b/api.go index f3b0f4bcc..3c60d727b 100644 --- a/api.go +++ b/api.go @@ -452,6 +452,7 @@ func postContainersStop(srv *Server, w http.ResponseWriter, r *http.Request, var if err != nil || t < 0 { t = 10 } + if vars == nil { return nil, fmt.Errorf("Missing parameter") } diff --git a/api_test.go b/api_test.go index 66c90ad10..ef02fa471 100644 --- a/api_test.go +++ b/api_test.go @@ -6,7 +6,10 @@ import ( "github.com/dotcloud/docker/auth" "net/http" "net/http/httptest" + "os" + "path" "testing" + "time" ) func TestAuth(t *testing.T) { @@ -236,8 +239,7 @@ func TestGetImage(t *testing.T) { } } -func TestCreateListStartStopRestartKillWaitDelete(t *testing.T) { - +func TestPostContainersCreate(t *testing.T) { runtime, err := newTestRuntime() if err != nil { t.Fatal(err) @@ -246,36 +248,18 @@ func TestCreateListStartStopRestartKillWaitDelete(t *testing.T) { srv := &Server{runtime: runtime} - containers := testListContainers(t, srv, -1) - for _, container := range containers { - testDeleteContainer(t, srv, container.Id) - } - testCreateContainer(t, srv) - id := testListContainers(t, srv, 1)[0].Id - testContainerStart(t, srv, id) - testContainerStop(t, srv, id) - testContainerRestart(t, srv, id) - testContainerKill(t, srv, id) - testContainerWait(t, srv, id) - testDeleteContainer(t, srv, id) - testListContainers(t, srv, 0) -} - -func testCreateContainer(t *testing.T, srv *Server) { - r := httptest.NewRecorder() - config, _, err := ParseRun([]string{unitTestImageName, "touch test"}, nil) + configJson, err := json.Marshal(&Config{ + Image: GetTestImage(runtime).Id, + Memory: 33554432, + Cmd: []string{"touch", "/test"}, + }) if err != nil { t.Fatal(err) } - configJson, err := json.Marshal(config) - if err != nil { - t.Fatal(err) - } - - req, err := http.NewRequest("POST", "/containers", bytes.NewReader(configJson)) + req, err := http.NewRequest("POST", "/containers/create", bytes.NewReader(configJson)) if err != nil { t.Fatal(err) } @@ -284,62 +268,121 @@ func testCreateContainer(t *testing.T, srv *Server) { if err != nil { t.Fatal(err) } - - if body == nil { - t.Fatalf("Body expected, received: nil\n") + if r.Code != http.StatusCreated { + t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code) } - if r.Code != http.StatusCreated { - t.Fatalf("%d Created expected, received %d\n", http.StatusNoContent, r.Code) + apiRun := &ApiRun{} + if err := json.Unmarshal(body, apiRun); err != nil { + t.Fatal(err) + } + + container := srv.runtime.Get(apiRun.Id) + if container == nil { + t.Fatalf("Container not created") + } + + if err := container.Run(); err != nil { + t.Fatal(err) + } + + if _, err := os.Stat(path.Join(container.rwPath(), "test")); err != nil { + if os.IsNotExist(err) { + Debugf("Err: %s", err) + t.Fatalf("The test file has not been created") + } + t.Fatal(err) } } -func testListContainers(t *testing.T, srv *Server, expected int) []ApiContainers { +func TestGetContainersPs(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) - r := httptest.NewRecorder() + srv := &Server{runtime: runtime} + + container, err := NewBuilder(runtime).Create(&Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"echo", "test"}, + }) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) req, err := http.NewRequest("GET", "/containers?quiet=1&all=1", nil) if err != nil { t.Fatal(err) } - body, err := getContainersPs(srv, r, req, nil) + body, err := getContainersPs(srv, nil, req, nil) if err != nil { t.Fatal(err) } - - var outs []ApiContainers - err = json.Unmarshal(body, &outs) + containers := []ApiContainers{} + err = json.Unmarshal(body, &containers) if err != nil { t.Fatal(err) } - if expected >= 0 && len(outs) != expected { - t.Errorf("Excepted %d container, %d found", expected, len(outs)) + if len(containers) != 1 { + t.Fatalf("Excepted %d container, %d found", 1, len(containers)) + } + if containers[0].Id != container.ShortId() { + t.Fatalf("Container ID mismatch. Expected: %s, received: %s\n", container.ShortId(), containers[0].Id) } - return outs } -func testContainerStart(t *testing.T, srv *Server, id string) { +func TestPostContainersStart(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + container, err := NewBuilder(runtime).Create( + &Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"/bin/cat"}, + OpenStdin: true, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) r := httptest.NewRecorder() - req, err := http.NewRequest("POST", "/containers/"+id+"/start", nil) + body, err := postContainersStart(srv, r, nil, map[string]string{"name": container.Id}) if err != nil { t.Fatal(err) } - - body, err := postContainersStart(srv, r, req, nil) - if err != nil { - t.Fatal(err) - } - if body != nil { t.Fatalf("No body expected, received: %s\n", body) } - if r.Code != http.StatusNoContent { t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) } + + // Give some time to the process to start + container.WaitTimeout(500 * time.Millisecond) + + if !container.State.Running { + t.Errorf("Container should be running") + } + + if _, err = postContainersStart(srv, r, nil, map[string]string{"name": container.Id}); err == nil { + t.Fatalf("A running containter should be able to be started") + } + + if err := container.Kill(); err != nil { + t.Fatal(err) + } } func testContainerRestart(t *testing.T, srv *Server, id string) { @@ -365,27 +408,63 @@ func testContainerRestart(t *testing.T, srv *Server, id string) { } } -func testContainerStop(t *testing.T, srv *Server, id string) { +// testContainerRestart(t, srv, id) +// testContainerKill(t, srv, id) +// testContainerWait(t, srv, id) +// testDeleteContainer(t, srv, id) +// testListContainers(t, srv, 0) +func TestPostContainersStop(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + container, err := NewBuilder(runtime).Create( + &Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"/bin/cat"}, + OpenStdin: true, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + + if err := container.Start(); err != nil { + t.Fatal(err) + } + + // Give some time to the process to start + container.WaitTimeout(500 * time.Millisecond) + + if !container.State.Running { + t.Errorf("Container should be running") + } r := httptest.NewRecorder() - req, err := http.NewRequest("POST", "/containers/"+id+"/stop?t=1", nil) + // Note: as it is a POST request, it requires a body. + req, err := http.NewRequest("POST", "/containers/"+container.Id+"/stop?t=1", bytes.NewReader([]byte{})) if err != nil { t.Fatal(err) } - - body, err := postContainersStop(srv, r, req, nil) + body, err := postContainersStop(srv, r, req, map[string]string{"name": container.Id}) if err != nil { t.Fatal(err) } - if body != nil { t.Fatalf("No body expected, received: %s\n", body) } - if r.Code != http.StatusNoContent { t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) } + if container.State.Running { + t.Fatalf("The container hasn't been stopped") + } } func testContainerKill(t *testing.T, srv *Server, id string) { @@ -436,27 +515,56 @@ func testContainerWait(t *testing.T, srv *Server, id string) { } } -func testDeleteContainer(t *testing.T, srv *Server, id string) { +// FIXME: Test deleting runnign container +// FIXME: Test deleting container with volume +// FIXME: Test deleting volume in use by other container +func TestDeleteContainers(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + container, err := NewBuilder(runtime).Create(&Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"touch", "/test"}, + }) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + + if err := container.Run(); err != nil { + t.Fatal(err) + } r := httptest.NewRecorder() - req, err := http.NewRequest("DELETE", "/containers/"+id, nil) + req, err := http.NewRequest("DELETE", "/containers/"+container.Id, nil) if err != nil { t.Fatal(err) } - body, err := deleteContainers(srv, r, req, nil) + body, err := deleteContainers(srv, r, req, map[string]string{"name": container.Id}) if err != nil { t.Fatal(err) } - if body != nil { t.Fatalf("No body expected, received: %s\n", body) } - if r.Code != http.StatusNoContent { t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) } + + if c := runtime.Get(container.Id); c != nil { + t.Fatalf("The container as not been deleted") + } + + if _, err := os.Stat(path.Join(container.rwPath(), "test")); err == nil { + t.Fatalf("The test file has not been deleted") + } } func testContainerChanges(t *testing.T, srv *Server, id string) { diff --git a/container_test.go b/container_test.go index 5b63b2a0e..9770e800f 100644 --- a/container_test.go +++ b/container_test.go @@ -399,6 +399,11 @@ func TestStart(t *testing.T) { } defer runtime.Destroy(container) + cStdin, err := container.StdinPipe() + if err != nil { + t.Fatal(err) + } + if err := container.Start(); err != nil { t.Fatal(err) } @@ -414,7 +419,6 @@ func TestStart(t *testing.T) { } // Try to avoid the timeoout in destroy. Best effort, don't check error - cStdin, _ := container.StdinPipe() cStdin.Close() container.WaitTimeout(2 * time.Second) } From 0c6380cc3237ac9eb1947223479ee13253d04287 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 9 May 2013 19:19:55 -0700 Subject: [PATCH 61/69] Rename "v" in "removeVolume" --- api.go | 4 ++-- server.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api.go b/api.go index 3c60d727b..90257c39b 100644 --- a/api.go +++ b/api.go @@ -411,9 +411,9 @@ func deleteContainers(srv *Server, w http.ResponseWriter, r *http.Request, vars return nil, fmt.Errorf("Missing parameter") } name := vars["name"] - v := r.Form.Get("v") == "1" + removeVolume := r.Form.Get("removeVolume") == "1" - if err := srv.ContainerDestroy(name, v); err != nil { + if err := srv.ContainerDestroy(name, removeVolume); err != nil { return nil, err } w.WriteHeader(http.StatusNoContent) diff --git a/server.go b/server.go index baf7657c9..cd4771f41 100644 --- a/server.go +++ b/server.go @@ -413,7 +413,7 @@ func (srv *Server) ContainerRestart(name string, t int) error { return nil } -func (srv *Server) ContainerDestroy(name string, v bool) error { +func (srv *Server) ContainerDestroy(name string, removeVolume bool) error { if container := srv.runtime.Get(name); container != nil { volumes := make(map[string]struct{}) @@ -425,7 +425,7 @@ func (srv *Server) ContainerDestroy(name string, v bool) error { return fmt.Errorf("Error destroying container %s: %s", name, err.Error()) } - if v { + if removeVolume { // Retrieve all volumes from all remaining containers usedVolumes := make(map[string]*Container) for _, container := range srv.runtime.List() { From 24816a8b80cb9d5b97d876f99a8300924f6abf9c Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 9 May 2013 20:13:52 -0700 Subject: [PATCH 62/69] Add/improve unit tests --- api.go | 21 +-- api_test.go | 473 ++++++++++++++++++++++++++++++++++------------------ 2 files changed, 318 insertions(+), 176 deletions(-) diff --git a/api.go b/api.go index 90257c39b..b3cdb59bc 100644 --- a/api.go +++ b/api.go @@ -40,10 +40,11 @@ func httpError(w http.ResponseWriter, err error) { } func getAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { - var out auth.AuthConfig - out.Username = srv.runtime.authConfig.Username - out.Email = srv.runtime.authConfig.Email - b, err := json.Marshal(out) + config := &auth.AuthConfig{ + Username: srv.runtime.authConfig.Username, + Email: srv.runtime.authConfig.Email, + } + b, err := json.Marshal(config) if err != nil { return nil, err } @@ -51,8 +52,8 @@ func getAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[strin } func postAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { - var config auth.AuthConfig - if err := json.NewDecoder(r.Body).Decode(&config); err != nil { + config := &auth.AuthConfig{} + if err := json.NewDecoder(r.Body).Decode(config); err != nil { return nil, err } @@ -69,7 +70,7 @@ func postAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[stri srv.runtime.authConfig = newAuthConfig } if status != "" { - b, err := json.Marshal(ApiAuth{status}) + b, err := json.Marshal(&ApiAuth{Status: status}) if err != nil { return nil, err } @@ -113,7 +114,7 @@ func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request, va return nil, nil } -func getImages(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { +func getImagesJson(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) ([]byte, error) { if err := parseForm(r); err != nil { return nil, err } @@ -474,7 +475,7 @@ func postContainersWait(srv *Server, w http.ResponseWriter, r *http.Request, var if err != nil { return nil, err } - b, err := json.Marshal(ApiWait{status}) + b, err := json.Marshal(&ApiWait{StatusCode: status}) if err != nil { return nil, err } @@ -551,7 +552,7 @@ func ListenAndServe(addr string, srv *Server, logging bool) error { "/auth": getAuth, "/version": getVersion, "/info": getInfo, - "/images/json": getImages, + "/images/json": getImagesJson, "/images/viz": getImagesViz, "/images/search": getImagesSearch, "/images/{name:.*}/history": getImagesHistory, diff --git a/api_test.go b/api_test.go index ef02fa471..632a57861 100644 --- a/api_test.go +++ b/api_test.go @@ -12,7 +12,7 @@ import ( "time" ) -func TestAuth(t *testing.T) { +func TestGetAuth(t *testing.T) { runtime, err := newTestRuntime() if err != nil { t.Fatal(err) @@ -50,29 +50,14 @@ func TestAuth(t *testing.T) { t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) } - authConfig = &auth.AuthConfig{} - - req, err = http.NewRequest("GET", "/auth", nil) - if err != nil { - t.Fatal(err) - } - - body, err = getAuth(srv, nil, req, nil) - if err != nil { - t.Fatal(err) - } - - err = json.Unmarshal(body, authConfig) - if err != nil { - t.Fatal(err) - } - - if authConfig.Username != "utest" { - t.Errorf("Expected username to be utest, %s found", authConfig.Username) + if runtime.authConfig.Username != authConfig.Username || + runtime.authConfig.Password != authConfig.Password || + runtime.authConfig.Email != authConfig.Email { + t.Fatalf("The auth configuration hasn't been set correctly") } } -func TestVersion(t *testing.T) { +func TestGetVersion(t *testing.T) { runtime, err := newTestRuntime() if err != nil { t.Fatal(err) @@ -97,46 +82,7 @@ func TestVersion(t *testing.T) { } } -func TestContainersExport(t *testing.T) { - //FIXME: Implement this test -} - -func TestGetImages(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } - defer nuke(runtime) - - srv := &Server{runtime: runtime} - - // FIXME: Do more tests with filter - req, err := http.NewRequest("GET", "/images?quiet=0&all=0", nil) - if err != nil { - t.Fatal(err) - } - - body, err := getImages(srv, nil, req, nil) - if err != nil { - t.Fatal(err) - } - - images := []ApiImages{} - err = json.Unmarshal(body, &images) - if err != nil { - t.Fatal(err) - } - - if len(images) != 1 { - t.Errorf("Excepted 1 image, %d found", len(images)) - } - - if images[0].Repository != "docker-ut" { - t.Errorf("Excepted image docker-ut, %s found", images[0].Repository) - } -} - -func TestInfo(t *testing.T) { +func TestGetInfo(t *testing.T) { runtime, err := newTestRuntime() if err != nil { t.Fatal(err) @@ -159,7 +105,7 @@ func TestInfo(t *testing.T) { } } -func TestHistory(t *testing.T) { +func TestGetImagesJson(t *testing.T) { runtime, err := newTestRuntime() if err != nil { t.Fatal(err) @@ -168,23 +114,38 @@ func TestHistory(t *testing.T) { srv := &Server{runtime: runtime} - body, err := getImagesHistory(srv, nil, nil, map[string]string{"name": unitTestImageName}) + // FIXME: Do more tests with filter + req, err := http.NewRequest("GET", "/images/json?quiet=0&all=0", nil) if err != nil { t.Fatal(err) } - history := []ApiHistory{} - - err = json.Unmarshal(body, &history) + body, err := getImagesJson(srv, nil, req, nil) if err != nil { t.Fatal(err) } - if len(history) != 1 { - t.Errorf("Excepted 1 line, %d found", len(history)) + + images := []ApiImages{} + err = json.Unmarshal(body, &images) + if err != nil { + t.Fatal(err) + } + + if len(images) != 1 { + t.Errorf("Excepted 1 image, %d found", len(images)) + } + + if images[0].Repository != "docker-ut" { + t.Errorf("Excepted image docker-ut, %s found", images[0].Repository) } } -func TestImagesSearch(t *testing.T) { +func TestGetImagesViz(t *testing.T) { + //FIXME: Implement this test (or remove this endpoint) + t.Log("Test on implemented") +} + +func TestGetImagesSearch(t *testing.T) { runtime, err := newTestRuntime() if err != nil { t.Fatal(err) @@ -214,7 +175,32 @@ func TestImagesSearch(t *testing.T) { } } -func TestGetImage(t *testing.T) { +func TestGetImagesHistory(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + body, err := getImagesHistory(srv, nil, nil, map[string]string{"name": unitTestImageName}) + if err != nil { + t.Fatal(err) + } + + history := []ApiHistory{} + + err = json.Unmarshal(body, &history) + if err != nil { + t.Fatal(err) + } + if len(history) != 1 { + t.Errorf("Excepted 1 line, %d found", len(history)) + } +} + +func TestGetImagesByName(t *testing.T) { runtime, err := newTestRuntime() if err != nil { t.Fatal(err) @@ -239,6 +225,136 @@ func TestGetImage(t *testing.T) { } } +func TestGetContainersPs(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + container, err := NewBuilder(runtime).Create(&Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"echo", "test"}, + }) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + + req, err := http.NewRequest("GET", "/containers?quiet=1&all=1", nil) + if err != nil { + t.Fatal(err) + } + + body, err := getContainersPs(srv, nil, req, nil) + if err != nil { + t.Fatal(err) + } + containers := []ApiContainers{} + err = json.Unmarshal(body, &containers) + if err != nil { + t.Fatal(err) + } + if len(containers) != 1 { + t.Fatalf("Excepted %d container, %d found", 1, len(containers)) + } + if containers[0].Id != container.ShortId() { + t.Fatalf("Container ID mismatch. Expected: %s, received: %s\n", container.ShortId(), containers[0].Id) + } +} + +func TestGetContainersExport(t *testing.T) { + //FIXME: Implement this test + t.Log("Test on implemented") +} + +func TestGetContainerChanges(t *testing.T) { + // FIXME: Implement this test + t.Log("Test on implemented") + // r := httptest.NewRecorder() + + // req, err := http.NewRequest("GET", "/containers/"+id+"/changes", nil) + // if err != nil { + // t.Fatal(err) + // } + + // body, err := getContainersChanges(srv, r, req, nil) + // if err != nil { + // t.Fatal(err) + // } + + // if body == nil { + // t.Fatalf("Body expected, received: nil\n") + // } + + // if r.Code != http.StatusOK { + // t.Fatalf("%d OK expected, received %d\n", http.StatusNoContent, r.Code) + // } +} + +func TestPostAuth(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + authConfigOrig := &auth.AuthConfig{ + Username: "utest", + Email: "utest@yopmail.com", + } + runtime.authConfig = authConfigOrig + + body, err := getAuth(srv, nil, nil, nil) + if err != nil { + t.Fatal(err) + } + + authConfig := &auth.AuthConfig{} + err = json.Unmarshal(body, authConfig) + if err != nil { + t.Fatal(err) + } + + if authConfig.Username != authConfigOrig.Username || authConfig.Email != authConfigOrig.Email { + t.Errorf("The retrieve auth mismatch with the one set.") + } +} + +func TestPostCommit(t *testing.T) { + //FIXME: Implement this test + t.Log("Test on implemented") +} + +func TestPostBuild(t *testing.T) { + //FIXME: Implement this test + t.Log("Test on implemented") +} + +func TestPostImagesCreate(t *testing.T) { + //FIXME: Implement this test + t.Log("Test on implemented") +} + +func TestPostImagesInsert(t *testing.T) { + //FIXME: Implement this test (or remove this endpoint) + t.Log("Test on implemented") +} + +func TestPostImagesPush(t *testing.T) { + //FIXME: Implement this test + t.Log("Test on implemented") +} + +func TestPostImagesTag(t *testing.T) { + //FIXME: Implement this test + t.Log("Test on implemented") +} + func TestPostContainersCreate(t *testing.T) { runtime, err := newTestRuntime() if err != nil { @@ -295,7 +411,7 @@ func TestPostContainersCreate(t *testing.T) { } } -func TestGetContainersPs(t *testing.T) { +func TestPostContainersKill(t *testing.T) { runtime, err := newTestRuntime() if err != nil { t.Fatal(err) @@ -304,34 +420,104 @@ func TestGetContainersPs(t *testing.T) { srv := &Server{runtime: runtime} - container, err := NewBuilder(runtime).Create(&Config{ - Image: GetTestImage(runtime).Id, - Cmd: []string{"echo", "test"}, - }) + container, err := NewBuilder(runtime).Create( + &Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"/bin/cat"}, + OpenStdin: true, + }, + ) if err != nil { t.Fatal(err) } defer runtime.Destroy(container) - req, err := http.NewRequest("GET", "/containers?quiet=1&all=1", nil) - if err != nil { + if err := container.Start(); err != nil { t.Fatal(err) } - body, err := getContainersPs(srv, nil, req, nil) + // Give some time to the process to start + container.WaitTimeout(500 * time.Millisecond) + + if !container.State.Running { + t.Errorf("Container should be running") + } + + r := httptest.NewRecorder() + + body, err := postContainersKill(srv, r, nil, map[string]string{"name": container.Id}) if err != nil { t.Fatal(err) } - containers := []ApiContainers{} - err = json.Unmarshal(body, &containers) + if body != nil { + t.Fatalf("No body expected, received: %s\n", body) + } + if r.Code != http.StatusNoContent { + t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) + } + if container.State.Running { + t.Fatalf("The container hasn't been killed") + } +} + +func TestPostContainersRestart(t *testing.T) { + runtime, err := newTestRuntime() if err != nil { t.Fatal(err) } - if len(containers) != 1 { - t.Fatalf("Excepted %d container, %d found", 1, len(containers)) + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + container, err := NewBuilder(runtime).Create( + &Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"/bin/cat"}, + OpenStdin: true, + }, + ) + if err != nil { + t.Fatal(err) } - if containers[0].Id != container.ShortId() { - t.Fatalf("Container ID mismatch. Expected: %s, received: %s\n", container.ShortId(), containers[0].Id) + defer runtime.Destroy(container) + + if err := container.Start(); err != nil { + t.Fatal(err) + } + + // Give some time to the process to start + container.WaitTimeout(500 * time.Millisecond) + + if !container.State.Running { + t.Errorf("Container should be running") + } + + r := httptest.NewRecorder() + + req, err := http.NewRequest("POST", "/containers/"+container.Id+"/restart?t=1", bytes.NewReader([]byte{})) + if err != nil { + t.Fatal(err) + } + body, err := postContainersRestart(srv, r, req, map[string]string{"name": container.Id}) + if err != nil { + t.Fatal(err) + } + if body != nil { + t.Fatalf("No body expected, received: %s\n", body) + } + if r.Code != http.StatusNoContent { + t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) + } + + // Give some time to the process to restart + container.WaitTimeout(500 * time.Millisecond) + + if !container.State.Running { + t.Fatalf("Container should be running") + } + + if err := container.Kill(); err != nil { + t.Fatal(err) } } @@ -385,34 +571,6 @@ func TestPostContainersStart(t *testing.T) { } } -func testContainerRestart(t *testing.T, srv *Server, id string) { - - r := httptest.NewRecorder() - - req, err := http.NewRequest("POST", "/containers/"+id+"/restart?t=1", nil) - if err != nil { - t.Fatal(err) - } - - body, err := postContainersRestart(srv, r, req, nil) - if err != nil { - t.Fatal(err) - } - - if body != nil { - t.Fatalf("No body expected, received: %s\n", body) - } - - if r.Code != http.StatusNoContent { - t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) - } -} - -// testContainerRestart(t, srv, id) -// testContainerKill(t, srv, id) -// testContainerWait(t, srv, id) -// testDeleteContainer(t, srv, id) -// testListContainers(t, srv, 0) func TestPostContainersStop(t *testing.T) { runtime, err := newTestRuntime() if err != nil { @@ -467,52 +625,53 @@ func TestPostContainersStop(t *testing.T) { } } -func testContainerKill(t *testing.T, srv *Server, id string) { - - r := httptest.NewRecorder() - - req, err := http.NewRequest("POST", "/containers/"+id+"/kill", nil) +func TestPostContainersWait(t *testing.T) { + runtime, err := newTestRuntime() if err != nil { t.Fatal(err) } + defer nuke(runtime) - body, err := postContainersKill(srv, r, req, nil) + srv := &Server{runtime: runtime} + + container, err := NewBuilder(runtime).Create( + &Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"/bin/sleep", "1"}, + OpenStdin: true, + }, + ) if err != nil { t.Fatal(err) } + defer runtime.Destroy(container) - if body != nil { - t.Fatalf("No body expected, received: %s\n", body) + if err := container.Start(); err != nil { + t.Fatal(err) } - if r.Code != http.StatusNoContent { - t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) + setTimeout(t, "Wait timed out", 3*time.Second, func() { + body, err := postContainersWait(srv, nil, nil, nil) + if err != nil { + t.Fatal(err) + } + apiWait := &ApiWait{} + if err := json.Unmarshal(body, apiWait); err != nil { + t.Fatal(err) + } + if apiWait.StatusCode != 0 { + t.Fatalf("Non zero exit code for sleep: %d\n", apiWait.StatusCode) + } + }) + + if container.State.Running { + t.Fatalf("The container should be stopped after wait") } } -func testContainerWait(t *testing.T, srv *Server, id string) { - - r := httptest.NewRecorder() - - req, err := http.NewRequest("POST", "/containers/"+id+"/wait", nil) - req.Header.Set("Content-Type", "plain/text") - - if err != nil { - t.Fatal(err) - } - - body, err := postContainersWait(srv, r, req, nil) - if err != nil { - t.Fatal(err) - } - - if body == nil { - t.Fatalf("Body expected, received: nil\n") - } - - if r.Code != http.StatusOK { - t.Fatalf("%d OK expected, received %d\n", http.StatusNoContent, r.Code) - } +func TestPostContainersAttach(t *testing.T) { + //FIXME: Implement this test + t.Log("Test on implemented") } // FIXME: Test deleting runnign container @@ -567,25 +726,7 @@ func TestDeleteContainers(t *testing.T) { } } -func testContainerChanges(t *testing.T, srv *Server, id string) { - - r := httptest.NewRecorder() - - req, err := http.NewRequest("GET", "/containers/"+id+"/changes", nil) - if err != nil { - t.Fatal(err) - } - - body, err := getContainersChanges(srv, r, req, nil) - if err != nil { - t.Fatal(err) - } - - if body == nil { - t.Fatalf("Body expected, received: nil\n") - } - - if r.Code != http.StatusOK { - t.Fatalf("%d OK expected, received %d\n", http.StatusNoContent, r.Code) - } +func TestDeleteImages(t *testing.T) { + //FIXME: Implement this test + t.Log("Test on implemented") } From eeaea4e873a4850a24e300461975e5d9636fef08 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 9 May 2013 20:19:21 -0700 Subject: [PATCH 63/69] Update the routes within commands.go --- api.go | 2 +- commands.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api.go b/api.go index b3cdb59bc..cd6697dcd 100644 --- a/api.go +++ b/api.go @@ -412,7 +412,7 @@ func deleteContainers(srv *Server, w http.ResponseWriter, r *http.Request, vars return nil, fmt.Errorf("Missing parameter") } name := vars["name"] - removeVolume := r.Form.Get("removeVolume") == "1" + removeVolume := r.Form.Get("v") == "1" if err := srv.ContainerDestroy(name, removeVolume); err != nil { return nil, err diff --git a/commands.go b/commands.go index 63a1f3ed3..41b415c3b 100644 --- a/commands.go +++ b/commands.go @@ -783,7 +783,7 @@ func CmdPs(args ...string) error { v.Set("before", *before) } - body, _, err := call("GET", "/containers?"+v.Encode(), nil) + body, _, err := call("GET", "/containers/ps?"+v.Encode(), nil) if err != nil { return err } @@ -1082,7 +1082,7 @@ func CmdRun(args ...string) error { } //create the container - body, statusCode, err := call("POST", "/containers", *config) + body, statusCode, err := call("POST", "/containers/create", config) //if image not found try to pull it if statusCode == 404 { v := url.Values{} @@ -1091,7 +1091,7 @@ func CmdRun(args ...string) error { if err != nil { return err } - body, _, err = call("POST", "/containers", *config) + body, _, err = call("POST", "/containers/create", config) if err != nil { return err } From 483c9425208fbc649cd12d157892be5a7ea92924 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 9 May 2013 21:53:28 -0700 Subject: [PATCH 64/69] Fix typos whithin unit tests --- api_test.go | 24 ++++++++++++------------ commands.go | 18 +++++++++--------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/api_test.go b/api_test.go index 632a57861..2abd166c5 100644 --- a/api_test.go +++ b/api_test.go @@ -142,7 +142,7 @@ func TestGetImagesJson(t *testing.T) { func TestGetImagesViz(t *testing.T) { //FIXME: Implement this test (or remove this endpoint) - t.Log("Test on implemented") + t.Log("Test not implemented") } func TestGetImagesSearch(t *testing.T) { @@ -267,7 +267,7 @@ func TestGetContainersPs(t *testing.T) { func TestGetContainersExport(t *testing.T) { //FIXME: Implement this test - t.Log("Test on implemented") + t.Log("Test not implemented") } func TestGetContainerChanges(t *testing.T) { @@ -289,9 +289,9 @@ func TestGetContainerChanges(t *testing.T) { // t.Fatalf("Body expected, received: nil\n") // } - // if r.Code != http.StatusOK { - // t.Fatalf("%d OK expected, received %d\n", http.StatusNoContent, r.Code) - // } +func TestGetContainersByName(t *testing.T) { + //FIXME: Implement this test + t.Log("Test not implemented") } func TestPostAuth(t *testing.T) { @@ -327,32 +327,32 @@ func TestPostAuth(t *testing.T) { func TestPostCommit(t *testing.T) { //FIXME: Implement this test - t.Log("Test on implemented") + t.Log("Test not implemented") } func TestPostBuild(t *testing.T) { //FIXME: Implement this test - t.Log("Test on implemented") + t.Log("Test not implemented") } func TestPostImagesCreate(t *testing.T) { //FIXME: Implement this test - t.Log("Test on implemented") + t.Log("Test not implemented") } func TestPostImagesInsert(t *testing.T) { //FIXME: Implement this test (or remove this endpoint) - t.Log("Test on implemented") + t.Log("Test not implemented") } func TestPostImagesPush(t *testing.T) { //FIXME: Implement this test - t.Log("Test on implemented") + t.Log("Test not implemented") } func TestPostImagesTag(t *testing.T) { //FIXME: Implement this test - t.Log("Test on implemented") + t.Log("Test not implemented") } func TestPostContainersCreate(t *testing.T) { @@ -651,7 +651,7 @@ func TestPostContainersWait(t *testing.T) { } setTimeout(t, "Wait timed out", 3*time.Second, func() { - body, err := postContainersWait(srv, nil, nil, nil) + body, err := postContainersWait(srv, nil, nil, map[string]string{"name": container.Id}) if err != nil { t.Fatal(err) } diff --git a/commands.go b/commands.go index 41b415c3b..71b1df790 100644 --- a/commands.go +++ b/commands.go @@ -844,13 +844,13 @@ func CmdCommit(args ...string) error { return err } - var out ApiId - err = json.Unmarshal(body, &out) + apiId := &ApiId{} + err = json.Unmarshal(body, apiId) if err != nil { return err } - fmt.Println(out.Id) + fmt.Println(apiId.Id) return nil } @@ -886,7 +886,7 @@ func CmdDiff(args ...string) error { return err } - var changes []Change + changes := []Change{} err = json.Unmarshal(body, &changes) if err != nil { return err @@ -933,8 +933,8 @@ func CmdAttach(args ...string) error { return err } - var container Container - err = json.Unmarshal(body, &container) + container := &Container{} + err = json.Unmarshal(body, container) if err != nil { return err } @@ -968,7 +968,7 @@ func CmdSearch(args ...string) error { return err } - var outs []ApiSearch + outs := []ApiSearch{} err = json.Unmarshal(body, &outs) if err != nil { return err @@ -1100,8 +1100,8 @@ func CmdRun(args ...string) error { return err } - var out ApiRun - err = json.Unmarshal(body, &out) + out := &ApiRun{} + err = json.Unmarshal(body, out) if err != nil { return err } From 28fd289b448164b77affd8103c0d96fd8110daf9 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 9 May 2013 21:53:56 -0700 Subject: [PATCH 65/69] Reduce the Destroy timeout from 10 to 3 seconds --- runtime.go | 2 +- server.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/runtime.go b/runtime.go index 7db3b4032..f2914dba2 100644 --- a/runtime.go +++ b/runtime.go @@ -187,7 +187,7 @@ func (runtime *Runtime) Destroy(container *Container) error { return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.Id) } - if err := container.Stop(10); err != nil { + if err := container.Stop(3); err != nil { return err } if mounted, err := container.Mounted(); err != nil { diff --git a/server.go b/server.go index cd4771f41..3e95d5849 100644 --- a/server.go +++ b/server.go @@ -497,6 +497,7 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std if container == nil { return fmt.Errorf("No such container: %s", name) } + //logs if logs { if stdout { From 310fec4819422ad7ee7a73543746bbd1977ef285 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 9 May 2013 21:54:43 -0700 Subject: [PATCH 66/69] More unit tests for remote api --- api_test.go | 62 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/api_test.go b/api_test.go index 2abd166c5..89093848f 100644 --- a/api_test.go +++ b/api_test.go @@ -1,9 +1,12 @@ package docker import ( + "bufio" "bytes" "encoding/json" "github.com/dotcloud/docker/auth" + "io" + "net" "net/http" "net/http/httptest" "os" @@ -270,24 +273,53 @@ func TestGetContainersExport(t *testing.T) { t.Log("Test not implemented") } -func TestGetContainerChanges(t *testing.T) { - // FIXME: Implement this test - t.Log("Test on implemented") - // r := httptest.NewRecorder() +func TestGetContainersChanges(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) - // req, err := http.NewRequest("GET", "/containers/"+id+"/changes", nil) - // if err != nil { - // t.Fatal(err) - // } + srv := &Server{runtime: runtime} - // body, err := getContainersChanges(srv, r, req, nil) - // if err != nil { - // t.Fatal(err) - // } + builder := NewBuilder(runtime) - // if body == nil { - // t.Fatalf("Body expected, received: nil\n") - // } + // Create a container and remove a file + container, err := builder.Create( + &Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"/bin/rm", "/etc/passwd"}, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + + if err := container.Run(); err != nil { + t.Fatal(err) + } + + body, err := getContainersChanges(srv, nil, nil, map[string]string{"name": container.Id}) + if err != nil { + t.Fatal(err) + } + changes := []Change{} + if err := json.Unmarshal(body, &changes); err != nil { + t.Fatal(err) + } + + // Check the changelog + success := false + for _, elem := range changes { + if elem.Path == "/etc/passwd" && elem.Kind == 2 { + success = true + } + } + if !success { + t.Fatalf("/etc/passwd as been removed but is not present in the diff") + } +} func TestGetContainersByName(t *testing.T) { //FIXME: Implement this test From 15ae314cbb8ede09a2d2c9e70c6a0d7d87817b64 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 9 May 2013 21:55:08 -0700 Subject: [PATCH 67/69] Mock Hijack and Implement Unit test for Attach --- api_test.go | 119 +++++++++++++++++++++++++++++++++++++++++++++-- commands_test.go | 12 ++--- 2 files changed, 121 insertions(+), 10 deletions(-) diff --git a/api_test.go b/api_test.go index 89093848f..5ac3d7ad0 100644 --- a/api_test.go +++ b/api_test.go @@ -702,8 +702,94 @@ func TestPostContainersWait(t *testing.T) { } func TestPostContainersAttach(t *testing.T) { - //FIXME: Implement this test - t.Log("Test on implemented") + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + container, err := NewBuilder(runtime).Create( + &Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"/bin/cat"}, + OpenStdin: true, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + + // Start the process + if err := container.Start(); err != nil { + t.Fatal(err) + } + + stdin, stdinPipe := io.Pipe() + stdout, stdoutPipe := io.Pipe() + + // Attach to it + c1 := make(chan struct{}) + go func() { + // We're simulating a disconnect so the return value doesn't matter. What matters is the + // fact that CmdAttach returns. + + r := &hijackTester{ + ResponseRecorder: httptest.NewRecorder(), + in: stdin, + out: stdoutPipe, + } + + req, err := http.NewRequest("POST", "/containers/"+container.Id+"/attach?stream=1&stdin=1&stdout=1&stderr=1", bytes.NewReader([]byte{})) + if err != nil { + t.Fatal(err) + } + + body, err := postContainersAttach(srv, r, req, map[string]string{"name": container.Id}) + close(c1) + if err != nil { + t.Fatal(err) + } + if body != nil { + t.Fatalf("No body expected, received: %s\n", body) + } + }() + + // Acknowledge hijack + setTimeout(t, "hijack acknowledge timed out", 2*time.Second, func() { + stdout.Read([]byte{}) + stdout.Read(make([]byte, 4096)) + }) + + setTimeout(t, "read/write assertion timed out", 2*time.Second, func() { + if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil { + t.Fatal(err) + } + }) + + // Close pipes (client disconnects) + if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil { + t.Fatal(err) + } + + // Wait for attach to finish, the client disconnected, therefore, Attach finished his job + setTimeout(t, "Waiting for CmdAttach timed out", 2*time.Second, func() { + <-c1 + }) + + // We closed stdin, expect /bin/cat to still be running + // Wait a little bit to make sure container.monitor() did his thing + err = container.WaitTimeout(500 * time.Millisecond) + if err == nil || !container.State.Running { + t.Fatalf("/bin/cat is not running after closing stdin") + } + + // Try to avoid the timeoout in destroy. Best effort, don't check error + cStdin, _ := container.StdinPipe() + cStdin.Close() + container.Wait() } // FIXME: Test deleting runnign container @@ -760,5 +846,32 @@ func TestDeleteContainers(t *testing.T) { func TestDeleteImages(t *testing.T) { //FIXME: Implement this test - t.Log("Test on implemented") + t.Log("Test not implemented") +} + +// Mocked types for tests +type NopConn struct { + io.ReadCloser + io.Writer +} + +func (c *NopConn) LocalAddr() net.Addr { return nil } +func (c *NopConn) RemoteAddr() net.Addr { return nil } +func (c *NopConn) SetDeadline(t time.Time) error { return nil } +func (c *NopConn) SetReadDeadline(t time.Time) error { return nil } +func (c *NopConn) SetWriteDeadline(t time.Time) error { return nil } + +type hijackTester struct { + *httptest.ResponseRecorder + in io.ReadCloser + out io.Writer +} + +func (t *hijackTester) Hijack() (net.Conn, *bufio.ReadWriter, error) { + bufrw := bufio.NewReadWriter(bufio.NewReader(t.in), bufio.NewWriter(t.out)) + conn := &NopConn{ + ReadCloser: t.in, + Writer: t.out, + } + return conn, bufrw, nil } diff --git a/commands_test.go b/commands_test.go index c874e8cd2..80f31e4f7 100644 --- a/commands_test.go +++ b/commands_test.go @@ -1,17 +1,15 @@ package docker import ( - /*"bufio" + "bufio" "fmt" - "github.com/dotcloud/docker/rcli" "io" - "io/ioutil" - "strings"*/ + _ "io/ioutil" + "strings" "testing" "time" ) -/*TODO func closeWrap(args ...io.Closer) error { e := false ret := fmt.Errorf("Error closing elements") @@ -26,7 +24,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) @@ -44,7 +42,6 @@ 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 { @@ -61,6 +58,7 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error return nil } +/*TODO func cmdWait(srv *Server, container *Container) error { stdout, stdoutPipe := io.Pipe() From 1941c7919532b261984214ce4dc6a7927154d9e9 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 9 May 2013 22:28:52 -0700 Subject: [PATCH 68/69] make commands use the correct routes --- api.go | 4 ++-- commands.go | 11 +++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/api.go b/api.go index cd6697dcd..d5396a585 100644 --- a/api.go +++ b/api.go @@ -567,8 +567,8 @@ func ListenAndServe(addr string, srv *Server, logging bool) error { "/commit": postCommit, "/build": postBuild, "/images/create": postImagesCreate, - "/images/{name:*.}/insert": postImagesInsert, - "/images/{name:*.}/push": postImagesPush, + "/images/{name:.*}/insert": postImagesInsert, + "/images/{name:.*}/push": postImagesPush, "/images/{name:.*}/tag": postImagesTag, "/containers/create": postContainersCreate, "/containers/{name:.*}/kill": postContainersKill, diff --git a/commands.go b/commands.go index 71b1df790..5894cb0d2 100644 --- a/commands.go +++ b/commands.go @@ -588,7 +588,7 @@ func CmdImport(args ...string) error { v.Set("tag", tag) v.Set("fromSrc", src) - err := hijack("POST", "/images?"+v.Encode(), false) + err := hijack("POST", "/images/create?"+v.Encode(), false) if err != nil { return err } @@ -645,8 +645,7 @@ func CmdPush(args ...string) error { v := url.Values{} v.Set("registry", *registry) - - if err := hijack("POST", "/images"+name+"/push?"+v.Encode(), false); err != nil { + if err := hijack("POST", "/images/"+name+"/push?"+v.Encode(), false); err != nil { return err } return nil @@ -677,7 +676,7 @@ func CmdPull(args ...string) error { v.Set("tag", *tag) v.Set("registry", *registry) - if err := hijack("POST", "/images?"+v.Encode(), false); err != nil { + if err := hijack("POST", "/images/create?"+v.Encode(), false); err != nil { return err } @@ -716,7 +715,7 @@ func CmdImages(args ...string) error { v.Set("all", "1") } - body, _, err := call("GET", "/images?"+v.Encode(), nil) + body, _, err := call("GET", "/images/json?"+v.Encode(), nil) if err != nil { return err } @@ -1244,12 +1243,12 @@ func hijack(method, path string, setRawTerminal bool) error { if err := <-receiveStdout; err != nil { return err } + if !term.IsTerminal(int(os.Stdin.Fd())) { if err := <-sendStdin; err != nil { return err } } - return nil } From 9897e78e328152fa41f99bd53f7c3a6993c3f87a Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 9 May 2013 22:29:12 -0700 Subject: [PATCH 69/69] Update the doc for remote-api --- docs/sources/remote-api/api.rst | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/sources/remote-api/api.rst b/docs/sources/remote-api/api.rst index 0e9ded548..a6f066264 100644 --- a/docs/sources/remote-api/api.rst +++ b/docs/sources/remote-api/api.rst @@ -20,7 +20,7 @@ Docker Remote API List containers *************** -.. http:get:: /containers +.. http:get:: /containers/ps List containers @@ -28,7 +28,7 @@ List containers .. sourcecode:: http - GET /containers?trunc_cmd=0&all=1&only_ids=0&before=8dfafdbc3a40 HTTP/1.1 + GET /containers/ps?trunc_cmd=0&all=1&only_ids=0&before=8dfafdbc3a40 HTTP/1.1 **Example response**: @@ -81,7 +81,7 @@ List containers Create a container ****************** -.. http:post:: /containers +.. http:post:: /containers/create Create a container @@ -89,7 +89,7 @@ Create a container .. sourcecode:: http - POST /containers HTTP/1.1 + POST /containers/create HTTP/1.1 Content-Type: application/json { @@ -431,7 +431,7 @@ Wait a container Remove a container ******************* -.. http:delete:: /container/(id) +.. http:delete:: /containers/(id) Remove the container ``id`` from the filesystem @@ -459,7 +459,6 @@ Remove a container List Images *********** -.. http:get:: /images .. http:get:: /images/(format) List images ``format`` could be json or viz (json default) @@ -533,7 +532,7 @@ List Images Create an image *************** -.. http:post:: /images +.. http:post:: /images/create Create an image, either by pull it from the registry or by importing it @@ -541,7 +540,7 @@ Create an image .. sourcecode:: http - POST /images?fromImage=base HTTP/1.1 + POST /images/create?fromImage=base HTTP/1.1 **Example response**: @@ -641,7 +640,7 @@ Inspect an image Get the history of an image *************************** -.. http:get:: /images/(name) +.. http:get:: /images/(name)/history Return the history of the image ``name``