Browse Source

bump API version

Docker-DCO-1.1-Signed-off-by: Victor Vieux <victor.vieux@docker.com> (github: vieux)
Victor Vieux 11 years ago
parent
commit
e7cc88c39f

+ 3 - 3
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
 	}

+ 1 - 1
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"
 )

+ 59 - 69
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
 		}
 

+ 18 - 2
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/<name>/insert is the same as calling
-/v1.9/images/<name>/insert
+/v1.10/images/<name>/insert
 
 You can still call an old version of the api using
 /v1.0/images/<name>/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
 ****
 

+ 1282 - 0
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
+   <dockerbuilder>`).
+
+   :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 <hannibal@a-team.com>")
+    :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

+ 22 - 0
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
+}

+ 20 - 0
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)
+
+}