Skip to content

Commit e554bfe

Browse files
authored
Merge pull request #5662 from laurazard/consistent-attach-check
run: correctly handle only STDIN attached
2 parents 619ea8e + 7dab597 commit e554bfe

File tree

3 files changed

+60
-17
lines changed

3 files changed

+60
-17
lines changed

cli/command/container/run.go

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -161,15 +161,15 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
161161
waitDisplayID chan struct{}
162162
errCh chan error
163163
)
164-
if !config.AttachStdout && !config.AttachStderr {
164+
attach := config.AttachStdin || config.AttachStdout || config.AttachStderr
165+
if !attach {
165166
// Make this asynchronous to allow the client to write to stdin before having to read the ID
166167
waitDisplayID = make(chan struct{})
167168
go func() {
168169
defer close(waitDisplayID)
169170
_, _ = fmt.Fprintln(stdout, containerID)
170171
}()
171172
}
172-
attach := config.AttachStdin || config.AttachStdout || config.AttachStderr
173173
if attach {
174174
detachKeys := dockerCli.ConfigFile().DetachKeys
175175
if runOpts.detachKeys != "" {
@@ -215,14 +215,22 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
215215
return toStatusError(err)
216216
}
217217

218-
if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.Out().IsTerminal() {
218+
// Detached mode: wait for the id to be displayed and return.
219+
if !attach {
220+
// Detached mode
221+
<-waitDisplayID
222+
return nil
223+
}
224+
225+
if config.Tty && dockerCli.Out().IsTerminal() {
219226
if err := MonitorTtySize(ctx, dockerCli, containerID, false); err != nil {
220227
_, _ = fmt.Fprintln(stderr, "Error monitoring TTY size:", err)
221228
}
222229
}
223230

224-
if errCh != nil {
225-
if err := <-errCh; err != nil {
231+
select {
232+
case err := <-errCh:
233+
if err != nil {
226234
if _, ok := err.(term.EscapeError); ok {
227235
// The user entered the detach escape sequence.
228236
return nil
@@ -231,19 +239,20 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
231239
logrus.Debugf("Error hijack: %s", err)
232240
return err
233241
}
242+
status := <-statusChan
243+
if status != 0 {
244+
return cli.StatusError{StatusCode: status}
245+
}
246+
case status := <-statusChan:
247+
// notify hijackedIOStreamer that we're exiting and wait
248+
// so that the terminal can be restored.
249+
cancelFun()
250+
<-errCh
251+
if status != 0 {
252+
return cli.StatusError{StatusCode: status}
253+
}
234254
}
235255

236-
// Detached mode: wait for the id to be displayed and return.
237-
if !config.AttachStdout && !config.AttachStderr {
238-
// Detached mode
239-
<-waitDisplayID
240-
return nil
241-
}
242-
243-
status := <-statusChan
244-
if status != 0 {
245-
return cli.StatusError{StatusCode: status}
246-
}
247256
return nil
248257
}
249258

e2e/container/attach_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ func TestAttachInterrupt(t *testing.T) {
3737
// todo(laurazard): make this test work w/ dind over ssh
3838
skip.If(t, strings.Contains(os.Getenv("DOCKER_HOST"), "ssh://"))
3939

40-
// if
4140
result := icmd.RunCommand("docker", "run", "-d", fixtures.AlpineImage,
4241
"sh", "-c", "trap \"exit 33\" SIGINT; for i in $(seq 100); do sleep 0.1; done; exit 34")
4342
result.Assert(t, icmd.Success)

e2e/container/run_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package container
33
import (
44
"bytes"
55
"fmt"
6+
"os/exec"
67
"strings"
78
"syscall"
89
"testing"
910
"time"
1011

12+
"github.com/creack/pty"
1113
"github.com/docker/cli/e2e/internal/fixtures"
1214
"github.com/docker/cli/internal/test/environment"
1315
"github.com/docker/docker/api/types/versions"
@@ -38,6 +40,39 @@ func TestRunAttachedFromRemoteImageAndRemove(t *testing.T) {
3840
golden.Assert(t, result.Stderr(), "run-attached-from-remote-and-remove.golden")
3941
}
4042

43+
func TestRunAttach(t *testing.T) {
44+
skip.If(t, environment.RemoteDaemon())
45+
t.Parallel()
46+
47+
streams := []string{"stdin", "stdout", "stderr"}
48+
for _, stream := range streams {
49+
t.Run(stream, func(t *testing.T) {
50+
t.Parallel()
51+
c := exec.Command("docker", "run", "-a", stream, "--rm", "alpine",
52+
"sh", "-c", "sleep 1 && exit 7")
53+
d := bytes.Buffer{}
54+
c.Stdout = &d
55+
c.Stderr = &d
56+
_, err := pty.Start(c)
57+
assert.NilError(t, err)
58+
59+
done := make(chan error)
60+
go func() {
61+
done <- c.Wait()
62+
}()
63+
64+
select {
65+
case <-time.After(20 * time.Second):
66+
t.Fatal("docker run took too long, likely hang", d.String())
67+
case <-done:
68+
}
69+
70+
assert.Equal(t, c.ProcessState.ExitCode(), 7)
71+
assert.Check(t, is.Contains(d.String(), "exit status 7"))
72+
})
73+
}
74+
}
75+
4176
// Regression test for https://github.com/docker/cli/issues/5053
4277
func TestRunInvalidEntrypointWithAutoremove(t *testing.T) {
4378
environment.SkipIfDaemonNotLinux(t)

0 commit comments

Comments
 (0)