github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/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" 17 "github.com/docker/docker/integration-cli/cli/build" 18 "github.com/docker/docker/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/docker/docker/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 imgName = "vimage" 473 buildImageSuccessfully(c, imgName, build.WithDockerfile(` 474 FROM busybox 475 VOLUME ["/tmp/data"]`)) 476 477 cli.DockerCmd(c, "run", "--name=data1", imgName, "true") 478 cli.DockerCmd(c, "run", "--name=data2", imgName, "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 imgName = "vimage" 514 buildImageSuccessfully(c, imgName, build.WithDockerfile(` 515 FROM busybox 516 VOLUME ["/tmp/data"]`)) 517 518 cli.DockerCmd(c, "run", "--name=data1", imgName, "true") 519 cli.DockerCmd(c, "run", "--name=data2", imgName, "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 imgName = "vimage" 556 buildImageSuccessfully(c, imgName, build.WithDockerfile(` 557 FROM busybox 558 VOLUME ["/tmp/data"]`)) 559 560 cli.DockerCmd(c, "run", "--name=data1", imgName, "true") 561 cli.DockerCmd(c, "run", "--name=data2", imgName, "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 }