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  }