Explorar o código

Merge pull request #4078 from vieux/rewrite_rmi

Rewrite docker rmi
unclejack %!s(int64=11) %!d(string=hai) anos
pai
achega
bde192bb80

+ 13 - 5
api/client.go

@@ -780,7 +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")
+	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
 	}
@@ -789,9 +792,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")
@@ -2032,7 +2040,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
 	}
@@ -2109,7 +2117,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
 	}
@@ -2173,7 +2181,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"
 )

+ 66 - 75
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 float64, 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 float64, 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 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 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 float64, 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 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 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 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 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 float64, w http.ResponseWriter, r
 	job.Setenv("filter", r.Form.Get("filter"))
 	job.Setenv("all", r.Form.Get("all"))
 
-	if version >= 1.7 {
+	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 float64, w http.ResponseWriter, r
 		return err
 	}
 
-	if version < 1.7 && 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 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 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 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 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 float64, 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 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 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 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 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 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 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 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 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 float64, w http.ResponseWrite
 	job.Setenv("before", r.Form.Get("before"))
 	job.Setenv("limit", r.Form.Get("limit"))
 
-	if version >= 1.5 {
+	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 float64, w http.ResponseWrite
 	if err = job.Run(); err != nil {
 		return err
 	}
-	if version < 1.5 { // 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 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 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 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 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 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 version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := parseForm(r); err != nil {
 		return err
 	}
@@ -389,9 +390,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 +398,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", version.GreaterThan("1.3"))
 		job.SetenvJson("metaHeaders", metaHeaders)
 		job.SetenvJson("authConfig", authConfig)
 	} else { //import
@@ -408,7 +406,7 @@ func postImagesCreate(eng *engine.Engine, version float64, w http.ResponseWriter
 		job.Stdin.Add(r.Body)
 	}
 
-	if version > 1.0 {
+	if version.GreaterThan("1.0") {
 		job.SetenvBool("json", true)
 		streamJSON(job, w, true)
 	} else {
@@ -418,14 +416,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(version.GreaterThan("1.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 version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := parseForm(r); err != nil {
 		return err
 	}
@@ -457,19 +455,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 version.Version, 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 version.GreaterThan("1.0") {
 		job.SetenvBool("json", true)
 		streamJSON(job, w, false)
 	} else {
@@ -479,14 +473,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(version.GreaterThan("1.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 version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if vars == nil {
 		return fmt.Errorf("Missing parameter")
 	}
@@ -517,13 +511,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 version.GreaterThan("1.0") {
 		job.SetenvBool("json", true)
 		streamJSON(job, w, true)
 	} else {
@@ -534,17 +525,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(version.GreaterThan("1.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 version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if vars == nil {
 		return fmt.Errorf("Missing parameter")
 	}
-	if version > 1.0 {
+	if version.GreaterThan("1.0") {
 		w.Header().Set("Content-Type", "application/x-tar")
 	}
 	job := eng.Job("image_export", vars["name"])
@@ -552,13 +543,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 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 float64, 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
 	}
@@ -589,7 +580,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 version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := parseForm(r); err != nil {
 		return err
 	}
@@ -605,7 +596,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 version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := parseForm(r); err != nil {
 		return err
 	}
@@ -622,7 +613,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 version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := parseForm(r); err != nil {
 		return err
 	}
@@ -631,12 +622,12 @@ 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()
 }
 
-func postContainersStart(eng *engine.Engine, version float64, 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")
 	}
@@ -657,7 +648,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 version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := parseForm(r); err != nil {
 		return err
 	}
@@ -673,7 +664,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 version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if vars == nil {
 		return fmt.Errorf("Missing parameter")
 	}
@@ -695,7 +686,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 version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := parseForm(r); err != nil {
 		return err
 	}
@@ -708,7 +699,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 version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := parseForm(r); err != nil {
 		return err
 	}
@@ -750,7 +741,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") && version.GreaterThanOrEqualTo("1.6") {
 		errStream = utils.NewStdWriter(outStream, utils.Stderr)
 		outStream = utils.NewStdWriter(outStream, utils.Stdout)
 	} else {
@@ -773,7 +764,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 version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := parseForm(r); err != nil {
 		return err
 	}
@@ -805,7 +796,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 version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if vars == nil {
 		return fmt.Errorf("Missing parameter")
 	}
@@ -815,7 +806,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 version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if vars == nil {
 		return fmt.Errorf("Missing parameter")
 	}
@@ -825,8 +816,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 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 (
@@ -841,7 +832,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 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
@@ -859,7 +850,7 @@ func postBuild(eng *engine.Engine, version float64, w http.ResponseWriter, r *ht
 		}
 	}
 
-	if version >= 1.8 {
+	if version.GreaterThanOrEqualTo("1.8") {
 		job.SetenvBool("json", true)
 		streamJSON(job, w, true)
 	} else {
@@ -878,13 +869,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(version.GreaterThanOrEqualTo("1.8"))
 		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 version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if vars == nil {
 		return fmt.Errorf("Missing parameter")
 	}
@@ -917,7 +908,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 version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	w.WriteHeader(http.StatusOK)
 	return nil
 }
@@ -927,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)
@@ -938,20 +929,20 @@ 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, err := strconv.ParseFloat(mux.Vars(r)["version"], 64)
-		if err != nil {
+		version := version.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 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
 		}
 
@@ -1049,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 == "" {
@@ -1067,13 +1058,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 version.Version, 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
 }

+ 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.10
+:description: API Documentation for Docker
+:keywords: API, Docker, rcli, REST, documentation
+
+:orphan:
+
+=======================
+Docker Remote API v1.10
+=======================
+
+.. 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

+ 2 - 0
docs/sources/reference/commandline/cli.rst

@@ -1020,6 +1020,8 @@ containers will not be deleted.
     Usage: docker rmi IMAGE [IMAGE...]
 
     Remove one or more images
+
+      -f, --force=false: Force
     
 Removing tagged images
 ~~~~~~~~~~~~~~~~~~~~~~

+ 3 - 0
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"
@@ -1175,6 +1176,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, "")

+ 4 - 1
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 {

+ 8 - 8
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"
@@ -35,7 +36,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 +48,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 +57,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 +448,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 +683,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)
 	}
 
@@ -692,7 +692,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)
 	}
 }

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

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

+ 48 - 101
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,33 @@ 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)
+	repoName, tag = utils.ParseRepositoryTag(name)
+	if tag == "" {
+		tag = DEFAULTTAG
 	}
 
-	// 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)
+	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 nil, nil
+		return fmt.Errorf("No such image: %s", name)
 	}
 
-	if !strings.Contains(img.ID, name) {
-		repoName, tag = utils.ParseRepositoryTag(name)
+	if strings.Contains(img.ID, name) {
+		repoName = ""
+		tag = ""
 	}
 
-	// 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,51 +1847,68 @@ 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 {
 		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)
 		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, "")
 		}
 	}
-
-	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
+	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
 			}
-		} else if err := srv.deleteImageParents(img, imgs); err != nil {
-			if err != ErrImageReferenced {
-				return imgs, err
+			if err := srv.runtime.graph.Delete(img.ID); 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
+				}
+
+			}
+
 		}
 	}
-	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 {
+	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 {