Selaa lähdekoodia

Cleanup client not found errors.

And fix remove calls to return a notFound error

Signed-off-by: Daniel Nephin <dnephin@docker.com>
Daniel Nephin 7 vuotta sitten
vanhempi
commit
81bb9978ab

+ 1 - 5
client/checkpoint_list.go

@@ -2,7 +2,6 @@ package client
 
 
 import (
 import (
 	"encoding/json"
 	"encoding/json"
-	"net/http"
 	"net/url"
 	"net/url"
 
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
@@ -20,10 +19,7 @@ func (cli *Client) CheckpointList(ctx context.Context, container string, options
 
 
 	resp, err := cli.get(ctx, "/containers/"+container+"/checkpoints", query, nil)
 	resp, err := cli.get(ctx, "/containers/"+container+"/checkpoints", query, nil)
 	if err != nil {
 	if err != nil {
-		if resp.statusCode == http.StatusNotFound {
-			return checkpoints, containerNotFoundError{container}
-		}
-		return checkpoints, err
+		return checkpoints, wrapResponseError(err, resp, "container", container)
 	}
 	}
 
 
 	err = json.NewDecoder(resp.body).Decode(&checkpoints)
 	err = json.NewDecoder(resp.body).Decode(&checkpoints)

+ 1 - 5
client/config_inspect.go

@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"bytes"
 	"encoding/json"
 	"encoding/json"
 	"io/ioutil"
 	"io/ioutil"
-	"net/http"
 
 
 	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/api/types/swarm"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
@@ -17,10 +16,7 @@ func (cli *Client) ConfigInspectWithRaw(ctx context.Context, id string) (swarm.C
 	}
 	}
 	resp, err := cli.get(ctx, "/configs/"+id, nil, nil)
 	resp, err := cli.get(ctx, "/configs/"+id, nil, nil)
 	if err != nil {
 	if err != nil {
-		if resp.statusCode == http.StatusNotFound {
-			return swarm.Config{}, nil, configNotFoundError{id}
-		}
-		return swarm.Config{}, nil, err
+		return swarm.Config{}, nil, wrapResponseError(err, resp, "config", id)
 	}
 	}
 	defer ensureReaderClosed(resp)
 	defer ensureReaderClosed(resp)
 
 

+ 1 - 1
client/config_remove.go

@@ -9,5 +9,5 @@ func (cli *Client) ConfigRemove(ctx context.Context, id string) error {
 	}
 	}
 	resp, err := cli.delete(ctx, "/configs/"+id, nil, nil)
 	resp, err := cli.delete(ctx, "/configs/"+id, nil, nil)
 	ensureReaderClosed(resp)
 	ensureReaderClosed(resp)
-	return err
+	return wrapResponseError(err, resp, "config", id)
 }
 }

+ 1 - 1
client/container_create.go

@@ -45,7 +45,7 @@ func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config
 	serverResp, err := cli.post(ctx, "/containers/create", query, body, nil)
 	serverResp, err := cli.post(ctx, "/containers/create", query, body, nil)
 	if err != nil {
 	if err != nil {
 		if serverResp.statusCode == 404 && strings.Contains(err.Error(), "No such image") {
 		if serverResp.statusCode == 404 && strings.Contains(err.Error(), "No such image") {
-			return response, imageNotFoundError{config.Image}
+			return response, objectNotFoundError{object: "image", id: config.Image}
 		}
 		}
 		return response, err
 		return response, err
 	}
 	}

+ 2 - 9
client/container_inspect.go

@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"bytes"
 	"encoding/json"
 	"encoding/json"
 	"io/ioutil"
 	"io/ioutil"
-	"net/http"
 	"net/url"
 	"net/url"
 
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
@@ -15,10 +14,7 @@ import (
 func (cli *Client) ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error) {
 func (cli *Client) ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error) {
 	serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", nil, nil)
 	serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", nil, nil)
 	if err != nil {
 	if err != nil {
-		if serverResp.statusCode == http.StatusNotFound {
-			return types.ContainerJSON{}, containerNotFoundError{containerID}
-		}
-		return types.ContainerJSON{}, err
+		return types.ContainerJSON{}, wrapResponseError(err, serverResp, "container", containerID)
 	}
 	}
 
 
 	var response types.ContainerJSON
 	var response types.ContainerJSON
@@ -35,10 +31,7 @@ func (cli *Client) ContainerInspectWithRaw(ctx context.Context, containerID stri
 	}
 	}
 	serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", query, nil)
 	serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", query, nil)
 	if err != nil {
 	if err != nil {
-		if serverResp.statusCode == http.StatusNotFound {
-			return types.ContainerJSON{}, nil, containerNotFoundError{containerID}
-		}
-		return types.ContainerJSON{}, nil, err
+		return types.ContainerJSON{}, nil, wrapResponseError(err, serverResp, "container", containerID)
 	}
 	}
 	defer ensureReaderClosed(serverResp)
 	defer ensureReaderClosed(serverResp)
 
 

