Browse code

api: add TypeTmpfs to api/types/mount

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

Akihiro Suda authored on 2016/09/23 05:14:15
Showing 14 changed files
... ...
@@ -1,24 +1,35 @@
1 1
 package mount
2 2
 
3
+import (
4
+	"os"
5
+)
6
+
3 7
 // Type represents the type of a mount.
4 8
 type Type string
5 9
 
10
+// Type constants
6 11
 const (
7
-	// TypeBind BIND
12
+	// TypeBind is the type for mounting host dir
8 13
 	TypeBind Type = "bind"
9
-	// TypeVolume VOLUME
14
+	// TypeVolume is the type for remote storage volumes
10 15
 	TypeVolume Type = "volume"
16
+	// TypeTmpfs is the type for mounting tmpfs
17
+	TypeTmpfs Type = "tmpfs"
11 18
 )
12 19
 
13 20
 // Mount represents a mount (volume).
14 21
 type Mount struct {
15
-	Type     Type   `json:",omitempty"`
22
+	Type Type `json:",omitempty"`
23
+	// Source specifies the name of the mount. Depending on mount type, this
24
+	// may be a volume name or a host path, or even ignored.
25
+	// Source is not supported for tmpfs (must be an empty value)
16 26
 	Source   string `json:",omitempty"`
17 27
 	Target   string `json:",omitempty"`
18 28
 	ReadOnly bool   `json:",omitempty"`
19 29
 
20 30
 	BindOptions   *BindOptions   `json:",omitempty"`
21 31
 	VolumeOptions *VolumeOptions `json:",omitempty"`
32
+	TmpfsOptions  *TmpfsOptions  `json:",omitempty"`
22 33
 }
23 34
 
24 35
 // Propagation represents the propagation of a mount.
... ...
@@ -56,3 +67,37 @@ type Driver struct {
56 56
 	Name    string            `json:",omitempty"`
57 57
 	Options map[string]string `json:",omitempty"`
58 58
 }
