docker_cli_rmi_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "strings"
  6. "testing"
  7. "time"
  8. "github.com/docker/docker/integration-cli/cli"
  9. "github.com/docker/docker/integration-cli/cli/build"
  10. "github.com/docker/docker/pkg/stringid"
  11. "gotest.tools/v3/assert"
  12. "gotest.tools/v3/icmd"
  13. "gotest.tools/v3/skip"
  14. )
  15. type DockerCLIRmiSuite struct {
  16. ds *DockerSuite
  17. }
  18. func (s *DockerCLIRmiSuite) TearDownTest(ctx context.Context, c *testing.T) {
  19. s.ds.TearDownTest(ctx, c)
  20. }
  21. func (s *DockerCLIRmiSuite) OnTimeout(c *testing.T) {
  22. s.ds.OnTimeout(c)
  23. }
  24. func (s *DockerCLIRmiSuite) TestRmiWithContainerFails(c *testing.T) {
  25. errSubstr := "is using it"
  26. // create a container
  27. cID := cli.DockerCmd(c, "run", "-d", "busybox", "true").Stdout()
  28. cID = strings.TrimSpace(cID)
  29. // try to delete the image
  30. out, _, err := dockerCmdWithError("rmi", "busybox")
  31. // Container is using image, should not be able to rmi
  32. assert.ErrorContains(c, err, "")
  33. // Container is using image, error message should contain errSubstr
  34. assert.Assert(c, strings.Contains(out, errSubstr), "Container: %q", cID)
  35. // make sure it didn't delete the busybox name
  36. images := cli.DockerCmd(c, "images").Stdout()
  37. // The name 'busybox' should not have been removed from images
  38. assert.Assert(c, strings.Contains(images, "busybox"))
  39. }
  40. func (s *DockerCLIRmiSuite) TestRmiTag(c *testing.T) {
  41. imagesBefore := cli.DockerCmd(c, "images", "-a").Stdout()
  42. cli.DockerCmd(c, "tag", "busybox", "utest:tag1")
  43. cli.DockerCmd(c, "tag", "busybox", "utest/docker:tag2")
  44. cli.DockerCmd(c, "tag", "busybox", "utest:5000/docker:tag3")
  45. {
  46. imagesAfter := cli.DockerCmd(c, "images", "-a").Stdout()
  47. assert.Equal(c, strings.Count(imagesAfter, "\n"), strings.Count(imagesBefore, "\n")+3, fmt.Sprintf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter))
  48. }
  49. cli.DockerCmd(c, "rmi", "utest/docker:tag2")
  50. {
  51. imagesAfter := cli.DockerCmd(c, "images", "-a").Stdout()
  52. assert.Equal(c, strings.Count(imagesAfter, "\n"), strings.Count(imagesBefore, "\n")+2, fmt.Sprintf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter))
  53. }
  54. cli.DockerCmd(c, "rmi", "utest:5000/docker:tag3")
  55. {
  56. imagesAfter := cli.DockerCmd(c, "images", "-a").Stdout()
  57. assert.Equal(c, strings.Count(imagesAfter, "\n"), strings.Count(imagesBefore, "\n")+1, fmt.Sprintf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter))
  58. }
  59. cli.DockerCmd(c, "rmi", "utest:tag1")
  60. {
  61. imagesAfter := cli.DockerCmd(c, "images", "-a").Stdout()
  62. assert.Equal(c, strings.Count(imagesAfter, "\n"), strings.Count(imagesBefore, "\n"), fmt.Sprintf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter))
  63. }
  64. }
  65. func (s *DockerCLIRmiSuite) TestRmiImgIDMultipleTag(c *testing.T) {
  66. cID := cli.DockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir '/busybox-one'").Combined()
  67. cID = strings.TrimSpace(cID)
  68. // Wait for it to exit as cannot commit a running container on Windows, and
  69. // it will take a few seconds to exit
  70. if testEnv.DaemonInfo.OSType == "windows" {
  71. cli.WaitExited(c, cID, 60*time.Second)
  72. }
  73. cli.DockerCmd(c, "commit", cID, "busybox-one")
  74. imagesBefore := cli.DockerCmd(c, "images", "-a").Combined()
  75. cli.DockerCmd(c, "tag", "busybox-one", "busybox-one:tag1")
  76. cli.DockerCmd(c, "tag", "busybox-one", "busybox-one:tag2")
  77. imagesAfter := cli.DockerCmd(c, "images", "-a").Combined()
  78. // tag busybox to create 2 more images with same imageID
  79. assert.Equal(c, strings.Count(imagesAfter, "\n"), strings.Count(imagesBefore, "\n")+2, fmt.Sprintf("docker images shows: %q\n", imagesAfter))
  80. imgID := inspectField(c, "busybox-one:tag1", "Id")
  81. // run a container with the image
  82. cID = runSleepingContainerInImage(c, "busybox-one")
  83. cID = strings.TrimSpace(cID)
  84. // first checkout without force it fails
  85. // rmi tagged in multiple repos should have failed without force
  86. cli.Docker(cli.Args("rmi", imgID)).Assert(c, icmd.Expected{
  87. ExitCode: 1,
  88. Err: fmt.Sprintf("conflict: unable to delete %s (cannot be forced) - image is being used by running container %s", stringid.TruncateID(imgID), stringid.TruncateID(cID)),
  89. })
  90. cli.DockerCmd(c, "stop", cID)
  91. cli.DockerCmd(c, "rmi", "-f", imgID)
  92. imagesAfter = cli.DockerCmd(c, "images", "-a").Combined()
  93. // rmi -f failed, image still exists
  94. assert.Assert(c, !strings.Contains(imagesAfter, imgID[:12]), "ImageID:%q; ImagesAfter: %q", imgID, imagesAfter)
  95. }
  96. func (s *DockerCLIRmiSuite) TestRmiImgIDForce(c *testing.T) {
  97. cID := cli.DockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir '/busybox-test'").Combined()
  98. cID = strings.TrimSpace(cID)
  99. // Wait for it to exit as cannot commit a running container on Windows, and
  100. // it will take a few seconds to exit
  101. if testEnv.DaemonInfo.OSType == "windows" {
  102. cli.WaitExited(c, cID, 60*time.Second)
  103. }
  104. cli.DockerCmd(c, "commit", cID, "busybox-test")
  105. imagesBefore := cli.DockerCmd(c, "images", "-a").Combined()
  106. cli.DockerCmd(c, "tag", "busybox-test", "utest:tag1")
  107. cli.DockerCmd(c, "tag", "busybox-test", "utest:tag2")
  108. cli.DockerCmd(c, "tag", "busybox-test", "utest/docker:tag3")
  109. cli.DockerCmd(c, "tag", "busybox-test", "utest:5000/docker:tag4")
  110. {
  111. imagesAfter := cli.DockerCmd(c, "images", "-a").Combined()
  112. assert.Equal(c, strings.Count(imagesAfter, "\n"), strings.Count(imagesBefore, "\n")+4, fmt.Sprintf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter))
  113. }
  114. imgID := inspectField(c, "busybox-test", "Id")
  115. // first checkout without force it fails
  116. cli.Docker(cli.Args("rmi", imgID)).Assert(c, icmd.Expected{
  117. ExitCode: 1,
  118. Err: "(must be forced) - image is referenced in multiple repositories",
  119. })
  120. cli.DockerCmd(c, "rmi", "-f", imgID)
  121. {
  122. imagesAfter := cli.DockerCmd(c, "images", "-a").Combined()
  123. // rmi failed, image still exists
  124. assert.Assert(c, !strings.Contains(imagesAfter, imgID[:12]))
  125. }
  126. }
  127. // See https://github.com/docker/docker/issues/14116
  128. func (s *DockerCLIRmiSuite) TestRmiImageIDForceWithRunningContainersAndMultipleTags(c *testing.T) {
  129. dockerfile := "FROM busybox\nRUN echo test 14116\n"
  130. buildImageSuccessfully(c, "test-14116", build.WithDockerfile(dockerfile))
  131. imgID := getIDByName(c, "test-14116")
  132. newTag := "newtag"
  133. cli.DockerCmd(c, "tag", imgID, newTag)
  134. runSleepingContainerInImage(c, imgID)
  135. out, _, err := dockerCmdWithError("rmi", "-f", imgID)
  136. // rmi -f should not delete image with running containers
  137. assert.ErrorContains(c, err, "")
  138. assert.Assert(c, strings.Contains(out, "(cannot be forced) - image is being used by running container"))
  139. }
  140. func (s *DockerCLIRmiSuite) TestRmiTagWithExistingContainers(c *testing.T) {
  141. container := "test-delete-tag"
  142. newtag := "busybox:newtag"
  143. bb := "busybox:latest"
  144. cli.DockerCmd(c, "tag", bb, newtag)
  145. cli.DockerCmd(c, "run", "--name", container, bb, "/bin/true")
  146. out := cli.DockerCmd(c, "rmi", newtag).Combined()
  147. assert.Equal(c, strings.Count(out, "Untagged: "), 1)
  148. }
  149. func (s *DockerCLIRmiSuite) TestRmiForceWithExistingContainers(c *testing.T) {
  150. const imgName = "busybox-clone"
  151. icmd.RunCmd(icmd.Cmd{
  152. Command: []string{dockerBinary, "build", "--no-cache", "-t", imgName, "-"},
  153. Stdin: strings.NewReader(`FROM busybox
  154. MAINTAINER foo`),
  155. }).Assert(c, icmd.Success)
  156. cli.DockerCmd(c, "run", "--name", "test-force-rmi", imgName, "/bin/true")
  157. cli.DockerCmd(c, "rmi", "-f", imgName)
  158. }
  159. func (s *DockerCLIRmiSuite) TestRmiWithMultipleRepositories(c *testing.T) {
  160. newRepo := "127.0.0.1:5000/busybox"
  161. oldRepo := "busybox"
  162. newTag := "busybox:test"
  163. cli.DockerCmd(c, "tag", oldRepo, newRepo)
  164. cli.DockerCmd(c, "run", "--name", "test", oldRepo, "touch", "/abcd")
  165. cli.DockerCmd(c, "commit", "test", newTag)
  166. out := cli.DockerCmd(c, "rmi", newTag).Combined()
  167. assert.Assert(c, strings.Contains(out, "Untagged: "+newTag))
  168. }
  169. func (s *DockerCLIRmiSuite) TestRmiForceWithMultipleRepositories(c *testing.T) {
  170. imageName := "rmiimage"
  171. tag1 := imageName + ":tag1"
  172. tag2 := imageName + ":tag2"
  173. buildImageSuccessfully(c, tag1, build.WithDockerfile(`FROM busybox
  174. MAINTAINER "docker"`))
  175. cli.DockerCmd(c, "tag", tag1, tag2)
  176. out := cli.DockerCmd(c, "rmi", "-f", tag2).Combined()
  177. assert.Assert(c, strings.Contains(out, "Untagged: "+tag2))
  178. assert.Assert(c, !strings.Contains(out, "Untagged: "+tag1))
  179. // Check built image still exists
  180. images := cli.DockerCmd(c, "images", "-a").Stdout()
  181. assert.Assert(c, strings.Contains(images, imageName), "Built image missing %q; Images: %q", imageName, images)
  182. }
  183. func (s *DockerCLIRmiSuite) TestRmiBlank(c *testing.T) {
  184. out, _, err := dockerCmdWithError("rmi", " ")
  185. // Should have failed to delete ' ' image
  186. assert.ErrorContains(c, err, "")
  187. // Wrong error message generated
  188. assert.Assert(c, !strings.Contains(out, "no such id"), "out: %s", out)
  189. // Expected error message not generated
  190. assert.Assert(c, strings.Contains(out, "image name cannot be blank"), "out: %s", out)
  191. }
  192. func (s *DockerCLIRmiSuite) TestRmiContainerImageNotFound(c *testing.T) {
  193. // Build 2 images for testing.
  194. imageNames := []string{"test1", "test2"}
  195. imageIds := make([]string, 2)
  196. for i, name := range imageNames {
  197. dockerfile := fmt.Sprintf("FROM busybox\nMAINTAINER %s\nRUN echo %s\n", name, name)
  198. buildImageSuccessfully(c, name, build.WithoutCache, build.WithDockerfile(dockerfile))
  199. id := getIDByName(c, name)
  200. imageIds[i] = id
  201. }
  202. // Create a long-running container.
  203. runSleepingContainerInImage(c, imageNames[0])
  204. // Create a stopped container, and then force remove its image.
  205. cli.DockerCmd(c, "run", imageNames[1], "true")
  206. cli.DockerCmd(c, "rmi", "-f", imageIds[1])
  207. // Try to remove the image of the running container and see if it fails as expected.
  208. out, _, err := dockerCmdWithError("rmi", "-f", imageIds[0])
  209. // The image of the running container should not be removed.
  210. assert.ErrorContains(c, err, "")
  211. assert.Assert(c, strings.Contains(out, "image is being used by running container"), "out: %s", out)
  212. }
  213. // #13422
  214. func (s *DockerCLIRmiSuite) TestRmiUntagHistoryLayer(c *testing.T) {
  215. const imgName = "tmp1"
  216. // Build an image for testing.
  217. dockerfile := `FROM busybox
  218. MAINTAINER foo
  219. RUN echo 0 #layer0
  220. RUN echo 1 #layer1
  221. RUN echo 2 #layer2
  222. `
  223. buildImageSuccessfully(c, imgName, build.WithoutCache, build.WithDockerfile(dockerfile))
  224. out := cli.DockerCmd(c, "history", "-q", imgName).Stdout()
  225. ids := strings.Split(out, "\n")
  226. idToTag := ids[2]
  227. // Tag layer0 to "tmp2".
  228. newTag := "tmp2"
  229. cli.DockerCmd(c, "tag", idToTag, newTag)
  230. // Create a container based on "tmp1".
  231. cli.DockerCmd(c, "run", "-d", imgName, "true")
  232. // See if the "tmp2" can be untagged.
  233. out = cli.DockerCmd(c, "rmi", newTag).Combined()
  234. // Expected 1 untagged entry
  235. assert.Equal(c, strings.Count(out, "Untagged: "), 1, fmt.Sprintf("out: %s", out))
  236. // Now let's add the tag again and create a container based on it.
  237. cli.DockerCmd(c, "tag", idToTag, newTag)
  238. cID := cli.DockerCmd(c, "run", "-d", newTag, "true").Stdout()
  239. cID = strings.TrimSpace(cID)
  240. // At this point we have 2 containers, one based on layer2 and another based on layer0.
  241. // Try to untag "tmp2" without the -f flag.
  242. out, _, err := dockerCmdWithError("rmi", newTag)
  243. // should not be untagged without the -f flag
  244. assert.ErrorContains(c, err, "")
  245. assert.Assert(c, strings.Contains(out, cID[:12]))
  246. assert.Assert(c, strings.Contains(out, "(must force)") || strings.Contains(out, "(must be forced)"))
  247. // Add the -f flag and test again.
  248. out = cli.DockerCmd(c, "rmi", "-f", newTag).Combined()
  249. // should be allowed to untag with the -f flag
  250. assert.Assert(c, strings.Contains(out, fmt.Sprintf("Untagged: %s:latest", newTag)))
  251. }
  252. func (*DockerCLIRmiSuite) TestRmiParentImageFail(c *testing.T) {
  253. skip.If(c, testEnv.UsingSnapshotter(), "image are independent when using the containerd image store")
  254. buildImageSuccessfully(c, "test", build.WithDockerfile(`
  255. FROM busybox
  256. RUN echo hello`))
  257. id := inspectField(c, "busybox", "ID")
  258. out, _, err := dockerCmdWithError("rmi", id)
  259. assert.ErrorContains(c, err, "")
  260. if !strings.Contains(out, "image has dependent child images") {
  261. c.Fatalf("rmi should have failed because it's a parent image, got %s", out)
  262. }
  263. }
  264. func (s *DockerCLIRmiSuite) TestRmiWithParentInUse(c *testing.T) {
  265. cID := cli.DockerCmd(c, "create", "busybox").Stdout()
  266. cID = strings.TrimSpace(cID)
  267. imageID := cli.DockerCmd(c, "commit", cID).Stdout()
  268. imageID = strings.TrimSpace(imageID)
  269. cID = cli.DockerCmd(c, "create", imageID).Stdout()
  270. cID = strings.TrimSpace(cID)
  271. imageID = cli.DockerCmd(c, "commit", cID).Stdout()
  272. imageID = strings.TrimSpace(imageID)
  273. cli.DockerCmd(c, "rmi", imageID)
  274. }
  275. // #18873
  276. func (s *DockerCLIRmiSuite) TestRmiByIDHardConflict(c *testing.T) {
  277. cli.DockerCmd(c, "create", "busybox")
  278. imgID := inspectField(c, "busybox:latest", "Id")
  279. _, _, err := dockerCmdWithError("rmi", imgID[:12])
  280. assert.ErrorContains(c, err, "")
  281. // check that tag was not removed
  282. imgID2 := inspectField(c, "busybox:latest", "Id")
  283. assert.Equal(c, imgID, imgID2)
  284. }