Skip to content

Commit

Permalink
run: Add --uts flag
Browse files Browse the repository at this point in the history
Adds support for specifying the host UTS namespace (the only thing
docker supported). This also consolidates the namespace options
checks for linux on run into a single function.

Signed-off-by: Danny Canter <danny@dcantah.dev>
  • Loading branch information
dcantah committed Dec 26, 2022
1 parent 9dee88f commit f5d939c
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 31 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@ Basic flags:
- :whale: `--pull=(always|missing|never)`: Pull image before running
- Default: "missing"
- :whale: `--pid=(host|container:<container>)`: PID namespace to use
- :whale: `--uts=(host)` : UTS namespace to use
- :whale: `--stop-signal`: Signal to stop a container (default "SIGTERM")
- :whale: `--stop-timeout`: Timeout (in seconds) to stop a container

Expand Down Expand Up @@ -577,7 +578,7 @@ Unimplemented `docker run` flags:
`--attach`, `--blkio-weight-device`, `--cgroup-parent`, `--cpu-rt-*`, `--detach-keys`, `--device-*`,
`--disable-content-trust`, `--domainname`, `--expose`, `--health-*`, `--ip6`, `--isolation`, `--no-healthcheck`,
`--link*`, `--mac-address`, `--publish-all`, `--sig-proxy`, `--storage-opt`,
`--userns`, `--uts`, `--volume-driver`, `--volumes-from`
`--userns`, `--volume-driver`, `--volumes-from`

### :whale: :blue_square: nerdctl exec
Run a command in a running container.
Expand Down
29 changes: 21 additions & 8 deletions cmd/nerdctl/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ func setCreateFlags(cmd *cobra.Command) {
cmd.Flags().Bool("oom-kill-disable", false, "Disable OOM Killer")
cmd.Flags().Int("oom-score-adj", 0, "Tune container’s OOM preferences (-1000 to 1000, rootless: 100 to 1000)")
cmd.Flags().String("pid", "", "PID namespace to use")
cmd.Flags().String("uts", "", "UTS namespace to use")
cmd.RegisterFlagCompletionFunc("pid", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"host"}, cobra.ShellCompDirectiveNoFileComp
})
Expand Down Expand Up @@ -565,18 +566,30 @@ func createContainer(ctx context.Context, cmd *cobra.Command, client *containerd
if err != nil {
return nil, nil, err
}

uts, err := cmd.Flags().GetString("uts")
if err != nil {
return nil, nil, err
}

if customHostname != "" {
// Docker considers this a validation error so keep compat.
if uts != "" {
return nil, nil, errors.New("conflicting options: hostname and UTS mode")
}
hostname = customHostname
}
opts = append(opts, oci.WithHostname(hostname))
internalLabels.hostname = hostname
// `/etc/hostname` does not exist on FreeBSD
if runtime.GOOS == "linux" {
hostnamePath := filepath.Join(stateDir, "hostname")
if err := os.WriteFile(hostnamePath, []byte(hostname+"\n"), 0644); err != nil {
return nil, nil, err
if uts == "" {
opts = append(opts, oci.WithHostname(hostname))
internalLabels.hostname = hostname
// `/etc/hostname` does not exist on FreeBSD
if runtime.GOOS == "linux" {
hostnamePath := filepath.Join(stateDir, "hostname")
if err := os.WriteFile(hostnamePath, []byte(hostname+"\n"), 0644); err != nil {
return nil, nil, err
}
opts = append(opts, withCustomEtcHostname(hostnamePath))
}
opts = append(opts, withCustomEtcHostname(hostnamePath))
}

netOpts, netSlice, ipAddress, ports, macAddress, err := generateNetOpts(cmd, dataStore, stateDir, ns, id)
Expand Down
9 changes: 8 additions & 1 deletion cmd/nerdctl/run_freebsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ func runShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]s
return nil, cobra.ShellCompDirectiveNoFileComp
}

func setPlatformOptions(ctx context.Context, opts []oci.SpecOpts, cmd *cobra.Command, client *containerd.Client, id string, internalLabels internalLabels) ([]oci.SpecOpts, internalLabels, error) {
func setPlatformOptions(
ctx context.Context,
opts []oci.SpecOpts,
cmd *cobra.Command,
client *containerd.Client,
id string,
internalLabels internalLabels,
) ([]oci.SpecOpts, intenralLabels, error) {
return opts, internalLabels, nil
}
86 changes: 66 additions & 20 deletions cmd/nerdctl/run_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,14 @@ func runShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]s
return nil, cobra.ShellCompDirectiveNoFileComp
}

