Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cli/command/container/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ type containerOptions struct {
runtime string
autoRemove bool
init bool
umask opts.UmaskOpt
annotations *opts.MapOpts

Image string
Expand Down Expand Up @@ -313,6 +314,7 @@ 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 the umask for the container")

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"})
Expand Down Expand Up @@ -710,6 +712,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() {
Expand Down
13 changes: 13 additions & 0 deletions cli/command/container/opts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions cmd/docker-trust/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,5 @@ require (
google.golang.org/grpc v1.80.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)

replace github.com/moby/moby/api => github.com/zhangyoufu/moby/api v0.0.0-20260716054254-a5f8a6c9858d
25 changes: 25 additions & 0 deletions e2e/container/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
37 changes: 37 additions & 0 deletions opts/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"net"
"path"
"slices"
"strconv"
"strings"

"github.com/docker/cli/internal/lazyregexp"
Expand Down Expand Up @@ -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 "(unset)" if the pointer is nil.
func (u *UmaskOpt) String() string {
if u.ptr == nil {
return "(unset)"
}
return fmt.Sprintf("%#04o", uint64(*u.ptr))
}
58 changes: 58 additions & 0 deletions opts/opts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -428,3 +428,61 @@ func TestParseCPUsReturnZeroOnInvalidValues(t *testing.T) {
resValue, _ = ParseCPUs("1e-32")
assert.Equal(t, z1, resValue)
}

func TestUmaskOpt(t *testing.T) {
t.Run("unset by default", func(t *testing.T) {
var opt UmaskOpt
assert.Assert(t, opt.Value() == nil)
assert.Equal(t, opt.String(), "(unset)")
})

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(), "(unset)")
})

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(), "(unset)")
})

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(), "(unset)")
})
}
2 changes: 2 additions & 0 deletions vendor.mod
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,5 @@ require (
google.golang.org/grpc v1.81.1 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)

replace github.com/moby/moby/api => github.com/zhangyoufu/moby/api v0.0.0-20260716054254-a5f8a6c9858d
4 changes: 2 additions & 2 deletions vendor.sum
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,6 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8=
github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU=
github.com/moby/moby/api v1.55.0 h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc=
github.com/moby/moby/api v1.55.0/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
github.com/moby/moby/client v0.5.0 h1:5XhyPk2fuOWf6RlSFa3MkIIgDZkF25xToXW8Q/BH7cc=
github.com/moby/moby/client v0.5.0/go.mod h1:rcVpF8ncl9vo5gaIBdol6CnbEtSj1uxMvEV/UrykF/s=
github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U=
Expand Down Expand Up @@ -202,6 +200,8 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zhangyoufu/moby/api v0.0.0-20260716054254-a5f8a6c9858d h1:lZGeAaZ0K/yk6xu2U8INf7oz7Zopu/n0onCXFeFunLM=
github.com/zhangyoufu/moby/api v0.0.0-20260716054254-a5f8a6c9858d/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ=
go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion vendor/modules.txt
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ github.com/moby/docker-image-spec/specs-go/v1
github.com/moby/go-archive
github.com/moby/go-archive/compression
github.com/moby/go-archive/tarheader
# github.com/moby/moby/api v1.55.0
# github.com/moby/moby/api v1.55.0 => github.com/zhangyoufu/moby/api v0.0.0-20260716054254-a5f8a6c9858d
## explicit; go 1.24
github.com/moby/moby/api/pkg/authconfig
github.com/moby/moby/api/pkg/stdcopy
Expand Down Expand Up @@ -565,3 +565,4 @@ gotest.tools/v3/skip
# tags.cncf.io/container-device-interface v1.1.0
## explicit; go 1.21
tags.cncf.io/container-device-interface/pkg/parser
# github.com/moby/moby/api => github.com/zhangyoufu/moby/api v0.0.0-20260716054254-a5f8a6c9858d