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