+ 1 - 1
client/container_remove.go

@@ -23,5 +23,5 @@ func (cli *Client) ContainerRemove(ctx context.Context, containerID string, opti
 
 
 	resp, err := cli.delete(ctx, "/containers/"+containerID, query, nil)
 	resp, err := cli.delete(ctx, "/containers/"+containerID, query, nil)
 	ensureReaderClosed(resp)
 	ensureReaderClosed(resp)
-	return err
+	return wrapResponseError(err, resp, "container", containerID)
 }
 }

+ 11 - 5
client/container_remove_test.go

@@ -9,6 +9,7 @@ import (
 	"testing"
 	"testing"
 
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
+	"github.com/stretchr/testify/assert"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
 
 
@@ -17,9 +18,16 @@ func TestContainerRemoveError(t *testing.T) {
 		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
 		client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
 	}
 	}
 	err := client.ContainerRemove(context.Background(), "container_id", types.ContainerRemoveOptions{})
 	err := client.ContainerRemove(context.Background(), "container_id", types.ContainerRemoveOptions{})
-	if err == nil || err.Error() != "Error response from daemon: Server error" {
-		t.Fatalf("expected a Server Error, got %v", err)
+	assert.EqualError(t, err, "Error response from daemon: Server error")
+}
+
+func TestContainerRemoveNotFoundError(t *testing.T) {
+	client := &Client{
+		client: newMockClient(errorMock(http.StatusNotFound, "missing")),
 	}
 	}
+	err := client.ContainerRemove(context.Background(), "container_id", types.ContainerRemoveOptions{})
+	assert.EqualError(t, err, "Error: No such container: container_id")
+	assert.True(t, IsErrNotFound(err))
 }
 }
 
 
 func TestContainerRemove(t *testing.T) {
 func TestContainerRemove(t *testing.T) {
@@ -53,7 +61,5 @@ func TestContainerRemove(t *testing.T) {
 		RemoveVolumes: true,
 		RemoveVolumes: true,
 		Force:         true,
 		Force:         true,
 	})
 	})
-	if err != nil {
-		t.Fatal(err)
-	}
+	assert.NoError(t, err)
 }
 }

+ 24 - 153
client/errors.go

@@ -3,6 +3,8 @@ package client
 import (
 import (
 	"fmt"
 	"fmt"
 
 
+	"net/http"
+
 	"github.com/docker/docker/api/types/versions"
 	"github.com/docker/docker/api/types/versions"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 )
 )
@@ -43,19 +45,28 @@ func IsErrNotFound(err error) bool {
 	return ok && te.NotFound()
 	return ok && te.NotFound()
 }
 }
 
 
-// imageNotFoundError implements an error returned when an image is not in the docker host.
-type imageNotFoundError struct {
-	imageID string
+type objectNotFoundError struct {
+	object string
+	id     string
 }
 }
 
 
-// NotFound indicates that this error type is of NotFound
-func (e imageNotFoundError) NotFound() bool {
+func (e objectNotFoundError) NotFound() bool {
 	return true
 	return true
 }
 }
 
 
-// Error returns a string representation of an imageNotFoundError
-func (e imageNotFoundError) Error() string {
-	return fmt.Sprintf("Error: No such image: %s", e.imageID)
+func (e objectNotFoundError) Error() string {
+	return fmt.Sprintf("Error: No such %s: %s", e.object, e.id)
+}
+
+func wrapResponseError(err error, resp serverResponse, object, id string) error {
+	switch {
+	case err == nil:
+		return nil
+	case resp.statusCode == http.StatusNotFound:
+		return objectNotFoundError{object: object, id: id}
+	default:
+		return err
+	}
 }
 }
 
 
 // IsErrImageNotFound returns true if the error is caused
 // IsErrImageNotFound returns true if the error is caused
@@ -66,21 +77,6 @@ func IsErrImageNotFound(err error) bool {
 	return IsErrNotFound(err)
 	return IsErrNotFound(err)
 }
 }
 
 
-// containerNotFoundError implements an error returned when a container is not in the docker host.
-type containerNotFoundError struct {
-	containerID string
-}
-
-// NotFound indicates that this error type is of NotFound
-func (e containerNotFoundError) NotFound() bool {
-	return true
-}
-
-// Error returns a string representation of a containerNotFoundError
-func (e containerNotFoundError) Error() string {
-	return fmt.Sprintf("Error: No such container: %s", e.containerID)
-}
-
 // IsErrContainerNotFound returns true if the error is caused
 // IsErrContainerNotFound returns true if the error is caused
 // when a container is not found in the docker host.
 // when a container is not found in the docker host.
 //
 //
@@ -89,21 +85,6 @@ func IsErrContainerNotFound(err error) bool {
 	return IsErrNotFound(err)
 	return IsErrNotFound(err)
 }
 }
 
 
