From ae277688f170827c4db0a975152d6a9090e86dfe Mon Sep 17 00:00:00 2001 From: CodeChanning Date: Wed, 12 Jun 2024 15:07:32 -0700 Subject: [PATCH] feat: support for --sig-proxy in run Signed-off-by: CodeChanning --- cmd/nerdctl/container_run.go | 11 +++- cmd/nerdctl/container_run_linux_test.go | 75 +++++++++++++++++++++++++ docs/command-reference.md | 3 +- pkg/api/types/container_types.go | 2 + pkg/composer/run.go | 1 + pkg/testutil/testutil.go | 5 ++ pkg/testutil/testutil_linux.go | 19 +++++++ 7 files changed, 113 insertions(+), 3 deletions(-) diff --git a/cmd/nerdctl/container_run.go b/cmd/nerdctl/container_run.go index 036b1f57da6..9344cd2b2c1 100644 --- a/cmd/nerdctl/container_run.go +++ b/cmd/nerdctl/container_run.go @@ -79,6 +79,7 @@ func setCreateFlags(cmd *cobra.Command) { cmd.Flags().Bool("help", false, "show help") cmd.Flags().BoolP("tty", "t", false, "Allocate a pseudo-TTY") + cmd.Flags().Bool("sig-proxy", true, "Proxy received signals to the process (default true)") cmd.Flags().BoolP("interactive", "i", false, "Keep STDIN open even if not attached") cmd.Flags().String("restart", "no", `Restart policy to apply when a container exits (implemented values: "no"|"always|on-failure:n|unless-stopped")`) cmd.RegisterFlagCompletionFunc("restart", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { @@ -287,6 +288,10 @@ func processCreateCommandFlagsInRun(cmd *cobra.Command) (opt types.ContainerCrea opt.InRun = true + opt.SigProxy, err = cmd.Flags().GetBool("sig-proxy") + if err != nil { + return + } opt.Interactive, err = cmd.Flags().GetBool("interactive") if err != nil { return @@ -394,8 +399,10 @@ func runAction(cmd *cobra.Command, args []string) error { log.L.WithError(err).Error("console resize") } } else { - sigC := signalutil.ForwardAllSignals(ctx, task) - defer signalutil.StopCatch(sigC) + if createOpt.SigProxy { + sigC := signalutil.ForwardAllSignals(ctx, task) + defer signalutil.StopCatch(sigC) + } } statusC, err := task.Wait(ctx) diff --git a/cmd/nerdctl/container_run_linux_test.go b/cmd/nerdctl/container_run_linux_test.go index 63943258677..15b17ab614c 100644 --- a/cmd/nerdctl/container_run_linux_test.go +++ b/cmd/nerdctl/container_run_linux_test.go @@ -29,6 +29,7 @@ import ( "runtime" "strconv" "strings" + "syscall" "testing" "time" @@ -311,6 +312,80 @@ func TestRunTTY(t *testing.T) { assert.Equal(t, 0, res.ExitCode, res.Combined()) } +func runSigProxy(t *testing.T, args ...string) (string, bool, bool) { + t.Parallel() + base := testutil.NewBase(t) + testContainerName := testutil.Identifier(t) + defer base.Cmd("rm", "-f", testContainerName).Run() + + fullArgs := []string{"run"} + fullArgs = append(fullArgs, args...) + fullArgs = append(fullArgs, + "--name", + testContainerName, + testutil.CommonImage, + "sh", + "-c", + testutil.SigProxyTestScript, + ) + + result := base.Cmd(fullArgs...).Start() + process := result.Cmd.Process + + // Waits until we reach the trap command in the shell script, then sends SIGINT. + time.Sleep(3 * time.Second) + syscall.Kill(process.Pid, syscall.SIGINT) + + // Waits until SIGINT is sent and responded to, then kills process to avoid timeout + time.Sleep(3 * time.Second) + process.Kill() + + sigIntRecieved := strings.Contains(result.Stdout(), testutil.SigProxyTrueOut) + timedOut := strings.Contains(result.Stdout(), testutil.SigProxyTimeoutMsg) + + return result.Stdout(), sigIntRecieved, timedOut +} + +func TestRunSigProxy(t *testing.T) { + + type testCase struct { + name string + args []string + want bool + expectedOut string + } + testCases := []testCase{ + { + name: "SigProxyDefault", + args: []string{}, + want: true, + expectedOut: testutil.SigProxyTrueOut, + }, + { + name: "SigProxyTrue", + args: []string{"--sig-proxy=true"}, + want: true, + expectedOut: testutil.SigProxyTrueOut, + }, + { + name: "SigProxyFalse", + args: []string{"--sig-proxy=false"}, + want: false, + expectedOut: "", + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + stdout, sigIntRecieved, timedOut := runSigProxy(t, tc.args...) + errorMsg := fmt.Sprintf("%s failed;\nExpected: '%s'\nActual: '%s'", tc.name, tc.expectedOut, stdout) + assert.Equal(t, false, timedOut, errorMsg) + assert.Equal(t, tc.want, sigIntRecieved, errorMsg) + }) + } +} + func TestRunWithFluentdLogDriver(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("fluentd log driver is not yet implemented on Windows") diff --git a/docs/command-reference.md b/docs/command-reference.md index 2540264bc95..7c0c8c3ad76 100644 --- a/docs/command-reference.md +++ b/docs/command-reference.md @@ -140,6 +140,7 @@ Basic flags: - :whale: :blue_square: `-i, --interactive`: Keep STDIN open even if not attached" - :whale: :blue_square: `-t, --tty`: Allocate a pseudo-TTY - :warning: WIP: currently `-t` conflicts with `-d` +- :whale: `-sig-proxy`: Proxy received signals to the process (default true) - :whale: :blue_square: `-d, --detach`: Run container in background and print container ID - :whale: `--restart=(no|always|on-failure|unless-stopped)`: Restart policy to apply when a container exits - Default: "no" @@ -388,7 +389,7 @@ IPFS flags: Unimplemented `docker run` flags: `--attach`, `--blkio-weight-device`, `--cpu-rt-*`, `--device-*`, `--disable-content-trust`, `--domainname`, `--expose`, `--health-*`, `--isolation`, `--no-healthcheck`, - `--link*`, `--publish-all`, `--sig-proxy`, `--storage-opt`, + `--link*`, `--publish-all`, `--storage-opt`, `--userns`, `--volume-driver` ### :whale: :blue_square: nerdctl exec diff --git a/pkg/api/types/container_types.go b/pkg/api/types/container_types.go index 4759eb29678..16fd41301d7 100644 --- a/pkg/api/types/container_types.go +++ b/pkg/api/types/container_types.go @@ -62,6 +62,8 @@ type ContainerCreateOptions struct { Interactive bool // TTY specifies whether to allocate a pseudo-TTY for the container TTY bool + // SigProxy specifies whether to proxy all received signals to the process + SigProxy bool // Detach runs container in background and print container ID Detach bool // The key sequence for detaching a container. diff --git a/pkg/composer/run.go b/pkg/composer/run.go index c7e73df9cb0..d63b388aa01 100644 --- a/pkg/composer/run.go +++ b/pkg/composer/run.go @@ -45,6 +45,7 @@ type RunOptions struct { Detach bool NoDeps bool Tty bool + SigProxy bool Interactive bool Rm bool User string diff --git a/pkg/testutil/testutil.go b/pkg/testutil/testutil.go index 600fdb29ebe..25d315d5781 100644 --- a/pkg/testutil/testutil.go +++ b/pkg/testutil/testutil.go @@ -366,6 +366,11 @@ func (c *Cmd) runIfNecessary() *icmd.Result { return c.runResult } +func (c *Cmd) Start() *icmd.Result { + c.Base.T.Helper() + return icmd.StartCmd(c.Cmd) +} + func (c *Cmd) CmdOption(cmdOptions ...func(*Cmd)) *Cmd { for _, opt := range cmdOptions { opt(c) diff --git a/pkg/testutil/testutil_linux.go b/pkg/testutil/testutil_linux.go index 41266be6109..b6573b122a5 100644 --- a/pkg/testutil/testutil_linux.go +++ b/pkg/testutil/testutil_linux.go @@ -56,6 +56,25 @@ var ( // It should be "connection refused" as per the TCP RFC. // https://www.rfc-editor.org/rfc/rfc793 ExpectedConnectionRefusedError = "connection refused" + + SigProxyTrueOut = "received SIGINT" + SigProxyTimeoutMsg = "Timed Out; No signal received" + SigProxyTestScript = `#!/bin/sh + set -eu + + sig_msg () { + printf "` + SigProxyTrueOut + `" + end + } + + trap sig_msg INT + timeout=0 + while [ $timeout -ne 10 ]; do + timeout=$((timeout+1)) + sleep 1 + done + printf "` + SigProxyTimeoutMsg + `" + end` ) const (