-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
e.g. docker -H ssh://me@server Signed-off-by: Akihiro Suda <[email protected]>
- Loading branch information
1 parent
6c9232a
commit 9fbd086
Showing
13 changed files
with
443 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
package system | ||
|
||
import ( | ||
"context" | ||
"io" | ||
"time" | ||
|
||
"github.com/docker/cli/cli" | ||
"github.com/docker/cli/cli/command" | ||
"github.com/pkg/errors" | ||
"github.com/sirupsen/logrus" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
type dialStdioOptions struct { | ||
// equivalent of `socat -t` and `nc -q` | ||
timeout time.Duration | ||
} | ||
|
||
// newDialStdioCommand creates a new cobra.Command for `docker system dial-stdio` | ||
func newDialStdioCommand(dockerCli command.Cli) *cobra.Command { | ||
var opts dialStdioOptions | ||
|
||
cmd := &cobra.Command{ | ||
Use: "dial-stdio [OPTIONS]", | ||
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(dockerCli, opts) | ||
}, | ||
} | ||
|
||
flags := cmd.Flags() | ||
flags.DurationVarP(&opts.timeout, "timeout", "t", 500*time.Millisecond, "After EOF from one of inputs, wait for this duration before exit") | ||
return cmd | ||
} | ||
|
||
func runDialStdio(dockerCli command.Cli, opts dialStdioOptions) error { | ||
ctx, cancel := context.WithCancel(context.Background()) | ||
defer cancel() | ||
conn, err := dockerCli.Client().DialRaw(ctx) | ||
if err != nil { | ||
return errors.Wrap(err, "failed to open the raw stream connection") | ||
} | ||
connHalfCloser, ok := conn.(halfCloser) | ||
if !ok { | ||
return errors.New("the raw stream connection does not implement halfCloser") | ||
} | ||
stdio := &cliHalfCloser{dockerCli} | ||
stdin2conn := make(chan error) | ||
conn2stdout := make(chan error) | ||
|
||
go func() { | ||
err := copier(connHalfCloser, stdio) | ||
stdin2conn <- err | ||
}() | ||
go func() { | ||
err := copier(stdio, connHalfCloser) | ||
conn2stdout <- err | ||
}() | ||
select { | ||
case err := <-stdin2conn: | ||
logrus.Debugf("stdin2conn: %v", err) | ||
case err := <-conn2stdout: | ||
logrus.Debugf("conn2stdout: %v", err) | ||
} | ||
select { | ||
case <-time.After(opts.timeout): | ||
logrus.Debugf("timedout after %v", opts.timeout) | ||
case err := <-stdin2conn: | ||
logrus.Debugf("stdin2conn: %v", err) | ||
case err := <-conn2stdout: | ||
logrus.Debugf("conn2stdout: %v", err) | ||
} | ||
return nil | ||
} | ||
|
||
func copier(to halfCloser, from halfCloser) error { | ||
if _, err := io.Copy(to, from); err != nil { | ||
return errors.Wrapf(err, "error while Copy (to=%+v, from=%+v)", to, from) | ||
} | ||
if err := from.CloseRead(); err != nil { | ||
return errors.Wrapf(err, "error while CloseRead (from=%+v)", from) | ||
} | ||
if err := to.CloseWrite(); err != nil { | ||
return errors.Wrapf(err, "error while CloseWrite (to=%+v)", to) | ||
} | ||
return nil | ||
} | ||
|
||
type halfReadCloser interface { | ||
io.Reader | ||
CloseRead() error | ||
} | ||
|
||
type halfWriteCloser interface { | ||
io.Writer | ||
CloseWrite() error | ||
} | ||
|
||
type halfCloser interface { | ||
halfReadCloser | ||
halfWriteCloser | ||
} | ||
|
||
type cliHalfCloser struct { | ||
command.Cli | ||
} | ||
|
||
func (x *cliHalfCloser) Read(p []byte) (int, error) { | ||
return x.In().Read(p) | ||
} | ||
|
||
func (x *cliHalfCloser) CloseRead() error { | ||
return x.In().Close() | ||
} | ||
|
||
func (x *cliHalfCloser) Write(p []byte) (int, error) { | ||
return x.Out().Write(p) | ||
} | ||
|
||
func (x *cliHalfCloser) CloseWrite() error { | ||
return x.Out().Close() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
// Package connhelper provides helpers for connecting to a remote daemon host with custom logic. | ||
package connhelper | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
"net" | ||
"net/url" | ||
"os" | ||
"os/exec" | ||
"time" | ||
|
||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
// ConnectionHelper allows to connect to a remote host with custom stream provider binary. | ||
type ConnectionHelper struct { | ||
Dialer func(ctx context.Context, network, addr string) (net.Conn, error) | ||
DummyHost string // dummy URL used for HTTP requests. e.g. "http://docker" | ||
} | ||
|
||
// GetConnectionHelper returns Docker-specific connection helper for the given URL. | ||
// GetConnectionHelper returns nil without error when no helper is registered for the scheme. | ||
// URL is like "ssh://me@server01". | ||
func GetConnectionHelper(daemonURL string) (*ConnectionHelper, error) { | ||
u, err := url.Parse(daemonURL) | ||
if err != nil { | ||
return nil, err | ||
} | ||
switch scheme := u.Scheme; scheme { | ||
case "ssh": | ||
return newSSHConnectionHelper(daemonURL) | ||
} | ||
// Future version may support plugins via ~/.docker/config.json. e.g. "dind" | ||
// See docker/cli#889 for the previous discussion. | ||
return nil, err | ||
} | ||
|
||
func newCommandConn(ctx context.Context, cmd string, args ...string) (net.Conn, error) { | ||
var ( | ||
c commandConn | ||
err error | ||
) | ||
c.cmd = exec.CommandContext(ctx, cmd, args...) | ||
// we assume that args never contains sensitive information | ||
logrus.Debugf("connhelper (%s): starting with %v", cmd, args) | ||
c.cmd.Env = os.Environ() | ||
c.stdin, err = c.cmd.StdinPipe() | ||
if err != nil { | ||
return nil, err | ||
} | ||
c.stdout, err = c.cmd.StdoutPipe() | ||
if err != nil { | ||
return nil, err | ||
} | ||
c.cmd.Stderr = &logrusDebugWriter{ | ||
prefix: fmt.Sprintf("connhelper (%s):", cmd), | ||
} | ||
c.localAddr = dummyAddr{network: "dummy", s: "dummy-0"} | ||
c.remoteAddr = dummyAddr{network: "dummy", s: "dummy-1"} | ||
return &c, c.cmd.Start() | ||
} | ||
|
||
// commandConn implements net.Conn | ||
type commandConn struct { | ||
cmd *exec.Cmd | ||
stdin io.WriteCloser | ||
stdout io.ReadCloser | ||
localAddr net.Addr | ||
remoteAddr net.Addr | ||
} | ||
|
||
func (c *commandConn) CloseRead() error { | ||
return c.stdout.Close() | ||
} | ||
|
||
func (c *commandConn) Read(p []byte) (int, error) { | ||
return c.stdout.Read(p) | ||
} | ||
|
||
func (c *commandConn) CloseWrite() error { | ||
return c.stdin.Close() | ||
} | ||
|
||
func (c *commandConn) Write(p []byte) (int, error) { | ||
return c.stdin.Write(p) | ||
} | ||
|
||
func (c *commandConn) Close() error { | ||
if err := c.stdin.Close(); err != nil { | ||
logrus.Warnf("error while closing stdin: %v", err) | ||
} | ||
if err := c.stdout.Close(); err != nil { | ||
logrus.Warnf("error while closing stdout: %v", err) | ||
} | ||
if err := c.cmd.Process.Kill(); err != nil { | ||
return err | ||
} | ||
_, err := c.cmd.Process.Wait() | ||
return err | ||
} | ||
|
||
func (c *commandConn) LocalAddr() net.Addr { | ||
return c.localAddr | ||
} | ||
func (c *commandConn) RemoteAddr() net.Addr { | ||
return c.remoteAddr | ||
} | ||
func (c *commandConn) SetDeadline(t time.Time) error { | ||
logrus.Debugf("unimplemented call: SetDeadline(%v)", t) | ||
return nil | ||
} | ||
func (c *commandConn) SetReadDeadline(t time.Time) error { | ||
logrus.Debugf("unimplemented call: SetReadDeadline(%v)", t) | ||
return nil | ||
} | ||
func (c *commandConn) SetWriteDeadline(t time.Time) error { | ||
logrus.Debugf("unimplemented call: SetWriteDeadline(%v)", t) | ||
return nil | ||
} | ||
|
||
type dummyAddr struct { | ||
network string | ||
s string | ||
} | ||
|
||
func (d dummyAddr) Network() string { | ||
return d.network | ||
} | ||
|
||
func (d dummyAddr) String() string { | ||
return d.s | ||
} | ||
|
||
type logrusDebugWriter struct { | ||
prefix string | ||
} | ||
|
||
func (w *logrusDebugWriter) Write(p []byte) (int, error) { | ||
logrus.Debugf("%s%s", w.prefix, string(p)) | ||
return len(p), nil | ||
} |
Oops, something went wrong.