Merge pull request #4078 from vieux/rewrite_rmi

Rewrite docker rmi
This commit is contained in:
unclejack 2014-02-26 21:30:24 +02:00
commit bde192bb80
12 changed files with 1523 additions and 194 deletions

View File

@ -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
}

View File

@ -9,7 +9,7 @@ import (
)
const (
APIVERSION = 1.9
APIVERSION = "1.10"
DEFAULTHTTPHOST = "127.0.0.1"
DEFAULTUNIXSOCKET = "/var/run/docker.sock"
)

View File

@ -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
}

View File

@ -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
****

File diff suppressed because it is too large Load Diff

View File

@ -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
~~~~~~~~~~~~~~~~~~~~~~

View File

@ -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, "")

View File

@ -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 {

View File

@ -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
pkg/version/version.go Normal file
View File

@ -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
}

View File

@ -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)
}

151
server.go
View File

@ -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{}
)
repoName, tag = utils.ParseRepositoryTag(name)
if tag == "" {
tag = DEFAULTTAG
}
img, err := srv.runtime.repositories.LookupImage(name)
if err != nil {
return nil, fmt.Errorf("No such image: %s", name)
}
// FIXME: What does autoPrune mean ?
if !autoPrune {
if err := srv.runtime.graph.Delete(img.ID); err != nil {
return nil, fmt.Errorf("Cannot delete image %s: %s", name, err)
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, "")
}
}
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
}
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
}
if len(srv.runtime.repositories.ByID()[img.ID]) == 0 {
if err := srv.deleteImageAndChildren(img.ID, imgs, nil); err != nil {
if err != ErrImageReferenced {
return imgs, err
}
} else if err := srv.deleteImageParents(img, imgs); err != nil {
if err != ErrImageReferenced {
return imgs, err
}
}
}
return imgs, nil
return nil
}
func (srv *Server) ImageDelete(job *engine.Job) engine.Status {
if n := len(job.Args); n != 1 {
return job.Errorf("Usage: %s IMAGE", job.Name)
}
imgs, err := srv.DeleteImage(job.Args[0], job.GetenvBool("autoPrune"))
if err != nil {
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 {