59
+
60
+// TmpfsOptions defines options specific to mounts of type "tmpfs".
61
+type TmpfsOptions struct {
62
+	// Size sets the size of the tmpfs, in bytes.
63
+	//
64
+	// This will be converted to an operating system specific value
65
+	// depending on the host. For example, on linux, it will be convered to
66
+	// use a 'k', 'm' or 'g' syntax. BSD, though not widely supported with
67
+	// docker, uses a straight byte value.
68
+	//
69
+	// Percentages are not supported.
70
+	SizeBytes int64 `json:",omitempty"`
71
+	// Mode of the tmpfs upon creation
72
+	Mode os.FileMode `json:",omitempty"`
73
+
74
+	// TODO(stevvooe): There are several more tmpfs flags, specified in the
75
+	// daemon, that are accepted. Only the most basic are added for now.
76
+	//
77
+	// From docker/docker/pkg/mount/flags.go:
78
+	//
79
+	// var validFlags = map[string]bool{
80
+	// 	"":          true,
81
+	// 	"size":      true, X
82
+	// 	"mode":      true, X
83
+	// 	"uid":       true,
84
+	// 	"gid":       true,
85
+	// 	"nr_inodes": true,
86
+	// 	"nr_blocks": true,
87
+	// 	"mpol":      true,
88
+	// }
89
+	//
90
+	// Some of these may be straightforward to add, but others, such as
91
+	// uid/gid have implications in a clustered system.
92
+}
... ...
@@ -12,6 +12,7 @@ import (
12 12
 
13 13
 	"github.com/Sirupsen/logrus"
14 14
 	containertypes "github.com/docker/docker/api/types/container"
15
+	mounttypes "github.com/docker/docker/api/types/mount"
15 16
 	"github.com/docker/docker/pkg/chrootarchive"
16 17
 	"github.com/docker/docker/pkg/stringid"
17 18
 	"github.com/docker/docker/pkg/symlink"
... ...
@@ -406,7 +407,7 @@ func copyOwnership(source, destination string) error {
406 406
 }
407 407
 
408 408
 // TmpfsMounts returns the list of tmpfs mounts
409
-func (container *Container) TmpfsMounts() []Mount {
409
+func (container *Container) TmpfsMounts() ([]Mount, error) {
410 410
 	var mounts []Mount
411 411
 	for dest, data := range container.HostConfig.Tmpfs {
412 412
 		mounts = append(mounts, Mount{
... ...
@@ -415,7 +416,20 @@ func (container *Container) TmpfsMounts() []Mount {
415 415
 			Data:        data,
416 416
 		})
417 417
 	}
418
-	return mounts
418
+	for dest, mnt := range container.MountPoints {
419
+		if mnt.Type == mounttypes.TypeTmpfs {
420
+			data, err := volume.ConvertTmpfsOptions(mnt.Spec.TmpfsOptions)
421
+			if err != nil {
422
+				return nil, err
423
+			}
424
+			mounts = append(mounts, Mount{
425
+				Source:      "tmpfs",
426
+				Destination: dest,
427
+				Data:        data,
428
+			})
429
+		}
430
+	}
431
+	return mounts, nil
419 432
 }
420 433
 
421 434
 // cleanResourcePath cleans a resource path and prepares to combine with mnt path
... ...
@@ -82,9 +82,9 @@ func (container *Container) UnmountVolumes(forceSyscall bool, volumeEventLog fun
82 82
 }
83 83
 
84 84
 // TmpfsMounts returns the list of tmpfs mounts
85
-func (container *Container) TmpfsMounts() []Mount {
85
+func (container *Container) TmpfsMounts() ([]Mount, error) {
86 86
 	var mounts []Mount
87
-	return mounts
87
+	return mounts, nil
88 88
 }
89 89
 
90 90
 // UpdateContainer updates configuration of a container
... ...
@@ -473,7 +473,7 @@ func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []c
473 473
 		}
474 474
 
475 475
 		if m.Source == "tmpfs" {
476
-			data := c.HostConfig.Tmpfs[m.Destination]
476
+			data := m.Data
477 477
 			options := []string{"noexec", "nosuid", "nodev", string(volume.DefaultPropagationMode)}
478 478
 			if data != "" {
479 479
 				options = append(options, strings.Split(data, ",")...)
... ...
@@ -707,7 +707,11 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
707 707
 		return nil, err
708 708
 	}
709 709
 	ms = append(ms, c.IpcMounts()...)
710
-	ms = append(ms, c.TmpfsMounts()...)
710
+	tmpfsMounts, err := c.TmpfsMounts()
711
+	if err != nil {
712
+		return nil, err
713
+	}
714
+	ms = append(ms, tmpfsMounts...)
711 715
 	sort.Sort(mounts(ms))
712 716
 	if err := setMounts(daemon, &s, c, ms); err != nil {
713 717
 		return nil, fmt.Errorf("linux mounts: %v", err)
... ...
@@ -24,7 +24,11 @@ func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, er
24 24
 	var mounts []container.Mount
25 25
 	// TODO: tmpfs mounts should be part of Mountpoints
26 26
 	tmpfsMounts := make(map[string]bool)
27
-	for _, m := range c.TmpfsMounts() {
27
+	tmpfsMountInfo, err := c.TmpfsMounts()
28
+	if err != nil {
29
+		return nil, err
30
+	}
31
+	for _, m := range tmpfsMountInfo {
28 32
 		tmpfsMounts[m.Destination] = true
29 33
 	}
30 34
 	for _, m := range c.MountPoints {
... ...
@@ -139,7 +139,7 @@ This section lists each version from latest to oldest.  Each listing includes a
139 139
 * `DELETE /volumes/(name)` now accepts a `force` query parameter to force removal of volumes that were already removed out of band by the volume driver plugin.
140 140
 * `POST /containers/create/` and `POST /containers/(name)/update` now validates restart policies.
141 141
 * `POST /containers/create` now validates IPAMConfig in NetworkingConfig, and returns error for invalid IPv4 and IPv6 addresses (`--ip` and `--ip6` in `docker create/run`).
142
-* `POST /containers/create` now takes a `Mounts` field in `HostConfig` which replaces `Binds` and `Volumes`. *note*: `Binds` and `Volumes` are still available but are exclusive with `Mounts`
142
+* `POST /containers/create` now takes a `Mounts` field in `HostConfig` which replaces `Binds`, `Volumes`, and `Tmpfs`. *note*: `Binds`, `Volumes`, and `Tmpfs` are still available and can be combined with `Mounts`.
143 143
 * `POST /build` now performs a preliminary validation of the `Dockerfile` before starting the build, and returns an error if the syntax is incorrect. Note that this change is _unversioned_ and applied to all API versions.
144 144
 * `POST /build` accepts `cachefrom` parameter to specify images used for build cache.
145 145
 * `GET /networks/` endpoint now correctly returns a list of *all* networks,
... ...
@@ -511,10 +511,11 @@ Create a container
511 511
     -   **Mounts** – Specification for mounts to be added to the container.
512 512
         - **Target** – Container path.
513 513
         - **Source** – Mount source (e.g. a volume name, a host path).
514
-        - **Type** – The mount type (`bind`, or `volume`).
514
+        - **Type** – The mount type (`bind`, `volume`, or `tmpfs`).
515 515
           Available types (for the `Type` field):
516 516
           - **bind** - Mounts a file or directory from the host into the container. Must exist prior to creating the container.
517 517
           - **volume** - Creates a volume with the given name and options (or uses a pre-existing volume with the same name and options). These are **not** removed when the container is removed.
518
+          - **tmpfs** - Create a tmpfs with the given options. The mount source cannot be specified for tmpfs.
518 519
         - **ReadOnly** – A boolean indicating whether the mount should be read-only.
519 520
         - **BindOptions** - Optional configuration for the `bind` type.
520 521
           - **Propagation** – A propagation mode with the value `[r]private`, `[r]shared`, or `[r]slave`.
... ...
@@ -525,6 +526,9 @@ Create a container
525 525
             - **DriverConfig** – Map of driver-specific options.
526 526
               - **Name** - Name of the driver to use to create the volume.
527 527
               - **Options** - key/value map of driver specific options.
528
+        - **TmpfsOptions** – Optional configuration for the `tmpfs` type.
529
+            - **SizeBytes** – The size for the tmpfs mount in bytes.
530
+            - **Mode** – The permission mode for the tmpfs mount in an integer.
528 531
 
529 532
 
530 533
 **Query parameters**:
... ...
@@ -1569,13 +1569,80 @@ func (s *DockerSuite) TestContainersAPICreateMountsValidation(c *check.C) {
1569 1569
 	notExistPath := prefix + slash + "notexist"
1570 1570
 
1571 1571
 	cases := []testCase{
1572
-		{cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "notreal", Target: destPath}}}}, http.StatusBadRequest, "mount type unknown"},
1573
-		{cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "bind"}}}}, http.StatusBadRequest, "Target must not be empty"},
1574
-		{cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "bind", Target: destPath}}}}, http.StatusBadRequest, "Source must not be empty"},
1575
-		{cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "bind", Source: notExistPath, Target: destPath}}}}, http.StatusBadRequest, "bind source path does not exist"},
1576
-		{cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "volume"}}}}, http.StatusBadRequest, "Target must not be empty"},
1577
-		{cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "volume", Source: "hello", Target: destPath}}}}, http.StatusCreated, ""},
1578
-		{cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "volume", Source: "hello2", Target: destPath, VolumeOptions: &mounttypes.VolumeOptions{DriverConfig: &mounttypes.Driver{Name: "local"}}}}}}, http.StatusCreated, ""},
1572
+		{
1573
+			config: cfg{
1574
+				Image: "busybox",
1575
+				HostConfig: hc{
1576
+					Mounts: []m{{
1577
+						Type:   "notreal",
1578
+						Target: destPath}}}},
1579
+			status: http.StatusBadRequest,
1580
+			msg:    "mount type unknown",
1581
+		},
1582
+		{
1583
+			config: cfg{
1584
+				Image: "busybox",
1585
+				HostConfig: hc{
1586
+					Mounts: []m{{
1587
+						Type: "bind"}}}},
1588
+			status: http.StatusBadRequest,
1589
+			msg:    "Target must not be empty",
1590
+		},
1591
+		{
1592
+			config: cfg{
1593
+				Image: "busybox",
1594
+				HostConfig: hc{
1595
+					Mounts: []m{{
1596
+						Type:   "bind",
1597
+						Target: destPath}}}},
1598
+			status: http.StatusBadRequest,
1599
+			msg:    "Source must not be empty",
1600
+		},
1601
+		{
1602
+			config: cfg{
1603
+				Image: "busybox",
1604
+				HostConfig: hc{
1605
+					Mounts: []m{{
1606
+						Type:   "bind",
1607
+						Source: notExistPath,
1608
+						Target: destPath}}}},
1609
+			status: http.StatusBadRequest,
1610
+			msg:    "bind source path does not exist",
1611
+		},
1612
+		{
1613
+			config: cfg{
1614
+				Image: "busybox",
1615
+				HostConfig: hc{
1616
+					Mounts: []m{{
1617
+						Type: "volume"}}}},
1618
+			status: http.StatusBadRequest,
1619
+			msg:    "Target must not be empty",
1620
+		},
1621
+		{
1622
+			config: cfg{
1623
+				Image: "busybox",
1624
+				HostConfig: hc{
1625
+					Mounts: []m{{
1626
+						Type:   "volume",
1627
+						Source: "hello",
1628
+						Target: destPath}}}},
1629
+			status: http.StatusCreated,
1630
+			msg:    "",
1631
+		},
1632
+		{
1633
+			config: cfg{
1634
+				Image: "busybox",
1635
+				HostConfig: hc{
1636
+					Mounts: []m{{
1637
+						Type:   "volume",
1638
+						Source: "hello2",
1639
+						Target: destPath,
1640
+						VolumeOptions: &mounttypes.VolumeOptions{
1641
+							DriverConfig: &mounttypes.Driver{
1642
+								Name: "local"}}}}}},
1643
+			status: http.StatusCreated,
1644
+			msg:    "",
1645
+		},
1579 1646
 	}
