diff --git a/api/client/cli.go b/api/client/cli.go index b073c3abc8..31e1572da9 100644 --- a/api/client/cli.go +++ b/api/client/cli.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io" + "net/http" "os" "runtime" @@ -108,7 +109,12 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientF verStr = tmpStr } - client, err := lib.NewClient(host, verStr, clientFlags.Common.TLSOptions, customHeaders) + clientTransport, err := newClientTransport(clientFlags.Common.TLSOptions) + if err != nil { + return err + } + + client, err := lib.NewClient(host, verStr, clientTransport, customHeaders) if err != nil { return err } @@ -145,3 +151,17 @@ func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (host string, host, err = opts.ParseHost(defaultHost, host) return } + +func newClientTransport(tlsOptions *tlsconfig.Options) (*http.Transport, error) { + if tlsOptions == nil { + return &http.Transport{}, nil + } + + config, err := tlsconfig.Client(*tlsOptions) + if err != nil { + return nil, err + } + return &http.Transport{ + TLSClientConfig: config, + }, nil +} diff --git a/api/client/lib/client.go b/api/client/lib/client.go index 2bae2d101e..9e079cbcd4 100644 --- a/api/client/lib/client.go +++ b/api/client/lib/client.go @@ -3,12 +3,13 @@ package lib import ( "crypto/tls" "fmt" + "net" "net/http" "net/url" + "os" + "path/filepath" "strings" - - "github.com/docker/docker/pkg/sockets" - "github.com/docker/docker/pkg/tlsconfig" + "time" ) // Client is the API client that performs all operations @@ -32,11 +33,35 @@ type Client struct { customHTTPHeaders map[string]string } +// NewEnvClient initializes a new API client based on environment variables. +// Use DOCKER_HOST to set the url to the docker server. +// Use DOCKER_API_VERSION to set the version of the API to reach, leave empty for latest. +// Use DOCKER_CERT_PATH to load the tls certificates from. +func NewEnvClient() (*Client, error) { + var transport *http.Transport + if dockerCertPath := os.Getenv("DOCKER_CERT_PATH"); dockerCertPath != "" { + tlsc := &tls.Config{} + + cert, err := tls.LoadX509KeyPair(filepath.Join(dockerCertPath, "cert.pem"), filepath.Join(dockerCertPath, "key.pem")) + if err != nil { + return nil, fmt.Errorf("Error loading x509 key pair: %s", err) + } + + tlsc.Certificates = append(tlsc.Certificates, cert) + tlsc.InsecureSkipVerify = os.Getenv("DOCKER_TLS_VERIFY") == "" + transport = &http.Transport{ + TLSClientConfig: tlsc, + } + } + + return NewClient(os.Getenv("DOCKER_HOST"), os.Getenv("DOCKER_API_VERSION"), transport, nil) +} + // NewClient initializes a new API client for the given host and API version. // It won't send any version information if the version number is empty. -// It uses the tlsOptions to decide whether to use a secure connection or not. +// It uses the transport to create a new http client. // It also initializes the custom http headers to add to each request. -func NewClient(host string, version string, tlsOptions *tlsconfig.Options, httpHeaders map[string]string) (*Client, error) { +func NewClient(host string, version string, transport *http.Transport, httpHeaders map[string]string) (*Client, error) { var ( basePath string tlsConfig *tls.Config @@ -54,21 +79,11 @@ func NewClient(host string, version string, tlsOptions *tlsconfig.Options, httpH basePath = parsed.Path } - if tlsOptions != nil { + transport = configureTransport(transport, proto, addr) + if transport.TLSClientConfig != nil { scheme = "https" - var err error - tlsConfig, err = tlsconfig.Client(*tlsOptions) - if err != nil { - return nil, err - } } - // The transport is created here for reuse during the client session. - transport := &http.Transport{ - TLSClientConfig: tlsConfig, - } - sockets.ConfigureTCPTransport(transport, proto, addr) - return &Client{ proto: proto, addr: addr, @@ -103,3 +118,24 @@ func (cli *Client) getAPIPath(p string, query url.Values) string { func (cli *Client) ClientVersion() string { return cli.version } + +func configureTransport(tr *http.Transport, proto, addr string) *http.Transport { + if tr == nil { + tr = &http.Transport{} + } + + // Why 32? See https://github.com/docker/docker/pull/8035. + timeout := 32 * time.Second + if proto == "unix" { + // No need for compression in local communications. + tr.DisableCompression = true + tr.Dial = func(_, _ string) (net.Conn, error) { + return net.DialTimeout(proto, addr, timeout) + } + } else { + tr.Proxy = http.ProxyFromEnvironment + tr.Dial = (&net.Dialer{Timeout: timeout}).Dial + } + + return tr +}