-// networkNotFoundError implements an error returned when a network is not in the docker host.
-type networkNotFoundError struct {
-	networkID string
-}
-
-// NotFound indicates that this error type is of NotFound
-func (e networkNotFoundError) NotFound() bool {
-	return true
-}
-
-// Error returns a string representation of a networkNotFoundError
-func (e networkNotFoundError) Error() string {
-	return fmt.Sprintf("Error: No such network: %s", e.networkID)
-}
-
 // IsErrNetworkNotFound returns true if the error is caused
 // IsErrNetworkNotFound returns true if the error is caused
 // when a network is not found in the docker host.
 // when a network is not found in the docker host.
 //
 //
@@ -112,21 +93,6 @@ func IsErrNetworkNotFound(err error) bool {
 	return IsErrNotFound(err)
 	return IsErrNotFound(err)
 }
 }
 
 
-// volumeNotFoundError implements an error returned when a volume is not in the docker host.
-type volumeNotFoundError struct {
-	volumeID string
-}
-
-// NotFound indicates that this error type is of NotFound
-func (e volumeNotFoundError) NotFound() bool {
-	return true
-}
-
-// Error returns a string representation of a volumeNotFoundError
-func (e volumeNotFoundError) Error() string {
-	return fmt.Sprintf("Error: No such volume: %s", e.volumeID)
-}
-
 // IsErrVolumeNotFound returns true if the error is caused
 // IsErrVolumeNotFound returns true if the error is caused
 // when a volume is not found in the docker host.
 // when a volume is not found in the docker host.
 //
 //
@@ -152,43 +118,12 @@ func IsErrUnauthorized(err error) bool {
 	return ok
 	return ok
 }
 }
 
 
-// nodeNotFoundError implements an error returned when a node is not found.
-type nodeNotFoundError struct {
-	nodeID string
-}
-
-// Error returns a string representation of a nodeNotFoundError
-func (e nodeNotFoundError) Error() string {
-	return fmt.Sprintf("Error: No such node: %s", e.nodeID)
-}
-
-// NotFound indicates that this error type is of NotFound
-func (e nodeNotFoundError) NotFound() bool {
-	return true
-}
-
 // IsErrNodeNotFound returns true if the error is caused
 // IsErrNodeNotFound returns true if the error is caused
 // when a node is not found.
 // when a node is not found.
 //
 //
 // Deprecated: Use IsErrNotFound
 // Deprecated: Use IsErrNotFound
 func IsErrNodeNotFound(err error) bool {
 func IsErrNodeNotFound(err error) bool {
-	_, ok := err.(nodeNotFoundError)
-	return ok
-}
-
-// serviceNotFoundError implements an error returned when a service is not found.
-type serviceNotFoundError struct {
-	serviceID string
-}
-
-// Error returns a string representation of a serviceNotFoundError
-func (e serviceNotFoundError) Error() string {
-	return fmt.Sprintf("Error: No such service: %s", e.serviceID)
-}
-
-// NotFound indicates that this error type is of NotFound
-func (e serviceNotFoundError) NotFound() bool {
-	return true
+	return IsErrNotFound(err)
 }
 }
 
 
 // IsErrServiceNotFound returns true if the error is caused
 // IsErrServiceNotFound returns true if the error is caused
