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