daemon/attach.go
41cfaa73
 package daemon
 
 import (
cfdf84d5
 	"context"
af94f941
 	"fmt"
41cfaa73
 	"io"
 
06d8f504
 	"github.com/docker/docker/api/types/backend"
6bb0d181
 	"github.com/docker/docker/container"
2ddec975
 	"github.com/docker/docker/container/stream"
ca5ede2d
 	"github.com/docker/docker/daemon/logger"
d453fe35
 	"github.com/docker/docker/errdefs"
e2acca67
 	"github.com/docker/docker/pkg/stdcopy"
91e5bb95
 	"github.com/docker/docker/pkg/term"
ebcb7d6b
 	"github.com/pkg/errors"
1009e6a4
 	"github.com/sirupsen/logrus"
41cfaa73
 )
 
a77b7dd2
 // ContainerAttach attaches to logs according to the config passed in. See ContainerAttachConfig.
 func (daemon *Daemon) ContainerAttach(prefixOrName string, c *backend.ContainerAttachConfig) error {
91e5bb95
 	keys := []byte{}
 	var err error
 	if c.DetachKeys != "" {
 		keys, err = term.ToBytes(c.DetachKeys)
 		if err != nil {
87a12421
 			return errdefs.InvalidParameter(errors.Errorf("Invalid detach keys (%s) provided", c.DetachKeys))
91e5bb95
 		}
 	}
 
d7d512bb
 	container, err := daemon.GetContainer(prefixOrName)
8aef1a33
 	if err != nil {
a793564b
 		return err
af94f941
 	}
 	if container.IsPaused() {
9b47b7b1
 		err := fmt.Errorf("container %s is paused, unpause the container before attach", prefixOrName)
87a12421
 		return errdefs.Conflict(err)
5010e095
 	}
 	if container.IsRestarting() {
9b47b7b1
 		err := fmt.Errorf("container %s is restarting, wait until the container is running", prefixOrName)
87a12421
 		return errdefs.Conflict(err)
af94f941
 	}
 
84d6240c
 	cfg := stream.AttachConfig{
dc0ee988
 		UseStdin:   c.UseStdin,
84d6240c
 		UseStdout:  c.UseStdout,
 		UseStderr:  c.UseStderr,
 		TTY:        container.Config.Tty,
 		CloseStdin: container.Config.StdinOnce,
 		DetachKeys: keys,
 	}
 	container.StreamConfig.AttachStreams(&cfg)
 
a77b7dd2
 	inStream, outStream, errStream, err := c.GetStreams()
af94f941
 	if err != nil {
8aef1a33
 		return err
 	}
a77b7dd2
 	defer inStream.Close()
8aef1a33
 
a77b7dd2
 	if !container.Config.Tty && c.MuxStreams {
 		errStream = stdcopy.NewStdWriter(errStream, stdcopy.Stderr)
af94f941
 		outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
21e44d7a
 	}
 
84d6240c
 	if cfg.UseStdin {
 		cfg.Stdin = inStream
e2acca67
 	}
84d6240c
 	if cfg.UseStdout {
 		cfg.Stdout = outStream
e2acca67
 	}
84d6240c
 	if cfg.UseStderr {
 		cfg.Stderr = errStream
21e44d7a
 	}
 
84d6240c
 	if err := daemon.containerAttach(container, &cfg, c.Logs, c.Stream); err != nil {
af94f941
 		fmt.Fprintf(outStream, "Error attaching: %s\n", err)
 	}
 	return nil
e2acca67
 }
21e44d7a
 
a77b7dd2
 // ContainerAttachRaw attaches the provided streams to the container's stdio
