Skip to content

Commit 47f19b6

Browse files
committed
cli/command/container: add create/run --umask
Signed-off-by: Youfu Zhang <zhangyoufu@gmail.com>
1 parent c574e03 commit 47f19b6

5 files changed

Lines changed: 136 additions & 0 deletions

File tree

cli/command/container/opts.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ type containerOptions struct {
139139
runtime string
140140
autoRemove bool
141141
init bool
142+
umask opts.UmaskOpt
142143
annotations *opts.MapOpts
143144

144145
Image string
@@ -313,6 +314,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
313314
flags.Var(&copts.shmSize, "shm-size", "Size of /dev/shm")
314315
flags.StringVar(&copts.utsMode, "uts", "", "UTS namespace to use")
315316
flags.StringVar(&copts.runtime, "runtime", "", "Runtime to use for this container")
317+
flags.Var(&copts.umask, "umask", "Set the umask for the container")
316318

317319
flags.BoolVar(&copts.init, "init", false, "Run an init inside the container that forwards signals and reaps processes")
318320
flags.SetAnnotation("init", "version", []string{"1.25"})
@@ -710,6 +712,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
710712
MaskedPaths: maskedPaths,
711713
ReadonlyPaths: readonlyPaths,
712714
Annotations: copts.annotations.GetAll(),
715+
Umask: copts.umask.Value(),
713716
}
714717

715718
if copts.autoRemove && !hostConfig.RestartPolicy.IsNone() {

cli/command/container/opts_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,19 @@ func TestParseRunLinks(t *testing.T) {
154154
}
155155
}
156156

