support SSH connection

e.g. docker -H ssh://me@server

The `docker` CLI also needs to be installed on the remote host to
provide `docker system dial-stdio`, which proxies the daemon socket to stdio.

Please refer to docs/reference/commandline/dockerd.md .

Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
This commit is contained in:
Akihiro Suda 2018-02-19 18:31:29 +09:00
parent 261ff66d61
commit 6f61cf053a
21 changed files with 644 additions and 37 deletions

View File

@ -14,6 +14,7 @@ import (
"github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config"
cliconfig "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/config/configfile"
"github.com/docker/cli/cli/connhelper"
cliflags "github.com/docker/cli/cli/flags" cliflags "github.com/docker/cli/cli/flags"
manifeststore "github.com/docker/cli/cli/manifest/store" manifeststore "github.com/docker/cli/cli/manifest/store"
registryclient "github.com/docker/cli/cli/registry/client" 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 // NewAPIClientFromFlags creates a new APIClient from command line flags
func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.ConfigFile) (client.APIClient, error) { 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 { if err != nil {
return &client.Client{}, err 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 customHeaders := configFile.HTTPHeaders
if customHeaders == nil { if customHeaders == nil {
customHeaders = map[string]string{} customHeaders = map[string]string{}
} }
customHeaders["User-Agent"] = UserAgent() customHeaders["User-Agent"] = UserAgent()
clientOpts = append(clientOpts, client.WithHTTPHeaders(customHeaders))
verStr := api.DefaultVersion verStr := api.DefaultVersion
if tmpStr := os.Getenv("DOCKER_API_VERSION"); tmpStr != "" { if tmpStr := os.Getenv("DOCKER_API_VERSION"); tmpStr != "" {
verStr = tmpStr verStr = tmpStr
} }
clientOpts = append(clientOpts, client.WithVersion(verStr))
return client.NewClientWithOpts( return client.NewClientWithOpts(clientOpts...)
withHTTPClient(opts.TLSOptions),
client.WithHTTPHeaders(customHeaders),
client.WithVersion(verStr),
client.WithHost(host),
)
} }
func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (string, error) { func getUnparsedServerHost(hosts []string) (string, error) {
var host string var host string
switch len(hosts) { switch len(hosts) {
case 0: case 0:
@ -282,8 +306,7 @@ func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (string, error
default: default:
return "", errors.New("Please specify only one -H") return "", errors.New("Please specify only one -H")
} }
return host, nil
return dopts.ParseHost(tlsOptions != nil, host)
} }
func withHTTPClient(tlsOpts *tlsconfig.Options) func(*client.Client) error { func withHTTPClient(tlsOpts *tlsconfig.Options) func(*client.Client) error {

View File

@ -19,6 +19,7 @@ func NewSystemCommand(dockerCli command.Cli) *cobra.Command {
NewInfoCommand(dockerCli), NewInfoCommand(dockerCli),
newDiskUsageCommand(dockerCli), newDiskUsageCommand(dockerCli),
newPruneCommand(dockerCli), newPruneCommand(dockerCli),
newDialStdioCommand(dockerCli),
) )
return cmd return cmd

View File

@ -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()
}

View File

@ -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
}

View File

@ -0,0 +1,12 @@
package connhelper
import (
"os/exec"
"syscall"
)
func setPdeathsig(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{
Pdeathsig: syscall.SIGKILL,
}
}

View File

@ -0,0 +1,10 @@
// +build !linux
package connhelper
import (
"os/exec"
)
func setPdeathsig(cmd *exec.Cmd) {
}

View File

@ -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))
}

70
cli/connhelper/ssh/ssh.go Normal file
View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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.CertFile), "tlscert", "Path to TLS certificate file")
flags.Var(opts.NewQuotedString(&tlsOptions.KeyFile), "tlskey", "Path to TLS key 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") flags.VarP(hostOpt, "host", "H", "Daemon socket(s) to connect to")
} }

View File

@ -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 environment variables (or the lowercase versions thereof). `HTTPS_PROXY` takes
precedence over `HTTP_PROXY`. 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 #### Bind Docker to another host/port or a Unix socket
> **Warning**: > **Warning**:

View File

@ -8,7 +8,7 @@ github.com/coreos/etcd v3.3.9
github.com/cpuguy83/go-md2man v1.0.8 github.com/cpuguy83/go-md2man v1.0.8
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76 # v1.1.0 github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76 # v1.1.0
github.com/docker/distribution 83389a148052d74ac602f5f1d62f86ff2f3c4aa5 github.com/docker/distribution 83389a148052d74ac602f5f1d62f86ff2f3c4aa5
github.com/docker/docker b711437bbd8596312c962d4189e9ad4d2108c2dc github.com/docker/docker 562df8c2d6f48601c8d1df7256389569d25c0bf1
github.com/docker/docker-credential-helpers 5241b46610f2491efdf9d1c85f1ddf5b02f6d962 github.com/docker/docker-credential-helpers 5241b46610f2491efdf9d1c85f1ddf5b02f6d962
# the docker/go package contains a customized version of canonical/json # 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. # and is used by Notary. The package is periodically rebased on current Go versions.

