github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/pkg/varlinkapi/attach.go (about) 1 // +build varlink 2 3 package varlinkapi 4 5 import ( 6 "bufio" 7 "io" 8 9 "github.com/containers/libpod/libpod" 10 "github.com/containers/libpod/libpod/define" 11 "github.com/containers/libpod/libpod/events" 12 iopodman "github.com/containers/libpod/pkg/varlink" 13 "github.com/containers/libpod/pkg/varlinkapi/virtwriter" 14 "github.com/pkg/errors" 15 "github.com/sirupsen/logrus" 16 "k8s.io/client-go/tools/remotecommand" 17 ) 18 19 func setupStreams(call iopodman.VarlinkCall) (*bufio.Reader, *bufio.Writer, *io.PipeReader, *io.PipeWriter, *define.AttachStreams) { 20 21 // These are the varlink sockets 22 reader := call.Call.Reader 23 writer := call.Call.Writer 24 25 // This pipe is used to pass stdin from the client to the input stream 26 // once the msg has been "decoded" 27 pr, pw := io.Pipe() 28 29 stdoutWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.ToStdout) 30 // TODO if runc ever starts passing stderr, we can too 31 // stderrWriter := NewVirtWriteCloser(writer, ToStderr) 32 33 streams := define.AttachStreams{ 34 OutputStream: stdoutWriter, 35 InputStream: bufio.NewReader(pr), 36 // Runc eats the error stream 37 ErrorStream: stdoutWriter, 38 AttachInput: true, 39 AttachOutput: true, 40 // Runc eats the error stream 41 AttachError: true, 42 } 43 return reader, writer, pr, pw, &streams 44 } 45 46 // Attach connects to a containers console 47 func (i *VarlinkAPI) Attach(call iopodman.VarlinkCall, name string, detachKeys string, start bool) error { 48 var finalErr error 49 resize := make(chan remotecommand.TerminalSize) 50 errChan := make(chan error) 51 52 if !call.WantsUpgrade() { 53 return call.ReplyErrorOccurred("client must use upgraded connection to attach") 54 } 55 ctr, err := i.Runtime.LookupContainer(name) 56 if err != nil { 57 return call.ReplyErrorOccurred(err.Error()) 58 } 59 state, err := ctr.State() 60 if err != nil { 61 return call.ReplyErrorOccurred(err.Error()) 62 } 63 if !start && state != define.ContainerStateRunning { 64 return call.ReplyErrorOccurred("container must be running to attach") 65 } 66 67 // ACK the client upgrade request 68 if err := call.ReplyAttach(); err != nil { 69 return call.ReplyErrorOccurred(err.Error()) 70 } 71 72 reader, writer, _, pw, streams := setupStreams(call) 73 go func() { 74 if err := virtwriter.Reader(reader, nil, nil, pw, resize, nil); err != nil { 75 errChan <- err 76 } 77 }() 78 79 if state == define.ContainerStateRunning { 80 finalErr = attach(ctr, streams, detachKeys, resize, errChan) 81 } else { 82 finalErr = startAndAttach(ctr, streams, detachKeys, resize, errChan) 83 } 84 85 exitCode := define.ExitCode(finalErr) 86 if finalErr != define.ErrDetach && finalErr != nil { 87 logrus.Error(finalErr) 88 } else { 89 if ecode, err := ctr.Wait(); err != nil { 90 if errors.Cause(err) == define.ErrNoSuchCtr { 91 // Check events 92 event, err := i.Runtime.GetLastContainerEvent(ctr.ID(), events.Exited) 93 if err != nil { 94 logrus.Errorf("Cannot get exit code: %v", err) 95 exitCode = define.ExecErrorCodeNotFound 96 } else { 97 exitCode = event.ContainerExitCode 98 } 99 } else { 100 exitCode = define.ExitCode(err) 101 } 102 } else { 103 exitCode = int(ecode) 104 } 105 } 106 107 if ctr.AutoRemove() { 108 err := i.Runtime.RemoveContainer(getContext(), ctr, false, false) 109 if err != nil { 110 logrus.Errorf("Failed to remove container %s: %s", ctr.ID(), err.Error()) 111 } 112 } 113 114 if err = virtwriter.HangUp(writer, uint32(exitCode)); err != nil { 115 logrus.Errorf("Failed to HANG-UP attach to %s: %s", ctr.ID(), err.Error()) 116 } 117 return call.Writer.Flush() 118 } 119 120 func attach(ctr *libpod.Container, streams *define.AttachStreams, detachKeys string, resize chan remotecommand.TerminalSize, errChan chan error) error { 121 go func() { 122 if err := ctr.Attach(streams, detachKeys, resize); err != nil { 123 errChan <- err 124 } 125 }() 126 attachError := <-errChan 127 return attachError 128 } 129 130 func startAndAttach(ctr *libpod.Container, streams *define.AttachStreams, detachKeys string, resize chan remotecommand.TerminalSize, errChan chan error) error { 131 var finalErr error 132 attachChan, err := ctr.StartAndAttach(getContext(), streams, detachKeys, resize, false) 133 if err != nil { 134 return err 135 } 136 select { 137 case attachChanErr := <-attachChan: 138 finalErr = attachChanErr 139 case chanError := <-errChan: 140 finalErr = chanError 141 } 142 return finalErr 143 }