From 0724fa65be92405b903f2e72d5e15344459e1f95 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 11 Feb 2014 02:44:17 +0000 Subject: [PATCH 1/9] Rewrite docker rmi Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) Upstream-commit: 795ed6b1e511fa6713fbc9ea4e8569aab15b98ff Component: engine --- components/engine/api/client.go | 8 +- components/engine/api/server.go | 2 +- components/engine/integration/api_test.go | 2 + .../engine/integration/commands_test.go | 5 +- components/engine/integration/server_test.go | 13 +- components/engine/server.go | 138 +++++------------- 6 files changed, 58 insertions(+), 110 deletions(-) diff --git a/components/engine/api/client.go b/components/engine/api/client.go index eb345ae40b..7acf63f9ca 100644 --- a/components/engine/api/client.go +++ b/components/engine/api/client.go @@ -781,6 +781,7 @@ func (cli *DockerCli) CmdPort(args ...string) error { // 'docker rmi IMAGE' removes all images with the name IMAGE func (cli *DockerCli) CmdRmi(args ...string) error { cmd := cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images") + force := cmd.Bool([]string{"f", "-force"}, false, "Force") if err := cmd.Parse(args); err != nil { return nil } @@ -789,9 +790,14 @@ func (cli *DockerCli) CmdRmi(args ...string) error { return nil } + v := url.Values{} + if *force { + v.Set("force", "1") + } + var encounteredError error for _, name := range cmd.Args() { - body, _, err := readBody(cli.call("DELETE", "/images/"+name, nil, false)) + body, _, err := readBody(cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, false)) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to remove one or more images") diff --git a/components/engine/api/server.go b/components/engine/api/server.go index 0dd9d966e6..03f451fe8b 100644 --- a/components/engine/api/server.go +++ b/components/engine/api/server.go @@ -631,7 +631,7 @@ func deleteImages(eng *engine.Engine, version float64, w http.ResponseWriter, r } var job = eng.Job("image_delete", vars["name"]) streamJSON(job, w, false) - job.SetenvBool("autoPrune", version > 1.1) + job.Setenv("force", r.Form.Get("force")) return job.Run() } diff --git a/components/engine/integration/api_test.go b/components/engine/integration/api_test.go index 5779e6b226..abbc1a1c59 100644 --- a/components/engine/integration/api_test.go +++ b/components/engine/integration/api_test.go @@ -1175,6 +1175,8 @@ func TestGetEnabledCors(t *testing.T) { func TestDeleteImages(t *testing.T) { eng := NewTestEngine(t) + //we expect errors, so we disable stderr + eng.Stderr = ioutil.Discard defer mkRuntimeFromEngine(eng, t).Nuke() initialImages := getImages(eng, t, true, "") diff --git a/components/engine/integration/commands_test.go b/components/engine/integration/commands_test.go index a3359ec631..0cf7fda660 100644 --- a/components/engine/integration/commands_test.go +++ b/components/engine/integration/commands_test.go @@ -1031,7 +1031,10 @@ func TestContainerOrphaning(t *testing.T) { buildSomething(template2, imageName) // remove the second image by name - resp, err := srv.DeleteImage(imageName, true) + resp := engine.NewTable("", 0) + if err := srv.DeleteImage(imageName, resp, true, false); err == nil { + t.Fatal("Expected error, got none") + } // see if we deleted the first image (and orphaned the container) for _, i := range resp.Data { diff --git a/components/engine/integration/server_test.go b/components/engine/integration/server_test.go index 2234bcab08..8600e78573 100644 --- a/components/engine/integration/server_test.go +++ b/components/engine/integration/server_test.go @@ -35,7 +35,7 @@ func TestImageTagImageDelete(t *testing.T) { t.Errorf("Expected %d images, %d found", nExpected, nActual) } - if _, err := srv.DeleteImage("utest/docker:tag2", true); err != nil { + if err := srv.DeleteImage("utest/docker:tag2", engine.NewTable("", 0), true, false); err != nil { t.Fatal(err) } @@ -47,7 +47,7 @@ func TestImageTagImageDelete(t *testing.T) { t.Errorf("Expected %d images, %d found", nExpected, nActual) } - if _, err := srv.DeleteImage("utest:5000/docker:tag3", true); err != nil { + if err := srv.DeleteImage("utest:5000/docker:tag3", engine.NewTable("", 0), true, false); err != nil { t.Fatal(err) } @@ -56,7 +56,7 @@ func TestImageTagImageDelete(t *testing.T) { nExpected = len(initialImages.Data[0].GetList("RepoTags")) + 1 nActual = len(images.Data[0].GetList("RepoTags")) - if _, err := srv.DeleteImage("utest:tag1", true); err != nil { + if err := srv.DeleteImage("utest:tag1", engine.NewTable("", 0), true, false); err != nil { t.Fatal(err) } @@ -447,8 +447,7 @@ func TestRmi(t *testing.T) { t.Fatalf("Expected 2 new images, found %d.", images.Len()-initialImages.Len()) } - _, err = srv.DeleteImage(imageID, true) - if err != nil { + if err = srv.DeleteImage(imageID, engine.NewTable("", 0), true, false); err != nil { t.Fatal(err) } @@ -683,8 +682,8 @@ func TestDeleteTagWithExistingContainers(t *testing.T) { } // Try to remove the tag - imgs, err := srv.DeleteImage("utest:tag1", true) - if err != nil { + imgs := engine.NewTable("", 0) + if err := srv.DeleteImage("utest:tag1", imgs, true, false); err != nil { t.Fatal(err) } diff --git a/components/engine/server.go b/components/engine/server.go index 190ccbcc4b..91266c23b9 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -2,7 +2,6 @@ package docker import ( "encoding/json" - "errors" "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/auth" @@ -1810,102 +1809,27 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status { return engine.StatusOK } -var ErrImageReferenced = errors.New("Image referenced by a repository") - -func (srv *Server) deleteImageAndChildren(id string, imgs *engine.Table, byParents map[string][]*Image) error { - // If the image is referenced by a repo, do not delete - if len(srv.runtime.repositories.ByID()[id]) != 0 { - return ErrImageReferenced - } - // If the image is not referenced but has children, go recursive - referenced := false - for _, img := range byParents[id] { - if err := srv.deleteImageAndChildren(img.ID, imgs, byParents); err != nil { - if err != ErrImageReferenced { - return err - } - referenced = true - } - } - if referenced { - return ErrImageReferenced - } - - // If the image is not referenced and has no children, remove it - byParents, err := srv.runtime.graph.ByParent() - if err != nil { - return err - } - if len(byParents[id]) == 0 && srv.canDeleteImage(id) == nil { - if err := srv.runtime.repositories.DeleteAll(id); err != nil { - return err - } - err := srv.runtime.graph.Delete(id) - if err != nil { - return err - } - out := &engine.Env{} - out.Set("Deleted", id) - imgs.Add(out) - srv.LogEvent("delete", id, "") - return nil - } - return nil -} - -func (srv *Server) deleteImageParents(img *Image, imgs *engine.Table) error { - if img.Parent != "" { - parent, err := srv.runtime.graph.Get(img.Parent) - if err != nil { - return err - } - byParents, err := srv.runtime.graph.ByParent() - if err != nil { - return err - } - // Remove all children images - if err := srv.deleteImageAndChildren(img.Parent, imgs, byParents); err != nil { - return err - } - return srv.deleteImageParents(parent, imgs) - } - return nil -} - -func (srv *Server) DeleteImage(name string, autoPrune bool) (*engine.Table, error) { +func (srv *Server) DeleteImage(name string, imgs *engine.Table, first, force bool) error { var ( repoName, tag string img, err = srv.runtime.repositories.LookupImage(name) - imgs = engine.NewTable("", 0) tags = []string{} ) if err != nil { - return nil, fmt.Errorf("No such image: %s", name) - } - - // FIXME: What does autoPrune mean ? - if !autoPrune { - if err := srv.runtime.graph.Delete(img.ID); err != nil { - return nil, fmt.Errorf("Cannot delete image %s: %s", name, err) - } - return nil, nil + return fmt.Errorf("No such image: %s", name) } if !strings.Contains(img.ID, name) { repoName, tag = utils.ParseRepositoryTag(name) + if tag == "" { + tag = DEFAULTTAG + } } - // If we have a repo and the image is not referenced anywhere else - // then just perform an untag and do not validate. - // - // i.e. only validate if we are performing an actual delete and not - // an untag op - if repoName != "" && len(srv.runtime.repositories.ByID()[img.ID]) == 1 { - // Prevent deletion if image is used by a container - if err := srv.canDeleteImage(img.ID); err != nil { - return nil, err - } + byParents, err := srv.runtime.graph.ByParent() + if err != nil { + return err } //If delete by id, see if the id belong only to one repository @@ -1917,10 +1841,10 @@ func (srv *Server) DeleteImage(name string, autoPrune bool) (*engine.Table, erro if parsedTag != "" { tags = append(tags, parsedTag) } - } else if repoName != parsedRepo { + } else if repoName != parsedRepo && !force { // the id belongs to multiple repos, like base:latest and user:test, // in that case return conflict - return nil, fmt.Errorf("Conflict, cannot delete image %s because it is tagged in multiple repositories", utils.TruncateID(img.ID)) + return fmt.Errorf("Conflict, cannot delete image %s because it is tagged in multiple repositories, use -f to force", name) } } } else { @@ -1931,37 +1855,51 @@ func (srv *Server) DeleteImage(name string, autoPrune bool) (*engine.Table, erro for _, tag := range tags { tagDeleted, err := srv.runtime.repositories.Delete(repoName, tag) if err != nil { - return nil, err + return err } if tagDeleted { out := &engine.Env{} - out.Set("Untagged", img.ID) + out.Set("Untagged", repoName+":"+tag) imgs.Add(out) srv.LogEvent("untag", img.ID, "") } } + tags = srv.runtime.repositories.ByID()[img.ID] + if (len(tags) <= 1 && repoName == "") || len(tags) == 0 { + if len(byParents[img.ID]) == 0 { + if err := srv.canDeleteImage(img.ID); err != nil { + return err + } + if err := srv.runtime.repositories.DeleteAll(img.ID); err != nil { + return err + } + err := srv.runtime.graph.Delete(img.ID) + if err != nil { + return err + } + out := &engine.Env{} + out.Set("Deleted", img.ID) + imgs.Add(out) + srv.LogEvent("delete", img.ID, "") + if img.Parent != "" { + err := srv.DeleteImage(img.Parent, imgs, false, force) + if first { + return err + } - if len(srv.runtime.repositories.ByID()[img.ID]) == 0 { - if err := srv.deleteImageAndChildren(img.ID, imgs, nil); err != nil { - if err != ErrImageReferenced { - return imgs, err - } - } else if err := srv.deleteImageParents(img, imgs); err != nil { - if err != ErrImageReferenced { - return imgs, err } + } } - return imgs, nil + return nil } func (srv *Server) ImageDelete(job *engine.Job) engine.Status { if n := len(job.Args); n != 1 { return job.Errorf("Usage: %s IMAGE", job.Name) } - - imgs, err := srv.DeleteImage(job.Args[0], job.GetenvBool("autoPrune")) - if err != nil { + var imgs = engine.NewTable("", 0) + if err := srv.DeleteImage(job.Args[0], imgs, true, job.GetenvBool("force")); err != nil { return job.Error(err) } if len(imgs.Data) == 0 { From 0820020142dd0e9b25ef9ba1ddbdd6234ec08a9c Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 14 Feb 2014 22:53:53 +0000 Subject: [PATCH 2/9] bump API version Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) Upstream-commit: e7cc88c39f302c020f70a00bca54bddeecb608a4 Component: engine --- components/engine/api/client.go | 6 +- components/engine/api/common.go | 2 +- components/engine/api/server.go | 128 +- .../reference/api/docker_remote_api.rst | 20 +- .../reference/api/docker_remote_api_v1.10.rst | 1282 +++++++++++++++++ components/engine/utils/utils.go | 22 + components/engine/utils/utils_test.go | 20 + 7 files changed, 1405 insertions(+), 75 deletions(-) create mode 100644 components/engine/docs/sources/reference/api/docker_remote_api_v1.10.rst diff --git a/components/engine/api/client.go b/components/engine/api/client.go index 7acf63f9ca..da2e7730f1 100644 --- a/components/engine/api/client.go +++ b/components/engine/api/client.go @@ -2038,7 +2038,7 @@ func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo b re := regexp.MustCompile("/+") path = re.ReplaceAllString(path, "/") - req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), params) + req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", APIVERSION, path), params) if err != nil { return nil, -1, err } @@ -2115,7 +2115,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h re := regexp.MustCompile("/+") path = re.ReplaceAllString(path, "/") - req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), in) + req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", APIVERSION, path), in) if err != nil { return err } @@ -2179,7 +2179,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea re := regexp.MustCompile("/+") path = re.ReplaceAllString(path, "/") - req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil) + req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", APIVERSION, path), nil) if err != nil { return err } diff --git a/components/engine/api/common.go b/components/engine/api/common.go index c79b16fda2..10e7ddb4ae 100644 --- a/components/engine/api/common.go +++ b/components/engine/api/common.go @@ -9,7 +9,7 @@ import ( ) const ( - APIVERSION = 1.9 + APIVERSION = "1.10" DEFAULTHTTPHOST = "127.0.0.1" DEFAULTUNIXSOCKET = "/var/run/docker.sock" ) diff --git a/components/engine/api/server.go b/components/engine/api/server.go index 03f451fe8b..007c07ddb8 100644 --- a/components/engine/api/server.go +++ b/components/engine/api/server.go @@ -32,7 +32,7 @@ var ( activationLock chan struct{} ) -type HttpApiFunc func(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error +type HttpApiFunc func(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { conn, _, err := w.(http.Hijacker).Hijack() @@ -113,7 +113,7 @@ func getBoolParam(value string) (bool, error) { return ret, nil } -func postAuth(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postAuth(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { var ( authConfig, err = ioutil.ReadAll(r.Body) job = eng.Job("auth") @@ -136,13 +136,13 @@ func postAuth(eng *engine.Engine, version float64, w http.ResponseWriter, r *htt return nil } -func getVersion(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getVersion(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.Header().Set("Content-Type", "application/json") eng.ServeHTTP(w, r) return nil } -func postContainersKill(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersKill(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -160,7 +160,7 @@ func postContainersKill(eng *engine.Engine, version float64, w http.ResponseWrit return nil } -func getContainersExport(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getContainersExport(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -172,7 +172,7 @@ func getContainersExport(eng *engine.Engine, version float64, w http.ResponseWri return nil } -func getImagesJSON(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getImagesJSON(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -186,7 +186,7 @@ func getImagesJSON(eng *engine.Engine, version float64, w http.ResponseWriter, r job.Setenv("filter", r.Form.Get("filter")) job.Setenv("all", r.Form.Get("all")) - if version >= 1.7 { + if utils.CompareVersion(version, "1.7") >= 0 { streamJSON(job, w, false) } else if outs, err = job.Stdout.AddListTable(); err != nil { return err @@ -196,7 +196,7 @@ func getImagesJSON(eng *engine.Engine, version float64, w http.ResponseWriter, r return err } - if version < 1.7 && outs != nil { // Convert to legacy format + if utils.CompareVersion(version, "1.7") < 0 && outs != nil { // Convert to legacy format outsLegacy := engine.NewTable("Created", 0) for _, out := range outs.Data { for _, repoTag := range out.GetList("RepoTags") { @@ -219,8 +219,8 @@ func getImagesJSON(eng *engine.Engine, version float64, w http.ResponseWriter, r return nil } -func getImagesViz(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if version > 1.6 { +func getImagesViz(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if utils.CompareVersion(version, "1.6") > 0 { w.WriteHeader(http.StatusNotFound) return fmt.Errorf("This is now implemented in the client.") } @@ -228,13 +228,13 @@ func getImagesViz(eng *engine.Engine, version float64, w http.ResponseWriter, r return nil } -func getInfo(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getInfo(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.Header().Set("Content-Type", "application/json") eng.ServeHTTP(w, r) return nil } -func getEvents(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getEvents(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -245,7 +245,7 @@ func getEvents(eng *engine.Engine, version float64, w http.ResponseWriter, r *ht return job.Run() } -func getImagesHistory(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getImagesHistory(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -259,7 +259,7 @@ func getImagesHistory(eng *engine.Engine, version float64, w http.ResponseWriter return nil } -func getContainersChanges(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getContainersChanges(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -269,8 +269,8 @@ func getContainersChanges(eng *engine.Engine, version float64, w http.ResponseWr return job.Run() } -func getContainersTop(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if version < 1.4 { +func getContainersTop(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if utils.CompareVersion(version, "1.4") < 0 { return fmt.Errorf("top was improved a lot since 1.3, Please upgrade your docker client.") } if vars == nil { @@ -285,7 +285,7 @@ func getContainersTop(eng *engine.Engine, version float64, w http.ResponseWriter return job.Run() } -func getContainersJSON(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getContainersJSON(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -301,7 +301,7 @@ func getContainersJSON(eng *engine.Engine, version float64, w http.ResponseWrite job.Setenv("before", r.Form.Get("before")) job.Setenv("limit", r.Form.Get("limit")) - if version >= 1.5 { + if utils.CompareVersion(version, "1.5") >= 0 { streamJSON(job, w, false) } else if outs, err = job.Stdout.AddTable(); err != nil { return err @@ -309,7 +309,7 @@ func getContainersJSON(eng *engine.Engine, version float64, w http.ResponseWrite if err = job.Run(); err != nil { return err } - if version < 1.5 { // Convert to legacy format + if utils.CompareVersion(version, "1.5") < 0 { // Convert to legacy format for _, out := range outs.Data { ports := engine.NewTable("", 0) ports.ReadListFrom([]byte(out.Get("Ports"))) @@ -323,7 +323,7 @@ func getContainersJSON(eng *engine.Engine, version float64, w http.ResponseWrite return nil } -func postImagesTag(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postImagesTag(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -340,7 +340,7 @@ func postImagesTag(eng *engine.Engine, version float64, w http.ResponseWriter, r return nil } -func postCommit(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postCommit(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -369,7 +369,7 @@ func postCommit(eng *engine.Engine, version float64, w http.ResponseWriter, r *h } // Creates an image from Pull or from Import -func postImagesCreate(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postImagesCreate(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -389,9 +389,6 @@ func postImagesCreate(eng *engine.Engine, version float64, w http.ResponseWriter authConfig = &auth.AuthConfig{} } } - if version > 1.0 { - w.Header().Set("Content-Type", "application/json") - } if image != "" { //pull metaHeaders := map[string][]string{} for k, v := range r.Header { @@ -400,7 +397,7 @@ func postImagesCreate(eng *engine.Engine, version float64, w http.ResponseWriter } } job = eng.Job("pull", r.Form.Get("fromImage"), tag) - job.SetenvBool("parallel", version > 1.3) + job.SetenvBool("parallel", utils.CompareVersion(version, "1.3") > 0) job.SetenvJson("metaHeaders", metaHeaders) job.SetenvJson("authConfig", authConfig) } else { //import @@ -408,7 +405,7 @@ func postImagesCreate(eng *engine.Engine, version float64, w http.ResponseWriter job.Stdin.Add(r.Body) } - if version > 1.0 { + if utils.CompareVersion(version, "1.0") > 0 { job.SetenvBool("json", true) streamJSON(job, w, true) } else { @@ -418,14 +415,14 @@ func postImagesCreate(eng *engine.Engine, version float64, w http.ResponseWriter if !job.Stdout.Used() { return err } - sf := utils.NewStreamFormatter(version > 1.0) + sf := utils.NewStreamFormatter(utils.CompareVersion(version, "1.0") > 0) w.Write(sf.FormatError(err)) } return nil } -func getImagesSearch(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getImagesSearch(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -457,19 +454,15 @@ func getImagesSearch(eng *engine.Engine, version float64, w http.ResponseWriter, return job.Run() } -func postImagesInsert(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postImagesInsert(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } if vars == nil { return fmt.Errorf("Missing parameter") } - if version > 1.0 { - w.Header().Set("Content-Type", "application/json") - } - job := eng.Job("insert", vars["name"], r.Form.Get("url"), r.Form.Get("path")) - if version > 1.0 { + if utils.CompareVersion(version, "1.0") > 0 { job.SetenvBool("json", true) streamJSON(job, w, false) } else { @@ -479,14 +472,14 @@ func postImagesInsert(eng *engine.Engine, version float64, w http.ResponseWriter if !job.Stdout.Used() { return err } - sf := utils.NewStreamFormatter(version > 1.0) + sf := utils.NewStreamFormatter(utils.CompareVersion(version, "1.0") > 0) w.Write(sf.FormatError(err)) } return nil } -func postImagesPush(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postImagesPush(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -517,13 +510,10 @@ func postImagesPush(eng *engine.Engine, version float64, w http.ResponseWriter, } } - if version > 1.0 { - w.Header().Set("Content-Type", "application/json") - } job := eng.Job("push", vars["name"]) job.SetenvJson("metaHeaders", metaHeaders) job.SetenvJson("authConfig", authConfig) - if version > 1.0 { + if utils.CompareVersion(version, "1.0") > 0 { job.SetenvBool("json", true) streamJSON(job, w, true) } else { @@ -534,17 +524,17 @@ func postImagesPush(eng *engine.Engine, version float64, w http.ResponseWriter, if !job.Stdout.Used() { return err } - sf := utils.NewStreamFormatter(version > 1.0) + sf := utils.NewStreamFormatter(utils.CompareVersion(version, "1.0") > 0) w.Write(sf.FormatError(err)) } return nil } -func getImagesGet(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getImagesGet(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } - if version > 1.0 { + if utils.CompareVersion(version, "1.0") > 0 { w.Header().Set("Content-Type", "application/x-tar") } job := eng.Job("image_export", vars["name"]) @@ -552,13 +542,13 @@ func getImagesGet(eng *engine.Engine, version float64, w http.ResponseWriter, r return job.Run() } -func postImagesLoad(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postImagesLoad(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { job := eng.Job("load") job.Stdin.Add(r.Body) return job.Run() } -func postContainersCreate(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersCreate(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return nil } @@ -589,7 +579,7 @@ func postContainersCreate(eng *engine.Engine, version float64, w http.ResponseWr return writeJSON(w, http.StatusCreated, out) } -func postContainersRestart(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersRestart(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -605,7 +595,7 @@ func postContainersRestart(eng *engine.Engine, version float64, w http.ResponseW return nil } -func deleteContainers(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func deleteContainers(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -622,7 +612,7 @@ func deleteContainers(eng *engine.Engine, version float64, w http.ResponseWriter return nil } -func deleteImages(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func deleteImages(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -636,7 +626,7 @@ func deleteImages(eng *engine.Engine, version float64, w http.ResponseWriter, r return job.Run() } -func postContainersStart(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersStart(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -657,7 +647,7 @@ func postContainersStart(eng *engine.Engine, version float64, w http.ResponseWri return nil } -func postContainersStop(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersStop(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -673,7 +663,7 @@ func postContainersStop(eng *engine.Engine, version float64, w http.ResponseWrit return nil } -func postContainersWait(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersWait(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -695,7 +685,7 @@ func postContainersWait(eng *engine.Engine, version float64, w http.ResponseWrit return writeJSON(w, http.StatusOK, env) } -func postContainersResize(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersResize(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -708,7 +698,7 @@ func postContainersResize(eng *engine.Engine, version float64, w http.ResponseWr return nil } -func postContainersAttach(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersAttach(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -750,7 +740,7 @@ func postContainersAttach(eng *engine.Engine, version float64, w http.ResponseWr fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") - if c.GetSubEnv("Config") != nil && !c.GetSubEnv("Config").GetBool("Tty") && version >= 1.6 { + if c.GetSubEnv("Config") != nil && !c.GetSubEnv("Config").GetBool("Tty") && utils.CompareVersion(version, "1.6") >= 0 { errStream = utils.NewStdWriter(outStream, utils.Stderr) outStream = utils.NewStdWriter(outStream, utils.Stdout) } else { @@ -773,7 +763,7 @@ func postContainersAttach(eng *engine.Engine, version float64, w http.ResponseWr return nil } -func wsContainersAttach(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func wsContainersAttach(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -805,7 +795,7 @@ func wsContainersAttach(eng *engine.Engine, version float64, w http.ResponseWrit return nil } -func getContainersByName(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getContainersByName(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -815,7 +805,7 @@ func getContainersByName(eng *engine.Engine, version float64, w http.ResponseWri return job.Run() } -func getImagesByName(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getImagesByName(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -825,8 +815,8 @@ func getImagesByName(eng *engine.Engine, version float64, w http.ResponseWriter, return job.Run() } -func postBuild(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if version < 1.3 { +func postBuild(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if utils.CompareVersion(version, "1.3") < 0 { return fmt.Errorf("Multipart upload for build is no longer supported. Please upgrade your docker client.") } var ( @@ -841,7 +831,7 @@ func postBuild(eng *engine.Engine, version float64, w http.ResponseWriter, r *ht // Both headers will be parsed and sent along to the daemon, but if a non-empty // ConfigFile is present, any value provided as an AuthConfig directly will // be overridden. See BuildFile::CmdFrom for details. - if version < 1.9 && authEncoded != "" { + if utils.CompareVersion(version, "1.9") < 0 && authEncoded != "" { authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { // for a pull it is not an error if no auth was given @@ -859,7 +849,7 @@ func postBuild(eng *engine.Engine, version float64, w http.ResponseWriter, r *ht } } - if version >= 1.8 { + if utils.CompareVersion(version, "1.8") >= 0 { job.SetenvBool("json", true) streamJSON(job, w, true) } else { @@ -878,13 +868,13 @@ func postBuild(eng *engine.Engine, version float64, w http.ResponseWriter, r *ht if !job.Stdout.Used() { return err } - sf := utils.NewStreamFormatter(version >= 1.8) + sf := utils.NewStreamFormatter(utils.CompareVersion(version, "1.8") >= 0) w.Write(sf.FormatError(err)) } return nil } -func postContainersCopy(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersCopy(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -917,7 +907,7 @@ func postContainersCopy(eng *engine.Engine, version float64, w http.ResponseWrit return nil } -func optionsHandler(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func optionsHandler(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.WriteHeader(http.StatusOK) return nil } @@ -942,16 +932,16 @@ func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, local utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], dockerVersion) } } - version, err := strconv.ParseFloat(mux.Vars(r)["version"], 64) - if err != nil { + version := mux.Vars(r)["version"] + if version == "" { version = APIVERSION } if enableCors { writeCorsHeaders(w, r) } - if version == 0 || version > APIVERSION { - http.Error(w, fmt.Errorf("client and server don't have same version (client : %g, server: %g)", version, APIVERSION).Error(), http.StatusNotFound) + if utils.CompareVersion(version, APIVERSION) == 1 { + http.Error(w, fmt.Errorf("client and server don't have same version (client : %s, server: %s)", version, APIVERSION).Error(), http.StatusNotFound) return } diff --git a/components/engine/docs/sources/reference/api/docker_remote_api.rst b/components/engine/docs/sources/reference/api/docker_remote_api.rst index 69bbf71ec9..51c4a06c91 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api.rst +++ b/components/engine/docs/sources/reference/api/docker_remote_api.rst @@ -26,15 +26,31 @@ Docker Remote API 2. Versions =========== -The current version of the API is 1.9 +The current version of the API is 1.10 Calling /images//insert is the same as calling -/v1.9/images//insert +/v1.10/images//insert You can still call an old version of the api using /v1.0/images//insert +v1.10 +**** + +Full Documentation +------------------ + +:doc:`docker_remote_api_v1.10` + +What's new +---------- + +.. http:delete:: /images/(name) + + **New!** You can now use the force parameter to force delete of an image, even if it's + tagged in multiple repositories. + v1.9 **** diff --git a/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.rst b/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.rst new file mode 100644 index 0000000000..6ee3febc27 --- /dev/null +++ b/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.rst @@ -0,0 +1,1282 @@ +:title: Remote API v1.9 +:description: API Documentation for Docker +:keywords: API, Docker, rcli, REST, documentation + +:orphan: + +====================== +Docker Remote API v1.9 +====================== + +.. contents:: Table of Contents + +1. Brief introduction +===================== + +- The Remote API has replaced rcli +- The daemon listens on ``unix:///var/run/docker.sock``, but you can + :ref:`bind_docker`. +- The API tends to be REST, but for some complex commands, like + ``attach`` or ``pull``, the HTTP connection is hijacked to transport + ``stdout, stdin`` and ``stderr`` + +2. Endpoints +============ + +2.1 Containers +-------------- + +List containers +*************** + +.. http:get:: /containers/json + + List containers + + **Example request**: + + .. sourcecode:: http + + GET /containers/json?all=1&before=8dfafdbc3a40&size=1 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Id": "8dfafdbc3a40", + "Image": "base:latest", + "Command": "echo 1", + "Created": 1367854155, + "Status": "Exit 0", + "Ports":[{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}], + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "9cd87474be90", + "Image": "base:latest", + "Command": "echo 222222", + "Created": 1367854155, + "Status": "Exit 0", + "Ports":[], + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "3176a2479c92", + "Image": "base:latest", + "Command": "echo 3333333333333333", + "Created": 1367854154, + "Status": "Exit 0", + "Ports":[], + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "4cb07b47f9fb", + "Image": "base:latest", + "Command": "echo 444444444444444444444444444444444", + "Created": 1367854152, + "Status": "Exit 0", + "Ports":[], + "SizeRw":12288, + "SizeRootFs":0 + } + ] + + :query all: 1/True/true or 0/False/false, Show all containers. Only running containers are shown 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. + :query size: 1/True/true or 0/False/false, Show the containers sizes + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 500: server error + + +Create a container +****************** + +.. http:post:: /containers/create + + Create a container + + **Example request**: + + .. sourcecode:: http + + POST /containers/create HTTP/1.1 + Content-Type: application/json + + { + "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":{ + "/tmp": {} + }, + "VolumesFrom":"", + "WorkingDir":"", + "ExposedPorts":{ + "22/tcp": {} + } + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 201 OK + Content-Type: application/json + + { + "Id":"e90e34656806" + "Warnings":[] + } + + :jsonparam config: the container's configuration + :query name: Assign the specified name to the container. Must match ``/?[a-zA-Z0-9_-]+``. + :statuscode 201: no error + :statuscode 404: no such container + :statuscode 406: impossible to attach (container not running) + :statuscode 500: server error + + +Inspect a container +******************* + +.. http:get:: /containers/(id)/json + + Return low-level information on the container ``id`` + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/json HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "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": "", + "WorkingDir":"" + + }, + "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": {}, + "HostConfig": { + "Binds": null, + "ContainerIDFile": "", + "LxcConf": [], + "Privileged": false, + "PortBindings": { + "80/tcp": [ + { + "HostIp": "0.0.0.0", + "HostPort": "49153" + } + ] + }, + "Links": null, + "PublishAllPorts": false + } + } + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +List processes running inside a container +***************************************** + +.. http:get:: /containers/(id)/top + + List processes running inside the container ``id`` + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/top HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Titles":[ + "USER", + "PID", + "%CPU", + "%MEM", + "VSZ", + "RSS", + "TTY", + "STAT", + "START", + "TIME", + "COMMAND" + ], + "Processes":[ + ["root","20147","0.0","0.1","18060","1864","pts/4","S","10:06","0:00","bash"], + ["root","20271","0.0","0.0","4312","352","pts/4","S+","10:07","0:00","sleep","10"] + ] + } + + :query ps_args: ps arguments to use (eg. aux) + :statuscode 200: no error + :statuscode 404: 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 + Content-Type: application/json + + [ + { + "Path":"/dev", + "Kind":0 + }, + { + "Path":"/dev/kmsg", + "Kind":1 + }, + { + "Path":"/test", + "Kind":1 + } + ] + + :statuscode 200: no error + :statuscode 404: 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: application/octet-stream + + {{ STREAM }} + + :statuscode 200: no error + :statuscode 404: 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/(id)/start HTTP/1.1 + Content-Type: application/json + + { + "Binds":["/tmp:/tmp"], + "LxcConf":{"lxc.utsname":"docker"}, + "PortBindings":{ "22/tcp": [{ "HostPort": "11022" }] }, + "PublishAllPorts":false, + "Privileged":false + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 No Content + Content-Type: text/plain + + :jsonparam hostConfig: the container's host configuration (optional) + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Stop a container +**************** + +.. 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 204 OK + + :query t: number of seconds to wait before killing the container + :statuscode 204: no error + :statuscode 404: 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 204 OK + + :query t: number of seconds to wait before killing the container + :statuscode 204: no error + :statuscode 404: 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 204 OK + + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Attach to a container +********************* + +.. http:post:: /containers/(id)/attach + + Attach to 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: application/vnd.docker.raw-stream + + {{ STREAM }} + + :query logs: 1/True/true or 0/False/false, return logs. Default false + :query stream: 1/True/true or 0/False/false, return stream. Default false + :query stdin: 1/True/true or 0/False/false, if stream=true, attach to stdin. Default false + :query stdout: 1/True/true or 0/False/false, if logs=true, return stdout log, if stream=true, attach to stdout. Default false + :query stderr: 1/True/true or 0/False/false, if logs=true, return stderr log, if stream=true, attach to stderr. Default false + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 404: no such container + :statuscode 500: server error + + **Stream details**: + + When using the TTY setting is enabled in + :http:post:`/containers/create`, the stream is the raw data + from the process PTY and client's stdin. When the TTY is + disabled, then the stream is multiplexed to separate stdout + and stderr. + + The format is a **Header** and a **Payload** (frame). + + **HEADER** + + The header will contain the information on which stream write + the stream (stdout or stderr). It also contain the size of + the associated frame encoded on the last 4 bytes (uint32). + + It is encoded on the first 8 bytes like this:: + + header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4} + + ``STREAM_TYPE`` can be: + + - 0: stdin (will be writen on stdout) + - 1: stdout + - 2: stderr + + ``SIZE1, SIZE2, SIZE3, SIZE4`` are the 4 bytes of the uint32 size encoded as big endian. + + **PAYLOAD** + + The payload is the raw stream. + + **IMPLEMENTATION** + + The simplest way to implement the Attach protocol is the following: + + 1) Read 8 bytes + 2) chose stdout or stderr depending on the first byte + 3) Extract the frame size from the last 4 byets + 4) Read the extracted size and output it on the correct output + 5) Goto 1) + + + +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 + Content-Type: application/json + + {"StatusCode":0} + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Remove a container +******************* + +.. http:delete:: /containers/(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 204 OK + + :query v: 1/True/true or 0/False/false, Remove the volumes associated to the container. Default false + :statuscode 204: no error + :statuscode 400: bad parameter + :statuscode 404: no such container + :statuscode 500: server error + + +Copy files or folders from a container +************************************** + +.. http:post:: /containers/(id)/copy + + Copy files or folders of container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/4fa6e0f0c678/copy HTTP/1.1 + Content-Type: application/json + + { + "Resource":"test.txt" + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/octet-stream + + {{ STREAM }} + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +2.2 Images +---------- + +List Images +*********** + +.. http:get:: /images/json + + **Example request**: + + .. sourcecode:: http + + GET /images/json?all=0 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "RepoTags": [ + "ubuntu:12.04", + "ubuntu:precise", + "ubuntu:latest" + ], + "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", + "Created": 1365714795, + "Size": 131506275, + "VirtualSize": 131506275 + }, + { + "RepoTags": [ + "ubuntu:12.10", + "ubuntu:quantal" + ], + "ParentId": "27cf784147099545", + "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "Created": 1364102658, + "Size": 24653, + "VirtualSize": 180116135 + } + ] + + +Create an image +*************** + +.. http:post:: /images/create + + Create an image, either by pull it from the registry or by importing it + + **Example request**: + + .. sourcecode:: http + + POST /images/create?fromImage=base HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"Pulling..."} + {"status":"Pulling", "progress":"1 B/ 100 B", "progressDetail":{"current":1, "total":100}} + {"error":"Invalid..."} + ... + + When using this endpoint to pull an image from the registry, + the ``X-Registry-Auth`` header can be used to include a + base64-encoded AuthConfig object. + + :query fromImage: name of the image to pull + :query fromSrc: source to import, - means stdin + :query repo: repository + :query tag: tag + :query registry: the registry to pull from + :reqheader X-Registry-Auth: base64-encoded AuthConfig object + :statuscode 200: no error + :statuscode 500: server error + + + +Insert a file in an 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 + Content-Type: application/json + + {"status":"Inserting..."} + {"status":"Inserting", "progress":"1/? (n/a)", "progressDetail":{"current":1}} + {"error":"Invalid..."} + ... + + :statuscode 200: no error + :statuscode 500: server error + + +Inspect an image +**************** + +.. http:get:: /images/(name)/json + + Return low-level information on the image ``name`` + + **Example request**: + + .. sourcecode:: http + + GET /images/base/json HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "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":"", + "WorkingDir":"" + }, + "Size": 6824592 + } + + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 500: server error + + +Get the history of an image +*************************** + +.. http:get:: /images/(name)/history + + 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 + Content-Type: application/json + + [ + { + "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: application/json + + {"status":"Pushing..."} + {"status":"Pushing", "progress":"1/? (n/a)", "progressDetail":{"current":1}}} + {"error":"Invalid..."} + ... + + :query registry: the registry you wan to push, optional + :reqheader X-Registry-Auth: include a base64-encoded AuthConfig object. + :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 201 OK + + :query repo: The repository to tag in + :query force: 1/True/true or 0/False/false, default false + :statuscode 201: no error + :statuscode 400: bad parameter + :statuscode 404: no such image + :statuscode 409: conflict + :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 + Content-type: application/json + + [ + {"Untagged":"3e2f21a89f"}, + {"Deleted":"3e2f21a89f"}, + {"Deleted":"53b4f83ac9"} + ] + + :query force: 1/True/true or 0/False/false, default false + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 409: conflict + :statuscode 500: server error + + +Search images +************* + +.. http:get:: /images/search + + Search for an image in the docker index. + + .. note:: + + The response keys have changed from API v1.6 to reflect the JSON + sent by the registry server to the docker daemon's request. + + **Example request**: + + .. sourcecode:: http + + GET /images/search?term=sshd HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "description": "", + "is_official": false, + "is_trusted": false, + "name": "wma55/u1210sshd", + "star_count": 0 + }, + { + "description": "", + "is_official": false, + "is_trusted": false, + "name": "jdswinbank/sshd", + "star_count": 0 + }, + { + "description": "", + "is_official": false, + "is_trusted": false, + "name": "vgauthier/sshd", + "star_count": 0 + } + ... + ] + + :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 + Content-Type: application/json + + {"stream":"Step 1..."} + {"stream":"..."} + {"error":"Error...", "errorDetail":{"code": 123, "message": "Error..."}} + + + The stream must be a tar archive compressed with one of the + following algorithms: identity (no compression), gzip, bzip2, + xz. + + The archive must include a file called ``Dockerfile`` at its + root. It may include any number of other files, which will be + accessible in the build context (See the :ref:`ADD build command + `). + + :query t: repository name (and optionally a tag) to be applied to the resulting image in case of success + :query q: suppress verbose build output + :query nocache: do not use the cache when building the image + :reqheader Content-type: should be set to ``"application/tar"``. + :reqheader X-Registry-Config: base64-encoded ConfigFile object + :statuscode 200: no error + :statuscode 500: server error + + + +Check auth configuration +************************ + +.. http:post:: /auth + + Get the default username and email + + **Example request**: + + .. sourcecode:: http + + POST /auth HTTP/1.1 + Content-Type: application/json + + { + "username":"hannibal", + "password:"xxxx", + "email":"hannibal@a-team.com", + "serveraddress":"https://index.docker.io/v1/" + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :statuscode 200: no error + :statuscode 204: 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 + Content-Type: application/json + + { + "Containers":11, + "Images":16, + "Debug":false, + "NFd": 11, + "NGoroutines":21, + "MemoryLimit":true, + "SwapLimit":false, + "IPv4Forwarding":true + } + + :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 + Content-Type: application/json + + { + "Version":"0.2.2", + "GitCommit":"5a2a5cc+CHANGES", + "GoVersion":"go1.0.3" + } + + :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 201 OK + Content-Type: application/vnd.docker.raw-stream + + {"Id":"596069db4bf5"} + + :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 201: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Monitor Docker's events +*********************** + +.. http:get:: /events + + Get events from docker, either in real time via streaming, or via polling (using `since`) + + **Example request**: + + .. sourcecode:: http + + GET /events?since=1374067924 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"create","id":"dfdf82bd3881","from":"base:latest","time":1374067924} + {"status":"start","id":"dfdf82bd3881","from":"base:latest","time":1374067924} + {"status":"stop","id":"dfdf82bd3881","from":"base:latest","time":1374067966} + {"status":"destroy","id":"dfdf82bd3881","from":"base:latest","time":1374067970} + + :query since: timestamp used for polling + :statuscode 200: no error + :statuscode 500: server error + +Get a tarball containing all images and tags in a repository +************************************************************ + +.. http:get:: /images/(name)/get + + Get a tarball containing all images and metadata for the repository specified by ``name``. + + **Example request** + + .. sourcecode:: http + + GET /images/ubuntu/get + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/x-tar + + Binary data stream + + :statuscode 200: no error + :statuscode 500: server error + +Load a tarball with a set of images and tags into docker +******************************************************** + +.. http:post:: /images/load + + Load a set of images and tags into the docker repository. + + **Example request** + + .. sourcecode:: http + + POST /images/load + + Tarball in body + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :statuscode 200: no error + :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 +* If in detached mode or only stdin is attached: + * Display the container's id + + +3.2 Hijacking +------------- + +In this version of the API, /attach, uses hijacking to transport stdin, stdout and stderr on the same socket. This might change in the future. + +3.3 CORS Requests +----------------- + +To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode. + +.. code-block:: bash + + docker -d -H="192.168.1.9:4243" -api-enable-cors diff --git a/components/engine/utils/utils.go b/components/engine/utils/utils.go index 1aba80ff41..8d15f85262 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/utils/utils.go @@ -972,3 +972,25 @@ func NewReadCloserWrapper(r io.Reader, closer func() error) io.ReadCloser { closer: closer, } } + +func CompareVersion(a, b string) int { + aa := strings.Split(a, ".") + bb := strings.Split(b, ".") + for i, s := range aa { + var ai, bi int + ai, _ = strconv.Atoi(s) + if len(bb) > i { + bi, _ = strconv.Atoi(bb[i]) + } + if ai > bi { + return 1 + } + if bi > ai { + return -1 + } + } + if len(bb) > len(aa) { + return -1 + } + return 0 +} diff --git a/components/engine/utils/utils_test.go b/components/engine/utils/utils_test.go index 7e63a45cf7..41dba72aa7 100644 --- a/components/engine/utils/utils_test.go +++ b/components/engine/utils/utils_test.go @@ -479,3 +479,23 @@ func StrSlicesEqual(a, b []string) bool { return true } + +func asserVersion(t *testing.T, a, b string, result int) { + if r := CompareVersion(a, b); r != result { + t.Fatalf("Unexpected version comparison result. Found %d, expected %d", r, result) + } +} + +func TestCompareVersion(t *testing.T) { + asserVersion(t, "1.12", "1.12", 0) + asserVersion(t, "1.05.00.0156", "1.0.221.9289", 1) + asserVersion(t, "1", "1.0.1", -1) + asserVersion(t, "1.0.1", "1", 1) + asserVersion(t, "1.0.1", "1.0.2", -1) + asserVersion(t, "1.0.2", "1.0.3", -1) + asserVersion(t, "1.0.3", "1.1", -1) + asserVersion(t, "1.1", "1.1.1", -1) + asserVersion(t, "1.1.1", "1.1.2", -1) + asserVersion(t, "1.1.2", "1.2", -1) + +} From a7de7303da58d90e3d2cce577c795b71aa0554a0 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Sat, 15 Feb 2014 00:10:57 +0000 Subject: [PATCH 3/9] := / var Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) Upstream-commit: c7f825c0f08e8b6e3cd9dcebc81c21002afcf133 Component: engine --- components/engine/api/client.go | 6 ++++-- components/engine/server.go | 7 +++---- components/engine/utils/utils.go | 6 ++++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/components/engine/api/client.go b/components/engine/api/client.go index da2e7730f1..a6ad41a714 100644 --- a/components/engine/api/client.go +++ b/components/engine/api/client.go @@ -780,8 +780,10 @@ func (cli *DockerCli) CmdPort(args ...string) error { // 'docker rmi IMAGE' removes all images with the name IMAGE func (cli *DockerCli) CmdRmi(args ...string) error { - cmd := cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images") - force := cmd.Bool([]string{"f", "-force"}, false, "Force") + var ( + cmd = cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images") + force = cmd.Bool([]string{"f", "-force"}, false, "Force") + ) if err := cmd.Parse(args); err != nil { return nil } diff --git a/components/engine/server.go b/components/engine/server.go index 91266c23b9..7c3d482b86 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -1812,10 +1812,10 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status { func (srv *Server) DeleteImage(name string, imgs *engine.Table, first, force bool) error { var ( repoName, tag string - img, err = srv.runtime.repositories.LookupImage(name) tags = []string{} ) + img, err := srv.runtime.repositories.LookupImage(name) if err != nil { return fmt.Errorf("No such image: %s", name) } @@ -1873,8 +1873,7 @@ func (srv *Server) DeleteImage(name string, imgs *engine.Table, first, force boo if err := srv.runtime.repositories.DeleteAll(img.ID); err != nil { return err } - err := srv.runtime.graph.Delete(img.ID) - if err != nil { + if err := srv.runtime.graph.Delete(img.ID); err != nil { return err } out := &engine.Env{} @@ -1898,7 +1897,7 @@ func (srv *Server) ImageDelete(job *engine.Job) engine.Status { if n := len(job.Args); n != 1 { return job.Errorf("Usage: %s IMAGE", job.Name) } - var imgs = engine.NewTable("", 0) + imgs := engine.NewTable("", 0) if err := srv.DeleteImage(job.Args[0], imgs, true, job.GetenvBool("force")); err != nil { return job.Error(err) } diff --git a/components/engine/utils/utils.go b/components/engine/utils/utils.go index 8d15f85262..88f43c8bd5 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/utils/utils.go @@ -974,8 +974,10 @@ func NewReadCloserWrapper(r io.Reader, closer func() error) io.ReadCloser { } func CompareVersion(a, b string) int { - aa := strings.Split(a, ".") - bb := strings.Split(b, ".") + var ( + aa = strings.Split(a, ".") + bb = strings.Split(b, ".") + ) for i, s := range aa { var ai, bi int ai, _ = strconv.Atoi(s) From fcbc1feb4a8370dac05957c97527bb8302008fee Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 18 Feb 2014 01:44:53 +0000 Subject: [PATCH 4/9] fix tests Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) Upstream-commit: 964d82d00506c3005d102b9a1f94848182c5989d Component: engine --- components/engine/api/server.go | 4 ++-- components/engine/integration/api_test.go | 1 + components/engine/integration/server_test.go | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/components/engine/api/server.go b/components/engine/api/server.go index 007c07ddb8..1019521e03 100644 --- a/components/engine/api/server.go +++ b/components/engine/api/server.go @@ -1057,13 +1057,13 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st // ServeRequest processes a single http request to the docker remote api. // FIXME: refactor this to be part of Server and not require re-creating a new // router each time. This requires first moving ListenAndServe into Server. -func ServeRequest(eng *engine.Engine, apiversion float64, w http.ResponseWriter, req *http.Request) error { +func ServeRequest(eng *engine.Engine, apiversion string, w http.ResponseWriter, req *http.Request) error { router, err := createRouter(eng, false, true, "") if err != nil { return err } // Insert APIVERSION into the request as a convenience - req.URL.Path = fmt.Sprintf("/v%g%s", apiversion, req.URL.Path) + req.URL.Path = fmt.Sprintf("/v%s%s", apiversion, req.URL.Path) router.ServeHTTP(w, req) return nil } diff --git a/components/engine/integration/api_test.go b/components/engine/integration/api_test.go index abbc1a1c59..4c22d54b3d 100644 --- a/components/engine/integration/api_test.go +++ b/components/engine/integration/api_test.go @@ -13,6 +13,7 @@ import ( "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" "io" + "io/ioutil" "net" "net/http" "net/http/httptest" diff --git a/components/engine/integration/server_test.go b/components/engine/integration/server_test.go index 8600e78573..2fe16551dc 100644 --- a/components/engine/integration/server_test.go +++ b/components/engine/integration/server_test.go @@ -691,7 +691,7 @@ func TestDeleteTagWithExistingContainers(t *testing.T) { t.Fatalf("Should only have deleted one untag %d", len(imgs.Data)) } - if untag := imgs.Data[0].Get("Untagged"); untag != unitTestImageID { + if untag := imgs.Data[0].Get("Untagged"); untag != "utest:tag1" { t.Fatalf("Expected %s got %s", unitTestImageID, untag) } } From dde3a7b46761afcd7ab9a034eb0c0e56e8cf26d0 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 18 Feb 2014 03:03:19 +0000 Subject: [PATCH 5/9] fix message 'No such image: ubuntu' => 'No such image: ubuntu:latest' Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) Upstream-commit: 626cee87f779dff2840b1a31cbe6912545758a9f Component: engine --- components/engine/server.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/components/engine/server.go b/components/engine/server.go index 7c3d482b86..9feca36e5b 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -1815,16 +1815,22 @@ func (srv *Server) DeleteImage(name string, imgs *engine.Table, first, force boo tags = []string{} ) + repoName, tag = utils.ParseRepositoryTag(name) + if tag == "" { + tag = DEFAULTTAG + } + img, err := srv.runtime.repositories.LookupImage(name) if err != nil { + if r, _ := srv.runtime.repositories.Get(repoName); r != nil { + return fmt.Errorf("No such image: %s:%s", repoName, tag) + } return fmt.Errorf("No such image: %s", name) } - if !strings.Contains(img.ID, name) { - repoName, tag = utils.ParseRepositoryTag(name) - if tag == "" { - tag = DEFAULTTAG - } + if strings.Contains(img.ID, name) { + repoName = "" + tag = "" } byParents, err := srv.runtime.graph.ByParent() From 4637d495ffb5a0e70f7ac4533e36c7aa0caf2dff Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 19 Feb 2014 01:21:32 +0000 Subject: [PATCH 6/9] fix doc Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) Upstream-commit: 21f56c04e4f4df4e171985896b54b243c6ef0fcb Component: engine --- .../docs/sources/reference/api/docker_remote_api.rst | 2 +- .../sources/reference/api/docker_remote_api_v1.10.rst | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/engine/docs/sources/reference/api/docker_remote_api.rst b/components/engine/docs/sources/reference/api/docker_remote_api.rst index 51c4a06c91..3733effaf8 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api.rst +++ b/components/engine/docs/sources/reference/api/docker_remote_api.rst @@ -36,7 +36,7 @@ You can still call an old version of the api using v1.10 -**** +***** Full Documentation ------------------ diff --git a/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.rst b/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.rst index 6ee3febc27..a6eec3551f 100644 --- a/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.rst +++ b/components/engine/docs/sources/reference/api/docker_remote_api_v1.10.rst @@ -1,12 +1,12 @@ -:title: Remote API v1.9 +:title: Remote API v1.10 :description: API Documentation for Docker :keywords: API, Docker, rcli, REST, documentation :orphan: -====================== -Docker Remote API v1.9 -====================== +======================= +Docker Remote API v1.10 +======================= .. contents:: Table of Contents From 5892660f0af064425fbe7dd3392700fbd8d4ab35 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 19 Feb 2014 01:34:37 +0000 Subject: [PATCH 7/9] fix bad rebase Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) Upstream-commit: 3f6324f0e8e1b4d117a5d22f752e1d5980eea939 Component: engine --- components/engine/server.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/engine/server.go b/components/engine/server.go index 9feca36e5b..759739cdd4 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -1857,6 +1857,10 @@ func (srv *Server) DeleteImage(name string, imgs *engine.Table, first, force boo tags = append(tags, tag) } + if !first && len(tags) > 0 { + return nil + } + //Untag the current image for _, tag := range tags { tagDeleted, err := srv.runtime.repositories.Delete(repoName, tag) From cae176477e917303923fb2f06cd9e348504c9330 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 20 Feb 2014 00:13:37 +0000 Subject: [PATCH 8/9] add missing doc Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) Upstream-commit: 10faefac3b88069f22283f3f2e018d100a66f2b9 Component: engine --- components/engine/docs/sources/reference/commandline/cli.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/engine/docs/sources/reference/commandline/cli.rst b/components/engine/docs/sources/reference/commandline/cli.rst index b221f0de6b..84cf2c1d67 100644 --- a/components/engine/docs/sources/reference/commandline/cli.rst +++ b/components/engine/docs/sources/reference/commandline/cli.rst @@ -1023,6 +1023,8 @@ containers will not be deleted. Usage: docker rmi IMAGE [IMAGE...] Remove one or more images + + -f, --force=false: Force Removing tagged images ~~~~~~~~~~~~~~~~~~~~~~ From af968e223121c9f27ad51e353274d8c7cff505fc Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 21 Feb 2014 23:15:28 +0000 Subject: [PATCH 9/9] add version pkg Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) Upstream-commit: 8dad771daa6572ca15949d3e53e825f4837c0af9 Component: engine --- components/engine/api/server.go | 123 +++++++++--------- components/engine/integration/server_test.go | 1 + components/engine/pkg/version/version.go | 52 ++++++++ components/engine/pkg/version/version_test.go | 25 ++++ components/engine/utils/utils.go | 24 ---- components/engine/utils/utils_test.go | 20 --- 6 files changed, 140 insertions(+), 105 deletions(-) create mode 100644 components/engine/pkg/version/version.go create mode 100644 components/engine/pkg/version/version_test.go diff --git a/components/engine/api/server.go b/components/engine/api/server.go index 1019521e03..fc4a5c2f04 100644 --- a/components/engine/api/server.go +++ b/components/engine/api/server.go @@ -13,6 +13,7 @@ import ( "github.com/dotcloud/docker/pkg/listenbuffer" "github.com/dotcloud/docker/pkg/systemd" "github.com/dotcloud/docker/pkg/user" + "github.com/dotcloud/docker/pkg/version" "github.com/dotcloud/docker/utils" "github.com/gorilla/mux" "io" @@ -32,7 +33,7 @@ var ( activationLock chan struct{} ) -type HttpApiFunc func(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error +type HttpApiFunc func(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { conn, _, err := w.(http.Hijacker).Hijack() @@ -113,7 +114,7 @@ func getBoolParam(value string) (bool, error) { return ret, nil } -func postAuth(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { var ( authConfig, err = ioutil.ReadAll(r.Body) job = eng.Job("auth") @@ -136,13 +137,13 @@ func postAuth(eng *engine.Engine, version string, w http.ResponseWriter, r *http return nil } -func getVersion(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getVersion(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.Header().Set("Content-Type", "application/json") eng.ServeHTTP(w, r) return nil } -func postContainersKill(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersKill(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -160,7 +161,7 @@ func postContainersKill(eng *engine.Engine, version string, w http.ResponseWrite return nil } -func getContainersExport(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getContainersExport(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -172,7 +173,7 @@ func getContainersExport(eng *engine.Engine, version string, w http.ResponseWrit return nil } -func getImagesJSON(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getImagesJSON(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -186,7 +187,7 @@ func getImagesJSON(eng *engine.Engine, version string, w http.ResponseWriter, r job.Setenv("filter", r.Form.Get("filter")) job.Setenv("all", r.Form.Get("all")) - if utils.CompareVersion(version, "1.7") >= 0 { + if version.GreaterThanOrEqualTo("1.7") { streamJSON(job, w, false) } else if outs, err = job.Stdout.AddListTable(); err != nil { return err @@ -196,7 +197,7 @@ func getImagesJSON(eng *engine.Engine, version string, w http.ResponseWriter, r return err } - if utils.CompareVersion(version, "1.7") < 0 && outs != nil { // Convert to legacy format + if version.LessThan("1.7") && outs != nil { // Convert to legacy format outsLegacy := engine.NewTable("Created", 0) for _, out := range outs.Data { for _, repoTag := range out.GetList("RepoTags") { @@ -219,8 +220,8 @@ func getImagesJSON(eng *engine.Engine, version string, w http.ResponseWriter, r return nil } -func getImagesViz(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if utils.CompareVersion(version, "1.6") > 0 { +func getImagesViz(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if version.GreaterThan("1.6") { w.WriteHeader(http.StatusNotFound) return fmt.Errorf("This is now implemented in the client.") } @@ -228,13 +229,13 @@ func getImagesViz(eng *engine.Engine, version string, w http.ResponseWriter, r * return nil } -func getInfo(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getInfo(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.Header().Set("Content-Type", "application/json") eng.ServeHTTP(w, r) return nil } -func getEvents(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getEvents(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -245,7 +246,7 @@ func getEvents(eng *engine.Engine, version string, w http.ResponseWriter, r *htt return job.Run() } -func getImagesHistory(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getImagesHistory(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -259,7 +260,7 @@ func getImagesHistory(eng *engine.Engine, version string, w http.ResponseWriter, return nil } -func getContainersChanges(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getContainersChanges(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -269,8 +270,8 @@ func getContainersChanges(eng *engine.Engine, version string, w http.ResponseWri return job.Run() } -func getContainersTop(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if utils.CompareVersion(version, "1.4") < 0 { +func getContainersTop(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if version.LessThan("1.4") { return fmt.Errorf("top was improved a lot since 1.3, Please upgrade your docker client.") } if vars == nil { @@ -285,7 +286,7 @@ func getContainersTop(eng *engine.Engine, version string, w http.ResponseWriter, return job.Run() } -func getContainersJSON(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getContainersJSON(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -301,7 +302,7 @@ func getContainersJSON(eng *engine.Engine, version string, w http.ResponseWriter job.Setenv("before", r.Form.Get("before")) job.Setenv("limit", r.Form.Get("limit")) - if utils.CompareVersion(version, "1.5") >= 0 { + if version.GreaterThanOrEqualTo("1.5") { streamJSON(job, w, false) } else if outs, err = job.Stdout.AddTable(); err != nil { return err @@ -309,7 +310,7 @@ func getContainersJSON(eng *engine.Engine, version string, w http.ResponseWriter if err = job.Run(); err != nil { return err } - if utils.CompareVersion(version, "1.5") < 0 { // Convert to legacy format + if version.LessThan("1.5") { // Convert to legacy format for _, out := range outs.Data { ports := engine.NewTable("", 0) ports.ReadListFrom([]byte(out.Get("Ports"))) @@ -323,7 +324,7 @@ func getContainersJSON(eng *engine.Engine, version string, w http.ResponseWriter return nil } -func postImagesTag(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postImagesTag(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -340,7 +341,7 @@ func postImagesTag(eng *engine.Engine, version string, w http.ResponseWriter, r return nil } -func postCommit(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -369,7 +370,7 @@ func postCommit(eng *engine.Engine, version string, w http.ResponseWriter, r *ht } // Creates an image from Pull or from Import -func postImagesCreate(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postImagesCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -397,7 +398,7 @@ func postImagesCreate(eng *engine.Engine, version string, w http.ResponseWriter, } } job = eng.Job("pull", r.Form.Get("fromImage"), tag) - job.SetenvBool("parallel", utils.CompareVersion(version, "1.3") > 0) + job.SetenvBool("parallel", version.GreaterThan("1.3")) job.SetenvJson("metaHeaders", metaHeaders) job.SetenvJson("authConfig", authConfig) } else { //import @@ -405,7 +406,7 @@ func postImagesCreate(eng *engine.Engine, version string, w http.ResponseWriter, job.Stdin.Add(r.Body) } - if utils.CompareVersion(version, "1.0") > 0 { + if version.GreaterThan("1.0") { job.SetenvBool("json", true) streamJSON(job, w, true) } else { @@ -415,14 +416,14 @@ func postImagesCreate(eng *engine.Engine, version string, w http.ResponseWriter, if !job.Stdout.Used() { return err } - sf := utils.NewStreamFormatter(utils.CompareVersion(version, "1.0") > 0) + sf := utils.NewStreamFormatter(version.GreaterThan("1.0")) w.Write(sf.FormatError(err)) } return nil } -func getImagesSearch(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getImagesSearch(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -454,7 +455,7 @@ func getImagesSearch(eng *engine.Engine, version string, w http.ResponseWriter, return job.Run() } -func postImagesInsert(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postImagesInsert(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -462,7 +463,7 @@ func postImagesInsert(eng *engine.Engine, version string, w http.ResponseWriter, return fmt.Errorf("Missing parameter") } job := eng.Job("insert", vars["name"], r.Form.Get("url"), r.Form.Get("path")) - if utils.CompareVersion(version, "1.0") > 0 { + if version.GreaterThan("1.0") { job.SetenvBool("json", true) streamJSON(job, w, false) } else { @@ -472,14 +473,14 @@ func postImagesInsert(eng *engine.Engine, version string, w http.ResponseWriter, if !job.Stdout.Used() { return err } - sf := utils.NewStreamFormatter(utils.CompareVersion(version, "1.0") > 0) + sf := utils.NewStreamFormatter(version.GreaterThan("1.0")) w.Write(sf.FormatError(err)) } return nil } -func postImagesPush(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postImagesPush(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -513,7 +514,7 @@ func postImagesPush(eng *engine.Engine, version string, w http.ResponseWriter, r job := eng.Job("push", vars["name"]) job.SetenvJson("metaHeaders", metaHeaders) job.SetenvJson("authConfig", authConfig) - if utils.CompareVersion(version, "1.0") > 0 { + if version.GreaterThan("1.0") { job.SetenvBool("json", true) streamJSON(job, w, true) } else { @@ -524,17 +525,17 @@ func postImagesPush(eng *engine.Engine, version string, w http.ResponseWriter, r if !job.Stdout.Used() { return err } - sf := utils.NewStreamFormatter(utils.CompareVersion(version, "1.0") > 0) + sf := utils.NewStreamFormatter(version.GreaterThan("1.0")) w.Write(sf.FormatError(err)) } return nil } -func getImagesGet(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getImagesGet(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } - if utils.CompareVersion(version, "1.0") > 0 { + if version.GreaterThan("1.0") { w.Header().Set("Content-Type", "application/x-tar") } job := eng.Job("image_export", vars["name"]) @@ -542,13 +543,13 @@ func getImagesGet(eng *engine.Engine, version string, w http.ResponseWriter, r * return job.Run() } -func postImagesLoad(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postImagesLoad(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { job := eng.Job("load") job.Stdin.Add(r.Body) return job.Run() } -func postContainersCreate(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return nil } @@ -579,7 +580,7 @@ func postContainersCreate(eng *engine.Engine, version string, w http.ResponseWri return writeJSON(w, http.StatusCreated, out) } -func postContainersRestart(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersRestart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -595,7 +596,7 @@ func postContainersRestart(eng *engine.Engine, version string, w http.ResponseWr return nil } -func deleteContainers(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func deleteContainers(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -612,7 +613,7 @@ func deleteContainers(eng *engine.Engine, version string, w http.ResponseWriter, return nil } -func deleteImages(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func deleteImages(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -626,7 +627,7 @@ func deleteImages(eng *engine.Engine, version string, w http.ResponseWriter, r * return job.Run() } -func postContainersStart(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersStart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -647,7 +648,7 @@ func postContainersStart(eng *engine.Engine, version string, w http.ResponseWrit return nil } -func postContainersStop(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersStop(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -663,7 +664,7 @@ func postContainersStop(eng *engine.Engine, version string, w http.ResponseWrite return nil } -func postContainersWait(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersWait(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -685,7 +686,7 @@ func postContainersWait(eng *engine.Engine, version string, w http.ResponseWrite return writeJSON(w, http.StatusOK, env) } -func postContainersResize(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersResize(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -698,7 +699,7 @@ func postContainersResize(eng *engine.Engine, version string, w http.ResponseWri return nil } -func postContainersAttach(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersAttach(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -740,7 +741,7 @@ func postContainersAttach(eng *engine.Engine, version string, w http.ResponseWri fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") - if c.GetSubEnv("Config") != nil && !c.GetSubEnv("Config").GetBool("Tty") && utils.CompareVersion(version, "1.6") >= 0 { + if c.GetSubEnv("Config") != nil && !c.GetSubEnv("Config").GetBool("Tty") && version.GreaterThanOrEqualTo("1.6") { errStream = utils.NewStdWriter(outStream, utils.Stderr) outStream = utils.NewStdWriter(outStream, utils.Stdout) } else { @@ -763,7 +764,7 @@ func postContainersAttach(eng *engine.Engine, version string, w http.ResponseWri return nil } -func wsContainersAttach(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func wsContainersAttach(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -795,7 +796,7 @@ func wsContainersAttach(eng *engine.Engine, version string, w http.ResponseWrite return nil } -func getContainersByName(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getContainersByName(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -805,7 +806,7 @@ func getContainersByName(eng *engine.Engine, version string, w http.ResponseWrit return job.Run() } -func getImagesByName(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getImagesByName(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -815,8 +816,8 @@ func getImagesByName(eng *engine.Engine, version string, w http.ResponseWriter, return job.Run() } -func postBuild(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if utils.CompareVersion(version, "1.3") < 0 { +func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if version.LessThan("1.3") { return fmt.Errorf("Multipart upload for build is no longer supported. Please upgrade your docker client.") } var ( @@ -831,7 +832,7 @@ func postBuild(eng *engine.Engine, version string, w http.ResponseWriter, r *htt // Both headers will be parsed and sent along to the daemon, but if a non-empty // ConfigFile is present, any value provided as an AuthConfig directly will // be overridden. See BuildFile::CmdFrom for details. - if utils.CompareVersion(version, "1.9") < 0 && authEncoded != "" { + if version.LessThan("1.9") && authEncoded != "" { authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { // for a pull it is not an error if no auth was given @@ -849,7 +850,7 @@ func postBuild(eng *engine.Engine, version string, w http.ResponseWriter, r *htt } } - if utils.CompareVersion(version, "1.8") >= 0 { + if version.GreaterThanOrEqualTo("1.8") { job.SetenvBool("json", true) streamJSON(job, w, true) } else { @@ -868,13 +869,13 @@ func postBuild(eng *engine.Engine, version string, w http.ResponseWriter, r *htt if !job.Stdout.Used() { return err } - sf := utils.NewStreamFormatter(utils.CompareVersion(version, "1.8") >= 0) + sf := utils.NewStreamFormatter(version.GreaterThanOrEqualTo("1.8")) w.Write(sf.FormatError(err)) } return nil } -func postContainersCopy(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersCopy(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -907,7 +908,7 @@ func postContainersCopy(eng *engine.Engine, version string, w http.ResponseWrite return nil } -func optionsHandler(eng *engine.Engine, version string, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func optionsHandler(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.WriteHeader(http.StatusOK) return nil } @@ -917,7 +918,7 @@ func writeCorsHeaders(w http.ResponseWriter, r *http.Request) { w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS") } -func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc, enableCors bool, dockerVersion string) http.HandlerFunc { +func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc, enableCors bool, dockerVersion version.Version) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // log the request utils.Debugf("Calling %s %s", localMethod, localRoute) @@ -928,11 +929,11 @@ func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, local if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") { userAgent := strings.Split(r.Header.Get("User-Agent"), "/") - if len(userAgent) == 2 && userAgent[1] != dockerVersion { + if len(userAgent) == 2 && !dockerVersion.Equal(userAgent[1]) { utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], dockerVersion) } } - version := mux.Vars(r)["version"] + version := version.Version(mux.Vars(r)["version"]) if version == "" { version = APIVERSION } @@ -940,7 +941,7 @@ func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, local writeCorsHeaders(w, r) } - if utils.CompareVersion(version, APIVERSION) == 1 { + if version.GreaterThan(APIVERSION) { http.Error(w, fmt.Errorf("client and server don't have same version (client : %s, server: %s)", version, APIVERSION).Error(), http.StatusNotFound) return } @@ -1039,7 +1040,7 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st localMethod := method // build the handler function - f := makeHttpHandler(eng, logging, localMethod, localRoute, localFct, enableCors, dockerVersion) + f := makeHttpHandler(eng, logging, localMethod, localRoute, localFct, enableCors, version.Version(dockerVersion)) // add the new route if localRoute == "" { @@ -1057,7 +1058,7 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st // ServeRequest processes a single http request to the docker remote api. // FIXME: refactor this to be part of Server and not require re-creating a new // router each time. This requires first moving ListenAndServe into Server. -func ServeRequest(eng *engine.Engine, apiversion string, w http.ResponseWriter, req *http.Request) error { +func ServeRequest(eng *engine.Engine, apiversion version.Version, w http.ResponseWriter, req *http.Request) error { router, err := createRouter(eng, false, true, "") if err != nil { return err diff --git a/components/engine/integration/server_test.go b/components/engine/integration/server_test.go index 2fe16551dc..1247e8d2d8 100644 --- a/components/engine/integration/server_test.go +++ b/components/engine/integration/server_test.go @@ -2,6 +2,7 @@ package docker import ( "github.com/dotcloud/docker" + "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/runconfig" "strings" "testing" diff --git a/components/engine/pkg/version/version.go b/components/engine/pkg/version/version.go new file mode 100644 index 0000000000..3721d64aa8 --- /dev/null +++ b/components/engine/pkg/version/version.go @@ -0,0 +1,52 @@ +package version + +import ( + "strconv" + "strings" +) + +type Version string + +func (me Version) compareTo(other string) int { + var ( + meTab = strings.Split(string(me), ".") + otherTab = strings.Split(other, ".") + ) + for i, s := range meTab { + var meInt, otherInt int + meInt, _ = strconv.Atoi(s) + if len(otherTab) > i { + otherInt, _ = strconv.Atoi(otherTab[i]) + } + if meInt > otherInt { + return 1 + } + if otherInt > meInt { + return -1 + } + } + if len(otherTab) > len(meTab) { + return -1 + } + return 0 +} + +func (me Version) LessThan(other string) bool { + return me.compareTo(other) == -1 +} + +func (me Version) LessThanOrEqualTo(other string) bool { + return me.compareTo(other) <= 0 +} + +func (me Version) GreaterThan(other string) bool { + return me.compareTo(other) == 1 +} + +func (me Version) GreaterThanOrEqualTo(other string) bool { + return me.compareTo(other) >= 0 +} + +func (me Version) Equal(other string) bool { + return me.compareTo(other) == 0 +} diff --git a/components/engine/pkg/version/version_test.go b/components/engine/pkg/version/version_test.go new file mode 100644 index 0000000000..4bebd0c434 --- /dev/null +++ b/components/engine/pkg/version/version_test.go @@ -0,0 +1,25 @@ +package version + +import ( + "testing" +) + +func assertVersion(t *testing.T, a, b string, result int) { + if r := Version(a).compareTo(b); r != result { + t.Fatalf("Unexpected version comparison result. Found %d, expected %d", r, result) + } +} + +func TestCompareVersion(t *testing.T) { + assertVersion(t, "1.12", "1.12", 0) + assertVersion(t, "1.05.00.0156", "1.0.221.9289", 1) + assertVersion(t, "1", "1.0.1", -1) + assertVersion(t, "1.0.1", "1", 1) + assertVersion(t, "1.0.1", "1.0.2", -1) + assertVersion(t, "1.0.2", "1.0.3", -1) + assertVersion(t, "1.0.3", "1.1", -1) + assertVersion(t, "1.1", "1.1.1", -1) + assertVersion(t, "1.1.1", "1.1.2", -1) + assertVersion(t, "1.1.2", "1.2", -1) + +} diff --git a/components/engine/utils/utils.go b/components/engine/utils/utils.go index 88f43c8bd5..1aba80ff41 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/utils/utils.go @@ -972,27 +972,3 @@ func NewReadCloserWrapper(r io.Reader, closer func() error) io.ReadCloser { closer: closer, } } - -func CompareVersion(a, b string) int { - var ( - aa = strings.Split(a, ".") - bb = strings.Split(b, ".") - ) - for i, s := range aa { - var ai, bi int - ai, _ = strconv.Atoi(s) - if len(bb) > i { - bi, _ = strconv.Atoi(bb[i]) - } - if ai > bi { - return 1 - } - if bi > ai { - return -1 - } - } - if len(bb) > len(aa) { - return -1 - } - return 0 -} diff --git a/components/engine/utils/utils_test.go b/components/engine/utils/utils_test.go index 41dba72aa7..7e63a45cf7 100644 --- a/components/engine/utils/utils_test.go +++ b/components/engine/utils/utils_test.go @@ -479,23 +479,3 @@ func StrSlicesEqual(a, b []string) bool { return true } - -func asserVersion(t *testing.T, a, b string, result int) { - if r := CompareVersion(a, b); r != result { - t.Fatalf("Unexpected version comparison result. Found %d, expected %d", r, result) - } -} - -func TestCompareVersion(t *testing.T) { - asserVersion(t, "1.12", "1.12", 0) - asserVersion(t, "1.05.00.0156", "1.0.221.9289", 1) - asserVersion(t, "1", "1.0.1", -1) - asserVersion(t, "1.0.1", "1", 1) - asserVersion(t, "1.0.1", "1.0.2", -1) - asserVersion(t, "1.0.2", "1.0.3", -1) - asserVersion(t, "1.0.3", "1.1", -1) - asserVersion(t, "1.1", "1.1.1", -1) - asserVersion(t, "1.1.1", "1.1.2", -1) - asserVersion(t, "1.1.2", "1.2", -1) - -}