Przeglądaj źródła

Rewrite docker rmi

Docker-DCO-1.1-Signed-off-by: Victor Vieux <victor.vieux@docker.com> (github: vieux)
Victor Vieux 11 lat temu
rodzic
commit
795ed6b1e5
6 zmienionych plików z 58 dodań i 110 usunięć
  1. 7 1
      api/client.go
  2. 1 1
      api/server.go
  3. 2 0
      integration/api_test.go
  4. 4 1
      integration/commands_test.go
  5. 6 7
      integration/server_test.go
  6. 38 100
      server.go

+ 7 - 1
api/client.go

@@ -781,6 +781,7 @@ func (cli *DockerCli) CmdPort(args ...string) error {
 // 'docker rmi IMAGE' removes all images with the name IMAGE
 func (cli *DockerCli) CmdRmi(args ...string) error {
 	cmd := cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images")
+	force := cmd.Bool([]string{"f", "-force"}, false, "Force")
 	if err := cmd.Parse(args); err != nil {
 		return nil
 	}
@@ -789,9 +790,14 @@ func (cli *DockerCli) CmdRmi(args ...string) error {
 		return nil
 	}
 
+	v := url.Values{}
+	if *force {
+		v.Set("force", "1")
+	}
+
 	var encounteredError error
 	for _, name := range cmd.Args() {
-		body, _, err := readBody(cli.call("DELETE", "/images/"+name, nil, false))
+		body, _, err := readBody(cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, false))
 		if err != nil {
 			fmt.Fprintf(cli.err, "%s\n", err)
 			encounteredError = fmt.Errorf("Error: failed to remove one or more images")

+ 1 - 1
api/server.go

@@ -631,7 +631,7 @@ func deleteImages(eng *engine.Engine, version float64, w http.ResponseWriter, r
 	}
 	var job = eng.Job("image_delete", vars["name"])
 	streamJSON(job, w, false)
-	job.SetenvBool("autoPrune", version > 1.1)
+	job.Setenv("force", r.Form.Get("force"))
 
 	return job.Run()
 }

+ 2 - 0
integration/api_test.go

@@ -1175,6 +1175,8 @@ func TestGetEnabledCors(t *testing.T) {
 
 func TestDeleteImages(t *testing.T) {
 	eng := NewTestEngine(t)
+	//we expect errors, so we disable stderr
+	eng.Stderr = ioutil.Discard
 	defer mkRuntimeFromEngine(eng, t).Nuke()
 
 	initialImages := getImages(eng, t, true, "")

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

+ 6 - 7
integration/server_test.go

@@ -35,7 +35,7 @@ func TestImageTagImageDelete(t *testing.T) {
 		t.Errorf("Expected %d images, %d found", nExpected, nActual)
 	}
 
-	if _, err := srv.DeleteImage("utest/docker:tag2", true); err != nil {
+	if err := srv.DeleteImage("utest/docker:tag2", engine.NewTable("", 0), true, false); err != nil {
 		t.Fatal(err)
 	}
 
@@ -47,7 +47,7 @@ func TestImageTagImageDelete(t *testing.T) {
 		t.Errorf("Expected %d images, %d found", nExpected, nActual)
 	}
 
-	if _, err := srv.DeleteImage("utest:5000/docker:tag3", true); err != nil {
+	if err := srv.DeleteImage("utest:5000/docker:tag3", engine.NewTable("", 0), true, false); err != nil {
 		t.Fatal(err)
 	}
 
@@ -56,7 +56,7 @@ func TestImageTagImageDelete(t *testing.T) {
 	nExpected = len(initialImages.Data[0].GetList("RepoTags")) + 1
 	nActual = len(images.Data[0].GetList("RepoTags"))
 
-	if _, err := srv.DeleteImage("utest:tag1", true); err != nil {
+	if err := srv.DeleteImage("utest:tag1", engine.NewTable("", 0), true, false); err != nil {
 		t.Fatal(err)
 	}
 
@@ -447,8 +447,7 @@ func TestRmi(t *testing.T) {
 		t.Fatalf("Expected 2 new images, found %d.", images.Len()-initialImages.Len())
 	}
 
-	_, err = srv.DeleteImage(imageID, true)
-	if err != nil {
+	if err = srv.DeleteImage(imageID, engine.NewTable("", 0), true, false); err != nil {
 		t.Fatal(err)
 	}
 
@@ -683,8 +682,8 @@ func TestDeleteTagWithExistingContainers(t *testing.T) {
 	}
 
 	// Try to remove the tag
-	imgs, err := srv.DeleteImage("utest:tag1", true)
-	if err != nil {
+	imgs := engine.NewTable("", 0)
+	if err := srv.DeleteImage("utest:tag1", imgs, true, false); err != nil {
 		t.Fatal(err)
 	}
 

+ 38 - 100
server.go

@@ -2,7 +2,6 @@ package docker
 
 import (
 	"encoding/json"
-	"errors"
 	"fmt"
 	"github.com/dotcloud/docker/archive"
 	"github.com/dotcloud/docker/auth"
@@ -1810,102 +1809,27 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status {
 	return engine.StatusOK
 }
 
-var ErrImageReferenced = errors.New("Image referenced by a repository")
-
-func (srv *Server) deleteImageAndChildren(id string, imgs *engine.Table, byParents map[string][]*Image) error {
-	// If the image is referenced by a repo, do not delete
-	if len(srv.runtime.repositories.ByID()[id]) != 0 {
-		return ErrImageReferenced
-	}
-	// If the image is not referenced but has children, go recursive
-	referenced := false
-	for _, img := range byParents[id] {
-		if err := srv.deleteImageAndChildren(img.ID, imgs, byParents); err != nil {
-			if err != ErrImageReferenced {
-				return err
-			}
-			referenced = true
-		}
-	}
-	if referenced {
-		return ErrImageReferenced
-	}
-
-	// If the image is not referenced and has no children, remove it
-	byParents, err := srv.runtime.graph.ByParent()
-	if err != nil {
-		return err
-	}
-	if len(byParents[id]) == 0 && srv.canDeleteImage(id) == nil {
-		if err := srv.runtime.repositories.DeleteAll(id); err != nil {
-			return err
-		}
-		err := srv.runtime.graph.Delete(id)
-		if err != nil {
-			return err
-		}
-		out := &engine.Env{}
-		out.Set("Deleted", id)
-		imgs.Add(out)
-		srv.LogEvent("delete", id, "")
-		return nil
-	}
-	return nil
-}
-
-func (srv *Server) deleteImageParents(img *Image, imgs *engine.Table) error {
-	if img.Parent != "" {
-		parent, err := srv.runtime.graph.Get(img.Parent)
-		if err != nil {
-			return err
-		}
-		byParents, err := srv.runtime.graph.ByParent()
-		if err != nil {
-			return err
-		}
-		// Remove all children images
-		if err := srv.deleteImageAndChildren(img.Parent, imgs, byParents); err != nil {
-			return err
-		}
-		return srv.deleteImageParents(parent, imgs)
-	}
-	return nil
-}
-
-func (srv *Server) DeleteImage(name string, autoPrune bool) (*engine.Table, error) {
+func (srv *Server) DeleteImage(name string, imgs *engine.Table, first, force bool) error {
 	var (
 		repoName, tag string
 		img, err      = srv.runtime.repositories.LookupImage(name)
-		imgs          = engine.NewTable("", 0)
 		tags          = []string{}
 	)
 
 	if err != nil {
-		return nil, fmt.Errorf("No such image: %s", name)
-	}
-
-	// FIXME: What does autoPrune mean ?
-	if !autoPrune {
-		if err := srv.runtime.graph.Delete(img.ID); err != nil {
-			return nil, fmt.Errorf("Cannot delete image %s: %s", name, err)
-		}
-		return nil, nil
+		return fmt.Errorf("No such image: %s", name)
 	}
 
 	if !strings.Contains(img.ID, name) {
 		repoName, tag = utils.ParseRepositoryTag(name)
+		if tag == "" {
+			tag = DEFAULTTAG
+		}
 	}
 
-	// If we have a repo and the image is not referenced anywhere else
-	// then just perform an untag and do not validate.
-	//
-	// i.e. only validate if we are performing an actual delete and not
-	// an untag op
-	if repoName != "" && len(srv.runtime.repositories.ByID()[img.ID]) == 1 {
-		// Prevent deletion if image is used by a container
-		if err := srv.canDeleteImage(img.ID); err != nil {
-			return nil, err
-		}
+	byParents, err := srv.runtime.graph.ByParent()
+	if err != nil {
+		return err
 	}
 
 	//If delete by id, see if the id belong only to one repository
@@ -1917,10 +1841,10 @@ func (srv *Server) DeleteImage(name string, autoPrune bool) (*engine.Table, erro
 				if parsedTag != "" {
 					tags = append(tags, parsedTag)
 				}
-			} else if repoName != parsedRepo {
+			} else if repoName != parsedRepo && !force {
 				// the id belongs to multiple repos, like base:latest and user:test,
 				// in that case return conflict
-				return nil, fmt.Errorf("Conflict, cannot delete image %s because it is tagged in multiple repositories", utils.TruncateID(img.ID))
+				return fmt.Errorf("Conflict, cannot delete image %s because it is tagged in multiple repositories, use -f to force", name)
 			}
 		}
 	} else {
@@ -1931,37 +1855,51 @@ func (srv *Server) DeleteImage(name string, autoPrune bool) (*engine.Table, erro
 	for _, tag := range tags {
 		tagDeleted, err := srv.runtime.repositories.Delete(repoName, tag)
 		if err != nil {
-			return nil, err
+			return err
 		}
 		if tagDeleted {
 			out := &engine.Env{}
-			out.Set("Untagged", img.ID)
+			out.Set("Untagged", repoName+":"+tag)
 			imgs.Add(out)
 			srv.LogEvent("untag", img.ID, "")
 		}
 	}
-
-	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
+			err := srv.runtime.graph.Delete(img.ID)
+			if err != nil {
+				return err
 			}
+			out := &engine.Env{}
+			out.Set("Deleted", img.ID)
+			imgs.Add(out)
+			srv.LogEvent("delete", img.ID, "")
+			if img.Parent != "" {
+				err := srv.DeleteImage(img.Parent, imgs, false, force)
+				if first {
+					return err
+				}
+
+			}
+
 		}
 	}
-	return imgs, nil
+	return nil
 }
 
 func (srv *Server) ImageDelete(job *engine.Job) engine.Status {
 	if n := len(job.Args); n != 1 {
 		return job.Errorf("Usage: %s IMAGE", job.Name)
 	}
-
-	imgs, err := srv.DeleteImage(job.Args[0], job.GetenvBool("autoPrune"))
-	if err != nil {
+	var imgs = engine.NewTable("", 0)
+	if err := srv.DeleteImage(job.Args[0], imgs, true, job.GetenvBool("force")); err != nil {
 		return job.Error(err)
 	}
 	if len(imgs.Data) == 0 {