157+
func TestParseRunWithoutUmask(t *testing.T) {
158+
_, hostConfig, _, err := parseRun([]string{"ubuntu", "bash"})
159+
assert.NilError(t, err)
160+
assert.Assert(t, hostConfig.Umask == nil)
161+
}
162+
163+
func TestParseRunUmask(t *testing.T) {
164+
_, hostConfig, _, err := parseRun([]string{"--umask", "0022", "ubuntu", "bash"})
165+
assert.NilError(t, err)
166+
assert.Assert(t, hostConfig.Umask != nil)
167+
assert.Equal(t, uint32(0o22), *hostConfig.Umask)
168+
}
169+
157170
func TestParseRunAttach(t *testing.T) {
158171
tests := []struct {
159172
input string

e2e/container/run_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,31 @@ func TestRunWithCgroupNamespace(t *testing.T) {
110110
result.Assert(t, icmd.Success)
111111
}
112112

113+
func TestRunUmask(t *testing.T) {
114+
environment.SkipIfDaemonNotLinux(t)
115+
116+
testCases := []struct {
117+
name string
118+
args []string
119+
expected string
120+
}{
121+
{name: "unset", expected: "0022\n"},
122+
{name: "zero", args: []string{"--umask", "0"}, expected: "0000\n"},
123+
{name: "octal-022", args: []string{"--umask", "022"}, expected: "0022\n"},
124+
{name: "octal-777", args: []string{"--umask", "777"}, expected: "0777\n"},
125+
}
126+
127+
for _, tc := range testCases {
128+
t.Run(tc.name, func(t *testing.T) {
129+
args := []string{"run", "--rm", fixtures.AlpineImage, "sh", "-c", "umask"}
130+
args = append(args[:2], append(tc.args, args[2:]...)...)
131+
result := icmd.RunCommand("docker", args...)
132+
result.Assert(t, icmd.Success)
133+
assert.Equal(t, result.Stdout(), tc.expected)
134+
})
135+
}
136+
}
137+
113138
func TestMountSubvolume(t *testing.T) {
114139
skip.If(t, versions.LessThan(environment.DaemonAPIVersion(t), "1.45"))
115140
volName := "test-volume-" + t.Name()

opts/opts.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"net"
1212
"path"
1313
"slices"
14+
"strconv"
1415
"strings"
1516

1617
"github.com/docker/cli/internal/lazyregexp"
@@ -477,3 +478,39 @@ func (m *MemSwapBytes) UnmarshalJSON(s []byte) error {
477478
b := MemBytes(*m)
478479
return b.UnmarshalJSON(s)
479480
}
481+
482+
// UmaskOpt is a type for umask values in octal format
483+
type UmaskOpt struct {
484+
ptr *uint32
485+
}
486+
487+
// Set sets the value of the UmaskOpt by passing a string in octal format
488+
func (u *UmaskOpt) Set(s string) error {
489+
v, err := strconv.ParseUint(s, 8, 32)
490+
if err != nil {
491+
return err
492+
}
493+
if u.ptr == nil {
494+
u.ptr = new(uint32)
495+
}
496+
*u.ptr = uint32(v)
497+
return nil
498+
}
499+
500+
// Type returns the type
501+
func (*UmaskOpt) Type() string {
502+
return "umask"
503+
}
504+
505+
// Value returns the uint32 ptr
506+
func (u *UmaskOpt) Value() *uint32 {
507+
return u.ptr
508+
}
509+
510+
// String returns the umask value in octal format, or "(unset)" if the pointer is nil.
511+
func (u *UmaskOpt) String() string {
512+
if u.ptr == nil {
513+
return "(unset)"
514+
}
515+
return fmt.Sprintf("%#04o", uint64(*u.ptr))
516+
}

opts/opts_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,3 +428,61 @@ func TestParseCPUsReturnZeroOnInvalidValues(t *testing.T) {
428428
resValue, _ = ParseCPUs("1e-32")
429429
assert.Equal(t, z1, resValue)
430430
}
431+
432+
func TestUmaskOpt(t *testing.T) {
433+
t.Run("unset by default", func(t *testing.T) {
434+
var opt UmaskOpt
435+
assert.Assert(t, opt.Value() == nil)
436+
assert.Equal(t, opt.String(), "(unset)")
437+
})
438+
439+
t.Run("rejects empty string", func(t *testing.T) {
440+
var opt UmaskOpt
441+
err := opt.Set("")
442+
assert.Assert(t, err != nil)
443+
assert.Assert(t, opt.Value() == nil)
444+
assert.Equal(t, opt.String(), "(unset)")
445+
})
446+
447+
t.Run("parses zero", func(t *testing.T) {
448+
var opt UmaskOpt
449+
err := opt.Set("0")
450+
assert.NilError(t, err)
451+
assert.Equal(t, *opt.Value(), uint32(0))
452+
assert.Equal(t, opt.String(), "0000")
453+
})
454+
455+
t.Run("parses valid octal values", func(t *testing.T) {
456+
var opt UmaskOpt
457+
err := opt.Set("022")
458+
assert.NilError(t, err)
459+
assert.Assert(t, opt.Value() != nil)
460+
assert.Equal(t, *opt.Value(), uint32(0o22))
461+
assert.Equal(t, opt.String(), "0022")
462+
})
463+
464+
t.Run("rejects octal values with 0o prefix", func(t *testing.T) {
465+
var opt UmaskOpt
466+
err := opt.Set("0o22")
467+
assert.Assert(t, err != nil)
468+
assert.Assert(t, opt.Value() == nil)
469+
assert.Equal(t, opt.String(), "(unset)")
470+
})
471+
472+
t.Run("parses large octal values", func(t *testing.T) {
473+
var opt UmaskOpt
474+
err := opt.Set("077777")
475+
assert.NilError(t, err)
476+
assert.Assert(t, opt.Value() != nil)
477+
assert.Equal(t, *opt.Value(), uint32(0o77777))
478+
assert.Equal(t, opt.String(), "077777")
479+
})
480+
481+
t.Run("rejects invalid octal values", func(t *testing.T) {
482+
var opt UmaskOpt
483+
err := opt.Set("9")
484+
assert.Assert(t, err != nil)
485+
assert.Assert(t, opt.Value() == nil)
486+
assert.Equal(t, opt.String(), "(unset)")
487+
})
488+
}

0 commit comments

Comments
 (0)