github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/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  }