Browse code

Merge pull request #3304 from vieux/prevent_orphan_deletion

Prevent orphan in docker rmi

Victor Vieux authored on 2013/12/24 08:44:46
Showing 2 changed files
... ...
@@ -968,3 +968,66 @@ func TestRunCidFile(t *testing.T) {
968 968
 	})
969 969
 
970 970
 }
971
+
972
+func TestContainerOrphaning(t *testing.T) {
973
+
974
+	// setup a temporary directory
975
+	tmpDir, err := ioutil.TempDir("", "project")
976
+	if err != nil {
977
+		t.Fatal(err)
978
+	}
979
+	defer os.RemoveAll(tmpDir)
980
+
981
+	// setup a CLI and server
982
+	cli := docker.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr)
983
+	defer cleanup(globalEngine, t)
984
+	srv := mkServerFromEngine(globalEngine, t)
985
+
986
+	// closure to build something
987
+	buildSomething := func(template string, image string) string {
988
+		dockerfile := path.Join(tmpDir, "Dockerfile")
989
+		replacer := strings.NewReplacer("{IMAGE}", unitTestImageID)
990
+		contents := replacer.Replace(template)
991
+		ioutil.WriteFile(dockerfile, []byte(contents), 0x777)
992
+		if err := cli.CmdBuild("-t", image, tmpDir); err != nil {
993
+			t.Fatal(err)
994
+		}
995
+		img, err := srv.ImageInspect(image)
996
+		if err != nil {
997
+			t.Fatal(err)
998
+		}
999
+		return img.ID
1000
+	}
1001
+
1002
+	// build an image
1003
+	imageName := "orphan-test"
1004
+	template1 := `
1005
+	from {IMAGE}
1006
+	cmd ["/bin/echo", "holla"]
1007
+	`
1008
+	img1 := buildSomething(template1, imageName)
1009
+
1010
+	// create a container using the fist image
1011
+	if err := cli.CmdRun(imageName); err != nil {
1012
+		t.Fatal(err)
1013
+	}
1014
+
1015
+	// build a new image that splits lineage
1016
+	template2 := `
1017
+	from {IMAGE}
1018
+	cmd ["/bin/echo", "holla"]
1019
+	expose 22
1020
+	`
1021
+	buildSomething(template2, imageName)
1022
+
1023
+	// remove the second image by name
1024
+	resp, err := srv.ImageDelete(imageName, true)
1025
+
1026
+	// see if we deleted the first image (and orphaned the container)
1027
+	for _, i := range resp {
1028
+		if img1 == i.Deleted {
1029
+			t.Fatal("Orphaned image with container")
1030
+		}
1031
+	}
1032
+
1033
+}
... ...
@@ -1543,7 +1543,7 @@ func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi, byParents m
1543 1543
 	if err != nil {
1544 1544
 		return err
1545 1545
 	}
1546
-	if len(byParents[id]) == 0 {
1546
+	if len(byParents[id]) == 0 && srv.canDeleteImage(id) == nil {
1547 1547
 		if err := srv.runtime.repositories.DeleteAll(id); err != nil {
1548 1548
 			return err
1549 1549
 		}
... ...
@@ -1631,9 +1631,8 @@ func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, erro
1631 1631
 func (srv *Server) ImageDelete(name string, autoPrune bool) ([]APIRmi, error) {
1632 1632
 	var (
1633 1633
 		repository, tag string
1634
-		validate        = true
1634
+		img, err        = srv.runtime.repositories.LookupImage(name)
1635 1635
 	)
1636
-	img, err := srv.runtime.repositories.LookupImage(name)
1637 1636
 	if err != nil {
1638 1637
 		return nil, fmt.Errorf("No such image: %s", name)
1639 1638
 	}
... ...
@@ -1655,29 +1654,32 @@ func (srv *Server) ImageDelete(name string, autoPrune bool) ([]APIRmi, error) {
1655 1655
 	//
1656 1656
 	// i.e. only validate if we are performing an actual delete and not
1657 1657
 	// an untag op
1658
-	if repository != "" {
1659
-		validate = len(srv.runtime.repositories.ByID()[img.ID]) == 1
1658
+	if repository != "" && len(srv.runtime.repositories.ByID()[img.ID]) == 1 {
1659
+		// Prevent deletion if image is used by a container
1660
+		if err := srv.canDeleteImage(img.ID); err != nil {
1661
+			return nil, err
1662
+		}
1660 1663
 	}
1664
+	return srv.deleteImage(img, repository, tag)
1665
+}
1661 1666
 
1662
-	if validate {
1663
-		// Prevent deletion if image is used by a container
1664
-		for _, container := range srv.runtime.List() {
1665
-			parent, err := srv.runtime.repositories.LookupImage(container.Image)
1666
-			if err != nil {
1667
-				return nil, err
1668
-			}
1667
+func (srv *Server) canDeleteImage(imgID string) error {
1668
+	for _, container := range srv.runtime.List() {
1669
+		parent, err := srv.runtime.repositories.LookupImage(container.Image)
1670
+		if err != nil {
1671
+			return err
1672
+		}
1669 1673
 
1670
-			if err := parent.WalkHistory(func(p *Image) error {
1671
-				if img.ID == p.ID {
1672
-					return fmt.Errorf("Conflict, cannot delete %s because the container %s is using it", name, container.ID)
1673
-				}
1674
-				return nil
1675
-			}); err != nil {
1676
-				return nil, err
1674
+		if err := parent.WalkHistory(func(p *Image) error {
1675
+			if imgID == p.ID {
1676
+				return fmt.Errorf("Conflict, cannot delete %s because the container %s is using it", utils.TruncateID(imgID), utils.TruncateID(container.ID))
1677 1677
 			}
1678
+			return nil
1679
+		}); err != nil {
1680
+			return err
1678 1681
 		}
1679 1682
 	}
1680
-	return srv.deleteImage(img, repository, tag)
1683
+	return nil
1681 1684
 }
1682 1685
 
1683 1686
 func (srv *Server) ImageGetCached(imgID string, config *Config) (*Image, error) {