This commit is contained in:
Drew Erny 2024-04-19 14:15:28 -04:00 committed by GitHub
commit 84495cb4bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 126 additions and 1 deletions

View file

@ -442,6 +442,9 @@ definitions:
Mode:
description: "The permission mode for the tmpfs mount in an integer."
type: "integer"
Options:
description: "The list of options to be passed to the tmpfs mount in a string."
type: "string"
RestartPolicy:
description: |

View file

@ -119,7 +119,8 @@ type TmpfsOptions struct {
SizeBytes int64 `json:",omitempty"`
// Mode of the tmpfs upon creation
Mode os.FileMode `json:",omitempty"`
// Options passed directly to the tmpfs mount
Options 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.
//

View file

@ -136,6 +136,7 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) *types.ContainerSpec {
mount.TmpfsOptions = &mounttypes.TmpfsOptions{
SizeBytes: m.TmpfsOptions.SizeBytes,
Mode: m.TmpfsOptions.Mode,
Options: m.TmpfsOptions.Options,
}
}
containerSpec.Mounts = append(containerSpec.Mounts, mount)
@ -423,6 +424,7 @@ func containerToGRPC(c *types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
mount.TmpfsOptions = &swarmapi.Mount_TmpfsOptions{
SizeBytes: m.TmpfsOptions.SizeBytes,
Mode: m.TmpfsOptions.Mode,
Options: m.TmpfsOptions.Options,
}
}

View file

@ -364,6 +364,7 @@ func convertMount(m api.Mount) enginemount.Mount {
mount.TmpfsOptions = &enginemount.TmpfsOptions{
SizeBytes: m.TmpfsOptions.SizeBytes,
Mode: m.TmpfsOptions.Mode,
Options: m.TmpfsOptions.Options,
}
}

View file

@ -4,8 +4,10 @@ import (
"testing"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/mount"
swarmapi "github.com/moby/swarmkit/v2/api"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestIsolationConversion(t *testing.T) {
@ -117,6 +119,7 @@ func TestCredentialSpecConversion(t *testing.T) {
to: []string{"credentialspec=registry://testing"},
},
}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
@ -139,3 +142,75 @@ func TestCredentialSpecConversion(t *testing.T) {
})
}
}
func TestTmpfsConversion(t *testing.T) {
cases := []struct {
name string
from []swarmapi.Mount
to []mount.Mount
}{
{
name: "tmpfs-exec",
from: []swarmapi.Mount{
{
Source: "/foo",
Target: "/bar",
Type: swarmapi.MountTypeTmpfs,
TmpfsOptions: &swarmapi.Mount_TmpfsOptions{
Options: "exec",
},
},
},
to: []mount.Mount{
{
Source: "/foo",
Target: "/bar",
Type: mount.TypeTmpfs,
TmpfsOptions: &mount.TmpfsOptions{
Options: "exec",
},
},
},
},
{
name: "tmpfs-noexec",
from: []swarmapi.Mount{
{
Source: "/foo",
Target: "/bar",
Type: swarmapi.MountTypeTmpfs,
TmpfsOptions: &swarmapi.Mount_TmpfsOptions{
Options: "noexec",
},
},
},
to: []mount.Mount{
{
Source: "/foo",
Target: "/bar",
Type: mount.TypeTmpfs,
TmpfsOptions: &mount.TmpfsOptions{
Options: "noexec",
},
},
},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
task := swarmapi.Task{
Spec: swarmapi.TaskSpec{
Runtime: &swarmapi.TaskSpec_Container{
Container: &swarmapi.ContainerSpec{
Image: "alpine:latest",
Mounts: c.from,
},
},
},
}
config := containerConfig{task: &task}
assert.Check(t, is.DeepEqual(c.to, config.hostConfig(nil).Mounts))
})
}
}

View file

@ -30,6 +30,7 @@ keywords: "API, Docker, rcli, REST, documentation"
values originally submitted to the `POST /containers/create` endpoint. The
newly introduced `DNSNames` should now be used instead when short container
IDs are needed.
* `POST /containers/create` now takes `Options` as part of `HostConfig.Mounts` to set options for tmpfs mounts.
## v1.44 API changes

View file

@ -204,6 +204,22 @@ func linuxValidMountMode(mode string) bool {
return true
}
var validTmpfsOptions = map[string]bool{
"exec": true,
"noexec": true,
}
func validateTmpfsOptions(rawOptions string) ([]string, error) {
var options []string
for _, opt := range strings.Split(rawOptions, ",") {
if _, ok := validTmpfsOptions[opt]; !ok {
return nil, errors.New("invalid option: " + opt)
}
options = append(options, opt)
}
return options, nil
}
func (p *linuxParser) ReadWrite(mode string) bool {
if !linuxValidMountMode(mode) {
return false
@ -406,6 +422,15 @@ func (p *linuxParser) ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool
rawOpts = append(rawOpts, fmt.Sprintf("size=%d%s", size, suffix))
}
if opt != nil && len(opt.Options) > 0 {
tmpfsOpts, err := validateTmpfsOptions(opt.Options)
if err != nil {
return "", err
}
rawOpts = append(rawOpts, tmpfsOpts...)
}
return strings.Join(rawOpts, ","), nil
}

View file

@ -238,6 +238,7 @@ func TestConvertTmpfsOptions(t *testing.T) {
readOnly bool
expectedSubstrings []string
unexpectedSubstrings []string
err bool
}
cases := []testCase{
{
@ -252,10 +253,26 @@ func TestConvertTmpfsOptions(t *testing.T) {
expectedSubstrings: []string{"ro"},
unexpectedSubstrings: []string{},
},
{
opt: mount.TmpfsOptions{Options: "exec"},
readOnly: true,
expectedSubstrings: []string{"ro", "exec"},
unexpectedSubstrings: []string{"noexec"},
},
{
opt: mount.TmpfsOptions{Options: "INVALID"},
err: true,
},
}
p := NewLinuxParser()
for _, tc := range cases {
data, err := p.ConvertTmpfsOptions(&tc.opt, tc.readOnly)
if tc.err {
if err == nil {
t.Fatalf("expected error for %+v, got nil", tc.opt)
}
continue
}
if err != nil {
t.Fatalf("could not convert %+v (readOnly: %v) to string: %v",
tc.opt, tc.readOnly, err)