فهرست منبع

Merge 2922732df59c8193760fd6da39862c039547ab7e into ee8b788538ea2c6d46d65f17be156de65bc21bb9

Drew Erny 1 سال پیش
والد
کامیت
84495cb4bb

+ 3 - 0
api/swagger.yaml

@@ -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: |

+ 2 - 1
api/types/mount/mount.go

@@ -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.
 	//

+ 2 - 0
daemon/cluster/convert/container.go

@@ -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,
 			}
 		}
 

+ 1 - 0
daemon/cluster/executor/container/container.go

@@ -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,
 		}
 	}
 

+ 75 - 0
daemon/cluster/executor/container/container_test.go

@@ -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))
+		})
+	}
+}

+ 1 - 0
docs/api/version-history.md

@@ -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
 

+ 25 - 0
volume/mounts/linux_parser.go

@@ -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
 }
 

+ 17 - 0
volume/mounts/linux_parser_test.go

@@ -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)