1580 1647
 
1581 1648
 	if SameHostDaemon.Condition() {
... ...
@@ -1583,14 +1650,85 @@ func (s *DockerSuite) TestContainersAPICreateMountsValidation(c *check.C) {
1583 1583
 		c.Assert(err, checker.IsNil)
1584 1584
 		defer os.RemoveAll(tmpDir)
1585 1585
 		cases = append(cases, []testCase{
1586
-			{cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "bind", Source: tmpDir, Target: destPath}}}}, http.StatusCreated, ""},
1587
-			{cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "bind", Source: tmpDir, Target: destPath, VolumeOptions: &mounttypes.VolumeOptions{}}}}}, http.StatusBadRequest, "VolumeOptions must not be specified"},
1586
+			{
1587
+				config: cfg{
1588
+					Image: "busybox",
1589
+					HostConfig: hc{
1590
+						Mounts: []m{{
1591
+							Type:   "bind",
1592
+							Source: tmpDir,
1593
+							Target: destPath}}}},
1594
+				status: http.StatusCreated,
1595
+				msg:    "",
1596
+			},
1597
+			{
1598
+				config: cfg{
1599
+					Image: "busybox",
1600
+					HostConfig: hc{
1601
+						Mounts: []m{{
1602
+							Type:          "bind",
1603
+							Source:        tmpDir,
1604
+							Target:        destPath,
1605
+							VolumeOptions: &mounttypes.VolumeOptions{}}}}},
1606
+				status: http.StatusBadRequest,
1607
+				msg:    "VolumeOptions must not be specified",
1608
+			},
1588 1609
 		}...)
