Skip to content

Commit 772edd0

Browse files
committed
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 <[email protected]>
1 parent 9769ef3 commit 772edd0

3 files changed

Lines changed: 120 additions & 40 deletions

File tree

client/client.go

Lines changed: 114 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,14 @@ func CheckRedirect(req *http.Request, via []*http.Request) error {
107107
// Use DOCKER_API_VERSION to set the version of the API to reach, leave empty for latest.
108108
// Use DOCKER_CERT_PATH to load the TLS certificates from.
109109
// Use DOCKER_TLS_VERIFY to enable or disable TLS verification, off by default.
110+
// deprecated: use NewClientWithOpts(FromEnv)
110111
func NewEnvClient() (*Client, error) {
111-
var client *http.Client
112+
return NewClientWithOpts(FromEnv)
113+
}
114+
115+
// FromEnv enhance the default client with values from environment variables
116+
func FromEnv(c *Client) error {
117+
var httpClient *http.Client
112118
if dockerCertPath := os.Getenv("DOCKER_CERT_PATH"); dockerCertPath != "" {
113119
options := tlsconfig.Options{
114120
CAFile: filepath.Join(dockerCertPath, "ca.pem"),
@@ -118,10 +124,10 @@ func NewEnvClient() (*Client, error) {
118124
}
119125
tlsc, err := tlsconfig.Client(options)
120126
if err != nil {
121-
return nil, err
127+
return err
122128
}
123129

124-
client = &http.Client{
130+
httpClient = &http.Client{
125131
Transport: &http.Transport{
126132
TLSClientConfig: tlsc,
127133
},
@@ -130,74 +136,142 @@ func NewEnvClient() (*Client, error) {
130136
}
131137

132138
host := os.Getenv("DOCKER_HOST")
133-
if host == "" {
134-
host = DefaultDockerHost
139+
if host != "" {
140+
var err error
141+
if err := WithHost(host)(c); err != nil {
142+
return err
143+
}
144+
httpClient, err = defaultHTTPClient(host)
145+
if err != nil {
146+
return err
147+
}
148+
}
149+
if httpClient != nil {
150+
if err := WithHTTPClient(httpClient)(c); err != nil {
151+
return err
152+
}
135153
}
136154
version := os.Getenv("DOCKER_API_VERSION")
137-
if version == "" {
138-
version = api.DefaultVersion
155+
if version != "" {
156+
c.version = version
157+
c.manualOverride = true
139158
}
159+
return nil
160+
}
140161

141-
cli, err := NewClient(host, version, client, nil)
142-
if err != nil {
143-
return cli, err
162+
// WithVersion overrides the client version with the specified one
163+
func WithVersion(version string) func(*Client) error {
164+
return func(c *Client) error {
165+
c.version = version
166+
return nil
144167
}
145-
if os.Getenv("DOCKER_API_VERSION") != "" {
146-
cli.manualOverride = true
168+
}
169+
170+
// WithHost overrides the client host with the specified one
171+
func WithHost(host string) func(*Client) error {
172+
return func(c *Client) error {
173+
hostURL, err := ParseHostURL(host)
174+
if err != nil {
175+
return err
176+
}
177+
c.host = host
178+
c.proto = hostURL.Scheme
179+
c.addr = hostURL.Host
180+
c.basePath = hostURL.Path
181+
client, err := defaultHTTPClient(host)
182+
if err != nil {
183+
return err
184+
}
185+
return WithHTTPClient(client)(c)
147186
}
148-
return cli, nil
149187
}
150188

151-
// NewClient initializes a new API client for the given host and API version.
152-
// It uses the given http client as transport.
189+
// WithHTTPClient overrides the client http client with the specified one
190+
func WithHTTPClient(client *http.Client) func(*Client) error {
191+
return func(c *Client) error {
192+
if client != nil {
193+
c.client = client
194+
}
195+
return nil
196+
}
197+
}
198+
199+
// WithHTTPHeaders overrides the client default http headers
200+
func WithHTTPHeaders(headers map[string]string) func(*Client) error {
201+
return func(c *Client) error {
202+
c.customHTTPHeaders = headers
203+
return nil
204+
}
205+
}
206+
207+
// NewClientWithOpts initializes a new API client with default values. It takes functors
208+
// to modify values when creating it, like `NewClientWithOpts(WithVersion(…))`
153209
// It also initializes the custom http headers to add to each request.
154210
//
155211
// It won't send any version information if the version number is empty. It is
156212
// highly recommended that you set a version or your client may break if the
157213
// server is upgraded.
158-
func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) {
159-
hostURL, err := ParseHostURL(host)
214+
func NewClientWithOpts(ops ...func(*Client) error) (*Client, error) {
215+
client, err := defaultHTTPClient(DefaultDockerHost)
160216
if err != nil {
161217
return nil, err
162218
}
219+
c := &Client{
220+
host: DefaultDockerHost,
221+
version: api.DefaultVersion,
222+
scheme: "http",
223+
client: client,
224+
proto: defaultProto,
225+
addr: defaultAddr,
226+
}
163227

164-
if client != nil {
165-
if _, ok := client.Transport.(http.RoundTripper); !ok {
166-
return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", client.Transport)
167-
}
168-
} else {
169-
transport := new(http.Transport)
170-
sockets.ConfigureTransport(transport, hostURL.Scheme, hostURL.Host)
171-
client = &http.Client{
172-
Transport: transport,
173-
CheckRedirect: CheckRedirect,
228+
for _, op := range ops {
229+
if err := op(c); err != nil {
230+
return nil, err
174231
}
175232
}
176233

177-
scheme := "http"
178-
tlsConfig := resolveTLSConfig(client.Transport)
234+
if _, ok := c.client.Transport.(http.RoundTripper); !ok {
235+
return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", c.client.Transport)
236+
}
237+
tlsConfig := resolveTLSConfig(c.client.Transport)
179238
if tlsConfig != nil {
180239
// TODO(stevvooe): This isn't really the right way to write clients in Go.
181240
// `NewClient` should probably only take an `*http.Client` and work from there.
182241
// Unfortunately, the model of having a host-ish/url-thingy as the connection
183242
// string has us confusing protocol and transport layers. We continue doing
184243
// this to avoid breaking existing clients but this should be addressed.
185-
scheme = "https"
244+
c.scheme = "https"
186245
}
187246

188-
// TODO: store URL instead of proto/addr/basePath
189-
return &Client{
190-
scheme: scheme,
191-
host: host,
192-
proto: hostURL.Scheme,
193-
addr: hostURL.Host,
194-
basePath: hostURL.Path,
195-
client: client,
196-
version: version,
197-
customHTTPHeaders: httpHeaders,
247+
return c, nil
248+
}
249+
250+
func defaultHTTPClient(host string) (*http.Client, error) {
251+
url, err := ParseHostURL(host)
252+
if err != nil {
253+
return nil, err
254+
}
255+
transport := new(http.Transport)
256+
sockets.ConfigureTransport(transport, url.Scheme, url.Host)
257+
return &http.Client{
258+
Transport: transport,
259+
CheckRedirect: CheckRedirect,
198260
}, nil
199261
}
200262

263+
// NewClient initializes a new API client for the given host and API version.
264+
// It uses the given http client as transport.
265+
// It also initializes the custom http headers to add to each request.
266+
//
267+
// It won't send any version information if the version number is empty. It is
268+
// highly recommended that you set a version or your client may break if the
269+
// server is upgraded.
270+
// deprecated: use NewClientWithOpts
271+
func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) {
272+
return NewClientWithOpts(WithHost(host), WithVersion(version), WithHTTPClient(client), WithHTTPHeaders(httpHeaders))
273+
}
274+
201275
// Close the transport used by the client
202276
func (cli *Client) Close() error {
203277
if t, ok := cli.client.Transport.(*http.Transport); ok {

client/client_unix.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ package client // import "github.com/docker/docker/client"
44

55
// DefaultDockerHost defines os specific default if DOCKER_HOST is unset
66
const DefaultDockerHost = "unix:///var/run/docker.sock"
7+
8+
const defaultProto = "unix"
9+
const defaultAddr = "/var/run/docker.sock"

client/client_windows.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@ package client // import "github.com/docker/docker/client"
22

33
// DefaultDockerHost defines os specific default if DOCKER_HOST is unset
44
const DefaultDockerHost = "npipe:////./pipe/docker_engine"
5+
6+
const defaultProto = "npipe"
7+
const defaultAddr = "//./pipe/docker_engine"

0 commit comments

Comments
 (0)