From 38e2d00199db5dabe9972ff9c3153ac6b63266f2 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 10 Apr 2013 19:48:21 -0700 Subject: [PATCH 001/101] 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 0000000000..853f7abbde --- /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 20d6b45c97..722d4414bf 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 1b1c21990d..73ea726d50 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 002/101] 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 0000000000..853f7abbde --- /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 82a0ce103d..10890ad92c 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 83c47c6f1e..693ddd72f8 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 003/101] 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 853f7abbde..353a93288a 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 0000000000..277074964d --- /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 10890ad92c..b30f9690e4 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 0000000000..014a4bbe62 --- /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 693ddd72f8..4639921d52 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 004/101] added kill and images(wip) --- api.go | 100 +++++++++++++++++++++++- api_params.go | 25 +++++- commands.go | 1 - commands2.go | 205 ++++++++++++++++++++++++++++++++++++++------------ 4 files changed, 274 insertions(+), 57 deletions(-) diff --git a/api.go b/api.go index 353a93288a..4e2750b332 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 277074964d..f882b353db 100644 --- a/api_params.go +++ b/api_params.go @@ -1,7 +1,24 @@ package docker -type VersionOut struct { - Version string - GitCommit string - MemoryLimitDisabled bool +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 } diff --git a/commands.go b/commands.go index b30f9690e4..59bd87d573 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 014a4bbe62..a2bdedeec6 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 005/101] 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 4e2750b332..948b48d8bb 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 f882b353db..4a2bbee139 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 59bd87d573..f7361b1a13 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 a2bdedeec6..02056f543f 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 74706a4079..96cd423a06 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 006/101] restify api --- api.go | 324 ++++++++++------------------ commands.go | 587 +++++++++++++++++++++++++++++---------------------- commands2.go | 528 --------------------------------------------- 3 files changed, 454 insertions(+), 985 deletions(-) delete mode 100644 commands2.go diff --git a/api.go b/api.go index 948b48d8bb..f5c6acf53f 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 f7361b1a13..8bcbec551e 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")) - } - } - } - // 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")) - } + + 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 (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 02056f543f..0000000000 --- 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 007/101] 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 f5c6acf53f..fdf87f3f15 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 4a2bbee139..8f8b332a09 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 8bcbec551e..e9b0701580 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 428db1b968..7d13efd88f 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 008/101] 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 fdf87f3f15..782276740b 100644 --- a/api.go +++ b/api.go @@ -1,12 +1,14 @@ package docker import ( - _"bytes" + _ "bytes" "encoding/json" "fmt" "github.com/gorilla/mux" + "io" "io/ioutil" "log" + "net" "net/http" "os" "runtime" @@ -20,8 +22,8 @@ func ListenAndServe(addr string, rtime *Runtime) error { log.Printf("Listening for HTTP on %s\n", addr) r.Path("/version").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.RequestURI) - m := VersionOut{VERSION, GIT_COMMIT, NO_MEMORY_LIMIT} + log.Println(r.Method, r.RequestURI) + m := ApiVersion{VERSION, GIT_COMMIT, NO_MEMORY_LIMIT} b, err := json.Marshal(m) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -31,26 +33,29 @@ func ListenAndServe(addr string, rtime *Runtime) error { }) r.Path("/containers/{name:.*}/kill").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.RequestURI) + log.Println(r.Method, r.RequestURI) vars := mux.Vars(r) - name := vars["name"] - if container := rtime.Get(name); container != nil { - if err := container.Kill(); err != nil { - http.Error(w, "Error restarting container "+name+": "+err.Error(), http.StatusInternalServerError) - return - } - } else { - http.Error(w, "No such container: "+name, http.StatusInternalServerError) - return - } - w.WriteHeader(200) + name := vars["name"] + if container := rtime.Get(name); container != nil { + if err := container.Kill(); err != nil { + http.Error(w, "Error restarting container "+name+": "+err.Error(), http.StatusInternalServerError) + return + } + } else { + http.Error(w, "No such container: "+name, http.StatusInternalServerError) + return + } + w.WriteHeader(200) }) r.Path("/images").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.RequestURI) + log.Println(r.Method, r.RequestURI) + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } All := r.Form.Get("all") - NameFilter := r.Form.Get("filter") - Quiet := r.Form.Get("quiet") + NameFilter := r.Form.Get("filter") + Quiet := r.Form.Get("quiet") var allImages map[string]*Image var err error @@ -63,13 +68,13 @@ func ListenAndServe(addr string, rtime *Runtime) error { w.WriteHeader(500) return } - var outs []ImagesOut + var outs []ApiImages for name, repository := range rtime.repositories.Repositories { if NameFilter != "" && name != NameFilter { continue } for tag, id := range repository { - var out ImagesOut + var out ApiImages image, err := rtime.graph.Get(id) if err != nil { log.Printf("Warning: couldn't load %s from %s/%s: %s", id, name, tag, err) @@ -90,7 +95,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { // Display images which aren't part of a if NameFilter == "" { for id, image := range allImages { - var out ImagesOut + var out ApiImages if Quiet != "true" { out.Repository = "" out.Tag = "" @@ -112,7 +117,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { }) r.Path("/info").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.RequestURI) + log.Println(r.Method, r.RequestURI) images, _ := rtime.graph.All() var imgcount int if images == nil { @@ -120,7 +125,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { } else { imgcount = len(images) } - var out InfoOut + var out ApiInfo out.Containers = len(rtime.List()) out.Version = VERSION out.Images = imgcount @@ -138,7 +143,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { }) r.Path("/images/{name:.*}/history").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.RequestURI) + log.Println(r.Method, r.RequestURI) vars := mux.Vars(r) name := vars["name"] @@ -147,9 +152,9 @@ func ListenAndServe(addr string, rtime *Runtime) error { http.Error(w, err.Error(), http.StatusInternalServerError) return } - var outs []HistoryOut + var outs []ApiHistory err = image.WalkHistory(func(img *Image) error { - var out HistoryOut + var out ApiHistory out.Id = rtime.repositories.ImageName(img.ShortId()) out.Created = HumanDuration(time.Now().Sub(img.Created)) + " ago" out.CreatedBy = strings.Join(img.ContainerConfig.Cmd, " ") @@ -165,12 +170,12 @@ func ListenAndServe(addr string, rtime *Runtime) error { }) r.Path("/containers/{name:.*}/logs").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.RequestURI) + log.Println(r.Method, r.RequestURI) vars := mux.Vars(r) name := vars["name"] if container := rtime.Get(name); container != nil { - var out LogsOut + var out ApiLogs logStdout, err := container.ReadLog("stdout") if err != nil { @@ -210,17 +215,47 @@ func ListenAndServe(addr string, rtime *Runtime) error { } }) + r.Path("/containers/{name:.*}/port").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(r.Method, r.RequestURI) + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + privatePort := r.Form.Get("port") + vars := mux.Vars(r) + name := vars["name"] + + if container := rtime.Get(name); container == nil { + http.Error(w, "No such container: "+name, http.StatusInternalServerError) + return + } else { + if frontend, exists := container.NetworkSettings.PortMapping[privatePort]; !exists { + http.Error(w, "No private port '"+privatePort+"' allocated on "+name, http.StatusInternalServerError) + return + } else { + b, err := json.Marshal(ApiPort{frontend}) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } else { + w.Write(b) + } + } + } + }) + r.Path("/containers").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.RequestURI) + log.Println(r.Method, r.RequestURI) + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } All := r.Form.Get("all") - NoTrunc := r.Form.Get("notrunc") - Quiet := r.Form.Get("quiet") + NoTrunc := r.Form.Get("notrunc") + Quiet := r.Form.Get("quiet") Last := r.Form.Get("n") n, err := strconv.Atoi(Last) if err != nil { n = -1 } - var outs []PsOut + var outs []ApiContainers for i, container := range rtime.List() { if !container.State.Running && All != "true" && n == -1 { @@ -229,7 +264,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { if i == n { break } - var out PsOut + var out ApiContainers out.Id = container.ShortId() if Quiet != "true" { command := fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")) @@ -252,41 +287,134 @@ func ListenAndServe(addr string, rtime *Runtime) error { } }) - r.Path("/pull").Methods("GET", "POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var in PullIn - //json.NewDecoder(r.Body).Decode(&in) - in.Name = "base" + r.Path("/images/{name:.*}/tag").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(r.Method, r.RequestURI) + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + vars := mux.Vars(r) + name := vars["name"] + repo := r.Form.Get("repo") + tag := r.Form.Get("tag") + var force bool + if r.Form.Get("force") == "true" { + force = true + } - hj, ok := w.(http.Hijacker) - if !ok { - http.Error(w, "webserver doesn't support hijacking", http.StatusInternalServerError) + if err := rtime.repositories.Set(repo, tag, name, force); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) return } - conn, bufrw, err := hj.Hijack() + w.WriteHeader(200) + }) + + r.Path("/images/{name:.*}/pull").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(r.Method, r.RequestURI) + vars := mux.Vars(r) + name := vars["name"] + + conn, _, err := w.(http.Hijacker).Hijack() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - // Don't forget to close the connection: defer conn.Close() + file, err := conn.(*net.TCPConn).File() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer file.Close() - - - if rtime.graph.LookupRemoteImage(in.Name, rtime.authConfig) { - if err := rtime.graph.PullImage(bufrw, in.Name, rtime.authConfig); err != nil { - //http.Error(w, err.Error(), http.StatusInternalServerError) + fmt.Fprintln(file, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n") + if rtime.graph.LookupRemoteImage(name, rtime.authConfig) { + if err := rtime.graph.PullImage(file, name, rtime.authConfig); err != nil { + fmt.Fprintln(file, "Error: "+err.Error()) } - return + return } - if err := rtime.graph.PullRepository(bufrw, in.Name, "", rtime.repositories, rtime.authConfig); err != nil { - //http.Error(w, err.Error(), http.StatusInternalServerError) + if err := rtime.graph.PullRepository(file, name, "", rtime.repositories, rtime.authConfig); err != nil { + fmt.Fprintln(file, "Error: "+err.Error()) } - return }) + r.Path("/containers").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Println(r.Method, r.RequestURI) + var config Config + if err := json.NewDecoder(r.Body).Decode(&config); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + conn, _, err := w.(http.Hijacker).Hijack() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer conn.Close() + file, err := conn.(*net.TCPConn).File() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer file.Close() + + fmt.Fprintln(file, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n") + container, err := rtime.Create(&config) + if err != nil { + // If container not found, try to pull it + if rtime.graph.IsNotExist(err) { + fmt.Fprintf(file, "Image %s not found, trying to pull it from registry.\r\n", config.Image) + //if err = srv.CmdPull(stdin, stdout, config.Image); err != nil { + // fmt.Fprintln(file, "Error: "+err.Error()) + // return + //} + //if container, err = srv.runtime.Create(config); err != nil { + // fmt.Fprintln(file, "Error: "+err.Error()) + // return + //} + } else { + fmt.Fprintln(file, "Error: "+err.Error()) + return + } + } + var ( + cStdin io.ReadCloser + cStdout, cStderr io.Writer + ) + if config.AttachStdin { + r, w := io.Pipe() + go func() { + defer w.Close() + defer Debugf("Closing buffered stdin pipe") + io.Copy(w, file) + }() + cStdin = r + } + if config.AttachStdout { + cStdout = file + } + if config.AttachStderr { + cStderr = file // FIXME: rcli can't differentiate stdout from stderr + } + + attachErr := container.Attach(cStdin, file, cStdout, cStderr) + Debugf("Starting\n") + if err := container.Start(); err != nil { + fmt.Fprintln(file, "Error: "+err.Error()) + return + } + if cStdout == nil && cStderr == nil { + fmt.Fprintln(file, container.ShortId()) + } + Debugf("Waiting for attach to return\n") + <-attachErr + // Expecting I/O pipe error, discarding + + }) r.Path("/containers/{name:.*}/restart").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.RequestURI) + log.Println(r.Method, r.RequestURI) vars := mux.Vars(r) name := vars["name"] if container := rtime.Get(name); container != nil { @@ -302,7 +430,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { }) r.Path("/containers/{name:.*}").Methods("DELETE").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.RequestURI) + log.Println(r.Method, r.RequestURI) vars := mux.Vars(r) name := vars["name"] if container := rtime.Get(name); container != nil { @@ -318,10 +446,10 @@ func ListenAndServe(addr string, rtime *Runtime) error { }) r.Path("/images/{name:.*}").Methods("DELETE").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.RequestURI) + log.Println(r.Method, r.RequestURI) vars := mux.Vars(r) name := vars["name"] - + img, err := rtime.repositories.LookupImage(name) if err != nil { http.Error(w, "No such image: "+name, http.StatusInternalServerError) @@ -336,7 +464,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { }) r.Path("/containers/{name:.*}/start").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.RequestURI) + log.Println(r.Method, r.RequestURI) vars := mux.Vars(r) name := vars["name"] if container := rtime.Get(name); container != nil { @@ -352,7 +480,7 @@ func ListenAndServe(addr string, rtime *Runtime) error { }) r.Path("/containers/{name:.*}/stop").Methods("POST").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log.Println(r.RequestURI) + log.Println(r.Method, r.RequestURI) vars := mux.Vars(r) name := vars["name"] if container := rtime.Get(name); container != nil { diff --git a/api_params.go b/api_params.go index 8f8b332a09..0b0254d5e9 100644 --- a/api_params.go +++ b/api_params.go @@ -1,33 +1,19 @@ package docker -type SimpleMessage struct { - Message string -} - -type HistoryIn struct { - Name string -} - -type HistoryOut struct { +type ApiHistory struct { Id string Created string CreatedBy string } -type ImagesIn struct { - NameFilter string - Quiet bool - All bool -} - -type ImagesOut struct { +type ApiImages struct { Repository string `json:",omitempty"` Tag string `json:",omitempty"` Id string Created string `json:",omitempty"` } -type InfoOut struct { +type ApiInfo struct { Containers int Version string Images int @@ -36,14 +22,7 @@ type InfoOut struct { NGoroutines int `json:",omitempty"` } -type PsIn struct { - Quiet bool - All bool - Full bool - Last int -} - -type PsOut struct { +type ApiContainers struct { Id string Image string `json:",omitempty"` Command string `json:",omitempty"` @@ -51,20 +30,16 @@ type PsOut struct { Status string `json:",omitempty"` } -type PullIn struct { - Name string -} - -type LogsIn struct { - Name string -} - -type LogsOut struct { +type ApiLogs struct { Stdout string Stderr string } -type VersionOut struct { +type ApiPort struct { + Port string +} + +type ApiVersion struct { Version string GitCommit string MemoryLimitDisabled bool diff --git a/commands.go b/commands.go index e9b0701580..2549e3823f 100644 --- a/commands.go +++ b/commands.go @@ -1,13 +1,15 @@ package docker import ( - _"bytes" + "bytes" "encoding/json" "flag" "fmt" - _"io" + "io" "io/ioutil" + "net" "net/http" + "net/http/httputil" "net/url" "os" "strconv" @@ -29,10 +31,14 @@ func ParseCommands(args []string) error { "history": CmdHistory, "kill": CmdKill, "logs": CmdLogs, + "port": CmdPort, "ps": CmdPs, + "pull": CmdPull, "restart": CmdRestart, "rm": CmdRm, "rmi": CmdRmi, + "run": CmdRun, + "tag": CmdTag, "start": CmdStart, "stop": CmdStop, "version": CmdVersion, @@ -41,7 +47,7 @@ func ParseCommands(args []string) error { if len(args) > 0 { cmd, exists := cmds[args[0]] if !exists { - //TODO display commend not found + fmt.Println("Error: Command not found:", args[0]) return cmdHelp(args) } return cmd(args[1:]) @@ -64,17 +70,17 @@ func cmdHelp(args []string) error { {"kill", "Kill a running container"}, // {"login", "Register or Login to the docker registry server"}, {"logs", "Fetch the logs of a container"}, - // {"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"}, + {"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"}, {"ps", "List containers"}, - // {"pull", "Pull an image or a repository from the docker registry server"}, + {"pull", "Pull an image or a repository from the docker registry server"}, // {"push", "Push an image or a repository to the docker registry server"}, {"restart", "Restart a running container"}, {"rm", "Remove a container"}, {"rmi", "Remove an image"}, - // {"run", "Run a command in a new container"}, + {"run", "Run a command in a new container"}, {"start", "Start a stopped container"}, {"stop", "Stop a running container"}, - // {"tag", "Tag an image into a repository"}, + {"tag", "Tag an image into a repository"}, {"version", "Show the docker version information"}, // {"wait", "Block until a container stops, then print its exit code"}, } { @@ -206,7 +212,6 @@ func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string } */ - // 'docker version': show version information func CmdVersion(args []string) error { cmd := Subcmd("version", "", "Show the docker version information.") @@ -218,12 +223,12 @@ func CmdVersion(args []string) error { return nil } - body, err := call("GET", "version") + body, err := call("GET", "/version") if err != nil { return err } - var out VersionOut + var out ApiVersion err = json.Unmarshal(body, &out) if err != nil { return err @@ -248,12 +253,12 @@ func CmdInfo(args []string) error { return nil } - body, err := call("GET", "info") + body, err := call("GET", "/info") if err != nil { return err } - var out InfoOut + var out ApiInfo err = json.Unmarshal(body, &out) if err != nil { return err @@ -277,7 +282,7 @@ func CmdStop(args []string) error { } for _, name := range args { - _, err := call("GET", "/containers/"+name+"/stop") + _, err := call("POST", "/containers/"+name+"/stop") if err != nil { fmt.Printf("%s", err) } else { @@ -298,7 +303,7 @@ func CmdRestart(args []string) error { } for _, name := range args { - _, err := call("GET", "/containers/"+name+"/restart") + _, err := call("POST", "/containers/"+name+"/restart") if err != nil { fmt.Printf("%s", err) } else { @@ -319,7 +324,7 @@ func CmdStart(args []string) error { } for _, name := range args { - _, err := call("GET", "/containers/"+name+"/start") + _, err := call("POST", "/containers/"+name+"/start") if err != nil { fmt.Printf("%s", err) } else { @@ -366,9 +371,8 @@ func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...str } */ -/* -func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "port", "CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT") +func CmdPort(args []string) error { + cmd := Subcmd("port", "CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT") if err := cmd.Parse(args); err != nil { return nil } @@ -376,20 +380,21 @@ func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string cmd.Usage() return nil } - name := cmd.Arg(0) - privatePort := cmd.Arg(1) - if container := srv.runtime.Get(name); container == nil { - return fmt.Errorf("No such container: %s", name) - } else { - if frontend, exists := container.NetworkSettings.PortMapping[privatePort]; !exists { - return fmt.Errorf("No private port '%s' allocated on %s", privatePort, name) - } else { - fmt.Fprintln(stdout, frontend) - } + v := url.Values{} + v.Set("port", cmd.Arg(1)) + body, err := call("GET", "/containers/"+cmd.Arg(0)+"/port?"+v.Encode()) + if err != nil { + return err } + + var out ApiPort + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + fmt.Println(out.Port) return nil } -*/ // 'docker rmi IMAGE' removes all images with the name IMAGE func CmdRmi(args []string) error { @@ -423,12 +428,12 @@ func CmdHistory(args []string) error { return nil } - body, err := call("GET", "images/"+cmd.Arg(0)+"/history") + body, err := call("GET", "/images/"+cmd.Arg(0)+"/history") if err != nil { return err } - var outs []HistoryOut + var outs []ApiHistory err = json.Unmarshal(body, &outs) if err != nil { return err @@ -599,38 +604,29 @@ func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ... } */ -/* -func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "pull", "NAME", "Pull an image or a repository from the registry") +func CmdPull(args []string) error { + cmd := Subcmd("pull", "NAME", "Pull an image or a repository from the registry") if err := cmd.Parse(args); err != nil { return nil } - remote := cmd.Arg(0) - if remote == "" { + + if cmd.NArg() != 1 { cmd.Usage() return nil } - // FIXME: CmdPull should be a wrapper around Runtime.Pull() - if srv.runtime.graph.LookupRemoteImage(remote, srv.runtime.authConfig) { - // if err := srv.runtime.graph.PullImage(stdout, remote, srv.runtime.authConfig); err != nil { - // return err - // } - return nil + if err := callStream("POST", "/images/"+cmd.Arg(0)+"/pull", nil, false); err != nil { + return err } - // FIXME: Allow pull repo:tag - //if err := srv.runtime.graph.PullRepository(stdout, remote, "", srv.runtime.repositories, srv.runtime.authConfig); err != nil { - // return err - //} + return nil } -*/ func CmdImages(args []string) error { cmd := Subcmd("images", "[OPTIONS] [NAME]", "List images") quiet := cmd.Bool("q", false, "only show numeric IDs") all := cmd.Bool("a", false, "show all images") - + if err := cmd.Parse(args); err != nil { return nil } @@ -649,12 +645,12 @@ func CmdImages(args []string) error { v.Set("all", "true") } - body, err := call("GET", "images?"+v.Encode()) + body, err := call("GET", "/images?"+v.Encode()) if err != nil { return err } - var outs []ImagesOut + var outs []ApiImages err = json.Unmarshal(body, &outs) if err != nil { return err @@ -705,13 +701,13 @@ func CmdPs(args []string) error { if *last != -1 { v.Set("n", strconv.Itoa(*last)) } - - body, err := call("GET", "containers?"+v.Encode()) + + body, err := call("GET", "/containers?"+v.Encode()) if err != nil { return err } - var outs []PsOut + var outs []ApiContainers err = json.Unmarshal(body, &outs) if err != nil { return err @@ -723,7 +719,7 @@ func CmdPs(args []string) error { for _, out := range outs { if !*quiet { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", out.Id, out.Image, out.Command, out.Status, out.Created) + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", out.Id, out.Image, out.Command, out.Status, out.Created) } else { fmt.Fprintln(w, out.Id) } @@ -818,12 +814,12 @@ func CmdLogs(args []string) error { cmd.Usage() return nil } - body, err := call("GET", "containers/"+cmd.Arg(0)+"/logs") + body, err := call("GET", "/containers/"+cmd.Arg(0)+"/logs") if err != nil { return err } - var out LogsOut + var out ApiLogs err = json.Unmarshal(body, &out) if err != nil { return err @@ -915,98 +911,56 @@ func (opts AttachOpts) Get(val string) bool { return false } - -/* -func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string) error { - cmd := rcli.Subcmd(stdout, "tag", "[OPTIONS] IMAGE REPOSITORY [TAG]", "Tag an image into a repository") +func CmdTag(args []string) error { + cmd := Subcmd("tag", "[OPTIONS] IMAGE REPOSITORY [TAG]", "Tag an image into a repository") force := cmd.Bool("f", false, "Force") if err := cmd.Parse(args); err != nil { return nil } - if cmd.NArg() < 2 { + if cmd.NArg() != 2 && cmd.NArg() != 3 { cmd.Usage() return nil } - return srv.runtime.repositories.Set(cmd.Arg(1), cmd.Arg(2), cmd.Arg(0), *force) -} -*/ -/* -func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error { - config, err := ParseRun(args) + v := url.Values{} + v.Set("repo", cmd.Arg(1)) + if cmd.NArg() == 3 { + v.Set("tag", cmd.Arg(2)) + } + + if *force { + v.Set("force", "true") + } + + if err := callStream("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil, false); err != nil { + return err + } + return nil +} + +func CmdRun(args []string) error { + fmt.Println("CmdRun") + config, cmd, err := ParseRun(args) if err != nil { return err } if config.Image == "" { - fmt.Fprintln(stdout, "Error: Image not specified") - return fmt.Errorf("Image not specified") + cmd.Usage() + return nil } if len(config.Cmd) == 0 { - fmt.Fprintln(stdout, "Error: Command not specified") - return fmt.Errorf("Command not specified") + cmd.Usage() + return nil } - if config.Tty { - stdout.SetOptionRawTerminal() - } - // Flush the options to make sure the client sets the raw mode - // or tell the client there is no options - stdout.Flush() - - // Create new container - container, err := srv.runtime.Create(config) - if err != nil { - // If container not found, try to pull it - if srv.runtime.graph.IsNotExist(err) { - fmt.Fprintf(stdout, "Image %s not found, trying to pull it from registry.\r\n", config.Image) - if err = srv.CmdPull(stdin, stdout, config.Image); err != nil { - return err - } - if container, err = srv.runtime.Create(config); err != nil { - return err - } - } else { - return err - } - } - var ( - cStdin io.ReadCloser - cStdout, cStderr io.Writer - ) - if config.AttachStdin { - r, w := io.Pipe() - go func() { - defer w.Close() - defer Debugf("Closing buffered stdin pipe") - io.Copy(w, stdin) - }() - cStdin = r - } - if config.AttachStdout { - cStdout = stdout - } - if config.AttachStderr { - cStderr = stdout // FIXME: rcli can't differentiate stdout from stderr - } - - attachErr := container.Attach(cStdin, stdin, cStdout, cStderr) - Debugf("Starting\n") - if err := container.Start(); err != nil { + if err := callStream("POST", "/containers", *config, config.Tty); err != nil { return err } - if cStdout == nil && cStderr == nil { - fmt.Fprintln(stdout, container.ShortId()) - } - Debugf("Waiting for attach to return\n") - <-attachErr - // Expecting I/O pipe error, discarding return nil - } - */ +} - func call(method, path string) ([]byte, error) { - req, err := http.NewRequest(method, "http://0.0.0.0:4243/" + path, nil) + req, err := http.NewRequest(method, "http://0.0.0.0:4243"+path, nil) if err != nil { return nil, err } @@ -1025,43 +979,59 @@ func call(method, path string) ([]byte, error) { return body, nil } -/* -func apiPost(path string, data interface{}) ([]byte, error) { - buf, err := json.Marshal(data) + +func callStream(method, path string, data interface{}, isTerminal bool) error { + var body io.Reader + if data != nil { + buf, err := json.Marshal(data) + if err != nil { + return err + } + body = bytes.NewBuffer(buf) + } + req, err := http.NewRequest(method, path, body) if err != nil { - return nil, err + return err } - dataBuf := bytes.NewBuffer(buf) - resp, err := http.Post("http://0.0.0.0:4243/"+path, "application/json", dataBuf) + + if data != nil { + req.Header.Set("Content-Type", "application/json") + } + + dial, err := net.Dial("tcp", "0.0.0.0:4243") if err != nil { - return nil, err + return err } - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err + clientconn := httputil.NewClientConn(dial, nil) + clientconn.Do(req) + defer clientconn.Close() + + rwc, _ := clientconn.Hijack() + defer rwc.Close() + + receiveStdout := Go(func() error { + _, err := io.Copy(os.Stdout, rwc) + return err + }) + sendStdin := Go(func() error { + _, err := io.Copy(rwc, os.Stdin) + rwc.Close() + return err + }) + + if err := <-receiveStdout; err != nil { + return err } - if resp.StatusCode != 200 { - return nil, fmt.Errorf("[error] %s", body) + if isTerminal { + if err := <-sendStdin; err != nil { + return err + } } - return body, nil + + return nil + } -func apiPostHijack(path string, data interface{}) (io.ReadCloser, error) { - buf, err := json.Marshal(data) - if err != nil { - return nil, err - } - dataBuf := bytes.NewBuffer(buf) - resp, err := http.Post("http://0.0.0.0:4243/"+path, "application/json", dataBuf) - if err != nil { - return nil, err - } - //TODO check status code - - return resp.Body, nil -} -*/ func Subcmd(name, signature, description string) *flag.FlagSet { flags := flag.NewFlagSet(name, flag.ContinueOnError) flags.Usage = func() { diff --git a/container.go b/container.go index 96cd423a06..0f40daadf3 100644 --- a/container.go +++ b/container.go @@ -2,6 +2,7 @@ package docker import ( "encoding/json" + "flag" "fmt" "github.com/kr/pty" "io" @@ -65,7 +66,7 @@ type Config struct { Image string // Name of the image as it was passed by the operator (eg. could be symbolic) } -func ParseRun(args []string) (*Config, error) { +func ParseRun(args []string) (*Config, *flag.FlagSet, error) { cmd := Subcmd("run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container") if len(args) > 0 && args[0] != "--help" { cmd.SetOutput(ioutil.Discard) @@ -95,10 +96,10 @@ func ParseRun(args []string) (*Config, error) { cmd.Var(&flDns, "dns", "Set custom dns servers") if err := cmd.Parse(args); err != nil { - return nil, err + return nil, cmd, err } if *flDetach && len(flAttach) > 0 { - return nil, fmt.Errorf("Conflicting options: -a and -d") + return nil, cmd, fmt.Errorf("Conflicting options: -a and -d") } // If neither -d or -a are set, attach to everything by default if len(flAttach) == 0 && !*flDetach { @@ -138,7 +139,7 @@ func ParseRun(args []string) (*Config, error) { if config.OpenStdin && config.AttachStdin { config.StdinOnce = true } - return config, nil + return config, cmd, nil } type NetworkSettings struct { diff --git a/registry.go b/registry.go index 7d13efd88f..428db1b968 100644 --- a/registry.go +++ b/registry.go @@ -1,7 +1,6 @@ package docker import ( - "bufio" "encoding/json" "fmt" "github.com/dotcloud/docker/auth" @@ -95,10 +94,10 @@ func (graph *Graph) LookupRemoteImage(imgId string, authConfig *auth.AuthConfig) // Retrieve an image from the Registry. // Returns the Image object as well as the layer as an Archive (io.Reader) -func (graph *Graph) getRemoteImage(stdout *bufio.ReadWriter, imgId string, authConfig *auth.AuthConfig) (*Image, Archive, error) { +func (graph *Graph) getRemoteImage(stdout io.Writer, imgId string, authConfig *auth.AuthConfig) (*Image, Archive, error) { client := &http.Client{} - fmt.Fprintf(stdout, "Pulling %s metadata\r\n", imgId);stdout.Flush() + fmt.Fprintf(stdout, "Pulling %s metadata\r\n", imgId) // Get the Json req, err := http.NewRequest("GET", REGISTRY_ENDPOINT+"/images/"+imgId+"/json", nil) if err != nil { @@ -126,7 +125,7 @@ func (graph *Graph) getRemoteImage(stdout *bufio.ReadWriter, imgId string, authC img.Id = imgId // Get the layer - fmt.Fprintf(stdout, "Pulling %s fs layer\r\n", imgId);stdout.Flush() + fmt.Fprintf(stdout, "Pulling %s fs layer\r\n", imgId) req, err = http.NewRequest("GET", REGISTRY_ENDPOINT+"/images/"+imgId+"/layer", nil) if err != nil { return nil, nil, fmt.Errorf("Error while getting from the server: %s\n", err) @@ -139,7 +138,7 @@ func (graph *Graph) getRemoteImage(stdout *bufio.ReadWriter, imgId string, authC return img, ProgressReader(res.Body, int(res.ContentLength), stdout), nil } -func (graph *Graph) PullImage(stdout *bufio.ReadWriter, imgId string, authConfig *auth.AuthConfig) error { +func (graph *Graph) PullImage(stdout io.Writer, imgId string, authConfig *auth.AuthConfig) error { history, err := graph.getRemoteHistory(imgId, authConfig) if err != nil { return err @@ -162,7 +161,7 @@ func (graph *Graph) PullImage(stdout *bufio.ReadWriter, imgId string, authConfig } // FIXME: Handle the askedTag parameter -func (graph *Graph) PullRepository(stdout *bufio.ReadWriter, remote, askedTag string, repositories *TagStore, authConfig *auth.AuthConfig) error { +func (graph *Graph) PullRepository(stdout io.Writer, remote, askedTag string, repositories *TagStore, authConfig *auth.AuthConfig) error { client := &http.Client{} fmt.Fprintf(stdout, "Pulling repository %s\r\n", remote) From 1e357c69690f5325d92ae7c41e396711865382f0 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 24 Apr 2013 14:01:40 +0200 Subject: [PATCH 009/101] 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 782276740b..776a0e725b 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 0b0254d5e9..cd6c77d1b1 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 2549e3823f..3065bc5410 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 010/101] 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 776a0e725b..e5e0052d43 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 cd6c77d1b1..ed19fd869c 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 3065bc5410..8ccba086ed 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 011/101] added export --- api.go | 37 +++++++++++++++++++++++++++++++++++++ commands.go | 42 +++++++++++++++++++++++------------------- 2 files changed, 60 insertions(+), 19 deletions(-) diff --git a/api.go b/api.go index e5e0052d43..1b9507dd6e 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 8ccba086ed..7d07e897cb 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 012/101] fixed inspect --- commands.go | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/commands.go b/commands.go index 7d07e897cb..e9458d43be 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 013/101] 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 1b9507dd6e..3de2479a44 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 e9458d43be..e39c03f242 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 014/101] 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 1b9507dd6e..81b4ff963b 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 e9458d43be..e39c03f242 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 015/101] 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 81b4ff963b..e66dc1e648 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 e39c03f242..c934fe6bb4 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 68e12b20bd..5ff1ad7a03 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 016/101] 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 e66dc1e648..3bcb423d5c 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 c934fe6bb4..f8a5d2a68d 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 017/101] [] 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 3bcb423d5c..7e0c8fc249 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 ed19fd869c..837b5a6426 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 f8a5d2a68d..963689598a 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 018/101] 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 7e0c8fc249..b574bab4d9 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 837b5a6426..0d3a6c08cb 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 963689598a..4fcf3dcd39 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 019/101] makefile from master --- Makefile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 67b805b830..f35362d2ca 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 020/101] 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 f35362d2ca..bae7d64909 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 df442ab39a..decd273e27 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 d847ed365f..87c6dcc55e 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 83b480d52a..784ef41965 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 80d424be3c..7cfc39c03e 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 e9be838c0e..d9e504e1a6 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 0000000000..ddf1f849e7 --- /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 021/101] 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 738d8d5633..23a63a4b33 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 0b46f8c727..b86265b178 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 0305c115af..168c04bc2f 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 4d6feb3f71..7ea71d1beb 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 40f67f7193..8f269df40c 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 022/101] 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 168c04bc2f..3103f52766 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 cf111cdf71..0000000000 --- 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 38f4a8c008..0000000000 --- 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 dbd579ffcd..0000000000 --- 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 0000000000..fe10cfd356 --- /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 8f269df40c..b5258fd494 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 23c5c130148985e3e527fa799beb45326ac791a9 Mon Sep 17 00:00:00 2001 From: Nate Jones Date: Mon, 6 May 2013 21:31:59 -0700 Subject: [PATCH 023/101] fix ByParent --- graph.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/graph.go b/graph.go index 21d9d9407c..9a8fd1b3cb 100644 --- a/graph.go +++ b/graph.go @@ -253,14 +253,14 @@ func (graph *Graph) WalkAll(handler func(*Image)) error { func (graph *Graph) ByParent() (map[string][]*Image, error) { byParent := make(map[string][]*Image) err := graph.WalkAll(func(image *Image) { - image, err := graph.Get(image.Parent) + parent, err := graph.Get(image.Parent) if err != nil { return } - if children, exists := byParent[image.Parent]; exists { - byParent[image.Parent] = []*Image{image} + if children, exists := byParent[parent.Id]; exists { + byParent[parent.Id] = []*Image{image} } else { - byParent[image.Parent] = append(children, image) + byParent[parent.Id] = append(children, image) } }) return byParent, err From 0b6c79b303547e38c758c16da2db1a430296953e Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 7 May 2013 17:19:41 +0200 Subject: [PATCH 024/101] 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 23a63a4b33..11c722f507 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 3103f52766..510ab849cf 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 c05c7d10a4..4c54d8bb62 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 4e724a0cd2..bbd879334c 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 0000000000..361289010b --- /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 0000000000..5b3b790b56 --- /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 d2eb2455a1fc379dbac94d651b0785b631b84add Mon Sep 17 00:00:00 2001 From: unclejack Date: Tue, 7 May 2013 20:57:21 +0300 Subject: [PATCH 025/101] kernel version - don't show the dash if flavor is empty --- utils.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/utils.go b/utils.go index 2d54ef4c6a..217fad0322 100644 --- a/utils.go +++ b/utils.go @@ -425,7 +425,11 @@ func GetKernelVersion() (*KernelVersionInfo, error) { } func (k *KernelVersionInfo) String() string { - return fmt.Sprintf("%d.%d.%d-%s", k.Kernel, k.Major, k.Minor, k.Flavor) + flavor := "" + if len(k.Flavor) > 0 { + flavor = fmt.Sprintf("-%s", k.Flavor) + } + return fmt.Sprintf("%d.%d.%d%s", k.Kernel, k.Major, k.Minor, flavor) } // Compare two KernelVersionInfo struct. From 42b1ea4889cd61790f81001ada5deac9354be378 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 7 May 2013 11:18:13 -0700 Subject: [PATCH 026/101] Check the command existance prior create and add Unit tests for the case --- builder.go | 2 +- runtime.go | 4 ++++ runtime_test.go | 25 ++++++++++++++++++++++++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/builder.go b/builder.go index e835c6d390..d8a22ac3c8 100644 --- a/builder.go +++ b/builder.go @@ -72,7 +72,7 @@ func (builder *Builder) Create(config *Config) (*Container, error) { builder.mergeConfig(config, img.Config) } - if config.Cmd == nil { + if config.Cmd == nil || len(config.Cmd) == 0 { return nil, fmt.Errorf("No command specified") } diff --git a/runtime.go b/runtime.go index 5958aa1811..7db3b40325 100644 --- a/runtime.go +++ b/runtime.go @@ -178,6 +178,10 @@ func (runtime *Runtime) LogToDisk(src *writeBroadcaster, dst string) error { } func (runtime *Runtime) Destroy(container *Container) error { + if container == nil { + return fmt.Errorf("The given container is ") + } + element := runtime.getContainerElement(container.Id) if element == nil { return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.Id) diff --git a/runtime_test.go b/runtime_test.go index 8e21f57bc5..68e1213497 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -118,7 +118,10 @@ func TestRuntimeCreate(t *testing.T) { if len(runtime.List()) != 0 { t.Errorf("Expected 0 containers, %v found", len(runtime.List())) } - container, err := NewBuilder(runtime).Create(&Config{ + + builder := NewBuilder(runtime) + + container, err := builder.Create(&Config{ Image: GetTestImage(runtime).Id, Cmd: []string{"ls", "-al"}, }, @@ -157,6 +160,26 @@ func TestRuntimeCreate(t *testing.T) { if !runtime.Exists(container.Id) { t.Errorf("Exists() returned false for a newly created container") } + + // Make sure crete with bad parameters returns an error + _, err = builder.Create( + &Config{ + Image: GetTestImage(runtime).Id, + }, + ) + if err == nil { + t.Fatal("Builder.Create should throw an error when Cmd is missing") + } + + _, err = builder.Create( + &Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{}, + }, + ) + if err == nil { + t.Fatal("Builder.Create should throw an error when Cmd is empty") + } } func TestDestroy(t *testing.T) { From 40794113750a3d21e784e2521ae517f23a081135 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 7 May 2013 20:59:04 +0200 Subject: [PATCH 027/101] 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 073a469522..91b61c82ac 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 d57637abd7..c727b2b292 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 d693bfc857..2af25440dc 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 00d5c73491..df81706aa1 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 028/101] 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 2af25440dc..1f4c3acfcb 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 029/101] remove useless wait in run --- commands.go | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/commands.go b/commands.go index 1f4c3acfcb..3f151a9d51 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 030/101] removed RAW mode on server --- server.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/server.go b/server.go index df81706aa1..a2525a9b90 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 031/101] 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 3f151a9d51..3020459f02 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 a2525a9b90..788e6bdf08 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 032/101] 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 91b61c82ac..2f00e300d9 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 3020459f02..2ada46ff96 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 788e6bdf08..b22e494401 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 033/101] 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 2f00e300d9..23ccd403bf 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 b22e494401..7399a399d9 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 034/101] 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 23ccd403bf..8eb160a142 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 7399a399d9..1d5eaa850d 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 035/101] Factorize api.go --- api.go | 381 ++++++++++++++++++++------------------------------------- 1 file changed, 131 insertions(+), 250 deletions(-) diff --git a/api.go b/api.go index 8eb160a142..ea66fce9ce 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 036/101] 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 ea66fce9ce..b77a60af42 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 2ada46ff96..9a3bfaab9d 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 037/101] 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 b77a60af42..b95b1c26d8 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 1d5eaa850d..5fb4191130 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 038/101] Small fix --- api.go | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/api.go b/api.go index b95b1c26d8..c24b969c35 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 039/101] 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 c24b969c35..ea7a6f1461 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 9a3bfaab9d..a19a37d5ff 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 5fb4191130..7f91bf0196 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 040/101] 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 ea7a6f1461..167ed1ad07 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 0000000000..d92eacf519 --- /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 a19a37d5ff..96960455de 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 210e289f34..1a430aa010 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 7f91bf0196..7ba3b68f83 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 041/101] 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 167ed1ad07..136cd52d7c 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 96960455de..02e02d6cec 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 1a430aa010..25007af123 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 7ba3b68f83..f57d8e6f06 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 042/101] 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 136cd52d7c..6d14f1f17b 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 02e02d6cec..81575c7b14 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 25007af123..eb26cf97ad 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 f57d8e6f06..ffc66d622a 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 043/101] 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 6d14f1f17b..a6022a105e 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 81575c7b14..167834397a 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 044/101] 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 a6022a105e..3d6484964d 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 eb26cf97ad..aa8c90bf99 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 045/101] fix commit without run parameter --- api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.go b/api.go index 3d6484964d..6f6e5dfc74 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 c4ebf870c8a9cef909e66fb517e607896ea8a518 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 8 May 2013 10:35:41 -0700 Subject: [PATCH 046/101] Use make instead of new --- image.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/image.go b/image.go index bf86e2e7f7..5778931655 100644 --- a/image.go +++ b/image.go @@ -267,13 +267,13 @@ func (img *Image) Checksum() (string, error) { } checksumDictPth := path.Join(root, "..", "..", "checksums") - checksums := new(map[string]string) + checksums := make(map[string]string) if checksumDict, err := ioutil.ReadFile(checksumDictPth); err == nil { if err := json.Unmarshal(checksumDict, checksums); err != nil { return "", err } - if checksum, ok := (*checksums)[img.Id]; ok { + if checksum, ok := checksums[img.Id]; ok { return checksum, nil } } @@ -304,10 +304,8 @@ func (img *Image) Checksum() (string, error) { } hash := "sha256:" + hex.EncodeToString(h.Sum(nil)) - if *checksums == nil { - *checksums = map[string]string{} - } - (*checksums)[img.Id] = hash + checksums[img.Id] = hash + checksumJson, err := json.Marshal(checksums) if err != nil { return hash, err From 8ff1765674c93a169fe29483503709a008f30618 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 8 May 2013 10:42:29 -0700 Subject: [PATCH 047/101] Make the checksum async within commit --- graph.go | 25 +++++++++++++++++++------ image.go | 27 +++++++++++++++++++++------ 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/graph.go b/graph.go index 9a8fd1b3cb..4b504525e7 100644 --- a/graph.go +++ b/graph.go @@ -9,14 +9,18 @@ import ( "path" "path/filepath" "strings" + "sync" "time" ) // A Graph is a store for versioned filesystem images and the relationship between them. type Graph struct { - Root string - idIndex *TruncIndex - httpClient *http.Client + Root string + idIndex *TruncIndex + httpClient *http.Client + checksumLock map[string]*sync.Mutex + lockSumFile *sync.Mutex + lockSumMap *sync.Mutex } // NewGraph instantiates a new graph at the given root path in the filesystem. @@ -31,8 +35,11 @@ func NewGraph(root string) (*Graph, error) { return nil, err } graph := &Graph{ - Root: abspath, - idIndex: NewTruncIndex(), + Root: abspath, + idIndex: NewTruncIndex(), + checksumLock: make(map[string]*sync.Mutex), + lockSumFile: &sync.Mutex{}, + lockSumMap: &sync.Mutex{}, } if err := graph.restore(); err != nil { return nil, err @@ -82,6 +89,11 @@ func (graph *Graph) Get(name string) (*Image, error) { return nil, fmt.Errorf("Image stored at '%s' has wrong id '%s'", id, img.Id) } img.graph = graph + graph.lockSumMap.Lock() + defer graph.lockSumMap.Unlock() + if _, exists := graph.checksumLock[img.Id]; !exists { + graph.checksumLock[img.Id] = &sync.Mutex{} + } return img, nil } @@ -103,7 +115,7 @@ func (graph *Graph) Create(layerData Archive, container *Container, comment, aut if err := graph.Register(layerData, img); err != nil { return nil, err } - img.Checksum() + go img.Checksum() return img, nil } @@ -131,6 +143,7 @@ func (graph *Graph) Register(layerData Archive, img *Image) error { } img.graph = graph graph.idIndex.Add(img.Id) + graph.checksumLock[img.Id] = &sync.Mutex{} return nil } diff --git a/image.go b/image.go index 5778931655..3e8ac55d3d 100644 --- a/image.go +++ b/image.go @@ -35,8 +35,9 @@ func LoadImage(root string) (*Image, error) { if err != nil { return nil, err } - var img Image - if err := json.Unmarshal(jsonData, &img); err != nil { + img := &Image{} + + if err := json.Unmarshal(jsonData, img); err != nil { return nil, err } if err := ValidateId(img.Id); err != nil { @@ -52,8 +53,7 @@ func LoadImage(root string) (*Image, error) { } else if !stat.IsDir() { return nil, fmt.Errorf("Couldn't load image %s: %s is not a directory", img.Id, layerPath(root)) } - - return &img, nil + return img, nil } func StoreImage(img *Image, layerData Archive, root string) error { @@ -261,6 +261,9 @@ func (img *Image) layer() (string, error) { } func (img *Image) Checksum() (string, error) { + img.graph.checksumLock[img.Id].Lock() + defer img.graph.checksumLock[img.Id].Unlock() + root, err := img.root() if err != nil { return "", err @@ -270,7 +273,7 @@ func (img *Image) Checksum() (string, error) { checksums := make(map[string]string) if checksumDict, err := ioutil.ReadFile(checksumDictPth); err == nil { - if err := json.Unmarshal(checksumDict, checksums); err != nil { + if err := json.Unmarshal(checksumDict, &checksums); err != nil { return "", err } if checksum, ok := checksums[img.Id]; ok { @@ -299,18 +302,30 @@ func (img *Image) Checksum() (string, error) { if _, err := h.Write([]byte("\n")); err != nil { return "", err } + + fmt.Printf("precopy %s: %s\n", img.ShortId(), time.Now().String()) + if _, err := io.Copy(h, layerData); err != nil { return "", err } + fmt.Printf("postcopy presum %s: %s\n", img.ShortId(), time.Now().String()) hash := "sha256:" + hex.EncodeToString(h.Sum(nil)) checksums[img.Id] = hash + fmt.Printf("postsum %s: %s\n", img.ShortId(), time.Now().String()) + // Reload the json file to make sure not to overwrite faster sums + img.graph.lockSumFile.Lock() + defer img.graph.lockSumFile.Unlock() + if checksumDict, err := ioutil.ReadFile(checksumDictPth); err == nil { + if err := json.Unmarshal(checksumDict, &checksums); err != nil { + return "", err + } + } checksumJson, err := json.Marshal(checksums) if err != nil { return hash, err } - if err := ioutil.WriteFile(checksumDictPth, checksumJson, 0600); err != nil { return hash, err } From 070b1cd541cb8a60755a3c5244af00bca6ccaf51 Mon Sep 17 00:00:00 2001 From: Thatcher Peskens Date: Wed, 8 May 2013 12:13:31 -0700 Subject: [PATCH 048/101] Added a line to css to make sure th woul align left. --- docs/theme/docker/static/css/main.css | 4 ++++ docs/theme/docker/static/css/main.less | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/docs/theme/docker/static/css/main.css b/docs/theme/docker/static/css/main.css index 3948acc55d..0629efeb48 100755 --- a/docs/theme/docker/static/css/main.css +++ b/docs/theme/docker/static/css/main.css @@ -330,3 +330,7 @@ section.header { @media (max-width: 480px) { } +/* Misc fixes */ +table th { + text-align: left; +} diff --git a/docs/theme/docker/static/css/main.less b/docs/theme/docker/static/css/main.less index 69f53f9e1b..50c8fe6b4b 100644 --- a/docs/theme/docker/static/css/main.less +++ b/docs/theme/docker/static/css/main.less @@ -449,4 +449,9 @@ section.header { @media (max-width: 480px) { +} + +/* Misc fixes */ +table th { + text-align: left; } \ No newline at end of file From c4ad6b077d87bb501ed651bfc87c3e332c7d38d7 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Wed, 8 May 2013 14:50:26 -0600 Subject: [PATCH 049/101] Swap "go get" for "go get -d", especially to compile on go1.1rc; fixes #561 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index bae7d64909..9527d3f750 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ $(DOCKER_BIN): $(DOCKER_DIR) $(DOCKER_DIR): @mkdir -p $(dir $@) @if [ -h $@ ]; then rm -f $@; fi; ln -sf $(CURDIR)/ $@ - @(cd $(DOCKER_MAIN); go get $(GO_OPTIONS)) + @(cd $(DOCKER_MAIN); go get -d $(GO_OPTIONS)) whichrelease: echo $(RELEASE_VERSION) From 1d42cbaa21826e1594911742b8ead439da11882e Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 8 May 2013 23:19:24 +0200 Subject: [PATCH 050/101] removed useless returns --- api.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/api.go b/api.go index 6f6e5dfc74..cc82f2f37a 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 6cafed45afb74d07b13e8c00798e559da47822e7 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 8 May 2013 14:19:38 -0700 Subject: [PATCH 051/101] Better error output upon push failure --- registry.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/registry.go b/registry.go index cc8ee68153..07268313a3 100644 --- a/registry.go +++ b/registry.go @@ -427,9 +427,15 @@ func pushImageRec(graph *Graph, stdout io.Writer, img *Image, registry string, t if err != nil { return fmt.Errorf("Failed to upload layer: %s", err) } - res3.Body.Close() + defer res3.Body.Close() + if res3.StatusCode != 200 { - return fmt.Errorf("Received HTTP code %d while uploading layer", res3.StatusCode) + errBody, err := ioutil.ReadAll(res3.Body) + if err != nil { + return fmt.Errorf("HTTP code %d while uploading metadata and error when"+ + " trying to parse response body: %v", res.StatusCode, err) + } + return fmt.Errorf("Received HTTP code %d while uploading layer: %s", res3.StatusCode, errBody) } return nil } @@ -612,8 +618,7 @@ func (graph *Graph) PushRepository(stdout io.Writer, remote string, localRepo Re } func (graph *Graph) Checksums(output io.Writer, repo Repository) ([]map[string]string, error) { - var result []map[string]string - checksums := map[string]string{} + checksums := make(map[string]string) for _, id := range repo { img, err := graph.Get(id) if err != nil { @@ -634,7 +639,7 @@ func (graph *Graph) Checksums(output io.Writer, repo Repository) ([]map[string]s } } i := 0 - result = make([]map[string]string, len(checksums)) + result := make([]map[string]string, len(checksums)) for id, sum := range checksums { result[i] = map[string]string{ "id": id, From 24c785bc062c19488a47d9ed9ed2799a3c6783f8 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 8 May 2013 23:57:14 +0200 Subject: [PATCH 052/101] fix login --- api.go | 3 +-- commands.go | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api.go b/api.go index cc82f2f37a..771242fb2d 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 167834397a..a6aa1c46cc 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 d6c24092eb0e24f7aaa921110c62e99e81ae1f88 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 8 May 2013 15:26:44 -0700 Subject: [PATCH 053/101] + Runtime: Add go version to debug infos --- commands.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/commands.go b/commands.go index dba8cd8572..1f2b13e6f0 100644 --- a/commands.go +++ b/commands.go @@ -11,6 +11,7 @@ import ( "log" "net/http" "net/url" + "os" "path/filepath" "runtime" "strconv" @@ -284,9 +285,10 @@ func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string VERSION, imgcount) - if !rcli.DEBUG_FLAG { + if os.Getenv("DEBUG") == "" { return nil } + fmt.Fprintf(stdout, "Go version: %s\n", runtime.Version()) fmt.Fprintln(stdout, "debug mode enabled") fmt.Fprintf(stdout, "fds: %d\ngoroutines: %d\n", getTotalUsedFds(), runtime.NumGoroutine()) return nil From 0e23b4e10ec7f7c49c5715cd115f859319adf476 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 8 May 2013 16:22:12 -0700 Subject: [PATCH 054/101] Store the checksums when pulling a repository --- graph.go | 2 +- image.go | 4 ---- registry.go | 52 +++++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/graph.go b/graph.go index 4b504525e7..84b8c9a1e9 100644 --- a/graph.go +++ b/graph.go @@ -31,7 +31,7 @@ func NewGraph(root string) (*Graph, error) { return nil, err } // Create the root directory if it doesn't exists - if err := os.Mkdir(root, 0700); err != nil && !os.IsExist(err) { + if err := os.MkdirAll(root, 0700); err != nil && !os.IsExist(err) { return nil, err } graph := &Graph{ diff --git a/image.go b/image.go index 3e8ac55d3d..d208ed6ce8 100644 --- a/image.go +++ b/image.go @@ -303,16 +303,12 @@ func (img *Image) Checksum() (string, error) { return "", err } - fmt.Printf("precopy %s: %s\n", img.ShortId(), time.Now().String()) - if _, err := io.Copy(h, layerData); err != nil { return "", err } - fmt.Printf("postcopy presum %s: %s\n", img.ShortId(), time.Now().String()) hash := "sha256:" + hex.EncodeToString(h.Sum(nil)) checksums[img.Id] = hash - fmt.Printf("postsum %s: %s\n", img.ShortId(), time.Now().String()) // Reload the json file to make sure not to overwrite faster sums img.graph.lockSumFile.Lock() diff --git a/registry.go b/registry.go index 07268313a3..bf6556f6d5 100644 --- a/registry.go +++ b/registry.go @@ -194,18 +194,16 @@ func (graph *Graph) getRemoteTags(stdout io.Writer, registries []string, reposit return nil, fmt.Errorf("Repository not found") } - result := new(map[string]string) + result := make(map[string]string) rawJson, err := ioutil.ReadAll(res.Body) if err != nil { return nil, err } - if err = json.Unmarshal(rawJson, result); err != nil { + if err = json.Unmarshal(rawJson, &result); err != nil { return nil, err } - - return *result, nil - + return result, nil } return nil, fmt.Errorf("Could not reach any registry endpoint") } @@ -308,6 +306,50 @@ func (graph *Graph) PullRepository(stdout io.Writer, remote, askedTag string, re return fmt.Errorf("Index response didn't contain any endpoints") } + checksumsJson, err := ioutil.ReadAll(res.Body) + if err != nil { + return err + } + + // Reload the json file to make sure not to overwrite faster sums + err = func() error { + localChecksums := make(map[string]string) + remoteChecksums := []struct { + Id string `json: "id"` + Checksum string `json: "checksum"` + }{} + checksumDictPth := path.Join(graph.Root, "..", "checksums") + + if err := json.Unmarshal(checksumsJson, &remoteChecksums); err != nil { + return err + } + + graph.lockSumFile.Lock() + defer graph.lockSumFile.Unlock() + + if checksumDict, err := ioutil.ReadFile(checksumDictPth); err == nil { + if err := json.Unmarshal(checksumDict, &localChecksums); err != nil { + return err + } + } + + for _, elem := range remoteChecksums { + localChecksums[elem.Id] = elem.Checksum + } + + checksumsJson, err = json.Marshal(localChecksums) + if err != nil { + return err + } + if err := ioutil.WriteFile(checksumDictPth, checksumsJson, 0600); err != nil { + return err + } + return nil + }() + if err != nil { + return err + } + var tagsList map[string]string if askedTag == "" { tagsList, err = graph.getRemoteTags(stdout, endpoints, remote, token) From 5098c4fc006c15788142a669a0d169b03de7be8b Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 8 May 2013 16:40:48 -0700 Subject: [PATCH 055/101] Display the go version inf CmdInfo in non-debug mode --- commands.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/commands.go b/commands.go index 1f2b13e6f0..34de4f60bd 100644 --- a/commands.go +++ b/commands.go @@ -284,11 +284,12 @@ func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string len(srv.runtime.List()), VERSION, imgcount) + fmt.Fprintf(stdout, "Go version: %s\n", runtime.Version()) if os.Getenv("DEBUG") == "" { return nil } - fmt.Fprintf(stdout, "Go version: %s\n", runtime.Version()) + fmt.Fprintln(stdout, "debug mode enabled") fmt.Fprintf(stdout, "fds: %d\ngoroutines: %d\n", getTotalUsedFds(), runtime.NumGoroutine()) return nil From 06767fb99d997fe939febb086e8fa12414419053 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 8 May 2013 16:52:47 -0700 Subject: [PATCH 056/101] Bumped version to 0.3.1 --- CHANGELOG.md | 25 +++++++++++++++++++++++++ commands.go | 2 +- packaging/ubuntu/changelog | 27 +++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5036d87c15..d28e843468 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## 0.3.1 (2013-05-08) + + Builder: Implement the autorun capability within docker builder + + Builder: Add caching to docker builder + + Builder: Add support for docker builder with native API as top level command + + Runtime: Add go version to debug infos + + Builder: Implement ENV within docker builder + + Registry: Add docker search top level command in order to search a repository + + Images: output graph of images to dot (graphviz) + + Documentation: new introduction and high-level overview + + Documentation: Add the documentation for docker builder + + Website: new high-level overview + - Makefile: Swap "go get" for "go get -d", especially to compile on go1.1rc + - Images: fix ByParent function + - Builder: Check the command existance prior create and add Unit tests for the case + - Registry: Fix pull for official images with specific tag + - Registry: Fix issue when login in with a different user and trying to push + - Documentation: CSS fix for docker documentation to make REST API docs look better. + - Documentation: Fixed CouchDB example page header mistake + - Documentation: fixed README formatting + * Registry: Improve checksum - async calculation + * Runtime: kernel version - don't show the dash if flavor is empty + * Documentation: updated www.docker.io website. + * Builder: use any whitespaces instead of tabs + * Packaging: packaging ubuntu; issue #510: Use goland-stable PPA package to build docker + ## 0.3.0 (2013-05-06) + Registry: Implement the new registry + Documentation: new example: sharing data between 2 couchdb databases diff --git a/commands.go b/commands.go index 34de4f60bd..9fb47819b5 100644 --- a/commands.go +++ b/commands.go @@ -21,7 +21,7 @@ import ( "unicode" ) -const VERSION = "0.3.0" +const VERSION = "0.3.1" var ( GIT_COMMIT string diff --git a/packaging/ubuntu/changelog b/packaging/ubuntu/changelog index b3e68558ff..2a5575d8c3 100644 --- a/packaging/ubuntu/changelog +++ b/packaging/ubuntu/changelog @@ -1,3 +1,30 @@ +lxc-docker (0.3.1-1) precise; urgency=low + - Builder: Implement the autorun capability within docker builder + - Builder: Add caching to docker builder + - Builder: Add support for docker builder with native API as top level command + - Runtime: Add go version to debug infos + - Builder: Implement ENV within docker builder + - Registry: Add docker search top level command in order to search a repository + - Images: output graph of images to dot (graphviz) + - Documentation: new introduction and high-level overview + - Documentation: Add the documentation for docker builder + - Website: new high-level overview + - Makefile: Swap "go get" for "go get -d", especially to compile on go1.1rc + - Images: fix ByParent function + - Builder: Check the command existance prior create and add Unit tests for the case + - Registry: Fix pull for official images with specific tag + - Registry: Fix issue when login in with a different user and trying to push + - Documentation: CSS fix for docker documentation to make REST API docs look better. + - Documentation: Fixed CouchDB example page header mistake + - Documentation: fixed README formatting + - Registry: Improve checksum - async calculation + - Runtime: kernel version - don't show the dash if flavor is empty + - Documentation: updated www.docker.io website. + - Builder: use any whitespaces instead of tabs + - Packaging: packaging ubuntu; issue #510: Use goland-stable PPA package to build docker + + -- dotCloud Fri, 8 May 2013 00:00:00 -0700 + lxc-docker (0.3.0-1) precise; urgency=low - Registry: Implement the new registry - Documentation: new example: sharing data between 2 couchdb databases From 0f4469c2b1164eab9bc6104913cf8dccacac214f Mon Sep 17 00:00:00 2001 From: Thatcher Peskens Date: Wed, 8 May 2013 17:05:46 -0700 Subject: [PATCH 057/101] Moved the static website html pages to their own folder under docs. This should make previewing documentation easier. Also updated the Makefile to now copy the theme dir into the _build/website/ dir. Make connect and Make push work. --- docs/Makefile | 23 ++-- docs/sources/.nojekyll | 0 docs/sources/CNAME | 1 - docs/sources/commandline/command/build.rst | 4 +- docs/sources/concepts/containers.rst | 122 +----------------- docs/sources/concepts/introduction.rst | 2 +- docs/{sources => website}/dotcloud.yml | 0 .../gettingstarted/index.html | 18 +-- docs/{sources => website}/index.html | 20 +-- docs/{sources => website}/nginx.conf | 0 docs/website/static | 1 + 11 files changed, 36 insertions(+), 155 deletions(-) delete mode 100644 docs/sources/.nojekyll delete mode 100644 docs/sources/CNAME rename docs/{sources => website}/dotcloud.yml (100%) rename docs/{sources => website}/gettingstarted/index.html (92%) rename docs/{sources => website}/index.html (94%) rename docs/{sources => website}/nginx.conf (100%) create mode 120000 docs/website/static diff --git a/docs/Makefile b/docs/Makefile index 77f14ee92f..9298123f7f 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -46,23 +46,24 @@ clean: docs: -rm -rf $(BUILDDIR)/* $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/html - cp sources/index.html $(BUILDDIR)/html/ - cp -r sources/gettingstarted $(BUILDDIR)/html/ - cp sources/dotcloud.yml $(BUILDDIR)/html/ - cp sources/CNAME $(BUILDDIR)/html/ - cp sources/.nojekyll $(BUILDDIR)/html/ - cp sources/nginx.conf $(BUILDDIR)/html/ @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + @echo "Build finished. The documentation pages are now in $(BUILDDIR)/html." + + +site: + cp -r website $(BUILDDIR)/ + cp -r theme/docker/static/ $(BUILDDIR)/website/ + @echo + @echo "The Website pages are in $(BUILDDIR)/site." connect: - @echo pushing changes to staging site - @cd _build/html/ ; \ - @dotcloud list ; \ + @echo connecting dotcloud to www.docker.io website, make sure to use user 1 + @cd _build/website/ ; \ + dotcloud list ; \ dotcloud connect dockerwebsite push: - @cd _build/html/ ; \ + @cd _build/website/ ; \ dotcloud push github-deploy: docs diff --git a/docs/sources/.nojekyll b/docs/sources/.nojekyll deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs/sources/CNAME b/docs/sources/CNAME deleted file mode 100644 index 243e482261..0000000000 --- a/docs/sources/CNAME +++ /dev/null @@ -1 +0,0 @@ -docker.io diff --git a/docs/sources/commandline/command/build.rst b/docs/sources/commandline/command/build.rst index 6415f11f7b..8d07c725c2 100644 --- a/docs/sources/commandline/command/build.rst +++ b/docs/sources/commandline/command/build.rst @@ -1,6 +1,6 @@ -=========================================== +======================================================== ``build`` -- Build a container from Dockerfile via stdin -=========================================== +======================================================== :: diff --git a/docs/sources/concepts/containers.rst b/docs/sources/concepts/containers.rst index f432c4363d..8378a7e29f 100644 --- a/docs/sources/concepts/containers.rst +++ b/docs/sources/concepts/containers.rst @@ -5,124 +5,4 @@ :note: This version of the introduction is temporary, just to make sure we don't break the links from the website when the documentation is updated - -Introduction -============ - -Docker - The Linux container runtime ------------------------------------- - -Docker complements LXC with a high-level API which operates at the process level. It runs unix processes with strong guarantees of isolation and repeatability across servers. - -Docker is a great building block for automating distributed systems: large-scale web deployments, database clusters, continuous deployment systems, private PaaS, service-oriented architectures, etc. - - -- **Heterogeneous payloads** Any combination of binaries, libraries, configuration files, scripts, virtualenvs, jars, gems, tarballs, you name it. No more juggling between domain-specific tools. Docker can deploy and run them all. -- **Any server** Docker can run on any x64 machine with a modern linux kernel - whether it's a laptop, a bare metal server or a VM. This makes it perfect for multi-cloud deployments. -- **Isolation** docker isolates processes from each other and from the underlying host, using lightweight containers. -- **Repeatability** Because containers are isolated in their own filesystem, they behave the same regardless of where, when, and alongside what they run. - - - -What is a Standard Container? ------------------------------ - -Docker defines a unit of software delivery called a Standard Container. The goal of a Standard Container is to encapsulate a software component and all its dependencies in -a format that is self-describing and portable, so that any compliant runtime can run it without extra dependency, regardless of the underlying machine and the contents of the container. - -The spec for Standard Containers is currently work in progress, but it is very straightforward. It mostly defines 1) an image format, 2) a set of standard operations, and 3) an execution environment. - -A great analogy for this is the shipping container. Just like Standard Containers are a fundamental unit of software delivery, shipping containers (http://bricks.argz.com/ins/7823-1/12) are a fundamental unit of physical delivery. - -Standard operations -~~~~~~~~~~~~~~~~~~~ - -Just like shipping containers, Standard Containers define a set of STANDARD OPERATIONS. Shipping containers can be lifted, stacked, locked, loaded, unloaded and labelled. Similarly, standard containers can be started, stopped, copied, snapshotted, downloaded, uploaded and tagged. - - -Content-agnostic -~~~~~~~~~~~~~~~~~~~ - -Just like shipping containers, Standard Containers are CONTENT-AGNOSTIC: all standard operations have the same effect regardless of the contents. A shipping container will be stacked in exactly the same way whether it contains Vietnamese powder coffee or spare Maserati parts. Similarly, Standard Containers are started or uploaded in the same way whether they contain a postgres database, a php application with its dependencies and application server, or Java build artifacts. - - -Infrastructure-agnostic -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Both types of containers are INFRASTRUCTURE-AGNOSTIC: they can be transported to thousands of facilities around the world, and manipulated by a wide variety of equipment. A shipping container can be packed in a factory in Ukraine, transported by truck to the nearest routing center, stacked onto a train, loaded into a German boat by an Australian-built crane, stored in a warehouse at a US facility, etc. Similarly, a standard container can be bundled on my laptop, uploaded to S3, downloaded, run and snapshotted by a build server at Equinix in Virginia, uploaded to 10 staging servers in a home-made Openstack cluster, then sent to 30 production instances across 3 EC2 regions. - - -Designed for automation -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Because they offer the same standard operations regardless of content and infrastructure, Standard Containers, just like their physical counterpart, are extremely well-suited for automation. In fact, you could say automation is their secret weapon. - -Many things that once required time-consuming and error-prone human effort can now be programmed. Before shipping containers, a bag of powder coffee was hauled, dragged, dropped, rolled and stacked by 10 different people in 10 different locations by the time it reached its destination. 1 out of 50 disappeared. 1 out of 20 was damaged. The process was slow, inefficient and cost a fortune - and was entirely different depending on the facility and the type of goods. - -Similarly, before Standard Containers, by the time a software component ran in production, it had been individually built, configured, bundled, documented, patched, vendored, templated, tweaked and instrumented by 10 different people on 10 different computers. Builds failed, libraries conflicted, mirrors crashed, post-it notes were lost, logs were misplaced, cluster updates were half-broken. The process was slow, inefficient and cost a fortune - and was entirely different depending on the language and infrastructure provider. - - -Industrial-grade delivery -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -There are 17 million shipping containers in existence, packed with every physical good imaginable. Every single one of them can be loaded on the same boats, by the same cranes, in the same facilities, and sent anywhere in the World with incredible efficiency. It is embarrassing to think that a 30 ton shipment of coffee can safely travel half-way across the World in *less time* than it takes a software team to deliver its code from one datacenter to another sitting 10 miles away. - -With Standard Containers we can put an end to that embarrassment, by making INDUSTRIAL-GRADE DELIVERY of software a reality. - - -Standard Container Specification -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -(TODO) - -Image format -~~~~~~~~~~~~ - -Standard operations -~~~~~~~~~~~~~~~~~~~ - -- Copy -- Run -- Stop -- Wait -- Commit -- Attach standard streams -- List filesystem changes -- ... - -Execution environment -~~~~~~~~~~~~~~~~~~~~~ - -Root filesystem -^^^^^^^^^^^^^^^ - -Environment variables -^^^^^^^^^^^^^^^^^^^^^ - -Process arguments -^^^^^^^^^^^^^^^^^ - -Networking -^^^^^^^^^^ - -Process namespacing -^^^^^^^^^^^^^^^^^^^ - -Resource limits -^^^^^^^^^^^^^^^ - -Process monitoring -^^^^^^^^^^^^^^^^^^ - -Logging -^^^^^^^ - -Signals -^^^^^^^ - -Pseudo-terminal allocation -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Security -^^^^^^^^ - +This document has been moved to :ref:`introduction`, please update your bookmarks. \ No newline at end of file diff --git a/docs/sources/concepts/introduction.rst b/docs/sources/concepts/introduction.rst index 46698b4015..b7e1b04f05 100644 --- a/docs/sources/concepts/introduction.rst +++ b/docs/sources/concepts/introduction.rst @@ -2,7 +2,7 @@ :description: An introduction to docker and standard containers? :keywords: containers, lxc, concepts, explanation - +.. _introduction: Introduction ============ diff --git a/docs/sources/dotcloud.yml b/docs/website/dotcloud.yml similarity index 100% rename from docs/sources/dotcloud.yml rename to docs/website/dotcloud.yml diff --git a/docs/sources/gettingstarted/index.html b/docs/website/gettingstarted/index.html similarity index 92% rename from docs/sources/gettingstarted/index.html rename to docs/website/gettingstarted/index.html index 96175d6dec..c005cfc9f9 100644 --- a/docs/sources/gettingstarted/index.html +++ b/docs/website/gettingstarted/index.html @@ -13,15 +13,15 @@ - - + + - + - - + + @@ -35,8 +35,8 @@ @@ -186,7 +186,7 @@ - + - + +