github.com/gravitational/moby@v1.13.1/daemon/attach.go (about) 1 package daemon 2 3 import ( 4 "fmt" 5 "io" 6 "time" 7 8 "github.com/Sirupsen/logrus" 9 "github.com/docker/docker/api/errors" 10 "github.com/docker/docker/api/types/backend" 11 "github.com/docker/docker/container" 12 "github.com/docker/docker/daemon/logger" 13 "github.com/docker/docker/pkg/stdcopy" 14 "github.com/docker/docker/pkg/term" 15 ) 16 17 // ContainerAttach attaches to logs according to the config passed in. See ContainerAttachConfig. 18 func (daemon *Daemon) ContainerAttach(prefixOrName string, c *backend.ContainerAttachConfig) error { 19 keys := []byte{} 20 var err error 21 if c.DetachKeys != "" { 22 keys, err = term.ToBytes(c.DetachKeys) 23 if err != nil { 24 return fmt.Errorf("Invalid escape keys (%s) provided", c.DetachKeys) 25 } 26 } 27 28 container, err := daemon.GetContainer(prefixOrName) 29 if err != nil { 30 return err 31 } 32 if container.IsPaused() { 33 err := fmt.Errorf("Container %s is paused. Unpause the container before attach", prefixOrName) 34 return errors.NewRequestConflictError(err) 35 } 36 37 inStream, outStream, errStream, err := c.GetStreams() 38 if err != nil { 39 return err 40 } 41 defer inStream.Close() 42 43 if !container.Config.Tty && c.MuxStreams { 44 errStream = stdcopy.NewStdWriter(errStream, stdcopy.Stderr) 45 outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) 46 } 47 48 var stdin io.ReadCloser 49 var stdout, stderr io.Writer 50 51 if c.UseStdin { 52 stdin = inStream 53 } 54 if c.UseStdout { 55 stdout = outStream 56 } 57 if c.UseStderr { 58 stderr = errStream 59 } 60 61 if err := daemon.containerAttach(container, stdin, stdout, stderr, c.Logs, c.Stream, keys); err != nil { 62 fmt.Fprintf(outStream, "Error attaching: %s\n", err) 63 } 64 return nil 65 } 66 67 // ContainerAttachRaw attaches the provided streams to the container's stdio 68 func (daemon *Daemon) ContainerAttachRaw(prefixOrName string, stdin io.ReadCloser, stdout, stderr io.Writer, stream bool) error { 69 container, err := daemon.GetContainer(prefixOrName) 70 if err != nil { 71 return err 72 } 73 return daemon.containerAttach(container, stdin, stdout, stderr, false, stream, nil) 74 } 75 76 func (daemon *Daemon) containerAttach(c *container.Container, stdin io.ReadCloser, stdout, stderr io.Writer, logs, stream bool, keys []byte) error { 77 if logs { 78 logDriver, err := daemon.getLogger(c) 79 if err != nil { 80 return err 81 } 82 cLog, ok := logDriver.(logger.LogReader) 83 if !ok { 84 return logger.ErrReadLogsNotSupported 85 } 86 logs := cLog.ReadLogs(logger.ReadConfig{Tail: -1}) 87 88 LogLoop: 89 for { 90 select { 91 case msg, ok := <-logs.Msg: 92 if !ok { 93 break LogLoop 94 } 95 if msg.Source == "stdout" && stdout != nil { 96 stdout.Write(msg.Line) 97 } 98 if msg.Source == "stderr" && stderr != nil { 99 stderr.Write(msg.Line) 100 } 101 case err := <-logs.Err: 102 logrus.Errorf("Error streaming logs: %v", err) 103 break LogLoop 104 } 105 } 106 } 107 108 daemon.LogContainerEvent(c, "attach") 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 } 130 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 } 139 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 { 143 <-waitChan 144 } 145 } 146 return nil 147 }