mirror of
https://github.com/docker/cli.git
synced 2026-09-27 09:46:01 -04:00
cli/command/container: add create/run --umask
Signed-off-by: Youfu Zhang <zhangyoufu@gmail.com>
This commit is contained in:
@@ -139,6 +139,7 @@ type containerOptions struct {
|
||||
runtime string
|
||||
autoRemove bool
|
||||
init bool
|
||||
umask opts.UmaskOpt
|
||||
annotations *opts.MapOpts
|
||||
|
||||
Image string
|
||||
@@ -313,6 +314,9 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
|
||||
flags.Var(&copts.shmSize, "shm-size", "Size of /dev/shm")
|
||||
flags.StringVar(&copts.utsMode, "uts", "", "UTS namespace to use")
|
||||
flags.StringVar(&copts.runtime, "runtime", "", "Runtime to use for this container")
|
||||
flags.Var(&copts.umask, "umask", "Set umask for the container")
|
||||
flags.SetAnnotation("umask", "version", []string{"1.56"})
|
||||
flags.SetAnnotation("umask", "ostype", []string{"linux"})
|
||||
|
||||
flags.BoolVar(&copts.init, "init", false, "Run an init inside the container that forwards signals and reaps processes")
|
||||
flags.SetAnnotation("init", "version", []string{"1.25"})
|
||||
@@ -710,6 +714,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
|
||||
MaskedPaths: maskedPaths,
|
||||
ReadonlyPaths: readonlyPaths,
|
||||
Annotations: copts.annotations.GetAll(),
|
||||
Umask: copts.umask.Value(),
|
||||
}
|
||||
|
||||
if copts.autoRemove && !hostConfig.RestartPolicy.IsNone() {
|
||||
|
||||
@@ -154,6 +154,19 @@ func TestParseRunLinks(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRunWithoutUmask(t *testing.T) {
|
||||
_, hostConfig, _, err := parseRun([]string{"ubuntu", "bash"})
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, hostConfig.Umask == nil)
|
||||
}
|
||||
|
||||
func TestParseRunUmask(t *testing.T) {
|
||||
_, hostConfig, _, err := parseRun([]string{"--umask", "0022", "ubuntu", "bash"})
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, hostConfig.Umask != nil)
|
||||
assert.Equal(t, uint32(0o22), *hostConfig.Umask)
|
||||
}
|
||||
|
||||
func TestParseRunAttach(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
|
||||
@@ -102,6 +102,7 @@ Create a new container
|
||||
| `--tmpfs` | `list` | | Mount a tmpfs directory |
|
||||
| `-t`, `--tty` | `bool` | | Allocate a pseudo-TTY |
|
||||
| `--ulimit` | `ulimit` | | Ulimit options |
|
||||
| `--umask` | `umask` | `<nil>` | Set umask for the container |
|
||||
| `--use-api-socket` | `bool` | | Bind mount Docker API socket and required auth |
|
||||
| `-u`, `--user` | `string` | | Username or UID (format: <name\|uid>[:<group\|gid>]) |
|
||||
| `--userns` | `string` | | User namespace to use |
|
||||
|
||||
@@ -105,6 +105,7 @@ Create and run a new container from an image
|
||||
| [`--tmpfs`](#tmpfs) | `list` | | Mount a tmpfs directory |
|
||||
| [`-t`](#tty), [`--tty`](#tty) | `bool` | | Allocate a pseudo-TTY |
|
||||
| [`--ulimit`](#ulimit) | `ulimit` | | Ulimit options |
|
||||
| [`--umask`](#umask) | `umask` | `<nil>` | Set umask for the container |
|
||||
| `--use-api-socket` | `bool` | | Bind mount Docker API socket and required auth |
|
||||
| `-u`, `--user` | `string` | | Username or UID (format: <name\|uid>[:<group\|gid>]) |
|
||||
| [`--userns`](#userns) | `string` | | User namespace to use |
|
||||
@@ -1392,6 +1393,56 @@ The 4th container fails and reports a "[8] System error: resource temporarily un
|
||||
This fails because the caller set `nproc=3` resulting in the first three containers using up
|
||||
the three processes quota set for the `daemon` user.
|
||||
|
||||
### <a name="umask"></a> Set umask for the container (--umask)
|
||||
|
||||
The `--umask` flag sets the umask for the container's processes, which
|
||||
controls the default permissions for files and directories created inside
|
||||
the container. The value must be specified in octal notation. Leading zeros
|
||||
are optional. For example, a umask of `022` creates new files with `644`
|
||||
permissions (`-rw-r--r--`) and new directories with `755` permissions
|
||||
(`drwxr-xr-x`).
|
||||
|
||||
If you don't set the `--umask` flag, the OCI runtime applies its own
|
||||
default umask. The default OCI runtime (runc) uses a default umask of
|
||||
`0022`, but this value is implementation-defined and may vary between OCI
|
||||
runtimes:
|
||||
|
||||
```console
|
||||
$ docker run --rm busybox sh -c umask
|
||||
0022
|
||||
```
|
||||
|
||||
When the `--umask` flag is set, the value is included in the OCI process
|
||||
configuration used for the container's entrypoint, for processes started
|
||||
with `docker exec`, and for healthchecks. Whether an OCI runtime honors the
|
||||
value depends on the runtime; runc honors the configured umask for
|
||||
`docker exec` and for healthchecks.
|
||||
|
||||
To set a custom umask, use the `--umask` flag with an octal value:
|
||||
|
||||
```console
|
||||
$ docker run --rm --umask 077 busybox sh -c umask
|
||||
0077
|
||||
```
|
||||
|
||||
The umask is also applied to processes started later with `docker exec`:
|
||||
|
||||
```console
|
||||
$ docker run --rm -d --name umask-test --umask 077 busybox sleep 60
|
||||
$ docker exec umask-test sh -c umask
|
||||
0077
|
||||
```
|
||||
|
||||
Setting the umask to `0` masks no permission bits, so files and directories
|
||||
keep their full permissions. The following example creates a file and a
|
||||
directory inside the container:
|
||||
|
||||
```console
|
||||
$ docker run --rm --umask 0 busybox sh -c 'touch file && mkdir dir && stat -c "%a %n" file dir'
|
||||
666 file
|
||||
777 dir
|
||||
```
|
||||
|
||||
### <a name="stop-signal"></a> Stop container with signal (--stop-signal)
|
||||
|
||||
The `--stop-signal` flag sends the system call signal to the
|
||||
|
||||
@@ -102,6 +102,7 @@ Create a new container
|
||||
| `--tmpfs` | `list` | | Mount a tmpfs directory |
|
||||
| `-t`, `--tty` | `bool` | | Allocate a pseudo-TTY |
|
||||
| `--ulimit` | `ulimit` | | Ulimit options |
|
||||
| `--umask` | `umask` | `<nil>` | Set umask for the container |
|
||||
| `--use-api-socket` | `bool` | | Bind mount Docker API socket and required auth |
|
||||
| `-u`, `--user` | `string` | | Username or UID (format: <name\|uid>[:<group\|gid>]) |
|
||||
| `--userns` | `string` | | User namespace to use |
|
||||
|
||||
@@ -105,6 +105,7 @@ Create and run a new container from an image
|
||||
| `--tmpfs` | `list` | | Mount a tmpfs directory |
|
||||
| `-t`, `--tty` | `bool` | | Allocate a pseudo-TTY |
|
||||
| `--ulimit` | `ulimit` | | Ulimit options |
|
||||
| `--umask` | `umask` | `<nil>` | Set umask for the container |
|
||||
| `--use-api-socket` | `bool` | | Bind mount Docker API socket and required auth |
|
||||
| `-u`, `--user` | `string` | | Username or UID (format: <name\|uid>[:<group\|gid>]) |
|
||||
| `--userns` | `string` | | User namespace to use |
|
||||
|
||||
@@ -110,6 +110,31 @@ func TestRunWithCgroupNamespace(t *testing.T) {
|
||||
result.Assert(t, icmd.Success)
|
||||
}
|
||||
|
||||
func TestRunUmask(t *testing.T) {
|
||||
environment.SkipIfDaemonNotLinux(t)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
expected string
|
||||
}{
|
||||
{name: "unset", expected: "0022\n"},
|
||||
{name: "zero", args: []string{"--umask", "0"}, expected: "0000\n"},
|
||||
{name: "octal-022", args: []string{"--umask", "022"}, expected: "0022\n"},
|
||||
{name: "octal-777", args: []string{"--umask", "777"}, expected: "0777\n"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
args := []string{"run", "--rm", fixtures.AlpineImage, "sh", "-c", "umask"}
|
||||
args = append(args[:2], append(tc.args, args[2:]...)...)
|
||||
result := icmd.RunCommand("docker", args...)
|
||||
result.Assert(t, icmd.Success)
|
||||
assert.Equal(t, result.Stdout(), tc.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMountSubvolume(t *testing.T) {
|
||||
skip.If(t, versions.LessThan(environment.DaemonAPIVersion(t), "1.45"))
|
||||
volName := "test-volume-" + t.Name()
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"net"
|
||||
"path"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/internal/lazyregexp"
|
||||
@@ -477,3 +478,39 @@ func (m *MemSwapBytes) UnmarshalJSON(s []byte) error {
|
||||
b := MemBytes(*m)
|
||||
return b.UnmarshalJSON(s)
|
||||
}
|
||||
|
||||
// UmaskOpt is a type for umask values in octal format
|
||||
type UmaskOpt struct {
|
||||
ptr *uint32
|
||||
}
|
||||
|
||||
// Set sets the value of the UmaskOpt by passing a string in octal format
|
||||
func (u *UmaskOpt) Set(s string) error {
|
||||
v, err := strconv.ParseUint(s, 8, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if u.ptr == nil {
|
||||
u.ptr = new(uint32)
|
||||
}
|
||||
*u.ptr = uint32(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type returns the type
|
||||
func (*UmaskOpt) Type() string {
|
||||
return "umask"
|
||||
}
|
||||
|
||||
// Value returns the uint32 ptr
|
||||
func (u *UmaskOpt) Value() *uint32 {
|
||||
return u.ptr
|
||||
}
|
||||
|
||||
// String returns the umask value in octal format, or "<nil>" if the pointer is nil.
|
||||
func (u *UmaskOpt) String() string {
|
||||
if u.ptr == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
return fmt.Sprintf("%#04o", uint64(*u.ptr))
|
||||
}
|
||||
|
||||
@@ -428,3 +428,66 @@ func TestParseCPUsReturnZeroOnInvalidValues(t *testing.T) {
|
||||
resValue, _ = ParseCPUs("1e-32")
|
||||
assert.Equal(t, z1, resValue)
|
||||
}
|
||||
|
||||
func TestUmaskOpt(t *testing.T) {
|
||||
t.Run("type", func(t *testing.T) {
|
||||
var opt UmaskOpt
|
||||
assert.Equal(t, opt.Type(), "umask")
|
||||
})
|
||||
|
||||
t.Run("nil by default", func(t *testing.T) {
|
||||
var opt UmaskOpt
|
||||
assert.Assert(t, opt.Value() == nil)
|
||||
assert.Equal(t, opt.String(), "<nil>")
|
||||
})
|
||||
|
||||
t.Run("rejects empty string", func(t *testing.T) {
|
||||
var opt UmaskOpt
|
||||
err := opt.Set("")
|
||||
assert.Assert(t, err != nil)
|
||||
assert.Assert(t, opt.Value() == nil)
|
||||
assert.Equal(t, opt.String(), "<nil>")
|
||||
})
|
||||
|
||||
t.Run("parses zero", func(t *testing.T) {
|
||||
var opt UmaskOpt
|
||||
err := opt.Set("0")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, *opt.Value(), uint32(0))
|
||||
assert.Equal(t, opt.String(), "0000")
|
||||
})
|
||||
|
||||
t.Run("parses valid octal values", func(t *testing.T) {
|
||||
var opt UmaskOpt
|
||||
err := opt.Set("022")
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, opt.Value() != nil)
|
||||
assert.Equal(t, *opt.Value(), uint32(0o22))
|
||||
assert.Equal(t, opt.String(), "0022")
|
||||
})
|
||||
|
||||
t.Run("rejects octal values with 0o prefix", func(t *testing.T) {
|
||||
var opt UmaskOpt
|
||||
err := opt.Set("0o22")
|
||||
assert.Assert(t, err != nil)
|
||||
assert.Assert(t, opt.Value() == nil)
|
||||
assert.Equal(t, opt.String(), "<nil>")
|
||||
})
|
||||
|
||||
t.Run("parses large octal values", func(t *testing.T) {
|
||||
var opt UmaskOpt
|
||||
err := opt.Set("077777")
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, opt.Value() != nil)
|
||||
assert.Equal(t, *opt.Value(), uint32(0o77777))
|
||||
assert.Equal(t, opt.String(), "077777")
|
||||
})
|
||||
|
||||
t.Run("rejects invalid octal values", func(t *testing.T) {
|
||||
var opt UmaskOpt
|
||||
err := opt.Set("9")
|
||||
assert.Assert(t, err != nil)
|
||||
assert.Assert(t, opt.Value() == nil)
|
||||
assert.Equal(t, opt.String(), "<nil>")
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user