Browse code

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 authored on 2018/02/01 04:28:33
Showing 3 changed files
... ...
@@ -107,8 +107,14 @@ func CheckRedirect(req *http.Request, via []*http.Request) error {
107 107
 // Use DOCKER_API_VERSION to set the version of the API to reach, leave empty for latest.
108 108
 // Use DOCKER_CERT_PATH to load the TLS certificates from.
109 109
 // Use DOCKER_TLS_VERIFY to enable or disable TLS verification, off by default.
110
+// deprecated: use NewClientWithOpts(FromEnv)
110 111
 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
112 118
 	if dockerCertPath := os.Getenv("DOCKER_CERT_PATH"); dockerCertPath != "" {
113 119
 		options := tlsconfig.Options{
114 120
 			CAFile:             filepath.Join(dockerCertPath, "ca.pem"),
... ...
@@ -118,10 +124,10 @@ func NewEnvClient() (*Client, error) {
118 118
 		}
119 119
 		tlsc, err := tlsconfig.Client(options)
120 120
 		if err != nil {
121
-			return nil, err
121
+			return err
122 122
 		}
123 123
 
124
-		client = &http.Client{
124
+		httpClient = &http.Client{
125 125
 			Transport: &http.Transport{
126 126
 				TLSClientConfig: tlsc,
127 127
 			},
... ...
@@ -130,74 +136,142 @@ func NewEnvClient() (*Client, error) {
130 130
 	}
131 131
 
132 132
 	host := os.Getenv("DOCKER_HOST")
133
-	if host == "" {
134
-		host = DefaultDockerHost
133
+	if host != "" {
134
+		var err error
135
+		if err := WithHost(host)(c); err != nil {
136
+			return err
137
+		}
138
+		httpClient, err = defaultHTTPClient(host)
139
+		if err != nil {
140
+			return err
141
+		}
142
+	}
143
+	if httpClient != nil {
144
+		if err := WithHTTPClient(httpClient)(c); err != nil {
145
+			return err
146
+		}
135 147
 	}
136 148
 	version := os.Getenv("DOCKER_API_VERSION")
137
-	if version == "" {
138
-		version = api.DefaultVersion
149
+	if version != "" {
150
+		c.version = version
151
+		c.manualOverride = true
139 152
 	}
153
+	return nil
154
+}
140 155
 
141
-	cli, err := NewClient(host, version, client, nil)
142
-	if err != nil {
143
-		return cli, err
156
+// WithVersion overrides the client version with the specified one
157
+func WithVersion(version string) func(*Client) error {
158
+	return func(c *Client) error {
159
+		c.version = version
160
+		return nil
144 161
 	}
145
-	if os.Getenv("DOCKER_API_VERSION") != "" {
146
-		cli.manualOverride = true
162
+}
163
+
164
+// WithHost overrides the client host with the specified one
165
+func WithHost(host string) func(*Client) error {
166
+	return func(c *Client) error {
167
+		hostURL, err := ParseHostURL(host)
168
+		if err != nil {
169
+			return err
170
+		}
171
+		c.host = host
172
+		c.proto = hostURL.Scheme
173
+		c.addr = hostURL.Host
174
+		c.basePath = hostURL.Path
175
+		client, err := defaultHTTPClient(host)
176
+		if err != nil {
177
+			return err
178
+		}
179
+		return WithHTTPClient(client)(c)
147 180
 	}
148
-	return cli, nil
149 181
 }
150 182
 
151
-// NewClient initializes a new API client for the given host and API version.
152
-// It uses the given http client as transport.
183
+// WithHTTPClient overrides the client http client with the specified one
184
+func WithHTTPClient(client *http.Client) func(*Client) error {
185
+	return func(c *Client) error {
186
+		if client != nil {
187
+			c.client = client
188
+		}
189
+		return nil
190
+	}
191
+}
192
+
193
+// WithHTTPHeaders overrides the client default http headers
194
+func WithHTTPHeaders(headers map[string]string) func(*Client) error {
195
+	return func(c *Client) error {
196
+		c.customHTTPHeaders = headers
197
+		return nil
198
+	}
199
+}
200
+
201
+// NewClientWithOpts initializes a new API client with default values. It takes functors
202
+// to modify values when creating it, like `NewClientWithOpts(WithVersion(…))`
153 203
 // It also initializes the custom http headers to add to each request.
154 204
 //
155 205
 // It won't send any version information if the version number is empty. It is
156 206
 // highly recommended that you set a version or your client may break if the
157 207
 // server is upgraded.
158
-func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) {
159
-	hostURL, err := ParseHostURL(host)
208
+func NewClientWithOpts(ops ...func(*Client) error) (*Client, error) {
209
+	client, err := defaultHTTPClient(DefaultDockerHost)
160 210
 	if err != nil {
161 211
 		return nil, err
162 212
 	}
213
+	c := &Client{
214
+		host:    DefaultDockerHost,
215
+		version: api.DefaultVersion,
216
+		scheme:  "http",
217
+		client:  client,
218
+		proto:   defaultProto,
219
+		addr:    defaultAddr,
220
+	}
163 221
 
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,
222
+	for _, op := range ops {
223
+		if err := op(c); err != nil {
224
+			return nil, err
174 225
 		}
175 226
 	}
176 227
 
177
-	scheme := "http"
178
-	tlsConfig := resolveTLSConfig(client.Transport)
228
+	if _, ok := c.client.Transport.(http.RoundTripper); !ok {
229
+		return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", c.client.Transport)
230
+	}
231
+	tlsConfig := resolveTLSConfig(c.client.Transport)
179 232
 	if tlsConfig != nil {
180 233
 		// TODO(stevvooe): This isn't really the right way to write clients in Go.
181 234
 		// `NewClient` should probably only take an `*http.Client` and work from there.
182 235
 		// Unfortunately, the model of having a host-ish/url-thingy as the connection
183 236
 		// string has us confusing protocol and transport layers. We continue doing
184 237
 		// this to avoid breaking existing clients but this should be addressed.
185
-		scheme = "https"
238
+		c.scheme = "https"
186 239
 	}
187 240
 
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,
241
+	return c, nil
242
+}
243
+
244
+func defaultHTTPClient(host string) (*http.Client, error) {
245
+	url, err := ParseHostURL(host)
246
+	if err != nil {
247
+		return nil, err
248
+	}
249
+	transport := new(http.Transport)
250
+	sockets.ConfigureTransport(transport, url.Scheme, url.Host)
251
+	return &http.Client{
252
+		Transport:     transport,
253
+		CheckRedirect: CheckRedirect,
198 254
 	}, nil
199 255
 }
200 256
 
257
+// NewClient initializes a new API client for the given host and API version.
258
+// It uses the given http client as transport.
259
+// It also initializes the custom http headers to add to each request.
260
+//
261
+// It won't send any version information if the version number is empty. It is
262
+// highly recommended that you set a version or your client may break if the
263
+// server is upgraded.
264
+// deprecated: use NewClientWithOpts
265
+func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) {
266
+	return NewClientWithOpts(WithHost(host), WithVersion(version), WithHTTPClient(client), WithHTTPHeaders(httpHeaders))
267
+}
268
+
201 269
 // Close the transport used by the client
202 270
 func (cli *Client) Close() error {
203 271
 	if t, ok := cli.client.Transport.(*http.Transport); ok {
... ...
@@ -4,3 +4,6 @@ package client // import "github.com/docker/docker/client"
4 4
 
5 5
 // DefaultDockerHost defines os specific default if DOCKER_HOST is unset
6 6
 const DefaultDockerHost = "unix:///var/run/docker.sock"
7
+
8
+const defaultProto = "unix"
9
+const defaultAddr = "/var/run/docker.sock"
... ...
@@ -2,3 +2,6 @@ package client // import "github.com/docker/docker/client"
2 2
 
3 3
 // DefaultDockerHost defines os specific default if DOCKER_HOST is unset
4 4
 const DefaultDockerHost = "npipe:////./pipe/docker_engine"
5
+
6
+const defaultProto = "npipe"
7
+const defaultAddr = "//./pipe/docker_engine"