github.com/toplink-cn/moby@v0.0.0-20240305205811-460b4aebdf81/daemon/attach.go (about)

     1  package daemon // import "github.com/docker/docker/daemon"
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  
     8  	"github.com/containerd/log"
     9  	"github.com/docker/docker/api/types/backend"
    10  	"github.com/docker/docker/api/types/events"
    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/errdefs"
    15  	"github.com/docker/docker/pkg/stdcopy"
    16  	"github.com/moby/term"
    17  	"github.com/pkg/errors"
    18  )
    19  
    20  // ContainerAttach attaches to logs according to the config passed in. See ContainerAttachConfig.
    21  func (daemon *Daemon) ContainerAttach(prefixOrName string, c *backend.ContainerAttachConfig) error {
    22  	keys := []byte{}
    23  	var err error
    24  	if c.DetachKeys != "" {
    25  		keys, err = term.ToBytes(c.DetachKeys)
    26  		if err != nil {
    27  			return errdefs.InvalidParameter(errors.Errorf("Invalid detach keys (%s) provided", c.DetachKeys))
    28  		}
    29  	}
    30  
    31  	ctr, err := daemon.GetContainer(prefixOrName)
    32  	if err != nil {
    33  		return err
    34  	}
    35  	if ctr.IsPaused() {
    36  		err := fmt.Errorf("container %s is paused, unpause the container before attach", prefixOrName)
    37  		return errdefs.Conflict(err)
    38  	}
    39  	if ctr.IsRestarting() {
    40  		err := fmt.Errorf("container %s is restarting, wait until the container is running", prefixOrName)
    41  		return errdefs.Conflict(err)
    42  	}
    43  
    44  	cfg := stream.AttachConfig{
    45  		UseStdin:   c.UseStdin,
    46  		UseStdout:  c.UseStdout,
    47  		UseStderr:  c.UseStderr,
    48  		TTY:        ctr.Config.Tty,
    49  		CloseStdin: ctr.Config.StdinOnce,
    50  		DetachKeys: keys,
    51  	}
    52  	ctr.StreamConfig.AttachStreams(&cfg)
    53  
    54  	multiplexed := !ctr.Config.Tty && c.MuxStreams
    55  	inStream, outStream, errStream, err := c.GetStreams(multiplexed)
    56  	if err != nil {
    57  		return err
    58  	}
    59  	defer inStream.Close()
    60  
    61  	if multiplexed {
    62  		errStream = stdcopy.NewStdWriter(errStream, stdcopy.Stderr)
    63  		outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
    64  	}
    65  
    66  	if cfg.UseStdin {
    67  		cfg.Stdin = inStream
    68  	}
    69  	if cfg.UseStdout {
    70  		cfg.Stdout = outStream
    71  	}
    72  	if cfg.UseStderr {
    73  		cfg.Stderr = errStream
    74  	}
    75  
    76  	if err := daemon.containerAttach(ctr, &cfg, c.Logs, c.Stream); err != nil {
    77  		fmt.Fprintf(outStream, "Error attaching: %s\n", err)
    78  	}
    79  	return nil
    80  }
    81  
    82  // ContainerAttachRaw attaches the provided streams to the container's stdio
    83  func (daemon *Daemon) ContainerAttachRaw(prefixOrName string, stdin io.ReadCloser, stdout, stderr io.Writer, doStream bool, attached chan struct{}) error {
    84  	ctr, err := daemon.GetContainer(prefixOrName)
    85  	if err != nil {
    86  		return err
    87  	}
    88  	cfg := stream.AttachConfig{
    89  		UseStdin:   stdin != nil,
    90  		UseStdout:  stdout != nil,
    91  		UseStderr:  stderr != nil,
    92  		TTY:        ctr.Config.Tty,
    93  		CloseStdin: ctr.Config.StdinOnce,
    94  	}
    95  	ctr.StreamConfig.AttachStreams(&cfg)
    96  	close(attached)
    97  	if cfg.UseStdin {
    98  		cfg.Stdin = stdin
    99  	}
   100  	if cfg.UseStdout {
   101  		cfg.Stdout = stdout
   102  	}
   103  	if cfg.UseStderr {
   104  		cfg.Stderr = stderr
   105  	}
   106  
   107  	return daemon.containerAttach(ctr, &cfg, false, doStream)
   108  }
   109  
   110  func (daemon *Daemon) containerAttach(c *container.Container, cfg *stream.AttachConfig, logs, doStream bool) error {
   111  	if logs {
   112  		logDriver, logCreated, err := daemon.getLogger(c)
   113  		if err != nil {
   114  			return err
   115  		}
   116  		if logCreated {
   117  			defer func() {
   118  				if err = logDriver.Close(); err != nil {
   119  					log.G(context.TODO()).Errorf("Error closing logger: %v", err)
   120  				}
   121  			}()
   122  		}
   123  		cLog, ok := logDriver.(logger.LogReader)
   124  		if !ok {
   125  			return logger.ErrReadLogsNotSupported{}
   126  		}
   127  		logs := cLog.ReadLogs(logger.ReadConfig{Tail: -1})
   128  		defer logs.ConsumerGone()
   129  
   130  	LogLoop:
   131  		for {
   132  			select {
   133  			case msg, ok := <-logs.Msg:
   134  				if !ok {
   135  					break LogLoop
   136  				}
   137  				if msg.Source == "stdout" && cfg.Stdout != nil {
   138  					cfg.Stdout.Write(msg.Line)
   139  				}
   140  				if msg.Source == "stderr" && cfg.Stderr != nil {
   141  					cfg.Stderr.Write(msg.Line)
   142  				}
   143  			case err := <-logs.Err:
   144  				log.G(context.TODO()).Errorf("Error streaming logs: %v", err)
   145  				break LogLoop
   146  			}
   147  		}
   148  	}
   149  
   150  	daemon.LogContainerEvent(c, events.ActionAttach)
   151  
   152  	if !doStream {
   153  		return nil
   154  	}
   155  
   156  	if cfg.Stdin != nil {
   157  		r, w := io.Pipe()
   158  		go func(stdin io.ReadCloser) {
   159  			defer w.Close()
   160  			defer log.G(context.TODO()).Debug("Closing buffered stdin pipe")
   161  			io.Copy(w, stdin)
   162  		}(cfg.Stdin)
   163  		cfg.Stdin = r
   164  	}
   165  
   166  	if !c.Config.OpenStdin {
   167  		cfg.Stdin = nil
   168  	}
   169  
   170  	if c.Config.StdinOnce && !c.Config.Tty {
   171  		// Wait for the container to stop before returning.
   172  		waitChan := c.Wait(context.Background(), container.WaitConditionNotRunning)
   173  		defer func() {
   174  			<-waitChan // Ignore returned exit code.
   175  		}()
   176  	}
   177  
   178  	ctx := c.AttachContext()
   179  	err := <-c.StreamConfig.CopyStreams(ctx, cfg)
   180  	if err != nil {
   181  		var ierr term.EscapeError
   182  		if errors.Is(err, context.Canceled) || errors.As(err, &ierr) {
   183  			daemon.LogContainerEvent(c, events.ActionDetach)
   184  		} else {
   185  			log.G(ctx).Errorf("attach failed with error: %v", err)
   186  		}
   187  	}
   188  
   189  	return nil
   190  }