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 }