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  }