1589 1610
 	}
1590 1611
 
1591 1612
 	if DaemonIsLinux.Condition() {
1592 1613
 		cases = append(cases, []testCase{
1593
-			{cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "volume", Source: "hello3", Target: destPath, VolumeOptions: &mounttypes.VolumeOptions{DriverConfig: &mounttypes.Driver{Name: "local", Options: map[string]string{"o": "size=1"}}}}}}}, http.StatusCreated, ""},
1614
+			{
1615
+				config: cfg{
1616
+					Image: "busybox",
1617
+					HostConfig: hc{
1618
+						Mounts: []m{{
1619
+							Type:   "volume",
1620
+							Source: "hello3",
1621
+							Target: destPath,
1622
+							VolumeOptions: &mounttypes.VolumeOptions{
1623
+								DriverConfig: &mounttypes.Driver{
1624
+									Name:    "local",
1625
+									Options: map[string]string{"o": "size=1"}}}}}}},
1626
+				status: http.StatusCreated,
1627
+				msg:    "",
1628
+			},
1629
+			{
1630
+				config: cfg{
1631
+					Image: "busybox",
1632
+					HostConfig: hc{
1633
+						Mounts: []m{{
1634
+							Type:   "tmpfs",
1635
+							Target: destPath}}}},
1636
+				status: http.StatusCreated,
1637
+				msg:    "",
1638
+			},
1639
+			{
1640
+				config: cfg{
1641
+					Image: "busybox",
1642
+					HostConfig: hc{
1643
+						Mounts: []m{{
1644
+							Type:   "tmpfs",
1645
+							Target: destPath,
1646
+							TmpfsOptions: &mounttypes.TmpfsOptions{
1647
+								SizeBytes: 4096 * 1024,
1648
+								Mode:      0700,
1649
+							}}}}},
1650
+				status: http.StatusCreated,
1651
+				msg:    "",
1652
+			},
1653
+
1654
+			{
1655
+				config: cfg{
1656
+					Image: "busybox",
1657
+					HostConfig: hc{
1658
+						Mounts: []m{{
1659
+							Type:   "tmpfs",
1660
+							Source: "/shouldnotbespecified",
1661
+							Target: destPath}}}},
1662
+				status: http.StatusBadRequest,
1663
+				msg:    "Source must not be specified",
1664
+			},
1594 1665
 		}...)
