diff --git a/cli/command/cli.go b/cli/command/cli.go index b17aaf238f..1e12a1ace8 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -14,6 +14,7 @@ import ( "github.com/docker/cli/cli/config" cliconfig "github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config/configfile" + "github.com/docker/cli/cli/connhelper" cliflags "github.com/docker/cli/cli/flags" manifeststore "github.com/docker/cli/cli/manifest/store" registryclient "github.com/docker/cli/cli/registry/client" @@ -248,31 +249,54 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, isTrusted bool) *DockerC // NewAPIClientFromFlags creates a new APIClient from command line flags func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.ConfigFile) (client.APIClient, error) { - host, err := getServerHost(opts.Hosts, opts.TLSOptions) + unparsedHost, err := getUnparsedServerHost(opts.Hosts) if err != nil { return &client.Client{}, err } + var clientOpts []func(*client.Client) error + helper, err := connhelper.GetConnectionHelper(unparsedHost) + if err != nil { + return &client.Client{}, err + } + if helper == nil { + clientOpts = append(clientOpts, withHTTPClient(opts.TLSOptions)) + host, err := dopts.ParseHost(opts.TLSOptions != nil, unparsedHost) + if err != nil { + return &client.Client{}, err + } + clientOpts = append(clientOpts, client.WithHost(host)) + } else { + clientOpts = append(clientOpts, func(c *client.Client) error { + httpClient := &http.Client{ + // No tls + // No proxy + Transport: &http.Transport{ + DialContext: helper.Dialer, + }, + } + return client.WithHTTPClient(httpClient)(c) + }) + clientOpts = append(clientOpts, client.WithHost(helper.Host)) + clientOpts = append(clientOpts, client.WithDialContext(helper.Dialer)) + } customHeaders := configFile.HTTPHeaders if customHeaders == nil { customHeaders = map[string]string{} } customHeaders["User-Agent"] = UserAgent() + clientOpts = append(clientOpts, client.WithHTTPHeaders(customHeaders)) verStr := api.DefaultVersion if tmpStr := os.Getenv("DOCKER_API_VERSION"); tmpStr != "" { verStr = tmpStr } + clientOpts = append(clientOpts, client.WithVersion(verStr)) - return client.NewClientWithOpts( - withHTTPClient(opts.TLSOptions), - client.WithHTTPHeaders(customHeaders), - client.WithVersion(verStr), - client.WithHost(host), - ) + return client.NewClientWithOpts(clientOpts...) } -func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (string, error) { +func getUnparsedServerHost(hosts []string) (string, error) { var host string switch len(hosts) { case 0: @@ -282,8 +306,7 @@ func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (string, error default: return "", errors.New("Please specify only one -H") } - - return dopts.ParseHost(tlsOptions != nil, host) + return host, nil } func withHTTPClient(tlsOpts *tlsconfig.Options) func(*client.Client) error { diff --git a/cli/command/system/cmd.go b/cli/command/system/cmd.go index 7b9d6819c3..6accb98f0c 100644 --- a/cli/command/system/cmd.go +++ b/cli/command/system/cmd.go @@ -19,6 +19,7 @@ func NewSystemCommand(dockerCli command.Cli) *cobra.Command { NewInfoCommand(dockerCli), newDiskUsageCommand(dockerCli), newPruneCommand(dockerCli), + newDialStdioCommand(dockerCli), ) return cmd diff --git a/cli/command/system/dial_stdio.go b/cli/command/system/dial_stdio.go new file mode 100644 index 0000000000..642d79505e --- /dev/null +++ b/cli/command/system/dial_stdio.go @@ -0,0 +1,107 @@ +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(dockerCli) + }, + } + return cmd +} + +func runDialStdio(dockerCli command.Cli) error { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + dialer := dockerCli.Client().Dialer() + conn, err := dialer(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") + } + stdin2conn := make(chan error) + conn2stdout := make(chan error) + 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 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() +} diff --git a/cli/connhelper/connhelper.go b/cli/connhelper/connhelper.go new file mode 100644 index 0000000000..30c9dd8e45 --- /dev/null +++ b/cli/connhelper/connhelper.go @@ -0,0 +1,239 @@ +// Package connhelper provides helpers for connecting to a remote daemon host with custom logic. +package connhelper + +import ( + "bytes" + "context" + "fmt" + "io" + "net" + "net/url" + "os" + "os/exec" + "strings" + "sync" + "time" + + "github.com/docker/cli/cli/connhelper/ssh" + "github.com/pkg/errors" + "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) + Host 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": + sshCmd, sshArgs, err := ssh.New(daemonURL) + if err != nil { + return nil, err + } + return &ConnectionHelper{ + Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) { + return newCommandConn(ctx, sshCmd, sshArgs...) + }, + Host: "http://docker", + }, nil + } + // 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: starting %s with %v", cmd, args) + c.cmd.Env = os.Environ() + setPdeathsig(c.cmd) + 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 = &stderrWriter{ + stderrMu: &c.stderrMu, + stderr: &c.stderr, + debugPrefix: 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 + stderrMu sync.Mutex + stderr bytes.Buffer + stdioClosedMu sync.Mutex // for stdinClosed and stdoutClosed + stdinClosed bool + stdoutClosed bool + localAddr net.Addr + remoteAddr net.Addr +} + +// killIfStdioClosed kills the cmd if both stdin and stdout are closed. +func (c *commandConn) killIfStdioClosed() error { + c.stdioClosedMu.Lock() + stdioClosed := c.stdoutClosed && c.stdinClosed + c.stdioClosedMu.Unlock() + if !stdioClosed { + return nil + } + var err error + // NOTE: maybe already killed here + if err = c.cmd.Process.Kill(); err == nil { + err = c.cmd.Wait() + } + if err != nil { + // err is typically "os: process already finished". + // we check ProcessState here instead of `strings.Contains(err, "os: process already finished")` + if c.cmd.ProcessState.Exited() { + err = nil + } + } + return err +} + +func (c *commandConn) onEOF(eof error) error { + werr := c.cmd.Wait() + if werr == nil { + return eof + } + c.stderrMu.Lock() + stderr := c.stderr.String() + c.stderrMu.Unlock() + return errors.Errorf("command %v has exited with %v, please make sure the URL is valid, and Docker 18.09 or later is installed on the remote host: stderr=%q", c.cmd.Args, werr, stderr) +} + +func ignorableCloseError(err error) bool { + errS := err.Error() + ss := []string{ + os.ErrClosed.Error(), + } + for _, s := range ss { + if strings.Contains(errS, s) { + return true + } + } + return false +} + +func (c *commandConn) CloseRead() error { + // NOTE: maybe already closed here + if err := c.stdout.Close(); err != nil && !ignorableCloseError(err) { + logrus.Warnf("commandConn.CloseRead: %v", err) + } + c.stdioClosedMu.Lock() + c.stdoutClosed = true + c.stdioClosedMu.Unlock() + return c.killIfStdioClosed() +} + +func (c *commandConn) Read(p []byte) (int, error) { + n, err := c.stdout.Read(p) + if err == io.EOF { + err = c.onEOF(err) + } + return n, err +} + +func (c *commandConn) CloseWrite() error { + // NOTE: maybe already closed here + if err := c.stdin.Close(); err != nil && !ignorableCloseError(err) { + logrus.Warnf("commandConn.CloseWrite: %v", err) + } + c.stdioClosedMu.Lock() + c.stdinClosed = true + c.stdioClosedMu.Unlock() + return c.killIfStdioClosed() +} + +func (c *commandConn) Write(p []byte) (int, error) { + n, err := c.stdin.Write(p) + if err == io.EOF { + err = c.onEOF(err) + } + return n, err +} + +func (c *commandConn) Close() error { + var err error + if err = c.CloseRead(); err != nil { + logrus.Warnf("commandConn.Close: CloseRead: %v", err) + } + if err = c.CloseWrite(); err != nil { + logrus.Warnf("commandConn.Close: CloseWrite: %v", err) + } + 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 stderrWriter struct { + stderrMu *sync.Mutex + stderr *bytes.Buffer + debugPrefix string +} + +func (w *stderrWriter) Write(p []byte) (int, error) { + logrus.Debugf("%s%s", w.debugPrefix, string(p)) + w.stderrMu.Lock() + if w.stderr.Len() > 4096 { + w.stderr.Reset() + } + n, err := w.stderr.Write(p) + w.stderrMu.Unlock() + return n, err +} diff --git a/cli/connhelper/connhelper_linux.go b/cli/connhelper/connhelper_linux.go new file mode 100644 index 0000000000..f138f53675 --- /dev/null +++ b/cli/connhelper/connhelper_linux.go @@ -0,0 +1,12 @@ +package connhelper + +import ( + "os/exec" + "syscall" +) + +func setPdeathsig(cmd *exec.Cmd) { + cmd.SysProcAttr = &syscall.SysProcAttr{ + Pdeathsig: syscall.SIGKILL, + } +} diff --git a/cli/connhelper/connhelper_nolinux.go b/cli/connhelper/connhelper_nolinux.go new file mode 100644 index 0000000000..c8350d9d77 --- /dev/null +++ b/cli/connhelper/connhelper_nolinux.go @@ -0,0 +1,10 @@ +// +build !linux + +package connhelper + +import ( + "os/exec" +) + +func setPdeathsig(cmd *exec.Cmd) { +} diff --git a/cli/connhelper/connhelper_unix_test.go b/cli/connhelper/connhelper_unix_test.go new file mode 100644 index 0000000000..c57d655e16 --- /dev/null +++ b/cli/connhelper/connhelper_unix_test.go @@ -0,0 +1,44 @@ +// +build !windows + +package connhelper + +import ( + "context" + "io" + "testing" + + "gotest.tools/assert" + is "gotest.tools/assert/cmp" +) + +// For https://github.com/docker/cli/pull/1014#issuecomment-409308139 +func TestCommandConnEOFWithError(t *testing.T) { + ctx := context.TODO() + cmd := "sh" + args := []string{"-c", "echo hello; echo some error >&2; exit 42"} + c, err := newCommandConn(ctx, cmd, args...) + assert.NilError(t, err) + b := make([]byte, 32) + n, err := c.Read(b) + assert.Check(t, is.Equal(len("hello\n"), n)) + assert.NilError(t, err) + n, err = c.Read(b) + assert.Check(t, is.Equal(0, n)) + assert.ErrorContains(t, err, "some error") + assert.ErrorContains(t, err, "42") +} + +func TestCommandConnEOFWithoutError(t *testing.T) { + ctx := context.TODO() + cmd := "sh" + args := []string{"-c", "echo hello; echo some debug log >&2; exit 0"} + c, err := newCommandConn(ctx, cmd, args...) + assert.NilError(t, err) + b := make([]byte, 32) + n, err := c.Read(b) + assert.Check(t, is.Equal(len("hello\n"), n)) + assert.NilError(t, err) + n, err = c.Read(b) + assert.Check(t, is.Equal(0, n)) + assert.Check(t, is.Equal(io.EOF, err)) +} diff --git a/cli/connhelper/ssh/ssh.go b/cli/connhelper/ssh/ssh.go new file mode 100644 index 0000000000..0f2cfabc07 --- /dev/null +++ b/cli/connhelper/ssh/ssh.go @@ -0,0 +1,70 @@ +// Package ssh provides the connection helper for ssh:// URL. +// Requires Docker 18.09 or later on the remote host. +package ssh + +import ( + "net/url" + + "github.com/pkg/errors" +) + +// New returns cmd and its args +func New(daemonURL string) (string, []string, error) { + sp, err := parseSSHURL(daemonURL) + if err != nil { + return "", nil, err + } + return "ssh", append(sp.Args(), []string{"--", "docker", "system", "dial-stdio"}...), nil +} + +func parseSSHURL(daemonURL string) (*sshSpec, error) { + u, err := url.Parse(daemonURL) + if err != nil { + return nil, err + } + if u.Scheme != "ssh" { + return nil, errors.Errorf("expected scheme ssh, got %s", u.Scheme) + } + + var sp sshSpec + + if u.User != nil { + sp.user = u.User.Username() + if _, ok := u.User.Password(); ok { + return nil, errors.New("ssh helper does not accept plain-text password") + } + } + sp.host = u.Hostname() + if sp.host == "" { + return nil, errors.Errorf("host is not specified") + } + sp.port = u.Port() + if u.Path != "" { + return nil, errors.Errorf("extra path: %s", u.Path) + } + if u.RawQuery != "" { + return nil, errors.Errorf("extra query: %s", u.RawQuery) + } + if u.Fragment != "" { + return nil, errors.Errorf("extra fragment: %s", u.Fragment) + } + return &sp, err +} + +type sshSpec struct { + user string + host string + port string +} + +func (sp *sshSpec) Args() []string { + var args []string + if sp.user != "" { + args = append(args, "-l", sp.user) + } + if sp.port != "" { + args = append(args, "-p", sp.port) + } + args = append(args, sp.host) + return args +} diff --git a/cli/connhelper/ssh/ssh_test.go b/cli/connhelper/ssh/ssh_test.go new file mode 100644 index 0000000000..30a638545c --- /dev/null +++ b/cli/connhelper/ssh/ssh_test.go @@ -0,0 +1,64 @@ +package ssh + +import ( + "testing" + + "gotest.tools/assert" + is "gotest.tools/assert/cmp" +) + +func TestParseSSHURL(t *testing.T) { + testCases := []struct { + url string + expectedArgs []string + expectedError string + }{ + { + url: "ssh://foo", + expectedArgs: []string{ + "foo", + }, + }, + { + url: "ssh://me@foo:10022", + expectedArgs: []string{ + "-l", "me", + "-p", "10022", + "foo", + }, + }, + { + url: "ssh://me:passw0rd@foo", + expectedError: "ssh helper does not accept plain-text password", + }, + { + url: "ssh://foo/bar", + expectedError: "extra path", + }, + { + url: "ssh://foo?bar", + expectedError: "extra query", + }, + { + url: "ssh://foo#bar", + expectedError: "extra fragment", + }, + { + url: "ssh://", + expectedError: "host is not specified", + }, + { + url: "foo://bar", + expectedError: "expected scheme ssh", + }, + } + for _, tc := range testCases { + sp, err := parseSSHURL(tc.url) + if tc.expectedError == "" { + assert.NilError(t, err) + assert.Check(t, is.DeepEqual(tc.expectedArgs, sp.Args())) + } else { + assert.ErrorContains(t, err, tc.expectedError) + } + } +} diff --git a/cli/flags/common.go b/cli/flags/common.go index 3834097c3a..22faf12ca6 100644 --- a/cli/flags/common.go +++ b/cli/flags/common.go @@ -67,7 +67,8 @@ func (commonOpts *CommonOptions) InstallFlags(flags *pflag.FlagSet) { flags.Var(opts.NewQuotedString(&tlsOptions.CertFile), "tlscert", "Path to TLS certificate file") flags.Var(opts.NewQuotedString(&tlsOptions.KeyFile), "tlskey", "Path to TLS key file") - hostOpt := opts.NewNamedListOptsRef("hosts", &commonOpts.Hosts, opts.ValidateHost) + // opts.ValidateHost is not used here, so as to allow connection helpers + hostOpt := opts.NewNamedListOptsRef("hosts", &commonOpts.Hosts, nil) flags.VarP(hostOpt, "host", "H", "Daemon socket(s) to connect to") } diff --git a/docs/reference/commandline/dockerd.md b/docs/reference/commandline/dockerd.md index 1afc27e8b1..8f8aa722a3 100644 --- a/docs/reference/commandline/dockerd.md +++ b/docs/reference/commandline/dockerd.md @@ -181,6 +181,19 @@ The Docker client will honor the `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` environment variables (or the lowercase versions thereof). `HTTPS_PROXY` takes precedence over `HTTP_PROXY`. +Starting with Docker 18.09, the Docker client supports connecting to a remote +daemon via SSH: + +``` +$ docker -H ssh://me@example.com:22 ps +$ docker -H ssh://me@example.com ps +$ docker -H ssh://example.com ps +``` + +To use SSH connection, you need to set up `ssh` so that it can reach the +remote host with public key authentication. +Also, you need to have `docker` binary 18.09 or later on the daemon host. + #### Bind Docker to another host/port or a Unix socket > **Warning**: diff --git a/vendor.conf b/vendor.conf index 543c96a4f2..734b7828fe 100755 --- a/vendor.conf +++ b/vendor.conf @@ -8,7 +8,7 @@ github.com/coreos/etcd v3.3.9 github.com/cpuguy83/go-md2man v1.0.8 github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76 # v1.1.0 github.com/docker/distribution 83389a148052d74ac602f5f1d62f86ff2f3c4aa5 -github.com/docker/docker b711437bbd8596312c962d4189e9ad4d2108c2dc +github.com/docker/docker 562df8c2d6f48601c8d1df7256389569d25c0bf1 github.com/docker/docker-credential-helpers 5241b46610f2491efdf9d1c85f1ddf5b02f6d962 # the docker/go package contains a customized version of canonical/json # and is used by Notary. The package is periodically rebased on current Go versions. diff --git a/vendor/github.com/docker/docker/api/types/volume/volume_create.go b/vendor/github.com/docker/docker/api/types/volume/volume_create.go index 539e9b97d9..f12e48612c 100644 --- a/vendor/github.com/docker/docker/api/types/volume/volume_create.go +++ b/vendor/github.com/docker/docker/api/types/volume/volume_create.go @@ -7,7 +7,7 @@ package volume // See hack/generate-swagger-api.sh // ---------------------------------------------------------------------------- -// VolumeCreateBody +// VolumeCreateBody Volume configuration // swagger:model VolumeCreateBody type VolumeCreateBody struct { diff --git a/vendor/github.com/docker/docker/api/types/volume/volume_list.go b/vendor/github.com/docker/docker/api/types/volume/volume_list.go index 1bb279dbb3..020198f737 100644 --- a/vendor/github.com/docker/docker/api/types/volume/volume_list.go +++ b/vendor/github.com/docker/docker/api/types/volume/volume_list.go @@ -9,7 +9,7 @@ package volume import "github.com/docker/docker/api/types" -// VolumeListOKBody +// VolumeListOKBody Volume list response // swagger:model VolumeListOKBody type VolumeListOKBody struct { diff --git a/vendor/github.com/docker/docker/client/build_cancel.go b/vendor/github.com/docker/docker/client/build_cancel.go index 4cf8c980a9..74df495089 100644 --- a/vendor/github.com/docker/docker/client/build_cancel.go +++ b/vendor/github.com/docker/docker/client/build_cancel.go @@ -1,9 +1,8 @@ package client // import "github.com/docker/docker/client" import ( + "context" "net/url" - - "golang.org/x/net/context" ) // BuildCancel requests the daemon to cancel ongoing build request diff --git a/vendor/github.com/docker/docker/client/client.go b/vendor/github.com/docker/docker/client/client.go index b874b3b522..7475d3f968 100644 --- a/vendor/github.com/docker/docker/client/client.go +++ b/vendor/github.com/docker/docker/client/client.go @@ -173,10 +173,17 @@ func WithTLSClientConfig(cacertPath, certPath, keyPath string) func(*Client) err // WithDialer applies the dialer.DialContext to the client transport. This can be // used to set the Timeout and KeepAlive settings of the client. +// Deprecated: use WithDialContext func WithDialer(dialer *net.Dialer) func(*Client) error { + return WithDialContext(dialer.DialContext) +} + +// WithDialContext applies the dialer to the client transport. This can be +// used to set the Timeout and KeepAlive settings of the client. +func WithDialContext(dialContext func(ctx context.Context, network, addr string) (net.Conn, error)) func(*Client) error { return func(c *Client) error { if transport, ok := c.client.Transport.(*http.Transport); ok { - transport.DialContext = dialer.DialContext + transport.DialContext = dialContext return nil } return errors.Errorf("cannot apply dialer to transport: %T", c.client.Transport) @@ -400,3 +407,16 @@ func (cli *Client) CustomHTTPHeaders() map[string]string { func (cli *Client) SetCustomHTTPHeaders(headers map[string]string) { cli.customHTTPHeaders = headers } + +// Dialer returns a dialer for a raw stream connection, with HTTP/1.1 header, that can be used for proxying the daemon connection. +// Used by `docker dial-stdio` (docker/cli#889). +func (cli *Client) Dialer() func(context.Context) (net.Conn, error) { + return func(ctx context.Context) (net.Conn, error) { + if transport, ok := cli.client.Transport.(*http.Transport); ok { + if transport.DialContext != nil { + return transport.DialContext(ctx, cli.proto, cli.addr) + } + } + return fallbackDial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport)) + } +} diff --git a/vendor/github.com/docker/docker/client/hijack.go b/vendor/github.com/docker/docker/client/hijack.go index 35f5dd86dc..0ac8248f2c 100644 --- a/vendor/github.com/docker/docker/client/hijack.go +++ b/vendor/github.com/docker/docker/client/hijack.go @@ -30,7 +30,7 @@ func (cli *Client) postHijacked(ctx context.Context, path string, query url.Valu } req = cli.addHeaders(req, headers) - conn, err := cli.setupHijackConn(req, "tcp") + conn, err := cli.setupHijackConn(ctx, req, "tcp") if err != nil { return types.HijackedResponse{}, err } @@ -38,7 +38,9 @@ func (cli *Client) postHijacked(ctx context.Context, path string, query url.Valu return types.HijackedResponse{Conn: conn, Reader: bufio.NewReader(conn)}, err } -func dial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) { +// fallbackDial is used when WithDialer() was not called. +// See cli.Dialer(). +func fallbackDial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) { if tlsConfig != nil && proto != "unix" && proto != "npipe" { return tls.Dial(proto, addr, tlsConfig) } @@ -48,12 +50,13 @@ func dial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) { return net.Dial(proto, addr) } -func (cli *Client) setupHijackConn(req *http.Request, proto string) (net.Conn, error) { +func (cli *Client) setupHijackConn(ctx context.Context, req *http.Request, proto string) (net.Conn, error) { req.Host = cli.addr req.Header.Set("Connection", "Upgrade") req.Header.Set("Upgrade", proto) - conn, err := dial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport)) + dialer := cli.Dialer() + conn, err := dialer(ctx) if err != nil { return nil, errors.Wrap(err, "cannot connect to the Docker daemon. Is 'docker daemon' running on this host?") } diff --git a/vendor/github.com/docker/docker/client/interface.go b/vendor/github.com/docker/docker/client/interface.go index 9250c468a6..663749f008 100644 --- a/vendor/github.com/docker/docker/client/interface.go +++ b/vendor/github.com/docker/docker/client/interface.go @@ -39,6 +39,7 @@ type CommonAPIClient interface { NegotiateAPIVersion(ctx context.Context) NegotiateAPIVersionPing(types.Ping) DialSession(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) + Dialer() func(context.Context) (net.Conn, error) Close() error } diff --git a/vendor/github.com/docker/docker/client/session.go b/vendor/github.com/docker/docker/client/session.go index c247123b45..df199f3d03 100644 --- a/vendor/github.com/docker/docker/client/session.go +++ b/vendor/github.com/docker/docker/client/session.go @@ -14,5 +14,5 @@ func (cli *Client) DialSession(ctx context.Context, proto string, meta map[strin } req = cli.addHeaders(req, meta) - return cli.setupHijackConn(req, proto) + return cli.setupHijackConn(ctx, req, proto) } diff --git a/vendor/github.com/docker/docker/pkg/system/rm.go b/vendor/github.com/docker/docker/pkg/system/rm.go index 02e4d26221..b310991800 100644 --- a/vendor/github.com/docker/docker/pkg/system/rm.go +++ b/vendor/github.com/docker/docker/pkg/system/rm.go @@ -34,7 +34,7 @@ func EnsureRemoveAll(dir string) error { for { err := os.RemoveAll(dir) if err == nil { - return err + return nil } pe, ok := err.(*os.PathError) diff --git a/vendor/github.com/docker/docker/vendor.conf b/vendor/github.com/docker/docker/vendor.conf index 33e2dc8445..9c4981c1c0 100644 --- a/vendor/github.com/docker/docker/vendor.conf +++ b/vendor/github.com/docker/docker/vendor.conf @@ -1,13 +1,13 @@ # the following lines are in sorted order, FYI github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109 -github.com/Microsoft/hcsshim v0.6.11 +github.com/Microsoft/hcsshim v0.6.12 github.com/Microsoft/go-winio v0.4.8 github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a github.com/go-check/check 4ed411733c5785b40214c70bce814c3a3a689609 https://github.com/cpuguy83/check.git github.com/golang/gddo 9b12a26f3fbd7397dee4e20939ddca719d840d2a github.com/gorilla/context v1.1 github.com/gorilla/mux v1.1 -github.com/Microsoft/opengcs v0.3.6 +github.com/Microsoft/opengcs v0.3.8 github.com/kr/pty 5cf931ef8f github.com/mattn/go-shellwords v1.0.3 github.com/sirupsen/logrus v1.0.3 @@ -22,11 +22,11 @@ gotest.tools v2.1.0 github.com/google/go-cmp v0.2.0 github.com/RackSec/srslog 456df3a81436d29ba874f3590eeeee25d666f8a5 -github.com/imdario/mergo v0.3.5 +github.com/imdario/mergo v0.3.6 golang.org/x/sync fd80eb99c8f653c847d294a001bdf2a3a6f768f5 # buildkit -github.com/moby/buildkit 9acf51e49185b348608e0096b2903dd72907adcb +github.com/moby/buildkit 98f1604134f945d48538ffca0e18662337b4a850 github.com/tonistiigi/fsutil 8abad97ee3969cdf5e9c367f46adba2c212b3ddb github.com/grpc-ecosystem/grpc-opentracing 8e809c8a86450a29b90dcc9efbf062d0fe6d9746 github.com/opentracing/opentracing-go 1361b9cd60be79c4c3a7fa9841b3c132e40066a7 @@ -37,7 +37,7 @@ github.com/mitchellh/hashstructure 2bca23e0e452137f789efbc8610126fd8b94f73b #get libnetwork packages # When updating, also update LIBNETWORK_COMMIT in hack/dockerfile/install/proxy accordingly -github.com/docker/libnetwork 430c00a6a6b3dfdd774f21e1abd4ad6b0216c629 +github.com/docker/libnetwork d00ceed44cc447c77f25cdf5d59e83163bdcb4c9 github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9 github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80 github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec @@ -95,7 +95,7 @@ github.com/philhofer/fwd 98c11a7a6ec829d672b03833c3d69a7fae1ca972 github.com/tinylib/msgp 3b556c64540842d4f82967be066a7f7fffc3adad # fsnotify -github.com/fsnotify/fsnotify 4da3e2cfbabc9f751898f250b49f2439785783a1 +github.com/fsnotify/fsnotify v1.4.7 # awslogs deps github.com/aws/aws-sdk-go v1.12.66 @@ -114,22 +114,22 @@ github.com/googleapis/gax-go v2.0.0 google.golang.org/genproto 694d95ba50e67b2e363f3483057db5d4910c18f9 # containerd -github.com/containerd/containerd 08f7ee9828af1783dc98cc5cc1739e915697c667 +github.com/containerd/containerd b41633746ed4833f52c3c071e8edcfa2713e5677 github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c -github.com/containerd/continuity d3c23511c1bf5851696cba83143d9cbcd666869b +github.com/containerd/continuity 0377f7d767206f3a9e8881d0f02267b0d89c7a62 github.com/containerd/cgroups fe281dd265766145e943a034aa41086474ea6130 -github.com/containerd/console 9290d21dc56074581f619579c43d970b4514bc08 +github.com/containerd/console 5d1b48d6114b8c9666f0c8b916f871af97b0a761 github.com/containerd/go-runc f271fa2021de855d4d918dbef83c5fe19db1bdd -github.com/containerd/typeurl f6943554a7e7e88b3c14aad190bf05932da84788 -github.com/stevvooe/ttrpc d4528379866b0ce7e9d71f3eb96f0582fc374577 +github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40 +github.com/containerd/ttrpc 94dde388801693c54f88a6596f713b51a8b30b2d github.com/gogo/googleapis 08a7655d27152912db7aaf4f983275eaf8d128ef # cluster -github.com/docker/swarmkit edd5641391926a50bc5f7040e20b7efc05003c26 +github.com/docker/swarmkit 68266392a176434d282760d2d6d0ab4c68edcae6 github.com/gogo/protobuf v1.0.0 -github.com/cloudflare/cfssl 7fb22c8cba7ecaf98e4082d22d65800cf45e042a +github.com/cloudflare/cfssl 1.3.2 github.com/fernet/fernet-go 1b2437bc582b3cfbb341ee5a29f8ef5b42912ff2 -github.com/google/certificate-transparency d90e65c3a07988180c5b1ece71791c0b6506826e +github.com/google/certificate-transparency-go v1.0.20 golang.org/x/crypto 1a580b3eff7814fc9b40602fd35256c63b50f491 golang.org/x/time a4bde12657593d5e90d0533a3e4fd95e635124cb github.com/hashicorp/go-memdb cb9a474f84cc5e41b273b20c6927680b2a8776ad