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/container/stream"
13 "github.com/docker/docker/daemon/logger"
14 "github.com/docker/docker/pkg/stdcopy"
15 "github.com/docker/docker/pkg/term"
18 // ContainerAttach attaches to logs according to the config passed in. See ContainerAttachConfig.
19 func (daemon *Daemon) ContainerAttach(prefixOrName string, c *backend.ContainerAttachConfig) error {
22 if c.DetachKeys != "" {
23 keys, err = term.ToBytes(c.DetachKeys)
25 return fmt.Errorf("Invalid detach keys (%s) provided", c.DetachKeys)
29 container, err := daemon.GetContainer(prefixOrName)
33 if container.IsPaused() {
34 err := fmt.Errorf("Container %s is paused, unpause the container before attach.", prefixOrName)
35 return errors.NewRequestConflictError(err)
37 if container.IsRestarting() {
38 err := fmt.Errorf("Container %s is restarting, wait until the container is running.", prefixOrName)
39 return errors.NewRequestConflictError(err)
42 cfg := stream.AttachConfig{
44 UseStdout: c.UseStdout,
45 UseStderr: c.UseStderr,
46 TTY: container.Config.Tty,
47 CloseStdin: container.Config.StdinOnce,
50 container.StreamConfig.AttachStreams(&cfg)
52 inStream, outStream, errStream, err := c.GetStreams()
56 defer inStream.Close()
58 if !container.Config.Tty && c.MuxStreams {
59 errStream = stdcopy.NewStdWriter(errStream, stdcopy.Stderr)
60 outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
67 cfg.Stdout = outStream
70 cfg.Stderr = errStream
73 if err := daemon.containerAttach(container, &cfg, c.Logs, c.Stream); err != nil {
74 fmt.Fprintf(outStream, "Error attaching: %s\n", err)
79 // ContainerAttachRaw attaches the provided streams to the container's stdio
80 func (daemon *Daemon) ContainerAttachRaw(prefixOrName string, stdin io.ReadCloser, stdout, stderr io.Writer, doStream bool, attached chan struct{}) error {
81 container, err := daemon.GetContainer(prefixOrName)
85 cfg := stream.AttachConfig{
86 UseStdin: stdin != nil,
87 UseStdout: stdout != nil,
88 UseStderr: stderr != nil,
89 TTY: container.Config.Tty,
90 CloseStdin: container.Config.StdinOnce,
92 container.StreamConfig.AttachStreams(&cfg)
104 return daemon.containerAttach(container, &cfg, false, doStream)
107 func (daemon *Daemon) containerAttach(c *container.Container, cfg *stream.AttachConfig, logs, doStream bool) error {
109 logDriver, logCreated, err := daemon.getLogger(c)
115 if err = logDriver.Close(); err != nil {
116 logrus.Errorf("Error closing logger: %v", err)
120 cLog, ok := logDriver.(logger.LogReader)
122 return logger.ErrReadLogsNotSupported
124 logs := cLog.ReadLogs(logger.ReadConfig{Tail: -1})
130 case msg, ok := <-logs.Msg:
134 if msg.Source == "stdout" && cfg.Stdout != nil {
135 cfg.Stdout.Write(msg.Line)
137 if msg.Source == "stderr" && cfg.Stderr != nil {
138 cfg.Stderr.Write(msg.Line)
140 case err := <-logs.Err:
141 logrus.Errorf("Error streaming logs: %v", err)
147 daemon.LogContainerEvent(c, "attach")
153 if cfg.Stdin != nil {
155 go func(stdin io.ReadCloser) {
157 defer logrus.Debug("Closing buffered stdin pipe")
163 if !c.Config.OpenStdin {
167 if c.Config.StdinOnce && !c.Config.Tty {
168 // Wait for the container to stop before returning.
169 waitChan := c.Wait(context.Background(), container.WaitConditionNotRunning)
171 _ = <-waitChan // Ignore returned exit code.
175 ctx := c.InitAttachContext()
176 err := <-c.StreamConfig.CopyStreams(ctx, cfg)
178 if _, ok := err.(term.EscapeError); ok {
179 daemon.LogContainerEvent(c, "detach")
181 logrus.Errorf("attach failed with error: %v", err)