1595 1666
 
1596 1667
 	}
... ...
@@ -1759,3 +1897,45 @@ func (s *DockerSuite) TestContainersAPICreateMountsCreate(c *check.C) {
1759 1759
 		}
1760 1760
 	}
1761 1761
 }
1762
+
1763
+func (s *DockerSuite) TestContainersAPICreateMountsTmpfs(c *check.C) {
1764
+	testRequires(c, DaemonIsLinux)
1765
+	type testCase struct {
1766
+		cfg             map[string]interface{}
1767
+		expectedOptions []string
1768
+	}
1769
+	target := "/foo"
1770
+	cases := []testCase{
1771
+		{
1772
+			cfg: map[string]interface{}{
1773
+				"Type":   "tmpfs",
1774
+				"Target": target},
1775
+			expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime"},
1776
+		},
1777
+		{
1778
+			cfg: map[string]interface{}{
1779
+				"Type":   "tmpfs",
1780
+				"Target": target,
1781
+				"TmpfsOptions": map[string]interface{}{
1782
+					"SizeBytes": 4096 * 1024, "Mode": 0700}},
1783
+			expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime", "size=4096k", "mode=700"},
1784
+		},
1785
+	}
1786
+
1787
+	for i, x := range cases {
1788
+		cName := fmt.Sprintf("test-tmpfs-%d", i)
1789
+		data := map[string]interface{}{
1790
+			"Image": "busybox",
1791
+			"Cmd": []string{"/bin/sh", "-c",
1792
+				fmt.Sprintf("mount | grep 'tmpfs on %s'", target)},
1793
+			"HostConfig": map[string]interface{}{"Mounts": []map[string]interface{}{x.cfg}},
1794
+		}
1795
+		status, resp, err := sockRequest("POST", "/containers/create?name="+cName, data)
1796
+		c.Assert(err, checker.IsNil, check.Commentf(string(resp)))
1797
+		c.Assert(status, checker.Equals, http.StatusCreated, check.Commentf(string(resp)))
1798
+		out, _ := dockerCmd(c, "start", "-a", cName)
1799
+		for _, option := range x.expectedOptions {
1800
+			c.Assert(out, checker.Contains, option)
1801
+		}
1802
+	}
1803
+}
... ...
@@ -48,7 +48,7 @@ func DecodeContainerConfig(src io.Reader) (*container.Config, *container.HostCon
48 48
 		}