func setPlatformOptions(ctx context.Context, opts []oci.SpecOpts, cmd *cobra.Command, client *containerd.Client, id string, internalLabels internalLabels) ([]oci.SpecOpts, internalLabels, error) {
func setPlatformOptions(
ctx context.Context,
opts []oci.SpecOpts,
cmd *cobra.Command,
client *containerd.Client,
id string,
internalLabels internalLabels,
) ([]oci.SpecOpts, internalLabels, error) {
opts = append(opts,
oci.WithDefaultUnixDevices,
WithoutRunMount(), // unmount default tmpfs on "/run": https://github.com/containerd/nerdctl/issues/157)
Expand Down Expand Up @@ -129,17 +136,6 @@ func setPlatformOptions(ctx context.Context, opts []oci.SpecOpts, cmd *cobra.Com
opts = append(opts, oci.WithDevShmSize(shmBytes/1024))
}

pid, err := cmd.Flags().GetString("pid")
if err != nil {
return nil, internalLabels, err
}
pidOpts, pidInternalLabel, err := generatePIDOpts(ctx, client, pid)
if err != nil {
return nil, internalLabels, err
}
internalLabels.pidContainer = pidInternalLabel
opts = append(opts, pidOpts...)

ulimitOpts, err := generateUlimitsOpts(cmd)
if err != nil {
return nil, internalLabels, err
Expand Down Expand Up @@ -168,17 +164,11 @@ func setPlatformOptions(ctx context.Context, opts []oci.SpecOpts, cmd *cobra.Com
opts = append(opts, oci.WithRdt(rdtClass, "", ""))
}

ipc, err := cmd.Flags().GetString("ipc")
nsOpts, err := generateNamespaceOpts(ctx, cmd, client, &internalLabels)
if err != nil {
return nil, internalLabels, err
}
// if nothing is specified, or if private, default to normal behavior
if ipc == "host" {
opts = append(opts, oci.WithHostNamespace(specs.IPCNamespace))
opts = append(opts, withBindMountHostIPC)
} else if ipc != "" && ipc != "private" {
return nil, internalLabels, fmt.Errorf("error: %v", "invalid ipc value, supported values are 'private' or 'host'")
}
opts = append(opts, nsOpts...)

opts, err = setOOMScoreAdj(opts, cmd)
if err != nil {
Expand All @@ -188,6 +178,62 @@ func setPlatformOptions(ctx context.Context, opts []oci.SpecOpts, cmd *cobra.Com
return opts, internalLabels, nil
}

// Helper to validate the namespace options exposed via run and return the correct
// opts.
func generateNamespaceOpts(
ctx context.Context,
cmd *cobra.Command,
client *containerd.Client,
internalLabels *internalLabels,
) ([]oci.SpecOpts, error) {
var opts []oci.SpecOpts

// UTS
uts, err := cmd.Flags().GetString("uts")
if err != nil {
return nil, err
}

switch uts {
case "host":
opts = append(opts, oci.WithHostNamespace(specs.UTSNamespace))
case "":
// Default, do nothing. Every container gets its own UTS ns by default.
default:
return nil, fmt.Errorf("unknown uts value. valid value(s) are 'host', got: %q", uts)
}

// IPC
ipc, err := cmd.Flags().GetString("ipc")
if err != nil {
return nil, err
}

switch ipc {
case "host":
opts = append(opts, oci.WithHostNamespace(specs.IPCNamespace))
opts = append(opts, withBindMountHostIPC)
case "private", "":
// If nothing is specified, or if private, default to normal behavior
default:
return nil, fmt.Errorf("unknown ipc value. valid values are 'private' or 'host', got: %q", ipc)
}

// PID
pid, err := cmd.Flags().GetString("pid")
if err != nil {
return nil, err
}
pidOpts, pidLabel, err := generatePIDOpts(ctx, client, pid)
if err != nil {
return nil, err
}
internalLabels.pidContainer = pidLabel
opts = append(opts, pidOpts...)

return opts, nil
}

func setOOMScoreAdj(opts []oci.SpecOpts, cmd *cobra.Command) ([]oci.SpecOpts, error) {
if !cmd.Flags().Changed("oom-score-adj") {
return opts, nil
Expand Down
15 changes: 15 additions & 0 deletions cmd/nerdctl/run_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,21 @@ func TestRunPidHost(t *testing.T) {
base.Cmd("run", "--rm", "--pid=host", testutil.AlpineImage, "ps", "auxw").AssertOutContains(strconv.Itoa(pid))
}

func TestRunUtsHost(t *testing.T) {
t.Parallel()
base := testutil.NewBase(t)

// Was thinking of os.ReadLink("/proc/1/ns/uts")
// but you'd get EPERM for rootless. Just validate the
// hostname is the same.
hostName, err := os.Hostname()
assert.NilError(base.T, err)

base.Cmd("run", "--rm", "--uts=host", testutil.AlpineImage, "hostname").AssertOutContains(hostName)
// Validate we can't provide a hostname with uts=host
base.Cmd("run", "--rm", "--uts=host", "--hostname=foobar", testutil.AlpineImage, "hostname").AssertFail()
}

func TestRunPidContainer(t *testing.T) {
t.Parallel()
base := testutil.NewBase(t)
Expand Down
9 changes: 8 additions & 1 deletion cmd/nerdctl/run_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,14 @@ func runShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]s
return nil, cobra.ShellCompDirectiveNoFileComp
}

func setPlatformOptions(ctx context.Context, opts []oci.SpecOpts, cmd *cobra.Command, client *containerd.Client, id string, internalLabels internalLabels) ([]oci.SpecOpts, internalLabels, error) {
func setPlatformOptions(
ctx context.Context,
opts []oci.SpecOpts,
cmd *cobra.Command,
client *containerd.Client,
id string,
internalLabels internalLabels,
) ([]oci.SpecOpts, internalLabels, error) {
cpus, err := cmd.Flags().GetFloat64("cpus")
if err != nil {
return nil, internalLabels, err
Expand Down

0 comments on commit f5d939c

Please sign in to comment.