Browse code

Cleanup client not found errors.

And fix remove calls to return a notFound error

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

Daniel Nephin authored on 2017/09/09 01:04:34
Showing 31 changed files
... ...
@@ -2,7 +2,6 @@ package client
2 2
 
3 3
 import (
4 4
 	"encoding/json"
5
-	"net/http"
6 5
 	"net/url"
7 6
 
8 7
 	"github.com/docker/docker/api/types"
... ...
@@ -20,10 +19,7 @@ func (cli *Client) CheckpointList(ctx context.Context, container string, options
20 20
 
21 21
 	resp, err := cli.get(ctx, "/containers/"+container+"/checkpoints", query, nil)
22 22
 	if err != nil {
23
-		if resp.statusCode == http.StatusNotFound {
24
-			return checkpoints, containerNotFoundError{container}
25
-		}
26
-		return checkpoints, err
23
+		return checkpoints, wrapResponseError(err, resp, "container", container)
27 24
 	}
28 25
 
29 26
 	err = json.NewDecoder(resp.body).Decode(&checkpoints)
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"bytes"
5 5
 	"encoding/json"
6 6
 	"io/ioutil"
7
-	"net/http"
8 7
 
9 8
 	"github.com/docker/docker/api/types/swarm"
10 9
 	"golang.org/x/net/context"
... ...
@@ -17,10 +16,7 @@ func (cli *Client) ConfigInspectWithRaw(ctx context.Context, id string) (swarm.C
17 17
 	}
18 18
 	resp, err := cli.get(ctx, "/configs/"+id, nil, nil)
19 19
 	if err != nil {
20
-		if resp.statusCode == http.StatusNotFound {
21
-			return swarm.Config{}, nil, configNotFoundError{id}
22
-		}
23
-		return swarm.Config{}, nil, err
20
+		return swarm.Config{}, nil, wrapResponseError(err, resp, "config", id)
24 21
 	}
25 22
 	defer ensureReaderClosed(resp)
26 23
 
... ...
@@ -9,5 +9,5 @@ func (cli *Client) ConfigRemove(ctx context.Context, id string) error {
9 9
 	}
10 10
 	resp, err := cli.delete(ctx, "/configs/"+id, nil, nil)
11 11
 	ensureReaderClosed(resp)
12
-	return err
12
+	return wrapResponseError(err, resp, "config", id)
13 13
 }
... ...
@@ -45,7 +45,7 @@ func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config
45 45
 	serverResp, err := cli.post(ctx, "/containers/create", query, body, nil)
46 46
 	if err != nil {
47 47
 		if serverResp.statusCode == 404 && strings.Contains(err.Error(), "No such image") {
48
-			return response, imageNotFoundError{config.Image}
48
+			return response, objectNotFoundError{object: "image", id: config.Image}
49 49
 		}
50 50
 		return response, err
51 51
 	}
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"bytes"
5 5
 	"encoding/json"
6 6
 	"io/ioutil"
7
-	"net/http"
8 7
 	"net/url"
9 8
 
10 9
 	"github.com/docker/docker/api/types"
... ...
@@ -15,10 +14,7 @@ import (
15 15
 func (cli *Client) ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error) {
16 16
 	serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", nil, nil)
17 17
 	if err != nil {
18
-		if serverResp.statusCode == http.StatusNotFound {
19
-			return types.ContainerJSON{}, containerNotFoundError{containerID}
20
-		}
21
-		return types.ContainerJSON{}, err
18
+		return types.ContainerJSON{}, wrapResponseError(err, serverResp, "container", containerID)
22 19
 	}
23 20
 
24 21
 	var response types.ContainerJSON
... ...
@@ -35,10 +31,7 @@ func (cli *Client) ContainerInspectWithRaw(ctx context.Context, containerID stri
35 35
 	}
36 36
 	serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", query, nil)
37 37
 	if err != nil {
38
-		if serverResp.statusCode == http.StatusNotFound {
39
-			return types.ContainerJSON{}, nil, containerNotFoundError{containerID}
40
-		}
41
-		return types.ContainerJSON{}, nil, err
38
+		return types.ContainerJSON{}, nil, wrapResponseError(err, serverResp, "container", containerID)
42 39
 	}
43 40
 	defer ensureReaderClosed(serverResp)
44 41
 
... ...
@@ -23,5 +23,5 @@ func (cli *Client) ContainerRemove(ctx context.Context, containerID string, opti
23 23
 
24 24
 	resp, err := cli.delete(ctx, "/containers/"+containerID, query, nil)
25 25
 	ensureReaderClosed(resp)
26
-	return err
26
+	return wrapResponseError(err, resp, "container", containerID)
27 27
 }
... ...
@@ -9,6 +9,7 @@ import (
9 9
 	"testing"
10 10
 
11 11
 	"github.com/docker/docker/api/types"
12
+	"github.com/stretchr/testify/assert"
12 13
 	"golang.org/x/net/context"
13 14
 )
14 15
 
... ...
@@ -17,9 +18,16 @@ func TestContainerRemoveError(t *testing.T) {
17 17
 		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
18 18
 	}
19 19
 	err := client.ContainerRemove(context.Background(), "container_id", types.ContainerRemoveOptions{})
20
-	if err == nil || err.Error() != "Error response from daemon: Server error" {
21
-		t.Fatalf("expected a Server Error, got %v", err)
20
+	assert.EqualError(t, err, "Error response from daemon: Server error")
21
+}
22
+
23
+func TestContainerRemoveNotFoundError(t *testing.T) {
24
+	client := &Client{
25
+		client: newMockClient(errorMock(http.StatusNotFound, "missing")),
22 26
 	}
27
+	err := client.ContainerRemove(context.Background(), "container_id", types.ContainerRemoveOptions{})
28
+	assert.EqualError(t, err, "Error: No such container: container_id")
29
+	assert.True(t, IsErrNotFound(err))
23 30
 }
24 31
 
25 32
 func TestContainerRemove(t *testing.T) {
... ...
@@ -53,7 +61,5 @@ func TestContainerRemove(t *testing.T) {
53 53
 		RemoveVolumes: true,
54 54
 		Force:         true,
55 55
 	})
56
-	if err != nil {
57
-		t.Fatal(err)
58
-	}
56
+	assert.NoError(t, err)
59 57
 }
... ...
@@ -3,6 +3,8 @@ package client
3 3
 import (
4 4
 	"fmt"
5 5
 
6
+	"net/http"
7
+
6 8
 	"github.com/docker/docker/api/types/versions"
7 9
 	"github.com/pkg/errors"
8 10
 )
... ...
@@ -43,19 +45,28 @@ func IsErrNotFound(err error) bool {
43 43
 	return ok && te.NotFound()
44 44
 }
45 45
 
46
-// imageNotFoundError implements an error returned when an image is not in the docker host.
47
-type imageNotFoundError struct {
48
-	imageID string
46
+type objectNotFoundError struct {
47
+	object string
48
+	id     string
49 49
 }
50 50
 
51
-// NotFound indicates that this error type is of NotFound
52
-func (e imageNotFoundError) NotFound() bool {
51
+func (e objectNotFoundError) NotFound() bool {
53 52
 	return true
54 53
 }
55 54
 
56
-// Error returns a string representation of an imageNotFoundError
57
-func (e imageNotFoundError) Error() string {
58
-	return fmt.Sprintf("Error: No such image: %s", e.imageID)
55
+func (e objectNotFoundError) Error() string {
56
+	return fmt.Sprintf("Error: No such %s: %s", e.object, e.id)
57
+}
58
+
59
+func wrapResponseError(err error, resp serverResponse, object, id string) error {
60
+	switch {
61
+	case err == nil:
62
+		return nil
63
+	case resp.statusCode == http.StatusNotFound:
64
+		return objectNotFoundError{object: object, id: id}
65
+	default:
66
+		return err
67
+	}
59 68
 }
60 69
 
61 70
 // IsErrImageNotFound returns true if the error is caused
... ...
@@ -66,21 +77,6 @@ func IsErrImageNotFound(err error) bool {
66 66
 	return IsErrNotFound(err)
67 67
 }
68 68
 
69
-// containerNotFoundError implements an error returned when a container is not in the docker host.
70
-type containerNotFoundError struct {
71
-	containerID string
72
-}
73
-
74
-// NotFound indicates that this error type is of NotFound
75
-func (e containerNotFoundError) NotFound() bool {
76
-	return true
77
-}
78
-
79
-// Error returns a string representation of a containerNotFoundError
80
-func (e containerNotFoundError) Error() string {
81
-	return fmt.Sprintf("Error: No such container: %s", e.containerID)
82
-}
83
-
84 69
 // IsErrContainerNotFound returns true if the error is caused
85 70
 // when a container is not found in the docker host.
86 71
 //
... ...
@@ -89,21 +85,6 @@ func IsErrContainerNotFound(err error) bool {
89 89
 	return IsErrNotFound(err)
90 90
 }
91 91
 
92
-// networkNotFoundError implements an error returned when a network is not in the docker host.
93
-type networkNotFoundError struct {
94
-	networkID string
95
-}
96
-
97
-// NotFound indicates that this error type is of NotFound
98
-func (e networkNotFoundError) NotFound() bool {
99
-	return true
100
-}
101
-
102
-// Error returns a string representation of a networkNotFoundError
103
-func (e networkNotFoundError) Error() string {
104
-	return fmt.Sprintf("Error: No such network: %s", e.networkID)
105
-}
106
-
107 92
 // IsErrNetworkNotFound returns true if the error is caused
108 93
 // when a network is not found in the docker host.
109 94
 //
... ...
@@ -112,21 +93,6 @@ func IsErrNetworkNotFound(err error) bool {
112 112
 	return IsErrNotFound(err)
113 113
 }
114 114
 
115
-// volumeNotFoundError implements an error returned when a volume is not in the docker host.
116
-type volumeNotFoundError struct {
117
-	volumeID string
118
-}
119
-
120
-// NotFound indicates that this error type is of NotFound
121
-func (e volumeNotFoundError) NotFound() bool {
122
-	return true
123
-}
124
-
125
-// Error returns a string representation of a volumeNotFoundError
126
-func (e volumeNotFoundError) Error() string {
127
-	return fmt.Sprintf("Error: No such volume: %s", e.volumeID)
128
-}
129
-
130 115
 // IsErrVolumeNotFound returns true if the error is caused
131 116
 // when a volume is not found in the docker host.
132 117
 //
... ...
@@ -152,43 +118,12 @@ func IsErrUnauthorized(err error) bool {
152 152
 	return ok
153 153
 }
154 154
 
155
-// nodeNotFoundError implements an error returned when a node is not found.
156
-type nodeNotFoundError struct {
157
-	nodeID string
158
-}
159
-
160
-// Error returns a string representation of a nodeNotFoundError
161
-func (e nodeNotFoundError) Error() string {
162
-	return fmt.Sprintf("Error: No such node: %s", e.nodeID)
163
-}
164
-
165
-// NotFound indicates that this error type is of NotFound
166
-func (e nodeNotFoundError) NotFound() bool {
167
-	return true
168
-}
169
-
170 155
 // IsErrNodeNotFound returns true if the error is caused
171 156
 // when a node is not found.
172 157
 //
173 158
 // Deprecated: Use IsErrNotFound
174 159
 func IsErrNodeNotFound(err error) bool {
175
-	_, ok := err.(nodeNotFoundError)
176
-	return ok
177
-}
178
-
179
-// serviceNotFoundError implements an error returned when a service is not found.
180
-type serviceNotFoundError struct {
181
-	serviceID string
182
-}
183
-
184
-// Error returns a string representation of a serviceNotFoundError
185
-func (e serviceNotFoundError) Error() string {
186
-	return fmt.Sprintf("Error: No such service: %s", e.serviceID)
187
-}
188
-
189
-// NotFound indicates that this error type is of NotFound
190
-func (e serviceNotFoundError) NotFound() bool {
191
-	return true
160
+	return IsErrNotFound(err)
192 161
 }
193 162
 
194 163
 // IsErrServiceNotFound returns true if the error is caused
... ...
@@ -196,23 +131,7 @@ func (e serviceNotFoundError) NotFound() bool {
196 196
 //
197 197
 // Deprecated: Use IsErrNotFound
198 198
 func IsErrServiceNotFound(err error) bool {
199
-	_, ok := err.(serviceNotFoundError)
200
-	return ok
201
-}
202
-
203
-// taskNotFoundError implements an error returned when a task is not found.
204
-type taskNotFoundError struct {
205
-	taskID string
206
-}
207
-
208
-// Error returns a string representation of a taskNotFoundError
209
-func (e taskNotFoundError) Error() string {
210
-	return fmt.Sprintf("Error: No such task: %s", e.taskID)
211
-}
212
-
213
-// NotFound indicates that this error type is of NotFound
214
-func (e taskNotFoundError) NotFound() bool {
215
-	return true
199
+	return IsErrNotFound(err)
216 200
 }
217 201
 
218 202
 // IsErrTaskNotFound returns true if the error is caused
... ...
@@ -220,8 +139,7 @@ func (e taskNotFoundError) NotFound() bool {
220 220
 //
221 221
 // Deprecated: Use IsErrNotFound
222 222
 func IsErrTaskNotFound(err error) bool {
223
-	_, ok := err.(taskNotFoundError)
224
-	return ok
223
+	return IsErrNotFound(err)
225 224
 }
226 225
 
227 226
 type pluginPermissionDenied struct {
... ...
@@ -248,43 +166,12 @@ func (cli *Client) NewVersionError(APIrequired, feature string) error {
248 248
 	return nil
249 249
 }
250 250
 
251
-// secretNotFoundError implements an error returned when a secret is not found.
252
-type secretNotFoundError struct {
253
-	name string
254
-}
255
-
256
-// Error returns a string representation of a secretNotFoundError
257
-func (e secretNotFoundError) Error() string {
258
-	return fmt.Sprintf("Error: no such secret: %s", e.name)
259
-}
260
-
261
-// NotFound indicates that this error type is of NotFound
262
-func (e secretNotFoundError) NotFound() bool {
263
-	return true
264
-}
265
-
266 251
 // IsErrSecretNotFound returns true if the error is caused
267 252
 // when a secret is not found.
268 253
 //
269 254
 // Deprecated: Use IsErrNotFound
270 255
 func IsErrSecretNotFound(err error) bool {
271
-	_, ok := err.(secretNotFoundError)
272
-	return ok
273
-}
274
-
275
-// configNotFoundError implements an error returned when a config is not found.
276
-type configNotFoundError struct {
277
-	name string
278
-}
279
-
280
-// Error returns a string representation of a configNotFoundError
281
-func (e configNotFoundError) Error() string {
282
-	return fmt.Sprintf("Error: no such config: %s", e.name)
283
-}
284
-
285
-// NotFound indicates that this error type is of NotFound
286
-func (e configNotFoundError) NotFound() bool {
287
-	return true
256
+	return IsErrNotFound(err)
288 257
 }
289 258
 
290 259
 // IsErrConfigNotFound returns true if the error is caused
... ...
@@ -292,23 +179,7 @@ func (e configNotFoundError) NotFound() bool {
292 292
 //
293 293
 // Deprecated: Use IsErrNotFound
294 294
 func IsErrConfigNotFound(err error) bool {
295
-	_, ok := err.(configNotFoundError)
296
-	return ok
297
-}
298
-
299
-// pluginNotFoundError implements an error returned when a plugin is not in the docker host.
300
-type pluginNotFoundError struct {
301
-	name string
302
-}
303
-
304
-// NotFound indicates that this error type is of NotFound
305
-func (e pluginNotFoundError) NotFound() bool {
306
-	return true
307
-}
308
-
309
-// Error returns a string representation of a pluginNotFoundError
310
-func (e pluginNotFoundError) Error() string {
311
-	return fmt.Sprintf("Error: No such plugin: %s", e.name)
295
+	return IsErrNotFound(err)
312 296
 }
313 297
 
314 298
 // IsErrPluginNotFound returns true if the error is caused
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"bytes"
5 5
 	"encoding/json"
6 6
 	"io/ioutil"
7
-	"net/http"
8 7
 
9 8
 	"github.com/docker/docker/api/types"
10 9
 	"golang.org/x/net/context"
... ...
@@ -14,10 +13,7 @@ import (
14 14
 func (cli *Client) ImageInspectWithRaw(ctx context.Context, imageID string) (types.ImageInspect, []byte, error) {
15 15
 	serverResp, err := cli.get(ctx, "/images/"+imageID+"/json", nil, nil)
16 16
 	if err != nil {
17
-		if serverResp.statusCode == http.StatusNotFound {
18
-			return types.ImageInspect{}, nil, imageNotFoundError{imageID}
19
-		}
20
-		return types.ImageInspect{}, nil, err
17
+		return types.ImageInspect{}, nil, wrapResponseError(err, serverResp, "image", imageID)
21 18
 	}
22 19
 	defer ensureReaderClosed(serverResp)
23 20
 
... ...
@@ -2,7 +2,6 @@ package client
2 2
 
3 3
 import (
4 4
 	"encoding/json"
5
-	"net/http"
6 5
 	"net/url"
7 6
 
8 7
 	"github.com/docker/docker/api/types"
... ...
@@ -20,15 +19,12 @@ func (cli *Client) ImageRemove(ctx context.Context, imageID string, options type
20 20
 		query.Set("noprune", "1")
21 21
 	}
22 22
 
23
+	var dels []types.ImageDeleteResponseItem
23 24
 	resp, err := cli.delete(ctx, "/images/"+imageID, query, nil)
24 25
 	if err != nil {
25
-		if resp.statusCode == http.StatusNotFound {
26
-			return nil, imageNotFoundError{imageID}
27
-		}
28
-		return nil, err
26
+		return dels, wrapResponseError(err, resp, "image", imageID)
29 27
 	}
30 28
 
31
-	var dels []types.ImageDeleteResponseItem
32 29
 	err = json.NewDecoder(resp.body).Decode(&dels)
33 30
 	ensureReaderClosed(resp)
34 31
 	return dels, err
... ...
@@ -10,6 +10,7 @@ import (
10 10
 	"testing"
11 11
 
12 12
 	"github.com/docker/docker/api/types"
13
+	"github.com/stretchr/testify/assert"
13 14
 	"golang.org/x/net/context"
14 15
 )
15 16
 
... ...
@@ -19,20 +20,17 @@ func TestImageRemoveError(t *testing.T) {
19 19
 	}
20 20
 
21 21
 	_, err := client.ImageRemove(context.Background(), "image_id", types.ImageRemoveOptions{})
22
-	if err == nil || err.Error() != "Error response from daemon: Server error" {
23
-		t.Fatalf("expected a Server Error, got %v", err)
24
-	}
22
+	assert.EqualError(t, err, "Error response from daemon: Server error")
25 23
 }
26 24
 
27 25
 func TestImageRemoveImageNotFound(t *testing.T) {
28 26
 	client := &Client{
29
-		client: newMockClient(errorMock(http.StatusNotFound, "Server error")),
27
+		client: newMockClient(errorMock(http.StatusNotFound, "missing")),
30 28
 	}
31 29
 
32 30
 	_, err := client.ImageRemove(context.Background(), "unknown", types.ImageRemoveOptions{})
33
-	if err == nil || !IsErrNotFound(err) {
34
-		t.Fatalf("expected an imageNotFoundError error, got %v", err)
35
-	}
31
+	assert.EqualError(t, err, "Error: No such image: unknown")
32
+	assert.True(t, IsErrNotFound(err))
36 33
 }
37 34
 
38 35
 func TestImageRemove(t *testing.T) {
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"bytes"
5 5
 	"encoding/json"
6 6
 	"io/ioutil"
7
-	"net/http"
8 7
 	"net/url"
9 8
 
10 9
 	"github.com/docker/docker/api/types"
... ...
@@ -33,10 +32,7 @@ func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string,
33 33
 	}
34 34
 	resp, err = cli.get(ctx, "/networks/"+networkID, query, nil)
35 35
 	if err != nil {
36
-		if resp.statusCode == http.StatusNotFound {
37
-			return networkResource, nil, networkNotFoundError{networkID}
38
-		}
39
-		return networkResource, nil, err
36
+		return networkResource, nil, wrapResponseError(err, resp, "network", networkID)
40 37
 	}
41 38
 	defer ensureReaderClosed(resp)
42 39
 
... ...
@@ -21,20 +21,17 @@ func TestNetworkInspectError(t *testing.T) {
21 21
 	}
22 22
 
23 23
 	_, err := client.NetworkInspect(context.Background(), "nothing", types.NetworkInspectOptions{})
24
-	if err == nil || err.Error() != "Error response from daemon: Server error" {
25
-		t.Fatalf("expected a Server Error, got %v", err)
26
-	}
24
+	assert.EqualError(t, err, "Error response from daemon: Server error")
27 25
 }
28 26
 
29
-func TestNetworkInspectContainerNotFound(t *testing.T) {
27
+func TestNetworkInspectNotFoundError(t *testing.T) {
30 28
 	client := &Client{
31
-		client: newMockClient(errorMock(http.StatusNotFound, "Server error")),
29
+		client: newMockClient(errorMock(http.StatusNotFound, "missing")),
32 30
 	}
33 31
 
34 32
 	_, err := client.NetworkInspect(context.Background(), "unknown", types.NetworkInspectOptions{})
35
-	if err == nil || !IsErrNetworkNotFound(err) {
36
-		t.Fatalf("expected a networkNotFound error, got %v", err)
37
-	}
33
+	assert.EqualError(t, err, "Error: No such network: unknown")
34
+	assert.True(t, IsErrNotFound(err))
38 35
 }
39 36
 
40 37
 func TestNetworkInspect(t *testing.T) {
... ...
@@ -6,5 +6,5 @@ import "golang.org/x/net/context"
6 6
 func (cli *Client) NetworkRemove(ctx context.Context, networkID string) error {
7 7
 	resp, err := cli.delete(ctx, "/networks/"+networkID, nil, nil)
8 8
 	ensureReaderClosed(resp)
9
-	return err
9
+	return wrapResponseError(err, resp, "network", networkID)
10 10
 }
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"bytes"
5 5
 	"encoding/json"
6 6
 	"io/ioutil"
7
-	"net/http"
8 7
 
9 8
 	"github.com/docker/docker/api/types/swarm"
10 9
 	"golang.org/x/net/context"
... ...
@@ -14,10 +13,7 @@ import (
14 14
 func (cli *Client) NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error) {
15 15
 	serverResp, err := cli.get(ctx, "/nodes/"+nodeID, nil, nil)
16 16
 	if err != nil {
17
-		if serverResp.statusCode == http.StatusNotFound {
18
-			return swarm.Node{}, nil, nodeNotFoundError{nodeID}
19
-		}
20
-		return swarm.Node{}, nil, err
17
+		return swarm.Node{}, nil, wrapResponseError(err, serverResp, "node", nodeID)
21 18
 	}
22 19
 	defer ensureReaderClosed(serverResp)
23 20
 
... ...
@@ -17,5 +17,5 @@ func (cli *Client) NodeRemove(ctx context.Context, nodeID string, options types.
17 17
 
18 18
 	resp, err := cli.delete(ctx, "/nodes/"+nodeID, query, nil)
19 19
 	ensureReaderClosed(resp)
20
-	return err
20
+	return wrapResponseError(err, resp, "node", nodeID)
21 21
 }
... ...
@@ -28,7 +28,5 @@ func (cli *Client) Ping(ctx context.Context) (types.Ping, error) {
28 28
 		}
29 29
 		ping.OSType = serverResp.header.Get("OSType")
30 30
 	}
31
-
32
-	err = cli.checkResponseErr(serverResp)
33
-	return ping, err
31
+	return ping, cli.checkResponseErr(serverResp)
34 32
 }
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"bytes"
5 5
 	"encoding/json"
6 6
 	"io/ioutil"
7
-	"net/http"
8 7
 
9 8
 	"github.com/docker/docker/api/types"
10 9
 	"golang.org/x/net/context"
... ...
@@ -14,10 +13,7 @@ import (
14 14
 func (cli *Client) PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error) {
15 15
 	resp, err := cli.get(ctx, "/plugins/"+name+"/json", nil, nil)
16 16
 	if err != nil {
17
-		if resp.statusCode == http.StatusNotFound {
18
-			return nil, nil, pluginNotFoundError{name}
19
-		}
20
-		return nil, nil, err
17
+		return nil, nil, wrapResponseError(err, resp, "plugin", name)
21 18
 	}
22 19
 
23 20
 	defer ensureReaderClosed(resp)
... ...
@@ -16,5 +16,5 @@ func (cli *Client) PluginRemove(ctx context.Context, name string, options types.
16 16
 
17 17
 	resp, err := cli.delete(ctx, "/plugins/"+name, query, nil)
18 18
 	ensureReaderClosed(resp)
19
-	return err
19
+	return wrapResponseError(err, resp, "plugin", name)
20 20
 }
... ...
@@ -203,7 +203,7 @@ func (cli *Client) checkResponseErr(serverResp serverResponse) error {
203 203
 		return err
204 204
 	}
205 205
 	if len(body) == 0 {
206
-		return fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), serverResp.reqURL)
206
+		return fmt.Errorf("request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), serverResp.reqURL)
207 207
 	}
208 208
 
209 209
 	var ct string
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"bytes"
5 5
 	"encoding/json"
6 6
 	"io/ioutil"
7
-	"net/http"
8 7
 
9 8
 	"github.com/docker/docker/api/types/swarm"
10 9
 	"golang.org/x/net/context"
... ...
@@ -17,10 +16,7 @@ func (cli *Client) SecretInspectWithRaw(ctx context.Context, id string) (swarm.S
17 17
 	}
18 18
 	resp, err := cli.get(ctx, "/secrets/"+id, nil, nil)
19 19
 	if err != nil {
20
-		if resp.statusCode == http.StatusNotFound {
21
-			return swarm.Secret{}, nil, secretNotFoundError{id}
22
-		}
23
-		return swarm.Secret{}, nil, err
20
+		return swarm.Secret{}, nil, wrapResponseError(err, resp, "secret", id)
24 21
 	}
25 22
 	defer ensureReaderClosed(resp)
26 23
 
... ...
@@ -9,5 +9,5 @@ func (cli *Client) SecretRemove(ctx context.Context, id string) error {
9 9
 	}
10 10
 	resp, err := cli.delete(ctx, "/secrets/"+id, nil, nil)
11 11
 	ensureReaderClosed(resp)
12
-	return err
12
+	return wrapResponseError(err, resp, "secret", id)
13 13
 }
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"encoding/json"
6 6
 	"fmt"
7 7
 	"io/ioutil"
8
-	"net/http"
9 8
 	"net/url"
10 9
 
11 10
 	"github.com/docker/docker/api/types"
... ...
@@ -19,10 +18,7 @@ func (cli *Client) ServiceInspectWithRaw(ctx context.Context, serviceID string,
19 19
 	query.Set("insertDefaults", fmt.Sprintf("%v", opts.InsertDefaults))
20 20
 	serverResp, err := cli.get(ctx, "/services/"+serviceID, query, nil)
21 21
 	if err != nil {
22
-		if serverResp.statusCode == http.StatusNotFound {
23
-			return swarm.Service{}, nil, serviceNotFoundError{serviceID}
24
-		}
25
-		return swarm.Service{}, nil, err
22
+		return swarm.Service{}, nil, wrapResponseError(err, serverResp, "service", serviceID)
26 23
 	}
27 24
 	defer ensureReaderClosed(serverResp)
28 25
 
... ...
@@ -6,5 +6,5 @@ import "golang.org/x/net/context"
6 6
 func (cli *Client) ServiceRemove(ctx context.Context, serviceID string) error {
7 7
 	resp, err := cli.delete(ctx, "/services/"+serviceID, nil, nil)
8 8
 	ensureReaderClosed(resp)
9
-	return err
9
+	return wrapResponseError(err, resp, "service", serviceID)
10 10
 }
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"strings"
9 9
 	"testing"
10 10
 
11
+	"github.com/stretchr/testify/assert"
11 12
 	"golang.org/x/net/context"
12 13
 )
13 14
 
... ...
@@ -17,9 +18,17 @@ func TestServiceRemoveError(t *testing.T) {
17 17
 	}
18 18
 
19 19
 	err := client.ServiceRemove(context.Background(), "service_id")
20
-	if err == nil || err.Error() != "Error response from daemon: Server error" {
21
-		t.Fatalf("expected a Server Error, got %v", err)
20
+	assert.EqualError(t, err, "Error response from daemon: Server error")
21
+}
22
+
23
+func TestServiceRemoveNotFoundError(t *testing.T) {
24
+	client := &Client{
25
+		client: newMockClient(errorMock(http.StatusNotFound, "missing")),
22 26
 	}
27
+
28
+	err := client.ServiceRemove(context.Background(), "service_id")
29
+	assert.EqualError(t, err, "Error: No such service: service_id")
30
+	assert.True(t, IsErrNotFound(err))
23 31
 }
24 32
 
25 33
 func TestServiceRemove(t *testing.T) {
... ...
@@ -4,10 +4,8 @@ import (
4 4
 	"bytes"
5 5
 	"encoding/json"
6 6
 	"io/ioutil"
7
-	"net/http"
8 7
 
9 8
 	"github.com/docker/docker/api/types/swarm"
10
-
11 9
 	"golang.org/x/net/context"
12 10
 )
13 11
 
... ...
@@ -15,10 +13,7 @@ import (
15 15
 func (cli *Client) TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error) {
16 16
 	serverResp, err := cli.get(ctx, "/tasks/"+taskID, nil, nil)
17 17
 	if err != nil {
18
-		if serverResp.statusCode == http.StatusNotFound {
19
-			return swarm.Task{}, nil, taskNotFoundError{taskID}
20
-		}
21
-		return swarm.Task{}, nil, err
18
+		return swarm.Task{}, nil, wrapResponseError(err, serverResp, "task", taskID)
22 19
 	}
23 20
 	defer ensureReaderClosed(serverResp)
24 21
 
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"bytes"
5 5
 	"encoding/json"
6 6
 	"io/ioutil"
7
-	"net/http"
8 7
 	"path"
9 8
 
10 9
 	"github.com/docker/docker/api/types"
... ...
@@ -23,16 +22,13 @@ func (cli *Client) VolumeInspectWithRaw(ctx context.Context, volumeID string) (t
23 23
 	// request url will not contain a trailing / which calls the volume list API
24 24
 	// instead of volume inspect
25 25
 	if volumeID == "" {
26
-		return types.Volume{}, nil, volumeNotFoundError{volumeID}
26
+		return types.Volume{}, nil, objectNotFoundError{object: "volume", id: volumeID}
27 27
 	}
28 28
 
29 29
 	var volume types.Volume
30 30
 	resp, err := cli.get(ctx, path.Join("/volumes", volumeID), nil, nil)
31 31
 	if err != nil {
32
-		if resp.statusCode == http.StatusNotFound {
33
-			return volume, nil, volumeNotFoundError{volumeID}
34
-		}
35
-		return volume, nil, err
32
+		return volume, nil, wrapResponseError(err, resp, "volume", volumeID)
36 33
 	}
37 34
 	defer ensureReaderClosed(resp)
38 35
 
... ...
@@ -17,5 +17,5 @@ func (cli *Client) VolumeRemove(ctx context.Context, volumeID string, force bool
17 17
 	}
18 18
 	resp, err := cli.delete(ctx, "/volumes/"+volumeID, query, nil)
19 19
 	ensureReaderClosed(resp)
20
-	return err
20
+	return wrapResponseError(err, resp, "volume", volumeID)
21 21
 }
... ...
@@ -1617,7 +1617,7 @@ func (s *DockerSuite) TestContainerAPIDeleteWithEmptyName(c *check.C) {
1617 1617
 	defer cli.Close()
1618 1618
 
1619 1619
 	err = cli.ContainerRemove(context.Background(), "", types.ContainerRemoveOptions{})
1620
-	c.Assert(err.Error(), checker.Contains, "Error response from daemon: page not found")
1620
+	c.Assert(err.Error(), checker.Contains, "No such container")
1621 1621
 }
1622 1622
 
1623 1623
 func (s *DockerSuite) TestContainerAPIStatsWithNetworkDisabled(c *check.C) {
... ...
@@ -55,10 +55,8 @@ func (s *DockerSwarmSuite) TestAPISwarmConfigsDelete(c *check.C) {
55 55
 	c.Assert(err, checker.IsNil)
56 56
 	defer cli.Close()
57 57
 
58
-	expected := "no such config"
59
-
60 58
 	_, _, err = cli.ConfigInspectWithRaw(context.Background(), id)
61
-	c.Assert(err.Error(), checker.Contains, expected)
59
+	c.Assert(err.Error(), checker.Contains, "No such config")
62 60
 }
63 61
 
64 62
 func (s *DockerSwarmSuite) TestAPISwarmConfigsUpdate(c *check.C) {
... ...
@@ -64,14 +64,12 @@ func (s *DockerSwarmSuite) TestAPISwarmSecretsDelete(c *check.C) {
64 64
 	c.Assert(err, checker.IsNil)
65 65
 	defer cli.Close()
66 66
 
67
-	expected := "no such secret"
68 67
 	_, _, err = cli.SecretInspectWithRaw(context.Background(), id)
69
-	c.Assert(err.Error(), checker.Contains, expected)
68
+	c.Assert(err.Error(), checker.Contains, "No such secret")
70 69
 
71 70
 	id = "non-existing"
72
-	expected = "secret non-existing not found"
73 71
 	err = cli.SecretRemove(context.Background(), id)
74
-	c.Assert(err.Error(), checker.Contains, expected)
72
+	c.Assert(err.Error(), checker.Contains, "No such secret: non-existing")
75 73
 }
76 74
 
77 75
 func (s *DockerSwarmSuite) TestAPISwarmSecretsUpdate(c *check.C) {