moby/daemon/attach.go
Zhang Wei 83ad006d47 Add detach event
If we attach to a running container and stream is closed afterwards, we
can never be sure if the container is stopped or detached. Adding a new
type of `detach` event can explicitly notify client that container is
detached, so client will know that there's no need to wait for its exit
code and it can move forward to next step now.

Signed-off-by: Zhang Wei <zhangwei555@huawei.com>
2016-06-03 11:59:11 +08:00

139 lines
3.4 KiB
Go

package daemon
import (
"fmt"
"io"
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/types/backend"
"github.com/docker/docker/container"
"github.com/docker/docker/daemon/logger"
"github.com/docker/docker/errors"
"github.com/docker/docker/pkg/stdcopy"
"github.com/docker/docker/pkg/term"
)
// ContainerAttach attaches to logs according to the config passed in. See ContainerAttachConfig.
func (daemon *Daemon) ContainerAttach(prefixOrName string, c *backend.ContainerAttachConfig) error {
keys := []byte{}
var err error
if c.DetachKeys != "" {
keys, err = term.ToBytes(c.DetachKeys)
if err != nil {
return fmt.Errorf("Invalid escape keys (%s) provided", c.DetachKeys)
}
}
container, err := daemon.GetContainer(prefixOrName)
if err != nil {
return err
}
if container.IsPaused() {
err := fmt.Errorf("Container %s is paused. Unpause the container before attach", prefixOrName)
return errors.NewRequestConflictError(err)
}
inStream, outStream, errStream, err := c.GetStreams()
if err != nil {
return err
}
defer inStream.Close()
if !container.Config.Tty && c.MuxStreams {
errStream = stdcopy.NewStdWriter(errStream, stdcopy.Stderr)
outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
}
var stdin io.ReadCloser
var stdout, stderr io.Writer
if c.UseStdin {
stdin = inStream
}
if c.UseStdout {
stdout = outStream
}
if c.UseStderr {
stderr = errStream
}
if err := daemon.containerAttach(container, stdin, stdout, stderr, c.Logs, c.Stream, keys); err != nil {
fmt.Fprintf(outStream, "Error attaching: %s\n", err)
}
return nil
}
// ContainerAttachRaw attaches the provided streams to the container's stdio
func (daemon *Daemon) ContainerAttachRaw(prefixOrName string, stdin io.ReadCloser, stdout, stderr io.Writer, stream bool) error {
container, err := daemon.GetContainer(prefixOrName)
if err != nil {
return err
}
return daemon.containerAttach(container, stdin, stdout, stderr, false, stream, nil)
}
func (daemon *Daemon) containerAttach(c *container.Container, stdin io.ReadCloser, stdout, stderr io.Writer, logs, stream bool, keys []byte) error {
if logs {
logDriver, err := daemon.getLogger(c)
if err != nil {
return err
}
cLog, ok := logDriver.(logger.LogReader)
if !ok {
return logger.ErrReadLogsNotSupported
}
logs := cLog.ReadLogs(logger.ReadConfig{Tail: -1})
LogLoop:
for {
select {
case msg, ok := <-logs.Msg:
if !ok {
break LogLoop
}
if msg.Source == "stdout" && stdout != nil {
stdout.Write(msg.Line)
}
if msg.Source == "stderr" && stderr != nil {
stderr.Write(msg.Line)
}
case err := <-logs.Err:
logrus.Errorf("Error streaming logs: %v", err)
break LogLoop
}
}
}
daemon.LogContainerEvent(c, "attach")
//stream
if stream {
var stdinPipe io.ReadCloser
if stdin != nil {
r, w := io.Pipe()
go func() {
defer w.Close()
defer logrus.Debugf("Closing buffered stdin pipe")
io.Copy(w, stdin)
}()
stdinPipe = r
}
err := <-c.Attach(stdinPipe, stdout, stderr, keys)
if err != nil {
e, ok := err.(container.AttachError)
if ok && e.IsDetached() {
daemon.LogContainerEvent(c, "detach")
} else {
logrus.Errorf("attach failed with error: %v", err)
}
}
// If we are in stdinonce mode, wait for the process to end
// otherwise, simply return
if c.Config.StdinOnce && !c.Config.Tty {
c.WaitStop(-1 * time.Second)
}
}
return nil
}