Forráskód Böngészése

Introduce NewClientWithOpts func to build custom client easily

This allows to create a client with default values and override those
using functors. As an example, `NewEnvClient()` becomes
`NewClientWithOpts(FromEnv)` ; and if you want a different api version
for this client : `NewClientWithOpts(FromEnv, WithVersion("1.35"))`

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
Vincent Demeester 7 éve
szülő
commit
772edd020c
3 módosított fájl, 120 hozzáadás és 40 törlés
  1. 114 40
      client/client.go
  2. 3 0
      client/client_unix.go
  3. 3 0
      client/client_windows.go

+ 114 - 40
client/client.go

@@ -107,8 +107,14 @@ func CheckRedirect(req *http.Request, via []*http.Request) error {
 // 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.
 // Use DOCKER_TLS_VERIFY to enable or disable TLS verification, off by default.
+// deprecated: use NewClientWithOpts(FromEnv)
 func NewEnvClient() (*Client, error) {
-	var client *http.Client
+	return NewClientWithOpts(FromEnv)
+}
+
+// FromEnv enhance the default client with values from environment variables
+func FromEnv(c *Client) error {
+	var httpClient *http.Client
 	if dockerCertPath := os.Getenv("DOCKER_CERT_PATH"); dockerCertPath != "" {
 		options := tlsconfig.Options{
 			CAFile:             filepath.Join(dockerCertPath, "ca.pem"),
@@ -118,10 +124,10 @@ func NewEnvClient() (*Client, error) {
 		}
 		tlsc, err := tlsconfig.Client(options)
 		if err != nil {
-			return nil, err
+			return err
 		}
 
-		client = &http.Client{
+		httpClient = &http.Client{
 			Transport: &http.Transport{
 				TLSClientConfig: tlsc,
 			},
@@ -130,74 +136,142 @@ func NewEnvClient() (*Client, error) {
 	}
 
 	host := os.Getenv("DOCKER_HOST")
-	if host == "" {
-		host = DefaultDockerHost
+	if host != "" {
+		var err error
+		if err := WithHost(host)(c); err != nil {
+			return err
+		}
+		httpClient, err = defaultHTTPClient(host)
+		if err != nil {
+			return err
+		}
+	}
+	if httpClient != nil {
+		if err := WithHTTPClient(httpClient)(c); err != nil {
+			return err
+		}
 	}
 	version := os.Getenv("DOCKER_API_VERSION")
-	if version == "" {
-		version = api.DefaultVersion
+	if version != "" {
+		c.version = version
+		c.manualOverride = true
 	}
+	return nil
+}
 
-	cli, err := NewClient(host, version, client, nil)
-	if err != nil {
-		return cli, err
+// WithVersion overrides the client version with the specified one
+func WithVersion(version string) func(*Client) error {
+	return func(c *Client) error {
+		c.version = version
+		return nil
 	}
-	if os.Getenv("DOCKER_API_VERSION") != "" {
-		cli.manualOverride = true
+}
+
+// WithHost overrides the client host with the specified one
+func WithHost(host string) func(*Client) error {
+	return func(c *Client) error {
+		hostURL, err := ParseHostURL(host)
+		if err != nil {
+			return err
+		}
+		c.host = host
+		c.proto = hostURL.Scheme
+		c.addr = hostURL.Host
+		c.basePath = hostURL.Path
+		client, err := defaultHTTPClient(host)
+		if err != nil {
+			return err
+		}
+		return WithHTTPClient(client)(c)
 	}
-	return cli, nil
 }
 
-// NewClient initializes a new API client for the given host and API version.
-// It uses the given http client as transport.
+// WithHTTPClient overrides the client http client with the specified one
+func WithHTTPClient(client *http.Client) func(*Client) error {
+	return func(c *Client) error {
+		if client != nil {
+			c.client = client
+		}
+		return nil
+	}
+}
+
+// WithHTTPHeaders overrides the client default http headers
+func WithHTTPHeaders(headers map[string]string) func(*Client) error {
+	return func(c *Client) error {
+		c.customHTTPHeaders = headers
+		return nil
+	}
+}
+
+// NewClientWithOpts initializes a new API client with default values. It takes functors
+// to modify values when creating it, like `NewClientWithOpts(WithVersion(…))`
 // It also initializes the custom http headers to add to each request.
 //
 // It won't send any version information if the version number is empty. It is
 // highly recommended that you set a version or your client may break if the
 // server is upgraded.
-func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) {
-	hostURL, err := ParseHostURL(host)
+func NewClientWithOpts(ops ...func(*Client) error) (*Client, error) {
+	client, err := defaultHTTPClient(DefaultDockerHost)
 	if err != nil {
 		return nil, err
 	}
+	c := &Client{
+		host:    DefaultDockerHost,
+		version: api.DefaultVersion,
+		scheme:  "http",
+		client:  client,
+		proto:   defaultProto,
+		addr:    defaultAddr,
+	}
 
-	if client != nil {
-		if _, ok := client.Transport.(http.RoundTripper); !ok {
-			return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", client.Transport)
-		}
-	} else {
-		transport := new(http.Transport)
-		sockets.ConfigureTransport(transport, hostURL.Scheme, hostURL.Host)
-		client = &http.Client{
-			Transport:     transport,
-			CheckRedirect: CheckRedirect,
+	for _, op := range ops {
+		if err := op(c); err != nil {
+			return nil, err
 		}
 	}
 
-	scheme := "http"
-	tlsConfig := resolveTLSConfig(client.Transport)
+	if _, ok := c.client.Transport.(http.RoundTripper); !ok {
+		return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", c.client.Transport)
+	}
+	tlsConfig := resolveTLSConfig(c.client.Transport)
 	if tlsConfig != nil {
 		// TODO(stevvooe): This isn't really the right way to write clients in Go.
 		// `NewClient` should probably only take an `*http.Client` and work from there.
 		// Unfortunately, the model of having a host-ish/url-thingy as the connection
 		// string has us confusing protocol and transport layers. We continue doing
 		// this to avoid breaking existing clients but this should be addressed.
-		scheme = "https"
+		c.scheme = "https"
 	}
 
-	// TODO: store URL instead of proto/addr/basePath
-	return &Client{
-		scheme:            scheme,
-		host:              host,
-		proto:             hostURL.Scheme,
-		addr:              hostURL.Host,
-		basePath:          hostURL.Path,
-		client:            client,
-		version:           version,
-		customHTTPHeaders: httpHeaders,
+	return c, nil
+}
+
+func defaultHTTPClient(host string) (*http.Client, error) {
+	url, err := ParseHostURL(host)
+	if err != nil {
+		return nil, err
+	}
+	transport := new(http.Transport)
+	sockets.ConfigureTransport(transport, url.Scheme, url.Host)
+	return &http.Client{
+		Transport:     transport,
+		CheckRedirect: CheckRedirect,
 	}, nil
 }
 
+// NewClient initializes a new API client for the given host and API version.
+// It uses the given http client as transport.
+// It also initializes the custom http headers to add to each request.
+//
+// It won't send any version information if the version number is empty. It is
+// highly recommended that you set a version or your client may break if the
+// server is upgraded.
+// deprecated: use NewClientWithOpts
+func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) {
+	return NewClientWithOpts(WithHost(host), WithVersion(version), WithHTTPClient(client), WithHTTPHeaders(httpHeaders))
+}
+
 // Close the transport used by the client
 func (cli *Client) Close() error {
 	if t, ok := cli.client.Transport.(*http.Transport); ok {

+ 3 - 0
client/client_unix.go

@@ -4,3 +4,6 @@ package client // import "github.com/docker/docker/client"
 
 // DefaultDockerHost defines os specific default if DOCKER_HOST is unset
 const DefaultDockerHost = "unix:///var/run/docker.sock"
+
+const defaultProto = "unix"
+const defaultAddr = "/var/run/docker.sock"

+ 3 - 0
client/client_windows.go

@@ -2,3 +2,6 @@ package client // import "github.com/docker/docker/client"
 
 // DefaultDockerHost defines os specific default if DOCKER_HOST is unset
 const DefaultDockerHost = "npipe:////./pipe/docker_engine"
+
+const defaultProto = "npipe"
+const defaultAddr = "//./pipe/docker_engine"