Skip to content

Commit 3379da2

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

File tree

1 file changed

+91
-79
lines changed

1 file changed

+91
-79
lines changed

cli/command/container/run.go

+91-79
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,123 +141,140 @@ 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.
172+
err = apiClient.ContainerStart(ctx, containerID, container.StartOptions{})
173+
if err != nil {
207174
if attach {
208-
attachCancel()
209-
<-errCh
175+
attachWait(statusChan, err)
210176
}
211-
212177
if copts.autoRemove {
213178
// wait container to be removed
214179
<-statusChan
215180
}
216181
return toStatusError(err)
217182
}
218183

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

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-
}
199+
// ctx should not be cancellable here, as this would kill the stream to the container
200+
// and we want to keep the stream open until the process in the container exits or until
201+
// the user forcefully terminates the CLI.
202+
attachCtx, attachCancel := context.WithCancel(context.WithoutCancel(ctx))
203+
errCh, closeFn, err := attachContainer(attachCtx, dockerCli, containerID, config, container.AttachOptions{
204+
Stream: true,
205+
Stdin: config.AttachStdin,
206+
Stdout: config.AttachStdout,
207+
Stderr: config.AttachStderr,
208+
DetachKeys: detachKeys,
209+
})
210+
if err != nil {
211+
attachCancel()
212+
return nil, err
230213
}
231214

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-
}
215+
var sigc chan os.Signal
216+
if runOpts.sigProxy {
217+
sigc = notifyAllSignals()
218+
// since we're explicitly setting up signal handling here, and the daemon will
219+
// get notified independently of the clients ctx cancellation, we use this context
220+
// but without cancellation to avoid ForwardAllSignals from returning
221+
// before all signals are forwarded.
222+
bgCtx := context.WithoutCancel(ctx)
223+
go ForwardAllSignals(bgCtx, dockerCli.Client(), containerID, sigc)
224+
}
239225

240-
logrus.Debugf("Error hijack: %s", err)
241-
return err
226+
return func(statusC <-chan int, err error) error {
227+
defer closeFn()
228+
if runOpts.sigProxy {
229+
defer signal.StopCatch(sigc)
242230
}
243-
status := <-statusChan
244-
if status != 0 {
245-
return cli.StatusError{StatusCode: status}
231+
232+
// if the container failed to start, just cancel the streamer
233+
// and wait for the terminal to be restored
234+
if err != nil {
235+
attachCancel()
236+
<-errCh
237+
return nil
246238
}
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}
239+
240+
if config.Tty && dockerCli.Out().IsTerminal() {
241+
if err := MonitorTtySize(attachCtx, dockerCli, containerID, false); err != nil {
242+
_, _ = fmt.Fprintln(dockerCli.Err(), "Error monitoring TTY size:", err)
243+
}
254244
}
255-
}
256245

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

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

266280
var (
@@ -282,8 +296,6 @@ func attachContainer(ctx context.Context, dockerCli command.Cli, containerID str
282296
}
283297

284298
ch := make(chan error, 1)
285-
*errCh = ch
286-
287299
go func() {
288300
ch <- func() error {
289301
streamer := hijackedIOStreamer{
@@ -302,7 +314,7 @@ func attachContainer(ctx context.Context, dockerCli command.Cli, containerID str
302314
return errAttach
303315
}()
304316
}()
305-
return resp.Close, nil
317+
return ch, resp.Close, nil
306318
}
307319

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

0 commit comments

Comments
 (0)