View File

@ -7,7 +7,7 @@ package volume
// See hack/generate-swagger-api.sh // See hack/generate-swagger-api.sh
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// VolumeCreateBody // VolumeCreateBody Volume configuration
// swagger:model VolumeCreateBody // swagger:model VolumeCreateBody
type VolumeCreateBody struct { type VolumeCreateBody struct {

View File

@ -9,7 +9,7 @@ package volume
import "github.com/docker/docker/api/types" import "github.com/docker/docker/api/types"
// VolumeListOKBody // VolumeListOKBody Volume list response
// swagger:model VolumeListOKBody // swagger:model VolumeListOKBody
type VolumeListOKBody struct { type VolumeListOKBody struct {

View File

@ -1,9 +1,8 @@
package client // import "github.com/docker/docker/client" package client // import "github.com/docker/docker/client"
import ( import (
"context"
"net/url" "net/url"
"golang.org/x/net/context"
) )
// BuildCancel requests the daemon to cancel ongoing build request // BuildCancel requests the daemon to cancel ongoing build request

View File

@ -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 // WithDialer applies the dialer.DialContext to the client transport. This can be
// used to set the Timeout and KeepAlive settings of the client. // used to set the Timeout and KeepAlive settings of the client.
// Deprecated: use WithDialContext
func WithDialer(dialer *net.Dialer) func(*Client) error { 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 { return func(c *Client) error {
if transport, ok := c.client.Transport.(*http.Transport); ok { if transport, ok := c.client.Transport.(*http.Transport); ok {
transport.DialContext = dialer.DialContext transport.DialContext = dialContext
return nil return nil
} }
return errors.Errorf("cannot apply dialer to transport: %T", c.client.Transport) 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) { func (cli *Client) SetCustomHTTPHeaders(headers map[string]string) {
cli.customHTTPHeaders = headers 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))
}
}

View File

@ -30,7 +30,7 @@ func (cli *Client) postHijacked(ctx context.Context, path string, query url.Valu
} }
req = cli.addHeaders(req, headers) req = cli.addHeaders(req, headers)
conn, err := cli.setupHijackConn(req, "tcp") conn, err := cli.setupHijackConn(ctx, req, "tcp")
if err != nil { if err != nil {
return types.HijackedResponse{}, err 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 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" { if tlsConfig != nil && proto != "unix" && proto != "npipe" {
return tls.Dial(proto, addr, tlsConfig) 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) 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.Host = cli.addr
req.Header.Set("Connection", "Upgrade") req.Header.Set("Connection", "Upgrade")
req.Header.Set("Upgrade", proto) 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 { if err != nil {
return nil, errors.Wrap(err, "cannot connect to the Docker daemon. Is 'docker daemon' running on this host?") return nil, errors.Wrap(err, "cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
} }

View File

@ -39,6 +39,7 @@ type CommonAPIClient interface {
NegotiateAPIVersion(ctx context.Context) NegotiateAPIVersion(ctx context.Context)
NegotiateAPIVersionPing(types.Ping) NegotiateAPIVersionPing(types.Ping)
DialSession(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) DialSession(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error)
Dialer() func(context.Context) (net.Conn, error)
Close() error Close() error
} }

View File

@ -14,5 +14,5 @@ func (cli *Client) DialSession(ctx context.Context, proto string, meta map[strin
} }
req = cli.addHeaders(req, meta) req = cli.addHeaders(req, meta)
return cli.setupHijackConn(req, proto) return cli.setupHijackConn(ctx, req, proto)
} }

View File

@ -34,7 +34,7 @@ func EnsureRemoveAll(dir string) error {
for { for {
err := os.RemoveAll(dir) err := os.RemoveAll(dir)
if err == nil { if err == nil {
return err return nil
} }
pe, ok := err.(*os.PathError) pe, ok := err.(*os.PathError)

View File

@ -1,13 +1,13 @@
# the following lines are in sorted order, FYI # the following lines are in sorted order, FYI
github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109 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/Microsoft/go-winio v0.4.8
github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a
github.com/go-check/check 4ed411733c5785b40214c70bce814c3a3a689609 https://github.com/cpuguy83/check.git github.com/go-check/check 4ed411733c5785b40214c70bce814c3a3a689609 https://github.com/cpuguy83/check.git
github.com/golang/gddo 9b12a26f3fbd7397dee4e20939ddca719d840d2a github.com/golang/gddo 9b12a26f3fbd7397dee4e20939ddca719d840d2a
github.com/gorilla/context v1.1 github.com/gorilla/context v1.1
github.com/gorilla/mux 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/kr/pty 5cf931ef8f
github.com/mattn/go-shellwords v1.0.3 github.com/mattn/go-shellwords v1.0.3
github.com/sirupsen/logrus 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/google/go-cmp v0.2.0
github.com/RackSec/srslog 456df3a81436d29ba874f3590eeeee25d666f8a5 github.com/RackSec/srslog 456df3a81436d29ba874f3590eeeee25d666f8a5
github.com/imdario/mergo v0.3.5 github.com/imdario/mergo v0.3.6
golang.org/x/sync fd80eb99c8f653c847d294a001bdf2a3a6f768f5 golang.org/x/sync fd80eb99c8f653c847d294a001bdf2a3a6f768f5
# buildkit # buildkit
github.com/moby/buildkit 9acf51e49185b348608e0096b2903dd72907adcb github.com/moby/buildkit 98f1604134f945d48538ffca0e18662337b4a850
github.com/tonistiigi/fsutil 8abad97ee3969cdf5e9c367f46adba2c212b3ddb github.com/tonistiigi/fsutil 8abad97ee3969cdf5e9c367f46adba2c212b3ddb
github.com/grpc-ecosystem/grpc-opentracing 8e809c8a86450a29b90dcc9efbf062d0fe6d9746 github.com/grpc-ecosystem/grpc-opentracing 8e809c8a86450a29b90dcc9efbf062d0fe6d9746
github.com/opentracing/opentracing-go 1361b9cd60be79c4c3a7fa9841b3c132e40066a7 github.com/opentracing/opentracing-go 1361b9cd60be79c4c3a7fa9841b3c132e40066a7
@ -37,7 +37,7 @@ github.com/mitchellh/hashstructure 2bca23e0e452137f789efbc8610126fd8b94f73b
#get libnetwork packages #get libnetwork packages
# When updating, also update LIBNETWORK_COMMIT in hack/dockerfile/install/proxy accordingly # 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/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80 github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80
github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec
@ -95,7 +95,7 @@ github.com/philhofer/fwd 98c11a7a6ec829d672b03833c3d69a7fae1ca972
github.com/tinylib/msgp 3b556c64540842d4f82967be066a7f7fffc3adad github.com/tinylib/msgp 3b556c64540842d4f82967be066a7f7fffc3adad
# fsnotify # fsnotify
github.com/fsnotify/fsnotify 4da3e2cfbabc9f751898f250b49f2439785783a1 github.com/fsnotify/fsnotify v1.4.7
# awslogs deps # awslogs deps
github.com/aws/aws-sdk-go v1.12.66 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 google.golang.org/genproto 694d95ba50e67b2e363f3483057db5d4910c18f9
# containerd # containerd
github.com/containerd/containerd 08f7ee9828af1783dc98cc5cc1739e915697c667 github.com/containerd/containerd b41633746ed4833f52c3c071e8edcfa2713e5677
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
github.com/containerd/continuity d3c23511c1bf5851696cba83143d9cbcd666869b github.com/containerd/continuity 0377f7d767206f3a9e8881d0f02267b0d89c7a62
github.com/containerd/cgroups fe281dd265766145e943a034aa41086474ea6130 github.com/containerd/cgroups fe281dd265766145e943a034aa41086474ea6130
github.com/containerd/console 9290d21dc56074581f619579c43d970b4514bc08 github.com/containerd/console 5d1b48d6114b8c9666f0c8b916f871af97b0a761
github.com/containerd/go-runc f271fa2021de855d4d918dbef83c5fe19db1bdd github.com/containerd/go-runc f271fa2021de855d4d918dbef83c5fe19db1bdd
github.com/containerd/typeurl f6943554a7e7e88b3c14aad190bf05932da84788 github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
github.com/stevvooe/ttrpc d4528379866b0ce7e9d71f3eb96f0582fc374577 github.com/containerd/ttrpc 94dde388801693c54f88a6596f713b51a8b30b2d
github.com/gogo/googleapis 08a7655d27152912db7aaf4f983275eaf8d128ef github.com/gogo/googleapis 08a7655d27152912db7aaf4f983275eaf8d128ef
# cluster # cluster
github.com/docker/swarmkit edd5641391926a50bc5f7040e20b7efc05003c26 github.com/docker/swarmkit 68266392a176434d282760d2d6d0ab4c68edcae6
github.com/gogo/protobuf v1.0.0 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/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/crypto 1a580b3eff7814fc9b40602fd35256c63b50f491
golang.org/x/time a4bde12657593d5e90d0533a3e4fd95e635124cb golang.org/x/time a4bde12657593d5e90d0533a3e4fd95e635124cb
github.com/hashicorp/go-memdb cb9a474f84cc5e41b273b20c6927680b2a8776ad github.com/hashicorp/go-memdb cb9a474f84cc5e41b273b20c6927680b2a8776ad