Docker-DCO-1.1-Signed-off-by: Victor Vieux <victor.vieux@docker.com> (github: vieux)
| ... | ... |
@@ -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 {
|