49 49
 
50 50
 		// Now validate all the volumes and binds
51
-		if err := validateVolumesAndBindSettings(w.Config, hc); err != nil {
51
+		if err := validateMountSettings(w.Config, hc); err != nil {
52 52
 			return nil, nil, nil, err
53 53
 		}
54 54
 	}
... ...
@@ -76,22 +76,10 @@ func DecodeContainerConfig(src io.Reader) (*container.Config, *container.HostCon
76 76
 	return w.Config, hc, w.NetworkingConfig, nil
77 77
 }
78 78
 
79
-// validateVolumesAndBindSettings validates each of the volumes and bind settings
79
+// validateMountSettings validates each of the volumes and bind settings
80 80
 // passed by the caller to ensure they are valid.
81
-func validateVolumesAndBindSettings(c *container.Config, hc *container.HostConfig) error {
82
-	if len(hc.Mounts) > 0 {
83
-		if len(hc.Binds) > 0 {
84
-			return conflictError(fmt.Errorf("must not specify both Binds and Mounts"))
85
-		}
86
-
87
-		if len(c.Volumes) > 0 {
88
-			return conflictError(fmt.Errorf("must not specify both Volumes and Mounts"))
89
-		}
90
-
91
-		if len(hc.VolumeDriver) > 0 {
92
-			return conflictError(fmt.Errorf("must not specify both VolumeDriver and Mounts"))
93
-		}
94
-	}
81
+func validateMountSettings(c *container.Config, hc *container.HostConfig) error {
82
+	// it is ok to have len(hc.Mounts) > 0 && (len(hc.Binds) > 0 || len (c.Volumes) > 0 || len (hc.Tmpfs) > 0 )
95 83
 
96 84
 	// Ensure all volumes and binds are valid.
97 85
 	for spec := range c.Volumes {
... ...
@@ -87,6 +87,13 @@ func validateMountConfig(mnt *mount.Mount, options ...func(*validateOpts)) error
87 87
 				return &errMountConfig{mnt, err}
88 88
 			}
89 89
 		}
90
+	case mount.TypeTmpfs:
91
+		if len(mnt.Source) != 0 {
92
+			return &errMountConfig{mnt, errExtraField("Source")}
93
+		}
94
+		if _, err := ConvertTmpfsOptions(mnt.TmpfsOptions); err != nil {
95
+			return &errMountConfig{mnt, err}
96
+		}
90 97
 	default:
91 98
 		return &errMountConfig{mnt, errors.New("mount type unknown")}
92 99
 	}
