github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/integration-cli/docker_cli_volume_test.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"os/exec"
     8  	"path/filepath"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/Prakhar-Agarwal-byte/moby/api/types/container"
    13  	"github.com/Prakhar-Agarwal-byte/moby/api/types/mount"
    14  	"github.com/Prakhar-Agarwal-byte/moby/api/types/network"
    15  	"github.com/Prakhar-Agarwal-byte/moby/client"
    16  	"github.com/Prakhar-Agarwal-byte/moby/integration-cli/cli"
    17  	"github.com/Prakhar-Agarwal-byte/moby/integration-cli/cli/build"
    18  	"github.com/Prakhar-Agarwal-byte/moby/testutil"
    19  	"gotest.tools/v3/assert"
    20  	"gotest.tools/v3/icmd"
    21  )
    22  
    23  type DockerCLIVolumeSuite struct {
    24  	ds *DockerSuite
    25  }
    26  
    27  func (s *DockerCLIVolumeSuite) TearDownTest(ctx context.Context, c *testing.T) {
    28  	s.ds.TearDownTest(ctx, c)
    29  }
    30  
    31  func (s *DockerCLIVolumeSuite) OnTimeout(c *testing.T) {
    32  	s.ds.OnTimeout(c)
    33  }
    34  
    35  func (s *DockerCLIVolumeSuite) TestVolumeCLICreate(c *testing.T) {
    36  	cli.DockerCmd(c, "volume", "create")
    37  
    38  	_, _, err := dockerCmdWithError("volume", "create", "-d", "nosuchdriver")
    39  	assert.ErrorContains(c, err, "")
    40  
    41  	// test using hidden --name option
    42  	name := cli.DockerCmd(c, "volume", "create", "--name=test").Stdout()
    43  	name = strings.TrimSpace(name)
    44  	assert.Equal(c, name, "test")
    45  
    46  	name = cli.DockerCmd(c, "volume", "create", "test2").Stdout()
    47  	name = strings.TrimSpace(name)
    48  	assert.Equal(c, name, "test2")
    49  }
    50  
    51  func (s *DockerCLIVolumeSuite) TestVolumeCLIInspect(c *testing.T) {
    52  	assert.Assert(c, exec.Command(dockerBinary, "volume", "inspect", "doesnotexist").Run() != nil, "volume inspect should error on non-existent volume")
    53  	name := cli.DockerCmd(c, "volume", "create").Stdout()
    54  	name = strings.TrimSpace(name)
    55  	out := cli.DockerCmd(c, "volume", "inspect", "--format={{ .Name }}", name).Stdout()
    56  	assert.Equal(c, strings.TrimSpace(out), name)
    57  
    58  	cli.DockerCmd(c, "volume", "create", "test")
    59  	out = cli.DockerCmd(c, "volume", "inspect", "--format={{ .Name }}", "test").Stdout()
    60  	assert.Equal(c, strings.TrimSpace(out), "test")
    61  }
    62  
    63  func (s *DockerCLIVolumeSuite) TestVolumeCLIInspectMulti(c *testing.T) {
    64  	cli.DockerCmd(c, "volume", "create", "test1")
    65  	cli.DockerCmd(c, "volume", "create", "test2")
    66  	cli.DockerCmd(c, "volume", "create", "test3")
    67  
    68  	result := dockerCmdWithResult("volume", "inspect", "--format={{ .Name }}", "test1", "test2", "doesnotexist", "test3")
    69  	result.Assert(c, icmd.Expected{
    70  		ExitCode: 1,
    71  		Err:      "No such volume: doesnotexist",
    72  	})
    73  
    74  	out := result.Stdout()
    75  	assert.Assert(c, strings.Contains(out, "test1"))
    76  	assert.Assert(c, strings.Contains(out, "test2"))
    77  	assert.Assert(c, strings.Contains(out, "test3"))
    78  }
    79  
    80  func (s *DockerCLIVolumeSuite) TestVolumeCLILs(c *testing.T) {
    81  	prefix, _ := getPrefixAndSlashFromDaemonPlatform()
    82  	cli.DockerCmd(c, "volume", "create", "aaa")
    83  
    84  	cli.DockerCmd(c, "volume", "create", "test")
    85  
    86  	cli.DockerCmd(c, "volume", "create", "soo")
    87  	cli.DockerCmd(c, "run", "-v", "soo:"+prefix+"/foo", "busybox", "ls", "/")
    88  
    89  	out := cli.DockerCmd(c, "volume", "ls", "-q").Stdout()
    90  	assertVolumesInList(c, out, []string{"aaa", "soo", "test"})
    91  }
    92  
    93  func (s *DockerCLIVolumeSuite) TestVolumeLsFormat(c *testing.T) {
    94  	cli.DockerCmd(c, "volume", "create", "aaa")
    95  	cli.DockerCmd(c, "volume", "create", "test")
    96  	cli.DockerCmd(c, "volume", "create", "soo")
    97  
    98  	out := cli.DockerCmd(c, "volume", "ls", "--format", "{{.Name}}").Stdout()
    99  	assertVolumesInList(c, out, []string{"aaa", "soo", "test"})
   100  }
   101  
   102  func (s *DockerCLIVolumeSuite) TestVolumeLsFormatDefaultFormat(c *testing.T) {
   103  	cli.DockerCmd(c, "volume", "create", "aaa")
   104  	cli.DockerCmd(c, "volume", "create", "test")
   105  	cli.DockerCmd(c, "volume", "create", "soo")
   106  
   107  	const config = `{
   108  		"volumesFormat": "{{ .Name }} default"
   109  }`
   110  	d, err := os.MkdirTemp("", "integration-cli-")
   111  	assert.NilError(c, err)
   112  	defer os.RemoveAll(d)
   113  
   114  	err = os.WriteFile(filepath.Join(d, "config.json"), []byte(config), 0o644)
   115  	assert.NilError(c, err)
   116  
   117  	out := cli.DockerCmd(c, "--config", d, "volume", "ls").Stdout()
   118  	assertVolumesInList(c, out, []string{"aaa default", "soo default", "test default"})
   119  }
   120  
   121  func assertVolumesInList(c *testing.T, out string, expected []string) {
   122  	lines := strings.Split(strings.TrimSpace(out), "\n")
   123  	for _, expect := range expected {
   124  		found := false
   125  		for _, v := range lines {
   126  			found = v == expect
   127  			if found {
   128  				break
   129  			}
   130  		}
   131  		assert.Assert(c, found, "Expected volume not found: %v, got: %v", expect, lines)
   132  	}
   133  }
   134  
   135  func (s *DockerCLIVolumeSuite) TestVolumeCLILsFilterDangling(c *testing.T) {
   136  	prefix, _ := getPrefixAndSlashFromDaemonPlatform()
   137  	cli.DockerCmd(c, "volume", "create", "testnotinuse1")
   138  	cli.DockerCmd(c, "volume", "create", "testisinuse1")
   139  	cli.DockerCmd(c, "volume", "create", "testisinuse2")
   140  
   141  	// Make sure both "created" (but not started), and started
   142  	// containers are included in reference counting
   143  	cli.DockerCmd(c, "run", "--name", "volume-test1", "-v", "testisinuse1:"+prefix+"/foo", "busybox", "true")
   144  	cli.DockerCmd(c, "create", "--name", "volume-test2", "-v", "testisinuse2:"+prefix+"/foo", "busybox", "true")
   145  
   146  	// No filter, all volumes should show
   147  	out := cli.DockerCmd(c, "volume", "ls").Stdout()
   148  	assert.Assert(c, strings.Contains(out, "testnotinuse1\n"), "expected volume 'testnotinuse1' in output")
   149  	assert.Assert(c, strings.Contains(out, "testisinuse1\n"), "expected volume 'testisinuse1' in output")
   150  	assert.Assert(c, strings.Contains(out, "testisinuse2\n"), "expected volume 'testisinuse2' in output")
   151  
   152  	// Explicitly disabling dangling
   153  	out = cli.DockerCmd(c, "volume", "ls", "--filter", "dangling=false").Stdout()
   154  	assert.Assert(c, !strings.Contains(out, "testnotinuse1\n"), "expected volume 'testnotinuse1' in output")
   155  	assert.Assert(c, strings.Contains(out, "testisinuse1\n"), "expected volume 'testisinuse1' in output")
   156  	assert.Assert(c, strings.Contains(out, "testisinuse2\n"), "expected volume 'testisinuse2' in output")
   157  
   158  	// Filter "dangling" volumes; only "dangling" (unused) volumes should be in the output
   159  	out = cli.DockerCmd(c, "volume", "ls", "--filter", "dangling=true").Stdout()
   160  	assert.Assert(c, strings.Contains(out, "testnotinuse1\n"), "expected volume 'testnotinuse1' in output")
   161  	assert.Assert(c, !strings.Contains(out, "testisinuse1\n"), "volume 'testisinuse1' in output, but not expected")
   162  	assert.Assert(c, !strings.Contains(out, "testisinuse2\n"), "volume 'testisinuse2' in output, but not expected")
   163  
   164  	// Filter "dangling" volumes; only "dangling" (unused) volumes should be in the output, dangling also accept 1
   165  	out = cli.DockerCmd(c, "volume", "ls", "--filter", "dangling=1").Stdout()
   166  	assert.Assert(c, strings.Contains(out, "testnotinuse1\n"), "expected volume 'testnotinuse1' in output")
   167  	assert.Assert(c, !strings.Contains(out, "testisinuse1\n"), "volume 'testisinuse1' in output, but not expected")
   168  	assert.Assert(c, !strings.Contains(out, "testisinuse2\n"), "volume 'testisinuse2' in output, but not expected")
   169  
   170  	// dangling=0 is same as dangling=false case
   171  	out = cli.DockerCmd(c, "volume", "ls", "--filter", "dangling=0").Stdout()
   172  	assert.Assert(c, !strings.Contains(out, "testnotinuse1\n"), "expected volume 'testnotinuse1' in output")
   173  	assert.Assert(c, strings.Contains(out, "testisinuse1\n"), "expected volume 'testisinuse1' in output")
   174  	assert.Assert(c, strings.Contains(out, "testisinuse2\n"), "expected volume 'testisinuse2' in output")
   175  
   176  	out = cli.DockerCmd(c, "volume", "ls", "--filter", "name=testisin").Stdout()
   177  	assert.Assert(c, !strings.Contains(out, "testnotinuse1\n"), "expected volume 'testnotinuse1' in output")
   178  	assert.Assert(c, strings.Contains(out, "testisinuse1\n"), "expected volume 'testisinuse1' in output")
   179  	assert.Assert(c, strings.Contains(out, "testisinuse2\n"), "expected volume 'testisinuse2' in output")
   180  }
   181  
   182  func (s *DockerCLIVolumeSuite) TestVolumeCLILsErrorWithInvalidFilterName(c *testing.T) {
   183  	out, _, err := dockerCmdWithError("volume", "ls", "-f", "FOO=123")
   184  	assert.ErrorContains(c, err, "")
   185  	assert.Assert(c, strings.Contains(out, "invalid filter"))
   186  }
   187  
   188  func (s *DockerCLIVolumeSuite) TestVolumeCLILsWithIncorrectFilterValue(c *testing.T) {
   189  	out, _, err := dockerCmdWithError("volume", "ls", "-f", "dangling=invalid")
   190  	assert.ErrorContains(c, err, "")
   191  	assert.Assert(c, strings.Contains(out, "invalid filter"))
   192  }
   193  
   194  func (s *DockerCLIVolumeSuite) TestVolumeCLIRm(c *testing.T) {
   195  	prefix, _ := getPrefixAndSlashFromDaemonPlatform()
   196  	id := cli.DockerCmd(c, "volume", "create").Stdout()
   197  	id = strings.TrimSpace(id)
   198  
   199  	cli.DockerCmd(c, "volume", "create", "test")
   200  	cli.DockerCmd(c, "volume", "rm", id)
   201  	cli.DockerCmd(c, "volume", "rm", "test")
   202  
   203  	volumeID := "testing"
   204  	cli.DockerCmd(c, "run", "-v", volumeID+":"+prefix+"/foo", "--name=test", "busybox", "sh", "-c", "echo hello > /foo/bar")
   205  
   206  	icmd.RunCommand(dockerBinary, "volume", "rm", "testing").Assert(c, icmd.Expected{
   207  		ExitCode: 1,
   208  		Error:    "exit status 1",
   209  	})
   210  
   211  	out := cli.DockerCmd(c, "run", "--volumes-from=test", "--name=test2", "busybox", "sh", "-c", "cat /foo/bar").Combined()
   212  	assert.Equal(c, strings.TrimSpace(out), "hello")
   213  	cli.DockerCmd(c, "rm", "-fv", "test2")
   214  	cli.DockerCmd(c, "volume", "inspect", volumeID)
   215  	cli.DockerCmd(c, "rm", "-f", "test")
   216  
   217  	out = cli.DockerCmd(c, "run", "--name=test2", "-v", volumeID+":"+prefix+"/foo", "busybox", "sh", "-c", "cat /foo/bar").Combined()
   218  	assert.Equal(c, strings.TrimSpace(out), "hello", "volume data was removed")
   219  	cli.DockerCmd(c, "rm", "test2")
   220  
   221  	cli.DockerCmd(c, "volume", "rm", volumeID)
   222  	assert.Assert(c, exec.Command("volume", "rm", "doesnotexist").Run() != nil, "volume rm should fail with non-existent volume")
   223  }
   224  
   225  // FIXME(vdemeester) should be a unit test in cli/command/volume package
   226  func (s *DockerCLIVolumeSuite) TestVolumeCLINoArgs(c *testing.T) {
   227  	out := cli.DockerCmd(c, "volume").Combined()
   228  	// no args should produce the cmd usage output
   229  	usage := "Usage:	docker volume COMMAND"
   230  	assert.Assert(c, strings.Contains(out, usage))
   231  	// invalid arg should error and show the command usage on stderr
   232  	icmd.RunCommand(dockerBinary, "volume", "somearg").Assert(c, icmd.Expected{
   233  		ExitCode: 1,
   234  		Error:    "exit status 1",
   235  		Err:      usage,
   236  	})
   237  
   238  	// invalid flag should error and show the flag error and cmd usage
   239  	result := icmd.RunCommand(dockerBinary, "volume", "--no-such-flag")
   240  	result.Assert(c, icmd.Expected{
   241  		ExitCode: 125,
   242  		Error:    "exit status 125",
   243  		Err:      usage,
   244  	})
   245  	assert.Assert(c, strings.Contains(result.Stderr(), "unknown flag: --no-such-flag"))
   246  }
   247  
   248  func (s *DockerCLIVolumeSuite) TestVolumeCLIInspectTmplError(c *testing.T) {
   249  	name := cli.DockerCmd(c, "volume", "create").Stdout()
   250  	name = strings.TrimSpace(name)
   251  
   252  	out, exitCode, err := dockerCmdWithError("volume", "inspect", "--format='{{ .FooBar }}'", name)
   253  	assert.Assert(c, err != nil, "Output: %s", out)
   254  	assert.Equal(c, exitCode, 1, fmt.Sprintf("Output: %s", out))
   255  	assert.Assert(c, strings.Contains(out, "Template parsing error"))
   256  }
   257  
   258  func (s *DockerCLIVolumeSuite) TestVolumeCLICreateWithOpts(c *testing.T) {
   259  	testRequires(c, DaemonIsLinux)
   260  
   261  	cli.DockerCmd(c, "volume", "create", "-d", "local", "test", "--opt=type=tmpfs", "--opt=device=tmpfs", "--opt=o=size=1m,uid=1000")
   262  
   263  	out := cli.DockerCmd(c, "run", "-v", "test:/foo", "busybox", "mount").Stdout()
   264  	mounts := strings.Split(out, "\n")
   265  	var found bool
   266  	for _, m := range mounts {
   267  		if strings.Contains(m, "/foo") {
   268  			found = true
   269  			info := strings.Fields(m)
   270  			// tmpfs on <path> type tmpfs (rw,relatime,size=1024k,uid=1000)
   271  			assert.Equal(c, info[0], "tmpfs")
   272  			assert.Equal(c, info[2], "/foo")
   273  			assert.Equal(c, info[4], "tmpfs")
   274  			assert.Assert(c, strings.Contains(info[5], "uid=1000"))
   275  			assert.Assert(c, strings.Contains(info[5], "size=1024k"))
   276  			break
   277  		}
   278  	}
   279  	assert.Equal(c, found, true)
   280  }
   281  
   282  func (s *DockerCLIVolumeSuite) TestVolumeCLICreateLabel(c *testing.T) {
   283  	const testVol = "testvolcreatelabel"
   284  	const testLabel = "foo"
   285  	const testValue = "bar"
   286  
   287  	_, _, err := dockerCmdWithError("volume", "create", "--label", testLabel+"="+testValue, testVol)
   288  	assert.NilError(c, err)
   289  
   290  	out := cli.DockerCmd(c, "volume", "inspect", "--format={{ .Labels."+testLabel+" }}", testVol).Stdout()
   291  	assert.Equal(c, strings.TrimSpace(out), testValue)
   292  }
   293  
   294  func (s *DockerCLIVolumeSuite) TestVolumeCLICreateLabelMultiple(c *testing.T) {
   295  	const testVol = "testvolcreatelabel"
   296  
   297  	testLabels := map[string]string{
   298  		"foo": "bar",
   299  		"baz": "foo",
   300  	}
   301  
   302  	args := []string{
   303  		"volume",
   304  		"create",
   305  		testVol,
   306  	}
   307  
   308  	for k, v := range testLabels {
   309  		args = append(args, "--label", k+"="+v)
   310  	}
   311  
   312  	_, _, err := dockerCmdWithError(args...)
   313  	assert.NilError(c, err)
   314  
   315  	for k, v := range testLabels {
   316  		out := cli.DockerCmd(c, "volume", "inspect", "--format={{ .Labels."+k+" }}", testVol).Stdout()
   317  		assert.Equal(c, strings.TrimSpace(out), v)
   318  	}
   319  }
   320  
   321  func (s *DockerCLIVolumeSuite) TestVolumeCLILsFilterLabels(c *testing.T) {
   322  	testVol1 := "testvolcreatelabel-1"
   323  	_, _, err := dockerCmdWithError("volume", "create", "--label", "foo=bar1", testVol1)
   324  	assert.NilError(c, err)
   325  
   326  	testVol2 := "testvolcreatelabel-2"
   327  	_, _, err = dockerCmdWithError("volume", "create", "--label", "foo=bar2", testVol2)
   328  	assert.NilError(c, err)
   329  
   330  	// filter with label=key
   331  	out := cli.DockerCmd(c, "volume", "ls", "--filter", "label=foo").Stdout()
   332  	assert.Assert(c, strings.Contains(out, "testvolcreatelabel-1\n"), "expected volume 'testvolcreatelabel-1' in output")
   333  	assert.Assert(c, strings.Contains(out, "testvolcreatelabel-2\n"), "expected volume 'testvolcreatelabel-2' in output")
   334  
   335  	// filter with label=key=value
   336  	out = cli.DockerCmd(c, "volume", "ls", "--filter", "label=foo=bar1").Stdout()
   337  	assert.Assert(c, strings.Contains(out, "testvolcreatelabel-1\n"), "expected volume 'testvolcreatelabel-1' in output")
   338  	assert.Assert(c, !strings.Contains(out, "testvolcreatelabel-2\n"), "expected volume 'testvolcreatelabel-2 in output")
   339  
   340  	out = cli.DockerCmd(c, "volume", "ls", "--filter", "label=non-exist").Stdout()
   341  	outArr := strings.Split(strings.TrimSpace(out), "\n")
   342  	assert.Equal(c, len(outArr), 1, fmt.Sprintf("\n%s", out))
   343  
   344  	out = cli.DockerCmd(c, "volume", "ls", "--filter", "label=foo=non-exist").Stdout()
   345  	outArr = strings.Split(strings.TrimSpace(out), "\n")
   346  	assert.Equal(c, len(outArr), 1, fmt.Sprintf("\n%s", out))
   347  }
   348  
   349  func (s *DockerCLIVolumeSuite) TestVolumeCLILsFilterDrivers(c *testing.T) {
   350  	// using default volume driver local to create volumes
   351  	testVol1 := "testvol-1"
   352  	_, _, err := dockerCmdWithError("volume", "create", testVol1)
   353  	assert.NilError(c, err)
   354  
   355  	testVol2 := "testvol-2"
   356  	_, _, err = dockerCmdWithError("volume", "create", testVol2)
   357  	assert.NilError(c, err)
   358  
   359  	// filter with driver=local
   360  	out := cli.DockerCmd(c, "volume", "ls", "--filter", "driver=local").Stdout()
   361  	assert.Assert(c, strings.Contains(out, "testvol-1\n"), "expected volume 'testvol-1' in output")
   362  	assert.Assert(c, strings.Contains(out, "testvol-2\n"), "expected volume 'testvol-2' in output")
   363  
   364  	// filter with driver=invaliddriver
   365  	out = cli.DockerCmd(c, "volume", "ls", "--filter", "driver=invaliddriver").Stdout()
   366  	outArr := strings.Split(strings.TrimSpace(out), "\n")
   367  	assert.Equal(c, len(outArr), 1, fmt.Sprintf("\n%s", out))
   368  
   369  	// filter with driver=loca
   370  	out = cli.DockerCmd(c, "volume", "ls", "--filter", "driver=loca").Stdout()
   371  	outArr = strings.Split(strings.TrimSpace(out), "\n")
   372  	assert.Equal(c, len(outArr), 1, fmt.Sprintf("\n%s", out))
   373  
   374  	// filter with driver=
   375  	out = cli.DockerCmd(c, "volume", "ls", "--filter", "driver=").Stdout()
   376  	outArr = strings.Split(strings.TrimSpace(out), "\n")
   377  	assert.Equal(c, len(outArr), 1, fmt.Sprintf("\n%s", out))
   378  }
   379  
   380  func (s *DockerCLIVolumeSuite) TestVolumeCLIRmForceUsage(c *testing.T) {
   381  	id := cli.DockerCmd(c, "volume", "create").Stdout()
   382  	id = strings.TrimSpace(id)
   383  
   384  	cli.DockerCmd(c, "volume", "rm", "-f", id)
   385  	cli.DockerCmd(c, "volume", "rm", "--force", "nonexist")
   386  }
   387  
   388  func (s *DockerCLIVolumeSuite) TestVolumeCLIRmForce(c *testing.T) {
   389  	testRequires(c, testEnv.IsLocalDaemon, DaemonIsLinux)
   390  
   391  	const name = "test"
   392  	id := cli.DockerCmd(c, "volume", "create", name).Stdout()
   393  	id = strings.TrimSpace(id)
   394  	assert.Equal(c, id, name)
   395  
   396  	out := cli.DockerCmd(c, "volume", "inspect", "--format", "{{.Mountpoint}}", name).Stdout()
   397  	assert.Assert(c, strings.TrimSpace(out) != "")
   398  	// Mountpoint is in the form of "/var/lib/docker/volumes/.../_data", removing `/_data`
   399  	path := strings.TrimSuffix(strings.TrimSpace(out), "/_data")
   400  	icmd.RunCommand("rm", "-rf", path).Assert(c, icmd.Success)
   401  
   402  	cli.DockerCmd(c, "volume", "rm", "-f", name)
   403  	out = cli.DockerCmd(c, "volume", "ls").Stdout()
   404  	assert.Assert(c, !strings.Contains(out, name))
   405  	cli.DockerCmd(c, "volume", "create", name)
   406  	out = cli.DockerCmd(c, "volume", "ls").Stdout()
   407  	assert.Assert(c, strings.Contains(out, name))
   408  }
   409  
   410  // TestVolumeCLIRmForceInUse verifies that repeated `docker volume rm -f` calls does not remove a volume
   411  // if it is in use. Test case for https://github.com/Prakhar-Agarwal-byte/moby/issues/31446
   412  func (s *DockerCLIVolumeSuite) TestVolumeCLIRmForceInUse(c *testing.T) {
   413  	const name = "testvolume"
   414  	id := cli.DockerCmd(c, "volume", "create", name).Stdout()
   415  	id = strings.TrimSpace(id)
   416  	assert.Equal(c, id, name)
   417  
   418  	prefix, slash := getPrefixAndSlashFromDaemonPlatform()
   419  	cid := cli.DockerCmd(c, "create", "-v", "testvolume:"+prefix+slash+"foo", "busybox").Stdout()
   420  	cid = strings.TrimSpace(cid)
   421  
   422  	_, _, err := dockerCmdWithError("volume", "rm", "-f", name)
   423  	assert.ErrorContains(c, err, "")
   424  	assert.ErrorContains(c, err, "volume is in use")
   425  	out := cli.DockerCmd(c, "volume", "ls").Stdout()
   426  	assert.Assert(c, strings.Contains(out, name))
   427  	// The original issue did not _remove_ the volume from the list
   428  	// the first time. But a second call to `volume rm` removed it.
   429  	// Calling `volume rm` a second time to confirm it's not removed
   430  	// when calling twice.
   431  	_, _, err = dockerCmdWithError("volume", "rm", "-f", name)
   432  	assert.ErrorContains(c, err, "")
   433  	assert.ErrorContains(c, err, "volume is in use")
   434  	out = cli.DockerCmd(c, "volume", "ls").Stdout()
   435  	assert.Assert(c, strings.Contains(out, name))
   436  	// Verify removing the volume after the container is removed works
   437  	e := cli.DockerCmd(c, "rm", cid).ExitCode
   438  	assert.Equal(c, e, 0)
   439  
   440  	e = cli.DockerCmd(c, "volume", "rm", "-f", name).ExitCode
   441  	assert.Equal(c, e, 0)
   442  
   443  	result := cli.DockerCmd(c, "volume", "ls")
   444  	assert.Equal(c, result.ExitCode, 0)
   445  	assert.Assert(c, !strings.Contains(result.Stdout(), name))
   446  }
   447  
   448  func (s *DockerCLIVolumeSuite) TestVolumeCliInspectWithVolumeOpts(c *testing.T) {
   449  	testRequires(c, DaemonIsLinux)
   450  
   451  	// Without options
   452  	name := "test1"
   453  	cli.DockerCmd(c, "volume", "create", "-d", "local", name)
   454  	out := cli.DockerCmd(c, "volume", "inspect", "--format={{ .Options }}", name).Stdout()
   455  	assert.Assert(c, strings.Contains(strings.TrimSpace(out), "map[]"))
   456  	// With options
   457  	name = "test2"
   458  	k1, v1 := "type", "tmpfs"
   459  	k2, v2 := "device", "tmpfs"
   460  	k3, v3 := "o", "size=1m,uid=1000"
   461  	cli.DockerCmd(c, "volume", "create", "-d", "local", name, "--opt", fmt.Sprintf("%s=%s", k1, v1), "--opt", fmt.Sprintf("%s=%s", k2, v2), "--opt", fmt.Sprintf("%s=%s", k3, v3))
   462  	out = cli.DockerCmd(c, "volume", "inspect", "--format={{ .Options }}", name).Stdout()
   463  	assert.Assert(c, strings.Contains(strings.TrimSpace(out), fmt.Sprintf("%s:%s", k1, v1)))
   464  	assert.Assert(c, strings.Contains(strings.TrimSpace(out), fmt.Sprintf("%s:%s", k2, v2)))
   465  	assert.Assert(c, strings.Contains(strings.TrimSpace(out), fmt.Sprintf("%s:%s", k3, v3)))
   466  }
   467  
   468  // Test case (1) for 21845: duplicate targets for --volumes-from
   469  func (s *DockerCLIVolumeSuite) TestDuplicateMountpointsForVolumesFrom(c *testing.T) {
   470  	testRequires(c, DaemonIsLinux)
   471  
   472  	const image = "vimage"
   473  	buildImageSuccessfully(c, image, build.WithDockerfile(`
   474  		FROM busybox
   475  		VOLUME ["/tmp/data"]`))
   476  
   477  	cli.DockerCmd(c, "run", "--name=data1", image, "true")
   478  	cli.DockerCmd(c, "run", "--name=data2", image, "true")
   479  
   480  	data1 := cli.DockerCmd(c, "inspect", "--format", "{{(index .Mounts 0).Name}}", "data1").Stdout()
   481  	data1 = strings.TrimSpace(data1)
   482  	assert.Assert(c, data1 != "")
   483  
   484  	data2 := cli.DockerCmd(c, "inspect", "--format", "{{(index .Mounts 0).Name}}", "data2").Stdout()
   485  	data2 = strings.TrimSpace(data2)
   486  	assert.Assert(c, data2 != "")
   487  
   488  	// Both volume should exist
   489  	out := cli.DockerCmd(c, "volume", "ls", "-q").Stdout()
   490  	assert.Assert(c, strings.Contains(strings.TrimSpace(out), data1))
   491  	assert.Assert(c, strings.Contains(strings.TrimSpace(out), data2))
   492  	out, _, err := dockerCmdWithError("run", "--name=app", "--volumes-from=data1", "--volumes-from=data2", "-d", "busybox", "top")
   493  	assert.Assert(c, err == nil, "Out: %s", out)
   494  
   495  	// Only the second volume will be referenced, this is backward compatible
   496  	out = cli.DockerCmd(c, "inspect", "--format", "{{(index .Mounts 0).Name}}", "app").Stdout()
   497  	assert.Equal(c, strings.TrimSpace(out), data2)
   498  
   499  	cli.DockerCmd(c, "rm", "-f", "-v", "app")
   500  	cli.DockerCmd(c, "rm", "-f", "-v", "data1")
   501  	cli.DockerCmd(c, "rm", "-f", "-v", "data2")
   502  
   503  	// Both volume should not exist
   504  	out = cli.DockerCmd(c, "volume", "ls", "-q").Stdout()
   505  	assert.Assert(c, !strings.Contains(strings.TrimSpace(out), data1))
   506  	assert.Assert(c, !strings.Contains(strings.TrimSpace(out), data2))
   507  }
   508  
   509  // Test case (2) for 21845: duplicate targets for --volumes-from and -v (bind)
   510  func (s *DockerCLIVolumeSuite) TestDuplicateMountpointsForVolumesFromAndBind(c *testing.T) {
   511  	testRequires(c, DaemonIsLinux)
   512  
   513  	const image = "vimage"
   514  	buildImageSuccessfully(c, image, build.WithDockerfile(`
   515                  FROM busybox
   516                  VOLUME ["/tmp/data"]`))
   517  
   518  	cli.DockerCmd(c, "run", "--name=data1", image, "true")
   519  	cli.DockerCmd(c, "run", "--name=data2", image, "true")
   520  
   521  	data1 := cli.DockerCmd(c, "inspect", "--format", "{{(index .Mounts 0).Name}}", "data1").Stdout()
   522  	data1 = strings.TrimSpace(data1)
   523  	assert.Assert(c, data1 != "")
   524  
   525  	data2 := cli.DockerCmd(c, "inspect", "--format", "{{(index .Mounts 0).Name}}", "data2").Stdout()
   526  	data2 = strings.TrimSpace(data2)
   527  	assert.Assert(c, data2 != "")
   528  
   529  	// Both volume should exist
   530  	out := cli.DockerCmd(c, "volume", "ls", "-q").Stdout()
   531  	assert.Assert(c, strings.Contains(strings.TrimSpace(out), data1))
   532  	assert.Assert(c, strings.Contains(strings.TrimSpace(out), data2))
   533  	// /tmp/data is automatically created, because we are not using the modern mount API here
   534  	out, _, err := dockerCmdWithError("run", "--name=app", "--volumes-from=data1", "--volumes-from=data2", "-v", "/tmp/data:/tmp/data", "-d", "busybox", "top")
   535  	assert.Assert(c, err == nil, "Out: %s", out)
   536  
   537  	// No volume will be referenced (mount is /tmp/data), this is backward compatible
   538  	out = cli.DockerCmd(c, "inspect", "--format", "{{(index .Mounts 0).Name}}", "app").Stdout()
   539  	assert.Assert(c, !strings.Contains(strings.TrimSpace(out), data1))
   540  	assert.Assert(c, !strings.Contains(strings.TrimSpace(out), data2))
   541  	cli.DockerCmd(c, "rm", "-f", "-v", "app")
   542  	cli.DockerCmd(c, "rm", "-f", "-v", "data1")
   543  	cli.DockerCmd(c, "rm", "-f", "-v", "data2")
   544  
   545  	// Both volume should not exist
   546  	out = cli.DockerCmd(c, "volume", "ls", "-q").Stdout()
   547  	assert.Assert(c, !strings.Contains(strings.TrimSpace(out), data1))
   548  	assert.Assert(c, !strings.Contains(strings.TrimSpace(out), data2))
   549  }
   550  
   551  // Test case (3) for 21845: duplicate targets for --volumes-from and `Mounts` (API only)
   552  func (s *DockerCLIVolumeSuite) TestDuplicateMountpointsForVolumesFromAndMounts(c *testing.T) {
   553  	testRequires(c, testEnv.IsLocalDaemon, DaemonIsLinux)
   554  
   555  	const image = "vimage"
   556  	buildImageSuccessfully(c, image, build.WithDockerfile(`
   557                  FROM busybox
   558                  VOLUME ["/tmp/data"]`))
   559  
   560  	cli.DockerCmd(c, "run", "--name=data1", image, "true")
   561  	cli.DockerCmd(c, "run", "--name=data2", image, "true")
   562  
   563  	data1 := cli.DockerCmd(c, "inspect", "--format", "{{(index .Mounts 0).Name}}", "data1").Stdout()
   564  	data1 = strings.TrimSpace(data1)
   565  	assert.Assert(c, data1 != "")
   566  
   567  	data2 := cli.DockerCmd(c, "inspect", "--format", "{{(index .Mounts 0).Name}}", "data2").Stdout()
   568  	data2 = strings.TrimSpace(data2)
   569  	assert.Assert(c, data2 != "")
   570  
   571  	// Both volume should exist
   572  	out := cli.DockerCmd(c, "volume", "ls", "-q").Stdout()
   573  	assert.Assert(c, strings.Contains(strings.TrimSpace(out), data1))
   574  	assert.Assert(c, strings.Contains(strings.TrimSpace(out), data2))
   575  	err := os.MkdirAll("/tmp/data", 0o755)
   576  	assert.NilError(c, err)
   577  
   578  	// Mounts is available in API
   579  	apiClient, err := client.NewClientWithOpts(client.FromEnv)
   580  	assert.NilError(c, err)
   581  	defer apiClient.Close()
   582  
   583  	config := container.Config{
   584  		Cmd:   []string{"top"},
   585  		Image: "busybox",
   586  	}
   587  
   588  	hostConfig := container.HostConfig{
   589  		VolumesFrom: []string{"data1", "data2"},
   590  		Mounts: []mount.Mount{
   591  			{
   592  				Type:   "bind",
   593  				Source: "/tmp/data",
   594  				Target: "/tmp/data",
   595  			},
   596  		},
   597  	}
   598  	_, err = apiClient.ContainerCreate(testutil.GetContext(c), &config, &hostConfig, &network.NetworkingConfig{}, nil, "app")
   599  
   600  	assert.NilError(c, err)
   601  
   602  	// No volume will be referenced (mount is /tmp/data), this is backward compatible
   603  	out = cli.DockerCmd(c, "inspect", "--format", "{{(index .Mounts 0).Name}}", "app").Stdout()
   604  	assert.Assert(c, !strings.Contains(strings.TrimSpace(out), data1))
   605  	assert.Assert(c, !strings.Contains(strings.TrimSpace(out), data2))
   606  	cli.DockerCmd(c, "rm", "-f", "-v", "app")
   607  	cli.DockerCmd(c, "rm", "-f", "-v", "data1")
   608  	cli.DockerCmd(c, "rm", "-f", "-v", "data2")
   609  
   610  	// Both volume should not exist
   611  	out = cli.DockerCmd(c, "volume", "ls", "-q").Stdout()
   612  	assert.Assert(c, !strings.Contains(strings.TrimSpace(out), data1))
   613  	assert.Assert(c, !strings.Contains(strings.TrimSpace(out), data2))
   614  }