This cleans up attach a little bit, and moves it out of the container
package.
Really `AttachStream` is a method on `*stream.Config`, so moved if from
a package level function to one bound to `Config`.
In addition, uses a config struct rather than passing around tons and
tons of arguments.
Signed-off-by: Brian Goff <cpuguy83@gmail.com>
| ... | ... |
@@ -31,7 +31,6 @@ import ( |
| 31 | 31 |
"github.com/docker/docker/opts" |
| 32 | 32 |
"github.com/docker/docker/pkg/idtools" |
| 33 | 33 |
"github.com/docker/docker/pkg/ioutils" |
| 34 |
- "github.com/docker/docker/pkg/promise" |
|
| 35 | 34 |
"github.com/docker/docker/pkg/signal" |
| 36 | 35 |
"github.com/docker/docker/pkg/symlink" |
| 37 | 36 |
"github.com/docker/docker/restartmanager" |
| ... | ... |
@@ -58,13 +57,6 @@ var ( |
| 58 | 58 |
errInvalidNetwork = fmt.Errorf("invalid network settings while building port map info")
|
| 59 | 59 |
) |
| 60 | 60 |
|
| 61 |
-// DetachError is special error which returned in case of container detach. |
|
| 62 |
-type DetachError struct{}
|
|
| 63 |
- |
|
| 64 |
-func (DetachError) Error() string {
|
|
| 65 |
- return "detached from container" |
|
| 66 |
-} |
|
| 67 |
- |
|
| 68 | 61 |
// CommonContainer holds the fields for a container which are |
| 69 | 62 |
// applicable across all platforms supported by the daemon. |
| 70 | 63 |
type CommonContainer struct {
|
| ... | ... |
@@ -373,183 +365,17 @@ func (container *Container) GetExecIDs() []string {
|
| 373 | 373 |
return container.ExecCommands.List() |
| 374 | 374 |
} |
| 375 | 375 |
|
| 376 |
-// Attach connects to the container's TTY, delegating to standard |
|
| 377 |
-// streams or websockets depending on the configuration. |
|
| 378 |
-func (container *Container) Attach(stdin io.ReadCloser, stdout io.Writer, stderr io.Writer, keys []byte) chan error {
|
|
| 376 |
+// Attach connects to the container's stdio to the client streams |
|
| 377 |
+func (container *Container) Attach(cfg *stream.AttachConfig) chan error {
|
|
| 379 | 378 |
ctx := container.InitAttachContext() |
| 380 |
- return AttachStreams(ctx, container.StreamConfig, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, stdin, stdout, stderr, keys) |
|
| 381 |
-} |
|
| 382 |
- |
|
| 383 |
-// AttachStreams connects streams to a TTY. |
|
| 384 |
-// Used by exec too. Should this move somewhere else? |
|
| 385 |
-func AttachStreams(ctx context.Context, streamConfig *stream.Config, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer, keys []byte) chan error {
|
|
| 386 |
- var ( |
|
| 387 |
- cStdout, cStderr io.ReadCloser |
|
| 388 |
- cStdin io.WriteCloser |
|
| 389 |
- wg sync.WaitGroup |
|
| 390 |
- errors = make(chan error, 3) |
|
| 391 |
- ) |
|
| 392 | 379 |
|
| 393 |
- if stdin != nil && openStdin {
|
|
| 394 |
- cStdin = streamConfig.StdinPipe() |
|
| 395 |
- wg.Add(1) |
|
| 380 |
+ cfg.TTY = container.Config.Tty |
|
| 381 |
+ if !container.Config.OpenStdin {
|
|
| 382 |
+ cfg.Stdin = nil |
|
| 396 | 383 |
} |
| 384 |
+ cfg.CloseStdin = cfg.Stdin != nil && container.Config.StdinOnce |
|
| 397 | 385 |
|
| 398 |
- if stdout != nil {
|
|
| 399 |
- cStdout = streamConfig.StdoutPipe() |
|
| 400 |
- wg.Add(1) |
|
| 401 |
- } |
|
| 402 |
- |
|
| 403 |
- if stderr != nil {
|
|
| 404 |
- cStderr = streamConfig.StderrPipe() |
|
| 405 |
- wg.Add(1) |
|
| 406 |
- } |
|
| 407 |
- |
|
| 408 |
- // Connect stdin of container to the http conn. |
|
| 409 |
- go func() {
|
|
| 410 |
- if stdin == nil || !openStdin {
|
|
| 411 |
- return |
|
| 412 |
- } |
|
| 413 |
- logrus.Debug("attach: stdin: begin")
|
|
| 414 |
- |
|
| 415 |
- var err error |
|
| 416 |
- if tty {
|
|
| 417 |
- _, err = copyEscapable(cStdin, stdin, keys) |
|
| 418 |
- } else {
|
|
| 419 |
- _, err = io.Copy(cStdin, stdin) |
|
| 420 |
- } |
|
| 421 |
- if err == io.ErrClosedPipe {
|
|
| 422 |
- err = nil |
|
| 423 |
- } |
|
| 424 |
- if err != nil {
|
|
| 425 |
- logrus.Errorf("attach: stdin: %s", err)
|
|
| 426 |
- errors <- err |
|
| 427 |
- } |
|
| 428 |
- if stdinOnce && !tty {
|
|
| 429 |
- cStdin.Close() |
|
| 430 |
- } else {
|
|
| 431 |
- // No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr |
|
| 432 |
- if cStdout != nil {
|
|
| 433 |
- cStdout.Close() |
|
| 434 |
- } |
|
| 435 |
- if cStderr != nil {
|
|
| 436 |
- cStderr.Close() |
|
| 437 |
- } |
|
| 438 |
- } |
|
| 439 |
- logrus.Debug("attach: stdin: end")
|
|
| 440 |
- wg.Done() |
|
| 441 |
- }() |
|
| 442 |
- |
|
| 443 |
- attachStream := func(name string, stream io.Writer, streamPipe io.ReadCloser) {
|
|
| 444 |
- if stream == nil {
|
|
| 445 |
- return |
|
| 446 |
- } |
|
| 447 |
- |
|
| 448 |
- logrus.Debugf("attach: %s: begin", name)
|
|
| 449 |
- _, err := io.Copy(stream, streamPipe) |
|
| 450 |
- if err == io.ErrClosedPipe {
|
|
| 451 |
- err = nil |
|
| 452 |
- } |
|
| 453 |
- if err != nil {
|
|
| 454 |
- logrus.Errorf("attach: %s: %v", name, err)
|
|
| 455 |
- errors <- err |
|
| 456 |
- } |
|
| 457 |
- // Make sure stdin gets closed |
|
| 458 |
- if stdin != nil {
|
|
| 459 |
- stdin.Close() |
|
| 460 |
- } |
|
| 461 |
- streamPipe.Close() |
|
| 462 |
- logrus.Debugf("attach: %s: end", name)
|
|
| 463 |
- wg.Done() |
|
| 464 |
- } |
|
| 465 |
- |
|
| 466 |
- go attachStream("stdout", stdout, cStdout)
|
|
| 467 |
- go attachStream("stderr", stderr, cStderr)
|
|
| 468 |
- |
|
| 469 |
- return promise.Go(func() error {
|
|
| 470 |
- done := make(chan struct{})
|
|
| 471 |
- go func() {
|
|
| 472 |
- wg.Wait() |
|
| 473 |
- close(done) |
|
| 474 |
- }() |
|
| 475 |
- select {
|
|
| 476 |
- case <-done: |
|
| 477 |
- case <-ctx.Done(): |
|
| 478 |
- // close all pipes |
|
| 479 |
- if cStdin != nil {
|
|
| 480 |
- cStdin.Close() |
|
| 481 |
- } |
|
| 482 |
- if cStdout != nil {
|
|
| 483 |
- cStdout.Close() |
|
| 484 |
- } |
|
| 485 |
- if cStderr != nil {
|
|
| 486 |
- cStderr.Close() |
|
| 487 |
- } |
|
| 488 |
- <-done |
|
| 489 |
- } |
|
| 490 |
- close(errors) |
|
| 491 |
- for err := range errors {
|
|
| 492 |
- if err != nil {
|
|
| 493 |
- return err |
|
| 494 |
- } |
|
| 495 |
- } |
|
| 496 |
- return nil |
|
| 497 |
- }) |
|
| 498 |
-} |
|
| 499 |
- |
|
| 500 |
-// Code c/c from io.Copy() modified to handle escape sequence |
|
| 501 |
-func copyEscapable(dst io.Writer, src io.ReadCloser, keys []byte) (written int64, err error) {
|
|
| 502 |
- if len(keys) == 0 {
|
|
| 503 |
- // Default keys : ctrl-p ctrl-q |
|
| 504 |
- keys = []byte{16, 17}
|
|
| 505 |
- } |
|
| 506 |
- buf := make([]byte, 32*1024) |
|
| 507 |
- for {
|
|
| 508 |
- nr, er := src.Read(buf) |
|
| 509 |
- if nr > 0 {
|
|
| 510 |
- // ---- Docker addition |
|
| 511 |
- preservBuf := []byte{}
|
|
| 512 |
- for i, key := range keys {
|
|
| 513 |
- preservBuf = append(preservBuf, buf[0:nr]...) |
|
| 514 |
- if nr != 1 || buf[0] != key {
|
|
| 515 |
- break |
|
| 516 |
- } |
|
| 517 |
- if i == len(keys)-1 {
|
|
| 518 |
- src.Close() |
|
| 519 |
- return 0, DetachError{}
|
|
| 520 |
- } |
|
| 521 |
- nr, er = src.Read(buf) |
|
| 522 |
- } |
|
| 523 |
- var nw int |
|
| 524 |
- var ew error |
|
| 525 |
- if len(preservBuf) > 0 {
|
|
| 526 |
- nw, ew = dst.Write(preservBuf) |
|
| 527 |
- nr = len(preservBuf) |
|
| 528 |
- } else {
|
|
| 529 |
- // ---- End of docker |
|
| 530 |
- nw, ew = dst.Write(buf[0:nr]) |
|
| 531 |
- } |
|
| 532 |
- if nw > 0 {
|
|
| 533 |
- written += int64(nw) |
|
| 534 |
- } |
|
| 535 |
- if ew != nil {
|
|
| 536 |
- err = ew |
|
| 537 |
- break |
|
| 538 |
- } |
|
| 539 |
- if nr != nw {
|
|
| 540 |
- err = io.ErrShortWrite |
|
| 541 |
- break |
|
| 542 |
- } |
|
| 543 |
- } |
|
| 544 |
- if er == io.EOF {
|
|
| 545 |
- break |
|
| 546 |
- } |
|
| 547 |
- if er != nil {
|
|
| 548 |
- err = er |
|
| 549 |
- break |
|
| 550 |
- } |
|
| 551 |
- } |
|
| 552 |
- return written, err |
|
| 386 |
+ return container.StreamConfig.Attach(ctx, cfg) |
|
| 553 | 387 |
} |
| 554 | 388 |
|
| 555 | 389 |
// ShouldRestart decides whether the daemon should restart the container or not. |
| 556 | 390 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,209 @@ |
| 0 |
+package stream |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "io" |
|
| 4 |
+ "sync" |
|
| 5 |
+ |
|
| 6 |
+ "golang.org/x/net/context" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/Sirupsen/logrus" |
|
| 9 |
+ "github.com/docker/docker/pkg/promise" |
|
| 10 |
+) |
|
| 11 |
+ |
|
| 12 |
+// DetachError is special error which returned in case of container detach. |
|
| 13 |
+type DetachError struct{}
|
|
| 14 |
+ |
|
| 15 |
+func (DetachError) Error() string {
|
|
| 16 |
+ return "detached from container" |
|
| 17 |
+} |
|
| 18 |
+ |
|
| 19 |
+// AttachConfig is the config struct used to attach a client to a stream's stdio |
|
| 20 |
+type AttachConfig struct {
|
|
| 21 |
+ // Tells the attach copier that the stream's stdin is a TTY and to look for |
|
| 22 |
+ // escape sequences in stdin to detach from the stream. |
|
| 23 |
+ // When true the escape sequence is not passed to the underlying stream |
|
| 24 |
+ TTY bool |
|
| 25 |
+ // Specifies the detach keys the client will be using |
|
| 26 |
+ // Only useful when `TTY` is true |
|
| 27 |
+ DetachKeys []byte |
|
| 28 |
+ |
|
| 29 |
+ // CloseStdin signals that once done, stdin for the attached stream should be closed |
|
| 30 |
+ // For example, this would close the attached container's stdin. |
|
| 31 |
+ CloseStdin bool |
|
| 32 |
+ |
|
| 33 |
+ // Provide client streams to wire up to |
|
| 34 |
+ Stdin io.ReadCloser |
|
| 35 |
+ Stdout, Stderr io.Writer |
|
| 36 |
+} |
|
| 37 |
+ |
|
| 38 |
+// Attach attaches the stream config to the streams specified in |
|
| 39 |
+// the AttachOptions |
|
| 40 |
+func (c *Config) Attach(ctx context.Context, cfg *AttachConfig) chan error {
|
|
| 41 |
+ var ( |
|
| 42 |
+ cStdout, cStderr io.ReadCloser |
|
| 43 |
+ cStdin io.WriteCloser |
|
| 44 |
+ wg sync.WaitGroup |
|
| 45 |
+ errors = make(chan error, 3) |
|
| 46 |
+ ) |
|
| 47 |
+ |
|
| 48 |
+ if cfg.Stdin != nil {
|
|
| 49 |
+ cStdin = c.StdinPipe() |
|
| 50 |
+ wg.Add(1) |
|
| 51 |
+ } |
|
| 52 |
+ |
|
| 53 |
+ if cfg.Stdout != nil {
|
|
| 54 |
+ cStdout = c.StdoutPipe() |
|
| 55 |
+ wg.Add(1) |
|
| 56 |
+ } |
|
| 57 |
+ |
|
| 58 |
+ if cfg.Stderr != nil {
|
|
| 59 |
+ cStderr = c.StderrPipe() |
|
| 60 |
+ wg.Add(1) |
|
| 61 |
+ } |
|
| 62 |
+ |
|
| 63 |
+ // Connect stdin of container to the attach stdin stream. |
|
| 64 |
+ go func() {
|
|
| 65 |
+ if cfg.Stdin == nil {
|
|
| 66 |
+ return |
|
| 67 |
+ } |
|
| 68 |
+ logrus.Debug("attach: stdin: begin")
|
|
| 69 |
+ |
|
| 70 |
+ var err error |
|
| 71 |
+ if cfg.TTY {
|
|
| 72 |
+ _, err = copyEscapable(cStdin, cfg.Stdin, cfg.DetachKeys) |
|
| 73 |
+ } else {
|
|
| 74 |
+ _, err = io.Copy(cStdin, cfg.Stdin) |
|
| 75 |
+ } |
|
| 76 |
+ if err == io.ErrClosedPipe {
|
|
| 77 |
+ err = nil |
|
| 78 |
+ } |
|
| 79 |
+ if err != nil {
|
|
| 80 |
+ logrus.Errorf("attach: stdin: %s", err)
|
|
| 81 |
+ errors <- err |
|
| 82 |
+ } |
|
| 83 |
+ if cfg.CloseStdin && !cfg.TTY {
|
|
| 84 |
+ cStdin.Close() |
|
| 85 |
+ } else {
|
|
| 86 |
+ // No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr |
|
| 87 |
+ if cStdout != nil {
|
|
| 88 |
+ cStdout.Close() |
|
| 89 |
+ } |
|
| 90 |
+ if cStderr != nil {
|
|
| 91 |
+ cStderr.Close() |
|
| 92 |
+ } |
|
| 93 |
+ } |
|
| 94 |
+ logrus.Debug("attach: stdin: end")
|
|
| 95 |
+ wg.Done() |
|
| 96 |
+ }() |
|
| 97 |
+ |
|
| 98 |
+ attachStream := func(name string, stream io.Writer, streamPipe io.ReadCloser) {
|
|
| 99 |
+ if stream == nil {
|
|
| 100 |
+ return |
|
| 101 |
+ } |
|
| 102 |
+ |
|
| 103 |
+ logrus.Debugf("attach: %s: begin", name)
|
|
| 104 |
+ _, err := io.Copy(stream, streamPipe) |
|
| 105 |
+ if err == io.ErrClosedPipe {
|
|
| 106 |
+ err = nil |
|
| 107 |
+ } |
|
| 108 |
+ if err != nil {
|
|
| 109 |
+ logrus.Errorf("attach: %s: %v", name, err)
|
|
| 110 |
+ errors <- err |
|
| 111 |
+ } |
|
| 112 |
+ // Make sure stdin gets closed |
|
| 113 |
+ if cfg.Stdin != nil {
|
|
| 114 |
+ cfg.Stdin.Close() |
|
| 115 |
+ } |
|
| 116 |
+ streamPipe.Close() |
|
| 117 |
+ logrus.Debugf("attach: %s: end", name)
|
|
| 118 |
+ wg.Done() |
|
| 119 |
+ } |
|
| 120 |
+ |
|
| 121 |
+ go attachStream("stdout", cfg.Stdout, cStdout)
|
|
| 122 |
+ go attachStream("stderr", cfg.Stderr, cStderr)
|
|
| 123 |
+ |
|
| 124 |
+ return promise.Go(func() error {
|
|
| 125 |
+ done := make(chan struct{})
|
|
| 126 |
+ go func() {
|
|
| 127 |
+ wg.Wait() |
|
| 128 |
+ close(done) |
|
| 129 |
+ }() |
|
| 130 |
+ select {
|
|
| 131 |
+ case <-done: |
|
| 132 |
+ case <-ctx.Done(): |
|
| 133 |
+ // close all pipes |
|
| 134 |
+ if cStdin != nil {
|
|
| 135 |
+ cStdin.Close() |
|
| 136 |
+ } |
|
| 137 |
+ if cStdout != nil {
|
|
| 138 |
+ cStdout.Close() |
|
| 139 |
+ } |
|
| 140 |
+ if cStderr != nil {
|
|
| 141 |
+ cStderr.Close() |
|
| 142 |
+ } |
|
| 143 |
+ <-done |
|
| 144 |
+ } |
|
| 145 |
+ close(errors) |
|
| 146 |
+ for err := range errors {
|
|
| 147 |
+ if err != nil {
|
|
| 148 |
+ return err |
|
| 149 |
+ } |
|
| 150 |
+ } |
|
| 151 |
+ return nil |
|
| 152 |
+ }) |
|
| 153 |
+} |
|
| 154 |
+ |
|
| 155 |
+// Code c/c from io.Copy() modified to handle escape sequence |
|
| 156 |
+func copyEscapable(dst io.Writer, src io.ReadCloser, keys []byte) (written int64, err error) {
|
|
| 157 |
+ if len(keys) == 0 {
|
|
| 158 |
+ // Default keys : ctrl-p ctrl-q |
|
| 159 |
+ keys = []byte{16, 17}
|
|
| 160 |
+ } |
|
| 161 |
+ buf := make([]byte, 32*1024) |
|
| 162 |
+ for {
|
|
| 163 |
+ nr, er := src.Read(buf) |
|
| 164 |
+ if nr > 0 {
|
|
| 165 |
+ // ---- Docker addition |
|
| 166 |
+ preservBuf := []byte{}
|
|
| 167 |
+ for i, key := range keys {
|
|
| 168 |
+ preservBuf = append(preservBuf, buf[0:nr]...) |
|
| 169 |
+ if nr != 1 || buf[0] != key {
|
|
| 170 |
+ break |
|
| 171 |
+ } |
|
| 172 |
+ if i == len(keys)-1 {
|
|
| 173 |
+ src.Close() |
|
| 174 |
+ return 0, DetachError{}
|
|
| 175 |
+ } |
|
| 176 |
+ nr, er = src.Read(buf) |
|
| 177 |
+ } |
|
| 178 |
+ var nw int |
|
| 179 |
+ var ew error |
|
| 180 |
+ if len(preservBuf) > 0 {
|
|
| 181 |
+ nw, ew = dst.Write(preservBuf) |
|
| 182 |
+ nr = len(preservBuf) |
|
| 183 |
+ } else {
|
|
| 184 |
+ // ---- End of docker |
|
| 185 |
+ nw, ew = dst.Write(buf[0:nr]) |
|
| 186 |
+ } |
|
| 187 |
+ if nw > 0 {
|
|
| 188 |
+ written += int64(nw) |
|
| 189 |
+ } |
|
| 190 |
+ if ew != nil {
|
|
| 191 |
+ err = ew |
|
| 192 |
+ break |
|
| 193 |
+ } |
|
| 194 |
+ if nr != nw {
|
|
| 195 |
+ err = io.ErrShortWrite |
|
| 196 |
+ break |
|
| 197 |
+ } |
|
| 198 |
+ } |
|
| 199 |
+ if er == io.EOF {
|
|
| 200 |
+ break |
|
| 201 |
+ } |
|
| 202 |
+ if er != nil {
|
|
| 203 |
+ err = er |
|
| 204 |
+ break |
|
| 205 |
+ } |
|
| 206 |
+ } |
|
| 207 |
+ return written, err |
|
| 208 |
+} |
| ... | ... |
@@ -62,6 +62,7 @@ func (c *Config) StdinPipe() io.WriteCloser {
|
| 62 | 62 |
|
| 63 | 63 |
// StdoutPipe creates a new io.ReadCloser with an empty bytes pipe. |
| 64 | 64 |
// It adds this new out pipe to the Stdout broadcaster. |
| 65 |
+// This will block stdout if unconsumed. |
|
| 65 | 66 |
func (c *Config) StdoutPipe() io.ReadCloser {
|
| 66 | 67 |
bytesPipe := ioutils.NewBytesPipe() |
| 67 | 68 |
c.stdout.Add(bytesPipe) |
| ... | ... |
@@ -70,6 +71,7 @@ func (c *Config) StdoutPipe() io.ReadCloser {
|
| 70 | 70 |
|
| 71 | 71 |
// StderrPipe creates a new io.ReadCloser with an empty bytes pipe. |
| 72 | 72 |
// It adds this new err pipe to the Stderr broadcaster. |
| 73 |
+// This will block stderr if unconsumed. |
|
| 73 | 74 |
func (c *Config) StderrPipe() io.ReadCloser {
|
| 74 | 75 |
bytesPipe := ioutils.NewBytesPipe() |
| 75 | 76 |
c.stderr.Add(bytesPipe) |
| ... | ... |
@@ -9,11 +9,20 @@ import ( |
| 9 | 9 |
"github.com/docker/docker/api/errors" |
| 10 | 10 |
"github.com/docker/docker/api/types/backend" |
| 11 | 11 |
"github.com/docker/docker/container" |
| 12 |
+ "github.com/docker/docker/container/stream" |
|
| 12 | 13 |
"github.com/docker/docker/daemon/logger" |
| 13 | 14 |
"github.com/docker/docker/pkg/stdcopy" |
| 14 | 15 |
"github.com/docker/docker/pkg/term" |
| 15 | 16 |
) |
| 16 | 17 |
|
| 18 |
+type containerAttachConfig struct {
|
|
| 19 |
+ detachKeys []byte |
|
| 20 |
+ stdin io.ReadCloser |
|
| 21 |
+ stdout, stderr io.Writer |
|
| 22 |
+ showHistory bool |
|
| 23 |
+ stream bool |
|
| 24 |
+} |
|
| 25 |
+ |
|
| 17 | 26 |
// ContainerAttach attaches to logs according to the config passed in. See ContainerAttachConfig. |
| 18 | 27 |
func (daemon *Daemon) ContainerAttach(prefixOrName string, c *backend.ContainerAttachConfig) error {
|
| 19 | 28 |
keys := []byte{}
|
| ... | ... |
@@ -45,20 +54,23 @@ func (daemon *Daemon) ContainerAttach(prefixOrName string, c *backend.ContainerA |
| 45 | 45 |
outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) |
| 46 | 46 |
} |
| 47 | 47 |
|
| 48 |
- var stdin io.ReadCloser |
|
| 49 |
- var stdout, stderr io.Writer |
|
| 48 |
+ var cfg containerAttachConfig |
|
| 50 | 49 |
|
| 51 | 50 |
if c.UseStdin {
|
| 52 |
- stdin = inStream |
|
| 51 |
+ cfg.stdin = inStream |
|
| 53 | 52 |
} |
| 54 | 53 |
if c.UseStdout {
|
| 55 |
- stdout = outStream |
|
| 54 |
+ cfg.stdout = outStream |
|
| 56 | 55 |
} |
| 57 | 56 |
if c.UseStderr {
|
| 58 |
- stderr = errStream |
|
| 57 |
+ cfg.stderr = errStream |
|
| 59 | 58 |
} |
| 60 | 59 |
|
| 61 |
- if err := daemon.containerAttach(container, stdin, stdout, stderr, c.Logs, c.Stream, keys); err != nil {
|
|
| 60 |
+ cfg.showHistory = c.Logs |
|
| 61 |
+ cfg.stream = c.Stream |
|
| 62 |
+ cfg.detachKeys = keys |
|
| 63 |
+ |
|
| 64 |
+ if err := daemon.containerAttach(container, &cfg); err != nil {
|
|
| 62 | 65 |
fmt.Fprintf(outStream, "Error attaching: %s\n", err) |
| 63 | 66 |
} |
| 64 | 67 |
return nil |
| ... | ... |
@@ -70,11 +82,20 @@ func (daemon *Daemon) ContainerAttachRaw(prefixOrName string, stdin io.ReadClose |
| 70 | 70 |
if err != nil {
|
| 71 | 71 |
return err |
| 72 | 72 |
} |
| 73 |
- return daemon.containerAttach(container, stdin, stdout, stderr, false, stream, nil) |
|
| 73 |
+ cfg := &containerAttachConfig{
|
|
| 74 |
+ stdin: stdin, |
|
| 75 |
+ stdout: stdout, |
|
| 76 |
+ stderr: stderr, |
|
| 77 |
+ stream: stream, |
|
| 78 |
+ } |
|
| 79 |
+ return daemon.containerAttach(container, cfg) |
|
| 74 | 80 |
} |
| 75 | 81 |
|
| 76 |
-func (daemon *Daemon) containerAttach(c *container.Container, stdin io.ReadCloser, stdout, stderr io.Writer, logs, stream bool, keys []byte) error {
|
|
| 77 |
- if logs {
|
|
| 82 |
+func (daemon *Daemon) containerAttach(c *container.Container, cfg *containerAttachConfig) error {
|
|
| 83 |
+ stdin := cfg.stdin |
|
| 84 |
+ stdout := cfg.stdout |
|
| 85 |
+ stderr := cfg.stderr |
|
| 86 |
+ if cfg.showHistory {
|
|
| 78 | 87 |
logDriver, err := daemon.getLogger(c) |
| 79 | 88 |
if err != nil {
|
| 80 | 89 |
return err |
| ... | ... |
@@ -107,41 +128,47 @@ func (daemon *Daemon) containerAttach(c *container.Container, stdin io.ReadClose |
| 107 | 107 |
|
| 108 | 108 |
daemon.LogContainerEvent(c, "attach") |
| 109 | 109 |
|
| 110 |
- //stream |
|
| 111 |
- if stream {
|
|
| 112 |
- var stdinPipe io.ReadCloser |
|
| 113 |
- if stdin != nil {
|
|
| 114 |
- r, w := io.Pipe() |
|
| 115 |
- go func() {
|
|
| 116 |
- defer w.Close() |
|
| 117 |
- defer logrus.Debug("Closing buffered stdin pipe")
|
|
| 118 |
- io.Copy(w, stdin) |
|
| 119 |
- }() |
|
| 120 |
- stdinPipe = r |
|
| 121 |
- } |
|
| 122 |
- |
|
| 123 |
- waitChan := make(chan struct{})
|
|
| 124 |
- if c.Config.StdinOnce && !c.Config.Tty {
|
|
| 125 |
- go func() {
|
|
| 126 |
- c.WaitStop(-1 * time.Second) |
|
| 127 |
- close(waitChan) |
|
| 128 |
- }() |
|
| 129 |
- } |
|
| 110 |
+ if !cfg.stream {
|
|
| 111 |
+ return nil |
|
| 112 |
+ } |
|
| 130 | 113 |
|
| 131 |
- err := <-c.Attach(stdinPipe, stdout, stderr, keys) |
|
| 132 |
- if err != nil {
|
|
| 133 |
- if _, ok := err.(container.DetachError); ok {
|
|
| 134 |
- daemon.LogContainerEvent(c, "detach") |
|
| 135 |
- } else {
|
|
| 136 |
- logrus.Errorf("attach failed with error: %v", err)
|
|
| 137 |
- } |
|
| 138 |
- } |
|
| 114 |
+ var stdinPipe io.ReadCloser |
|
| 115 |
+ if stdin != nil {
|
|
| 116 |
+ r, w := io.Pipe() |
|
| 117 |
+ go func() {
|
|
| 118 |
+ defer w.Close() |
|
| 119 |
+ defer logrus.Debug("Closing buffered stdin pipe")
|
|
| 120 |
+ io.Copy(w, stdin) |
|
| 121 |
+ }() |
|
| 122 |
+ stdinPipe = r |
|
| 123 |
+ } |
|
| 139 | 124 |
|
| 140 |
- // If we are in stdinonce mode, wait for the process to end |
|
| 141 |
- // otherwise, simply return |
|
| 142 |
- if c.Config.StdinOnce && !c.Config.Tty {
|
|
| 125 |
+ waitChan := make(chan struct{})
|
|
| 126 |
+ if c.Config.StdinOnce && !c.Config.Tty {
|
|
| 127 |
+ defer func() {
|
|
| 143 | 128 |
<-waitChan |
| 129 |
+ }() |
|
| 130 |
+ go func() {
|
|
| 131 |
+ c.WaitStop(-1 * time.Second) |
|
| 132 |
+ close(waitChan) |
|
| 133 |
+ }() |
|
| 134 |
+ } |
|
| 135 |
+ |
|
| 136 |
+ aCfg := &stream.AttachConfig{
|
|
| 137 |
+ Stdin: stdinPipe, |
|
| 138 |
+ Stdout: stdout, |
|
| 139 |
+ Stderr: stderr, |
|
| 140 |
+ DetachKeys: cfg.detachKeys, |
|
| 141 |
+ } |
|
| 142 |
+ |
|
| 143 |
+ err := <-c.Attach(aCfg) |
|
| 144 |
+ if err != nil {
|
|
| 145 |
+ if _, ok := err.(stream.DetachError); ok {
|
|
| 146 |
+ daemon.LogContainerEvent(c, "detach") |
|
| 147 |
+ } else {
|
|
| 148 |
+ logrus.Errorf("attach failed with error: %v", err)
|
|
| 144 | 149 |
} |
| 145 | 150 |
} |
| 151 |
+ |
|
| 146 | 152 |
return nil |
| 147 | 153 |
} |
| ... | ... |
@@ -13,6 +13,7 @@ import ( |
| 13 | 13 |
"github.com/docker/docker/api/types" |
| 14 | 14 |
"github.com/docker/docker/api/types/strslice" |
| 15 | 15 |
"github.com/docker/docker/container" |
| 16 |
+ "github.com/docker/docker/container/stream" |
|
| 16 | 17 |
"github.com/docker/docker/daemon/exec" |
| 17 | 18 |
"github.com/docker/docker/libcontainerd" |
| 18 | 19 |
"github.com/docker/docker/pkg/pools" |
| ... | ... |
@@ -209,7 +210,15 @@ func (d *Daemon) ContainerExecStart(ctx context.Context, name string, stdin io.R |
| 209 | 209 |
return err |
| 210 | 210 |
} |
| 211 | 211 |
|
| 212 |
- attachErr := container.AttachStreams(ctx, ec.StreamConfig, ec.OpenStdin, true, ec.Tty, cStdin, cStdout, cStderr, ec.DetachKeys) |
|
| 212 |
+ attachConfig := &stream.AttachConfig{
|
|
| 213 |
+ TTY: ec.Tty, |
|
| 214 |
+ Stdin: cStdin, |
|
| 215 |
+ Stdout: cStdout, |
|
| 216 |
+ Stderr: cStderr, |
|
| 217 |
+ DetachKeys: ec.DetachKeys, |
|
| 218 |
+ CloseStdin: true, |
|
| 219 |
+ } |
|
| 220 |
+ attachErr := ec.StreamConfig.Attach(ctx, attachConfig) |
|
| 213 | 221 |
|
| 214 | 222 |
systemPid, err := d.containerd.AddProcess(ctx, c.ID, name, p, ec.InitializeStdio) |
| 215 | 223 |
if err != nil {
|
| ... | ... |
@@ -233,7 +242,7 @@ func (d *Daemon) ContainerExecStart(ctx context.Context, name string, stdin io.R |
| 233 | 233 |
return fmt.Errorf("context cancelled")
|
| 234 | 234 |
case err := <-attachErr: |
| 235 | 235 |
if err != nil {
|
| 236 |
- if _, ok := err.(container.DetachError); !ok {
|
|
| 236 |
+ if _, ok := err.(stream.DetachError); !ok {
|
|
| 237 | 237 |
return fmt.Errorf("exec attach failed with error: %v", err)
|
| 238 | 238 |
} |
| 239 | 239 |
d.LogContainerEvent(c, "exec_detach") |