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