Browse code

Cleanup client/ interface

- Remove ParseLogDetails, this is not part of the client. Moved to docker/cli
- Deprecate ParseHost and replace with ParseHostURL
- Deprecate redundant IsErr helpers

Signed-off-by: Daniel Nephin <dnephin@docker.com>

Daniel Nephin authored on 2017/09/08 01:22:11
Showing 5 changed files
... ...
@@ -1,10 +1,6 @@
1 1
 /*
2 2
 Package client is a Go client for the Docker Engine API.
3 3
 
4
-The "docker" command uses this package to communicate with the daemon. It can also
5
-be used by your own Go applications to do anything the command-line interface does
6
-- running containers, pulling images, managing swarms, etc.
7
-
8 4
 For more information about the Engine API, see the documentation:
9 5
 https://docs.docker.com/engine/reference/api/
10 6
 
... ...
@@ -160,7 +156,7 @@ func NewEnvClient() (*Client, error) {
160 160
 // highly recommended that you set a version or your client may break if the
161 161
 // server is upgraded.
162 162
 func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) {
163
-	proto, addr, basePath, err := ParseHost(host)
163
+	hostURL, err := ParseHostURL(host)
164 164
 	if err != nil {
165 165
 		return nil, err
166 166
 	}
... ...
@@ -171,7 +167,7 @@ func NewClient(host string, version string, client *http.Client, httpHeaders map
171 171
 		}
172 172
 	} else {
173 173
 		transport := new(http.Transport)
174
-		sockets.ConfigureTransport(transport, proto, addr)
174
+		sockets.ConfigureTransport(transport, hostURL.Scheme, hostURL.Host)
175 175
 		client = &http.Client{
176 176
 			Transport:     transport,
177 177
 			CheckRedirect: CheckRedirect,
... ...
@@ -189,28 +185,24 @@ func NewClient(host string, version string, client *http.Client, httpHeaders map
189 189
 		scheme = "https"
190 190
 	}
191 191
 
192
+	// TODO: store URL instead of proto/addr/basePath
192 193
 	return &Client{
193 194
 		scheme:            scheme,
194 195
 		host:              host,
195
-		proto:             proto,
196
-		addr:              addr,
197
-		basePath:          basePath,
196
+		proto:             hostURL.Scheme,
197
+		addr:              hostURL.Host,
198
+		basePath:          hostURL.Path,
198 199
 		client:            client,
199 200
 		version:           version,
200 201
 		customHTTPHeaders: httpHeaders,
201 202
 	}, nil
202 203
 }
203 204
 
204
-// Close ensures that transport.Client is closed
205
-// especially needed while using NewClient with *http.Client = nil
206
-// for example
207
-// client.NewClient("unix:///var/run/docker.sock", nil, "v1.18", map[string]string{"User-Agent": "engine-api-cli-1.0"})
205
+// Close the transport used by the client
208 206
 func (cli *Client) Close() error {
209
-
210 207
 	if t, ok := cli.client.Transport.(*http.Transport); ok {
211 208
 		t.CloseIdleConnections()
212 209
 	}
213
-
214 210
 	return nil
215 211
 }
216 212
 
... ...
@@ -234,23 +226,20 @@ func (cli *Client) getAPIPath(p string, query url.Values) string {
234 234
 	return u.String()
235 235
 }
236 236
 
237
-// ClientVersion returns the version string associated with this
238
-// instance of the Client. Note that this value can be changed
239
-// via the DOCKER_API_VERSION env var.
240
-// This operation doesn't acquire a mutex.
237
+// ClientVersion returns the API version used by this client.
241 238
 func (cli *Client) ClientVersion() string {
242 239
 	return cli.version
243 240
 }
244 241
 
245
-// NegotiateAPIVersion updates the version string associated with this
246
-// instance of the Client to match the latest version the server supports
242
+// NegotiateAPIVersion queries the API and updates the version to match the
243
+// API version. Any errors are silently ignored.
247 244
 func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
248 245
 	ping, _ := cli.Ping(ctx)
249 246
 	cli.NegotiateAPIVersionPing(ping)
250 247
 }
251 248
 
252
-// NegotiateAPIVersionPing updates the version string associated with this
253
-// instance of the Client to match the latest version the server supports
249
+// NegotiateAPIVersionPing updates the client version to match the Ping.APIVersion
250
+// if the ping version is less than the default version.
254 251
 func (cli *Client) NegotiateAPIVersionPing(p types.Ping) {
255 252
 	if cli.manualOverride {
256 253
 		return
... ...
@@ -272,17 +261,28 @@ func (cli *Client) NegotiateAPIVersionPing(p types.Ping) {
272 272
 	}
273 273
 }
274 274
 
275
-// DaemonHost returns the host associated with this instance of the Client.
276
-// This operation doesn't acquire a mutex.
275
+// DaemonHost returns the host address used by the client
277 276
 func (cli *Client) DaemonHost() string {
278 277
 	return cli.host
279 278
 }
280 279
 
281
-// ParseHost verifies that the given host strings is valid.
280
+// ParseHost parses a url string, validates the strings is a host url, and returns
281
+// the parsed host as: protocol, address, and base path
282
+// Deprecated: use ParseHostURL
282 283
 func ParseHost(host string) (string, string, string, error) {
284
+	hostURL, err := ParseHostURL(host)
285
+	if err != nil {
286
+		return "", "", "", err
287
+	}
288
+	return hostURL.Scheme, hostURL.Host, hostURL.Path, nil
289
+}
290
+
291
+// ParseHostURL parses a url string, validates the string is a host url, and
292
+// returns the parsed URL
293
+func ParseHostURL(host string) (*url.URL, error) {
283 294
 	protoAddrParts := strings.SplitN(host, "://", 2)
284 295
 	if len(protoAddrParts) == 1 {
285
-		return "", "", "", fmt.Errorf("unable to parse docker host `%s`", host)
296
+		return nil, fmt.Errorf("unable to parse docker host `%s`", host)
286 297
 	}
287 298
 
288 299
 	var basePath string
... ...
@@ -290,16 +290,19 @@ func ParseHost(host string) (string, string, string, error) {
290 290
 	if proto == "tcp" {
291 291
 		parsed, err := url.Parse("tcp://" + addr)
292 292
 		if err != nil {
293
-			return "", "", "", err
293
+			return nil, err
294 294
 		}
295 295
 		addr = parsed.Host
296 296
 		basePath = parsed.Path
297 297
 	}
298
-	return proto, addr, basePath, nil
298
+	return &url.URL{
299
+		Scheme: proto,
300
+		Host:   addr,
301
+		Path:   basePath,
302
+	}, nil
299 303
 }
300 304
 
301
-// CustomHTTPHeaders returns the custom http headers associated with this
302
-// instance of the Client. This operation doesn't acquire a mutex.
305
+// CustomHTTPHeaders returns the custom http headers stored by the client.
303 306
 func (cli *Client) CustomHTTPHeaders() map[string]string {
304 307
 	m := make(map[string]string)
305 308
 	for k, v := range cli.customHTTPHeaders {
... ...
@@ -308,8 +311,7 @@ func (cli *Client) CustomHTTPHeaders() map[string]string {
308 308
 	return m
309 309
 }
310 310
 
311
-// SetCustomHTTPHeaders updates the custom http headers associated with this
312
-// instance of the Client. This operation doesn't acquire a mutex.
311
+// SetCustomHTTPHeaders that will be set on every HTTP request made by the client.
313 312
 func (cli *Client) SetCustomHTTPHeaders(headers map[string]string) {
314 313
 	cli.customHTTPHeaders = headers
315 314
 }
... ...
@@ -11,6 +11,7 @@ import (
11 11
 
12 12
 	"github.com/docker/docker/api"
13 13
 	"github.com/docker/docker/api/types"
14
+	"github.com/docker/docker/internal/testutil"
14 15
 	"github.com/stretchr/testify/assert"
15 16
 )
16 17
 
... ...
@@ -152,7 +153,6 @@ func TestParseHost(t *testing.T) {
152 152
 
153 153
 	for _, cs := range cases {
154 154
 		p, a, b, e := ParseHost(cs.host)
155
-		// if we expected an error to be returned...
156 155
 		if cs.err {
157 156
 			assert.Error(t, e)
158 157
 		}
... ...
@@ -162,6 +162,43 @@ func TestParseHost(t *testing.T) {
162 162
 	}
163 163
 }
164 164
 
165
+func TestParseHostURL(t *testing.T) {
166
+	testcases := []struct {
167
+		host        string
168
+		expected    *url.URL
169
+		expectedErr string
170
+	}{
171
+		{
172
+			host:        "",
173
+			expectedErr: "unable to parse docker host",
174
+		},
175
+		{
176
+			host:        "foobar",
177
+			expectedErr: "unable to parse docker host",
178
+		},
179
+		{
180
+			host:     "foo://bar",
181
+			expected: &url.URL{Scheme: "foo", Host: "bar"},
182
+		},
183
+		{
184
+			host:     "tcp://localhost:2476",
185
+			expected: &url.URL{Scheme: "tcp", Host: "localhost:2476"},
186
+		},
187
+		{
188
+			host:     "tcp://localhost:2476/path",
189
+			expected: &url.URL{Scheme: "tcp", Host: "localhost:2476", Path: "/path"},
190
+		},
191
+	}
192
+
193
+	for _, testcase := range testcases {
194
+		actual, err := ParseHostURL(testcase.host)
195
+		if testcase.expectedErr != "" {
196
+			testutil.ErrorContains(t, err, testcase.expectedErr)
197
+		}
198
+		assert.Equal(t, testcase.expected, actual)
199
+	}
200
+}
201
+
165 202
 func TestNewEnvClientSetsDefaultVersion(t *testing.T) {
166 203
 	env := envToMap()
167 204
 	defer mapToEnv(env)
... ...
@@ -36,8 +36,8 @@ type notFound interface {
36 36
 	NotFound() bool // Is the error a NotFound error
37 37
 }
38 38
 
39
-// IsErrNotFound returns true if the error is caused with an
40
-// object (image, container, network, volume, …) is not found in the docker host.
39
+// IsErrNotFound returns true if the error is a NotFound error, which is returned
40
+// by the API when some object is not found.
41 41
 func IsErrNotFound(err error) bool {
42 42
 	te, ok := err.(notFound)
43 43
 	return ok && te.NotFound()
... ...
@@ -60,6 +60,8 @@ func (e imageNotFoundError) Error() string {
60 60
 
61 61
 // IsErrImageNotFound returns true if the error is caused
62 62
 // when an image is not found in the docker host.
63
+//
64
+// Deprecated: Use IsErrNotFound
63 65
 func IsErrImageNotFound(err error) bool {
64 66
 	return IsErrNotFound(err)
65 67
 }
... ...
@@ -81,6 +83,8 @@ func (e containerNotFoundError) Error() string {
81 81
 
82 82
 // IsErrContainerNotFound returns true if the error is caused
83 83
 // when a container is not found in the docker host.
84
+//
85
+// Deprecated: Use IsErrNotFound
84 86
 func IsErrContainerNotFound(err error) bool {
85 87
 	return IsErrNotFound(err)
86 88
 }
... ...
@@ -102,6 +106,8 @@ func (e networkNotFoundError) Error() string {
102 102
 
103 103
 // IsErrNetworkNotFound returns true if the error is caused
104 104
 // when a network is not found in the docker host.
105
+//
106
+// Deprecated: Use IsErrNotFound
105 107
 func IsErrNetworkNotFound(err error) bool {
106 108
 	return IsErrNotFound(err)
107 109
 }
... ...
@@ -123,6 +129,8 @@ func (e volumeNotFoundError) Error() string {
123 123
 
124 124
 // IsErrVolumeNotFound returns true if the error is caused
125 125
 // when a volume is not found in the docker host.
126
+//
127
+// Deprecated: Use IsErrNotFound
126 128
 func IsErrVolumeNotFound(err error) bool {
127 129
 	return IsErrNotFound(err)
128 130
 }
... ...
@@ -161,6 +169,8 @@ func (e nodeNotFoundError) NotFound() bool {
161 161
 
162 162
 // IsErrNodeNotFound returns true if the error is caused
163 163
 // when a node is not found.
164
+//
165
+// Deprecated: Use IsErrNotFound
164 166
 func IsErrNodeNotFound(err error) bool {
165 167
 	_, ok := err.(nodeNotFoundError)
166 168
 	return ok
... ...
@@ -183,6 +193,8 @@ func (e serviceNotFoundError) NotFound() bool {
183 183
 
184 184
 // IsErrServiceNotFound returns true if the error is caused
185 185
 // when a service is not found.
186
+//
187
+// Deprecated: Use IsErrNotFound
186 188
 func IsErrServiceNotFound(err error) bool {
187 189
 	_, ok := err.(serviceNotFoundError)
188 190
 	return ok
... ...
@@ -205,6 +217,8 @@ func (e taskNotFoundError) NotFound() bool {
205 205
 
206 206
 // IsErrTaskNotFound returns true if the error is caused
207 207
 // when a task is not found.
208
+//
209
+// Deprecated: Use IsErrNotFound
208 210
 func IsErrTaskNotFound(err error) bool {
209 211
 	_, ok := err.(taskNotFoundError)
210 212
 	return ok
... ...
@@ -251,6 +265,8 @@ func (e secretNotFoundError) NotFound() bool {
251 251
 
252 252
 // IsErrSecretNotFound returns true if the error is caused
253 253
 // when a secret is not found.
254
+//
255
+// Deprecated: Use IsErrNotFound
254 256
 func IsErrSecretNotFound(err error) bool {
255 257
 	_, ok := err.(secretNotFoundError)
256 258
 	return ok
... ...
@@ -273,6 +289,8 @@ func (e configNotFoundError) NotFound() bool {
273 273
 
274 274
 // IsErrConfigNotFound returns true if the error is caused
275 275
 // when a config is not found.
276
+//
277
+// Deprecated: Use IsErrNotFound
276 278
 func IsErrConfigNotFound(err error) bool {
277 279
 	_, ok := err.(configNotFoundError)
278 280
 	return ok
... ...
@@ -295,6 +313,8 @@ func (e pluginNotFoundError) Error() string {
295 295
 
296 296
 // IsErrPluginNotFound returns true if the error is caused
297 297
 // when a plugin is not found in the docker host.
298
+//
299
+// Deprecated: Use IsErrNotFound
298 300
 func IsErrPluginNotFound(err error) bool {
299 301
 	return IsErrNotFound(err)
300 302
 }
301 303
deleted file mode 100644
... ...
@@ -1,41 +0,0 @@
1
-package client
2
-
3
-// parse_logs.go contains utility helpers for getting information out of docker
4
-// log lines. really, it only contains ParseDetails right now. maybe in the
5
-// future there will be some desire to parse log messages back into a struct?
6
-// that would go here if we did
7
-
8
-import (
9
-	"net/url"
10
-	"strings"
11
-
12
-	"github.com/pkg/errors"
13
-)
14
-
15
-// ParseLogDetails takes a details string of key value pairs in the form
16
-// "k=v,l=w", where the keys and values are url query escaped, and each pair
17
-// is separated by a comma, returns a map. returns an error if the details
18
-// string is not in a valid format
19
-// the exact form of details encoding is implemented in
20
-// api/server/httputils/write_log_stream.go
21
-func ParseLogDetails(details string) (map[string]string, error) {
22
-	pairs := strings.Split(details, ",")
23
-	detailsMap := make(map[string]string, len(pairs))
24
-	for _, pair := range pairs {
25
-		p := strings.SplitN(pair, "=", 2)
26
-		// if there is no equals sign, we will only get 1 part back
27
-		if len(p) != 2 {
28
-			return nil, errors.New("invalid details format")
29
-		}
30
-		k, err := url.QueryUnescape(p[0])
31
-		if err != nil {
32
-			return nil, err
33
-		}
34
-		v, err := url.QueryUnescape(p[1])
35
-		if err != nil {
36
-			return nil, err
37
-		}
38
-		detailsMap[k] = v
39
-	}
40
-	return detailsMap, nil
41
-}
42 1
deleted file mode 100644
... ...
@@ -1,36 +0,0 @@
1
-package client
2
-
3
-import (
4
-	"reflect"
5
-	"testing"
6
-
7
-	"github.com/pkg/errors"
8
-)
9
-
10
-func TestParseLogDetails(t *testing.T) {
11
-	testCases := []struct {
12
-		line     string
13
-		expected map[string]string
14
-		err      error
15
-	}{
16
-		{"key=value", map[string]string{"key": "value"}, nil},
17
-		{"key1=value1,key2=value2", map[string]string{"key1": "value1", "key2": "value2"}, nil},
18
-		{"key+with+spaces=value%3Dequals,asdf%2C=", map[string]string{"key with spaces": "value=equals", "asdf,": ""}, nil},
19
-		{"key=,=nothing", map[string]string{"key": "", "": "nothing"}, nil},
20
-		{"=", map[string]string{"": ""}, nil},
21
-		{"errors", nil, errors.New("invalid details format")},
22
-	}
23
-	for _, tc := range testCases {
24
-		tc := tc // capture range variable
25
-		t.Run(tc.line, func(t *testing.T) {
26
-			t.Parallel()
27
-			res, err := ParseLogDetails(tc.line)
28
-			if err != nil && (err.Error() != tc.err.Error()) {
29
-				t.Fatalf("unexpected error parsing logs:\nExpected:\n\t%v\nActual:\n\t%v", tc.err, err)
30
-			}
31
-			if !reflect.DeepEqual(tc.expected, res) {
32
-				t.Errorf("result does not match expected:\nExpected:\n\t%#v\nActual:\n\t%#v", tc.expected, res)
33
-			}
34
-		})
35
-	}
36
-}