... ...
@@ -286,6 +286,8 @@ func ParseMountSpec(cfg mounttypes.Mount, options ...func(*validateOpts)) (*Moun
286 286
 				mp.Propagation = cfg.BindOptions.Propagation
287 287
 			}
288 288
 		}
289
+	case mounttypes.TypeTmpfs:
290
+		// NOP
289 291
 	}
290 292
 	return mp, nil
291 293
 }
292 294
new file mode 100644
... ...
@@ -0,0 +1,57 @@
0
+// +build linux
1
+
2
+package volume
3
+
4
+import (
5
+	"fmt"
6
+	"strings"
7
+
8
+	mounttypes "github.com/docker/docker/api/types/mount"
9
+)
10
+
11
+// ConvertTmpfsOptions converts *mounttypes.TmpfsOptions to the raw option string
12
+// for mount(2).
13
+// The logic is copy-pasted from daemon/cluster/executer/container.getMountMask.
14
+// It will be deduplicated when we migrated the cluster to the new mount scheme.
15
+func ConvertTmpfsOptions(opt *mounttypes.TmpfsOptions) (string, error) {
16
+	if opt == nil {
17
+		return "", nil
18
+	}
19
+	var rawOpts []string
20
+	if opt.Mode != 0 {
21
+		rawOpts = append(rawOpts, fmt.Sprintf("mode=%o", opt.Mode))
22
+	}
23
+
24
+	if opt.SizeBytes != 0 {
25
+		// calculate suffix here, making this linux specific, but that is
26
+		// okay, since API is that way anyways.
27
+
28
+		// we do this by finding the suffix that divides evenly into the
29
+		// value, returing the value itself, with no suffix, if it fails.
30
+		//
31
+		// For the most part, we don't enforce any semantic to this values.
32
+		// The operating system will usually align this and enforce minimum
33
+		// and maximums.
34
+		var (
35
+			size   = opt.SizeBytes
36
+			suffix string
37
+		)
38
+		for _, r := range []struct {
39
+			suffix  string
40
+			divisor int64
41
+		}{
42
+			{"g", 1 << 30},
43
+			{"m", 1 << 20},
44
+			{"k", 1 << 10},
45
+		} {
46
+			if size%r.divisor == 0 {
47
+				size = size / r.divisor
48
+				suffix = r.suffix
49
+				break
50
+			}
51
+		}
52
+
53
+		rawOpts = append(rawOpts, fmt.Sprintf("size=%d%s", size, suffix))
54
+	}
55
+	return strings.Join(rawOpts, ","), nil
56
+}
0 57
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+// +build linux
1
+
2
+package volume
3
+
4
+import (
5
+	"testing"
6
+
7
+	mounttypes "github.com/docker/docker/api/types/mount"
8
+)
9
+
10
+func TestConvertTmpfsOptions(t *testing.T) {
11
+	type testCase struct {
12
+		opt mounttypes.TmpfsOptions
13
+	}
14
+	cases := []testCase{
15
+		{mounttypes.TmpfsOptions{SizeBytes: 1024 * 1024, Mode: 0700}},
16
+	}
17
+	for _, c := range cases {
18
+		if _, err := ConvertTmpfsOptions(&c.opt); err != nil {
19
+			t.Fatalf("could not convert %+v to string: %v", c.opt, err)
20
+		}
21
+	}
22
+}
0 23
new file mode 100644
... ...
@@ -0,0 +1,16 @@
0
+// +build !linux
1
+
2
+package volume
3
+
4
+import (
5
+	"fmt"
6
+	"runtime"
7
+
8
+	mounttypes "github.com/docker/docker/api/types/mount"
9
+)
10
+
11
+// ConvertTmpfsOptions converts *mounttypes.TmpfsOptions to the raw option string
12
+// for mount(2).
13
+func ConvertTmpfsOptions(opt *mounttypes.TmpfsOptions) (string, error) {
14
+	return "", fmt.Errorf("%s does not support tmpfs", runtime.GOOS)
15
+}