Browse code

cli: add `--mount` to `docker run`

Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>

Akihiro Suda authored on 2017/03/31 15:41:45
Showing 8 changed files
... ...
@@ -11,6 +11,7 @@ import (
11 11
 	"strings"
12 12
 	"time"
13 13
 
14
+	"github.com/Sirupsen/logrus"
14 15
 	"github.com/docker/docker/api/types/container"
15 16
 	networktypes "github.com/docker/docker/api/types/network"
16 17
 	"github.com/docker/docker/api/types/strslice"
... ...
@@ -31,6 +32,7 @@ type containerOptions struct {
31 31
 	attach             opts.ListOpts
32 32
 	volumes            opts.ListOpts
33 33
 	tmpfs              opts.ListOpts
34
+	mounts             opts.MountOpt
34 35
 	blkioWeightDevice  opts.WeightdeviceOpt
35 36
 	deviceReadBps      opts.ThrottledeviceOpt
36 37
 	deviceWriteBps     opts.ThrottledeviceOpt
... ...
@@ -223,6 +225,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
223 223
 	flags.Var(&copts.tmpfs, "tmpfs", "Mount a tmpfs directory")
224 224
 	flags.Var(&copts.volumesFrom, "volumes-from", "Mount volumes from the specified container(s)")
225 225
 	flags.VarP(&copts.volumes, "volume", "v", "Bind mount a volume")
226
+	flags.Var(&copts.mounts, "mount", "Attach a filesystem mount to the container")
226 227
 
227 228
 	// Health-checking
228 229
 	flags.StringVar(&copts.healthCmd, "health-cmd", "", "Command to run to check health")
... ...
@@ -321,6 +324,10 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err
321 321
 		return nil, errors.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
322 322
 	}
323 323
 
324
+	mounts := copts.mounts.Value()
325
+	if len(mounts) > 0 && copts.volumeDriver != "" {
326
+		logrus.Warn("`--volume-driver` is ignored for volumes specified via `--mount`. Use `--mount type=volume,volume-driver=...` instead.")
327
+	}
324 328
 	var binds []string
325 329
 	volumes := copts.volumes.GetMap()
326 330
 	// add any bind targets to the list of container volumes
... ...
@@ -589,6 +596,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err
589 589
 		Tmpfs:          tmpfs,
590 590
 		Sysctls:        copts.sysctls.GetAll(),
591 591
 		Runtime:        copts.runtime,
592
+		Mounts:         mounts,
592 593
 	}
593 594
 
