Merge pull request #26837 from AkihiroSuda/newtmpfs
api: add TypeTmpfs to api/types/mount
This commit is contained in:
commit
87ddc8b0a5
14 changed files with 382 additions and 38 deletions
|
@ -1,24 +1,35 @@
|
|||
package mount
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// Type represents the type of a mount.
|
||||
type Type string
|
||||
|
||||
// Type constants
|
||||
const (
|
||||
// TypeBind BIND
|
||||
// TypeBind is the type for mounting host dir
|
||||
TypeBind Type = "bind"
|
||||
// TypeVolume VOLUME
|
||||
// TypeVolume is the type for remote storage volumes
|
||||
TypeVolume Type = "volume"
|
||||
// TypeTmpfs is the type for mounting tmpfs
|
||||
TypeTmpfs Type = "tmpfs"
|
||||
)
|
||||
|
||||
// Mount represents a mount (volume).
|
||||
type Mount struct {
|
||||
Type Type `json:",omitempty"`
|
||||
Type Type `json:",omitempty"`
|
||||
// Source specifies the name of the mount. Depending on mount type, this
|
||||
// may be a volume name or a host path, or even ignored.
|
||||
// Source is not supported for tmpfs (must be an empty value)
|
||||
Source string `json:",omitempty"`
|
||||
Target string `json:",omitempty"`
|
||||
ReadOnly bool `json:",omitempty"`
|
||||
|
||||
BindOptions *BindOptions `json:",omitempty"`
|
||||
VolumeOptions *VolumeOptions `json:",omitempty"`
|
||||
TmpfsOptions *TmpfsOptions `json:",omitempty"`
|
||||
}
|
||||
|
||||
// Propagation represents the propagation of a mount.
|
||||
|
@ -56,3 +67,37 @@ type Driver struct {
|
|||
Name string `json:",omitempty"`
|
||||
Options map[string]string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// TmpfsOptions defines options specific to mounts of type "tmpfs".
|
||||
type TmpfsOptions struct {
|
||||
// Size sets the size of the tmpfs, in bytes.
|
||||
//
|
||||
// This will be converted to an operating system specific value
|
||||
// depending on the host. For example, on linux, it will be convered to
|
||||
// use a 'k', 'm' or 'g' syntax. BSD, though not widely supported with
|
||||
// docker, uses a straight byte value.
|
||||
//
|
||||
// Percentages are not supported.
|
||||
SizeBytes int64 `json:",omitempty"`
|
||||
// Mode of the tmpfs upon creation
|
||||
Mode os.FileMode `json:",omitempty"`
|
||||
|
||||
// TODO(stevvooe): There are several more tmpfs flags, specified in the
|
||||
// daemon, that are accepted. Only the most basic are added for now.
|
||||
//
|
||||
// From docker/docker/pkg/mount/flags.go:
|
||||
//
|
||||
// var validFlags = map[string]bool{
|
||||
// "": true,
|
||||
// "size": true, X
|
||||
// "mode": true, X
|
||||
// "uid": true,
|
||||
// "gid": true,
|
||||
// "nr_inodes": true,
|
||||
// "nr_blocks": true,
|
||||
// "mpol": true,
|
||||
// }
|
||||
//
|
||||
// Some of these may be straightforward to add, but others, such as
|
||||
// uid/gid have implications in a clustered system.
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
"github.com/Sirupsen/logrus"
|
||||
containertypes "github.com/docker/docker/api/types/container"
|
||||
mounttypes "github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/pkg/chrootarchive"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/pkg/symlink"
|
||||
|
@ -406,7 +407,7 @@ func copyOwnership(source, destination string) error {
|
|||
}
|
||||
|
||||
// TmpfsMounts returns the list of tmpfs mounts
|
||||
func (container *Container) TmpfsMounts() []Mount {
|
||||
func (container *Container) TmpfsMounts() ([]Mount, error) {
|
||||
var mounts []Mount
|
||||
for dest, data := range container.HostConfig.Tmpfs {
|
||||
mounts = append(mounts, Mount{
|
||||
|
@ -415,7 +416,20 @@ func (container *Container) TmpfsMounts() []Mount {
|
|||
Data: data,
|
||||
})
|
||||
}
|
||||
return mounts
|
||||
for dest, mnt := range container.MountPoints {
|
||||
if mnt.Type == mounttypes.TypeTmpfs {
|
||||
data, err := volume.ConvertTmpfsOptions(mnt.Spec.TmpfsOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mounts = append(mounts, Mount{
|
||||
Source: "tmpfs",
|
||||
Destination: dest,
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
}
|
||||
return mounts, nil
|
||||
}
|
||||
|
||||
// cleanResourcePath cleans a resource path and prepares to combine with mnt path
|
||||
|
|
|
@ -82,9 +82,9 @@ func (container *Container) UnmountVolumes(forceSyscall bool, volumeEventLog fun
|
|||
}
|
||||
|
||||
// TmpfsMounts returns the list of tmpfs mounts
|
||||
func (container *Container) TmpfsMounts() []Mount {
|
||||
func (container *Container) TmpfsMounts() ([]Mount, error) {
|
||||
var mounts []Mount
|
||||
return mounts
|
||||
return mounts, nil
|
||||
}
|
||||
|
||||
// UpdateContainer updates configuration of a container
|
||||
|
|
|
@ -473,7 +473,7 @@ func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []c
|
|||
}
|
||||
|
||||
if m.Source == "tmpfs" {
|
||||
data := c.HostConfig.Tmpfs[m.Destination]
|
||||
data := m.Data
|
||||
options := []string{"noexec", "nosuid", "nodev", string(volume.DefaultPropagationMode)}
|
||||
if data != "" {
|
||||
options = append(options, strings.Split(data, ",")...)
|
||||
|
@ -707,7 +707,11 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
|
|||
return nil, err
|
||||
}
|
||||
ms = append(ms, c.IpcMounts()...)
|
||||
ms = append(ms, c.TmpfsMounts()...)
|
||||
tmpfsMounts, err := c.TmpfsMounts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ms = append(ms, tmpfsMounts...)
|
||||
sort.Sort(mounts(ms))
|
||||
if err := setMounts(daemon, &s, c, ms); err != nil {
|
||||
return nil, fmt.Errorf("linux mounts: %v", err)
|
||||
|
|
|
@ -24,7 +24,11 @@ func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, er
|
|||
var mounts []container.Mount
|
||||
// TODO: tmpfs mounts should be part of Mountpoints
|
||||
tmpfsMounts := make(map[string]bool)
|
||||
for _, m := range c.TmpfsMounts() {
|
||||
tmpfsMountInfo, err := c.TmpfsMounts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, m := range tmpfsMountInfo {
|
||||
tmpfsMounts[m.Destination] = true
|
||||
}
|
||||
for _, m := range c.MountPoints {
|
||||
|
|
|
@ -139,7 +139,7 @@ This section lists each version from latest to oldest. Each listing includes a
|
|||
* `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.
|
||||
* `POST /containers/create/` and `POST /containers/(name)/update` now validates restart policies.
|
||||
* `POST /containers/create` now validates IPAMConfig in NetworkingConfig, and returns error for invalid IPv4 and IPv6 addresses (`--ip` and `--ip6` in `docker create/run`).
|
||||
* `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`
|
||||
* `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`.
|
||||
* `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.
|
||||
* `POST /build` accepts `cachefrom` parameter to specify images used for build cache.
|
||||
* `GET /networks/` endpoint now correctly returns a list of *all* networks,
|
||||
|
|
|
@ -511,10 +511,11 @@ Create a container
|
|||
- **Mounts** – Specification for mounts to be added to the container.
|
||||
- **Target** – Container path.
|
||||
- **Source** – Mount source (e.g. a volume name, a host path).
|
||||
- **Type** – The mount type (`bind`, or `volume`).
|
||||
- **Type** – The mount type (`bind`, `volume`, or `tmpfs`).
|
||||
Available types (for the `Type` field):
|
||||
- **bind** - Mounts a file or directory from the host into the container. Must exist prior to creating the container.
|
||||
- **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.
|
||||
- **tmpfs** - Create a tmpfs with the given options. The mount source cannot be specified for tmpfs.
|
||||
- **ReadOnly** – A boolean indicating whether the mount should be read-only.
|
||||
- **BindOptions** - Optional configuration for the `bind` type.
|
||||
- **Propagation** – A propagation mode with the value `[r]private`, `[r]shared`, or `[r]slave`.
|
||||
|
@ -525,6 +526,9 @@ Create a container
|
|||
- **DriverConfig** – Map of driver-specific options.
|
||||
- **Name** - Name of the driver to use to create the volume.
|
||||
- **Options** - key/value map of driver specific options.
|
||||
- **TmpfsOptions** – Optional configuration for the `tmpfs` type.
|
||||
- **SizeBytes** – The size for the tmpfs mount in bytes.
|
||||
- **Mode** – The permission mode for the tmpfs mount in an integer.
|
||||
|
||||
|
||||
**Query parameters**:
|
||||
|
|
|
@ -1569,13 +1569,80 @@ func (s *DockerSuite) TestContainersAPICreateMountsValidation(c *check.C) {
|
|||
notExistPath := prefix + slash + "notexist"
|
||||
|
||||
cases := []testCase{
|
||||
{cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "notreal", Target: destPath}}}}, http.StatusBadRequest, "mount type unknown"},
|
||||
{cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "bind"}}}}, http.StatusBadRequest, "Target must not be empty"},
|
||||
{cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "bind", Target: destPath}}}}, http.StatusBadRequest, "Source must not be empty"},
|
||||
{cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "bind", Source: notExistPath, Target: destPath}}}}, http.StatusBadRequest, "bind source path does not exist"},
|
||||
{cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "volume"}}}}, http.StatusBadRequest, "Target must not be empty"},
|
||||
{cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "volume", Source: "hello", Target: destPath}}}}, http.StatusCreated, ""},
|
||||
{cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "volume", Source: "hello2", Target: destPath, VolumeOptions: &mounttypes.VolumeOptions{DriverConfig: &mounttypes.Driver{Name: "local"}}}}}}, http.StatusCreated, ""},
|
||||
{
|
||||
config: cfg{
|
||||
Image: "busybox",
|
||||
HostConfig: hc{
|
||||
Mounts: []m{{
|
||||
Type: "notreal",
|
||||
Target: destPath}}}},
|
||||
status: http.StatusBadRequest,
|
||||
msg: "mount type unknown",
|
||||
},
|
||||
{
|
||||
config: cfg{
|
||||
Image: "busybox",
|
||||
HostConfig: hc{
|
||||
Mounts: []m{{
|
||||
Type: "bind"}}}},
|
||||
status: http.StatusBadRequest,
|
||||
msg: "Target must not be empty",
|
||||
},
|
||||
{
|
||||
config: cfg{
|
||||
Image: "busybox",
|
||||
HostConfig: hc{
|
||||
Mounts: []m{{
|
||||
Type: "bind",
|
||||
Target: destPath}}}},
|
||||
status: http.StatusBadRequest,
|
||||
msg: "Source must not be empty",
|
||||
},
|
||||
{
|
||||
config: cfg{
|
||||
Image: "busybox",
|
||||
HostConfig: hc{
|
||||
Mounts: []m{{
|
||||
Type: "bind",
|
||||
Source: notExistPath,
|
||||
Target: destPath}}}},
|
||||
status: http.StatusBadRequest,
|
||||
msg: "bind source path does not exist",
|
||||
},
|
||||
{
|
||||
config: cfg{
|
||||
Image: "busybox",
|
||||
HostConfig: hc{
|
||||
Mounts: []m{{
|
||||
Type: "volume"}}}},
|
||||
status: http.StatusBadRequest,
|
||||
msg: "Target must not be empty",
|
||||
},
|
||||
{
|
||||
config: cfg{
|
||||
Image: "busybox",
|
||||
HostConfig: hc{
|
||||
Mounts: []m{{
|
||||
Type: "volume",
|
||||
Source: "hello",
|
||||
Target: destPath}}}},
|
||||
status: http.StatusCreated,
|
||||
msg: "",
|
||||
},
|
||||
{
|
||||
config: cfg{
|
||||
Image: "busybox",
|
||||
HostConfig: hc{
|
||||
Mounts: []m{{
|
||||
Type: "volume",
|
||||
Source: "hello2",
|
||||
Target: destPath,
|
||||
VolumeOptions: &mounttypes.VolumeOptions{
|
||||
DriverConfig: &mounttypes.Driver{
|
||||
Name: "local"}}}}}},
|
||||
status: http.StatusCreated,
|
||||
msg: "",
|
||||
},
|
||||
}
|
||||
|
||||
if SameHostDaemon.Condition() {
|
||||
|
@ -1583,14 +1650,85 @@ func (s *DockerSuite) TestContainersAPICreateMountsValidation(c *check.C) {
|
|||
c.Assert(err, checker.IsNil)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
cases = append(cases, []testCase{
|
||||
{cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "bind", Source: tmpDir, Target: destPath}}}}, http.StatusCreated, ""},
|
||||
{cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "bind", Source: tmpDir, Target: destPath, VolumeOptions: &mounttypes.VolumeOptions{}}}}}, http.StatusBadRequest, "VolumeOptions must not be specified"},
|
||||
{
|
||||
config: cfg{
|
||||
Image: "busybox",
|
||||
HostConfig: hc{
|
||||
Mounts: []m{{
|
||||
Type: "bind",
|
||||
Source: tmpDir,
|
||||
Target: destPath}}}},
|
||||
status: http.StatusCreated,
|
||||
msg: "",
|
||||
},
|
||||
{
|
||||
config: cfg{
|
||||
Image: "busybox",
|
||||
HostConfig: hc{
|
||||
Mounts: []m{{
|
||||
Type: "bind",
|
||||
Source: tmpDir,
|
||||
Target: destPath,
|
||||
VolumeOptions: &mounttypes.VolumeOptions{}}}}},
|
||||
status: http.StatusBadRequest,
|
||||
msg: "VolumeOptions must not be specified",
|
||||
},
|
||||
}...)
|
||||
}
|
||||
|
||||
if DaemonIsLinux.Condition() {
|
||||
cases = append(cases, []testCase{
|
||||
{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, ""},
|
||||
{
|
||||
config: 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"}}}}}}},
|
||||
status: http.StatusCreated,
|
||||
msg: "",
|
||||
},
|
||||
{
|
||||
config: cfg{
|
||||
Image: "busybox",
|
||||
HostConfig: hc{
|
||||
Mounts: []m{{
|
||||
Type: "tmpfs",
|
||||
Target: destPath}}}},
|
||||
status: http.StatusCreated,
|
||||
msg: "",
|
||||
},
|
||||
{
|
||||
config: cfg{
|
||||
Image: "busybox",
|
||||
HostConfig: hc{
|
||||
Mounts: []m{{
|
||||
Type: "tmpfs",
|
||||
Target: destPath,
|
||||
TmpfsOptions: &mounttypes.TmpfsOptions{
|
||||
SizeBytes: 4096 * 1024,
|
||||
Mode: 0700,
|
||||
}}}}},
|
||||
status: http.StatusCreated,
|
||||
msg: "",
|
||||
},
|
||||
|
||||
{
|
||||
config: cfg{
|
||||
Image: "busybox",
|
||||
HostConfig: hc{
|
||||
Mounts: []m{{
|
||||
Type: "tmpfs",
|
||||
Source: "/shouldnotbespecified",
|
||||
Target: destPath}}}},
|
||||
status: http.StatusBadRequest,
|
||||
msg: "Source must not be specified",
|
||||
},
|
||||
}...)
|
||||
|
||||
}
|
||||
|
@ -1759,3 +1897,45 @@ func (s *DockerSuite) TestContainersAPICreateMountsCreate(c *check.C) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestContainersAPICreateMountsTmpfs(c *check.C) {
|
||||
testRequires(c, DaemonIsLinux)
|
||||
type testCase struct {
|
||||
cfg map[string]interface{}
|
||||
expectedOptions []string
|
||||
}
|
||||
target := "/foo"
|
||||
cases := []testCase{
|
||||
{
|
||||
cfg: map[string]interface{}{
|
||||
"Type": "tmpfs",
|
||||
"Target": target},
|
||||
expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime"},
|
||||
},
|
||||
{
|
||||
cfg: map[string]interface{}{
|
||||
"Type": "tmpfs",
|
||||
"Target": target,
|
||||
"TmpfsOptions": map[string]interface{}{
|
||||
"SizeBytes": 4096 * 1024, "Mode": 0700}},
|
||||
expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime", "size=4096k", "mode=700"},
|
||||
},
|
||||
}
|
||||
|
||||
for i, x := range cases {
|
||||
cName := fmt.Sprintf("test-tmpfs-%d", i)
|
||||
data := map[string]interface{}{
|
||||
"Image": "busybox",
|
||||
"Cmd": []string{"/bin/sh", "-c",
|
||||
fmt.Sprintf("mount | grep 'tmpfs on %s'", target)},
|
||||
"HostConfig": map[string]interface{}{"Mounts": []map[string]interface{}{x.cfg}},
|
||||
}
|
||||
status, resp, err := sockRequest("POST", "/containers/create?name="+cName, data)
|
||||
c.Assert(err, checker.IsNil, check.Commentf(string(resp)))
|
||||
c.Assert(status, checker.Equals, http.StatusCreated, check.Commentf(string(resp)))
|
||||
out, _ := dockerCmd(c, "start", "-a", cName)
|
||||
for _, option := range x.expectedOptions {
|
||||
c.Assert(out, checker.Contains, option)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ func DecodeContainerConfig(src io.Reader) (*container.Config, *container.HostCon
|
|||
}
|
||||
|
||||
// Now validate all the volumes and binds
|
||||
if err := validateVolumesAndBindSettings(w.Config, hc); err != nil {
|
||||
if err := validateMountSettings(w.Config, hc); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
@ -76,22 +76,10 @@ func DecodeContainerConfig(src io.Reader) (*container.Config, *container.HostCon
|
|||
return w.Config, hc, w.NetworkingConfig, nil
|
||||
}
|
||||
|
||||
// validateVolumesAndBindSettings validates each of the volumes and bind settings
|
||||
// validateMountSettings validates each of the volumes and bind settings
|
||||
// passed by the caller to ensure they are valid.
|
||||
func validateVolumesAndBindSettings(c *container.Config, hc *container.HostConfig) error {
|
||||
if len(hc.Mounts) > 0 {
|
||||
if len(hc.Binds) > 0 {
|
||||
return conflictError(fmt.Errorf("must not specify both Binds and Mounts"))
|
||||
}
|
||||
|
||||
if len(c.Volumes) > 0 {
|
||||
return conflictError(fmt.Errorf("must not specify both Volumes and Mounts"))
|
||||
}
|
||||
|
||||
if len(hc.VolumeDriver) > 0 {
|
||||
return conflictError(fmt.Errorf("must not specify both VolumeDriver and Mounts"))
|
||||
}
|
||||
}
|
||||
func validateMountSettings(c *container.Config, hc *container.HostConfig) error {
|
||||
// it is ok to have len(hc.Mounts) > 0 && (len(hc.Binds) > 0 || len (c.Volumes) > 0 || len (hc.Tmpfs) > 0 )
|
||||
|
||||
// Ensure all volumes and binds are valid.
|
||||
for spec := range c.Volumes {
|
||||
|
|
|
@ -87,6 +87,13 @@ func validateMountConfig(mnt *mount.Mount, options ...func(*validateOpts)) error
|
|||
return &errMountConfig{mnt, err}
|
||||
}
|
||||
}
|
||||
case mount.TypeTmpfs:
|
||||
if len(mnt.Source) != 0 {
|
||||
return &errMountConfig{mnt, errExtraField("Source")}
|
||||
}
|
||||
if _, err := ConvertTmpfsOptions(mnt.TmpfsOptions); err != nil {
|
||||
return &errMountConfig{mnt, err}
|
||||
}
|
||||
default:
|
||||
return &errMountConfig{mnt, errors.New("mount type unknown")}
|
||||
}
|
||||
|
|
|
@ -286,6 +286,8 @@ func ParseMountSpec(cfg mounttypes.Mount, options ...func(*validateOpts)) (*Moun
|
|||
mp.Propagation = cfg.BindOptions.Propagation
|
||||
}
|
||||
}
|
||||
case mounttypes.TypeTmpfs:
|
||||
// NOP
|
||||
}
|
||||
return mp, nil
|
||||
}
|
||||
|
|
57
volume/volume_linux.go
Normal file
57
volume/volume_linux.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
// +build linux
|
||||
|
||||
package volume
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
mounttypes "github.com/docker/docker/api/types/mount"
|
||||
)
|
||||
|
||||
// ConvertTmpfsOptions converts *mounttypes.TmpfsOptions to the raw option string
|
||||
// for mount(2).
|
||||
// The logic is copy-pasted from daemon/cluster/executer/container.getMountMask.
|
||||
// It will be deduplicated when we migrated the cluster to the new mount scheme.
|
||||
func ConvertTmpfsOptions(opt *mounttypes.TmpfsOptions) (string, error) {
|
||||
if opt == nil {
|
||||
return "", nil
|
||||
}
|
||||
var rawOpts []string
|
||||
if opt.Mode != 0 {
|
||||
rawOpts = append(rawOpts, fmt.Sprintf("mode=%o", opt.Mode))
|
||||
}
|
||||
|
||||
if opt.SizeBytes != 0 {
|
||||
// calculate suffix here, making this linux specific, but that is
|
||||
// okay, since API is that way anyways.
|
||||
|
||||
// we do this by finding the suffix that divides evenly into the
|
||||
// value, returing the value itself, with no suffix, if it fails.
|
||||
//
|
||||
// For the most part, we don't enforce any semantic to this values.
|
||||
// The operating system will usually align this and enforce minimum
|
||||
// and maximums.
|
||||
var (
|
||||
size = opt.SizeBytes
|
||||
suffix string
|
||||
)
|
||||
for _, r := range []struct {
|
||||
suffix string
|
||||
divisor int64
|
||||
}{
|
||||
{"g", 1 << 30},
|
||||
{"m", 1 << 20},
|
||||
{"k", 1 << 10},
|
||||
} {
|
||||
if size%r.divisor == 0 {
|
||||
size = size / r.divisor
|
||||
suffix = r.suffix
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
rawOpts = append(rawOpts, fmt.Sprintf("size=%d%s", size, suffix))
|
||||
}
|
||||
return strings.Join(rawOpts, ","), nil
|
||||
}
|
23
volume/volume_linux_test.go
Normal file
23
volume/volume_linux_test.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
// +build linux
|
||||
|
||||
package volume
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
mounttypes "github.com/docker/docker/api/types/mount"
|
||||
)
|
||||
|
||||
func TestConvertTmpfsOptions(t *testing.T) {
|
||||
type testCase struct {
|
||||
opt mounttypes.TmpfsOptions
|
||||
}
|
||||
cases := []testCase{
|
||||
{mounttypes.TmpfsOptions{SizeBytes: 1024 * 1024, Mode: 0700}},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if _, err := ConvertTmpfsOptions(&c.opt); err != nil {
|
||||
t.Fatalf("could not convert %+v to string: %v", c.opt, err)
|
||||
}
|
||||
}
|
||||
}
|
16
volume/volume_unsupported.go
Normal file
16
volume/volume_unsupported.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
// +build !linux
|
||||
|
||||
package volume
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
mounttypes "github.com/docker/docker/api/types/mount"
|
||||
)
|
||||
|
||||
// ConvertTmpfsOptions converts *mounttypes.TmpfsOptions to the raw option string
|
||||
// for mount(2).
|
||||
func ConvertTmpfsOptions(opt *mounttypes.TmpfsOptions) (string, error) {
|
||||
return "", fmt.Errorf("%s does not support tmpfs", runtime.GOOS)
|
||||
}
|
Loading…
Reference in a new issue