diff --git a/cli/compose/convert/volume.go b/cli/compose/convert/volume.go index 38806d2972f2..70601acb0704 100644 --- a/cli/compose/convert/volume.go +++ b/cli/compose/convert/volume.go @@ -117,6 +117,7 @@ func handleTmpfsToMount(volume composetypes.ServiceVolumeConfig) (mount.Mount, e if volume.Tmpfs != nil { result.TmpfsOptions = &mount.TmpfsOptions{ SizeBytes: volume.Tmpfs.Size, + RawOptions: volume.Tmpfs.Options, } } return result, nil diff --git a/cli/compose/convert/volume_test.go b/cli/compose/convert/volume_test.go index d7d45eedf5d5..1d66ccc0d918 100644 --- a/cli/compose/convert/volume_test.go +++ b/cli/compose/convert/volume_test.go @@ -343,3 +343,21 @@ func TestConvertTmpfsToMountVolumeWithSource(t *testing.T) { _, err := convertVolumeToMount(config, volumes{}, NewNamespace("foo")) assert.Error(t, err, "invalid tmpfs source, source must be empty") } + +func TestConvertTmpfsToMountVolumeWithExecOption(t *testing.T) { + config := composetypes.ServiceVolumeConfig{ + Type: "tmpfs", + Target: "/foo/bar", + Tmpfs: &composetypes.ServiceVolumeTmpfs{ + Options: "exec", + }, + } + expected := mount.Mount{ + Type: mount.TypeTmpfs, + Target: "/foo/bar", + TmpfsOptions: &mount.TmpfsOptions{RawOptions: "exec"}, + } + mount, err := convertVolumeToMount(config, volumes{}, NewNamespace("foo")) + assert.NilError(t, err) + assert.Check(t, is.DeepEqual(expected, mount)) +} diff --git a/cli/compose/types/types.go b/cli/compose/types/types.go index 107dc8578437..4fe140d6501b 100644 --- a/cli/compose/types/types.go +++ b/cli/compose/types/types.go @@ -319,6 +319,7 @@ type ServiceVolumeVolume struct { // ServiceVolumeTmpfs are options for a service volume of type tmpfs type ServiceVolumeTmpfs struct { Size int64 `yaml:",omitempty"` + Options string `yaml:",omitempty"` } // FileReferenceConfig for a reference to a swarm file object diff --git a/opts/mount.go b/opts/mount.go index 3aa9849421ac..57fef432a969 100644 --- a/opts/mount.go +++ b/opts/mount.go @@ -126,6 +126,15 @@ func (m *MountOpt) Set(value string) error { return fmt.Errorf("invalid value for %s: %s", key, value) } tmpfsOptions().Mode = os.FileMode(ui64) + case "tmpfs-opt": + opt := strings.ToLower(value) + validOpts := map[string]bool { + "exec": true, + "noexec": true, + } + if _, ok := validOpts[opt]; ok { + tmpfsOptions().RawOptions = opt + } default: return fmt.Errorf("unexpected key '%s' in '%s'", key, field) } diff --git a/opts/mount_test.go b/opts/mount_test.go index bf7b0f2012e7..a72006d2a78a 100644 --- a/opts/mount_test.go +++ b/opts/mount_test.go @@ -183,3 +183,27 @@ func TestMountOptSetTmpfsError(t *testing.T) { assert.ErrorContains(t, m.Set("type=tmpfs,target=/foo,tmpfs-mode=foo"), "invalid value for tmpfs-mode") assert.ErrorContains(t, m.Set("type=tmpfs"), "target is required") } + +func TestMountOptSetTmpfsExecOpt(t *testing.T) { + for _, testcase := range []struct{ + opts string + expected string + }{ + {"type=tmpfs,target=/target,tmpfs-opt=exec", "exec"}, + {"type=tmpfs,target=/target,tmpfs-opt=noexec", "noexec"}, + } { + var mount MountOpt + + assert.NilError(t, mount.Set(testcase.opts)) + + mounts := mount.Value() + assert.Assert(t, is.Len(mounts, 1)) + assert.Check(t, is.DeepEqual(mounttypes.Mount{ + Type: mounttypes.TypeTmpfs, + Target: "/target", + TmpfsOptions: &mounttypes.TmpfsOptions{ + RawOptions: testcase.expected, + }, + }, mounts[0])) + } +} diff --git a/vendor/github.com/docker/docker/api/types/mount/mount.go b/vendor/github.com/docker/docker/api/types/mount/mount.go index 3fef974df883..d9b81ead2e3e 100644 --- a/vendor/github.com/docker/docker/api/types/mount/mount.go +++ b/vendor/github.com/docker/docker/api/types/mount/mount.go @@ -109,6 +109,9 @@ type TmpfsOptions struct { // Mode of the tmpfs upon creation Mode os.FileMode `json:",omitempty"` + // Options passed to the tmpfs + RawOptions string `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. //