594 595
 	if copts.autoRemove && !hostConfig.RestartPolicy.IsNone() {
... ...
@@ -1518,6 +1518,7 @@ _docker_container_run_and_create() {
1518 1518
 		--memory-swap
1519 1519
 		--memory-swappiness
1520 1520
 		--memory-reservation
1521
+		--mount
1521 1522
 		--name
1522 1523
 		--network
1523 1524
 		--network-alias
... ...
@@ -138,6 +138,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l link -d 'Add
138 138
 complete -c docker -A -f -n '__fish_seen_subcommand_from create' -s m -l memory -d 'Memory limit (format: <number>[<unit>], where unit = b, k, m or g)'
139 139
 complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l mac-address -d 'Container MAC address (e.g., 92:d0:c6:0a:29:33)'
140 140
 complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l memory-swap -d "Total memory usage (memory + swap), set '-1' to disable swap (format: <number>[<unit>], where unit = b, k, m or g)"
141
+complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l mount -d 'Attach a filesystem mount to the container'
141 142
 complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l name -d 'Assign a name to the container'
142 143
 complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l net -d 'Set the Network mode for the container'
143 144
 complete -c docker -A -f -n '__fish_seen_subcommand_from create' -s P -l publish-all -d 'Publish all exposed ports to random ports on the host interfaces'
... ...
@@ -330,6 +331,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l link -d 'Add li
330 330
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s m -l memory -d 'Memory limit (format: <number>[<unit>], where unit = b, k, m or g)'
331 331
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l mac-address -d 'Container MAC address (e.g., 92:d0:c6:0a:29:33)'
332 332
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l memory-swap -d "Total memory usage (memory + swap), set '-1' to disable swap (format: <number>[<unit>], where unit = b, k, m or g)"
333
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l mount -d 'Attach a filesystem mount to the container'
333 334
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l name -d 'Assign a name to the container'
334 335
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l net -d 'Set the Network mode for the container'
335 336
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s P -l publish-all -d 'Publish all exposed ports to random ports on the host interfaces'
... ...
@@ -629,6 +629,7 @@ __docker_container_subcommand() {
629 629
         "($help)--log-driver=[Default driver for container logs]:logging driver:__docker_complete_log_drivers"
630 630
         "($help)*--log-opt=[Log driver specific options]:log driver options:__docker_complete_log_options"
631 631
         "($help)--mac-address=[Container MAC address]:MAC address: "
632
+        "($help)*--mount=[Attach a filesystem mount to the container]:mount: "
632 633
         "($help)--name=[Container name]:name: "
633 634
         "($help)--network=[Connect a container to a network]:network mode:(bridge none container host)"
634 635
         "($help)*--network-alias=[Add network-scoped alias for the container]:alias: "
... ...
@@ -85,6 +85,7 @@ Options:
85 85
       --memory-reservation string   Memory soft limit
86 86
       --memory-swap string          Swap limit equal to memory plus swap: '-1' to enable unlimited swap
87 87
       --memory-swappiness int       Tune container memory swappiness (0 to 100) (default -1)
88
+      --mount value                 Attach a filesytem mount to the container (default [])
88 89
       --name string                 Assign a name to the container
89 90
       --network-alias value         Add network-scoped alias for the container (default [])
90 91
       --network string              Connect a container to a network (default "default")
... ...
@@ -95,6 +95,7 @@ Options:
95 95
       --memory-reservation string   Memory soft limit
96 96
       --memory-swap string          Swap limit equal to memory plus swap: '-1' to enable unlimited swap
97 97
       --memory-swappiness int       Tune container memory swappiness (0 to 100) (default -1)
98
+      --mount value                 Attach a filesystem mount to the container (default [])
98 99
       --name string                 Assign a name to the container
99 100
       --network-alias value         Add network-scoped alias for the container (default [])
100 101
       --network string              Connect a container to a network
... ...
@@ -316,6 +317,29 @@ docker run -v c:\foo:c:\existing-directory-with-contents ...
316 316
 
317 317
 For in-depth information about volumes, refer to [manage data in containers](https://docs.docker.com/engine/tutorials/dockervolumes/)
318 318
 
319
+
320
+### Add bind-mounts or volumes using the --mount flag
321
+
322
+The `--mount` flag allows you to mount volumes, host-directories and `tmpfs`
323
+mounts in a container.
324
+
325
+The `--mount` flag supports most options that are supported by the `-v` or the
326
+`--volume` flag, but uses a different syntax. For in-depth information on the
327
+`--mount` flag, and a comparison between `--volume` and `--mount`, refer to
328
+the [service create command reference](service_create.md#add-bind-mounts-or-volumes).
329
+
330
+Even though there is no plan to deprecate `--volume`, usage of `--mount` is recommended.
331
+
332
+Examples:
333
+
334
+```bash
335
+$ docker run --read-only --mount type=volume,target=/icanwrite busybox touch /icanwrite/here
336
+```
337
+
338
+```bash
339
+$ docker run -t -i --mount type=bind,src=/data,dst=/data busybox sh
340
+```
341
+
319 342
 ### Publish or expose port (-p, --expose)
320 343
 
321 344
 ```bash
... ...
@@ -4422,6 +4422,184 @@ func (s *DockerSuite) TestRunMountReadOnlyDevShm(c *check.C) {
4422 4422
 	c.Assert(out, checker.Contains, "Read-only file system")
4423 4423
 }
4424 4424
 
4425
+func (s *DockerSuite) TestRunMount(c *check.C) {
4426
+	testRequires(c, DaemonIsLinux, SameHostDaemon, NotUserNamespace)
4427
+
4428
+	// mnt1, mnt2, and testCatFooBar are commonly used in multiple test cases
4429
+	tmpDir, err := ioutil.TempDir("", "mount")
4430
+	if err != nil {
4431
+		c.Fatal(err)
4432
+	}
4433
+	defer os.RemoveAll(tmpDir)
4434
+	mnt1, mnt2 := path.Join(tmpDir, "mnt1"), path.Join(tmpDir, "mnt2")
4435
+	if err := os.Mkdir(mnt1, 0755); err != nil {
4436
+		c.Fatal(err)
4437
+	}
4438
+	if err := os.Mkdir(mnt2, 0755); err != nil {
4439
+		c.Fatal(err)
4440
+	}
4441
+	if err := ioutil.WriteFile(path.Join(mnt1, "test1"), []byte("test1"), 0644); err != nil {
4442
+		c.Fatal(err)
4443
+	}
4444
+	if err := ioutil.WriteFile(path.Join(mnt2, "test2"), []byte("test2"), 0644); err != nil {
4445
+		c.Fatal(err)
4446
+	}
4447
+	testCatFooBar := func(cName string) error {
4448
+		out, _ := dockerCmd(c, "exec", cName, "cat", "/foo/test1")
4449
+		if out != "test1" {
4450
+			return fmt.Errorf("%s not mounted on /foo", mnt1)
4451
+		}
4452
+		out, _ = dockerCmd(c, "exec", cName, "cat", "/bar/test2")
4453
+		if out != "test2" {
4454
+			return fmt.Errorf("%s not mounted on /bar", mnt2)
4455
+		}
4456
+		return nil
4457
+	}
4458
+
4459
+	type testCase struct {
4460
+		equivalents [][]string
4461
+		valid       bool
4462
+		// fn should be nil if valid==false
4463
+		fn func(cName string) error
4464
+	}
4465
+	cases := []testCase{
4466
+		{
4467
+			equivalents: [][]string{
4468
+				{
4469
+					"--mount", fmt.Sprintf("type=bind,src=%s,dst=/foo", mnt1),
4470
+					"--mount", fmt.Sprintf("type=bind,src=%s,dst=/bar", mnt2),
4471
+				},
4472
+				{
4473
+					"--mount", fmt.Sprintf("type=bind,src=%s,dst=/foo", mnt1),
4474
+					"--mount", fmt.Sprintf("type=bind,src=%s,target=/bar", mnt2),
4475
+				},
4476
+				{
4477
+					"--volume", mnt1 + ":/foo",
4478
+					"--mount", fmt.Sprintf("type=bind,src=%s,target=/bar", mnt2),
4479
+				},
4480
+			},
4481
+			valid: true,
4482
+			fn:    testCatFooBar,
4483
+		},
4484
+		{
4485
+			equivalents: [][]string{
4486
+				{
4487
+					"--mount", fmt.Sprintf("type=volume,src=%s,dst=/foo", mnt1),
4488
+					"--mount", fmt.Sprintf("type=volume,src=%s,dst=/bar", mnt2),
4489
+				},
4490
+				{
4491
+					"--mount", fmt.Sprintf("type=volume,src=%s,dst=/foo", mnt1),
4492
+					"--mount", fmt.Sprintf("type=volume,src=%s,target=/bar", mnt2),
4493
+				},
4494
+			},
4495
+			valid: false,
4496
+		},
4497
+		{
4498
+			equivalents: [][]string{
4499
+				{
4500
+					"--mount", fmt.Sprintf("type=bind,src=%s,dst=/foo", mnt1),
4501
+					"--mount", fmt.Sprintf("type=volume,src=%s,dst=/bar", mnt2),
4502
+				},
4503
+				{
4504
+					"--volume", mnt1 + ":/foo",
4505
+					"--mount", fmt.Sprintf("type=volume,src=%s,target=/bar", mnt2),
4506
+				},
4507
+			},
4508
+			valid: false,
4509
+			fn:    testCatFooBar,
4510
+		},
4511
+		{
4512
+			equivalents: [][]string{
4513
+				{
4514
+					"--read-only",
4515
+					"--mount", "type=volume,dst=/bar",
4516
+				},
4517
+			},
4518
+			valid: true,
4519
+			fn: func(cName string) error {
4520
+				_, _, err := dockerCmdWithError("exec", cName, "touch", "/bar/icanwritehere")
4521
+				return err
4522
+			},
4523
+		},
4524
+		{
4525
+			equivalents: [][]string{
4526
+				{
4527
+					"--read-only",
4528
+					"--mount", fmt.Sprintf("type=bind,src=%s,dst=/foo", mnt1),
4529
+					"--mount", "type=volume,dst=/bar",
4530
+				},
4531
+				{
4532
+					"--read-only",
4533
+					"--volume", fmt.Sprintf("%s:/foo", mnt1),
4534
+					"--mount", "type=volume,dst=/bar",
4535
+				},
4536
+			},
4537
+			valid: true,
4538
+			fn: func(cName string) error {
4539
+				out, _ := dockerCmd(c, "exec", cName, "cat", "/foo/test1")
4540
+				if out != "test1" {
4541
+					return fmt.Errorf("%s not mounted on /foo", mnt1)
4542
+				}
4543
+				_, _, err := dockerCmdWithError("exec", cName, "touch", "/bar/icanwritehere")
4544
+				return err
4545
+			},
4546
+		},
4547
+		{
4548
+			equivalents: [][]string{
4549
+				{
4550
+					"--mount", fmt.Sprintf("type=bind,src=%s,dst=/foo", mnt1),
4551
+					"--mount", fmt.Sprintf("type=bind,src=%s,dst=/foo", mnt2),
4552
+				},
4553
+				{
4554
+					"--mount", fmt.Sprintf("type=bind,src=%s,dst=/foo", mnt1),
4555
+					"--mount", fmt.Sprintf("type=bind,src=%s,target=/foo", mnt2),
4556
+				},
4557
+				{
4558
+					"--volume", fmt.Sprintf("%s:/foo", mnt1),
4559
+					"--mount", fmt.Sprintf("type=bind,src=%s,target=/foo", mnt2),
4560
+				},
4561
+			},
4562
+			valid: false,
4563
+		},
4564
+		{
4565
+			equivalents: [][]string{
4566
+				{
4567
+					"--volume", fmt.Sprintf("%s:/foo", mnt1),
4568
+					"--mount", fmt.Sprintf("type=volume,src=%s,target=/foo", mnt2),
4569
+				},
4570
+			},
4571
+			valid: false,
4572
+		},
4573
+		{
4574
+			equivalents: [][]string{
4575
+				{
4576
+					"--mount", "type=volume,target=/foo",
4577
+					"--mount", "type=volume,target=/foo",
4578
+				},
4579
+			},
4580
+			valid: false,
4581
+		},
4582
+	}
4583
+
4584
+	for i, testCase := range cases {
4585
+		for j, opts := range testCase.equivalents {
4586
+			cName := fmt.Sprintf("mount-%d-%d", i, j)
4587
+			_, _, err := dockerCmdWithError(append([]string{"run", "-i", "-d", "--name", cName},
4588
+				append(opts, []string{"busybox", "top"}...)...)...)
4589
+			if testCase.valid {
4590
+				c.Assert(err, check.IsNil,
4591
+					check.Commentf("got error while creating a container with %v (%s)", opts, cName))
4592
+				c.Assert(testCase.fn(cName), check.IsNil,
4593
+					check.Commentf("got error while executing test for %v (%s)", opts, cName))
4594
+				dockerCmd(c, "rm", "-f", cName)
4595
+			} else {
4596
+				c.Assert(err, checker.NotNil,
4597
+					check.Commentf("got nil while creating a container with %v (%s)", opts, cName))
4598
+			}
4599
+		}
4600
+	}
4601
+}
4602
+
4425 4603
 // Test that passing a FQDN as hostname properly sets hostname, and
4426 4604
 // /etc/hostname. Test case for 29100
4427 4605
 func (s *DockerSuite) TestRunHostnameFQDN(c *check.C) {
... ...
@@ -61,6 +61,7 @@ docker-run - Run a command in a new container
61 61
 [**--memory-reservation**[=*MEMORY-RESERVATION*]]
62 62
 [**--memory-swap**[=*LIMIT*]]
63 63
 [**--memory-swappiness**[=*MEMORY-SWAPPINESS*]]
64
+[**--mount**[=*[MOUNT]*]]
64 65
 [**--name**[=*NAME*]]
65 66
 [**--network-alias**[=*[]*]]
66 67
 [**--network**[=*"bridge"*]]
... ...
@@ -425,6 +426,42 @@ unit, `b` is used. Set LIMIT to `-1` to enable unlimited swap.
425 425
 The IPv6 link-local address will be based on the device's MAC address
426 426
 according to RFC4862.
427 427
 
428
+**--mount**=[*[type=TYPE[,TYPE-SPECIFIC-OPTIONS]]*]
429
+   Attach a filesystem mount to the container
430
+
431
+   Current supported mount `TYPES` are `bind`, `volume`, and `tmpfs`.
432
+
433
+   e.g.
434
+
435
+   `type=bind,source=/path/on/host,destination=/path/in/container`
436
+
437
+   `type=volume,source=my-volume,destination=/path/in/container,volume-label="color=red",volume-label="shape=round"`
438
+
439
+   `type=tmpfs,tmpfs-size=512M,destination=/path/in/container`
440
+
441
+   Common Options:
442
+
443
+   * `src`, `source`: mount source spec for `bind` and `volume`. Mandatory for `bind`.
444
+   * `dst`, `destination`, `target`: mount destination spec.
445
+   * `ro`, `read-only`: `true` or `false` (default).
446
+
447
+   Options specific to `bind`:
448
+
449
+   * `bind-propagation`: `shared`, `slave`, `private`, `rshared`, `rslave`, or `rprivate`(default). See also `mount(2)`.
450
+   * `consistency`: `consistent`(default), `cached`, or `delegated`. Currently, only effective for Docker for Mac.
451
+
452
+   Options specific to `volume`:
453
+
454
+   * `volume-driver`: Name of the volume-driver plugin.
455
+   * `volume-label`: Custom metadata.
456
+   * `volume-nocopy`: `true`(default) or `false`. If set to `false`, the Engine copies existing files and directories under the mount-path into the volume, allowing the host to access them.
457
+   * `volume-opt`: specific to a given volume driver.
458
+
459
+   Options specific to `tmpfs`:
460
+
461
+   * `tmpfs-size`: Size of the tmpfs mount in bytes. Unlimited by default in Linux.
462
+   * `tmpfs-mode`: File mode of the tmpfs in octal. (e.g. `700` or `0700`.) Defaults to `1777` in Linux.
463
+
428 464
 **--name**=""
429 465
    Assign a name to the container
430 466
 
... ...
@@ -604,6 +641,9 @@ options are the same as the Linux default `mount` flags. If you do not specify
604 604
 any options, the systems uses the following options:
605 605
 `rw,noexec,nosuid,nodev,size=65536k`.
606 606
 
607
+   See also `--mount`, which is the successor of `--tmpfs` and `--volume`.
608
+   Even though there is no plan to deprecate `--tmpfs`, usage of `--mount` is recommended.
609
+
607 610
 **-u**, **--user**=""
608 611
    Sets the username or UID used and optionally the groupname or GID for the specified command.
609 612
 
... ...
@@ -704,6 +744,9 @@ change propagation properties of source mount. Say `/` is source mount for
704 704
 To disable automatic copying of data from the container path to the volume, use
705 705
 the `nocopy` flag. The `nocopy` flag can be set on bind mounts and named volumes.
706 706
 
707
+See also `--mount`, which is the successor of `--tmpfs` and `--volume`.
708
+Even though there is no plan to deprecate `--volume`, usage of `--mount` is recommended.
709
+
707 710
 **--volume-driver**=""
708 711
    Container's volume driver. This driver creates volumes specified either from
709 712
    a Dockerfile's `VOLUME` instruction or from the `docker run -v` flag.