@@ -196,23 +131,7 @@ func (e serviceNotFoundError) NotFound() bool {
 //
 //
 // Deprecated: Use IsErrNotFound
 // Deprecated: Use IsErrNotFound
 func IsErrServiceNotFound(err error) bool {
 func IsErrServiceNotFound(err error) bool {
-	_, ok := err.(serviceNotFoundError)
-	return ok
-}
-
-// taskNotFoundError implements an error returned when a task is not found.
-type taskNotFoundError struct {
-	taskID string
-}
-
-// Error returns a string representation of a taskNotFoundError
-func (e taskNotFoundError) Error() string {
-	return fmt.Sprintf("Error: No such task: %s", e.taskID)
-}
-
-// NotFound indicates that this error type is of NotFound
-func (e taskNotFoundError) NotFound() bool {
-	return true
+	return IsErrNotFound(err)
 }
 }
 
 
 // IsErrTaskNotFound returns true if the error is caused
 // IsErrTaskNotFound returns true if the error is caused
@@ -220,8 +139,7 @@ func (e taskNotFoundError) NotFound() bool {
 //
 //
 // Deprecated: Use IsErrNotFound
 // Deprecated: Use IsErrNotFound
 func IsErrTaskNotFound(err error) bool {
 func IsErrTaskNotFound(err error) bool {
-	_, ok := err.(taskNotFoundError)
-	return ok
+	return IsErrNotFound(err)
 }
 }
 
 
 type pluginPermissionDenied struct {
 type pluginPermissionDenied struct {
@@ -248,43 +166,12 @@ func (cli *Client) NewVersionError(APIrequired, feature string) error {
 	return nil
 	return nil
 }
 }
 
 
-// secretNotFoundError implements an error returned when a secret is not found.
-type secretNotFoundError struct {
-	name string
-}
-
-// Error returns a string representation of a secretNotFoundError
-func (e secretNotFoundError) Error() string {
-	return fmt.Sprintf("Error: no such secret: %s", e.name)
-}
-
-// NotFound indicates that this error type is of NotFound
-func (e secretNotFoundError) NotFound() bool {
-	return true
-}
-
 // IsErrSecretNotFound returns true if the error is caused
 // IsErrSecretNotFound returns true if the error is caused
 // when a secret is not found.
 // when a secret is not found.
 //
 //
 // Deprecated: Use IsErrNotFound
 // Deprecated: Use IsErrNotFound
 func IsErrSecretNotFound(err error) bool {
 func IsErrSecretNotFound(err error) bool {
-	_, ok := err.(secretNotFoundError)
-	return ok
-}
-
-// configNotFoundError implements an error returned when a config is not found.
-type configNotFoundError struct {
-	name string
-}
-
-// Error returns a string representation of a configNotFoundError
-func (e configNotFoundError) Error() string {
-	return fmt.Sprintf("Error: no such config: %s", e.name)
-}
-
-// NotFound indicates that this error type is of NotFound
-func (e configNotFoundError) NotFound() bool {
-	return true
+	return IsErrNotFound(err)
 }
 }
 
 
 // IsErrConfigNotFound returns true if the error is caused
 // IsErrConfigNotFound returns true if the error is caused
@@ -292,23 +179,7 @@ func (e configNotFoundError) NotFound() bool {
 //
 //
 // Deprecated: Use IsErrNotFound
 // Deprecated: Use IsErrNotFound
 func IsErrConfigNotFound(err error) bool {
 func IsErrConfigNotFound(err error) bool {
-	_, ok := err.(configNotFoundError)
-	return ok
-}
-
-// pluginNotFoundError implements an error returned when a plugin is not in the docker host.
-type pluginNotFoundError struct {
-	name string
-}
-
-// NotFound indicates that this error type is of NotFound
-func (e pluginNotFoundError) NotFound() bool {
-	return true
-}
-
-// Error returns a string representation of a pluginNotFoundError
-func (e pluginNotFoundError) Error() string {
-	return fmt.Sprintf("Error: No such plugin: %s", e.name)
+	return IsErrNotFound(err)
 }
 }
 
 
 // IsErrPluginNotFound returns true if the error is caused
 // IsErrPluginNotFound returns true if the error is caused

+ 1 - 5
client/image_inspect.go

@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"bytes"
 	"encoding/json"
 	"encoding/json"
 	"io/ioutil"
 	"io/ioutil"
-	"net/http"
 
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
@@ -14,10 +13,7 @@ import (
 func (cli *Client) ImageInspectWithRaw(ctx context.Context, imageID string) (types.ImageInspect, []byte, error) {
 func (cli *Client) ImageInspectWithRaw(ctx context.Context, imageID string) (types.ImageInspect, []byte, error) {
 	serverResp, err := cli.get(ctx, "/images/"+imageID+"/json", nil, nil)
 	serverResp, err := cli.get(ctx, "/images/"+imageID+"/json", nil, nil)
 	if err != nil {
 	if err != nil {
-		if serverResp.statusCode == http.StatusNotFound {
-			return types.ImageInspect{}, nil, imageNotFoundError{imageID}
-		}
-		return types.ImageInspect{}, nil, err
+		return types.ImageInspect{}, nil, wrapResponseError(err, serverResp, "image", imageID)
 	}
 	}
 	defer ensureReaderClosed(serverResp)
 	defer ensureReaderClosed(serverResp)
 
 

+ 2 - 6
client/image_remove.go

@@ -2,7 +2,6 @@ package client
 
 
 import (
 import (
 	"encoding/json"
 	"encoding/json"
-	"net/http"
 	"net/url"
 	"net/url"
 
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
@@ -20,15 +19,12 @@ func (cli *Client) ImageRemove(ctx context.Context, imageID string, options type
 		query.Set("noprune", "1")
 		query.Set("noprune", "1")
 	}
 	}
 
 
+	var dels []types.ImageDeleteResponseItem
 	resp, err := cli.delete(ctx, "/images/"+imageID, query, nil)
 	resp, err := cli.delete(ctx, "/images/"+imageID, query, nil)
 	if err != nil {
 	if err != nil {
-		if resp.statusCode == http.StatusNotFound {
-			return nil, imageNotFoundError{imageID}
-		}
-		return nil, err
+		return dels, wrapResponseError(err, resp, "image", imageID)
 	}
 	}
 
 
-	var dels []types.ImageDeleteResponseItem
 	err = json.NewDecoder(resp.body).Decode(&dels)
 	err = json.NewDecoder(resp.body).Decode(&dels)
 	ensureReaderClosed(resp)
 	ensureReaderClosed(resp)
 	return dels, err
 	return dels, err

+ 5 - 7
client/image_remove_test.go

@@ -10,6 +10,7 @@ import (
 	"testing"
 	"testing"
 
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
+	"github.com/stretchr/testify/assert"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
 
 
@@ -19,20 +20,17 @@ func TestImageRemoveError(t *testing.T) {
 	}
 	}
 
 
 	_, err := client.ImageRemove(context.Background(), "image_id", types.ImageRemoveOptions{})
 	_, err := client.ImageRemove(context.Background(), "image_id", types.ImageRemoveOptions{})
-	if err == nil || err.Error() != "Error response from daemon: Server error" {
-		t.Fatalf("expected a Server Error, got %v", err)
-	}
+	assert.EqualError(t, err, "Error response from daemon: Server error")
 }
 }
 
 
 func TestImageRemoveImageNotFound(t *testing.T) {
 func TestImageRemoveImageNotFound(t *testing.T) {
 	client := &Client{
 	client := &Client{
-		client: newMockClient(errorMock(http.StatusNotFound, "Server error")),
+		client: newMockClient(errorMock(http.StatusNotFound, "missing")),
 	}
 	}
 
 
 	_, err := client.ImageRemove(context.Background(), "unknown", types.ImageRemoveOptions{})
 	_, err := client.ImageRemove(context.Background(), "unknown", types.ImageRemoveOptions{})
-	if err == nil || !IsErrNotFound(err) {
-		t.Fatalf("expected an imageNotFoundError error, got %v", err)
-	}
+	assert.EqualError(t, err, "Error: No such image: unknown")
+	assert.True(t, IsErrNotFound(err))
 }
 }
 
 
 func TestImageRemove(t *testing.T) {
 func TestImageRemove(t *testing.T) {

+ 1 - 5
client/network_inspect.go

@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"bytes"
 	"encoding/json"
 	"encoding/json"
 	"io/ioutil"
 	"io/ioutil"
-	"net/http"
 	"net/url"
 	"net/url"
 
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
@@ -33,10 +32,7 @@ func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string,
 	}
 	}
 	resp, err = cli.get(ctx, "/networks/"+networkID, query, nil)
 	resp, err = cli.get(ctx, "/networks/"+networkID, query, nil)
 	if err != nil {
 	if err != nil {
-		if resp.statusCode == http.StatusNotFound {
-			return networkResource, nil, networkNotFoundError{networkID}
-		}
-		return networkResource, nil, err
+		return networkResource, nil, wrapResponseError(err, resp, "network", networkID)
 	}
 	}
 	defer ensureReaderClosed(resp)
 	defer ensureReaderClosed(resp)
 
 

+ 5 - 8
client/network_inspect_test.go

@@ -21,20 +21,17 @@ func TestNetworkInspectError(t *testing.T) {
 	}
 	}
 
 
 	_, err := client.NetworkInspect(context.Background(), "nothing", types.NetworkInspectOptions{})
 	_, err := client.NetworkInspect(context.Background(), "nothing", types.NetworkInspectOptions{})
-	if err == nil || err.Error() != "Error response from daemon: Server error" {
-		t.Fatalf("expected a Server Error, got %v", err)
-	}
+	assert.EqualError(t, err, "Error response from daemon: Server error")
 }
 }
 
 
-func TestNetworkInspectContainerNotFound(t *testing.T) {
+func TestNetworkInspectNotFoundError(t *testing.T) {
 	client := &Client{
 	client := &Client{
-		client: newMockClient(errorMock(http.StatusNotFound, "Server error")),
+		client: newMockClient(errorMock(http.StatusNotFound, "missing")),
 	}
 	}
 
 
 	_, err := client.NetworkInspect(context.Background(), "unknown", types.NetworkInspectOptions{})
 	_, err := client.NetworkInspect(context.Background(), "unknown", types.NetworkInspectOptions{})
-	if err == nil || !IsErrNetworkNotFound(err) {
-		t.Fatalf("expected a networkNotFound error, got %v", err)
-	}
+	assert.EqualError(t, err, "Error: No such network: unknown")
+	assert.True(t, IsErrNotFound(err))
 }
 }
 
 
 func TestNetworkInspect(t *testing.T) {
 func TestNetworkInspect(t *testing.T) {

+ 1 - 1
client/network_remove.go

@@ -6,5 +6,5 @@ import "golang.org/x/net/context"
 func (cli *Client) NetworkRemove(ctx context.Context, networkID string) error {
 func (cli *Client) NetworkRemove(ctx context.Context, networkID string) error {
 	resp, err := cli.delete(ctx, "/networks/"+networkID, nil, nil)
 	resp, err := cli.delete(ctx, "/networks/"+networkID, nil, nil)
 	ensureReaderClosed(resp)
 	ensureReaderClosed(resp)
-	return err
+	return wrapResponseError(err, resp, "network", networkID)
 }
 }

+ 1 - 5
client/node_inspect.go

@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"bytes"
 	"encoding/json"
 	"encoding/json"
 	"io/ioutil"
 	"io/ioutil"
-	"net/http"
 
 
 	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/api/types/swarm"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
@@ -14,10 +13,7 @@ import (
 func (cli *Client) NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error) {
 func (cli *Client) NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error) {
 	serverResp, err := cli.get(ctx, "/nodes/"+nodeID, nil, nil)
 	serverResp, err := cli.get(ctx, "/nodes/"+nodeID, nil, nil)
 	if err != nil {
 	if err != nil {
-		if serverResp.statusCode == http.StatusNotFound {
-			return swarm.Node{}, nil, nodeNotFoundError{nodeID}
-		}
-		return swarm.Node{}, nil, err
+		return swarm.Node{}, nil, wrapResponseError(err, serverResp, "node", nodeID)
 	}
 	}
 	defer ensureReaderClosed(serverResp)
 	defer ensureReaderClosed(serverResp)
 
 

+ 1 - 1
client/node_remove.go

@@ -17,5 +17,5 @@ func (cli *Client) NodeRemove(ctx context.Context, nodeID string, options types.
 
 
 	resp, err := cli.delete(ctx, "/nodes/"+nodeID, query, nil)
 	resp, err := cli.delete(ctx, "/nodes/"+nodeID, query, nil)
 	ensureReaderClosed(resp)
 	ensureReaderClosed(resp)
-	return err
+	return wrapResponseError(err, resp, "node", nodeID)
 }
 }

+ 1 - 3
client/ping.go

@@ -28,7 +28,5 @@ func (cli *Client) Ping(ctx context.Context) (types.Ping, error) {
 		}
 		}
 		ping.OSType = serverResp.header.Get("OSType")
 		ping.OSType = serverResp.header.Get("OSType")
 	}
 	}
-
-	err = cli.checkResponseErr(serverResp)
-	return ping, err
+	return ping, cli.checkResponseErr(serverResp)
 }
 }

