github.com/xeptore/docker-cli@v20.10.14+incompatible/cli/command/system/dial_stdio.go (about)

     1  package system
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"os"
     7  
     8  	"github.com/docker/cli/cli"
     9  	"github.com/docker/cli/cli/command"
    10  	"github.com/pkg/errors"
    11  	"github.com/sirupsen/logrus"
    12  	"github.com/spf13/cobra"
    13  )
    14  
    15  // newDialStdioCommand creates a new cobra.Command for `docker system dial-stdio`
    16  func newDialStdioCommand(dockerCli command.Cli) *cobra.Command {
    17  	cmd := &cobra.Command{
    18  		Use:    "dial-stdio",
    19  		Short:  "Proxy the stdio stream to the daemon connection. Should not be invoked manually.",
    20  		Args:   cli.NoArgs,
    21  		Hidden: true,
    22  		RunE: func(cmd *cobra.Command, args []string) error {
    23  			return runDialStdio(dockerCli)
    24  		},
    25  	}
    26  	return cmd
    27  }
    28  
    29  func runDialStdio(dockerCli command.Cli) error {
    30  	ctx, cancel := context.WithCancel(context.Background())
    31  	defer cancel()
    32  	dialer := dockerCli.Client().Dialer()
    33  	conn, err := dialer(ctx)
    34  	if err != nil {
    35  		return errors.Wrap(err, "failed to open the raw stream connection")
    36  	}
    37  	defer conn.Close()
    38  
    39  	var connHalfCloser halfCloser
    40  	switch t := conn.(type) {
    41  	case halfCloser:
    42  		connHalfCloser = t
    43  	case halfReadWriteCloser:
    44  		connHalfCloser = &nopCloseReader{t}
    45  	default:
    46  		return errors.New("the raw stream connection does not implement halfCloser")
    47  	}
    48  
    49  	stdin2conn := make(chan error, 1)
    50  	conn2stdout := make(chan error, 1)
    51  	go func() {
    52  		stdin2conn <- copier(connHalfCloser, &halfReadCloserWrapper{os.Stdin}, "stdin to stream")
    53  	}()
    54  	go func() {
    55  		conn2stdout <- copier(&halfWriteCloserWrapper{os.Stdout}, connHalfCloser, "stream to stdout")
    56  	}()
    57  	select {
    58  	case err = <-stdin2conn:
    59  		if err != nil {
    60  			return err
    61  		}
    62  		// wait for stdout
    63  		err = <-conn2stdout
    64  	case err = <-conn2stdout:
    65  		// return immediately without waiting for stdin to be closed.
    66  		// (stdin is never closed when tty)
    67  	}
    68  	return err
    69  }
    70  
    71  func copier(to halfWriteCloser, from halfReadCloser, debugDescription string) error {
    72  	defer func() {
    73  		if err := from.CloseRead(); err != nil {
    74  			logrus.Errorf("error while CloseRead (%s): %v", debugDescription, err)
    75  		}
    76  		if err := to.CloseWrite(); err != nil {
    77  			logrus.Errorf("error while CloseWrite (%s): %v", debugDescription, err)
    78  		}
    79  	}()
    80  	if _, err := io.Copy(to, from); err != nil {
    81  		return errors.Wrapf(err, "error while Copy (%s)", debugDescription)
    82  	}
    83  	return nil
    84  }
    85  
    86  type halfReadCloser interface {
    87  	io.Reader
    88  	CloseRead() error
    89  }
    90  
    91  type halfWriteCloser interface {
    92  	io.Writer
    93  	CloseWrite() error
    94  }
    95  
    96  type halfCloser interface {
    97  	halfReadCloser
    98  	halfWriteCloser
    99  }
   100  
   101  type halfReadWriteCloser interface {
   102  	io.Reader
   103  	halfWriteCloser
   104  }
   105  
   106  type nopCloseReader struct {
   107  	halfReadWriteCloser
   108  }
   109  
   110  func (x *nopCloseReader) CloseRead() error {
   111  	return nil
   112  }
   113  
   114  type halfReadCloserWrapper struct {
   115  	io.ReadCloser
   116  }
   117  
   118  func (x *halfReadCloserWrapper) CloseRead() error {
   119  	return x.Close()
   120  }
   121  
   122  type halfWriteCloserWrapper struct {
   123  	io.WriteCloser
   124  }
   125  
   126  func (x *halfWriteCloserWrapper) CloseWrite() error {
   127  	return x.Close()
   128  }