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,123 +141,140 @@ 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.
172
+ err = apiClient .ContainerStart (ctx , containerID , container.StartOptions {})
173
+ if err != nil {
207
174
if attach {
208
- attachCancel ()
209
- <- errCh
175
+ attachWait (statusChan , err )
210
176
}
211
-
212
177
if copts .autoRemove {
213
178
// wait container to be removed
214
179
<- statusChan
215
180
}
216
181
return toStatusError (err )
217
182
}
218
183
184
+ if attach {
185
+ return attachWait (statusChan , nil )
186
+ }
187
+
219
188
// 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
224
197
}
225
198
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
230
213
}
231
214
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
+ }
239
225
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 )
242
230
}
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
246
238
}
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
+ }
254
244
}
255
- }
256
245
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
258
272
}
259
273
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 ) {
261
275
resp , errAttach := dockerCli .Client ().ContainerAttach (ctx , containerID , options )
262
276
if errAttach != nil {
263
- return nil , errAttach
277
+ return nil , nil , errAttach
264
278
}
265
279
266
280
var (
@@ -282,8 +296,6 @@ func attachContainer(ctx context.Context, dockerCli command.Cli, containerID str
282
296
}
283
297
284
298
ch := make (chan error , 1 )
285
- * errCh = ch
286
-
287
299
go func () {
288
300
ch <- func () error {
289
301
streamer := hijackedIOStreamer {
@@ -302,7 +314,7 @@ func attachContainer(ctx context.Context, dockerCli command.Cli, containerID str
302
314
return errAttach
303
315
}()
304
316
}()
305
- return resp .Close , nil
317
+ return ch , resp .Close , nil
306
318
}
307
319
308
320
// withHelp decorates the error with a suggestion to use "--help".
0 commit comments