Skip to content

Commit db4df1a

Browse files
committed
run/refactor: WIP extract attach logic from run
Signed-off-by: Laura Brehm <[email protected]>
1 parent a0cd512 commit db4df1a

File tree

1 file changed

+89
-80
lines changed

1 file changed

+89
-80
lines changed

cli/command/container/run.go

+89-80
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"io"
7+
"os"
78
"strings"
89
"syscall"
910

@@ -116,12 +117,8 @@ func runRun(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, ro
116117
return runContainer(ctx, dockerCli, ropts, copts, containerCfg)
117118
}
118119

119-
//nolint:gocyclo
120120
func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOptions, copts *containerOptions, containerCfg *containerConfig) error {
121121
config := containerCfg.Config
122-
stdout, stderr := dockerCli.Out(), dockerCli.Err()
123-
apiClient := dockerCli.Client()
124-
125122
config.ArgsEscaped = false
126123

127124
if !runOpts.detach {
@@ -144,71 +141,40 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
144141
return toStatusError(err)
145142
}
146143

144+
apiClient := dockerCli.Client()
145+
147146
// New context here because we don't to cancel waiting on container exit/remove
148147
// when we cancel attach, etc.
149148
statusCtx, cancelStatusCtx := context.WithCancel(context.WithoutCancel(ctx))
150149
defer cancelStatusCtx()
151150
statusChan := waitExitOrRemoved(statusCtx, apiClient, containerID, copts.autoRemove)
152151

153-
var (
154-
waitDisplayID chan struct{}
155-
errCh chan error
156-
)
152+
var waitDisplayID chan struct{}
157153
attach := config.AttachStdin || config.AttachStdout || config.AttachStderr
158154
if !attach {
159155
// Make this asynchronous to allow the client to write to stdin before having to read the ID
160156
waitDisplayID = make(chan struct{})
161157
go func() {
162158
defer close(waitDisplayID)
163-
_, _ = fmt.Fprintln(stdout, containerID)
159+
_, _ = fmt.Fprintln(dockerCli.Out(), containerID)
164160
}()
165161
}
166162

167-
attachCtx, attachCancel := context.WithCancel(context.WithoutCancel(ctx))
168-
defer attachCancel()
163+
var attachWait func(<-chan int, error) error
169164
if attach {
170-
detachKeys := dockerCli.ConfigFile().DetachKeys
171-
if runOpts.detachKeys != "" {
172-
detachKeys = runOpts.detachKeys
173-
}
174-
175-
// ctx should not be cancellable here, as this would kill the stream to the container
176-
// and we want to keep the stream open until the process in the container exits or until
177-
// the user forcefully terminates the CLI.
178-
closeFn, err := attachContainer(attachCtx, dockerCli, containerID, &errCh, config, container.AttachOptions{
179-
Stream: true,
180-
Stdin: config.AttachStdin,
181-
Stdout: config.AttachStdout,
182-
Stderr: config.AttachStderr,
183-
DetachKeys: detachKeys,
184-
})
165+
attachWait, err = setupContainerAttach(ctx, dockerCli, containerID, runOpts, config)
185166
if err != nil {
186167
return err
187168
}
188-
defer closeFn()
189-
}
190-
191-
if runOpts.sigProxy {
192-
sigc := notifyAllSignals()
193-
// since we're explicitly setting up signal handling here, and the daemon will
194-
// get notified independently of the clients ctx cancellation, we use this context
195-
// but without cancellation to avoid ForwardAllSignals from returning
196-
// before all signals are forwarded.
197-
bgCtx := context.WithoutCancel(ctx)
198-
go ForwardAllSignals(bgCtx, apiClient, containerID, sigc)
199-
defer signal.StopCatch(sigc)
200169
}
201170

202171
// start the container
203-
if err := apiClient.ContainerStart(ctx, containerID, container.StartOptions{}); err != nil {
204-
// If we have hijackedIOStreamer, we should notify
205-
// hijackedIOStreamer we are going to exit and wait
206-
// to avoid the terminal are not restored.
207-
if attach {
208-
attachCancel()
209-
<-errCh
210-
}
172+
err = apiClient.ContainerStart(ctx, containerID, container.StartOptions{})
173+
if attach {
174+
return attachWait(statusChan, err)
175+
}
211176

177+
if err != nil {
212178
if copts.autoRemove {
213179
// wait container to be removed
214180
<-statusChan
@@ -217,50 +183,95 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
217183
}
218184

219185
// Detached mode: wait for the id to be displayed and return.
220-
if !attach {
221-
// Detached mode
222-
<-waitDisplayID
223-
return nil
186+
<-waitDisplayID
187+
return nil
188+
}
189+
190+
func setupContainerAttach(ctx context.Context, dockerCli command.Cli, containerID string, runOpts *runOptions, config *container.Config) (func(<-chan int, error) error, error) {
191+
detachKeys := dockerCli.ConfigFile().DetachKeys
192+
if runOpts.detachKeys != "" {
193+
detachKeys = runOpts.detachKeys
224194
}
225195

226-
if config.Tty && dockerCli.Out().IsTerminal() {
227-
if err := MonitorTtySize(attachCtx, dockerCli, containerID, false); err != nil {
228-
_, _ = fmt.Fprintln(stderr, "Error monitoring TTY size:", err)
229-
}
196+
// ctx should not be cancellable here, as this would kill the stream to the container
197+
// and we want to keep the stream open until the process in the container exits or until
198+
// the user forcefully terminates the CLI.
199+
attachCtx, attachCancel := context.WithCancel(context.WithoutCancel(ctx))
200+
errCh, closeFn, err := attachContainer(attachCtx, dockerCli, containerID, config, container.AttachOptions{
201+
Stream: true,
202+
Stdin: config.AttachStdin,
203+
Stdout: config.AttachStdout,
204+
Stderr: config.AttachStderr,
205+
DetachKeys: detachKeys,
206+
})
207+
if err != nil {
208+
attachCancel()
209+
return nil, err
230210
}
231211

232-
select {
233-
case err := <-errCh:
234-
if err != nil {
235-
if _, ok := err.(term.EscapeError); ok {
236-
// The user entered the detach escape sequence.
237-
return nil
238-
}
212+
var sigc chan os.Signal
213+
if runOpts.sigProxy {
214+
sigc = notifyAllSignals()
215+
// since we're explicitly setting up signal handling here, and the daemon will
216+
// get notified independently of the clients ctx cancellation, we use this context
217+
// but without cancellation to avoid ForwardAllSignals from returning
218+
// before all signals are forwarded.
219+
bgCtx := context.WithoutCancel(ctx)
220+
go ForwardAllSignals(bgCtx, dockerCli.Client(), containerID, sigc)
221+
}
239222

240-
logrus.Debugf("Error hijack: %s", err)
241-
return err
223+
return func(statusC <-chan int, err error) error {
224+
defer closeFn()
225+
if runOpts.sigProxy {
226+
defer signal.StopCatch(sigc)
242227
}
243-
status := <-statusChan
244-
if status != 0 {
245-
return cli.StatusError{StatusCode: status}
228+
229+
// if the container failed to start, just cancel the streamer
230+
// and wait for the terminal to be restored
231+
if err != nil {
232+
attachCancel()
233+
<-errCh
234+
return nil
246235
}
247-
case status := <-statusChan:
248-
// notify hijackedIOStreamer that we're exiting and wait
249-
// so that the terminal can be restored.
250-
attachCancel()
251-
<-errCh
252-
if status != 0 {
253-
return cli.StatusError{StatusCode: status}
236+
237+
if config.Tty && dockerCli.Out().IsTerminal() {
238+
if err := MonitorTtySize(attachCtx, dockerCli, containerID, false); err != nil {
239+
_, _ = fmt.Fprintln(dockerCli.Err(), "Error monitoring TTY size:", err)
240+
}
254241
}
255-
}
256242

257-
return nil
243+
select {
244+
case err := <-errCh:
245+
if err != nil {
246+
if _, ok := err.(term.EscapeError); ok {
247+
// The user entered the detach escape sequence.
248+
return nil
249+
}
250+
251+
logrus.Debugf("Error hijack: %s", err)
252+
return err
253+
}
254+
status := <-statusC
255+
if status != 0 {
256+
return cli.StatusError{StatusCode: status}
257+
}
258+
case status := <-statusC:
259+
// notify hijackedIOStreamer that we're exiting and wait
260+
// so that the terminal can be restored.
261+
attachCancel()
262+
<-errCh
263+
if status != 0 {
264+
return cli.StatusError{StatusCode: status}
265+
}
266+
}
267+
return nil
268+
}, nil
258269
}
259270

260-
func attachContainer(ctx context.Context, dockerCli command.Cli, containerID string, errCh *chan error, config *container.Config, options container.AttachOptions) (func(), error) {
271+
func attachContainer(ctx context.Context, dockerCli command.Cli, containerID string, config *container.Config, options container.AttachOptions) (chan error, func(), error) {
261272
resp, errAttach := dockerCli.Client().ContainerAttach(ctx, containerID, options)
262273
if errAttach != nil {
263-
return nil, errAttach
274+
return nil, nil, errAttach
264275
}
265276

266277
var (
@@ -282,8 +293,6 @@ func attachContainer(ctx context.Context, dockerCli command.Cli, containerID str
282293
}
283294

284295
ch := make(chan error, 1)
285-
*errCh = ch
286-
287296
go func() {
288297
ch <- func() error {
289298
streamer := hijackedIOStreamer{
@@ -302,7 +311,7 @@ func attachContainer(ctx context.Context, dockerCli command.Cli, containerID str
302311
return errAttach
303312
}()
304313
}()
305-
return resp.Close, nil
314+
return ch, resp.Close, nil
306315
}
307316

308317
// withHelp decorates the error with a suggestion to use "--help".

0 commit comments

Comments
 (0)