+ 1 - 5
client/plugin_inspect.go

@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"bytes"
 	"encoding/json"
 	"encoding/json"
 	"io/ioutil"
 	"io/ioutil"
-	"net/http"
 
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
@@ -14,10 +13,7 @@ import (
 func (cli *Client) PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error) {
 func (cli *Client) PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error) {
 	resp, err := cli.get(ctx, "/plugins/"+name+"/json", nil, nil)
 	resp, err := cli.get(ctx, "/plugins/"+name+"/json", nil, nil)
 	if err != nil {
 	if err != nil {
-		if resp.statusCode == http.StatusNotFound {
-			return nil, nil, pluginNotFoundError{name}
-		}
-		return nil, nil, err
+		return nil, nil, wrapResponseError(err, resp, "plugin", name)
 	}
 	}
 
 
 	defer ensureReaderClosed(resp)
 	defer ensureReaderClosed(resp)

+ 1 - 1
client/plugin_remove.go

@@ -16,5 +16,5 @@ func (cli *Client) PluginRemove(ctx context.Context, name string, options types.
 
 
 	resp, err := cli.delete(ctx, "/plugins/"+name, query, nil)
 	resp, err := cli.delete(ctx, "/plugins/"+name, query, nil)
 	ensureReaderClosed(resp)
 	ensureReaderClosed(resp)
-	return err
+	return wrapResponseError(err, resp, "plugin", name)
 }
 }

