diff --git a/cli/command/container/run.go b/cli/command/container/run.go index ed4c96574c5f..5ef3e8b7bf7b 100644 --- a/cli/command/container/run.go +++ b/cli/command/container/run.go @@ -228,20 +228,31 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption } } - if err := <-errCh; err != nil { - if _, ok := err.(term.EscapeError); ok { - // The user entered the detach escape sequence. - return nil - } + select { + case err := <-errCh: + if err != nil { + if _, ok := err.(term.EscapeError); ok { + // The user entered the detach escape sequence. + return nil + } - logrus.Debugf("Error hijack: %s", err) - return err + logrus.Debugf("Error hijack: %s", err) + return err + } + status := <-statusChan + if status != 0 { + return cli.StatusError{StatusCode: status} + } + case status := <-statusChan: + // notify hijackedIOStreamer that we're exiting and wait + // so that the terminal can be restored. + cancelFun() + <-errCh + if status != 0 { + return cli.StatusError{StatusCode: status} + } } - status := <-statusChan - if status != 0 { - return cli.StatusError{StatusCode: status} - } return nil } diff --git a/e2e/container/run_test.go b/e2e/container/run_test.go index c835f1caf294..f4e591e21d4d 100644 --- a/e2e/container/run_test.go +++ b/e2e/container/run_test.go @@ -3,11 +3,13 @@ package container import ( "bytes" "fmt" + "os/exec" "strings" "syscall" "testing" "time" + "github.com/creack/pty" "github.com/docker/cli/e2e/internal/fixtures" "github.com/docker/cli/internal/test/environment" "github.com/docker/docker/api/types/versions" @@ -38,6 +40,39 @@ func TestRunAttachedFromRemoteImageAndRemove(t *testing.T) { golden.Assert(t, result.Stderr(), "run-attached-from-remote-and-remove.golden") } +func TestRunAttach(t *testing.T) { + skip.If(t, environment.RemoteDaemon()) + t.Parallel() + + streams := []string{"stdin", "stdout", "stderr"} + for _, stream := range streams { + t.Run(stream, func(t *testing.T) { + t.Parallel() + c := exec.Command("docker", "run", "-a", stream, "--rm", "alpine", + "sh", "-c", "sleep 1 && exit 7") + d := bytes.Buffer{} + c.Stdout = &d + c.Stderr = &d + _, err := pty.Start(c) + assert.NilError(t, err) + + done := make(chan error) + go func() { + done <- c.Wait() + }() + + select { + case <-time.After(20 * time.Second): + t.Fatal("docker run took too long, likely hang", d.String()) + case <-done: + } + + assert.Equal(t, c.ProcessState.ExitCode(), 7) + assert.Check(t, is.Contains(d.String(), "exit status 7")) + }) + } +} + // Regression test for https://github.com/docker/cli/issues/5053 func TestRunInvalidEntrypointWithAutoremove(t *testing.T) { environment.SkipIfDaemonNotLinux(t)