Browse code

Rewrite docker rmi

Docker-DCO-1.1-Signed-off-by: Victor Vieux <victor.vieux@docker.com> (github: vieux)

Victor Vieux authored on 2014/02/11 11:44:17
Showing 6 changed files
... ...
@@ -781,6 +781,7 @@ func (cli *DockerCli) CmdPort(args ...string) error {
781 781
 // 'docker rmi IMAGE' removes all images with the name IMAGE
782 782
 func (cli *DockerCli) CmdRmi(args ...string) error {
783 783
 	cmd := cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images")
784
+	force := cmd.Bool([]string{"f", "-force"}, false, "Force")
784 785
 	if err := cmd.Parse(args); err != nil {
785 786
 		return nil
786 787
 	}
... ...
@@ -789,9 +790,14 @@ func (cli *DockerCli) CmdRmi(args ...string) error {
789 789
 		return nil
790 790
 	}
791 791
 
792
+	v := url.Values{}
793
+	if *force {
794
+		v.Set("force", "1")
795
+	}
796
+
792 797
 	var encounteredError error
793 798
 	for _, name := range cmd.Args() {
794
-		body, _, err := readBody(cli.call("DELETE", "/images/"+name, nil, false))
799
+		body, _, err := readBody(cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, false))
795 800
 		if err != nil {
796 801
 			fmt.Fprintf(cli.err, "%s\n", err)
797 802
 			encounteredError = fmt.Errorf("Error: failed to remove one or more images")
... ...
@@ -631,7 +631,7 @@ func deleteImages(eng *engine.Engine, version float64, w http.ResponseWriter, r
631 631
 	}
632 632
 	var job = eng.Job("image_delete", vars["name"])
633 633
 	streamJSON(job, w, false)
634
-	job.SetenvBool("autoPrune", version > 1.1)
634
+	job.Setenv("force", r.Form.Get("force"))
635 635
 
636 636
 	return job.Run()
637 637
 }
... ...
@@ -1175,6 +1175,8 @@ func TestGetEnabledCors(t *testing.T) {
1175 1175
 
1176 1176
 func TestDeleteImages(t *testing.T) {
1177 1177
 	eng := NewTestEngine(t)
1178
+	//we expect errors, so we disable stderr
1179
+	eng.Stderr = ioutil.Discard
1178 1180
 	defer mkRuntimeFromEngine(eng, t).Nuke()
1179 1181
 
1180 1182
 	initialImages := getImages(eng, t, true, "")
... ...
@@ -1031,7 +1031,10 @@ func TestContainerOrphaning(t *testing.T) {
1031 1031
 	buildSomething(template2, imageName)
1032 1032
 
1033 1033
 	// remove the second image by name
1034
-	resp, err := srv.DeleteImage(imageName, true)
1034
+	resp := engine.NewTable("", 0)
1035
+	if err := srv.DeleteImage(imageName, resp, true, false); err == nil {
1036
+		t.Fatal("Expected error, got none")
1037
+	}
1035 1038
 
1036 1039
 	// see if we deleted the first image (and orphaned the container)
1037 1040
 	for _, i := range resp.Data {
... ...
@@ -35,7 +35,7 @@ func TestImageTagImageDelete(t *testing.T) {
35 35
 		t.Errorf("Expected %d images, %d found", nExpected, nActual)
36 36
 	}
37 37
 
38
-	if _, err := srv.DeleteImage("utest/docker:tag2", true); err != nil {
38
+	if err := srv.DeleteImage("utest/docker:tag2", engine.NewTable("", 0), true, false); err != nil {
39 39
 		t.Fatal(err)
40 40
 	}
41 41
 
... ...
@@ -47,7 +47,7 @@ func TestImageTagImageDelete(t *testing.T) {
47 47
 		t.Errorf("Expected %d images, %d found", nExpected, nActual)
48 48
 	}
49 49
 
50
-	if _, err := srv.DeleteImage("utest:5000/docker:tag3", true); err != nil {
50
+	if err := srv.DeleteImage("utest:5000/docker:tag3", engine.NewTable("", 0), true, false); err != nil {
51 51
 		t.Fatal(err)
52 52
 	}
53 53
 
... ...
@@ -56,7 +56,7 @@ func TestImageTagImageDelete(t *testing.T) {
56 56
 	nExpected = len(initialImages.Data[0].GetList("RepoTags")) + 1
57 57
 	nActual = len(images.Data[0].GetList("RepoTags"))
58 58
 
59
-	if _, err := srv.DeleteImage("utest:tag1", true); err != nil {
59
+	if err := srv.DeleteImage("utest:tag1", engine.NewTable("", 0), true, false); err != nil {
60 60
 		t.Fatal(err)
61 61
 	}
62 62
 
... ...
@@ -447,8 +447,7 @@ func TestRmi(t *testing.T) {
447 447
 		t.Fatalf("Expected 2 new images, found %d.", images.Len()-initialImages.Len())
448 448
 	}
449 449
 
450
-	_, err = srv.DeleteImage(imageID, true)
451
-	if err != nil {
450
+	if err = srv.DeleteImage(imageID, engine.NewTable("", 0), true, false); err != nil {
452 451
 		t.Fatal(err)
453 452
 	}
454 453
 
... ...
@@ -683,8 +682,8 @@ func TestDeleteTagWithExistingContainers(t *testing.T) {
683 683
 	}
684 684
 
685 685
 	// Try to remove the tag
686
-	imgs, err := srv.DeleteImage("utest:tag1", true)
687
-	if err != nil {
686
+	imgs := engine.NewTable("", 0)
687
+	if err := srv.DeleteImage("utest:tag1", imgs, true, false); err != nil {
688 688
 		t.Fatal(err)
689 689
 	}
690 690
 
... ...
@@ -2,7 +2,6 @@ package docker
2 2
 
3 3
 import (
4 4
 	"encoding/json"
5
-	"errors"
6 5
 	"fmt"
7 6
 	"github.com/dotcloud/docker/archive"
8 7
 	"github.com/dotcloud/docker/auth"
... ...
@@ -1810,102 +1809,27 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status {
1810 1810
 	return engine.StatusOK
1811 1811
 }
1812 1812
 
1813
-var ErrImageReferenced = errors.New("Image referenced by a repository")
1814
-
1815
-func (srv *Server) deleteImageAndChildren(id string, imgs *engine.Table, byParents map[string][]*Image) error {
1816
-	// If the image is referenced by a repo, do not delete
1817
-	if len(srv.runtime.repositories.ByID()[id]) != 0 {
1818
-		return ErrImageReferenced
1819
-	}
1820
-	// If the image is not referenced but has children, go recursive
1821
-	referenced := false
1822
-	for _, img := range byParents[id] {
1823
-		if err := srv.deleteImageAndChildren(img.ID, imgs, byParents); err != nil {
1824
-			if err != ErrImageReferenced {
1825
-				return err
1826
-			}
1827
-			referenced = true
1828
-		}
1829
-	}
1830
-	if referenced {
1831
-		return ErrImageReferenced
1832
-	}
1833
-
1834
-	// If the image is not referenced and has no children, remove it
1835
-	byParents, err := srv.runtime.graph.ByParent()
1836
-	if err != nil {
1837
-		return err
1838
-	}
1839
-	if len(byParents[id]) == 0 && srv.canDeleteImage(id) == nil {
1840
-		if err := srv.runtime.repositories.DeleteAll(id); err != nil {
1841
-			return err
1842
-		}
1843
-		err := srv.runtime.graph.Delete(id)
1844
-		if err != nil {
1845
-			return err
1846
-		}
1847
-		out := &engine.Env{}
1848
-		out.Set("Deleted", id)
1849
-		imgs.Add(out)
1850
-		srv.LogEvent("delete", id, "")
1851
-		return nil
1852
-	}
1853
-	return nil
1854
-}
1855
-
1856
-func (srv *Server) deleteImageParents(img *Image, imgs *engine.Table) error {
1857
-	if img.Parent != "" {
1858
-		parent, err := srv.runtime.graph.Get(img.Parent)
1859
-		if err != nil {
1860
-			return err
1861
-		}
1862
-		byParents, err := srv.runtime.graph.ByParent()
1863
-		if err != nil {
1864
-			return err
1865
-		}
1866
-		// Remove all children images
1867
-		if err := srv.deleteImageAndChildren(img.Parent, imgs, byParents); err != nil {
1868
-			return err
1869
-		}
1870
-		return srv.deleteImageParents(parent, imgs)
1871
-	}
1872
-	return nil
1873
-}
1874
-
1875
-func (srv *Server) DeleteImage(name string, autoPrune bool) (*engine.Table, error) {
1813
+func (srv *Server) DeleteImage(name string, imgs *engine.Table, first, force bool) error {
1876 1814
 	var (
1877 1815
 		repoName, tag string
1878 1816
 		img, err      = srv.runtime.repositories.LookupImage(name)
1879
-		imgs          = engine.NewTable("", 0)
1880 1817
 		tags          = []string{}
1881 1818
 	)
1882 1819
 
1883 1820
 	if err != nil {
1884
-		return nil, fmt.Errorf("No such image: %s", name)
1885
-	}
1886
-
1887
-	// FIXME: What does autoPrune mean ?
1888
-	if !autoPrune {
1889
-		if err := srv.runtime.graph.Delete(img.ID); err != nil {
1890
-			return nil, fmt.Errorf("Cannot delete image %s: %s", name, err)
1891
-		}
1892
-		return nil, nil
1821
+		return fmt.Errorf("No such image: %s", name)
1893 1822
 	}
1894 1823
 
1895 1824
 	if !strings.Contains(img.ID, name) {
1896 1825
 		repoName, tag = utils.ParseRepositoryTag(name)
1826
+		if tag == "" {
1827
+			tag = DEFAULTTAG
1828
+		}
1897 1829
 	}
1898 1830
 
1899
-	// If we have a repo and the image is not referenced anywhere else
1900
-	// then just perform an untag and do not validate.
1901
-	//
1902
-	// i.e. only validate if we are performing an actual delete and not
1903
-	// an untag op
1904
-	if repoName != "" && len(srv.runtime.repositories.ByID()[img.ID]) == 1 {
1905
-		// Prevent deletion if image is used by a container
1906
-		if err := srv.canDeleteImage(img.ID); err != nil {
1907
-			return nil, err
1908
-		}
1831
+	byParents, err := srv.runtime.graph.ByParent()
1832
+	if err != nil {
1833
+		return err
1909 1834
 	}
1910 1835
 
1911 1836
 	//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
1917 1917
 				if parsedTag != "" {
1918 1918
 					tags = append(tags, parsedTag)
1919 1919
 				}
1920
-			} else if repoName != parsedRepo {
1920
+			} else if repoName != parsedRepo && !force {
1921 1921
 				// the id belongs to multiple repos, like base:latest and user:test,
1922 1922
 				// in that case return conflict
1923
-				return nil, fmt.Errorf("Conflict, cannot delete image %s because it is tagged in multiple repositories", utils.TruncateID(img.ID))
1923
+				return fmt.Errorf("Conflict, cannot delete image %s because it is tagged in multiple repositories, use -f to force", name)
1924 1924
 			}
1925 1925
 		}
1926 1926
 	} else {
... ...
@@ -1931,37 +1855,51 @@ func (srv *Server) DeleteImage(name string, autoPrune bool) (*engine.Table, erro
1931 1931
 	for _, tag := range tags {
1932 1932
 		tagDeleted, err := srv.runtime.repositories.Delete(repoName, tag)
1933 1933
 		if err != nil {
1934
-			return nil, err
1934
+			return err
1935 1935
 		}
1936 1936
 		if tagDeleted {
1937 1937
 			out := &engine.Env{}
1938
-			out.Set("Untagged", img.ID)
1938
+			out.Set("Untagged", repoName+":"+tag)
1939 1939
 			imgs.Add(out)
1940 1940
 			srv.LogEvent("untag", img.ID, "")
1941 1941
 		}
1942 1942
 	}
1943
-
1944
-	if len(srv.runtime.repositories.ByID()[img.ID]) == 0 {
1945
-		if err := srv.deleteImageAndChildren(img.ID, imgs, nil); err != nil {
1946
-			if err != ErrImageReferenced {
1947
-				return imgs, err
1943
+	tags = srv.runtime.repositories.ByID()[img.ID]
1944
+	if (len(tags) <= 1 && repoName == "") || len(tags) == 0 {
1945
+		if len(byParents[img.ID]) == 0 {
1946
+			if err := srv.canDeleteImage(img.ID); err != nil {
1947
+				return err
1948
+			}
1949
+			if err := srv.runtime.repositories.DeleteAll(img.ID); err != nil {
1950
+				return err
1948 1951
 			}
1949
-		} else if err := srv.deleteImageParents(img, imgs); err != nil {
1950
-			if err != ErrImageReferenced {
1951
-				return imgs, err
1952
+			err := srv.runtime.graph.Delete(img.ID)
1953
+			if err != nil {
1954
+				return err
1952 1955
 			}
1956
+			out := &engine.Env{}
1957
+			out.Set("Deleted", img.ID)
1958
+			imgs.Add(out)
1959
+			srv.LogEvent("delete", img.ID, "")
1960
+			if img.Parent != "" {
1961
+				err := srv.DeleteImage(img.Parent, imgs, false, force)
1962
+				if first {
1963
+					return err
1964
+				}
1965
+
1966
+			}
1967
+
1953 1968
 		}
1954 1969
 	}
1955
-	return imgs, nil
1970
+	return nil
1956 1971
 }
1957 1972
 
1958 1973
 func (srv *Server) ImageDelete(job *engine.Job) engine.Status {
1959 1974
 	if n := len(job.Args); n != 1 {
1960 1975
 		return job.Errorf("Usage: %s IMAGE", job.Name)
1961 1976
 	}
1962
-
1963
-	imgs, err := srv.DeleteImage(job.Args[0], job.GetenvBool("autoPrune"))
1964
-	if err != nil {
1977
+	var imgs = engine.NewTable("", 0)
1978
+	if err := srv.DeleteImage(job.Args[0], imgs, true, job.GetenvBool("force")); err != nil {
1965 1979
 		return job.Error(err)
1966 1980
 	}
1967 1981
 	if len(imgs.Data) == 0 {