+ 1 - 1
client/request.go

@@ -203,7 +203,7 @@ func (cli *Client) checkResponseErr(serverResp serverResponse) error {
 		return err
 		return err
 	}
 	}
 	if len(body) == 0 {
 	if len(body) == 0 {
-		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)
+		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)
 	}
 	}
 
 
 	var ct string
 	var ct string

+ 1 - 5
client/secret_inspect.go

@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"bytes"
 	"encoding/json"
 	"encoding/json"
 	"io/ioutil"
 	"io/ioutil"
-	"net/http"
 
 
 	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/api/types/swarm"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
@@ -17,10 +16,7 @@ func (cli *Client) SecretInspectWithRaw(ctx context.Context, id string) (swarm.S
 	}
 	}
 	resp, err := cli.get(ctx, "/secrets/"+id, nil, nil)
 	resp, err := cli.get(ctx, "/secrets/"+id, nil, nil)
 	if err != nil {
 	if err != nil {
-		if resp.statusCode == http.StatusNotFound {
-			return swarm.Secret{}, nil, secretNotFoundError{id}
-		}
-		return swarm.Secret{}, nil, err
+		return swarm.Secret{}, nil, wrapResponseError(err, resp, "secret", id)
 	}
 	}
 	defer ensureReaderClosed(resp)
 	defer ensureReaderClosed(resp)
 
 

