This replaces the visitAll recursive function with a test that verifies that the option is set for all commands and subcommands, so that it doesn't have to be modified at runtime. We currently still have to loop over all functions for the setValidateArgs call, but that can be looked at separately. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
132 lines
2.9 KiB
Go
132 lines
2.9 KiB
Go
package system
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"os"
|
|
|
|
"github.com/docker/cli/cli"
|
|
"github.com/docker/cli/cli/command"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
// newDialStdioCommand creates a new cobra.Command for `docker system dial-stdio`
|
|
func newDialStdioCommand(dockerCLI command.Cli) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "dial-stdio",
|
|
Short: "Proxy the stdio stream to the daemon connection. Should not be invoked manually.",
|
|
Args: cli.NoArgs,
|
|
Hidden: true,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
return runDialStdio(cmd.Context(), dockerCLI)
|
|
},
|
|
ValidArgsFunction: cobra.NoFileCompletions,
|
|
DisableFlagsInUseLine: true,
|
|
}
|
|
return cmd
|
|
}
|
|
|
|
func runDialStdio(ctx context.Context, dockerCli command.Cli) error {
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
defer cancel()
|
|
|
|
dialer := dockerCli.Client().Dialer()
|
|
conn, err := dialer(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to open the raw stream connection")
|
|
}
|
|
defer conn.Close()
|
|
|
|
var connHalfCloser halfCloser
|
|
switch t := conn.(type) {
|
|
case halfCloser:
|
|
connHalfCloser = t
|
|
case halfReadWriteCloser:
|
|
connHalfCloser = &nopCloseReader{t}
|
|
default:
|
|
return errors.New("the raw stream connection does not implement halfCloser")
|
|
}
|
|
|
|
stdin2conn := make(chan error, 1)
|
|
conn2stdout := make(chan error, 1)
|
|
go func() {
|
|
stdin2conn <- copier(connHalfCloser, &halfReadCloserWrapper{os.Stdin}, "stdin to stream")
|
|
}()
|
|
go func() {
|
|
conn2stdout <- copier(&halfWriteCloserWrapper{os.Stdout}, connHalfCloser, "stream to stdout")
|
|
}()
|
|
select {
|
|
case err = <-stdin2conn:
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// wait for stdout
|
|
err = <-conn2stdout
|
|
case err = <-conn2stdout:
|
|
// return immediately without waiting for stdin to be closed.
|
|
// (stdin is never closed when tty)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func copier(to halfWriteCloser, from halfReadCloser, debugDescription string) error {
|
|
defer func() {
|
|
if err := from.CloseRead(); err != nil {
|
|
logrus.Errorf("error while CloseRead (%s): %v", debugDescription, err)
|
|
}
|
|
if err := to.CloseWrite(); err != nil {
|
|
logrus.Errorf("error while CloseWrite (%s): %v", debugDescription, err)
|
|
}
|
|
}()
|
|
if _, err := io.Copy(to, from); err != nil {
|
|
return errors.Wrapf(err, "error while Copy (%s)", debugDescription)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type halfReadCloser interface {
|
|
io.Reader
|
|
CloseRead() error
|
|
}
|
|
|
|
type halfWriteCloser interface {
|
|
io.Writer
|
|
CloseWrite() error
|
|
}
|
|
|
|
type halfCloser interface {
|
|
halfReadCloser
|
|
halfWriteCloser
|
|
}
|
|
|
|
type halfReadWriteCloser interface {
|
|
io.Reader
|
|
halfWriteCloser
|
|
}
|
|
|
|
type nopCloseReader struct {
|
|
halfReadWriteCloser
|
|
}
|
|
|
|
func (*nopCloseReader) CloseRead() error {
|
|
return nil
|
|
}
|
|
|
|
type halfReadCloserWrapper struct {
|
|
io.ReadCloser
|
|
}
|
|
|
|
func (x *halfReadCloserWrapper) CloseRead() error {
|
|
return x.Close()
|
|
}
|
|
|
|
type halfWriteCloserWrapper struct {
|
|
io.WriteCloser
|
|
}
|
|
|
|
func (x *halfWriteCloserWrapper) CloseWrite() error {
|
|
return x.Close()
|
|
}
|