32ca1214
 func (daemon *Daemon) ContainerAttachRaw(prefixOrName string, stdin io.ReadCloser, stdout, stderr io.Writer, doStream bool, attached chan struct{}) error {
d7d512bb
 	container, err := daemon.GetContainer(prefixOrName)
8aef1a33
 	if err != nil {
 		return err
 	}
84d6240c
 	cfg := stream.AttachConfig{
dc0ee988
 		UseStdin:   stdin != nil,
84d6240c
 		UseStdout:  stdout != nil,
 		UseStderr:  stderr != nil,
 		TTY:        container.Config.Tty,
 		CloseStdin: container.Config.StdinOnce,
 	}
 	container.StreamConfig.AttachStreams(&cfg)
32ca1214
 	close(attached)
84d6240c
 	if cfg.UseStdin {
 		cfg.Stdin = stdin
2ddec975
 	}
84d6240c
 	if cfg.UseStdout {
 		cfg.Stdout = stdout
 	}
 	if cfg.UseStderr {
 		cfg.Stderr = stderr
 	}
 
 	return daemon.containerAttach(container, &cfg, false, doStream)
9c332b16
 }
 
84d6240c
 func (daemon *Daemon) containerAttach(c *container.Container, cfg *stream.AttachConfig, logs, doStream bool) error {
 	if logs {
68e71aa3
 		logDriver, logCreated, err := daemon.getLogger(c)
ca5ede2d
 		if err != nil {
 			return err
 		}
68e71aa3
 		if logCreated {
 			defer func() {
 				if err = logDriver.Close(); err != nil {
 					logrus.Errorf("Error closing logger: %v", err)
 				}
 			}()
 		}
ca5ede2d
 		cLog, ok := logDriver.(logger.LogReader)
 		if !ok {
ebcb7d6b
 			return logger.ErrReadLogsNotSupported{}
ca5ede2d
 		}
 		logs := cLog.ReadLogs(logger.ReadConfig{Tail: -1})
68e71aa3
 		defer logs.Close()
ca5ede2d
 
 	LogLoop:
 		for {
 			select {
 			case msg, ok := <-logs.Msg:
 				if !ok {
 					break LogLoop
 				}
84d6240c
 				if msg.Source == "stdout" && cfg.Stdout != nil {
 					cfg.Stdout.Write(msg.Line)
ca5ede2d
 				}
84d6240c
 				if msg.Source == "stderr" && cfg.Stderr != nil {
 					cfg.Stderr.Write(msg.Line)
ca5ede2d
 				}
 			case err := <-logs.Err:
 				logrus.Errorf("Error streaming logs: %v", err)
 				break LogLoop
 			}
 		}
 	}
 
83ad006d
 	daemon.LogContainerEvent(c, "attach")
ca5ede2d
 
84d6240c
 	if !doStream {
2ddec975
 		return nil
 	}
c498d470
 
84d6240c
 	if cfg.Stdin != nil {
2ddec975
 		r, w := io.Pipe()
84d6240c
 		go func(stdin io.ReadCloser) {
2ddec975
 			defer w.Close()
 			defer logrus.Debug("Closing buffered stdin pipe")
 			io.Copy(w, stdin)
84d6240c
 		}(cfg.Stdin)
 		cfg.Stdin = r
2ddec975
 	}
83ad006d
 
dc0ee988
 	if !c.Config.OpenStdin {
 		cfg.Stdin = nil
 	}
 
2ddec975
 	if c.Config.StdinOnce && !c.Config.Tty {
cfdf84d5
 		// Wait for the container to stop before returning.
49211715
 		waitChan := c.Wait(context.Background(), container.WaitConditionNotRunning)
2ddec975
 		defer func() {
94cefa21
 			<-waitChan // Ignore returned exit code.
2ddec975
 		}()
 	}
 
84d6240c
 	ctx := c.InitAttachContext()
 	err := <-c.StreamConfig.CopyStreams(ctx, cfg)
2ddec975
 	if err != nil {
49211715
 		if _, ok := err.(term.EscapeError); ok {
2ddec975
 			daemon.LogContainerEvent(c, "detach")
 		} else {
 			logrus.Errorf("attach failed with error: %v", err)
ca5ede2d
 		}
 	}
2ddec975
 
ca5ede2d
 	return nil
c30a55f1
 }