+ 1 - 1
client/secret_remove.go

@@ -9,5 +9,5 @@ func (cli *Client) SecretRemove(ctx context.Context, id string) error {
 	}
 	}
 	resp, err := cli.delete(ctx, "/secrets/"+id, nil, nil)
 	resp, err := cli.delete(ctx, "/secrets/"+id, nil, nil)
 	ensureReaderClosed(resp)
 	ensureReaderClosed(resp)
-	return err
+	return wrapResponseError(err, resp, "secret", id)
 }
 }

+ 1 - 5
client/service_inspect.go

@@ -5,7 +5,6 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
 	"io/ioutil"
 	"io/ioutil"
-	"net/http"
 	"net/url"
 	"net/url"
 
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
@@ -19,10 +18,7 @@ func (cli *Client) ServiceInspectWithRaw(ctx context.Context, serviceID string,
 	query.Set("insertDefaults", fmt.Sprintf("%v", opts.InsertDefaults))
 	query.Set("insertDefaults", fmt.Sprintf("%v", opts.InsertDefaults))
 	serverResp, err := cli.get(ctx, "/services/"+serviceID, query, nil)
 	serverResp, err := cli.get(ctx, "/services/"+serviceID, query, nil)
 	if err != nil {
 	if err != nil {
-		if serverResp.statusCode == http.StatusNotFound {
-			return swarm.Service{}, nil, serviceNotFoundError{serviceID}
-		}
-		return swarm.Service{}, nil, err
+		return swarm.Service{}, nil, wrapResponseError(err, serverResp, "service", serviceID)
 	}
 	}
 	defer ensureReaderClosed(serverResp)
 	defer ensureReaderClosed(serverResp)
 
 

+ 1 - 1
client/service_remove.go

@@ -6,5 +6,5 @@ import "golang.org/x/net/context"
 func (cli *Client) ServiceRemove(ctx context.Context, serviceID string) error {
 func (cli *Client) ServiceRemove(ctx context.Context, serviceID string) error {
 	resp, err := cli.delete(ctx, "/services/"+serviceID, nil, nil)
 	resp, err := cli.delete(ctx, "/services/"+serviceID, nil, nil)
 	ensureReaderClosed(resp)
 	ensureReaderClosed(resp)
-	return err
+	return wrapResponseError(err, resp, "service", serviceID)
 }
 }

+ 11 - 2
client/service_remove_test.go

@@ -8,6 +8,7 @@ import (
 	"strings"
 	"strings"
 	"testing"
 	"testing"
 
 
+	"github.com/stretchr/testify/assert"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
 
 
@@ -17,9 +18,17 @@ func TestServiceRemoveError(t *testing.T) {
 	}
 	}
 
 
 	err := client.ServiceRemove(context.Background(), "service_id")
 	err := client.ServiceRemove(context.Background(), "service_id")
-	if err == nil || err.Error() != "Error response from daemon: Server error" {
-		t.Fatalf("expected a Server Error, got %v", err)
+	assert.EqualError(t, err, "Error response from daemon: Server error")
+}
+
+func TestServiceRemoveNotFoundError(t *testing.T) {
+	client := &Client{
+		client: newMockClient(errorMock(http.StatusNotFound, "missing")),
 	}
 	}
+
+	err := client.ServiceRemove(context.Background(), "service_id")
+	assert.EqualError(t, err, "Error: No such service: service_id")
+	assert.True(t, IsErrNotFound(err))
 }
 }
 
 
 func TestServiceRemove(t *testing.T) {
 func TestServiceRemove(t *testing.T) {

+ 1 - 6
client/task_inspect.go

@@ -4,10 +4,8 @@ import (
 	"bytes"
 	"bytes"
 	"encoding/json"
 	"encoding/json"
 	"io/ioutil"
 	"io/ioutil"
-	"net/http"
 
 
 	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/api/types/swarm"
-
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
 
 
@@ -15,10 +13,7 @@ import (
 func (cli *Client) TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error) {
 func (cli *Client) TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error) {
 	serverResp, err := cli.get(ctx, "/tasks/"+taskID, nil, nil)
 	serverResp, err := cli.get(ctx, "/tasks/"+taskID, nil, nil)
 	if err != nil {
 	if err != nil {
-		if serverResp.statusCode == http.StatusNotFound {
-			return swarm.Task{}, nil, taskNotFoundError{taskID}
-		}
-		return swarm.Task{}, nil, err
+		return swarm.Task{}, nil, wrapResponseError(err, serverResp, "task", taskID)
 	}
 	}
 	defer ensureReaderClosed(serverResp)
 	defer ensureReaderClosed(serverResp)
 
 

+ 2 - 6
client/volume_inspect.go

@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"bytes"
 	"encoding/json"
 	"encoding/json"
 	"io/ioutil"
 	"io/ioutil"
-	"net/http"
 	"path"
 	"path"
 
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
@@ -23,16 +22,13 @@ func (cli *Client) VolumeInspectWithRaw(ctx context.Context, volumeID string) (t
 	// request url will not contain a trailing / which calls the volume list API
 	// request url will not contain a trailing / which calls the volume list API
 	// instead of volume inspect
 	// instead of volume inspect
 	if volumeID == "" {
 	if volumeID == "" {
-		return types.Volume{}, nil, volumeNotFoundError{volumeID}
+		return types.Volume{}, nil, objectNotFoundError{object: "volume", id: volumeID}
 	}
 	}
 
 
 	var volume types.Volume
 	var volume types.Volume
 	resp, err := cli.get(ctx, path.Join("/volumes", volumeID), nil, nil)
 	resp, err := cli.get(ctx, path.Join("/volumes", volumeID), nil, nil)
 	if err != nil {
 	if err != nil {
-		if resp.statusCode == http.StatusNotFound {
-			return volume, nil, volumeNotFoundError{volumeID}
-		}
-		return volume, nil, err
+		return volume, nil, wrapResponseError(err, resp, "volume", volumeID)
 	}
 	}
 	defer ensureReaderClosed(resp)
 	defer ensureReaderClosed(resp)
 
 

+ 1 - 1
client/volume_remove.go

@@ -17,5 +17,5 @@ func (cli *Client) VolumeRemove(ctx context.Context, volumeID string, force bool
 	}
 	}
 	resp, err := cli.delete(ctx, "/volumes/"+volumeID, query, nil)
 	resp, err := cli.delete(ctx, "/volumes/"+volumeID, query, nil)
 	ensureReaderClosed(resp)
 	ensureReaderClosed(resp)
-	return err
+	return wrapResponseError(err, resp, "volume", volumeID)
 }
 }

+ 1 - 1
integration-cli/docker_api_containers_test.go

@@ -1617,7 +1617,7 @@ func (s *DockerSuite) TestContainerAPIDeleteWithEmptyName(c *check.C) {
 	defer cli.Close()
 	defer cli.Close()
 
 
 	err = cli.ContainerRemove(context.Background(), "", types.ContainerRemoveOptions{})
 	err = cli.ContainerRemove(context.Background(), "", types.ContainerRemoveOptions{})
-	c.Assert(err.Error(), checker.Contains, "Error response from daemon: page not found")
+	c.Assert(err.Error(), checker.Contains, "No such container")
 }
 }
 
 
 func (s *DockerSuite) TestContainerAPIStatsWithNetworkDisabled(c *check.C) {
 func (s *DockerSuite) TestContainerAPIStatsWithNetworkDisabled(c *check.C) {

+ 1 - 3
integration-cli/docker_api_swarm_config_test.go

@@ -55,10 +55,8 @@ func (s *DockerSwarmSuite) TestAPISwarmConfigsDelete(c *check.C) {
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
 	defer cli.Close()
 	defer cli.Close()
 
 
-	expected := "no such config"
-
 	_, _, err = cli.ConfigInspectWithRaw(context.Background(), id)
 	_, _, err = cli.ConfigInspectWithRaw(context.Background(), id)
-	c.Assert(err.Error(), checker.Contains, expected)
+	c.Assert(err.Error(), checker.Contains, "No such config")
 }
 }
 
 
 func (s *DockerSwarmSuite) TestAPISwarmConfigsUpdate(c *check.C) {
 func (s *DockerSwarmSuite) TestAPISwarmConfigsUpdate(c *check.C) {

+ 2 - 4
integration-cli/docker_api_swarm_secret_test.go

@@ -64,14 +64,12 @@ func (s *DockerSwarmSuite) TestAPISwarmSecretsDelete(c *check.C) {
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
 	defer cli.Close()
 	defer cli.Close()
 
 
-	expected := "no such secret"
 	_, _, err = cli.SecretInspectWithRaw(context.Background(), id)
 	_, _, err = cli.SecretInspectWithRaw(context.Background(), id)
-	c.Assert(err.Error(), checker.Contains, expected)
+	c.Assert(err.Error(), checker.Contains, "No such secret")
 
 
 	id = "non-existing"
 	id = "non-existing"
-	expected = "secret non-existing not found"
 	err = cli.SecretRemove(context.Background(), id)
 	err = cli.SecretRemove(context.Background(), id)
-	c.Assert(err.Error(), checker.Contains, expected)
+	c.Assert(err.Error(), checker.Contains, "No such secret: non-existing")
 }
 }
 
 
 func (s *DockerSwarmSuite) TestAPISwarmSecretsUpdate(c *check.C) {
 func (s *DockerSwarmSuite) TestAPISwarmSecretsUpdate(c *check.C) {