4
4
"context"
5
5
"fmt"
6
6
"io"
7
+ "os"
7
8
"strings"
8
9
"syscall"
9
10
@@ -116,12 +117,8 @@ func runRun(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, ro
116
117
return runContainer (ctx , dockerCli , ropts , copts , containerCfg )
117
118
}
118
119
119
- //nolint:gocyclo
120
120
func runContainer (ctx context.Context , dockerCli command.Cli , runOpts * runOptions , copts * containerOptions , containerCfg * containerConfig ) error {
121
121
config := containerCfg .Config
122
- stdout , stderr := dockerCli .Out (), dockerCli .Err ()
123
- apiClient := dockerCli .Client ()
124
-
125
122
config .ArgsEscaped = false
126
123
127
124
if ! runOpts .detach {
@@ -144,71 +141,40 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
144
141
return toStatusError (err )
145
142
}
146
143
144
+ apiClient := dockerCli .Client ()
145
+
147
146
// New context here because we don't to cancel waiting on container exit/remove
148
147
// when we cancel attach, etc.
149
148
statusCtx , cancelStatusCtx := context .WithCancel (context .WithoutCancel (ctx ))
150
149
defer cancelStatusCtx ()
151
150
statusChan := waitExitOrRemoved (statusCtx , apiClient , containerID , copts .autoRemove )
152
151
153
- var (
154
- waitDisplayID chan struct {}
155
- errCh chan error
156
- )
152
+ var waitDisplayID chan struct {}
157
153
attach := config .AttachStdin || config .AttachStdout || config .AttachStderr
158
154
if ! attach {
159
155
// Make this asynchronous to allow the client to write to stdin before having to read the ID
160
156
waitDisplayID = make (chan struct {})
161
157
go func () {
162
158
defer close (waitDisplayID )
163
- _ , _ = fmt .Fprintln (stdout , containerID )
159
+ _ , _ = fmt .Fprintln (dockerCli . Out () , containerID )
164
160
}()
165
161
}
166
162
167
- attachCtx , attachCancel := context .WithCancel (context .WithoutCancel (ctx ))
168
- defer attachCancel ()
163
+ var attachWait func (<- chan int , error ) error
169
164
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 )
185
166
if err != nil {
186
167
return err
187
168
}
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 )
200
169
}
201
170
202
171
// 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
+ }
211
176
177
+ if err != nil {
212
178
if copts .autoRemove {
213
179
// wait container to be removed
214
180
<- statusChan
@@ -217,50 +183,95 @@ func runContainer(ctx context.Context, dockerCli command.Cli, runOpts *runOption
217
183
}
218
184
219
185
// 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
224
194
}
225
195
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
230
210
}
231
211
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
+ }
239
222
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 )
242
227
}
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
246
235
}
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
+ }
254
241
}
255
- }
256
242
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
258
269
}
259
270
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 ) {
261
272
resp , errAttach := dockerCli .Client ().ContainerAttach (ctx , containerID , options )
262
273
if errAttach != nil {
263
- return nil , errAttach
274
+ return nil , nil , errAttach
264
275
}
265
276
266
277
var (
@@ -282,8 +293,6 @@ func attachContainer(ctx context.Context, dockerCli command.Cli, containerID str
282
293
}
283
294
284
295
ch := make (chan error , 1 )
285
- * errCh = ch
286
-
287
296
go func () {
288
297
ch <- func () error {
289
298
streamer := hijackedIOStreamer {
@@ -302,7 +311,7 @@ func attachContainer(ctx context.Context, dockerCli command.Cli, containerID str
302
311
return errAttach
303
312
}()
304
313
}()
305
- return resp .Close , nil
314
+ return ch , resp .Close , nil
306
315
}
307
316
308
317
// withHelp decorates the error with a suggestion to use "--help".
0 commit comments