Browse code

Remove string checking in API error handling

Use strongly typed errors to set HTTP status codes.
Error interfaces are defined in the api/errors package and errors
returned from controllers are checked against these interfaces.

Errors can be wraeped in a pkg/errors.Causer, as long as somewhere in the
line of causes one of the interfaces is implemented. The special error
interfaces take precedence over Causer, meaning if both Causer and one
of the new error interfaces are implemented, the Causer is not
traversed.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>

Brian Goff authored on 2017/07/19 23:20:13
Showing 127 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,54 @@
0
+package errdefs
1
+
2
+// ErrNotFound signals that the requested object doesn't exist
3
+type ErrNotFound interface {
4
+	NotFound()
5
+}
6
+
7
+// ErrInvalidParameter signals that the user input is invalid
8
+type ErrInvalidParameter interface {
9
+	InvalidParameter()
10
+}
11
+
12
+// ErrConflict signals that some internal state conflicts with the requested action and can't be performed.
13
+// A change in state should be able to clear this error.
14
+type ErrConflict interface {
15
+	Conflict()
16
+}
17
+
18
+// ErrUnauthorized is used to signify that the user is not authorized to perform a specific action
19
+type ErrUnauthorized interface {
20
+	Unauthorized()
21
+}
22
+
23
+// ErrUnavailable signals that the requested action/subsystem is not available.
24
+type ErrUnavailable interface {
25
+	Unavailable()
26
+}
27
+
28
+// ErrForbidden signals that the requested action cannot be performed under any circumstances.
29
+// When a ErrForbidden is returned, the caller should never retry the action.
30
+type ErrForbidden interface {
31
+	Forbidden()
32
+}
33
+
34
+// ErrSystem signals that some internal error occurred.
35
+// An example of this would be a failed mount request.
36
+type ErrSystem interface {
37
+	ErrSystem()
38
+}
39
+
40
+// ErrNotModified signals that an action can't be performed because it's already in the desired state
41
+type ErrNotModified interface {
42
+	NotModified()
43
+}
44
+
45
+// ErrNotImplemented signals that the requested action/feature is not implemented on the system as configured.
46
+type ErrNotImplemented interface {
47
+	NotImplemented()
48
+}
49
+
50
+// ErrUnknown signals that the kind of error that occurred is not known.
51
+type ErrUnknown interface {
52
+	Unknown()
53
+}
0 54
new file mode 100644
... ...
@@ -0,0 +1,8 @@
0
+// Package errdefs defines a set of error interfaces that packages should use for communicating classes of errors.
1
+// Errors that cross the package boundary should implement one (and only one) of these interfaces.
2
+//
3
+// Packages should not reference these interfaces directly, only implement them.
4
+// To check if a particular error implements one of these interfaces, there are helper
5
+// functions provided (e.g. `Is<SomeError>`) which can be used rather than asserting the interfaces directly.
6
+// If you must assert on these interfaces, be sure to check the causal chain (`err.Cause()`).
7
+package errdefs
0 8
new file mode 100644
... ...
@@ -0,0 +1,86 @@
0
+package errdefs
1
+
2
+type causer interface {
3
+	Cause() error
4
+}
5
+
6
+func getImplementer(err error) error {
7
+	switch e := err.(type) {
8
+	case
9
+		ErrNotFound,
10
+		ErrInvalidParameter,
11
+		ErrConflict,
12
+		ErrUnauthorized,
13
+		ErrUnavailable,
14
+		ErrForbidden,
15
+		ErrSystem,
16
+		ErrNotModified,
17
+		ErrNotImplemented,
18
+		ErrUnknown:
19
+		return e
20
+	case causer:
21
+		return getImplementer(e.Cause())
22
+	default:
23
+		return err
24
+	}
25
+}
26
+
27
+// IsNotFound returns if the passed in error is a ErrNotFound
28
+func IsNotFound(err error) bool {
29
+	_, ok := getImplementer(err).(ErrNotFound)
30
+	return ok
31
+}
32
+
33
+// IsInvalidParameter returns if the passed in error is an ErrInvalidParameter
34
+func IsInvalidParameter(err error) bool {
35
+	_, ok := getImplementer(err).(ErrInvalidParameter)
36
+	return ok
37
+}
38
+
39
+// IsConflict returns if the passed in error is a ErrConflict
40
+func IsConflict(err error) bool {
41
+	_, ok := getImplementer(err).(ErrConflict)
42
+	return ok
43
+}
44
+
45
+// IsUnauthorized returns if the the passed in error is an ErrUnauthorized
46
+func IsUnauthorized(err error) bool {
47
+	_, ok := getImplementer(err).(ErrUnauthorized)
48
+	return ok
49
+}
50
+
51
+// IsUnavailable returns if the passed in error is an ErrUnavailable
52
+func IsUnavailable(err error) bool {
53
+	_, ok := getImplementer(err).(ErrUnavailable)
54
+	return ok
55
+}
56
+
57
+// IsForbidden returns if the passed in error is a ErrForbidden
58
+func IsForbidden(err error) bool {
59
+	_, ok := getImplementer(err).(ErrForbidden)
60
+	return ok
61
+}
62
+
63
+// IsSystem returns if the passed in error is a ErrSystem
64
+func IsSystem(err error) bool {
65
+	_, ok := getImplementer(err).(ErrSystem)
66
+	return ok
67
+}
68
+
69
+// IsNotModified returns if the passed in error is a NotModified error
70
+func IsNotModified(err error) bool {
71
+	_, ok := getImplementer(err).(ErrNotModified)
72
+	return ok
73
+}
74
+
75
+// IsNotImplemented returns if the passed in error is a ErrNotImplemented
76
+func IsNotImplemented(err error) bool {
77
+	_, ok := getImplementer(err).(ErrNotImplemented)
78
+	return ok
79
+}
80
+
81
+// IsUnknown returns if the passed in error is an ErrUnknown
82
+func IsUnknown(err error) bool {
83
+	_, ok := getImplementer(err).(ErrUnknown)
84
+	return ok
85
+}
0 86
deleted file mode 100644
... ...
@@ -1,47 +0,0 @@
1
-package errors
2
-
3
-import "net/http"
4
-
5
-// apiError is an error wrapper that also
6
-// holds information about response status codes.
7
-type apiError struct {
8
-	error
9
-	statusCode int
10
-}
11
-
12
-// HTTPErrorStatusCode returns a status code.
13
-func (e apiError) HTTPErrorStatusCode() int {
14
-	return e.statusCode
15
-}
16
-
17
-// NewErrorWithStatusCode allows you to associate
18
-// a specific HTTP Status Code to an error.
19
-// The server will take that code and set
20
-// it as the response status.
21
-func NewErrorWithStatusCode(err error, code int) error {
22
-	return apiError{err, code}
23
-}
24
-
25
-// NewBadRequestError creates a new API error
26
-// that has the 400 HTTP status code associated to it.
27
-func NewBadRequestError(err error) error {
28
-	return NewErrorWithStatusCode(err, http.StatusBadRequest)
29
-}
30
-
31
-// NewRequestForbiddenError creates a new API error
32
-// that has the 403 HTTP status code associated to it.
33
-func NewRequestForbiddenError(err error) error {
34
-	return NewErrorWithStatusCode(err, http.StatusForbidden)
35
-}
36
-
37
-// NewRequestNotFoundError creates a new API error
38
-// that has the 404 HTTP status code associated to it.
39
-func NewRequestNotFoundError(err error) error {
40
-	return NewErrorWithStatusCode(err, http.StatusNotFound)
41
-}
42
-
43
-// NewRequestConflictError creates a new API error
44
-// that has the 409 HTTP status code associated to it.
45
-func NewRequestConflictError(err error) error {
46
-	return NewErrorWithStatusCode(err, http.StatusConflict)
47
-}
48 1
deleted file mode 100644
... ...
@@ -1,64 +0,0 @@
1
-package errors
2
-
3
-import (
4
-	"fmt"
5
-	"github.com/stretchr/testify/assert"
6
-	"net/http"
7
-	"testing"
8
-)
9
-
10
-func newError(errorname string) error {
11
-
12
-	return fmt.Errorf("test%v", errorname)
13
-}
14
-
15
-func TestErrors(t *testing.T) {
16
-	errmsg := newError("apiError")
17
-	err := apiError{
18
-		error:      errmsg,
19
-		statusCode: 0,
20
-	}
21
-	assert.Equal(t, err.HTTPErrorStatusCode(), err.statusCode)
22
-
23
-	errmsg = newError("ErrorWithStatusCode")
24
-	errcode := 1
25
-	serr := NewErrorWithStatusCode(errmsg, errcode)
26
-	apierr, ok := serr.(apiError)
27
-	if !ok {
28
-		t.Fatal("excepted err is apiError type")
29
-	}
30
-	assert.Equal(t, errcode, apierr.statusCode)
31
-
32
-	errmsg = newError("NewBadRequestError")
33
-	baderr := NewBadRequestError(errmsg)
34
-	apierr, ok = baderr.(apiError)
35
-	if !ok {
36
-		t.Fatal("excepted err is apiError type")
37
-	}
38
-	assert.Equal(t, http.StatusBadRequest, apierr.statusCode)
39
-
40
-	errmsg = newError("RequestForbiddenError")
41
-	ferr := NewRequestForbiddenError(errmsg)
42
-	apierr, ok = ferr.(apiError)
43
-	if !ok {
44
-		t.Fatal("excepted err is apiError type")
45
-	}
46
-	assert.Equal(t, http.StatusForbidden, apierr.statusCode)
47
-
48
-	errmsg = newError("RequestNotFoundError")
49
-	nerr := NewRequestNotFoundError(errmsg)
50
-	apierr, ok = nerr.(apiError)
51
-	if !ok {
52
-		t.Fatal("excepted err is apiError type")
53
-	}
54
-	assert.Equal(t, http.StatusNotFound, apierr.statusCode)
55
-
56
-	errmsg = newError("RequestConflictError")
57
-	cerr := NewRequestConflictError(errmsg)
58
-	apierr, ok = cerr.(apiError)
59
-	if !ok {
60
-		t.Fatal("excepted err is apiError type")
61
-	}
62
-	assert.Equal(t, http.StatusConflict, apierr.statusCode)
63
-
64
-}
... ...
@@ -1,9 +1,10 @@
1 1
 package httputils
2 2
 
3 3
 import (
4
+	"fmt"
4 5
 	"net/http"
5
-	"strings"
6 6
 
7
+	"github.com/docker/docker/api/errdefs"
7 8
 	"github.com/docker/docker/api/types"
8 9
 	"github.com/docker/docker/api/types/versions"
9 10
 	"github.com/gorilla/mux"
... ...
@@ -20,13 +21,8 @@ type httpStatusError interface {
20 20
 	HTTPErrorStatusCode() int
21 21
 }
22 22
 
23
-// inputValidationError is an interface
24
-// that errors generated by invalid
25
-// inputs can implement to tell the
26
-// api layer to set a 400 status code
27
-// in the response.
28
-type inputValidationError interface {
29
-	IsValidationError() bool
23
+type causer interface {
24
+	Cause() error
30 25
 }
31 26
 
32 27
 // GetHTTPErrorStatusCode retrieves status code from error message.
... ...
@@ -37,49 +33,44 @@ func GetHTTPErrorStatusCode(err error) int {
37 37
 	}
38 38
 
39 39
 	var statusCode int
40
-	errMsg := err.Error()
41 40
 
42
-	switch e := err.(type) {
43
-	case httpStatusError:
44
-		statusCode = e.HTTPErrorStatusCode()
45
-	case inputValidationError:
41
+	// Stop right there
42
+	// Are you sure you should be adding a new error class here? Do one of the existing ones work?
43
+
44
+	// Note that the below functions are already checking the error causal chain for matches.
45
+	switch {
46
+	case errdefs.IsNotFound(err):
47
+		statusCode = http.StatusNotFound
48
+	case errdefs.IsInvalidParameter(err):
46 49
 		statusCode = http.StatusBadRequest
50
+	case errdefs.IsConflict(err):
51
+		statusCode = http.StatusConflict
52
+	case errdefs.IsUnauthorized(err):
53
+		statusCode = http.StatusUnauthorized
54
+	case errdefs.IsUnavailable(err):
55
+		statusCode = http.StatusServiceUnavailable
56
+	case errdefs.IsForbidden(err):
57
+		statusCode = http.StatusForbidden
58
+	case errdefs.IsNotModified(err):
59
+		statusCode = http.StatusNotModified
60
+	case errdefs.IsNotImplemented(err):
61
+		statusCode = http.StatusNotImplemented
62
+	case errdefs.IsSystem(err) || errdefs.IsUnknown(err):
63
+		statusCode = http.StatusInternalServerError
47 64
 	default:
48 65
 		statusCode = statusCodeFromGRPCError(err)
49 66
 		if statusCode != http.StatusInternalServerError {
50 67
 			return statusCode
51 68
 		}
52 69
 
53
-		// FIXME: this is brittle and should not be necessary, but we still need to identify if
54
-		// there are errors falling back into this logic.
55
-		// If we need to differentiate between different possible error types,
56
-		// we should create appropriate error types that implement the httpStatusError interface.
57
-		errStr := strings.ToLower(errMsg)
58
-
59
-		for _, status := range []struct {
60
-			keyword string
61
-			code    int
62
-		}{
63
-			{"not found", http.StatusNotFound},
64
-			{"cannot find", http.StatusNotFound},
65
-			{"no such", http.StatusNotFound},
66
-			{"bad parameter", http.StatusBadRequest},
67
-			{"no command", http.StatusBadRequest},
68
-			{"conflict", http.StatusConflict},
69
-			{"impossible", http.StatusNotAcceptable},
70
-			{"wrong login/password", http.StatusUnauthorized},
71
-			{"unauthorized", http.StatusUnauthorized},
72
-			{"hasn't been activated", http.StatusForbidden},
73
-			{"this node", http.StatusServiceUnavailable},
74
-			{"needs to be unlocked", http.StatusServiceUnavailable},
75
-			{"certificates have expired", http.StatusServiceUnavailable},
76
-			{"repository does not exist", http.StatusNotFound},
77
-		} {
78
-			if strings.Contains(errStr, status.keyword) {
79
-				statusCode = status.code
80
-				break
81
-			}
70
+		if e, ok := err.(causer); ok {
71
+			return GetHTTPErrorStatusCode(e.Cause())
82 72
 		}
73
+
74
+		logrus.WithFields(logrus.Fields{
75
+			"module":     "api",
76
+			"error_type": fmt.Sprintf("%T", err),
77
+		}).Debugf("FIXME: Got an API for which error does not match any expected type!!!: %+v", err)
83 78
 	}
84 79
 
85 80
 	if statusCode == 0 {
... ...
@@ -133,6 +124,9 @@ func statusCodeFromGRPCError(err error) int {
133 133
 	case codes.Unavailable: // code 14
134 134
 		return http.StatusServiceUnavailable
135 135
 	default:
136
+		if e, ok := err.(causer); ok {
137
+			return statusCodeFromGRPCError(e.Cause())
138
+		}
136 139
 		// codes.Canceled(1)
137 140
 		// codes.Unknown(2)
138 141
 		// codes.DeadlineExceeded(4)
... ...
@@ -1,7 +1,6 @@
1 1
 package httputils
2 2
 
3 3
 import (
4
-	"errors"
5 4
 	"net/http"
6 5
 	"path/filepath"
7 6
 	"strconv"
... ...
@@ -49,6 +48,16 @@ type ArchiveOptions struct {
49 49
 	Path string
50 50
 }
51 51
 
52
+type badParameterError struct {
53
+	param string
54
+}
55
+
56
+func (e badParameterError) Error() string {
57
+	return "bad parameter: " + e.param + "cannot be empty"
58
+}
59
+
60
+func (e badParameterError) InvalidParameter() {}
61
+
52 62
 // ArchiveFormValues parses form values and turns them into ArchiveOptions.
53 63
 // It fails if the archive name and path are not in the request.
54 64
 func ArchiveFormValues(r *http.Request, vars map[string]string) (ArchiveOptions, error) {
... ...
@@ -57,14 +66,13 @@ func ArchiveFormValues(r *http.Request, vars map[string]string) (ArchiveOptions,
57 57
 	}
58 58
 
59 59
 	name := vars["name"]
60
-	path := filepath.FromSlash(r.Form.Get("path"))
61
-
62
-	switch {
63
-	case name == "":
64
-		return ArchiveOptions{}, errors.New("bad parameter: 'name' cannot be empty")
65
-	case path == "":
66
-		return ArchiveOptions{}, errors.New("bad parameter: 'path' cannot be empty")
60
+	if name == "" {
61
+		return ArchiveOptions{}, badParameterError{"name"}
67 62
 	}
68 63
 
64
+	path := filepath.FromSlash(r.Form.Get("path"))
65
+	if path == "" {
66
+		return ArchiveOptions{}, badParameterError{"path"}
67
+	}
69 68
 	return ArchiveOptions{name, path}, nil
70 69
 }
... ...
@@ -1,12 +1,12 @@
1 1
 package httputils
2 2
 
3 3
 import (
4
-	"fmt"
5 4
 	"io"
6 5
 	"mime"
7 6
 	"net/http"
8 7
 	"strings"
9 8
 
9
+	"github.com/pkg/errors"
10 10
 	"github.com/sirupsen/logrus"
11 11
 	"golang.org/x/net/context"
12 12
 )
... ...
@@ -43,6 +43,20 @@ func CloseStreams(streams ...interface{}) {
43 43
 	}
44 44
 }
45 45
 
46
+type validationError struct {
47
+	cause error
48
+}
49
+
50
+func (e validationError) Error() string {
51
+	return e.cause.Error()
52
+}
53
+
54
+func (e validationError) Cause() error {
55
+	return e.cause
56
+}
57
+
58
+func (e validationError) InvalidParameter() {}
59
+
46 60
 // CheckForJSON makes sure that the request's Content-Type is application/json.
47 61
 func CheckForJSON(r *http.Request) error {
48 62
 	ct := r.Header.Get("Content-Type")
... ...
@@ -58,7 +72,7 @@ func CheckForJSON(r *http.Request) error {
58 58
 	if matchesContentType(ct, "application/json") {
59 59
 		return nil
60 60
 	}
61
-	return fmt.Errorf("Content-Type specified (%s) must be 'application/json'", ct)
61
+	return validationError{errors.Errorf("Content-Type specified (%s) must be 'application/json'", ct)}
62 62
 }
63 63
 
64 64
 // ParseForm ensures the request form is parsed even with invalid content types.
... ...
@@ -68,7 +82,7 @@ func ParseForm(r *http.Request) error {
68 68
 		return nil
69 69
 	}
70 70
 	if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
71
-		return err
71
+		return validationError{err}
72 72
 	}
73 73
 	return nil
74 74
 }
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"net/http"
6 6
 	"runtime"
7 7
 
8
-	"github.com/docker/docker/api/errors"
9 8
 	"github.com/docker/docker/api/types/versions"
10 9
 	"golang.org/x/net/context"
11 10
 )
... ...
@@ -28,6 +27,16 @@ func NewVersionMiddleware(s, d, m string) VersionMiddleware {
28 28
 	}
29 29
 }
30 30
 
31
+type versionUnsupportedError struct {
32
+	version, minVersion string
33
+}
34
+
35
+func (e versionUnsupportedError) Error() string {
36
+	return fmt.Sprintf("client version %s is too old. Minimum supported API version is %s, please upgrade your client to a newer version", e.version, e.minVersion)
37
+}
38
+
39
+func (e versionUnsupportedError) InvalidParameter() {}
40
+
31 41
 // WrapHandler returns a new handler function wrapping the previous one in the request chain.
32 42
 func (v VersionMiddleware) WrapHandler(handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
33 43
 	return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
... ...
@@ -37,7 +46,7 @@ func (v VersionMiddleware) WrapHandler(handler func(ctx context.Context, w http.
37 37
 		}
38 38
 
39 39
 		if versions.LessThan(apiVersion, v.minVersion) {
40
-			return errors.NewBadRequestError(fmt.Errorf("client version %s is too old. Minimum supported API version is %s, please upgrade your client to a newer version", apiVersion, v.minVersion))
40
+			return versionUnsupportedError{apiVersion, v.minVersion}
41 41
 		}
42 42
 
43 43
 		header := fmt.Sprintf("Docker/%s (%s)", v.serverVersion, runtime.GOOS)
... ...
@@ -12,7 +12,6 @@ import (
12 12
 	"strings"
13 13
 	"sync"
14 14
 
15
-	apierrors "github.com/docker/docker/api/errors"
16 15
 	"github.com/docker/docker/api/server/httputils"
17 16
 	"github.com/docker/docker/api/types"
18 17
 	"github.com/docker/docker/api/types/backend"
... ...
@@ -27,6 +26,14 @@ import (
27 27
 	"golang.org/x/net/context"
28 28
 )
29 29
 
30
+type invalidIsolationError string
31
+
32
+func (e invalidIsolationError) Error() string {
33
+	return fmt.Sprintf("Unsupported isolation: %q", string(e))
34
+}
35
+
36
+func (e invalidIsolationError) InvalidParameter() {}
37
+
30 38
 func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBuildOptions, error) {
31 39
 	version := httputils.VersionFromContext(ctx)
32 40
 	options := &types.ImageBuildOptions{}
... ...
@@ -71,20 +78,20 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui
71 71
 
72 72
 	if i := container.Isolation(r.FormValue("isolation")); i != "" {
73 73
 		if !container.Isolation.IsValid(i) {
74
-			return nil, fmt.Errorf("Unsupported isolation: %q", i)
74
+			return nil, invalidIsolationError(i)
75 75
 		}
76 76
 		options.Isolation = i
77 77
 	}
78 78
 
79 79
 	if runtime.GOOS != "windows" && options.SecurityOpt != nil {
80
-		return nil, fmt.Errorf("The daemon on this platform does not support setting security options on build")
80
+		return nil, validationError{fmt.Errorf("The daemon on this platform does not support setting security options on build")}
81 81
 	}
82 82
 
83 83
 	var buildUlimits = []*units.Ulimit{}
84 84
 	ulimitsJSON := r.FormValue("ulimits")
85 85
 	if ulimitsJSON != "" {
86 86
 		if err := json.Unmarshal([]byte(ulimitsJSON), &buildUlimits); err != nil {
87
-			return nil, err
87
+			return nil, errors.Wrap(validationError{err}, "error reading ulimit settings")
88 88
 		}
89 89
 		options.Ulimits = buildUlimits
90 90
 	}
... ...
@@ -105,7 +112,7 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui
105 105
 	if buildArgsJSON != "" {
106 106
 		var buildArgs = map[string]*string{}
107 107
 		if err := json.Unmarshal([]byte(buildArgsJSON), &buildArgs); err != nil {
108
-			return nil, err
108
+			return nil, errors.Wrap(validationError{err}, "error reading build args")
109 109
 		}
110 110
 		options.BuildArgs = buildArgs
111 111
 	}
... ...
@@ -114,7 +121,7 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui
114 114
 	if labelsJSON != "" {
115 115
 		var labels = map[string]string{}
116 116
 		if err := json.Unmarshal([]byte(labelsJSON), &labels); err != nil {
117
-			return nil, err
117
+			return nil, errors.Wrap(validationError{err}, "error reading labels")
118 118
 		}
119 119
 		options.Labels = labels
120 120
 	}
... ...
@@ -140,6 +147,16 @@ func (br *buildRouter) postPrune(ctx context.Context, w http.ResponseWriter, r *
140 140
 	return httputils.WriteJSON(w, http.StatusOK, report)
141 141
 }
142 142
 
143
+type validationError struct {
144
+	cause error
145
+}
146
+
147
+func (e validationError) Error() string {
148
+	return e.cause.Error()
149
+}
150
+
151
+func (e validationError) InvalidParameter() {}
152
+
143 153
 func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
144 154
 	var (
145 155
 		notVerboseBuffer = bytes.NewBuffer(nil)
... ...
@@ -173,8 +190,7 @@ func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *
173 173
 	buildOptions.AuthConfigs = getAuthConfigs(r.Header)
174 174
 
175 175
 	if buildOptions.Squash && !br.daemon.HasExperimental() {
176
-		return apierrors.NewBadRequestError(
177
-			errors.New("squash is only supported with experimental mode"))
176
+		return validationError{errors.New("squash is only supported with experimental mode")}
178 177
 	}
179 178
 
180 179
 	out := io.Writer(output)
... ...
@@ -51,7 +51,7 @@ type stateBackend interface {
51 51
 type monitorBackend interface {
52 52
 	ContainerChanges(name string) ([]archive.Change, error)
53 53
 	ContainerInspect(name string, size bool, version string) (interface{}, error)
54
-	ContainerLogs(ctx context.Context, name string, config *types.ContainerLogsOptions) (<-chan *backend.LogMessage, error)
54
+	ContainerLogs(ctx context.Context, name string, config *types.ContainerLogsOptions) (msgs <-chan *backend.LogMessage, tty bool, err error)
55 55
 	ContainerStats(ctx context.Context, name string, config *backend.ContainerStatsConfig) error
56 56
 	ContainerTop(name string, psArgs string) (*container.ContainerTopOKBody, error)
57 57
 
... ...
@@ -6,13 +6,19 @@ import (
6 6
 )
7 7
 
8 8
 type validationError struct {
9
-	error
9
+	cause error
10 10
 }
11 11
 
12
-func (validationError) IsValidationError() bool {
13
-	return true
12
+func (e validationError) Error() string {
13
+	return e.cause.Error()
14 14
 }
15 15
 
16
+func (e validationError) Cause() error {
17
+	return e.cause
18
+}
19
+
20
+func (e validationError) InvalidParameter() {}
21
+
16 22
 // containerRouter is a router to talk with the container controller
17 23
 type containerRouter struct {
18 24
 	backend Backend
... ...
@@ -8,7 +8,7 @@ import (
8 8
 	"strconv"
9 9
 	"syscall"
10 10
 
11
-	"github.com/docker/docker/api"
11
+	"github.com/docker/docker/api/errdefs"
12 12
 	"github.com/docker/docker/api/server/httputils"
13 13
 	"github.com/docker/docker/api/types"
14 14
 	"github.com/docker/docker/api/types/backend"
... ...
@@ -18,6 +18,7 @@ import (
18 18
 	containerpkg "github.com/docker/docker/container"
19 19
 	"github.com/docker/docker/pkg/ioutils"
20 20
 	"github.com/docker/docker/pkg/signal"
21
+	"github.com/pkg/errors"
21 22
 	"github.com/sirupsen/logrus"
22 23
 	"golang.org/x/net/context"
23 24
 	"golang.org/x/net/websocket"
... ...
@@ -87,7 +88,7 @@ func (s *containerRouter) getContainersLogs(ctx context.Context, w http.Response
87 87
 	// with the appropriate status code.
88 88
 	stdout, stderr := httputils.BoolValue(r, "stdout"), httputils.BoolValue(r, "stderr")
89 89
 	if !(stdout || stderr) {
90
-		return fmt.Errorf("Bad parameters: you must choose at least one stream")
90
+		return validationError{errors.New("Bad parameters: you must choose at least one stream")}
91 91
 	}
92 92
 
93 93
 	containerName := vars["name"]
... ...
@@ -101,19 +102,7 @@ func (s *containerRouter) getContainersLogs(ctx context.Context, w http.Response
101 101
 		Details:    httputils.BoolValue(r, "details"),
102 102
 	}
103 103
 
104
-	// doesn't matter what version the client is on, we're using this internally only
105
-	// also do we need size? i'm thinking no we don't
106
-	raw, err := s.backend.ContainerInspect(containerName, false, api.DefaultVersion)
107
-	if err != nil {
108
-		return err
109
-	}
110
-	container, ok := raw.(*types.ContainerJSON)
111
-	if !ok {
112
-		// %T prints the type. handy!
113
-		return fmt.Errorf("expected container to be *types.ContainerJSON but got %T", raw)
114
-	}
115
-
116
-	msgs, err := s.backend.ContainerLogs(ctx, containerName, logsConfig)
104
+	msgs, tty, err := s.backend.ContainerLogs(ctx, containerName, logsConfig)
117 105
 	if err != nil {
118 106
 		return err
119 107
 	}
... ...
@@ -122,7 +111,7 @@ func (s *containerRouter) getContainersLogs(ctx context.Context, w http.Response
122 122
 	// this is the point of no return for writing a response. once we call
123 123
 	// WriteLogStream, the response has been started and errors will be
124 124
 	// returned in band by WriteLogStream
125
-	httputils.WriteLogStream(ctx, w, msgs, logsConfig, !container.Config.Tty)
125
+	httputils.WriteLogStream(ctx, w, msgs, logsConfig, !tty)
126 126
 	return nil
127 127
 }
128 128
 
... ...
@@ -130,6 +119,14 @@ func (s *containerRouter) getContainersExport(ctx context.Context, w http.Respon
130 130
 	return s.backend.ContainerExport(vars["name"], w)
131 131
 }
132 132
 
133
+type bodyOnStartError struct{}
134
+
135
+func (bodyOnStartError) Error() string {
136
+	return "starting container with non-empty request body was deprecated since API v1.22 and removed in v1.24"
137
+}
138
+
139
+func (bodyOnStartError) InvalidParameter() {}
140
+
133 141
 func (s *containerRouter) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
134 142
 	// If contentLength is -1, we can assumed chunked encoding
135 143
 	// or more technically that the length is unknown
... ...
@@ -143,7 +140,7 @@ func (s *containerRouter) postContainersStart(ctx context.Context, w http.Respon
143 143
 	// A non-nil json object is at least 7 characters.
144 144
 	if r.ContentLength > 7 || r.ContentLength == -1 {
145 145
 		if versions.GreaterThanOrEqualTo(version, "1.24") {
146
-			return validationError{fmt.Errorf("starting container with non-empty request body was deprecated since v1.10 and removed in v1.12")}
146
+			return bodyOnStartError{}
147 147
 		}
148 148
 
149 149
 		if err := httputils.CheckForJSON(r); err != nil {
... ...
@@ -193,10 +190,6 @@ func (s *containerRouter) postContainersStop(ctx context.Context, w http.Respons
193 193
 	return nil
194 194
 }
195 195
 
196
-type errContainerIsRunning interface {
197
-	ContainerIsRunning() bool
198
-}
199
-
200 196
 func (s *containerRouter) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
201 197
 	if err := httputils.ParseForm(r); err != nil {
202 198
 		return err
... ...
@@ -209,14 +202,14 @@ func (s *containerRouter) postContainersKill(ctx context.Context, w http.Respons
209 209
 	if sigStr := r.Form.Get("signal"); sigStr != "" {
210 210
 		var err error
211 211
 		if sig, err = signal.ParseSignal(sigStr); err != nil {
212
-			return err
212
+			return validationError{err}
213 213
 		}
214 214
 	}
215 215
 
216 216
 	if err := s.backend.ContainerKill(name, uint64(sig)); err != nil {
217 217
 		var isStopped bool
218
-		if e, ok := err.(errContainerIsRunning); ok {
219
-			isStopped = !e.ContainerIsRunning()
218
+		if errdefs.IsConflict(err) {
219
+			isStopped = true
220 220
 		}
221 221
 
222 222
 		// Return error that's not caused because the container is stopped.
... ...
@@ -224,7 +217,7 @@ func (s *containerRouter) postContainersKill(ctx context.Context, w http.Respons
224 224
 		// to keep backwards compatibility.
225 225
 		version := httputils.VersionFromContext(ctx)
226 226
 		if versions.GreaterThanOrEqualTo(version, "1.20") || !isStopped {
227
-			return fmt.Errorf("Cannot kill container %s: %v", name, err)
227
+			return errors.Wrapf(err, "Cannot kill container: %s", name)
228 228
 		}
229 229
 	}
230 230
 
... ...
@@ -458,11 +451,11 @@ func (s *containerRouter) postContainersResize(ctx context.Context, w http.Respo
458 458
 
459 459
 	height, err := strconv.Atoi(r.Form.Get("h"))
460 460
 	if err != nil {
461
-		return err
461
+		return validationError{err}
462 462
 	}
463 463
 	width, err := strconv.Atoi(r.Form.Get("w"))
464 464
 	if err != nil {
465
-		return err
465
+		return validationError{err}
466 466
 	}
467 467
 
468 468
 	return s.backend.ContainerResize(vars["name"], height, width)
... ...
@@ -480,7 +473,7 @@ func (s *containerRouter) postContainersAttach(ctx context.Context, w http.Respo
480 480
 
481 481
 	hijacker, ok := w.(http.Hijacker)
482 482
 	if !ok {
483
-		return fmt.Errorf("error attaching to container %s, hijack connection missing", containerName)
483
+		return validationError{errors.Errorf("error attaching to container %s, hijack connection missing", containerName)}
484 484
 	}
485 485
 
486 486
 	setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) {
... ...
@@ -597,7 +590,7 @@ func (s *containerRouter) postContainersPrune(ctx context.Context, w http.Respon
597 597
 
598 598
 	pruneFilters, err := filters.FromParam(r.Form.Get("filters"))
599 599
 	if err != nil {
600
-		return err
600
+		return validationError{err}
601 601
 	}
602 602
 
603 603
 	pruneReport, err := s.backend.ContainersPrune(ctx, pruneFilters)
... ...
@@ -3,11 +3,8 @@ package container
3 3
 import (
4 4
 	"encoding/base64"
5 5
 	"encoding/json"
6
-	"fmt"
7 6
 	"io"
8 7
 	"net/http"
9
-	"os"
10
-	"strings"
11 8
 
12 9
 	"github.com/docker/docker/api/server/httputils"
13 10
 	"github.com/docker/docker/api/types"
... ...
@@ -15,6 +12,14 @@ import (
15 15
 	"golang.org/x/net/context"
16 16
 )
17 17
 
18
+type pathError struct{}
19
+
20
+func (pathError) Error() string {
21
+	return "Path cannot be empty"
22
+}
23
+
24
+func (pathError) InvalidParameter() {}
25
+
18 26
 // postContainersCopy is deprecated in favor of getContainersArchive.
19 27
 func (s *containerRouter) postContainersCopy(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
20 28
 	// Deprecated since 1.8, Errors out since 1.12
... ...
@@ -33,18 +38,11 @@ func (s *containerRouter) postContainersCopy(ctx context.Context, w http.Respons
33 33
 	}
34 34
 
35 35
 	if cfg.Resource == "" {
36
-		return fmt.Errorf("Path cannot be empty")
36
+		return pathError{}
37 37
 	}
38 38
 
39 39
 	data, err := s.backend.ContainerCopy(vars["name"], cfg.Resource)
40 40
 	if err != nil {
41
-		if strings.Contains(strings.ToLower(err.Error()), "no such container") {
42
-			w.WriteHeader(http.StatusNotFound)
43
-			return nil
44
-		}
45
-		if os.IsNotExist(err) {
46
-			return fmt.Errorf("Could not find the file %s in container %s", cfg.Resource, vars["name"])
47
-		}
48 41
 		return err
49 42
 	}
50 43
 	defer data.Close()
... ...
@@ -24,6 +24,14 @@ func (s *containerRouter) getExecByID(ctx context.Context, w http.ResponseWriter
24 24
 	return httputils.WriteJSON(w, http.StatusOK, eConfig)
25 25
 }
26 26
 
27
+type execCommandError struct{}
28
+
29
+func (execCommandError) Error() string {
30
+	return "No exec command specified"
31
+}
32
+
33
+func (execCommandError) InvalidParameter() {}
34
+
27 35
 func (s *containerRouter) postContainerExecCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
28 36
 	if err := httputils.ParseForm(r); err != nil {
29 37
 		return err
... ...
@@ -39,7 +47,7 @@ func (s *containerRouter) postContainerExecCreate(ctx context.Context, w http.Re
39 39
 	}
40 40
 
41 41
 	if len(execConfig.Cmd) == 0 {
42
-		return fmt.Errorf("No exec command specified")
42
+		return execCommandError{}
43 43
 	}
44 44
 
45 45
 	// Register an instance of Exec in container.
... ...
@@ -129,11 +137,11 @@ func (s *containerRouter) postContainerExecResize(ctx context.Context, w http.Re
129 129
 	}
130 130
 	height, err := strconv.Atoi(r.Form.Get("h"))
131 131
 	if err != nil {
132
-		return err
132
+		return validationError{err}
133 133
 	}
134 134
 	width, err := strconv.Atoi(r.Form.Get("w"))
135 135
 	if err != nil {
136
-		return err
136
+		return validationError{err}
137 137
 	}
138 138
 
139 139
 	return s.backend.ContainerExecResize(vars["name"], height, width)
... ...
@@ -1,19 +1,13 @@
1 1
 package router
2 2
 
3 3
 import (
4
-	"errors"
5 4
 	"net/http"
6 5
 
7 6
 	"golang.org/x/net/context"
8 7
 
9
-	apierrors "github.com/docker/docker/api/errors"
10 8
 	"github.com/docker/docker/api/server/httputils"
11 9
 )
12 10
 
13
-var (
14
-	errExperimentalFeature = errors.New("This experimental feature is disabled by default. Start the Docker daemon in experimental mode in order to enable it.")
15
-)
16
-
17 11
 // ExperimentalRoute defines an experimental API route that can be enabled or disabled.
18 12
 type ExperimentalRoute interface {
19 13
 	Route
... ...
@@ -39,8 +33,16 @@ func (r *experimentalRoute) Disable() {
39 39
 	r.handler = experimentalHandler
40 40
 }
41 41
 
42
+type notImplementedError struct{}
43
+
44
+func (notImplementedError) Error() string {
45
+	return "This experimental feature is disabled by default. Start the Docker daemon in experimental mode in order to enable it."
46
+}
47
+
48
+func (notImplementedError) NotImplemented() {}
49
+
42 50
 func experimentalHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
43
-	return apierrors.NewErrorWithStatusCode(errExperimentalFeature, http.StatusNotImplemented)
51
+	return notImplementedError{}
44 52
 }
45 53
 
46 54
 // Handler returns returns the APIFunc to let the server wrap it in middlewares.
... ...
@@ -3,7 +3,6 @@ package image
3 3
 import (
4 4
 	"encoding/base64"
5 5
 	"encoding/json"
6
-	"fmt"
7 6
 	"io"
8 7
 	"net/http"
9 8
 	"runtime"
... ...
@@ -20,6 +19,7 @@ import (
20 20
 	"github.com/docker/docker/pkg/streamformatter"
21 21
 	"github.com/docker/docker/pkg/system"
22 22
 	"github.com/docker/docker/registry"
23
+	"github.com/pkg/errors"
23 24
 	"golang.org/x/net/context"
24 25
 )
25 26
 
... ...
@@ -161,6 +161,20 @@ func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrite
161 161
 	return nil
162 162
 }
163 163
 
164
+type validationError struct {
165
+	cause error
166
+}
167
+
168
+func (e validationError) Error() string {
169
+	return e.cause.Error()
170
+}
171
+
172
+func (e validationError) Cause() error {
173
+	return e.cause
174
+}
175
+
176
+func (validationError) InvalidParameter() {}
177
+
164 178
 func (s *imageRouter) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
165 179
 	metaHeaders := map[string][]string{}
166 180
 	for k, v := range r.Header {
... ...
@@ -184,7 +198,7 @@ func (s *imageRouter) postImagesPush(ctx context.Context, w http.ResponseWriter,
184 184
 	} else {
185 185
 		// the old format is supported for compatibility if there was no authConfig header
186 186
 		if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
187
-			return fmt.Errorf("Bad parameters and missing X-Registry-Auth: %v", err)
187
+			return errors.Wrap(validationError{err}, "Bad parameters and missing X-Registry-Auth")
188 188
 		}
189 189
 	}
190 190
 
... ...
@@ -246,6 +260,14 @@ func (s *imageRouter) postImagesLoad(ctx context.Context, w http.ResponseWriter,
246 246
 	return nil
247 247
 }
248 248
 
249
+type missingImageError struct{}
250
+
251
+func (missingImageError) Error() string {
252
+	return "image name cannot be blank"
253
+}
254
+
255
+func (missingImageError) InvalidParameter() {}
256
+
249 257
 func (s *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
250 258
 	if err := httputils.ParseForm(r); err != nil {
251 259
 		return err
... ...
@@ -254,7 +276,7 @@ func (s *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter, r
254 254
 	name := vars["name"]
255 255
 
256 256
 	if strings.TrimSpace(name) == "" {
257
-		return fmt.Errorf("image name cannot be blank")
257
+		return missingImageError{}
258 258
 	}
259 259
 
260 260
 	force := httputils.BoolValue(r, "force")
... ...
@@ -1,8 +1,6 @@
1 1
 package network
2 2
 
3 3
 import (
4
-	"fmt"
5
-
6 4
 	"github.com/docker/docker/api/types"
7 5
 	"github.com/docker/docker/api/types/filters"
8 6
 	"github.com/docker/docker/runconfig"
... ...
@@ -24,11 +22,19 @@ func filterNetworkByType(nws []types.NetworkResource, netType string) ([]types.N
24 24
 			}
25 25
 		}
26 26
 	default:
27
-		return nil, fmt.Errorf("Invalid filter: 'type'='%s'", netType)
27
+		return nil, invalidFilter(netType)
28 28
 	}
29 29
 	return retNws, nil
30 30
 }
31 31
 
32
+type invalidFilter string
33
+
34
+func (e invalidFilter) Error() string {
35
+	return "Invalid filter: 'type'='" + string(e) + "'"
36
+}
37
+
38
+func (e invalidFilter) InvalidParameter() {}
39
+
32 40
 // filterNetworks filters network list according to user specified filter
33 41
 // and returns user chosen networks
34 42
 func filterNetworks(nws []types.NetworkResource, filter filters.Args) ([]types.NetworkResource, error) {
... ...
@@ -2,14 +2,12 @@ package network
2 2
 
3 3
 import (
4 4
 	"encoding/json"
5
-	"fmt"
6 5
 	"net/http"
7 6
 	"strconv"
8 7
 	"strings"
9 8
 
10 9
 	"golang.org/x/net/context"
11 10
 
12
-	"github.com/docker/docker/api/errors"
13 11
 	"github.com/docker/docker/api/server/httputils"
14 12
 	"github.com/docker/docker/api/types"
15 13
 	"github.com/docker/docker/api/types/filters"
... ...
@@ -18,6 +16,7 @@ import (
18 18
 	"github.com/docker/libnetwork"
19 19
 	netconst "github.com/docker/libnetwork/datastore"
20 20
 	"github.com/docker/libnetwork/networkdb"
21
+	"github.com/pkg/errors"
21 22
 )
22 23
 
23 24
 var (
... ...
@@ -83,6 +82,24 @@ SKIP:
83 83
 	return httputils.WriteJSON(w, http.StatusOK, list)
84 84
 }
85 85
 
86
+type invalidRequestError struct {
87
+	cause error
88
+}
89
+
90
+func (e invalidRequestError) Error() string {
91
+	return e.cause.Error()
92
+}
93
+
94
+func (e invalidRequestError) InvalidParameter() {}
95
+
96
+type ambigousResultsError string
97
+
98
+func (e ambigousResultsError) Error() string {
99
+	return "network " + string(e) + " is ambiguous"
100
+}
101
+
102
+func (ambigousResultsError) InvalidParameter() {}
103
+
86 104
 func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
87 105
 	if err := httputils.ParseForm(r); err != nil {
88 106
 		return err
... ...
@@ -95,8 +112,7 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
95 95
 	)
96 96
 	if v := r.URL.Query().Get("verbose"); v != "" {
97 97
 		if verbose, err = strconv.ParseBool(v); err != nil {
98
-			err = fmt.Errorf("invalid value for verbose: %s", v)
99
-			return errors.NewBadRequestError(err)
98
+			return errors.Wrapf(invalidRequestError{err}, "invalid value for verbose: %s", v)
100 99
 		}
101 100
 	}
102 101
 	scope := r.URL.Query().Get("scope")
... ...
@@ -177,7 +193,7 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
177 177
 		}
178 178
 	}
179 179
 	if len(listByFullName) > 1 {
180
-		return fmt.Errorf("network %s is ambiguous (%d matches found based on name)", term, len(listByFullName))
180
+		return errors.Wrapf(ambigousResultsError(term), "%d matches found based on name", len(listByFullName))
181 181
 	}
182 182
 
183 183
 	// Find based on partial ID, returns true only if no duplicates
... ...
@@ -187,7 +203,7 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
187 187
 		}
188 188
 	}
189 189
 	if len(listByPartialID) > 1 {
190
-		return fmt.Errorf("network %s is ambiguous (%d matches found based on ID prefix)", term, len(listByPartialID))
190
+		return errors.Wrapf(ambigousResultsError(term), "%d matches found based on ID prefix", len(listByPartialID))
191 191
 	}
192 192
 
193 193
 	return libnetwork.ErrNoSuchNetwork(term)
... ...
@@ -3,14 +3,27 @@ package session
3 3
 import (
4 4
 	"net/http"
5 5
 
6
-	apierrors "github.com/docker/docker/api/errors"
7 6
 	"golang.org/x/net/context"
8 7
 )
9 8
 
9
+type invalidRequest struct {
10
+	cause error
11
+}
12
+
13
+func (e invalidRequest) Error() string {
14
+	return e.cause.Error()
15
+}
16
+
17
+func (e invalidRequest) Cause() error {
18
+	return e.cause
19
+}
20
+
21
+func (e invalidRequest) InvalidParameter() {}
22
+
10 23
 func (sr *sessionRouter) startSession(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
11 24
 	err := sr.backend.HandleHTTPRequest(ctx, w, r)
12 25
 	if err != nil {
13
-		return apierrors.NewBadRequestError(err)
26
+		return invalidRequest{err}
14 27
 	}
15 28
 	return nil
16 29
 }
... ...
@@ -6,13 +6,13 @@ import (
6 6
 	"net/http"
7 7
 	"strconv"
8 8
 
9
-	"github.com/docker/docker/api/errors"
10 9
 	"github.com/docker/docker/api/server/httputils"
11 10
 	basictypes "github.com/docker/docker/api/types"
12 11
 	"github.com/docker/docker/api/types/backend"
13 12
 	"github.com/docker/docker/api/types/filters"
14 13
 	types "github.com/docker/docker/api/types/swarm"
15 14
 	"github.com/docker/docker/api/types/versions"
15
+	"github.com/pkg/errors"
16 16
 	"github.com/sirupsen/logrus"
17 17
 	"golang.org/x/net/context"
18 18
 )
... ...
@@ -57,6 +57,20 @@ func (sr *swarmRouter) inspectCluster(ctx context.Context, w http.ResponseWriter
57 57
 	return httputils.WriteJSON(w, http.StatusOK, swarm)
58 58
 }
59 59
 
60
+type invalidRequestError struct {
61
+	err error
62
+}
63
+
64
+func (e invalidRequestError) Error() string {
65
+	return e.err.Error()
66
+}
67
+
68
+func (e invalidRequestError) Cause() error {
69
+	return e.err
70
+}
71
+
72
+func (e invalidRequestError) InvalidParameter() {}
73
+
60 74
 func (sr *swarmRouter) updateCluster(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
61 75
 	var swarm types.Spec
62 76
 	if err := json.NewDecoder(r.Body).Decode(&swarm); err != nil {
... ...
@@ -67,7 +81,7 @@ func (sr *swarmRouter) updateCluster(ctx context.Context, w http.ResponseWriter,
67 67
 	version, err := strconv.ParseUint(rawVersion, 10, 64)
68 68
 	if err != nil {
69 69
 		err := fmt.Errorf("invalid swarm version '%s': %v", rawVersion, err)
70
-		return errors.NewBadRequestError(err)
70
+		return invalidRequestError{err}
71 71
 	}
72 72
 
73 73
 	var flags types.UpdateFlags
... ...
@@ -76,7 +90,7 @@ func (sr *swarmRouter) updateCluster(ctx context.Context, w http.ResponseWriter,
76 76
 		rot, err := strconv.ParseBool(value)
77 77
 		if err != nil {
78 78
 			err := fmt.Errorf("invalid value for rotateWorkerToken: %s", value)
79
-			return errors.NewBadRequestError(err)
79
+			return invalidRequestError{err}
80 80
 		}
81 81
 
82 82
 		flags.RotateWorkerToken = rot
... ...
@@ -86,7 +100,7 @@ func (sr *swarmRouter) updateCluster(ctx context.Context, w http.ResponseWriter,
86 86
 		rot, err := strconv.ParseBool(value)
87 87
 		if err != nil {
88 88
 			err := fmt.Errorf("invalid value for rotateManagerToken: %s", value)
89
-			return errors.NewBadRequestError(err)
89
+			return invalidRequestError{err}
90 90
 		}
91 91
 
92 92
 		flags.RotateManagerToken = rot
... ...
@@ -95,7 +109,7 @@ func (sr *swarmRouter) updateCluster(ctx context.Context, w http.ResponseWriter,
95 95
 	if value := r.URL.Query().Get("rotateManagerUnlockKey"); value != "" {
96 96
 		rot, err := strconv.ParseBool(value)
97 97
 		if err != nil {
98
-			return errors.NewBadRequestError(fmt.Errorf("invalid value for rotateManagerUnlockKey: %s", value))
98
+			return invalidRequestError{fmt.Errorf("invalid value for rotateManagerUnlockKey: %s", value)}
99 99
 		}
100 100
 
101 101
 		flags.RotateManagerUnlockKey = rot
... ...
@@ -139,7 +153,7 @@ func (sr *swarmRouter) getServices(ctx context.Context, w http.ResponseWriter, r
139 139
 	}
140 140
 	filter, err := filters.FromParam(r.Form.Get("filters"))
141 141
 	if err != nil {
142
-		return err
142
+		return invalidRequestError{err}
143 143
 	}
144 144
 
145 145
 	services, err := sr.backend.GetServices(basictypes.ServiceListOptions{Filters: filter})
... ...
@@ -158,7 +172,7 @@ func (sr *swarmRouter) getService(ctx context.Context, w http.ResponseWriter, r
158 158
 		insertDefaults, err = strconv.ParseBool(value)
159 159
 		if err != nil {
160 160
 			err := fmt.Errorf("invalid value for insertDefaults: %s", value)
161
-			return errors.NewBadRequestError(err)
161
+			return errors.Wrapf(invalidRequestError{err}, "invalid value for insertDefaults: %s", value)
162 162
 		}
163 163
 	}
164 164
 
... ...
@@ -204,7 +218,7 @@ func (sr *swarmRouter) updateService(ctx context.Context, w http.ResponseWriter,
204 204
 	version, err := strconv.ParseUint(rawVersion, 10, 64)
205 205
 	if err != nil {
206 206
 		err := fmt.Errorf("invalid service version '%s': %v", rawVersion, err)
207
-		return errors.NewBadRequestError(err)
207
+		return invalidRequestError{err}
208 208
 	}
209 209
 
210 210
 	var flags basictypes.ServiceUpdateOptions
... ...
@@ -297,7 +311,7 @@ func (sr *swarmRouter) updateNode(ctx context.Context, w http.ResponseWriter, r
297 297
 	version, err := strconv.ParseUint(rawVersion, 10, 64)
298 298
 	if err != nil {
299 299
 		err := fmt.Errorf("invalid node version '%s': %v", rawVersion, err)
300
-		return errors.NewBadRequestError(err)
300
+		return invalidRequestError{err}
301 301
 	}
302 302
 
303 303
 	if err := sr.backend.UpdateNode(vars["id"], version, node); err != nil {
... ...
@@ -403,13 +417,13 @@ func (sr *swarmRouter) getSecret(ctx context.Context, w http.ResponseWriter, r *
403 403
 func (sr *swarmRouter) updateSecret(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
404 404
 	var secret types.SecretSpec
405 405
 	if err := json.NewDecoder(r.Body).Decode(&secret); err != nil {
406
-		return errors.NewBadRequestError(err)
406
+		return invalidRequestError{err}
407 407
 	}
408 408
 
409 409
 	rawVersion := r.URL.Query().Get("version")
410 410
 	version, err := strconv.ParseUint(rawVersion, 10, 64)
411 411
 	if err != nil {
412
-		return errors.NewBadRequestError(fmt.Errorf("invalid secret version"))
412
+		return invalidRequestError{fmt.Errorf("invalid secret version")}
413 413
 	}
414 414
 
415 415
 	id := vars["id"]
... ...
@@ -474,13 +488,13 @@ func (sr *swarmRouter) getConfig(ctx context.Context, w http.ResponseWriter, r *
474 474
 func (sr *swarmRouter) updateConfig(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
475 475
 	var config types.ConfigSpec
476 476
 	if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
477
-		return errors.NewBadRequestError(err)
477
+		return invalidRequestError{err}
478 478
 	}
479 479
 
480 480
 	rawVersion := r.URL.Query().Get("version")
481 481
 	version, err := strconv.ParseUint(rawVersion, 10, 64)
482 482
 	if err != nil {
483
-		return errors.NewBadRequestError(fmt.Errorf("invalid config version"))
483
+		return invalidRequestError{fmt.Errorf("invalid config version")}
484 484
 	}
485 485
 
486 486
 	id := vars["id"]
... ...
@@ -7,7 +7,6 @@ import (
7 7
 	"time"
8 8
 
9 9
 	"github.com/docker/docker/api"
10
-	"github.com/docker/docker/api/errors"
11 10
 	"github.com/docker/docker/api/server/httputils"
12 11
 	"github.com/docker/docker/api/types"
13 12
 	"github.com/docker/docker/api/types/events"
... ...
@@ -85,6 +84,16 @@ func (s *systemRouter) getDiskUsage(ctx context.Context, w http.ResponseWriter,
85 85
 	return httputils.WriteJSON(w, http.StatusOK, du)
86 86
 }
87 87
 
88
+type invalidRequestError struct {
89
+	Err error
90
+}
91
+
92
+func (e invalidRequestError) Error() string {
93
+	return e.Err.Error()
94
+}
95
+
96
+func (e invalidRequestError) InvalidParameter() {}
97
+
88 98
 func (s *systemRouter) getEvents(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
89 99
 	if err := httputils.ParseForm(r); err != nil {
90 100
 		return err
... ...
@@ -105,7 +114,7 @@ func (s *systemRouter) getEvents(ctx context.Context, w http.ResponseWriter, r *
105 105
 	)
106 106
 	if !until.IsZero() {
107 107
 		if until.Before(since) {
108
-			return errors.NewBadRequestError(fmt.Errorf("`since` time (%s) cannot be after `until` time (%s)", r.Form.Get("since"), r.Form.Get("until")))
108
+			return invalidRequestError{fmt.Errorf("`since` time (%s) cannot be after `until` time (%s)", r.Form.Get("since"), r.Form.Get("until"))}
109 109
 		}
110 110
 
111 111
 		now := time.Now()
... ...
@@ -2,12 +2,10 @@ package server
2 2
 
3 3
 import (
4 4
 	"crypto/tls"
5
-	"fmt"
6 5
 	"net"
7 6
 	"net/http"
8 7
 	"strings"
9 8
 
10
-	"github.com/docker/docker/api/errors"
11 9
 	"github.com/docker/docker/api/server/httputils"
12 10
 	"github.com/docker/docker/api/server/middleware"
13 11
 	"github.com/docker/docker/api/server/router"
... ...
@@ -158,6 +156,14 @@ func (s *Server) InitRouter(routers ...router.Router) {
158 158
 	}
159 159
 }
160 160
 
161
+type pageNotFoundError struct{}
162
+
163
+func (pageNotFoundError) Error() string {
164
+	return "page not found"
165
+}
166
+
167
+func (pageNotFoundError) NotFound() {}
168
+
161 169
 // createMux initializes the main router the server uses.
162 170
 func (s *Server) createMux() *mux.Router {
163 171
 	m := mux.NewRouter()
... ...
@@ -180,8 +186,7 @@ func (s *Server) createMux() *mux.Router {
180 180
 		m.Path("/debug" + r.Path()).Handler(f)
181 181
 	}
182 182
 
183
-	err := errors.NewRequestNotFoundError(fmt.Errorf("page not found"))
184
-	notFoundHandler := httputils.MakeErrorHandler(err)
183
+	notFoundHandler := httputils.MakeErrorHandler(pageNotFoundError{})
185 184
 	m.HandleFunc(versionMatcher+"/{path:.*}", notFoundHandler)
186 185
 	m.NotFoundHandler = notFoundHandler
187 186
 
... ...
@@ -3311,10 +3311,6 @@ paths:
3311 3311
           examples:
3312 3312
             application/json:
3313 3313
               message: "No such container: c2ada9df5af8"
3314
-        406:
3315
-          description: "impossible to attach"
3316
-          schema:
3317
-            $ref: "#/definitions/ErrorResponse"
3318 3314
         409:
3319 3315
           description: "conflict"
3320 3316
           schema:
... ...
@@ -5,7 +5,6 @@ package filters
5 5
 import (
6 6
 	"encoding/json"
7 7
 	"errors"
8
-	"fmt"
9 8
 	"regexp"
10 9
 	"strings"
11 10
 
... ...
@@ -258,17 +257,33 @@ func (filters Args) Include(field string) bool {
258 258
 	return ok
259 259
 }
260 260
 
261
+type invalidFilter string
262
+
263
+func (e invalidFilter) Error() string {
264
+	return "Invalid filter '" + string(e) + "'"
265
+}
266
+
267
+func (invalidFilter) InvalidParameter() {}
268
+
261 269
 // Validate ensures that all the fields in the filter are valid.
262 270
 // It returns an error as soon as it finds an invalid field.
263 271
 func (filters Args) Validate(accepted map[string]bool) error {
264 272
 	for name := range filters.fields {
265 273
 		if !accepted[name] {
266
-			return fmt.Errorf("Invalid filter '%s'", name)
274
+			return invalidFilter(name)
267 275
 		}
268 276
 	}
269 277
 	return nil
270 278
 }
271 279
 
280
+type invalidFilterError string
281
+
282
+func (e invalidFilterError) Error() string {
283
+	return "Invalid filter: '" + string(e) + "'"
284
+}
285
+
286
+func (invalidFilterError) InvalidParameter() {}
287
+
272 288
 // WalkValues iterates over the list of filtered values for a field.
273 289
 // It stops the iteration if it finds an error and it returns that error.
274 290
 func (filters Args) WalkValues(field string, op func(value string) error) error {
... ...
@@ -241,7 +241,7 @@ func (b *Builder) build(source builder.Source, dockerfile *parser.Result) (*buil
241 241
 
242 242
 	if err := checkDispatchDockerfile(dockerfile.AST); err != nil {
243 243
 		buildsFailed.WithValues(metricsDockerfileSyntaxError).Inc()
244
-		return nil, err
244
+		return nil, validationError{err}
245 245
 	}
246 246
 
247 247
 	dispatchState, err := b.dispatchDockerfileWithCancellation(dockerfile, source)
... ...
@@ -357,7 +357,7 @@ func BuildFromConfig(config *container.Config, changes []string) (*container.Con
357 357
 
358 358
 	dockerfile, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
359 359
 	if err != nil {
360
-		return nil, err
360
+		return nil, validationError{err}
361 361
 	}
362 362
 
363 363
 	// TODO @jhowardmsft LCOW support. For now, if LCOW enabled, switch to linux.
... ...
@@ -374,7 +374,7 @@ func BuildFromConfig(config *container.Config, changes []string) (*container.Con
374 374
 	// ensure that the commands are valid
375 375
 	for _, n := range dockerfile.AST.Children {
376 376
 		if !validCommitCommands[n.Value] {
377
-			return nil, fmt.Errorf("%s is not a valid change command", n.Value)
377
+			return nil, validationError{errors.Errorf("%s is not a valid change command", n.Value)}
378 378
 		}
379 379
 	}
380 380
 
... ...
@@ -383,7 +383,7 @@ func BuildFromConfig(config *container.Config, changes []string) (*container.Con
383 383
 	b.disableCommit = true
384 384
 
385 385
 	if err := checkDispatchDockerfile(dockerfile.AST); err != nil {
386
-		return nil, err
386
+		return nil, validationError{err}
387 387
 	}
388 388
 	dispatchState := newDispatchState()
389 389
 	dispatchState.runConfig = config
... ...
@@ -784,7 +784,7 @@ func stopSignal(req dispatchRequest) error {
784 784
 	sig := req.args[0]
785 785
 	_, err := signal.ParseSignal(sig)
786 786
 	if err != nil {
787
-		return err
787
+		return validationError{err}
788 788
 	}
789 789
 
790 790
 	req.state.runConfig.StopSignal = sig
791 791
new file mode 100644
... ...
@@ -0,0 +1,15 @@
0
+package dockerfile
1
+
2
+type validationError struct {
3
+	err error
4
+}
5
+
6
+func (e validationError) Error() string {
7
+	return e.err.Error()
8
+}
9
+
10
+func (e validationError) InvalidParameter() {}
11
+
12
+func (e validationError) Cause() error {
13
+	return e.err
14
+}
... ...
@@ -139,7 +139,7 @@ func (b *Builder) dispatch(options dispatchOptions) (*dispatchState, error) {
139 139
 	// on which the daemon is running does not support a builder command.
140 140
 	if err := platformSupports(strings.ToLower(cmd)); err != nil {
141 141
 		buildsFailed.WithValues(metricsCommandNotSupportedError).Inc()
142
-		return nil, err
142
+		return nil, validationError{err}
143 143
 	}
144 144
 
145 145
 	msg := bytes.NewBufferString(fmt.Sprintf("Step %s : %s%s",
... ...
@@ -151,7 +151,7 @@ func (b *Builder) dispatch(options dispatchOptions) (*dispatchState, error) {
151 151
 		var err error
152 152
 		ast, args, err = handleOnBuildNode(node, msg)
153 153
 		if err != nil {
154
-			return nil, err
154
+			return nil, validationError{err}
155 155
 		}
156 156
 	}
157 157
 
... ...
@@ -161,7 +161,7 @@ func (b *Builder) dispatch(options dispatchOptions) (*dispatchState, error) {
161 161
 	words, err := getDispatchArgsFromNode(ast, processFunc, msg)
162 162
 	if err != nil {
163 163
 		buildsFailed.WithValues(metricsErrorProcessingCommandsError).Inc()
164
-		return nil, err
164
+		return nil, validationError{err}
165 165
 	}
166 166
 	args = append(args, words...)
167 167
 
... ...
@@ -170,7 +170,7 @@ func (b *Builder) dispatch(options dispatchOptions) (*dispatchState, error) {
170 170
 	f, ok := evaluateTable[cmd]
171 171
 	if !ok {
172 172
 		buildsFailed.WithValues(metricsUnknownInstructionError).Inc()
173
-		return nil, fmt.Errorf("unknown instruction: %s", upperCasedCmd)
173
+		return nil, validationError{errors.Errorf("unknown instruction: %s", upperCasedCmd)}
174 174
 	}
175 175
 	options.state.updateRunConfig()
176 176
 	err = f(newDispatchRequestFromOptions(options, b, args))
... ...
@@ -247,7 +247,7 @@ func (s *dispatchState) setDefaultPath() {
247 247
 
248 248
 func handleOnBuildNode(ast *parser.Node, msg *bytes.Buffer) (*parser.Node, []string, error) {
249 249
 	if ast.Next == nil {
250
-		return nil, nil, errors.New("ONBUILD requires at least one argument")
250
+		return nil, nil, validationError{errors.New("ONBUILD requires at least one argument")}
251 251
 	}
252 252
 	ast = ast.Next.Children[0]
253 253
 	msg.WriteString(" " + ast.Value + formatFlags(ast.Flags))
254 254
new file mode 100644
... ...
@@ -0,0 +1,75 @@
0
+package remotecontext
1
+
2
+type notFoundError string
3
+
4
+func (e notFoundError) Error() string {
5
+	return string(e)
6
+}
7
+
8
+func (notFoundError) NotFound() {}
9
+
10
+type requestError string
11
+
12
+func (e requestError) Error() string {
13
+	return string(e)
14
+}
15
+
16
+func (e requestError) InvalidParameter() {}
17
+
18
+type unauthorizedError string
19
+
20
+func (e unauthorizedError) Error() string {
21
+	return string(e)
22
+}
23
+
24
+func (unauthorizedError) Unauthorized() {}
25
+
26
+type forbiddenError string
27
+
28
+func (e forbiddenError) Error() string {
29
+	return string(e)
30
+}
31
+
32
+func (forbiddenError) Forbidden() {}
33
+
34
+type dnsError struct {
35
+	cause error
36
+}
37
+
38
+func (e dnsError) Error() string {
39
+	return e.cause.Error()
40
+}
41
+
42
+func (e dnsError) NotFound() {}
43
+
44
+func (e dnsError) Cause() error {
45
+	return e.cause
46
+}
47
+
48
+type systemError struct {
49
+	cause error
50
+}
51
+
52
+func (e systemError) Error() string {
53
+	return e.cause.Error()
54
+}
55
+
56
+func (e systemError) SystemError() {}
57
+
58
+func (e systemError) Cause() error {
59
+	return e.cause
60
+}
61
+
62
+type unknownError struct {
63
+	cause error
64
+}
65
+
66
+func (e unknownError) Error() string {
67
+	return e.cause.Error()
68
+}
69
+
70
+func (unknownError) Unknown() {}
71
+
72
+func (e unknownError) Cause() error {
73
+	return e.cause
74
+}
... ...
@@ -5,7 +5,9 @@ import (
5 5
 	"fmt"
6 6
 	"io"
7 7
 	"io/ioutil"
8
+	"net"
8 9
 	"net/http"
10
+	"net/url"
9 11
 	"regexp"
10 12
 
11 13
 	"github.com/docker/docker/builder"
... ...
@@ -68,20 +70,37 @@ func MakeRemoteContext(remoteURL string, contentTypeHandlers map[string]func(io.
68 68
 
69 69
 // GetWithStatusError does an http.Get() and returns an error if the
70 70
 // status code is 4xx or 5xx.
71
-func GetWithStatusError(url string) (resp *http.Response, err error) {
72
-	if resp, err = http.Get(url); err != nil {
73
-		return nil, err
71
+func GetWithStatusError(address string) (resp *http.Response, err error) {
72
+	if resp, err = http.Get(address); err != nil {
73
+		if uerr, ok := err.(*url.Error); ok {
74
+			if derr, ok := uerr.Err.(*net.DNSError); ok && !derr.IsTimeout {
75
+				return nil, dnsError{err}
76
+			}
77
+		}
78
+		return nil, systemError{err}
74 79
 	}
75 80
 	if resp.StatusCode < 400 {
76 81
 		return resp, nil
77 82
 	}
78
-	msg := fmt.Sprintf("failed to GET %s with status %s", url, resp.Status)
83
+	msg := fmt.Sprintf("failed to GET %s with status %s", address, resp.Status)
79 84
 	body, err := ioutil.ReadAll(resp.Body)
80 85
 	resp.Body.Close()
81 86
 	if err != nil {
82
-		return nil, errors.Wrapf(err, msg+": error reading body")
87
+		return nil, errors.Wrap(systemError{err}, msg+": error reading body")
88
+	}
89
+
90
+	msg += ": " + string(bytes.TrimSpace(body))
91
+	switch resp.StatusCode {
92
+	case http.StatusNotFound:
93
+		return nil, notFoundError(msg)
94
+	case http.StatusBadRequest:
95
+		return nil, requestError(msg)
96
+	case http.StatusUnauthorized:
97
+		return nil, unauthorizedError(msg)
98
+	case http.StatusForbidden:
99
+		return nil, forbiddenError(msg)
83 100
 	}
84
-	return nil, errors.Errorf(msg+": %s", bytes.TrimSpace(body))
101
+	return nil, unknownError{errors.New(msg)}
85 102
 }
86 103
 
87 104
 // inspectResponse looks into the http response data at r to determine whether its
... ...
@@ -43,6 +43,7 @@ import (
43 43
 	"github.com/docker/libnetwork/options"
44 44
 	"github.com/docker/libnetwork/types"
45 45
 	agentexec "github.com/docker/swarmkit/agent/exec"
46
+	"github.com/pkg/errors"
46 47
 	"github.com/sirupsen/logrus"
47 48
 	"golang.org/x/net/context"
48 49
 )
... ...
@@ -55,8 +56,8 @@ const (
55 55
 )
56 56
 
57 57
 var (
58
-	errInvalidEndpoint = fmt.Errorf("invalid endpoint while building port map info")
59
-	errInvalidNetwork  = fmt.Errorf("invalid network settings while building port map info")
58
+	errInvalidEndpoint = errors.New("invalid endpoint while building port map info")
59
+	errInvalidNetwork  = errors.New("invalid network settings while building port map info")
60 60
 )
61 61
 
62 62
 // Container holds the structure defining a container object.
... ...
@@ -274,7 +275,7 @@ func (container *Container) SetupWorkingDirectory(rootIDs idtools.IDPair) error
274 274
 	if err := idtools.MkdirAllAndChownNew(pth, 0755, rootIDs); err != nil {
275 275
 		pthInfo, err2 := os.Stat(pth)
276 276
 		if err2 == nil && pthInfo != nil && !pthInfo.IsDir() {
277
-			return fmt.Errorf("Cannot mkdir: %s is not a directory", container.Config.WorkingDir)
277
+			return errors.Errorf("Cannot mkdir: %s is not a directory", container.Config.WorkingDir)
278 278
 		}
279 279
 
280 280
 		return err
... ...
@@ -357,7 +358,7 @@ func (container *Container) StartLogger() (logger.Logger, error) {
357 357
 	cfg := container.HostConfig.LogConfig
358 358
 	initDriver, err := logger.GetLogDriver(cfg.Type)
359 359
 	if err != nil {
360
-		return nil, fmt.Errorf("failed to get logging factory: %v", err)
360
+		return nil, errors.Wrap(err, "failed to get logging factory")
361 361
 	}
362 362
 	info := logger.Info{
363 363
 		Config:              cfg.Config,
... ...
@@ -723,18 +724,18 @@ func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network, epC
723 723
 
724 724
 			for _, ips := range ipam.LinkLocalIPs {
725 725
 				if linkip = net.ParseIP(ips); linkip == nil && ips != "" {
726
-					return nil, fmt.Errorf("Invalid link-local IP address:%s", ipam.LinkLocalIPs)
726
+					return nil, errors.Errorf("Invalid link-local IP address: %s", ipam.LinkLocalIPs)
727 727
 				}
728 728
 				ipList = append(ipList, linkip)
729 729
 
730 730
 			}
731 731
 
732 732
 			if ip = net.ParseIP(ipam.IPv4Address); ip == nil && ipam.IPv4Address != "" {
733
-				return nil, fmt.Errorf("Invalid IPv4 address:%s)", ipam.IPv4Address)
733
+				return nil, errors.Errorf("Invalid IPv4 address: %s)", ipam.IPv4Address)
734 734
 			}
735 735
 
736 736
 			if ip6 = net.ParseIP(ipam.IPv6Address); ip6 == nil && ipam.IPv6Address != "" {
737
-				return nil, fmt.Errorf("Invalid IPv6 address:%s)", ipam.IPv6Address)
737
+				return nil, errors.Errorf("Invalid IPv6 address: %s)", ipam.IPv6Address)
738 738
 			}
739 739
 
740 740
 			createOptions = append(createOptions,
... ...
@@ -838,7 +839,7 @@ func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network, epC
838 838
 				portStart, portEnd, err = newP.Range()
839 839
 			}
840 840
 			if err != nil {
841
-				return nil, fmt.Errorf("Error parsing HostPort value(%s):%v", binding[i].HostPort, err)
841
+				return nil, errors.Wrapf(err, "Error parsing HostPort value (%s)", binding[i].HostPort)
842 842
 			}
843 843
 			pbCopy.HostPort = uint16(portStart)
844 844
 			pbCopy.HostPortEnd = uint16(portEnd)
... ...
@@ -3,7 +3,6 @@
3 3
 package container
4 4
 
5 5
 import (
6
-	"fmt"
7 6
 	"io/ioutil"
8 7
 	"os"
9 8
 	"path/filepath"
... ...
@@ -262,6 +261,14 @@ func (container *Container) ConfigMounts() []Mount {
262 262
 	return mounts
263 263
 }
264 264
 
265
+type conflictingUpdateOptions string
266
+
267
+func (e conflictingUpdateOptions) Error() string {
268
+	return string(e)
269
+}
270
+
271
+func (e conflictingUpdateOptions) Conflict() {}
272
+
265 273
 // UpdateContainer updates configuration of a container. Callers must hold a Lock on the Container.
266 274
 func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfig) error {
267 275
 	// update resources of container
... ...
@@ -273,16 +280,16 @@ func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfi
273 273
 	// once NanoCPU is already set, updating CPUPeriod/CPUQuota will be blocked, and vice versa.
274 274
 	// In the following we make sure the intended update (resources) does not conflict with the existing (cResource).
275 275
 	if resources.NanoCPUs > 0 && cResources.CPUPeriod > 0 {
276
-		return fmt.Errorf("Conflicting options: Nano CPUs cannot be updated as CPU Period has already been set")
276
+		return conflictingUpdateOptions("Conflicting options: Nano CPUs cannot be updated as CPU Period has already been set")
277 277
 	}
278 278
 	if resources.NanoCPUs > 0 && cResources.CPUQuota > 0 {
279
-		return fmt.Errorf("Conflicting options: Nano CPUs cannot be updated as CPU Quota has already been set")
279
+		return conflictingUpdateOptions("Conflicting options: Nano CPUs cannot be updated as CPU Quota has already been set")
280 280
 	}
281 281
 	if resources.CPUPeriod > 0 && cResources.NanoCPUs > 0 {
282
-		return fmt.Errorf("Conflicting options: CPU Period cannot be updated as NanoCPUs has already been set")
282
+		return conflictingUpdateOptions("Conflicting options: CPU Period cannot be updated as NanoCPUs has already been set")
283 283
 	}
284 284
 	if resources.CPUQuota > 0 && cResources.NanoCPUs > 0 {
285
-		return fmt.Errorf("Conflicting options: CPU Quota cannot be updated as NanoCPUs has already been set")
285
+		return conflictingUpdateOptions("Conflicting options: CPU Quota cannot be updated as NanoCPUs has already been set")
286 286
 	}
287 287
 
288 288
 	if resources.BlkioWeight != 0 {
... ...
@@ -310,7 +317,7 @@ func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfi
310 310
 		// if memory limit smaller than already set memoryswap limit and doesn't
311 311
 		// update the memoryswap limit, then error out.
312 312
 		if resources.Memory > cResources.MemorySwap && resources.MemorySwap == 0 {
313
-			return fmt.Errorf("Memory limit should be smaller than already set memoryswap limit, update the memoryswap at the same time")
313
+			return conflictingUpdateOptions("Memory limit should be smaller than already set memoryswap limit, update the memoryswap at the same time")
314 314
 		}
315 315
 		cResources.Memory = resources.Memory
316 316
 	}
... ...
@@ -327,7 +334,7 @@ func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfi
327 327
 	// update HostConfig of container
328 328
 	if hostConfig.RestartPolicy.Name != "" {
329 329
 		if container.HostConfig.AutoRemove && !hostConfig.RestartPolicy.IsNone() {
330
-			return fmt.Errorf("Restart policy cannot be updated because AutoRemove is enabled for the container")
330
+			return conflictingUpdateOptions("Restart policy cannot be updated because AutoRemove is enabled for the container")
331 331
 		}
332 332
 		container.HostConfig.RestartPolicy = hostConfig.RestartPolicy
333 333
 	}
... ...
@@ -28,16 +28,20 @@ func (daemon *Daemon) ContainerCopy(name string, res string) (io.ReadCloser, err
28 28
 		return nil, err
29 29
 	}
30 30
 
31
-	if res[0] == '/' || res[0] == '\\' {
32
-		res = res[1:]
33
-	}
34
-
35 31
 	// Make sure an online file-system operation is permitted.
36 32
 	if err := daemon.isOnlineFSOperationPermitted(container); err != nil {
37
-		return nil, err
33
+		return nil, systemError{err}
34
+	}
35
+
36
+	data, err := daemon.containerCopy(container, res)
37
+	if err == nil {
38
+		return data, nil
38 39
 	}
39 40
 
40
-	return daemon.containerCopy(container, res)
41
+	if os.IsNotExist(err) {
42
+		return nil, containerFileNotFound{res, name}
43
+	}
44
+	return nil, systemError{err}
41 45
 }
42 46
 
43 47
 // ContainerStatPath stats the filesystem resource at the specified path in the
... ...
@@ -50,10 +54,18 @@ func (daemon *Daemon) ContainerStatPath(name string, path string) (stat *types.C
50 50
 
51 51
 	// Make sure an online file-system operation is permitted.
52 52
 	if err := daemon.isOnlineFSOperationPermitted(container); err != nil {
53
-		return nil, err
53
+		return nil, systemError{err}
54 54
 	}
55 55
 
56
-	return daemon.containerStatPath(container, path)
56
+	stat, err = daemon.containerStatPath(container, path)
57
+	if err == nil {
58
+		return stat, nil
59
+	}
60
+
61
+	if os.IsNotExist(err) {
62
+		return nil, containerFileNotFound{path, name}
63
+	}
64
+	return nil, systemError{err}
57 65
 }
58 66
 
59 67
 // ContainerArchivePath creates an archive of the filesystem resource at the
... ...
@@ -67,10 +79,18 @@ func (daemon *Daemon) ContainerArchivePath(name string, path string) (content io
67 67
 
68 68
 	// Make sure an online file-system operation is permitted.
69 69
 	if err := daemon.isOnlineFSOperationPermitted(container); err != nil {
70
-		return nil, nil, err
70
+		return nil, nil, systemError{err}
71
+	}
72
+
73
+	content, stat, err = daemon.containerArchivePath(container, path)
74
+	if err == nil {
75
+		return content, stat, nil
71 76
 	}
72 77
 
73
-	return daemon.containerArchivePath(container, path)
78
+	if os.IsNotExist(err) {
79
+		return nil, nil, containerFileNotFound{path, name}
80
+	}
81
+	return nil, nil, systemError{err}
74 82
 }
75 83
 
76 84
 // ContainerExtractToDir extracts the given archive to the specified location
... ...
@@ -87,10 +107,18 @@ func (daemon *Daemon) ContainerExtractToDir(name, path string, copyUIDGID, noOve
87 87
 
88 88
 	// Make sure an online file-system operation is permitted.
89 89
 	if err := daemon.isOnlineFSOperationPermitted(container); err != nil {
90
-		return err
90
+		return systemError{err}
91 91
 	}
92 92
 
93
-	return daemon.containerExtractToDir(container, path, copyUIDGID, noOverwriteDirNonDir, content)
93
+	err = daemon.containerExtractToDir(container, path, copyUIDGID, noOverwriteDirNonDir, content)
94
+	if err == nil {
95
+		return nil
96
+	}
97
+
98
+	if os.IsNotExist(err) {
99
+		return containerFileNotFound{path, name}
100
+	}
101
+	return systemError{err}
94 102
 }
95 103
 
96 104
 // containerStatPath stats the filesystem resource at the specified path in this
... ...
@@ -297,6 +325,9 @@ func (daemon *Daemon) containerExtractToDir(container *container.Container, path
297 297
 }
298 298
 
299 299
 func (daemon *Daemon) containerCopy(container *container.Container, resource string) (rc io.ReadCloser, err error) {
300
+	if resource[0] == '/' || resource[0] == '\\' {
301
+		resource = resource[1:]
302
+	}
300 303
 	container.Lock()
301 304
 
302 305
 	defer func() {
... ...
@@ -5,13 +5,13 @@ import (
5 5
 	"fmt"
6 6
 	"io"
7 7
 
8
-	"github.com/docker/docker/api/errors"
9 8
 	"github.com/docker/docker/api/types/backend"
10 9
 	"github.com/docker/docker/container"
11 10
 	"github.com/docker/docker/container/stream"
12 11
 	"github.com/docker/docker/daemon/logger"
13 12
 	"github.com/docker/docker/pkg/stdcopy"
14 13
 	"github.com/docker/docker/pkg/term"
14
+	"github.com/pkg/errors"
15 15
 	"github.com/sirupsen/logrus"
16 16
 )
17 17
 
... ...
@@ -22,7 +22,7 @@ func (daemon *Daemon) ContainerAttach(prefixOrName string, c *backend.ContainerA
22 22
 	if c.DetachKeys != "" {
23 23
 		keys, err = term.ToBytes(c.DetachKeys)
24 24
 		if err != nil {
25
-			return fmt.Errorf("Invalid detach keys (%s) provided", c.DetachKeys)
25
+			return validationError{errors.Errorf("Invalid detach keys (%s) provided", c.DetachKeys)}
26 26
 		}
27 27
 	}
28 28
 
... ...
@@ -32,11 +32,11 @@ func (daemon *Daemon) ContainerAttach(prefixOrName string, c *backend.ContainerA
32 32
 	}
33 33
 	if container.IsPaused() {
34 34
 		err := fmt.Errorf("Container %s is paused, unpause the container before attach.", prefixOrName)
35
-		return errors.NewRequestConflictError(err)
35
+		return stateConflictError{err}
36 36
 	}
37 37
 	if container.IsRestarting() {
38 38
 		err := fmt.Errorf("Container %s is restarting, wait until the container is running.", prefixOrName)
39
-		return errors.NewRequestConflictError(err)
39
+		return stateConflictError{err}
40 40
 	}
41 41
 
42 42
 	cfg := stream.AttachConfig{
... ...
@@ -119,7 +119,7 @@ func (daemon *Daemon) containerAttach(c *container.Container, cfg *stream.Attach
119 119
 		}
120 120
 		cLog, ok := logDriver.(logger.LogReader)
121 121
 		if !ok {
122
-			return logger.ErrReadLogsNotSupported
122
+			return logger.ErrReadLogsNotSupported{}
123 123
 		}
124 124
 		logs := cLog.ReadLogs(logger.ReadConfig{Tail: -1})
125 125
 		defer logs.Close()
... ...
@@ -72,21 +72,6 @@ const (
72 72
 	contextPrefix         = "com.docker.swarm"
73 73
 )
74 74
 
75
-// errNoSwarm is returned on leaving a cluster that was never initialized
76
-var errNoSwarm = errors.New("This node is not part of a swarm")
77
-
78
-// errSwarmExists is returned on initialize or join request for a cluster that has already been activated
79
-var errSwarmExists = errors.New("This node is already part of a swarm. Use \"docker swarm leave\" to leave this swarm and join another one.")
80
-
81
-// errSwarmJoinTimeoutReached is returned when cluster join could not complete before timeout was reached.
82
-var errSwarmJoinTimeoutReached = errors.New("Timeout was reached before node was joined. The attempt to join the swarm will continue in the background. Use the \"docker info\" command to see the current swarm status of your node.")
83
-
84
-// errSwarmLocked is returned if the swarm is encrypted and needs a key to unlock it.
85
-var errSwarmLocked = errors.New("Swarm is encrypted and needs to be unlocked before it can be used. Please use \"docker swarm unlock\" to unlock it.")
86
-
87
-// errSwarmCertificatesExpired is returned if docker was not started for the whole validity period and they had no chance to renew automatically.
88
-var errSwarmCertificatesExpired = errors.New("Swarm certificates have expired. To replace them, leave the swarm and join again.")
89
-
90 75
 // NetworkSubnetsProvider exposes functions for retrieving the subnets
91 76
 // of networks managed by Docker, so they can be filtered.
92 77
 type NetworkSubnetsProvider interface {
... ...
@@ -343,12 +328,12 @@ func (c *Cluster) errNoManager(st nodeState) error {
343 343
 		if st.err == errSwarmCertificatesExpired {
344 344
 			return errSwarmCertificatesExpired
345 345
 		}
346
-		return errors.New("This node is not a swarm manager. Use \"docker swarm init\" or \"docker swarm join\" to connect this node to swarm and try again.")
346
+		return errors.WithStack(notAvailableError("This node is not a swarm manager. Use \"docker swarm init\" or \"docker swarm join\" to connect this node to swarm and try again."))
347 347
 	}
348 348
 	if st.swarmNode.Manager() != nil {
349
-		return errors.New("This node is not a swarm manager. Manager is being prepared or has trouble connecting to the cluster.")
349
+		return errors.WithStack(notAvailableError("This node is not a swarm manager. Manager is being prepared or has trouble connecting to the cluster."))
350 350
 	}
351
-	return errors.New("This node is not a swarm manager. Worker nodes can't be used to view or modify cluster state. Please run this command on a manager node or promote the current node to a manager.")
351
+	return errors.WithStack(notAvailableError("This node is not a swarm manager. Worker nodes can't be used to view or modify cluster state. Please run this command on a manager node or promote the current node to a manager."))
352 352
 }
353 353
 
354 354
 // Cleanup stops active swarm node. This is run before daemon shutdown.
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"net/http"
7 7
 
8 8
 	"github.com/docker/distribution/reference"
9
+	"github.com/docker/docker/api/errdefs"
9 10
 	enginetypes "github.com/docker/docker/api/types"
10 11
 	"github.com/docker/docker/api/types/swarm/runtime"
11 12
 	"github.com/docker/docker/plugin"
... ...
@@ -198,8 +199,7 @@ func (p *Controller) Wait(ctx context.Context) error {
198 198
 }
199 199
 
200 200
 func isNotFound(err error) bool {
201
-	_, ok := errors.Cause(err).(plugin.ErrNotFound)
202
-	return ok
201
+	return errdefs.IsNotFound(err)
203 202
 }
204 203
 
205 204
 // Shutdown is the shutdown phase from swarmkit
206 205
new file mode 100644
... ...
@@ -0,0 +1,114 @@
0
+package cluster
1
+
2
+const (
3
+	// errNoSwarm is returned on leaving a cluster that was never initialized
4
+	errNoSwarm notAvailableError = "This node is not part of a swarm"
5
+
6
+	// errSwarmExists is returned on initialize or join request for a cluster that has already been activated
7
+	errSwarmExists notAvailableError = "This node is already part of a swarm. Use \"docker swarm leave\" to leave this swarm and join another one."
8
+
9
+	// errSwarmJoinTimeoutReached is returned when cluster join could not complete before timeout was reached.
10
+	errSwarmJoinTimeoutReached notAvailableError = "Timeout was reached before node joined. The attempt to join the swarm will continue in the background. Use the \"docker info\" command to see the current swarm status of your node."
11
+
12
+	// errSwarmLocked is returned if the swarm is encrypted and needs a key to unlock it.
13
+	errSwarmLocked notAvailableError = "Swarm is encrypted and needs to be unlocked before it can be used. Please use \"docker swarm unlock\" to unlock it."
14
+
15
+	// errSwarmCertificatesExpired is returned if docker was not started for the whole validity period and they had no chance to renew automatically.
16
+	errSwarmCertificatesExpired notAvailableError = "Swarm certificates have expired. To replace them, leave the swarm and join again."
17
+)
18
+
19
+type notFoundError struct {
20
+	cause error
21
+}
22
+
23
+func (e notFoundError) Error() string {
24
+	return e.cause.Error()
25
+}
26
+
27
+func (e notFoundError) NotFound() {}
28
+
29
+func (e notFoundError) Cause() error {
30
+	return e.cause
31
+}
32
+
33
+type ambiguousResultsError struct {
34
+	cause error
35
+}
36
+
37
+func (e ambiguousResultsError) Error() string {
38
+	return e.cause.Error()
39
+}
40
+
41
+func (e ambiguousResultsError) InvalidParameter() {}
42
+
43
+func (e ambiguousResultsError) Cause() error {
44
+	return e.cause
45
+}
46
+
47
+type convertError struct {
48
+	cause error
49
+}
50
+
51
+func (e convertError) Error() string {
52
+	return e.cause.Error()
53
+}
54
+
55
+func (e convertError) InvalidParameter() {}
56
+
57
+func (e convertError) Cause() error {
58
+	return e.cause
59
+}
60
+
61
+type notAllowedError string
62
+
63
+func (e notAllowedError) Error() string {
64
+	return string(e)
65
+}
66
+
67
+func (e notAllowedError) Forbidden() {}
68
+
69
+type validationError struct {
70
+	cause error
71
+}
72
+
73
+func (e validationError) Error() string {
74
+	return e.cause.Error()
75
+}
76
+
77
+func (e validationError) InvalidParameter() {}
78
+
79
+func (e validationError) Cause() error {
80
+	return e.cause
81
+}
82
+
83
+type notAvailableError string
84
+
85
+func (e notAvailableError) Error() string {
86
+	return string(e)
87
+}
88
+
89
+func (e notAvailableError) Unavailable() {}
90
+
91
+type configError string
92
+
93
+func (e configError) Error() string {
94
+	return string(e)
95
+}
96
+
97
+func (e configError) InvalidParameter() {}
98
+
99
+type invalidUnlockKey struct{}
100
+
101
+func (invalidUnlockKey) Error() string {
102
+	return "swarm could not be unlocked: invalid key provided"
103
+}
104
+
105
+func (invalidUnlockKey) Unauthorized() {}
106
+
107
+type notLockedError struct{}
108
+
109
+func (notLockedError) Error() string {
110
+	return "swarm is not locked"
111
+}
112
+
113
+func (notLockedError) Conflict() {}
... ...
@@ -34,7 +34,7 @@ type Backend interface {
34 34
 	CreateManagedContainer(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error)
35 35
 	ContainerStart(name string, hostConfig *container.HostConfig, checkpoint string, checkpointDir string) error
36 36
 	ContainerStop(name string, seconds *int) error
37
-	ContainerLogs(context.Context, string, *types.ContainerLogsOptions) (<-chan *backend.LogMessage, error)
37
+	ContainerLogs(context.Context, string, *types.ContainerLogsOptions) (msgs <-chan *backend.LogMessage, tty bool, err error)
38 38
 	ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error
39 39
 	ActivateContainerServiceBinding(containerName string) error
40 40
 	DeactivateContainerServiceBinding(containerName string) error
... ...
@@ -451,7 +451,7 @@ func (c *containerAdapter) logs(ctx context.Context, options api.LogSubscription
451 451
 			}
452 452
 		}
453 453
 	}
454
-	msgs, err := c.backend.ContainerLogs(ctx, c.container.name(), apiOptions)
454
+	msgs, _, err := c.backend.ContainerLogs(ctx, c.container.name(), apiOptions)
455 455
 	if err != nil {
456 456
 		return nil, err
457 457
 	}
... ...
@@ -3,8 +3,8 @@ package cluster
3 3
 import (
4 4
 	"fmt"
5 5
 
6
-	"github.com/docker/docker/api/errors"
7 6
 	swarmapi "github.com/docker/swarmkit/api"
7
+	"github.com/pkg/errors"
8 8
 	"golang.org/x/net/context"
9 9
 )
10 10
 
... ...
@@ -15,7 +15,7 @@ func getSwarm(ctx context.Context, c swarmapi.ControlClient) (*swarmapi.Cluster,
15 15
 	}
16 16
 
17 17
 	if len(rl.Clusters) == 0 {
18
-		return nil, errors.NewRequestNotFoundError(errNoSwarm)
18
+		return nil, errors.WithStack(errNoSwarm)
19 19
 	}
20 20
 
21 21
 	// TODO: assume one cluster only
... ...
@@ -48,11 +48,11 @@ func getNode(ctx context.Context, c swarmapi.ControlClient, input string) (*swar
48 48
 
49 49
 	if len(rl.Nodes) == 0 {
50 50
 		err := fmt.Errorf("node %s not found", input)
51
-		return nil, errors.NewRequestNotFoundError(err)
51
+		return nil, notFoundError{err}
52 52
 	}
53 53
 
54 54
 	if l := len(rl.Nodes); l > 1 {
55
-		return nil, fmt.Errorf("node %s is ambiguous (%d matches found)", input, l)
55
+		return nil, ambiguousResultsError{fmt.Errorf("node %s is ambiguous (%d matches found)", input, l)}
56 56
 	}
57 57
 
58 58
 	return rl.Nodes[0], nil
... ...
@@ -84,11 +84,11 @@ func getService(ctx context.Context, c swarmapi.ControlClient, input string, ins
84 84
 
85 85
 	if len(rl.Services) == 0 {
86 86
 		err := fmt.Errorf("service %s not found", input)
87
-		return nil, errors.NewRequestNotFoundError(err)
87
+		return nil, notFoundError{err}
88 88
 	}
89 89
 
90 90
 	if l := len(rl.Services); l > 1 {
91
-		return nil, fmt.Errorf("service %s is ambiguous (%d matches found)", input, l)
91
+		return nil, ambiguousResultsError{fmt.Errorf("service %s is ambiguous (%d matches found)", input, l)}
92 92
 	}
93 93
 
94 94
 	if !insertDefaults {
... ...
@@ -128,11 +128,11 @@ func getTask(ctx context.Context, c swarmapi.ControlClient, input string) (*swar
128 128
 
129 129
 	if len(rl.Tasks) == 0 {
130 130
 		err := fmt.Errorf("task %s not found", input)
131
-		return nil, errors.NewRequestNotFoundError(err)
131
+		return nil, notFoundError{err}
132 132
 	}
133 133
 
134 134
 	if l := len(rl.Tasks); l > 1 {
135
-		return nil, fmt.Errorf("task %s is ambiguous (%d matches found)", input, l)
135
+		return nil, ambiguousResultsError{fmt.Errorf("task %s is ambiguous (%d matches found)", input, l)}
136 136
 	}
137 137
 
138 138
 	return rl.Tasks[0], nil
... ...
@@ -164,11 +164,11 @@ func getSecret(ctx context.Context, c swarmapi.ControlClient, input string) (*sw
164 164
 
165 165
 	if len(rl.Secrets) == 0 {
166 166
 		err := fmt.Errorf("secret %s not found", input)
167
-		return nil, errors.NewRequestNotFoundError(err)
167
+		return nil, notFoundError{err}
168 168
 	}
169 169
 
170 170
 	if l := len(rl.Secrets); l > 1 {
171
-		return nil, fmt.Errorf("secret %s is ambiguous (%d matches found)", input, l)
171
+		return nil, ambiguousResultsError{fmt.Errorf("secret %s is ambiguous (%d matches found)", input, l)}
172 172
 	}
173 173
 
174 174
 	return rl.Secrets[0], nil
... ...
@@ -200,11 +200,11 @@ func getConfig(ctx context.Context, c swarmapi.ControlClient, input string) (*sw
200 200
 
201 201
 	if len(rl.Configs) == 0 {
202 202
 		err := fmt.Errorf("config %s not found", input)
203
-		return nil, errors.NewRequestNotFoundError(err)
203
+		return nil, notFoundError{err}
204 204
 	}
205 205
 
206 206
 	if l := len(rl.Configs); l > 1 {
207
-		return nil, fmt.Errorf("config %s is ambiguous (%d matches found)", input, l)
207
+		return nil, ambiguousResultsError{fmt.Errorf("config %s is ambiguous (%d matches found)", input, l)}
208 208
 	}
209 209
 
210 210
 	return rl.Configs[0], nil
... ...
@@ -238,7 +238,7 @@ func getNetwork(ctx context.Context, c swarmapi.ControlClient, input string) (*s
238 238
 	}
239 239
 
240 240
 	if l := len(rl.Networks); l > 1 {
241
-		return nil, fmt.Errorf("network %s is ambiguous (%d matches found)", input, l)
241
+		return nil, ambiguousResultsError{fmt.Errorf("network %s is ambiguous (%d matches found)", input, l)}
242 242
 	}
243 243
 
244 244
 	return rl.Networks[0], nil
... ...
@@ -1,20 +1,19 @@
1 1
 package cluster
2 2
 
3 3
 import (
4
-	"errors"
5 4
 	"fmt"
6 5
 	"net"
7 6
 )
8 7
 
9
-var (
10
-	errNoSuchInterface         = errors.New("no such interface")
11
-	errNoIP                    = errors.New("could not find the system's IP address")
12
-	errMustSpecifyListenAddr   = errors.New("must specify a listening address because the address to advertise is not recognized as a system address, and a system's IP address to use could not be uniquely identified")
13
-	errBadNetworkIdentifier    = errors.New("must specify a valid IP address or interface name")
14
-	errBadListenAddr           = errors.New("listen address must be an IP address or network interface (with optional port number)")
15
-	errBadAdvertiseAddr        = errors.New("advertise address must be a non-zero IP address or network interface (with optional port number)")
16
-	errBadDataPathAddr         = errors.New("data path address must be a non-zero IP address or network interface (without a port number)")
17
-	errBadDefaultAdvertiseAddr = errors.New("default advertise address must be a non-zero IP address or network interface (without a port number)")
8
+const (
9
+	errNoSuchInterface         configError = "no such interface"
10
+	errNoIP                    configError = "could not find the system's IP address"
11
+	errMustSpecifyListenAddr   configError = "must specify a listening address because the address to advertise is not recognized as a system address, and a system's IP address to use could not be uniquely identified"
12
+	errBadNetworkIdentifier    configError = "must specify a valid IP address or interface name"
13
+	errBadListenAddr           configError = "listen address must be an IP address or network interface (with optional port number)"
14
+	errBadAdvertiseAddr        configError = "advertise address must be a non-zero IP address or network interface (with optional port number)"
15
+	errBadDataPathAddr         configError = "data path address must be a non-zero IP address or network interface (without a port number)"
16
+	errBadDefaultAdvertiseAddr configError = "default advertise address must be a non-zero IP address or network interface (without a port number)"
18 17
 )
19 18
 
20 19
 func resolveListenAddr(specifiedAddr string) (string, string, error) {
... ...
@@ -125,13 +124,13 @@ func resolveInterfaceAddr(specifiedInterface string) (net.IP, error) {
125 125
 			if ipAddr.IP.To4() != nil {
126 126
 				// IPv4
127 127
 				if interfaceAddr4 != nil {
128
-					return nil, fmt.Errorf("interface %s has more than one IPv4 address (%s and %s)", specifiedInterface, interfaceAddr4, ipAddr.IP)
128
+					return nil, configError(fmt.Sprintf("interface %s has more than one IPv4 address (%s and %s)", specifiedInterface, interfaceAddr4, ipAddr.IP))
129 129
 				}
130 130
 				interfaceAddr4 = ipAddr.IP
131 131
 			} else {
132 132
 				// IPv6
133 133
 				if interfaceAddr6 != nil {
134
-					return nil, fmt.Errorf("interface %s has more than one IPv6 address (%s and %s)", specifiedInterface, interfaceAddr6, ipAddr.IP)
134
+					return nil, configError(fmt.Sprintf("interface %s has more than one IPv6 address (%s and %s)", specifiedInterface, interfaceAddr6, ipAddr.IP))
135 135
 				}
136 136
 				interfaceAddr6 = ipAddr.IP
137 137
 			}
... ...
@@ -139,7 +138,7 @@ func resolveInterfaceAddr(specifiedInterface string) (net.IP, error) {
139 139
 	}
140 140
 
141 141
 	if interfaceAddr4 == nil && interfaceAddr6 == nil {
142
-		return nil, fmt.Errorf("interface %s has no usable IPv4 or IPv6 address", specifiedInterface)
142
+		return nil, configError(fmt.Sprintf("interface %s has no usable IPv4 or IPv6 address", specifiedInterface))
143 143
 	}
144 144
 
145 145
 	// In the case that there's exactly one IPv4 address
... ...
@@ -296,7 +295,7 @@ func listSystemIPs() []net.IP {
296 296
 
297 297
 func errMultipleIPs(interfaceA, interfaceB string, addrA, addrB net.IP) error {
298 298
 	if interfaceA == interfaceB {
299
-		return fmt.Errorf("could not choose an IP address to advertise since this system has multiple addresses on interface %s (%s and %s)", interfaceA, addrA, addrB)
299
+		return configError(fmt.Sprintf("could not choose an IP address to advertise since this system has multiple addresses on interface %s (%s and %s)", interfaceA, addrA, addrB))
300 300
 	}
301
-	return fmt.Errorf("could not choose an IP address to advertise since this system has multiple addresses on different interfaces (%s on %s and %s on %s)", addrA, interfaceA, addrB, interfaceB)
301
+	return configError(fmt.Sprintf("could not choose an IP address to advertise since this system has multiple addresses on different interfaces (%s on %s and %s on %s)", addrA, interfaceA, addrB, interfaceB))
302 302
 }
... ...
@@ -3,7 +3,6 @@ package cluster
3 3
 import (
4 4
 	"fmt"
5 5
 
6
-	apierrors "github.com/docker/docker/api/errors"
7 6
 	apitypes "github.com/docker/docker/api/types"
8 7
 	"github.com/docker/docker/api/types/network"
9 8
 	types "github.com/docker/docker/api/types/swarm"
... ...
@@ -250,8 +249,8 @@ func (c *Cluster) DetachNetwork(target string, containerID string) error {
250 250
 // CreateNetwork creates a new cluster managed network.
251 251
 func (c *Cluster) CreateNetwork(s apitypes.NetworkCreateRequest) (string, error) {
252 252
 	if runconfig.IsPreDefinedNetwork(s.Name) {
253
-		err := fmt.Errorf("%s is a pre-defined network and cannot be created", s.Name)
254
-		return "", apierrors.NewRequestForbiddenError(err)
253
+		err := notAllowedError(fmt.Sprintf("%s is a pre-defined network and cannot be created", s.Name))
254
+		return "", errors.WithStack(err)
255 255
 	}
256 256
 
257 257
 	var resp *swarmapi.CreateNetworkResponse
... ...
@@ -299,14 +298,13 @@ func (c *Cluster) populateNetworkID(ctx context.Context, client swarmapi.Control
299 299
 				// and use its id for the request.
300 300
 				apiNetwork, err = getNetwork(ctx, client, ln.Name())
301 301
 				if err != nil {
302
-					err = fmt.Errorf("could not find the corresponding predefined swarm network: %v", err)
303
-					return apierrors.NewRequestNotFoundError(err)
302
+					return errors.Wrap(notFoundError{err}, "could not find the corresponding predefined swarm network")
304 303
 				}
305 304
 				goto setid
306 305
 			}
307 306
 			if ln != nil && !ln.Info().Dynamic() {
308
-				err = fmt.Errorf("The network %s cannot be used with services. Only networks scoped to the swarm can be used, such as those created with the overlay driver.", ln.Name())
309
-				return apierrors.NewRequestForbiddenError(err)
307
+				errMsg := fmt.Sprintf("The network %s cannot be used with services. Only networks scoped to the swarm can be used, such as those created with the overlay driver.", ln.Name())
308
+				return errors.WithStack(notAllowedError(errMsg))
310 309
 			}
311 310
 			return err
312 311
 		}
... ...
@@ -1,7 +1,6 @@
1 1
 package cluster
2 2
 
3 3
 import (
4
-	apierrors "github.com/docker/docker/api/errors"
5 4
 	apitypes "github.com/docker/docker/api/types"
6 5
 	types "github.com/docker/docker/api/types/swarm"
7 6
 	"github.com/docker/docker/daemon/cluster/convert"
... ...
@@ -65,7 +64,7 @@ func (c *Cluster) UpdateNode(input string, version uint64, spec types.NodeSpec)
65 65
 	return c.lockedManagerAction(func(ctx context.Context, state nodeState) error {
66 66
 		nodeSpec, err := convert.NodeSpecToGRPC(spec)
67 67
 		if err != nil {
68
-			return apierrors.NewBadRequestError(err)
68
+			return convertError{err}
69 69
 		}
70 70
 
71 71
 		ctx, cancel := c.getRequestContext()
... ...
@@ -11,7 +11,6 @@ import (
11 11
 	"time"
12 12
 
13 13
 	"github.com/docker/distribution/reference"
14
-	apierrors "github.com/docker/docker/api/errors"
15 14
 	apitypes "github.com/docker/docker/api/types"
16 15
 	"github.com/docker/docker/api/types/backend"
17 16
 	types "github.com/docker/docker/api/types/swarm"
... ...
@@ -129,7 +128,7 @@ func (c *Cluster) CreateService(s types.ServiceSpec, encodedAuth string, queryRe
129 129
 
130 130
 		serviceSpec, err := convert.ServiceSpecToGRPC(s)
131 131
 		if err != nil {
132
-			return apierrors.NewBadRequestError(err)
132
+			return convertError{err}
133 133
 		}
134 134
 
135 135
 		resp = &apitypes.ServiceCreateResponse{}
... ...
@@ -233,7 +232,7 @@ func (c *Cluster) UpdateService(serviceIDOrName string, version uint64, spec typ
233 233
 
234 234
 		serviceSpec, err := convert.ServiceSpecToGRPC(spec)
235 235
 		if err != nil {
236
-			return apierrors.NewBadRequestError(err)
236
+			return convertError{err}
237 237
 		}
238 238
 
239 239
 		currentService, err := getService(ctx, state.controlClient, serviceIDOrName, false)
... ...
@@ -6,7 +6,6 @@ import (
6 6
 	"strings"
7 7
 	"time"
8 8
 
9
-	apierrors "github.com/docker/docker/api/errors"
10 9
 	apitypes "github.com/docker/docker/api/types"
11 10
 	"github.com/docker/docker/api/types/filters"
12 11
 	types "github.com/docker/docker/api/types/swarm"
... ...
@@ -41,7 +40,7 @@ func (c *Cluster) Init(req types.InitRequest) (string, error) {
41 41
 	}
42 42
 
43 43
 	if err := validateAndSanitizeInitRequest(&req); err != nil {
44
-		return "", apierrors.NewBadRequestError(err)
44
+		return "", validationError{err}
45 45
 	}
46 46
 
47 47
 	listenHost, listenPort, err := resolveListenAddr(req.ListenAddr)
... ...
@@ -132,12 +131,12 @@ func (c *Cluster) Join(req types.JoinRequest) error {
132 132
 	c.mu.Lock()
133 133
 	if c.nr != nil {
134 134
 		c.mu.Unlock()
135
-		return errSwarmExists
135
+		return errors.WithStack(errSwarmExists)
136 136
 	}
137 137
 	c.mu.Unlock()
138 138
 
139 139
 	if err := validateAndSanitizeJoinRequest(&req); err != nil {
140
-		return apierrors.NewBadRequestError(err)
140
+		return validationError{err}
141 141
 	}
142 142
 
143 143
 	listenHost, listenPort, err := resolveListenAddr(req.ListenAddr)
... ...
@@ -222,7 +221,7 @@ func (c *Cluster) Update(version uint64, spec types.Spec, flags types.UpdateFlag
222 222
 		// will be used to swarmkit.
223 223
 		clusterSpec, err := convert.SwarmSpecToGRPC(spec)
224 224
 		if err != nil {
225
-			return apierrors.NewBadRequestError(err)
225
+			return convertError{err}
226 226
 		}
227 227
 
228 228
 		_, err = state.controlClient.UpdateCluster(
... ...
@@ -284,7 +283,7 @@ func (c *Cluster) UnlockSwarm(req types.UnlockRequest) error {
284 284
 	} else {
285 285
 		// when manager is active, return an error of "not locked"
286 286
 		c.mu.RUnlock()
287
-		return errors.New("swarm is not locked")
287
+		return notLockedError{}
288 288
 	}
289 289
 
290 290
 	// only when swarm is locked, code running reaches here
... ...
@@ -293,7 +292,7 @@ func (c *Cluster) UnlockSwarm(req types.UnlockRequest) error {
293 293
 
294 294
 	key, err := encryption.ParseHumanReadableKey(req.UnlockKey)
295 295
 	if err != nil {
296
-		return err
296
+		return validationError{err}
297 297
 	}
298 298
 
299 299
 	config := nr.config
... ...
@@ -312,9 +311,9 @@ func (c *Cluster) UnlockSwarm(req types.UnlockRequest) error {
312 312
 
313 313
 	if err := <-nr.Ready(); err != nil {
314 314
 		if errors.Cause(err) == errSwarmLocked {
315
-			return errors.New("swarm could not be unlocked: invalid key provided")
315
+			return invalidUnlockKey{}
316 316
 		}
317
-		return fmt.Errorf("swarm component could not be started: %v", err)
317
+		return errors.Errorf("swarm component could not be started: %v", err)
318 318
 	}
319 319
 	return nil
320 320
 }
... ...
@@ -328,7 +327,7 @@ func (c *Cluster) Leave(force bool) error {
328 328
 	nr := c.nr
329 329
 	if nr == nil {
330 330
 		c.mu.Unlock()
331
-		return errNoSwarm
331
+		return errors.WithStack(errNoSwarm)
332 332
 	}
333 333
 
334 334
 	state := c.currentNodeState()
... ...
@@ -337,7 +336,7 @@ func (c *Cluster) Leave(force bool) error {
337 337
 
338 338
 	if errors.Cause(state.err) == errSwarmLocked && !force {
339 339
 		// leave a locked swarm without --force is not allowed
340
-		return errors.New("Swarm is encrypted and locked. Please unlock it first or use `--force` to ignore this message.")
340
+		return errors.WithStack(notAvailableError("Swarm is encrypted and locked. Please unlock it first or use `--force` to ignore this message."))
341 341
 	}
342 342
 
343 343
 	if state.IsManager() && !force {
... ...
@@ -348,7 +347,7 @@ func (c *Cluster) Leave(force bool) error {
348 348
 				if active && removingManagerCausesLossOfQuorum(reachable, unreachable) {
349 349
 					if isLastManager(reachable, unreachable) {
350 350
 						msg += "Removing the last manager erases all current state of the swarm. Use `--force` to ignore this message. "
351
-						return errors.New(msg)
351
+						return errors.WithStack(notAvailableError(msg))
352 352
 					}
353 353
 					msg += fmt.Sprintf("Removing this node leaves %v managers out of %v. Without a Raft quorum your swarm will be inaccessible. ", reachable-1, reachable+unreachable)
354 354
 				}
... ...
@@ -358,7 +357,7 @@ func (c *Cluster) Leave(force bool) error {
358 358
 		}
359 359
 
360 360
 		msg += "The only way to restore a swarm that has lost consensus is to reinitialize it with `--force-new-cluster`. Use `--force` to suppress this message."
361
-		return errors.New(msg)
361
+		return errors.WithStack(notAvailableError(msg))
362 362
 	}
363 363
 	// release readers in here
364 364
 	if err := nr.Stop(); err != nil {
... ...
@@ -6,7 +6,6 @@ import (
6 6
 	"path/filepath"
7 7
 	"time"
8 8
 
9
-	"github.com/docker/docker/api/errors"
10 9
 	containertypes "github.com/docker/docker/api/types/container"
11 10
 	"github.com/docker/docker/api/types/strslice"
12 11
 	"github.com/docker/docker/container"
... ...
@@ -19,6 +18,7 @@ import (
19 19
 	"github.com/docker/docker/runconfig"
20 20
 	"github.com/docker/go-connections/nat"
21 21
 	"github.com/opencontainers/selinux/go-selinux/label"
22
+	"github.com/pkg/errors"
22 23
 )
23 24
 
24 25
 // GetContainer looks for a container using the provided information, which could be
... ...
@@ -30,7 +30,7 @@ import (
30 30
 //  If none of these searches succeed, an error is returned
31 31
 func (daemon *Daemon) GetContainer(prefixOrName string) (*container.Container, error) {
32 32
 	if len(prefixOrName) == 0 {
33
-		return nil, errors.NewBadRequestError(fmt.Errorf("No container name or ID supplied"))
33
+		return nil, errors.WithStack(invalidIdentifier(prefixOrName))
34 34
 	}
35 35
 
36 36
 	if containerByID := daemon.containers.Get(prefixOrName); containerByID != nil {
... ...
@@ -48,10 +48,9 @@ func (daemon *Daemon) GetContainer(prefixOrName string) (*container.Container, e
48 48
 	if indexError != nil {
49 49
 		// When truncindex defines an error type, use that instead
50 50
 		if indexError == truncindex.ErrNotExist {
51
-			err := fmt.Errorf("No such container: %s", prefixOrName)
52
-			return nil, errors.NewRequestNotFoundError(err)
51
+			return nil, containerNotFound(prefixOrName)
53 52
 		}
54
-		return nil, indexError
53
+		return nil, systemError{indexError}
55 54
 	}
56 55
 	return daemon.containers.Get(containerID), nil
57 56
 }
... ...
@@ -136,7 +135,7 @@ func (daemon *Daemon) newContainer(name string, platform string, config *contain
136 136
 		if config.Hostname == "" {
137 137
 			config.Hostname, err = os.Hostname()
138 138
 			if err != nil {
139
-				return nil, err
139
+				return nil, systemError{err}
140 140
 			}
141 141
 		}
142 142
 	} else {
... ...
@@ -255,19 +254,19 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *containertypes.HostCon
255 255
 		// Validate the healthcheck params of Config
256 256
 		if config.Healthcheck != nil {
257 257
 			if config.Healthcheck.Interval != 0 && config.Healthcheck.Interval < containertypes.MinimumDuration {
258
-				return nil, fmt.Errorf("Interval in Healthcheck cannot be less than %s", containertypes.MinimumDuration)
258
+				return nil, errors.Errorf("Interval in Healthcheck cannot be less than %s", containertypes.MinimumDuration)
259 259
 			}
260 260
 
261 261
 			if config.Healthcheck.Timeout != 0 && config.Healthcheck.Timeout < containertypes.MinimumDuration {
262
-				return nil, fmt.Errorf("Timeout in Healthcheck cannot be less than %s", containertypes.MinimumDuration)
262
+				return nil, errors.Errorf("Timeout in Healthcheck cannot be less than %s", containertypes.MinimumDuration)
263 263
 			}
264 264
 
265 265
 			if config.Healthcheck.Retries < 0 {
266
-				return nil, fmt.Errorf("Retries in Healthcheck cannot be negative")
266
+				return nil, errors.Errorf("Retries in Healthcheck cannot be negative")
267 267
 			}
268 268
 
269 269
 			if config.Healthcheck.StartPeriod != 0 && config.Healthcheck.StartPeriod < containertypes.MinimumDuration {
270
-				return nil, fmt.Errorf("StartPeriod in Healthcheck cannot be less than %s", containertypes.MinimumDuration)
270
+				return nil, errors.Errorf("StartPeriod in Healthcheck cannot be less than %s", containertypes.MinimumDuration)
271 271
 			}
272 272
 		}
273 273
 	}
... ...
@@ -277,7 +276,7 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *containertypes.HostCon
277 277
 	}
278 278
 
279 279
 	if hostConfig.AutoRemove && !hostConfig.RestartPolicy.IsNone() {
280
-		return nil, fmt.Errorf("can't create 'AutoRemove' container with restart policy")
280
+		return nil, errors.Errorf("can't create 'AutoRemove' container with restart policy")
281 281
 	}
282 282
 
283 283
 	for _, extraHost := range hostConfig.ExtraHosts {
... ...
@@ -289,12 +288,12 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *containertypes.HostCon
289 289
 	for port := range hostConfig.PortBindings {
290 290
 		_, portStr := nat.SplitProtoPort(string(port))
291 291
 		if _, err := nat.ParsePort(portStr); err != nil {
292
-			return nil, fmt.Errorf("invalid port specification: %q", portStr)
292
+			return nil, errors.Errorf("invalid port specification: %q", portStr)
293 293
 		}
294 294
 		for _, pb := range hostConfig.PortBindings[port] {
295 295
 			_, err := nat.NewPort(nat.SplitProtoPort(pb.HostPort))
296 296
 			if err != nil {
297
-				return nil, fmt.Errorf("invalid port specification: %q", pb.HostPort)
297
+				return nil, errors.Errorf("invalid port specification: %q", pb.HostPort)
298 298
 			}
299 299
 		}
300 300
 	}
... ...
@@ -304,16 +303,16 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *containertypes.HostCon
304 304
 	switch p.Name {
305 305
 	case "always", "unless-stopped", "no":
306 306
 		if p.MaximumRetryCount != 0 {
307
-			return nil, fmt.Errorf("maximum retry count cannot be used with restart policy '%s'", p.Name)
307
+			return nil, errors.Errorf("maximum retry count cannot be used with restart policy '%s'", p.Name)
308 308
 		}
309 309
 	case "on-failure":
310 310
 		if p.MaximumRetryCount < 0 {
311
-			return nil, fmt.Errorf("maximum retry count cannot be negative")
311
+			return nil, errors.Errorf("maximum retry count cannot be negative")
312 312
 		}
313 313
 	case "":
314 314
 		// do nothing
315 315
 	default:
316
-		return nil, fmt.Errorf("invalid restart policy '%s'", p.Name)
316
+		return nil, errors.Errorf("invalid restart policy '%s'", p.Name)
317 317
 	}
318 318
 
319 319
 	// Now do platform-specific verification
... ...
@@ -14,7 +14,7 @@ func (daemon *Daemon) saveApparmorConfig(container *container.Container) error {
14 14
 	}
15 15
 
16 16
 	if err := parseSecurityOpt(container, container.HostConfig); err != nil {
17
-		return err
17
+		return validationError{err}
18 18
 	}
19 19
 
20 20
 	if !container.HostConfig.Privileged {
... ...
@@ -10,7 +10,6 @@ import (
10 10
 	"strings"
11 11
 	"time"
12 12
 
13
-	derr "github.com/docker/docker/api/errors"
14 13
 	containertypes "github.com/docker/docker/api/types/container"
15 14
 	networktypes "github.com/docker/docker/api/types/network"
16 15
 	"github.com/docker/docker/container"
... ...
@@ -923,7 +922,7 @@ func (daemon *Daemon) getNetworkedContainer(containerID, connectedContainerID st
923 923
 	}
924 924
 	if !nc.IsRunning() {
925 925
 		err := fmt.Errorf("cannot join network of a non running container: %s", connectedContainerID)
926
-		return nil, derr.NewRequestConflictError(err)
926
+		return nil, stateConflictError{err}
927 927
 	}
928 928
 	if nc.IsRestarting() {
929 929
 		return nil, errContainerIsRestarting(connectedContainerID)
... ...
@@ -91,7 +91,7 @@ func (daemon *Daemon) getPidContainer(container *container.Container) (*containe
91 91
 
92 92
 func containerIsRunning(c *container.Container) error {
93 93
 	if !c.IsRunning() {
94
-		return errors.Errorf("container %s is not running", c.ID)
94
+		return stateConflictError{errors.Errorf("container %s is not running", c.ID)}
95 95
 	}
96 96
 	return nil
97 97
 }
... ...
@@ -9,7 +9,6 @@ import (
9 9
 
10 10
 	"github.com/pkg/errors"
11 11
 
12
-	apierrors "github.com/docker/docker/api/errors"
13 12
 	"github.com/docker/docker/api/types"
14 13
 	containertypes "github.com/docker/docker/api/types/container"
15 14
 	networktypes "github.com/docker/docker/api/types/network"
... ...
@@ -37,17 +36,17 @@ func (daemon *Daemon) ContainerCreate(params types.ContainerCreateConfig) (conta
37 37
 func (daemon *Daemon) containerCreate(params types.ContainerCreateConfig, managed bool) (containertypes.ContainerCreateCreatedBody, error) {
38 38
 	start := time.Now()
39 39
 	if params.Config == nil {
40
-		return containertypes.ContainerCreateCreatedBody{}, fmt.Errorf("Config cannot be empty in order to create a container")
40
+		return containertypes.ContainerCreateCreatedBody{}, validationError{errors.New("Config cannot be empty in order to create a container")}
41 41
 	}
42 42
 
43 43
 	warnings, err := daemon.verifyContainerSettings(params.HostConfig, params.Config, false)
44 44
 	if err != nil {
45
-		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, err
45
+		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, validationError{err}
46 46
 	}
47 47
 
48
-	err = daemon.verifyNetworkingConfig(params.NetworkingConfig)
48
+	err = verifyNetworkingConfig(params.NetworkingConfig)
49 49
 	if err != nil {
50
-		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, err
50
+		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, validationError{err}
51 51
 	}
52 52
 
53 53
 	if params.HostConfig == nil {
... ...
@@ -55,12 +54,12 @@ func (daemon *Daemon) containerCreate(params types.ContainerCreateConfig, manage
55 55
 	}
56 56
 	err = daemon.adaptContainerSettings(params.HostConfig, params.AdjustCPUShares)
57 57
 	if err != nil {
58
-		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, err
58
+		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, validationError{err}
59 59
 	}
60 60
 
61 61
 	container, err := daemon.create(params, managed)
62 62
 	if err != nil {
63
-		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, daemon.imageNotExistToErrcode(err)
63
+		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, err
64 64
 	}
65 65
 	containerActions.WithValues("create").UpdateSince(start)
66 66
 
... ...
@@ -113,11 +112,11 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (
113 113
 	}
114 114
 
115 115
 	if err := daemon.mergeAndVerifyConfig(params.Config, img); err != nil {
116
-		return nil, err
116
+		return nil, validationError{err}
117 117
 	}
118 118
 
119 119
 	if err := daemon.mergeAndVerifyLogConfig(&params.HostConfig.LogConfig); err != nil {
120
-		return nil, err
120
+		return nil, validationError{err}
121 121
 	}
122 122
 
123 123
 	if container, err = daemon.newContainer(params.Name, params.Platform, params.Config, params.HostConfig, imgID, managed); err != nil {
... ...
@@ -139,7 +138,7 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (
139 139
 
140 140
 	// Set RWLayer for container after mount labels have been set
141 141
 	if err := daemon.setRWLayer(container); err != nil {
142
-		return nil, err
142
+		return nil, systemError{err}
143 143
 	}
144 144
 
145 145
 	rootIDs := daemon.idMappings.RootPair()
... ...
@@ -295,7 +294,7 @@ func (daemon *Daemon) mergeAndVerifyConfig(config *containertypes.Config, img *i
295 295
 
296 296
 // Checks if the client set configurations for more than one network while creating a container
297 297
 // Also checks if the IPAMConfig is valid
298
-func (daemon *Daemon) verifyNetworkingConfig(nwConfig *networktypes.NetworkingConfig) error {
298
+func verifyNetworkingConfig(nwConfig *networktypes.NetworkingConfig) error {
299 299
 	if nwConfig == nil || len(nwConfig.EndpointsConfig) == 0 {
300 300
 		return nil
301 301
 	}
... ...
@@ -303,14 +302,14 @@ func (daemon *Daemon) verifyNetworkingConfig(nwConfig *networktypes.NetworkingCo
303 303
 		for _, v := range nwConfig.EndpointsConfig {
304 304
 			if v != nil && v.IPAMConfig != nil {
305 305
 				if v.IPAMConfig.IPv4Address != "" && net.ParseIP(v.IPAMConfig.IPv4Address).To4() == nil {
306
-					return apierrors.NewBadRequestError(fmt.Errorf("invalid IPv4 address: %s", v.IPAMConfig.IPv4Address))
306
+					return errors.Errorf("invalid IPv4 address: %s", v.IPAMConfig.IPv4Address)
307 307
 				}
308 308
 				if v.IPAMConfig.IPv6Address != "" {
309 309
 					n := net.ParseIP(v.IPAMConfig.IPv6Address)
310 310
 					// if the address is an invalid network address (ParseIP == nil) or if it is
311 311
 					// an IPv4 address (To4() != nil), then it is an invalid IPv6 address
312 312
 					if n == nil || n.To4() != nil {
313
-						return apierrors.NewBadRequestError(fmt.Errorf("invalid IPv6 address: %s", v.IPAMConfig.IPv6Address))
313
+						return errors.Errorf("invalid IPv6 address: %s", v.IPAMConfig.IPv6Address)
314 314
 					}
315 315
 				}
316 316
 			}
... ...
@@ -321,6 +320,5 @@ func (daemon *Daemon) verifyNetworkingConfig(nwConfig *networktypes.NetworkingCo
321 321
 	for k := range nwConfig.EndpointsConfig {
322 322
 		l = append(l, k)
323 323
 	}
324
-	err := fmt.Errorf("Container cannot be connected to network endpoints: %s", strings.Join(l, ", "))
325
-	return apierrors.NewBadRequestError(err)
324
+	return errors.Errorf("Container cannot be connected to network endpoints: %s", strings.Join(l, ", "))
326 325
 }
... ...
@@ -470,6 +470,7 @@ func verifyContainerResources(resources *containertypes.Resources, sysInfo *sysi
470 470
 		warnings = append(warnings, "Your kernel does not support BPS Block I/O write limit or the cgroup is not mounted. Block I/O BPS write limit discarded.")
471 471
 		logrus.Warn("Your kernel does not support BPS Block I/O write limit or the cgroup is not mounted. Block I/O BPS write limit discarded.")
472 472
 		resources.BlkioDeviceWriteBps = []*pblkiodev.ThrottleDevice{}
473
+
473 474
 	}
474 475
 	if len(resources.BlkioDeviceReadIOps) > 0 && !sysInfo.BlkioReadIOpsDevice {
475 476
 		warnings = append(warnings, "Your kernel does not support IOPS Block read limit or the cgroup is not mounted. Block I/O IOPS read limit discarded.")
... ...
@@ -1144,13 +1145,13 @@ func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *
1144 1144
 		}
1145 1145
 		child, err := daemon.GetContainer(name)
1146 1146
 		if err != nil {
1147
-			return fmt.Errorf("Could not get container for %s", name)
1147
+			return errors.Wrapf(err, "could not get container for %s", name)
1148 1148
 		}
1149 1149
 		for child.HostConfig.NetworkMode.IsContainer() {
1150 1150
 			parts := strings.SplitN(string(child.HostConfig.NetworkMode), ":", 2)
1151 1151
 			child, err = daemon.GetContainer(parts[1])
1152 1152
 			if err != nil {
1153
-				return fmt.Errorf("Could not get container for %s", parts[1])
1153
+				return errors.Wrapf(err, "Could not get container for %s", parts[1])
1154 1154
 			}
1155 1155
 		}
1156 1156
 		if child.HostConfig.NetworkMode.IsHost() {
... ...
@@ -1181,12 +1182,12 @@ func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container
1181 1181
 
1182 1182
 func (daemon *Daemon) stats(c *container.Container) (*types.StatsJSON, error) {
1183 1183
 	if !c.IsRunning() {
1184
-		return nil, errNotRunning{c.ID}
1184
+		return nil, errNotRunning(c.ID)
1185 1185
 	}
1186 1186
 	stats, err := daemon.containerd.Stats(c.ID)
1187 1187
 	if err != nil {
1188 1188
 		if strings.Contains(err.Error(), "container not found") {
1189
-			return nil, errNotFound{c.ID}
1189
+			return nil, containerNotFound(c.ID)
1190 1190
 		}
1191 1191
 		return nil, err
1192 1192
 	}
... ...
@@ -520,14 +520,14 @@ func driverOptions(config *config.Config) []nwconfig.Option {
520 520
 
521 521
 func (daemon *Daemon) stats(c *container.Container) (*types.StatsJSON, error) {
522 522
 	if !c.IsRunning() {
523
-		return nil, errNotRunning{c.ID}
523
+		return nil, errNotRunning(c.ID)
524 524
 	}
525 525
 
526 526
 	// Obtain the stats from HCS via libcontainerd
527 527
 	stats, err := daemon.containerd.Stats(c.ID)
528 528
 	if err != nil {
529 529
 		if strings.Contains(err.Error(), "container not found") {
530
-			return nil, errNotFound{c.ID}
530
+			return nil, containerNotFound(c.ID)
531 531
 		}
532 532
 		return nil, err
533 533
 	}
... ...
@@ -7,7 +7,6 @@ import (
7 7
 	"strings"
8 8
 	"time"
9 9
 
10
-	apierrors "github.com/docker/docker/api/errors"
11 10
 	"github.com/docker/docker/api/types"
12 11
 	"github.com/docker/docker/container"
13 12
 	"github.com/docker/docker/layer"
... ...
@@ -31,7 +30,7 @@ func (daemon *Daemon) ContainerRm(name string, config *types.ContainerRmConfig)
31 31
 	// Container state RemovalInProgress should be used to avoid races.
32 32
 	if inProgress := container.SetRemovalInProgress(); inProgress {
33 33
 		err := fmt.Errorf("removal of container %s is already in progress", name)
34
-		return apierrors.NewBadRequestError(err)
34
+		return stateConflictError{err}
35 35
 	}
36 36
 	defer container.ResetRemovalInProgress()
37 37
 
... ...
@@ -87,7 +86,7 @@ func (daemon *Daemon) cleanupContainer(container *container.Container, forceRemo
87 87
 				procedure = "Unpause and then " + strings.ToLower(procedure)
88 88
 			}
89 89
 			err := fmt.Errorf("You cannot remove a %s container %s. %s", state, container.ID, procedure)
90
-			return apierrors.NewRequestConflictError(err)
90
+			return stateConflictError{err}
91 91
 		}
92 92
 		if err := daemon.Kill(container); err != nil {
93 93
 			return fmt.Errorf("Could not kill running container %s, cannot remove - %v", container.ID, err)
... ...
@@ -151,7 +150,7 @@ func (daemon *Daemon) cleanupContainer(container *container.Container, forceRemo
151 151
 func (daemon *Daemon) VolumeRm(name string, force bool) error {
152 152
 	err := daemon.volumeRm(name)
153 153
 	if err != nil && volumestore.IsInUse(err) {
154
-		return apierrors.NewRequestConflictError(err)
154
+		return stateConflictError{err}
155 155
 	}
156 156
 	if err == nil || force {
157 157
 		daemon.volumes.Purge(name)
... ...
@@ -2,52 +2,215 @@ package daemon
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"strings"
6
+	"syscall"
5 7
 
6
-	"github.com/docker/docker/api/errors"
8
+	"github.com/pkg/errors"
9
+	"google.golang.org/grpc"
7 10
 )
8 11
 
9
-func (d *Daemon) imageNotExistToErrcode(err error) error {
10
-	if dne, isDNE := err.(ErrImageDoesNotExist); isDNE {
11
-		return errors.NewRequestNotFoundError(dne)
12
-	}
13
-	return err
12
+func errNotRunning(id string) error {
13
+	return stateConflictError{errors.Errorf("Container %s is not running", id)}
14
+}
15
+
16
+func containerNotFound(id string) error {
17
+	return objNotFoundError{"container", id}
18
+}
19
+
20
+func volumeNotFound(id string) error {
21
+	return objNotFoundError{"volume", id}
22
+}
23
+
24
+func networkNotFound(id string) error {
25
+	return objNotFoundError{"network", id}
26
+}
27
+
28
+type objNotFoundError struct {
29
+	object string
30
+	id     string
14 31
 }
15 32
 
16
-type errNotRunning struct {
17
-	containerID string
33
+func (e objNotFoundError) Error() string {
34
+	return "No such " + e.object + ": " + e.id
18 35
 }
19 36
 
20
-func (e errNotRunning) Error() string {
21
-	return fmt.Sprintf("Container %s is not running", e.containerID)
37
+func (e objNotFoundError) NotFound() {}
38
+
39
+type stateConflictError struct {
40
+	cause error
41
+}
42
+
43
+func (e stateConflictError) Error() string {
44
+	return e.cause.Error()
22 45
 }
23 46
 
24
-func (e errNotRunning) ContainerIsRunning() bool {
25
-	return false
47
+func (e stateConflictError) Cause() error {
48
+	return e.cause
26 49
 }
27 50
 
51
+func (e stateConflictError) Conflict() {}
52
+
28 53
 func errContainerIsRestarting(containerID string) error {
29
-	err := fmt.Errorf("Container %s is restarting, wait until the container is running", containerID)
30
-	return errors.NewRequestConflictError(err)
54
+	cause := errors.Errorf("Container %s is restarting, wait until the container is running", containerID)
55
+	return stateConflictError{cause}
31 56
 }
32 57
 
33 58
 func errExecNotFound(id string) error {
34
-	err := fmt.Errorf("No such exec instance '%s' found in daemon", id)
35
-	return errors.NewRequestNotFoundError(err)
59
+	return objNotFoundError{"exec instance", id}
36 60
 }
37 61
 
38 62
 func errExecPaused(id string) error {
39
-	err := fmt.Errorf("Container %s is paused, unpause the container before exec", id)
40
-	return errors.NewRequestConflictError(err)
63
+	cause := errors.Errorf("Container %s is paused, unpause the container before exec", id)
64
+	return stateConflictError{cause}
65
+}
66
+
67
+type validationError struct {
68
+	cause error
69
+}
70
+
71
+func (e validationError) Error() string {
72
+	return e.cause.Error()
73
+}
74
+
75
+func (e validationError) InvalidParameter() {}
76
+
77
+func (e validationError) Cause() error {
78
+	return e.cause
79
+}
80
+
81
+type notAllowedError struct {
82
+	cause error
83
+}
84
+
85
+func (e notAllowedError) Error() string {
86
+	return e.cause.Error()
87
+}
88
+
89
+func (e notAllowedError) Forbidden() {}
90
+
91
+func (e notAllowedError) Cause() error {
92
+	return e.cause
41 93
 }
42 94
 
43
-type errNotFound struct {
44
-	containerID string
95
+type containerNotModifiedError struct {
96
+	running bool
45 97
 }
46 98
 
47
-func (e errNotFound) Error() string {
48
-	return fmt.Sprintf("Container %s is not found", e.containerID)
99
+func (e containerNotModifiedError) Error() string {
100
+	if e.running {
101
+		return "Container is already started"
102
+	}
103
+	return "Container is already stopped"
104
+}
105
+
106
+func (e containerNotModifiedError) NotModified() {}
107
+
108
+type systemError struct {
109
+	cause error
110
+}
111
+
112
+func (e systemError) Error() string {
113
+	return e.cause.Error()
114
+}
115
+
116
+func (e systemError) SystemError() {}
117
+
118
+func (e systemError) Cause() error {
119
+	return e.cause
49 120
 }
50 121
 
51
-func (e errNotFound) ContainerNotFound() bool {
52
-	return true
122
+type invalidIdentifier string
123
+
124
+func (e invalidIdentifier) Error() string {
125
+	return fmt.Sprintf("invalid name or ID supplied: %q", string(e))
126
+}
127
+
128
+func (invalidIdentifier) InvalidParameter() {}
129
+
130
+type duplicateMountPointError string
131
+
132
+func (e duplicateMountPointError) Error() string {
133
+	return "Duplicate mount point: " + string(e)
134
+}
135
+func (duplicateMountPointError) InvalidParameter() {}
136
+
137
+type containerFileNotFound struct {
138
+	file      string
139
+	container string
140
+}
141
+
142
+func (e containerFileNotFound) Error() string {
143
+	return "Could not find the file " + e.file + " in container " + e.container
144
+}
145
+
146
+func (containerFileNotFound) NotFound() {}
147
+
148
+type invalidFilter struct {
149
+	filter string
150
+	value  interface{}
151
+}
152
+
153
+func (e invalidFilter) Error() string {
154
+	msg := "Invalid filter '" + e.filter
155
+	if e.value != nil {
156
+		msg += fmt.Sprintf("=%s", e.value)
157
+	}
158
+	return msg + "'"
159
+}
160
+
161
+func (e invalidFilter) InvalidParameter() {}
162
+
163
+type unknownError struct {
164
+	cause error
165
+}
166
+
167
+func (e unknownError) Error() string {
168
+	return e.cause.Error()
169
+}
170
+
171
+func (unknownError) Unknown() {}
172
+
173
+func (e unknownError) Cause() error {
174
+	return e.cause
175
+}
176
+
177
+type startInvalidConfigError string
178
+
179
+func (e startInvalidConfigError) Error() string {
180
+	return string(e)
181
+}
182
+
183
+func (e startInvalidConfigError) InvalidParameter() {} // Is this right???
184
+
185
+func translateContainerdStartErr(cmd string, setExitCode func(int), err error) error {
186
+	errDesc := grpc.ErrorDesc(err)
187
+	contains := func(s1, s2 string) bool {
188
+		return strings.Contains(strings.ToLower(s1), s2)
189
+	}
190
+	var retErr error = unknownError{errors.New(errDesc)}
191
+	// if we receive an internal error from the initial start of a container then lets
192
+	// return it instead of entering the restart loop
193
+	// set to 127 for container cmd not found/does not exist)
194
+	if contains(errDesc, cmd) &&
195
+		(contains(errDesc, "executable file not found") ||
196
+			contains(errDesc, "no such file or directory") ||
197
+			contains(errDesc, "system cannot find the file specified")) {
198
+		setExitCode(127)
199
+		retErr = startInvalidConfigError(errDesc)
200
+	}
201
+	// set to 126 for container cmd can't be invoked errors
202
+	if contains(errDesc, syscall.EACCES.Error()) {
203
+		setExitCode(126)
204
+		retErr = startInvalidConfigError(errDesc)
205
+	}
206
+
207
+	// attempted to mount a file onto a directory, or a directory onto a file, maybe from user specified bind mounts
208
+	if contains(errDesc, syscall.ENOTDIR.Error()) {
209
+		errDesc += ": Are you trying to mount a directory onto a file (or vice-versa)? Check if the specified host path exists and is the expected type"
210
+		setExitCode(127)
211
+		retErr = startInvalidConfigError(errDesc)
212
+	}
213
+
214
+	// TODO: it would be nice to get some better errors from containerd so we can return better errors here
215
+	return retErr
53 216
 }
... ...
@@ -8,7 +8,6 @@ import (
8 8
 
9 9
 	"golang.org/x/net/context"
10 10
 
11
-	"github.com/docker/docker/api/errors"
12 11
 	"github.com/docker/docker/api/types"
13 12
 	"github.com/docker/docker/api/types/strslice"
14 13
 	"github.com/docker/docker/container"
... ...
@@ -18,6 +17,7 @@ import (
18 18
 	"github.com/docker/docker/pkg/pools"
19 19
 	"github.com/docker/docker/pkg/signal"
20 20
 	"github.com/docker/docker/pkg/term"
21
+	"github.com/pkg/errors"
21 22
 	"github.com/sirupsen/logrus"
22 23
 )
23 24
 
... ...
@@ -81,7 +81,7 @@ func (d *Daemon) getActiveContainer(name string) (*container.Container, error) {
81 81
 	}
82 82
 
83 83
 	if !container.IsRunning() {
84
-		return nil, errNotRunning{container.ID}
84
+		return nil, errNotRunning(container.ID)
85 85
 	}
86 86
 	if container.IsPaused() {
87 87
 		return nil, errExecPaused(name)
... ...
@@ -157,12 +157,12 @@ func (d *Daemon) ContainerExecStart(ctx context.Context, name string, stdin io.R
157 157
 	if ec.ExitCode != nil {
158 158
 		ec.Unlock()
159 159
 		err := fmt.Errorf("Error: Exec command %s has already run", ec.ID)
160
-		return errors.NewRequestConflictError(err)
160
+		return stateConflictError{err}
161 161
 	}
162 162
 
163 163
 	if ec.Running {
164 164
 		ec.Unlock()
165
-		return fmt.Errorf("Error: Exec command %s is already running", ec.ID)
165
+		return stateConflictError{fmt.Errorf("Error: Exec command %s is already running", ec.ID)}
166 166
 	}
167 167
 	ec.Running = true
168 168
 	ec.Unlock()
... ...
@@ -233,7 +233,7 @@ func (d *Daemon) ContainerExecStart(ctx context.Context, name string, stdin io.R
233 233
 
234 234
 	systemPid, err := d.containerd.AddProcess(ctx, c.ID, name, p, ec.InitializeStdio)
235 235
 	if err != nil {
236
-		return err
236
+		return translateContainerdStartErr(ec.Entrypoint, ec.SetExitCode, err)
237 237
 	}
238 238
 	ec.Lock()
239 239
 	ec.Pid = systemPid
... ...
@@ -254,7 +254,7 @@ func (d *Daemon) ContainerExecStart(ctx context.Context, name string, stdin io.R
254 254
 	case err := <-attachErr:
255 255
 		if err != nil {
256 256
 			if _, ok := err.(term.EscapeError); !ok {
257
-				return fmt.Errorf("exec attach failed with error: %v", err)
257
+				return errors.Wrap(systemError{err}, "exec attach failed")
258 258
 			}
259 259
 			d.LogContainerEvent(c, "exec_detach")
260 260
 		}
... ...
@@ -62,6 +62,11 @@ func (c *Config) CloseStreams() error {
62 62
 	return c.StreamConfig.CloseStreams()
63 63
 }
64 64
 
65
+// SetExitCode sets the exec config's exit code
66
+func (c *Config) SetExitCode(code int) {
67
+	c.ExitCode = &code
68
+}
69
+
65 70
 // Store keeps track of the exec configurations.
66 71
 type Store struct {
67 72
 	commands map[string]*Config
... ...
@@ -8,12 +8,12 @@ import (
8 8
 	"github.com/docker/docker/pkg/stringid"
9 9
 )
10 10
 
11
-// ErrImageDoesNotExist is error returned when no image can be found for a reference.
12
-type ErrImageDoesNotExist struct {
11
+// errImageDoesNotExist is error returned when no image can be found for a reference.
12
+type errImageDoesNotExist struct {
13 13
 	ref reference.Reference
14 14
 }
15 15
 
16
-func (e ErrImageDoesNotExist) Error() string {
16
+func (e errImageDoesNotExist) Error() string {
17 17
 	ref := e.ref
18 18
 	if named, ok := ref.(reference.Named); ok {
19 19
 		ref = reference.TagNameOnly(named)
... ...
@@ -21,18 +21,20 @@ func (e ErrImageDoesNotExist) Error() string {
21 21
 	return fmt.Sprintf("No such image: %s", reference.FamiliarString(ref))
22 22
 }
23 23
 
24
+func (e errImageDoesNotExist) NotFound() {}
25
+
24 26
 // GetImageIDAndPlatform returns an image ID and platform corresponding to the image referred to by
25 27
 // refOrID.
26 28
 func (daemon *Daemon) GetImageIDAndPlatform(refOrID string) (image.ID, string, error) {
27 29
 	ref, err := reference.ParseAnyReference(refOrID)
28 30
 	if err != nil {
29
-		return "", "", err
31
+		return "", "", validationError{err}
30 32
 	}
31 33
 	namedRef, ok := ref.(reference.Named)
32 34
 	if !ok {
33 35
 		digested, ok := ref.(reference.Digested)
34 36
 		if !ok {
35
-			return "", "", ErrImageDoesNotExist{ref}
37
+			return "", "", errImageDoesNotExist{ref}
36 38
 		}
37 39
 		id := image.IDFromDigest(digested.Digest())
38 40
 		for platform := range daemon.stores {
... ...
@@ -40,7 +42,7 @@ func (daemon *Daemon) GetImageIDAndPlatform(refOrID string) (image.ID, string, e
40 40
 				return id, platform, nil
41 41
 			}
42 42
 		}
43
-		return "", "", ErrImageDoesNotExist{ref}
43
+		return "", "", errImageDoesNotExist{ref}
44 44
 	}
45 45
 
46 46
 	for platform := range daemon.stores {
... ...
@@ -71,7 +73,7 @@ func (daemon *Daemon) GetImageIDAndPlatform(refOrID string) (image.ID, string, e
71 71
 		}
72 72
 	}
73 73
 
74
-	return "", "", ErrImageDoesNotExist{ref}
74
+	return "", "", errImageDoesNotExist{ref}
75 75
 }
76 76
 
77 77
 // GetImage returns an image corresponding to the image referred to by refOrID.
... ...
@@ -6,11 +6,11 @@ import (
6 6
 	"time"
7 7
 
8 8
 	"github.com/docker/distribution/reference"
9
-	"github.com/docker/docker/api/errors"
10 9
 	"github.com/docker/docker/api/types"
11 10
 	"github.com/docker/docker/container"
12 11
 	"github.com/docker/docker/image"
13 12
 	"github.com/docker/docker/pkg/stringid"
13
+	"github.com/pkg/errors"
14 14
 )
15 15
 
16 16
 type conflictType int
... ...
@@ -67,7 +67,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
67 67
 
68 68
 	imgID, platform, err := daemon.GetImageIDAndPlatform(imageRef)
69 69
 	if err != nil {
70
-		return nil, daemon.imageNotExistToErrcode(err)
70
+		return nil, err
71 71
 	}
72 72
 
73 73
 	repoRefs := daemon.stores[platform].referenceStore.References(imgID.Digest())
... ...
@@ -84,8 +84,8 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
84 84
 				// this image would remain "dangling" and since
85 85
 				// we really want to avoid that the client must
86 86
 				// explicitly force its removal.
87
-				err := fmt.Errorf("conflict: unable to remove repository reference %q (must force) - container %s is using its referenced image %s", imageRef, stringid.TruncateID(container.ID), stringid.TruncateID(imgID.String()))
88
-				return nil, errors.NewRequestConflictError(err)
87
+				err := errors.Errorf("conflict: unable to remove repository reference %q (must force) - container %s is using its referenced image %s", imageRef, stringid.TruncateID(container.ID), stringid.TruncateID(imgID.String()))
88
+				return nil, stateConflictError{err}
89 89
 			}
90 90
 		}
91 91
 
... ...
@@ -285,6 +285,8 @@ func (idc *imageDeleteConflict) Error() string {
285 285
 	return fmt.Sprintf("conflict: unable to delete %s (%s) - %s", stringid.TruncateID(idc.imgID.String()), forceMsg, idc.message)
286 286
 }
287 287
 
288
+func (idc *imageDeleteConflict) Conflict() {}
289
+
288 290
 // imageDeleteHelper attempts to delete the given image from this daemon. If
289 291
 // the image has any hard delete conflicts (child images or running containers
290 292
 // using the image) then it cannot be deleted. If the image has any soft delete
... ...
@@ -26,7 +26,7 @@ func (daemon *Daemon) PullImage(ctx context.Context, image, tag, platform string
26 26
 
27 27
 	ref, err := reference.ParseNormalizedNamed(image)
28 28
 	if err != nil {
29
-		return err
29
+		return validationError{err}
30 30
 	}
31 31
 
32 32
 	if tag != "" {
... ...
@@ -39,7 +39,7 @@ func (daemon *Daemon) PullImage(ctx context.Context, image, tag, platform string
39 39
 			ref, err = reference.WithTag(ref, tag)
40 40
 		}
41 41
 		if err != nil {
42
-			return err
42
+			return validationError{err}
43 43
 		}
44 44
 	}
45 45
 
... ...
@@ -96,7 +96,7 @@ func (daemon *Daemon) GetRepository(ctx context.Context, ref reference.Named, au
96 96
 	}
97 97
 	// makes sure name is not empty or `scratch`
98 98
 	if err := distribution.ValidateRepoName(repoInfo.Name); err != nil {
99
-		return nil, false, err
99
+		return nil, false, validationError{err}
100 100
 	}
101 101
 
102 102
 	// get endpoints
... ...
@@ -71,7 +71,7 @@ func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs
71 71
 		if imageFilters.ExactMatch("dangling", "true") {
72 72
 			danglingOnly = true
73 73
 		} else if !imageFilters.ExactMatch("dangling", "false") {
74
-			return nil, fmt.Errorf("Invalid filter 'dangling=%s'", imageFilters.Get("dangling"))
74
+			return nil, invalidFilter{"dangling", imageFilters.Get("dangling")}
75 75
 		}
76 76
 	}
77 77
 	if danglingOnly {
... ...
@@ -42,16 +42,16 @@ func (daemon *Daemon) ImportImage(src string, repository, platform string, tag s
42 42
 		var err error
43 43
 		newRef, err = reference.ParseNormalizedNamed(repository)
44 44
 		if err != nil {
45
-			return err
45
+			return validationError{err}
46 46
 		}
47 47
 		if _, isCanonical := newRef.(reference.Canonical); isCanonical {
48
-			return errors.New("cannot import digest reference")
48
+			return validationError{errors.New("cannot import digest reference")}
49 49
 		}
50 50
 
51 51
 		if tag != "" {
52 52
 			newRef, err = reference.WithTag(newRef, tag)
53 53
 			if err != nil {
54
-				return err
54
+				return validationError{err}
55 55
 			}
56 56
 		}
57 57
 	}
... ...
@@ -69,7 +69,7 @@ func (daemon *Daemon) ImportImage(src string, repository, platform string, tag s
69 69
 		}
70 70
 		u, err := url.Parse(src)
71 71
 		if err != nil {
72
-			return err
72
+			return validationError{err}
73 73
 		}
74 74
 
75 75
 		resp, err = remotecontext.GetWithStatusError(u.String())
... ...
@@ -11,6 +11,7 @@ import (
11 11
 	"github.com/docker/docker/api/types/versions/v1p20"
12 12
 	"github.com/docker/docker/container"
13 13
 	"github.com/docker/docker/daemon/network"
14
+	volumestore "github.com/docker/docker/volume/store"
14 15
 	"github.com/docker/go-connections/nat"
15 16
 )
16 17
 
... ...
@@ -187,7 +188,7 @@ func (daemon *Daemon) getInspectData(container *container.Container) (*types.Con
187 187
 	// could have been removed, it will cause error if we try to get the metadata,
188 188
 	// we can ignore the error if the container is dead.
189 189
 	if err != nil && !container.Dead {
190
-		return nil, err
190
+		return nil, systemError{err}
191 191
 	}
192 192
 	contJSONBase.GraphDriver.Data = graphDriverData
193 193
 
... ...
@@ -228,7 +229,10 @@ func (daemon *Daemon) ContainerExecInspect(id string) (*backend.ExecInspect, err
228 228
 func (daemon *Daemon) VolumeInspect(name string) (*types.Volume, error) {
229 229
 	v, err := daemon.volumes.Get(name)
230 230
 	if err != nil {
231
-		return nil, err
231
+		if volumestore.IsNotExist(err) {
232
+			return nil, volumeNotFound(name)
233
+		}
234
+		return nil, systemError{err}
232 235
 	}
233 236
 	apiV := volumeToAPIType(v)
234 237
 	apiV.Mountpoint = v.Path()
... ...
@@ -10,6 +10,7 @@ import (
10 10
 
11 11
 	containerpkg "github.com/docker/docker/container"
12 12
 	"github.com/docker/docker/pkg/signal"
13
+	"github.com/pkg/errors"
13 14
 	"github.com/sirupsen/logrus"
14 15
 )
15 16
 
... ...
@@ -22,6 +23,8 @@ func (e errNoSuchProcess) Error() string {
22 22
 	return fmt.Sprintf("Cannot kill process (pid=%d) with signal %d: no such process.", e.pid, e.signal)
23 23
 }
24 24
 
25
+func (errNoSuchProcess) NotFound() {}
26
+
25 27
 // isErrNoSuchProcess returns true if the error
26 28
 // is an instance of errNoSuchProcess.
27 29
 func isErrNoSuchProcess(err error) bool {
... ...
@@ -61,7 +64,7 @@ func (daemon *Daemon) killWithSignal(container *containerpkg.Container, sig int)
61 61
 	defer container.Unlock()
62 62
 
63 63
 	if !container.Running {
64
-		return errNotRunning{container.ID}
64
+		return errNotRunning(container.ID)
65 65
 	}
66 66
 
67 67
 	var unpause bool
... ...
@@ -91,8 +94,9 @@ func (daemon *Daemon) killWithSignal(container *containerpkg.Container, sig int)
91 91
 	}
92 92
 
93 93
 	if err := daemon.kill(container, sig); err != nil {
94
-		err = fmt.Errorf("Cannot kill container %s: %s", container.ID, err)
94
+		err = errors.Wrapf(err, "Cannot kill container %s", container.ID)
95 95
 		// if container or process not exists, ignore the error
96
+		// TODO: we shouldn't have to parse error strings from containerd
96 97
 		if strings.Contains(err.Error(), "container not found") ||
97 98
 			strings.Contains(err.Error(), "no such process") {
98 99
 			logrus.Warnf("container kill failed because of 'container not found' or 'no such process': %s", err.Error())
... ...
@@ -119,7 +123,7 @@ func (daemon *Daemon) killWithSignal(container *containerpkg.Container, sig int)
119 119
 // Kill forcefully terminates a container.
120 120
 func (daemon *Daemon) Kill(container *containerpkg.Container) error {
121 121
 	if !container.IsRunning() {
122
-		return errNotRunning{container.ID}
122
+		return errNotRunning(container.ID)
123 123
 	}
124 124
 
125 125
 	// 1. Send SIGKILL
... ...
@@ -1,7 +1,6 @@
1 1
 package daemon
2 2
 
3 3
 import (
4
-	"errors"
5 4
 	"fmt"
6 5
 	"sort"
7 6
 	"strconv"
... ...
@@ -13,6 +12,7 @@ import (
13 13
 	"github.com/docker/docker/image"
14 14
 	"github.com/docker/docker/volume"
15 15
 	"github.com/docker/go-connections/nat"
16
+	"github.com/pkg/errors"
16 17
 	"github.com/sirupsen/logrus"
17 18
 )
18 19
 
... ...
@@ -265,7 +265,7 @@ func (daemon *Daemon) foldFilter(view container.View, config *types.ContainerLis
265 265
 
266 266
 	err = psFilters.WalkValues("status", func(value string) error {
267 267
 		if !container.IsValidStateString(value) {
268
-			return fmt.Errorf("Unrecognised filter value for status: %s", value)
268
+			return invalidFilter{"status", value}
269 269
 		}
270 270
 
271 271
 		config.All = true
... ...
@@ -284,13 +284,13 @@ func (daemon *Daemon) foldFilter(view container.View, config *types.ContainerLis
284 284
 			taskFilter = true
285 285
 			isTask = false
286 286
 		} else {
287
-			return nil, fmt.Errorf("Invalid filter 'is-task=%s'", psFilters.Get("is-task"))
287
+			return nil, invalidFilter{"is-task", psFilters.Get("is-task")}
288 288
 		}
289 289
 	}
290 290
 
291 291
 	err = psFilters.WalkValues("health", func(value string) error {
292 292
 		if !container.IsValidHealthString(value) {
293
-			return fmt.Errorf("Unrecognised filter value for health: %s", value)
293
+			return validationError{errors.Errorf("Unrecognised filter value for health: %s", value)}
294 294
 		}
295 295
 
296 296
 		return nil
... ...
@@ -567,7 +567,7 @@ func (daemon *Daemon) refreshImage(s *container.Snapshot, ctx *listContext) (*ty
567 567
 	image := s.Image // keep the original ref if still valid (hasn't changed)
568 568
 	if image != s.ImageID {
569 569
 		id, _, err := daemon.GetImageIDAndPlatform(image)
570
-		if _, isDNE := err.(ErrImageDoesNotExist); err != nil && !isDNE {
570
+		if _, isDNE := err.(errImageDoesNotExist); err != nil && !isDNE {
571 571
 			return nil, err
572 572
 		}
573 573
 		if err != nil || id.String() != s.ImageID {
... ...
@@ -653,7 +653,7 @@ func (daemon *Daemon) filterVolumes(vols []volume.Volume, filter filters.Args) (
653 653
 		if filter.ExactMatch("dangling", "true") || filter.ExactMatch("dangling", "1") {
654 654
 			danglingOnly = true
655 655
 		} else if !filter.ExactMatch("dangling", "false") && !filter.ExactMatch("dangling", "0") {
656
-			return nil, fmt.Errorf("Invalid filter 'dangling=%s'", filter.Get("dangling"))
656
+			return nil, invalidFilter{"dangling", filter.Get("dangling")}
657 657
 		}
658 658
 		retVols = daemon.volumes.FilterByUsed(retVols, !danglingOnly)
659 659
 	}
... ...
@@ -8,7 +8,6 @@
8 8
 package logger
9 9
 
10 10
 import (
11
-	"errors"
12 11
 	"sync"
13 12
 	"time"
14 13
 
... ...
@@ -16,8 +15,15 @@ import (
16 16
 	"github.com/docker/docker/pkg/jsonlog"
17 17
 )
18 18
 
19
-// ErrReadLogsNotSupported is returned when the logger does not support reading logs.
20
-var ErrReadLogsNotSupported = errors.New("configured logging driver does not support reading")
19
+// ErrReadLogsNotSupported is returned when the underlying log driver does not support reading
20
+type ErrReadLogsNotSupported struct{}
21
+
22
+func (ErrReadLogsNotSupported) Error() string {
23
+	return "configured logging driver does not support reading"
24
+}
25
+
26
+// NotImplemented makes this error implement the `NotImplemented` interface from api/errdefs
27
+func (ErrReadLogsNotSupported) NotImplemented() {}
21 28
 
22 29
 const (
23 30
 	// TimeFormat is the time format used for timestamps sent to log readers.
... ...
@@ -22,7 +22,7 @@ import (
22 22
 //
23 23
 // if it returns nil, the config channel will be active and return log
24 24
 // messages until it runs out or the context is canceled.
25
-func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, config *types.ContainerLogsOptions) (<-chan *backend.LogMessage, error) {
25
+func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, config *types.ContainerLogsOptions) (<-chan *backend.LogMessage, bool, error) {
26 26
 	lg := logrus.WithFields(logrus.Fields{
27 27
 		"module":    "daemon",
28 28
 		"method":    "(*Daemon).ContainerLogs",
... ...
@@ -30,24 +30,24 @@ func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, c
30 30
 	})
31 31
 
32 32
 	if !(config.ShowStdout || config.ShowStderr) {
33
-		return nil, errors.New("You must choose at least one stream")
33
+		return nil, false, validationError{errors.New("You must choose at least one stream")}
34 34
 	}
35 35
 	container, err := daemon.GetContainer(containerName)
36 36
 	if err != nil {
37
-		return nil, err
37
+		return nil, false, err
38 38
 	}
39 39
 
40 40
 	if container.RemovalInProgress || container.Dead {
41
-		return nil, errors.New("can not get logs from container which is dead or marked for removal")
41
+		return nil, false, stateConflictError{errors.New("can not get logs from container which is dead or marked for removal")}
42 42
 	}
43 43
 
44 44
 	if container.HostConfig.LogConfig.Type == "none" {
45
-		return nil, logger.ErrReadLogsNotSupported
45
+		return nil, false, logger.ErrReadLogsNotSupported{}
46 46
 	}
47 47
 
48 48
 	cLog, cLogCreated, err := daemon.getLogger(container)
49 49
 	if err != nil {
50
-		return nil, err
50
+		return nil, false, err
51 51
 	}
52 52
 	if cLogCreated {
53 53
 		defer func() {
... ...
@@ -59,7 +59,7 @@ func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, c
59 59
 
60 60
 	logReader, ok := cLog.(logger.LogReader)
61 61
 	if !ok {
62
-		return nil, logger.ErrReadLogsNotSupported
62
+		return nil, false, logger.ErrReadLogsNotSupported{}
63 63
 	}
64 64
 
65 65
 	follow := config.Follow && !cLogCreated
... ...
@@ -72,7 +72,7 @@ func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, c
72 72
 	if config.Since != "" {
73 73
 		s, n, err := timetypes.ParseTimestamps(config.Since, 0)
74 74
 		if err != nil {
75
-			return nil, err
75
+			return nil, false, err
76 76
 		}
77 77
 		since = time.Unix(s, n)
78 78
 	}
... ...
@@ -137,7 +137,7 @@ func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, c
137 137
 			}
138 138
 		}
139 139
 	}()
140
-	return messageChan, nil
140
+	return messageChan, container.Config.Tty, nil
141 141
 }
142 142
 
143 143
 func (daemon *Daemon) getLogger(container *container.Container) (l logger.Logger, created bool, err error) {
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"github.com/docker/docker/container"
9 9
 	"github.com/docker/docker/pkg/namesgenerator"
10 10
 	"github.com/docker/docker/pkg/stringid"
11
+	"github.com/pkg/errors"
11 12
 	"github.com/sirupsen/logrus"
12 13
 )
13 14
 
... ...
@@ -55,7 +56,7 @@ func (daemon *Daemon) generateIDAndName(name string) (string, string, error) {
55 55
 
56 56
 func (daemon *Daemon) reserveName(id, name string) (string, error) {
57 57
 	if !validContainerNamePattern.MatchString(strings.TrimPrefix(name, "/")) {
58
-		return "", fmt.Errorf("Invalid container name (%s), only %s are allowed", name, validContainerNameChars)
58
+		return "", validationError{errors.Errorf("Invalid container name (%s), only %s are allowed", name, validContainerNameChars)}
59 59
 	}
60 60
 	if name[0] != '/' {
61 61
 		name = "/" + name
... ...
@@ -68,9 +69,9 @@ func (daemon *Daemon) reserveName(id, name string) (string, error) {
68 68
 				logrus.Errorf("got unexpected error while looking up reserved name: %v", err)
69 69
 				return "", err
70 70
 			}
71
-			return "", fmt.Errorf("Conflict. The container name %q is already in use by container %q. You have to remove (or rename) that container to be able to reuse that name.", name, id)
71
+			return "", validationError{errors.Errorf("Conflict. The container name %q is already in use by container %q. You have to remove (or rename) that container to be able to reuse that name.", name, id)}
72 72
 		}
73
-		return "", fmt.Errorf("error reserving name: %q, error: %v", name, err)
73
+		return "", errors.Wrapf(err, "error reserving name: %q", name)
74 74
 	}
75 75
 	return name, nil
76 76
 }
... ...
@@ -8,7 +8,6 @@ import (
8 8
 	"strings"
9 9
 	"sync"
10 10
 
11
-	apierrors "github.com/docker/docker/api/errors"
12 11
 	"github.com/docker/docker/api/types"
13 12
 	"github.com/docker/docker/api/types/network"
14 13
 	clustertypes "github.com/docker/docker/daemon/cluster/provider"
... ...
@@ -57,10 +56,10 @@ func (daemon *Daemon) GetNetworkByID(partialID string) (libnetwork.Network, erro
57 57
 	list := daemon.GetNetworksByID(partialID)
58 58
 
59 59
 	if len(list) == 0 {
60
-		return nil, libnetwork.ErrNoSuchNetwork(partialID)
60
+		return nil, errors.WithStack(networkNotFound(partialID))
61 61
 	}
62 62
 	if len(list) > 1 {
63
-		return nil, libnetwork.ErrInvalidID(partialID)
63
+		return nil, errors.WithStack(invalidIdentifier(partialID))
64 64
 	}
65 65
 	return list[0], nil
66 66
 }
... ...
@@ -287,7 +286,7 @@ func (daemon *Daemon) CreateNetwork(create types.NetworkCreateRequest) (*types.N
287 287
 func (daemon *Daemon) createNetwork(create types.NetworkCreateRequest, id string, agent bool) (*types.NetworkCreateResponse, error) {
288 288
 	if runconfig.IsPreDefinedNetwork(create.Name) && !agent {
289 289
 		err := fmt.Errorf("%s is a pre-defined network and cannot be created", create.Name)
290
-		return nil, apierrors.NewRequestForbiddenError(err)
290
+		return nil, notAllowedError{err}
291 291
 	}
292 292
 
293 293
 	var warning string
... ...
@@ -504,7 +503,7 @@ func (daemon *Daemon) deleteNetwork(networkID string, dynamic bool) error {
504 504
 
505 505
 	if runconfig.IsPreDefinedNetwork(nw.Name()) && !dynamic {
506 506
 		err := fmt.Errorf("%s is a pre-defined network and cannot be removed", nw.Name())
507
-		return apierrors.NewRequestForbiddenError(err)
507
+		return notAllowedError{err}
508 508
 	}
509 509
 
510 510
 	if dynamic && !nw.Info().Dynamic() {
... ...
@@ -514,7 +513,7 @@ func (daemon *Daemon) deleteNetwork(networkID string, dynamic bool) error {
514 514
 			return nil
515 515
 		}
516 516
 		err := fmt.Errorf("%s is not a dynamic network", nw.Name())
517
-		return apierrors.NewRequestForbiddenError(err)
517
+		return notAllowedError{err}
518 518
 	}
519 519
 
520 520
 	if err := nw.Delete(); err != nil {
... ...
@@ -518,7 +518,7 @@ func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []c
518 518
 	for _, m := range mounts {
519 519
 		for _, cm := range s.Mounts {
520 520
 			if cm.Destination == m.Destination {
521
-				return fmt.Errorf("Duplicate mount point '%s'", m.Destination)
521
+				return duplicateMountPointError(m.Destination)
522 522
 			}
523 523
 		}
524 524
 
... ...
@@ -28,7 +28,7 @@ func (daemon *Daemon) containerPause(container *container.Container) error {
28 28
 
29 29
 	// We cannot Pause the container which is not running
30 30
 	if !container.Running {
31
-		return errNotRunning{container.ID}
31
+		return errNotRunning(container.ID)
32 32
 	}
33 33
 
34 34
 	// We cannot Pause the container which is already paused
... ...
@@ -186,7 +186,7 @@ func (daemon *Daemon) ImagesPrune(ctx context.Context, pruneFilters filters.Args
186 186
 		if pruneFilters.ExactMatch("dangling", "false") || pruneFilters.ExactMatch("dangling", "0") {
187 187
 			danglingOnly = false
188 188
 		} else if !pruneFilters.ExactMatch("dangling", "true") && !pruneFilters.ExactMatch("dangling", "1") {
189
-			return nil, fmt.Errorf("Invalid filter 'dangling=%s'", pruneFilters.Get("dangling"))
189
+			return nil, invalidFilter{"dangling", pruneFilters.Get("dangling")}
190 190
 		}
191 191
 	}
192 192
 
... ...
@@ -1,12 +1,11 @@
1 1
 package daemon
2 2
 
3 3
 import (
4
-	"errors"
5
-	"fmt"
6 4
 	"strings"
7 5
 
8 6
 	dockercontainer "github.com/docker/docker/container"
9 7
 	"github.com/docker/libnetwork"
8
+	"github.com/pkg/errors"
10 9
 	"github.com/sirupsen/logrus"
11 10
 )
12 11
 
... ...
@@ -20,7 +19,7 @@ func (daemon *Daemon) ContainerRename(oldName, newName string) error {
20 20
 	)
21 21
 
22 22
 	if oldName == "" || newName == "" {
23
-		return errors.New("Neither old nor new names may be empty")
23
+		return validationError{errors.New("Neither old nor new names may be empty")}
24 24
 	}
25 25
 
26 26
 	if newName[0] != '/' {
... ...
@@ -39,19 +38,19 @@ func (daemon *Daemon) ContainerRename(oldName, newName string) error {
39 39
 	oldIsAnonymousEndpoint := container.NetworkSettings.IsAnonymousEndpoint
40 40
 
41 41
 	if oldName == newName {
42
-		return errors.New("Renaming a container with the same name as its current name")
42
+		return validationError{errors.New("Renaming a container with the same name as its current name")}
43 43
 	}
44 44
 
45 45
 	links := map[string]*dockercontainer.Container{}
46 46
 	for k, v := range daemon.linkIndex.children(container) {
47 47
 		if !strings.HasPrefix(k, oldName) {
48
-			return fmt.Errorf("Linked container %s does not match parent %s", k, oldName)
48
+			return validationError{errors.Errorf("Linked container %s does not match parent %s", k, oldName)}
49 49
 		}
50 50
 		links[strings.TrimPrefix(k, oldName)] = v
51 51
 	}
52 52
 
53 53
 	if newName, err = daemon.reserveName(container.ID, newName); err != nil {
54
-		return fmt.Errorf("Error when allocating new name: %v", err)
54
+		return errors.Wrap(err, "Error when allocating new name")
55 55
 	}
56 56
 
57 57
 	for k, v := range links {
... ...
@@ -15,7 +15,7 @@ func (daemon *Daemon) ContainerResize(name string, height, width int) error {
15 15
 	}
16 16
 
17 17
 	if !container.IsRunning() {
18
-		return errNotRunning{container.ID}
18
+		return errNotRunning(container.ID)
19 19
 	}
20 20
 
21 21
 	if err = daemon.containerd.Resize(container.ID, libcontainerd.InitFriendlyName, width, height); err == nil {
... ...
@@ -1,7 +1,6 @@
1 1
 package daemon
2 2
 
3 3
 import (
4
-	"fmt"
5 4
 	"strconv"
6 5
 
7 6
 	"golang.org/x/net/context"
... ...
@@ -38,14 +37,14 @@ func (daemon *Daemon) SearchRegistryForImages(ctx context.Context, filtersArgs s
38 38
 		if searchFilters.UniqueExactMatch("is-automated", "true") {
39 39
 			isAutomated = true
40 40
 		} else if !searchFilters.UniqueExactMatch("is-automated", "false") {
41
-			return nil, fmt.Errorf("Invalid filter 'is-automated=%s'", searchFilters.Get("is-automated"))
41
+			return nil, invalidFilter{"is-automated", searchFilters.Get("is-automated")}
42 42
 		}
43 43
 	}
44 44
 	if searchFilters.Include("is-official") {
45 45
 		if searchFilters.UniqueExactMatch("is-official", "true") {
46 46
 			isOfficial = true
47 47
 		} else if !searchFilters.UniqueExactMatch("is-official", "false") {
48
-			return nil, fmt.Errorf("Invalid filter 'is-official=%s'", searchFilters.Get("is-official"))
48
+			return nil, invalidFilter{"is-official", searchFilters.Get("is-official")}
49 49
 		}
50 50
 	}
51 51
 	if searchFilters.Include("stars") {
... ...
@@ -53,7 +52,7 @@ func (daemon *Daemon) SearchRegistryForImages(ctx context.Context, filtersArgs s
53 53
 		for _, hasStar := range hasStars {
54 54
 			iHasStar, err := strconv.Atoi(hasStar)
55 55
 			if err != nil {
56
-				return nil, fmt.Errorf("Invalid filter 'stars=%s'", hasStar)
56
+				return nil, invalidFilter{"stars", hasStar}
57 57
 			}
58 58
 			if iHasStar > hasStarFilter {
59 59
 				hasStarFilter = iHasStar
... ...
@@ -1,26 +1,20 @@
1 1
 package daemon
2 2
 
3 3
 import (
4
-	"fmt"
5
-	"net/http"
6 4
 	"runtime"
7
-	"strings"
8
-	"syscall"
9 5
 	"time"
10 6
 
11
-	"google.golang.org/grpc"
12
-
13
-	apierrors "github.com/docker/docker/api/errors"
14 7
 	"github.com/docker/docker/api/types"
15 8
 	containertypes "github.com/docker/docker/api/types/container"
16 9
 	"github.com/docker/docker/container"
10
+	"github.com/pkg/errors"
17 11
 	"github.com/sirupsen/logrus"
18 12
 )
19 13
 
20 14
 // ContainerStart starts a container.
21 15
 func (daemon *Daemon) ContainerStart(name string, hostConfig *containertypes.HostConfig, checkpoint string, checkpointDir string) error {
22 16
 	if checkpoint != "" && !daemon.HasExperimental() {
23
-		return apierrors.NewBadRequestError(fmt.Errorf("checkpoint is only supported in experimental mode"))
17
+		return validationError{errors.New("checkpoint is only supported in experimental mode")}
24 18
 	}
25 19
 
26 20
 	container, err := daemon.GetContainer(name)
... ...
@@ -28,13 +22,26 @@ func (daemon *Daemon) ContainerStart(name string, hostConfig *containertypes.Hos
28 28
 		return err
29 29
 	}
30 30
 
31
-	if container.IsPaused() {
32
-		return fmt.Errorf("Cannot start a paused container, try unpause instead.")
31
+	validateState := func() error {
32
+		container.Lock()
33
+		defer container.Unlock()
34
+
35
+		if container.Paused {
36
+			return stateConflictError{errors.New("Cannot start a paused container, try unpause instead.")}
37
+		}
38
+
39
+		if container.Running {
40
+			return containerNotModifiedError{running: true}
41
+		}
42
+
43
+		if container.RemovalInProgress || container.Dead {
44
+			return stateConflictError{errors.New("Container is marked for removal and cannot be started.")}
45
+		}
46
+		return nil
33 47
 	}
34 48
 
35
-	if container.IsRunning() {
36
-		err := fmt.Errorf("Container already started")
37
-		return apierrors.NewErrorWithStatusCode(err, http.StatusNotModified)
49
+	if err := validateState(); err != nil {
50
+		return err
38 51
 	}
39 52
 
40 53
 	// Windows does not have the backwards compatibility issue here.
... ...
@@ -45,13 +52,13 @@ func (daemon *Daemon) ContainerStart(name string, hostConfig *containertypes.Hos
45 45
 			logrus.Warn("DEPRECATED: Setting host configuration options when the container starts is deprecated and has been removed in Docker 1.12")
46 46
 			oldNetworkMode := container.HostConfig.NetworkMode
47 47
 			if err := daemon.setSecurityOptions(container, hostConfig); err != nil {
48
-				return err
48
+				return validationError{err}
49 49
 			}
50 50
 			if err := daemon.mergeAndVerifyLogConfig(&hostConfig.LogConfig); err != nil {
51
-				return err
51
+				return validationError{err}
52 52
 			}
53 53
 			if err := daemon.setHostConfig(container, hostConfig); err != nil {
54
-				return err
54
+				return validationError{err}
55 55
 			}
56 56
 			newNetworkMode := container.HostConfig.NetworkMode
57 57
 			if string(oldNetworkMode) != string(newNetworkMode) {
... ...
@@ -59,31 +66,34 @@ func (daemon *Daemon) ContainerStart(name string, hostConfig *containertypes.Hos
59 59
 				// old networks. It is a deprecated feature and has been removed in Docker 1.12
60 60
 				container.NetworkSettings.Networks = nil
61 61
 				if err := container.CheckpointTo(daemon.containersReplica); err != nil {
62
-					return err
62
+					return systemError{err}
63 63
 				}
64 64
 			}
65 65
 			container.InitDNSHostConfig()
66 66
 		}
67 67
 	} else {
68 68
 		if hostConfig != nil {
69
-			return fmt.Errorf("Supplying a hostconfig on start is not supported. It should be supplied on create")
69
+			return validationError{errors.New("Supplying a hostconfig on start is not supported. It should be supplied on create")}
70 70
 		}
71 71
 	}
72 72
 
73 73
 	// check if hostConfig is in line with the current system settings.
74 74
 	// It may happen cgroups are umounted or the like.
75 75
 	if _, err = daemon.verifyContainerSettings(container.HostConfig, nil, false); err != nil {
76
-		return err
76
+		return validationError{err}
77 77
 	}
78 78
 	// Adapt for old containers in case we have updates in this function and
79 79
 	// old containers never have chance to call the new function in create stage.
80 80
 	if hostConfig != nil {
81 81
 		if err := daemon.adaptContainerSettings(container.HostConfig, false); err != nil {
82
-			return err
82
+			return validationError{err}
83 83
 		}
84 84
 	}
85 85
 
86
-	return daemon.containerStart(container, checkpoint, checkpointDir, true)
86
+	if err := daemon.containerStart(container, checkpoint, checkpointDir, true); err != nil {
87
+		return err
88
+	}
89
+	return nil
87 90
 }
88 91
 
89 92
 // containerStart prepares the container to run by setting up everything the
... ...
@@ -100,7 +110,7 @@ func (daemon *Daemon) containerStart(container *container.Container, checkpoint
100 100
 	}
101 101
 
102 102
 	if container.RemovalInProgress || container.Dead {
103
-		return fmt.Errorf("Container is marked for removal and cannot be started.")
103
+		return stateConflictError{errors.New("Container is marked for removal and cannot be started.")}
104 104
 	}
105 105
 
106 106
 	// if we encounter an error during start we need to ensure that any other
... ...
@@ -139,7 +149,7 @@ func (daemon *Daemon) containerStart(container *container.Container, checkpoint
139 139
 
140 140
 	spec, err := daemon.createSpec(container)
141 141
 	if err != nil {
142
-		return err
142
+		return systemError{err}
143 143
 	}
144 144
 
145 145
 	createOptions, err := daemon.getLibcontainerdCreateOptions(container)
... ...
@@ -160,32 +170,8 @@ func (daemon *Daemon) containerStart(container *container.Container, checkpoint
160 160
 	}
161 161
 
162 162
 	if err := daemon.containerd.Create(container.ID, checkpoint, checkpointDir, *spec, container.InitializeStdio, createOptions...); err != nil {
163
-		errDesc := grpc.ErrorDesc(err)
164
-		contains := func(s1, s2 string) bool {
165
-			return strings.Contains(strings.ToLower(s1), s2)
166
-		}
167
-		logrus.Errorf("Create container failed with error: %s", errDesc)
168
-		// if we receive an internal error from the initial start of a container then lets
169
-		// return it instead of entering the restart loop
170
-		// set to 127 for container cmd not found/does not exist)
171
-		if contains(errDesc, container.Path) &&
172
-			(contains(errDesc, "executable file not found") ||
173
-				contains(errDesc, "no such file or directory") ||
174
-				contains(errDesc, "system cannot find the file specified")) {
175
-			container.SetExitCode(127)
176
-		}
177
-		// set to 126 for container cmd can't be invoked errors
178
-		if contains(errDesc, syscall.EACCES.Error()) {
179
-			container.SetExitCode(126)
180
-		}
181
-
182
-		// attempted to mount a file onto a directory, or a directory onto a file, maybe from user specified bind mounts
183
-		if contains(errDesc, syscall.ENOTDIR.Error()) {
184
-			errDesc += ": Are you trying to mount a directory onto a file (or vice-versa)? Check if the specified host path exists and is the expected type"
185
-			container.SetExitCode(127)
186
-		}
163
+		return translateContainerdStartErr(container.Path, container.SetExitCode, err)
187 164
 
188
-		return fmt.Errorf("%s", errDesc)
189 165
 	}
190 166
 
191 167
 	containerActions.WithValues("start").UpdateSince(start)
... ...
@@ -3,10 +3,9 @@
3 3
 package daemon
4 4
 
5 5
 import (
6
-	"fmt"
7
-
8 6
 	"github.com/docker/docker/container"
9 7
 	"github.com/docker/docker/libcontainerd"
8
+	"github.com/pkg/errors"
10 9
 )
11 10
 
12 11
 // getLibcontainerdCreateOptions callers must hold a lock on the container
... ...
@@ -21,7 +20,7 @@ func (daemon *Daemon) getLibcontainerdCreateOptions(container *container.Contain
21 21
 
22 22
 	rt := daemon.configStore.GetRuntime(container.HostConfig.Runtime)
23 23
 	if rt == nil {
24
-		return nil, fmt.Errorf("no such runtime '%s'", container.HostConfig.Runtime)
24
+		return nil, validationError{errors.Errorf("no such runtime '%s'", container.HostConfig.Runtime)}
25 25
 	}
26 26
 	if UsingSystemd(daemon.configStore) {
27 27
 		rt.Args = append(rt.Args, "--systemd-cgroup=true")
... ...
@@ -113,10 +113,10 @@ func (s *Collector) Run() {
113 113
 
114 114
 type notRunningErr interface {
115 115
 	error
116
-	ContainerIsRunning() bool
116
+	Conflict()
117 117
 }
118 118
 
119 119
 type notFoundErr interface {
120 120
 	error
121
-	ContainerNotFound() bool
121
+	NotFound()
122 122
 }
... ...
@@ -3,10 +3,9 @@
3 3
 package daemon
4 4
 
5 5
 import (
6
-	"fmt"
7
-
8 6
 	"github.com/docker/docker/api/types"
9 7
 	"github.com/docker/docker/container"
8
+	"github.com/pkg/errors"
10 9
 )
11 10
 
12 11
 // Resolve Network SandboxID in case the container reuse another container's network stack
... ...
@@ -16,7 +15,7 @@ func (daemon *Daemon) getNetworkSandboxID(c *container.Container) (string, error
16 16
 		containerID := curr.HostConfig.NetworkMode.ConnectedContainer()
17 17
 		connected, err := daemon.GetContainer(containerID)
18 18
 		if err != nil {
19
-			return "", fmt.Errorf("Could not get container for %s", containerID)
19
+			return "", errors.Wrapf(err, "Could not get container for %s", containerID)
20 20
 		}
21 21
 		curr = connected
22 22
 	}
... ...
@@ -2,12 +2,10 @@ package daemon
2 2
 
3 3
 import (
4 4
 	"context"
5
-	"fmt"
6
-	"net/http"
7 5
 	"time"
8 6
 
9
-	"github.com/docker/docker/api/errors"
10 7
 	containerpkg "github.com/docker/docker/container"
8
+	"github.com/pkg/errors"
11 9
 	"github.com/sirupsen/logrus"
12 10
 )
13 11
 
... ...
@@ -23,15 +21,14 @@ func (daemon *Daemon) ContainerStop(name string, seconds *int) error {
23 23
 		return err
24 24
 	}
25 25
 	if !container.IsRunning() {
26
-		err := fmt.Errorf("Container %s is already stopped", name)
27
-		return errors.NewErrorWithStatusCode(err, http.StatusNotModified)
26
+		return containerNotModifiedError{running: false}
28 27
 	}
29 28
 	if seconds == nil {
30 29
 		stopTimeout := container.StopTimeout()
31 30
 		seconds = &stopTimeout
32 31
 	}
33 32
 	if err := daemon.containerStop(container, *seconds); err != nil {
34
-		return fmt.Errorf("Cannot stop container %s: %v", name, err)
33
+		return errors.Wrapf(systemError{err}, "cannot stop container: %s", name)
35 34
 	}
36 35
 	return nil
37 36
 }
... ...
@@ -130,7 +130,7 @@ func (daemon *Daemon) ContainerTop(name string, psArgs string) (*container.Conta
130 130
 	}
131 131
 
132 132
 	if !container.IsRunning() {
133
-		return nil, errNotRunning{container.ID}
133
+		return nil, errNotRunning(container.ID)
134 134
 	}
135 135
 
136 136
 	if container.IsRestarting() {
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"fmt"
5 5
 
6 6
 	"github.com/docker/docker/api/types/container"
7
+	"github.com/pkg/errors"
7 8
 )
8 9
 
9 10
 // ContainerUpdate updates configuration of the container
... ...
@@ -12,7 +13,7 @@ func (daemon *Daemon) ContainerUpdate(name string, hostConfig *container.HostCon
12 12
 
13 13
 	warnings, err := daemon.verifyContainerSettings(hostConfig, nil, true)
14 14
 	if err != nil {
15
-		return container.ContainerUpdateOKBody{Warnings: warnings}, err
15
+		return container.ContainerUpdateOKBody{Warnings: warnings}, validationError{err}
16 16
 	}
17 17
 
18 18
 	if err := daemon.update(name, hostConfig); err != nil {
... ...
@@ -72,7 +73,8 @@ func (daemon *Daemon) update(name string, hostConfig *container.HostConfig) erro
72 72
 	if container.IsRunning() && !container.IsRestarting() {
73 73
 		if err := daemon.containerd.UpdateResources(container.ID, toContainerdResources(hostConfig.Resources)); err != nil {
74 74
 			restoreConfig = true
75
-			return errCannotUpdate(container.ID, err)
75
+			// TODO: it would be nice if containerd responded with better errors here so we can classify this better.
76
+			return errCannotUpdate(container.ID, systemError{err})
76 77
 		}
77 78
 	}
78 79
 
... ...
@@ -82,5 +84,5 @@ func (daemon *Daemon) update(name string, hostConfig *container.HostConfig) erro
82 82
 }
83 83
 
84 84
 func errCannotUpdate(containerID string, err error) error {
85
-	return fmt.Errorf("Cannot update container %s: %v", containerID, err)
85
+	return errors.Wrap(err, "Cannot update container "+containerID)
86 86
 }
... ...
@@ -1,7 +1,6 @@
1 1
 package daemon
2 2
 
3 3
 import (
4
-	"errors"
5 4
 	"fmt"
6 5
 	"os"
7 6
 	"path/filepath"
... ...
@@ -9,13 +8,13 @@ import (
9 9
 	"strings"
10 10
 	"time"
11 11
 
12
-	dockererrors "github.com/docker/docker/api/errors"
13 12
 	"github.com/docker/docker/api/types"
14 13
 	containertypes "github.com/docker/docker/api/types/container"
15 14
 	mounttypes "github.com/docker/docker/api/types/mount"
16 15
 	"github.com/docker/docker/container"
17 16
 	"github.com/docker/docker/volume"
18 17
 	"github.com/docker/docker/volume/drivers"
18
+	"github.com/pkg/errors"
19 19
 	"github.com/sirupsen/logrus"
20 20
 )
21 21
 
... ...
@@ -149,7 +148,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
149 149
 		// #10618
150 150
 		_, tmpfsExists := hostConfig.Tmpfs[bind.Destination]
151 151
 		if binds[bind.Destination] || tmpfsExists {
152
-			return fmt.Errorf("Duplicate mount point '%s'", bind.Destination)
152
+			return duplicateMountPointError(bind.Destination)
153 153
 		}
154 154
 
155 155
 		if bind.Type == mounttypes.TypeVolume {
... ...
@@ -175,11 +174,11 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
175 175
 	for _, cfg := range hostConfig.Mounts {
176 176
 		mp, err := volume.ParseMountSpec(cfg)
177 177
 		if err != nil {
178
-			return dockererrors.NewBadRequestError(err)
178
+			return validationError{err}
179 179
 		}
180 180
 
181 181
 		if binds[mp.Destination] {
182
-			return fmt.Errorf("Duplicate mount point '%s'", cfg.Target)
182
+			return duplicateMountPointError(cfg.Target)
183 183
 		}
184 184
 
185 185
 		if mp.Type == mounttypes.TypeVolume {
... ...
@@ -1,6 +1,7 @@
1 1
 package distribution
2 2
 
3 3
 import (
4
+	"fmt"
4 5
 	"net/url"
5 6
 	"strings"
6 7
 	"syscall"
... ...
@@ -12,7 +13,6 @@ import (
12 12
 	"github.com/docker/distribution/registry/client"
13 13
 	"github.com/docker/distribution/registry/client/auth"
14 14
 	"github.com/docker/docker/distribution/xfer"
15
-	"github.com/pkg/errors"
16 15
 	"github.com/sirupsen/logrus"
17 16
 )
18 17
 
... ...
@@ -60,6 +60,45 @@ func shouldV2Fallback(err errcode.Error) bool {
60 60
 	return false
61 61
 }
62 62
 
63
+type notFoundError struct {
64
+	cause errcode.Error
65
+	ref   reference.Named
66
+}
67
+
68
+func (e notFoundError) Error() string {
69
+	switch e.cause.Code {
70
+	case errcode.ErrorCodeDenied:
71
+		// ErrorCodeDenied is used when access to the repository was denied
72
+		return fmt.Sprintf("pull access denied for %s, repository does not exist or may require 'docker login'", reference.FamiliarName(e.ref))
73
+	case v2.ErrorCodeManifestUnknown:
74
+		return fmt.Sprintf("manifest for %s not found", reference.FamiliarString(e.ref))
75
+	case v2.ErrorCodeNameUnknown:
76
+		return fmt.Sprintf("repository %s not found", reference.FamiliarName(e.ref))
77
+	}
78
+	// Shouldn't get here, but this is better than returning an empty string
79
+	return e.cause.Message
80
+}
81
+
82
+func (e notFoundError) NotFound() {}
83
+
84
+func (e notFoundError) Cause() error {
85
+	return e.cause
86
+}
87
+
88
+type unknownError struct {
89
+	cause error
90
+}
91
+
92
+func (e unknownError) Error() string {
93
+	return e.cause.Error()
94
+}
95
+
96
+func (e unknownError) Cause() error {
97
+	return e.cause
98
+}
99
+
100
+func (e unknownError) Unknown() {}
101
+
63 102
 // TranslatePullError is used to convert an error from a registry pull
64 103
 // operation to an error representing the entire pull operation. Any error
65 104
 // information which is not used by the returned error gets output to
... ...
@@ -74,25 +113,15 @@ func TranslatePullError(err error, ref reference.Named) error {
74 74
 			return TranslatePullError(v[0], ref)
75 75
 		}
76 76
 	case errcode.Error:
77
-		var newErr error
78 77
 		switch v.Code {
79
-		case errcode.ErrorCodeDenied:
80
-			// ErrorCodeDenied is used when access to the repository was denied
81
-			newErr = errors.Errorf("pull access denied for %s, repository does not exist or may require 'docker login'", reference.FamiliarName(ref))
82
-		case v2.ErrorCodeManifestUnknown:
83
-			newErr = errors.Errorf("manifest for %s not found", reference.FamiliarString(ref))
84
-		case v2.ErrorCodeNameUnknown:
85
-			newErr = errors.Errorf("repository %s not found", reference.FamiliarName(ref))
86
-		}
87
-		if newErr != nil {
88
-			logrus.Infof("Translating %q to %q", err, newErr)
89
-			return newErr
78
+		case errcode.ErrorCodeDenied, v2.ErrorCodeManifestUnknown, v2.ErrorCodeNameUnknown:
79
+			return notFoundError{v, ref}
90 80
 		}
91 81
 	case xfer.DoNotRetry:
92 82
 		return TranslatePullError(v.Err, ref)
93 83
 	}
94 84
 
95
-	return err
85
+	return unknownError{err}
96 86
 }
97 87
 
98 88
 // continueOnError returns true if we should fallback to the next endpoint
... ...
@@ -157,3 +186,30 @@ func retryOnError(err error) error {
157 157
 	// add them to the switch above.
158 158
 	return err
159 159
 }
160
+
161
+type invalidManifestClassError struct {
162
+	mediaType string
163
+	class     string
164
+}
165
+
166
+func (e invalidManifestClassError) Error() string {
167
+	return fmt.Sprintf("Encountered remote %q(%s) when fetching", e.mediaType, e.class)
168
+}
169
+
170
+func (e invalidManifestClassError) InvalidParameter() {}
171
+
172
+type invalidManifestFormatError struct{}
173
+
174
+func (invalidManifestFormatError) Error() string {
175
+	return "unsupported manifest format"
176
+}
177
+
178
+func (invalidManifestFormatError) InvalidParameter() {}
179
+
180
+type reservedNameError string
181
+
182
+func (e reservedNameError) Error() string {
183
+	return "'" + string(e) + "' is a reserved name"
184
+}
185
+
186
+func (e reservedNameError) Forbidden() {}
... ...
@@ -10,6 +10,7 @@ import (
10 10
 	refstore "github.com/docker/docker/reference"
11 11
 	"github.com/docker/docker/registry"
12 12
 	"github.com/opencontainers/go-digest"
13
+	"github.com/pkg/errors"
13 14
 	"github.com/sirupsen/logrus"
14 15
 	"golang.org/x/net/context"
15 16
 )
... ...
@@ -173,7 +174,7 @@ func writeStatus(requestedTag string, out progress.Output, layersDownloaded bool
173 173
 // ValidateRepoName validates the name of a repository.
174 174
 func ValidateRepoName(name reference.Named) error {
175 175
 	if reference.FamiliarName(name) == api.NoBaseImageSpecifier {
176
-		return fmt.Errorf("'%s' is a reserved name", api.NoBaseImageSpecifier)
176
+		return errors.WithStack(reservedNameError(api.NoBaseImageSpecifier))
177 177
 	}
178 178
 	return nil
179 179
 }
... ...
@@ -52,11 +52,7 @@ func (p *v1Puller) Pull(ctx context.Context, ref reference.Named) error {
52 52
 		registry.DockerHeaders(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders)...,
53 53
 	)
54 54
 	client := registry.HTTPClient(tr)
55
-	v1Endpoint, err := p.endpoint.ToV1Endpoint(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders)
56
-	if err != nil {
57
-		logrus.Debugf("Could not get v1 endpoint: %v", err)
58
-		return fallbackError{err: err}
59
-	}
55
+	v1Endpoint := p.endpoint.ToV1Endpoint(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders)
60 56
 	p.session, err = registry.NewSession(client, p.config.AuthConfig, v1Endpoint)
61 57
 	if err != nil {
62 58
 		// TODO(dmcgowan): Check if should fallback
... ...
@@ -2,7 +2,6 @@ package distribution
2 2
 
3 3
 import (
4 4
 	"encoding/json"
5
-	"errors"
6 5
 	"fmt"
7 6
 	"io"
8 7
 	"io/ioutil"
... ...
@@ -30,6 +29,7 @@ import (
30 30
 	refstore "github.com/docker/docker/reference"
31 31
 	"github.com/docker/docker/registry"
32 32
 	"github.com/opencontainers/go-digest"
33
+	"github.com/pkg/errors"
33 34
 	"github.com/sirupsen/logrus"
34 35
 	"golang.org/x/net/context"
35 36
 )
... ...
@@ -368,7 +368,7 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat
368 368
 			if configClass == "" {
369 369
 				configClass = "unknown"
370 370
 			}
371
-			return false, fmt.Errorf("Encountered remote %q(%s) when fetching", m.Manifest.Config.MediaType, configClass)
371
+			return false, invalidManifestClassError{m.Manifest.Config.MediaType, configClass}
372 372
 		}
373 373
 	}
374 374
 
... ...
@@ -404,7 +404,7 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat
404 404
 			return false, err
405 405
 		}
406 406
 	default:
407
-		return false, errors.New("unsupported manifest format")
407
+		return false, invalidManifestFormatError{}
408 408
 	}
409 409
 
410 410
 	progress.Message(p.config.ProgressOutput, "", "Digest: "+manifestDigest.String())
... ...
@@ -41,11 +41,7 @@ func (p *v1Pusher) Push(ctx context.Context) error {
41 41
 		registry.DockerHeaders(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders)...,
42 42
 	)
43 43
 	client := registry.HTTPClient(tr)
44
-	v1Endpoint, err := p.endpoint.ToV1Endpoint(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders)
45
-	if err != nil {
46
-		logrus.Debugf("Could not get v1 endpoint: %v", err)
47
-		return fallbackError{err: err}
48
-	}
44
+	v1Endpoint := p.endpoint.ToV1Endpoint(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders)
49 45
 	p.session, err = registry.NewSession(client, p.config.AuthConfig, v1Endpoint)
50 46
 	if err != nil {
51 47
 		// TODO(dmcgowan): Check if should fallback
... ...
@@ -180,13 +180,21 @@ func (is *store) Create(config []byte) (ID, error) {
180 180
 	return imageID, nil
181 181
 }
182 182
 
183
+type imageNotFoundError string
184
+
185
+func (e imageNotFoundError) Error() string {
186
+	return "No such image: " + string(e)
187
+}
188
+
189
+func (imageNotFoundError) NotFound() {}
190
+
183 191
 func (is *store) Search(term string) (ID, error) {
184 192
 	dgst, err := is.digestSet.Lookup(term)
185 193
 	if err != nil {
186 194
 		if err == digestset.ErrDigestNotFound {
187
-			err = fmt.Errorf("No such image: %s", term)
195
+			err = imageNotFoundError(term)
188 196
 		}
189
-		return "", err
197
+		return "", errors.WithStack(err)
190 198
 	}
191 199
 	return IDFromDigest(dgst), nil
192 200
 }
... ...
@@ -508,7 +508,7 @@ func (s *DockerSuite) TestContainerAPIBadPort(c *check.C) {
508 508
 
509 509
 	status, body, err := request.SockRequest("POST", "/containers/create", config, daemonHost())
510 510
 	c.Assert(err, checker.IsNil)
511
-	c.Assert(status, checker.Equals, http.StatusInternalServerError)
511
+	c.Assert(status, checker.Equals, http.StatusBadRequest)
512 512
 	c.Assert(getErrorMessage(c, body), checker.Equals, `invalid port specification: "aa80"`, check.Commentf("Incorrect error msg: %s", body))
513 513
 }
514 514
 
... ...
@@ -537,7 +537,7 @@ func (s *DockerSuite) TestContainerAPICreateEmptyConfig(c *check.C) {
537 537
 
538 538
 	status, body, err := request.SockRequest("POST", "/containers/create", config, daemonHost())
539 539
 	c.Assert(err, checker.IsNil)
540
-	c.Assert(status, checker.Equals, http.StatusInternalServerError)
540
+	c.Assert(status, checker.Equals, http.StatusBadRequest)
541 541
 
542 542
 	expected := "Config cannot be empty in order to create a container"
543 543
 	c.Assert(getErrorMessage(c, body), checker.Equals, expected)
... ...
@@ -673,13 +673,13 @@ func (s *DockerSuite) TestContainerAPIVerifyHeader(c *check.C) {
673 673
 	// Try with no content-type
674 674
 	res, body, err := create("")
675 675
 	c.Assert(err, checker.IsNil)
676
-	c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError)
676
+	c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest)
677 677
 	body.Close()
678 678
 
679 679
 	// Try with wrong content-type
680 680
 	res, body, err = create("application/xml")
681 681
 	c.Assert(err, checker.IsNil)
682
-	c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError)
682
+	c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest)
683 683
 	body.Close()
684 684
 
685 685
 	// now application/json
... ...
@@ -705,7 +705,7 @@ func (s *DockerSuite) TestContainerAPIInvalidPortSyntax(c *check.C) {
705 705
 
706 706
 	res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON)
707 707
 	c.Assert(err, checker.IsNil)
708
-	c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError)
708
+	c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest)
709 709
 
710 710
 	b, err := testutil.ReadBody(body)
711 711
 	c.Assert(err, checker.IsNil)
... ...
@@ -725,7 +725,7 @@ func (s *DockerSuite) TestContainerAPIRestartPolicyInvalidPolicyName(c *check.C)
725 725
 
726 726
 	res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON)
727 727
 	c.Assert(err, checker.IsNil)
728
-	c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError)
728
+	c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest)
729 729
 
730 730
 	b, err := testutil.ReadBody(body)
731 731
 	c.Assert(err, checker.IsNil)
... ...
@@ -745,7 +745,7 @@ func (s *DockerSuite) TestContainerAPIRestartPolicyRetryMismatch(c *check.C) {
745 745
 
746 746
 	res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON)
747 747
 	c.Assert(err, checker.IsNil)
748
-	c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError)
748
+	c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest)
749 749
 
750 750
 	b, err := testutil.ReadBody(body)
751 751
 	c.Assert(err, checker.IsNil)
... ...
@@ -765,7 +765,7 @@ func (s *DockerSuite) TestContainerAPIRestartPolicyNegativeRetryCount(c *check.C
765 765
 
766 766
 	res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON)
767 767
 	c.Assert(err, checker.IsNil)
768
-	c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError)
768
+	c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest)
769 769
 
770 770
 	b, err := testutil.ReadBody(body)
771 771
 	c.Assert(err, checker.IsNil)
... ...
@@ -850,7 +850,7 @@ func (s *DockerSuite) TestCreateWithTooLowMemoryLimit(c *check.C) {
850 850
 	b, err2 := testutil.ReadBody(body)
851 851
 	c.Assert(err2, checker.IsNil)
852 852
 
853
-	c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError)
853
+	c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest)
854 854
 	c.Assert(string(b), checker.Contains, "Minimum memory limit allowed is 4MB")
855 855
 }
856 856
 
... ...
@@ -1005,7 +1005,7 @@ func (s *DockerSuite) TestContainerAPICopyPre124(c *check.C) {
1005 1005
 	c.Assert(found, checker.True)
1006 1006
 }
1007 1007
 
1008
-func (s *DockerSuite) TestContainerAPICopyResourcePathEmptyPr124(c *check.C) {
1008
+func (s *DockerSuite) TestContainerAPICopyResourcePathEmptyPre124(c *check.C) {
1009 1009
 	testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
1010 1010
 	name := "test-container-api-copy-resource-empty"
1011 1011
 	dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt")
... ...
@@ -1016,7 +1016,7 @@ func (s *DockerSuite) TestContainerAPICopyResourcePathEmptyPr124(c *check.C) {
1016 1016
 
1017 1017
 	status, body, err := request.SockRequest("POST", "/v1.23/containers/"+name+"/copy", postData, daemonHost())
1018 1018
 	c.Assert(err, checker.IsNil)
1019
-	c.Assert(status, checker.Equals, http.StatusInternalServerError)
1019
+	c.Assert(status, checker.Equals, http.StatusBadRequest)
1020 1020
 	c.Assert(string(body), checker.Matches, "Path cannot be empty\n")
1021 1021
 }
1022 1022
 
... ...
@@ -1031,7 +1031,7 @@ func (s *DockerSuite) TestContainerAPICopyResourcePathNotFoundPre124(c *check.C)
1031 1031
 
1032 1032
 	status, body, err := request.SockRequest("POST", "/v1.23/containers/"+name+"/copy", postData, daemonHost())
1033 1033
 	c.Assert(err, checker.IsNil)
1034
-	c.Assert(status, checker.Equals, http.StatusInternalServerError)
1034
+	c.Assert(status, checker.Equals, http.StatusNotFound)
1035 1035
 	c.Assert(string(body), checker.Matches, "Could not find the file /notexist in container "+name+"\n")
1036 1036
 }
1037 1037
 
... ...
@@ -1301,7 +1301,7 @@ func (s *DockerSuite) TestPostContainersCreateWithWrongCpusetValues(c *check.C)
1301 1301
 	name := "wrong-cpuset-cpus"
1302 1302
 	status, body, err := request.SockRequest("POST", "/containers/create?name="+name, c1, daemonHost())
1303 1303
 	c.Assert(err, checker.IsNil)
1304
-	c.Assert(status, checker.Equals, http.StatusInternalServerError)
1304
+	c.Assert(status, checker.Equals, http.StatusBadRequest)
1305 1305
 	expected := "Invalid value 1-42,, for cpuset cpus"
1306 1306
 	c.Assert(getErrorMessage(c, body), checker.Equals, expected)
1307 1307
 
... ...
@@ -1312,7 +1312,7 @@ func (s *DockerSuite) TestPostContainersCreateWithWrongCpusetValues(c *check.C)
1312 1312
 	name = "wrong-cpuset-mems"
1313 1313
 	status, body, err = request.SockRequest("POST", "/containers/create?name="+name, c2, daemonHost())
1314 1314
 	c.Assert(err, checker.IsNil)
1315
-	c.Assert(status, checker.Equals, http.StatusInternalServerError)
1315
+	c.Assert(status, checker.Equals, http.StatusBadRequest)
1316 1316
 	expected = "Invalid value 42-3,1-- for cpuset mems"
1317 1317
 	c.Assert(getErrorMessage(c, body), checker.Equals, expected)
1318 1318
 }
... ...
@@ -1327,7 +1327,7 @@ func (s *DockerSuite) TestPostContainersCreateShmSizeNegative(c *check.C) {
1327 1327
 
1328 1328
 	status, body, err := request.SockRequest("POST", "/containers/create", config, daemonHost())
1329 1329
 	c.Assert(err, check.IsNil)
1330
-	c.Assert(status, check.Equals, http.StatusInternalServerError)
1330
+	c.Assert(status, check.Equals, http.StatusBadRequest)
1331 1331
 	c.Assert(getErrorMessage(c, body), checker.Contains, "SHM size can not be less than 0")
1332 1332
 }
1333 1333
 
... ...
@@ -1463,7 +1463,7 @@ func (s *DockerSuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *che
1463 1463
 	name := "oomscoreadj-over"
1464 1464
 	status, b, err := request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
1465 1465
 	c.Assert(err, check.IsNil)
1466
-	c.Assert(status, check.Equals, http.StatusInternalServerError)
1466
+	c.Assert(status, check.Equals, http.StatusBadRequest)
1467 1467
 
1468 1468
 	expected := "Invalid value 1001, range for oom score adj is [-1000, 1000]"
1469 1469
 	msg := getErrorMessage(c, b)
... ...
@@ -1478,7 +1478,7 @@ func (s *DockerSuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *che
1478 1478
 	name = "oomscoreadj-low"
1479 1479
 	status, b, err = request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
1480 1480
 	c.Assert(err, check.IsNil)
1481
-	c.Assert(status, check.Equals, http.StatusInternalServerError)
1481
+	c.Assert(status, check.Equals, http.StatusBadRequest)
1482 1482
 	expected = "Invalid value -1001, range for oom score adj is [-1000, 1000]"
1483 1483
 	msg = getErrorMessage(c, b)
1484 1484
 	if !strings.Contains(msg, expected) {
... ...
@@ -1488,10 +1488,9 @@ func (s *DockerSuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *che
1488 1488
 
1489 1489
 // test case for #22210 where an empty container name caused panic.
1490 1490
 func (s *DockerSuite) TestContainerAPIDeleteWithEmptyName(c *check.C) {
1491
-	status, out, err := request.SockRequest("DELETE", "/containers/", nil, daemonHost())
1491
+	status, _, err := request.SockRequest("DELETE", "/containers/", nil, daemonHost())
1492 1492
 	c.Assert(err, checker.IsNil)
1493 1493
 	c.Assert(status, checker.Equals, http.StatusBadRequest)
1494
-	c.Assert(string(out), checker.Contains, "No container name or ID supplied")
1495 1494
 }
1496 1495
 
1497 1496
 func (s *DockerSuite) TestContainerAPIStatsWithNetworkDisabled(c *check.C) {
... ...
@@ -25,7 +25,7 @@ func (s *DockerSuite) TestAPICreateWithInvalidHealthcheckParams(c *check.C) {
25 25
 
26 26
 	status, body, err := request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
27 27
 	c.Assert(err, check.IsNil)
28
-	c.Assert(status, check.Equals, http.StatusInternalServerError)
28
+	c.Assert(status, check.Equals, http.StatusBadRequest)
29 29
 	expected := fmt.Sprintf("Interval in Healthcheck cannot be less than %s", container.MinimumDuration)
30 30
 	c.Assert(getErrorMessage(c, body), checker.Contains, expected)
31 31
 
... ...
@@ -41,7 +41,7 @@ func (s *DockerSuite) TestAPICreateWithInvalidHealthcheckParams(c *check.C) {
41 41
 	}
42 42
 	status, body, err = request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
43 43
 	c.Assert(err, check.IsNil)
44
-	c.Assert(status, check.Equals, http.StatusInternalServerError)
44
+	c.Assert(status, check.Equals, http.StatusBadRequest)
45 45
 	c.Assert(getErrorMessage(c, body), checker.Contains, expected)
46 46
 
47 47
 	// test invalid Timeout in Healthcheck: less than 1ms
... ...
@@ -56,7 +56,7 @@ func (s *DockerSuite) TestAPICreateWithInvalidHealthcheckParams(c *check.C) {
56 56
 	}
57 57
 	status, body, err = request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
58 58
 	c.Assert(err, check.IsNil)
59
-	c.Assert(status, check.Equals, http.StatusInternalServerError)
59
+	c.Assert(status, check.Equals, http.StatusBadRequest)
60 60
 	expected = fmt.Sprintf("Timeout in Healthcheck cannot be less than %s", container.MinimumDuration)
61 61
 	c.Assert(getErrorMessage(c, body), checker.Contains, expected)
62 62
 
... ...
@@ -72,7 +72,7 @@ func (s *DockerSuite) TestAPICreateWithInvalidHealthcheckParams(c *check.C) {
72 72
 	}
73 73
 	status, body, err = request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
74 74
 	c.Assert(err, check.IsNil)
75
-	c.Assert(status, check.Equals, http.StatusInternalServerError)
75
+	c.Assert(status, check.Equals, http.StatusBadRequest)
76 76
 	expected = "Retries in Healthcheck cannot be negative"
77 77
 	c.Assert(getErrorMessage(c, body), checker.Contains, expected)
78 78
 
... ...
@@ -89,7 +89,7 @@ func (s *DockerSuite) TestAPICreateWithInvalidHealthcheckParams(c *check.C) {
89 89
 	}
90 90
 	status, body, err = request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
91 91
 	c.Assert(err, check.IsNil)
92
-	c.Assert(status, check.Equals, http.StatusInternalServerError)
92
+	c.Assert(status, check.Equals, http.StatusBadRequest)
93 93
 	expected = fmt.Sprintf("StartPeriod in Healthcheck cannot be less than %s", container.MinimumDuration)
94 94
 	c.Assert(getErrorMessage(c, body), checker.Contains, expected)
95 95
 }
... ...
@@ -22,7 +22,7 @@ func (s *DockerSuite) TestExecResizeAPIHeightWidthNoInt(c *check.C) {
22 22
 	endpoint := "/exec/" + cleanedContainerID + "/resize?h=foo&w=bar"
23 23
 	status, _, err := request.SockRequest("POST", endpoint, nil, daemonHost())
24 24
 	c.Assert(err, checker.IsNil)
25
-	c.Assert(status, checker.Equals, http.StatusInternalServerError)
25
+	c.Assert(status, checker.Equals, http.StatusBadRequest)
26 26
 }
27 27
 
28 28
 // Part of #14845
... ...
@@ -23,7 +23,7 @@ func (s *DockerSuite) TestExecAPICreateNoCmd(c *check.C) {
23 23
 
24 24
 	status, body, err := request.SockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": nil}, daemonHost())
25 25
 	c.Assert(err, checker.IsNil)
26
-	c.Assert(status, checker.Equals, http.StatusInternalServerError)
26
+	c.Assert(status, checker.Equals, http.StatusBadRequest)
27 27
 
28 28
 	comment := check.Commentf("Expected message when creating exec command with no Cmd specified")
29 29
 	c.Assert(getErrorMessage(c, body), checker.Contains, "No exec command specified", comment)
... ...
@@ -40,7 +40,7 @@ func (s *DockerSuite) TestExecAPICreateNoValidContentType(c *check.C) {
40 40
 
41 41
 	res, body, err := request.Post(fmt.Sprintf("/containers/%s/exec", name), request.RawContent(ioutil.NopCloser(jsonData)), request.ContentType("test/plain"))
42 42
 	c.Assert(err, checker.IsNil)
43
-	c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError)
43
+	c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest)
44 44
 
45 45
 	b, err := testutil.ReadBody(body)
46 46
 	c.Assert(err, checker.IsNil)
... ...
@@ -177,7 +177,7 @@ func (s *DockerSuite) TestExecAPIStartInvalidCommand(c *check.C) {
177 177
 	dockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
178 178
 
179 179
 	id := createExecCmd(c, name, "invalid")
180
-	startExec(c, id, http.StatusNotFound)
180
+	startExec(c, id, http.StatusBadRequest)
181 181
 	waitForExec(c, id)
182 182
 
183 183
 	var inspectJSON struct{ ExecIDs []string }
... ...
@@ -25,7 +25,7 @@ func (s *DockerSuite) TestResizeAPIHeightWidthNoInt(c *check.C) {
25 25
 
26 26
 	endpoint := "/containers/" + cleanedContainerID + "/resize?h=foo&w=bar"
27 27
 	status, _, err := request.SockRequest("POST", endpoint, nil, daemonHost())
28
-	c.Assert(status, check.Equals, http.StatusInternalServerError)
28
+	c.Assert(status, check.Equals, http.StatusBadRequest)
29 29
 	c.Assert(err, check.IsNil)
30 30
 }
31 31
 
... ...
@@ -38,7 +38,7 @@ func (s *DockerSuite) TestResizeAPIResponseWhenContainerNotStarted(c *check.C) {
38 38
 
39 39
 	endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40"
40 40
 	status, body, err := request.SockRequest("POST", endpoint, nil, daemonHost())
41
-	c.Assert(status, check.Equals, http.StatusInternalServerError)
41
+	c.Assert(status, check.Equals, http.StatusConflict)
42 42
 	c.Assert(err, check.IsNil)
43 43
 
44 44
 	c.Assert(getErrorMessage(c, body), checker.Contains, "is not running", check.Commentf("resize should fail with message 'Container is not running'"))
... ...
@@ -80,7 +80,7 @@ func (s *DockerSuite) TestAPIDockerAPIVersion(c *check.C) {
80 80
 func (s *DockerSuite) TestAPIErrorJSON(c *check.C) {
81 81
 	httpResp, body, err := request.Post("/containers/create", request.JSONBody(struct{}{}))
82 82
 	c.Assert(err, checker.IsNil)
83
-	c.Assert(httpResp.StatusCode, checker.Equals, http.StatusInternalServerError)
83
+	c.Assert(httpResp.StatusCode, checker.Equals, http.StatusBadRequest)
84 84
 	c.Assert(httpResp.Header.Get("Content-Type"), checker.Equals, "application/json")
85 85
 	b, err := testutil.ReadBody(body)
86 86
 	c.Assert(err, checker.IsNil)
... ...
@@ -93,7 +93,7 @@ func (s *DockerSuite) TestAPIErrorPlainText(c *check.C) {
93 93
 	testRequires(c, DaemonIsLinux)
94 94
 	httpResp, body, err := request.Post("/v1.23/containers/create", request.JSONBody(struct{}{}))
95 95
 	c.Assert(err, checker.IsNil)
96
-	c.Assert(httpResp.StatusCode, checker.Equals, http.StatusInternalServerError)
96
+	c.Assert(httpResp.StatusCode, checker.Equals, http.StatusBadRequest)
97 97
 	c.Assert(httpResp.Header.Get("Content-Type"), checker.Contains, "text/plain")
98 98
 	b, err := testutil.ReadBody(body)
99 99
 	c.Assert(err, checker.IsNil)
... ...
@@ -64,19 +64,17 @@ func (s *DockerSuite) TestCpFromErrDstParentNotExists(c *check.C) {
64 64
 	// Try with a file source.
65 65
 	srcPath := containerCpPath(containerID, "/file1")
66 66
 	dstPath := cpPath(tmpDir, "notExists", "file1")
67
+	_, dstStatErr := os.Lstat(filepath.Dir(dstPath))
68
+	c.Assert(os.IsNotExist(dstStatErr), checker.True)
67 69
 
68 70
 	err := runDockerCp(c, srcPath, dstPath, nil)
69
-	c.Assert(err, checker.NotNil)
70
-
71
-	c.Assert(isCpNotExist(err), checker.True, check.Commentf("expected IsNotExist error, but got %T: %s", err, err))
71
+	c.Assert(err.Error(), checker.Contains, dstStatErr.Error())
72 72
 
73 73
 	// Try with a directory source.
74 74
 	srcPath = containerCpPath(containerID, "/dir1")
75 75
 
76 76
 	err = runDockerCp(c, srcPath, dstPath, nil)
77
-	c.Assert(err, checker.NotNil)
78
-
79
-	c.Assert(isCpNotExist(err), checker.True, check.Commentf("expected IsNotExist error, but got %T: %s", err, err))
77
+	c.Assert(err.Error(), checker.Contains, dstStatErr.Error())
80 78
 }
81 79
 
82 80
 // Test for error when DST ends in a trailing
... ...
@@ -2,6 +2,7 @@ package main
2 2
 
3 3
 import (
4 4
 	"os"
5
+	"strings"
5 6
 
6 7
 	"github.com/docker/docker/integration-cli/checker"
7 8
 	"github.com/go-check/check"
... ...
@@ -30,11 +31,11 @@ func (s *DockerSuite) TestCpToErrSrcNotExists(c *check.C) {
30 30
 
31 31
 	srcPath := cpPath(tmpDir, "file1")
32 32
 	dstPath := containerCpPath(containerID, "file1")
33
+	_, srcStatErr := os.Stat(srcPath)
34
+	c.Assert(os.IsNotExist(srcStatErr), checker.True)
33 35
 
34 36
 	err := runDockerCp(c, srcPath, dstPath, nil)
35
-	c.Assert(err, checker.NotNil)
36
-
37
-	c.Assert(isCpNotExist(err), checker.True, check.Commentf("expected IsNotExist error, but got %T: %s", err, err))
37
+	c.Assert(strings.ToLower(err.Error()), checker.Contains, strings.ToLower(srcStatErr.Error()))
38 38
 }
39 39
 
40 40
 // Test for error when SRC ends in a trailing
... ...
@@ -231,7 +231,7 @@ func getTestDir(c *check.C, label string) (tmpDir string) {
231 231
 }
232 232
 
233 233
 func isCpNotExist(err error) bool {
234
-	return strings.Contains(err.Error(), "no such file or directory") || strings.Contains(err.Error(), "cannot find the file specified")
234
+	return strings.Contains(strings.ToLower(err.Error()), "could not find the file")
235 235
 }
236 236
 
237 237
 func isCpDirNotExist(err error) bool {
... ...
@@ -463,6 +463,5 @@ func (s *DockerSuite) TestInspectInvalidReference(c *check.C) {
463 463
 	// This test should work on both Windows and Linux
464 464
 	out, _, err := dockerCmdWithError("inspect", "FooBar")
465 465
 	c.Assert(err, checker.NotNil)
466
-	c.Assert(out, checker.Contains, "Error: No such object: FooBar")
467
-	c.Assert(err.Error(), checker.Contains, "Error: No such object: FooBar")
466
+	c.Assert(out, checker.Contains, "no such image: FooBar")
468 467
 }
... ...
@@ -28,7 +28,7 @@ func (s *DockerSuite) TestLinksInvalidContainerTarget(c *check.C) {
28 28
 	// an invalid container target should produce an error
29 29
 	c.Assert(err, checker.NotNil, check.Commentf("out: %s", out))
30 30
 	// an invalid container target should produce an error
31
-	c.Assert(out, checker.Contains, "Could not get container")
31
+	c.Assert(out, checker.Contains, "could not get container")
32 32
 }
33 33
 
34 34
 func (s *DockerSuite) TestLinksPingLinkedContainers(c *check.C) {
... ...
@@ -1,6 +1,8 @@
1 1
 package main
2 2
 
3 3
 import (
4
+	"strings"
5
+
4 6
 	"github.com/docker/docker/integration-cli/checker"
5 7
 	"github.com/docker/docker/runconfig"
6 8
 	"github.com/go-check/check"
... ...
@@ -47,7 +49,7 @@ func (s *DockerSuite) TestNetHostname(c *check.C) {
47 47
 	c.Assert(out, checker.Contains, "Invalid network mode: invalid container format container:<name|id>")
48 48
 
49 49
 	out, _ = dockerCmdWithFail(c, "run", "--net=weird", "busybox", "ps")
50
-	c.Assert(out, checker.Contains, "network weird not found")
50
+	c.Assert(strings.ToLower(out), checker.Contains, "no such network")
51 51
 }
52 52
 
53 53
 func (s *DockerSuite) TestConflictContainerNetworkAndLinks(c *check.C) {
... ...
@@ -208,7 +208,7 @@ func (s *DockerSuite) TestPsListContainersFilterStatus(c *check.C) {
208 208
 	result := cli.Docker(cli.Args("ps", "-a", "-q", "--filter=status=rubbish"), cli.WithTimeout(time.Second*60))
209 209
 	c.Assert(result, icmd.Matches, icmd.Expected{
210 210
 		ExitCode: 1,
211
-		Err:      "Unrecognised filter value for status",
211
+		Err:      "Invalid filter 'status=rubbish'",
212 212
 	})
213 213
 
214 214
 	// Windows doesn't support pausing of containers
... ...
@@ -1887,7 +1887,7 @@ func (s *DockerSwarmSuite) TestNetworkInspectWithDuplicateNames(c *check.C) {
1887 1887
 	// Name with duplicates
1888 1888
 	out, err = d.Cmd("network", "inspect", "--format", "{{.ID}}", name)
1889 1889
 	c.Assert(err, checker.NotNil, check.Commentf(out))
1890
-	c.Assert(out, checker.Contains, "network foo is ambiguous (2 matches found based on name)")
1890
+	c.Assert(out, checker.Contains, "2 matches found based on name")
1891 1891
 
1892 1892
 	out, err = d.Cmd("network", "rm", n2.ID)
1893 1893
 	c.Assert(err, checker.IsNil, check.Commentf(out))
... ...
@@ -1912,7 +1912,7 @@ func (s *DockerSwarmSuite) TestNetworkInspectWithDuplicateNames(c *check.C) {
1912 1912
 	// Name with duplicates
1913 1913
 	out, err = d.Cmd("network", "inspect", "--format", "{{.ID}}", name)
1914 1914
 	c.Assert(err, checker.NotNil, check.Commentf(out))
1915
-	c.Assert(out, checker.Contains, "network foo is ambiguous (2 matches found based on name)")
1915
+	c.Assert(out, checker.Contains, "2 matches found based on name")
1916 1916
 }
1917 1917
 
1918 1918
 func (s *DockerSwarmSuite) TestSwarmStopSignal(c *check.C) {
... ...
@@ -23,10 +23,9 @@ func (s *DockerSuite) TestDeprecatedContainerAPIStartHostConfig(c *check.C) {
23 23
 	config := map[string]interface{}{
24 24
 		"Binds": []string{"/aa:/bb"},
25 25
 	}
26
-	status, body, err := request.SockRequest("POST", "/containers/"+name+"/start", config, daemonHost())
26
+	status, _, err := request.SockRequest("POST", "/containers/"+name+"/start", config, daemonHost())
27 27
 	c.Assert(err, checker.IsNil)
28 28
 	c.Assert(status, checker.Equals, http.StatusBadRequest)
29
-	c.Assert(string(body), checker.Contains, "was deprecated since v1.10")
30 29
 }
31 30
 
32 31
 func (s *DockerSuite) TestDeprecatedContainerAPIStartVolumeBinds(c *check.C) {
... ...
@@ -81,7 +80,7 @@ func (s *DockerSuite) TestDeprecatedContainerAPIStartDupVolumeBinds(c *check.C)
81 81
 	}
82 82
 	status, body, err := request.SockRequest("POST", formatV123StartAPIURL("/containers/"+name+"/start"), config, daemonHost())
83 83
 	c.Assert(err, checker.IsNil)
84
-	c.Assert(status, checker.Equals, http.StatusInternalServerError)
84
+	c.Assert(status, checker.Equals, http.StatusBadRequest)
85 85
 	c.Assert(string(body), checker.Contains, "Duplicate mount point", check.Commentf("Expected failure due to duplicate bind mounts to same path, instead got: %q with error: %v", string(body), err))
86 86
 }
87 87
 
... ...
@@ -154,7 +153,7 @@ func (s *DockerSuite) TestDeprecatedStartWithTooLowMemoryLimit(c *check.C) {
154 154
 	c.Assert(err, checker.IsNil)
155 155
 	b, err2 := testutil.ReadBody(body)
156 156
 	c.Assert(err2, checker.IsNil)
157
-	c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError)
157
+	c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest)
158 158
 	c.Assert(string(b), checker.Contains, "Minimum memory limit allowed is 4MB")
159 159
 }
160 160
 
... ...
@@ -5,6 +5,8 @@ import (
5 5
 	"os"
6 6
 	"runtime"
7 7
 	"strings"
8
+
9
+	"github.com/pkg/errors"
8 10
 )
9 11
 
10 12
 // ValidateEnv validates an environment variable and returns it.
... ...
@@ -18,7 +20,7 @@ import (
18 18
 func ValidateEnv(val string) (string, error) {
19 19
 	arr := strings.Split(val, "=")
20 20
 	if arr[0] == "" {
21
-		return "", fmt.Errorf("invalid environment variable: %s", val)
21
+		return "", errors.Errorf("invalid environment variable: %s", val)
22 22
 	}
23 23
 	if len(arr) > 1 {
24 24
 		return val, nil
... ...
@@ -176,10 +176,7 @@ type authorizationError struct {
176 176
 	error
177 177
 }
178 178
 
179
-// HTTPErrorStatusCode returns the authorization error status code (forbidden)
180
-func (e authorizationError) HTTPErrorStatusCode() int {
181
-	return http.StatusForbidden
182
-}
179
+func (authorizationError) Forbidden() {}
183 180
 
184 181
 func newAuthorizationError(plugin, msg string) authorizationError {
185 182
 	return authorizationError{error: fmt.Errorf("authorization denied by plugin %s: %s", plugin, msg)}
... ...
@@ -6,7 +6,6 @@ import (
6 6
 	"archive/tar"
7 7
 	"compress/gzip"
8 8
 	"encoding/json"
9
-	"fmt"
10 9
 	"io"
11 10
 	"io/ioutil"
12 11
 	"net/http"
... ...
@@ -55,7 +54,7 @@ func (pm *Manager) Disable(refOrID string, config *types.PluginDisableConfig) er
55 55
 	pm.mu.RUnlock()
56 56
 
57 57
 	if !config.ForceDisable && p.GetRefCount() > 0 {
58
-		return fmt.Errorf("plugin %s is in use", p.Name())
58
+		return errors.WithStack(inUseError(p.Name()))
59 59
 	}
60 60
 
61 61
 	for _, typ := range p.GetTypes() {
... ...
@@ -142,7 +141,7 @@ func (s *tempConfigStore) Put(c []byte) (digest.Digest, error) {
142 142
 
143 143
 func (s *tempConfigStore) Get(d digest.Digest) ([]byte, error) {
144 144
 	if d != s.configDigest {
145
-		return nil, fmt.Errorf("digest not found")
145
+		return nil, errNotFound("digest not found")
146 146
 	}
147 147
 	return s.config, nil
148 148
 }
... ...
@@ -151,7 +150,7 @@ func (s *tempConfigStore) RootFSAndPlatformFromConfig(c []byte) (*image.RootFS,
151 151
 	return configToRootFS(c)
152 152
 }
153 153
 
154
-func computePrivileges(c types.PluginConfig) (types.PluginPrivileges, error) {
154
+func computePrivileges(c types.PluginConfig) types.PluginPrivileges {
155 155
 	var privileges types.PluginPrivileges
156 156
 	if c.Network.Type != "null" && c.Network.Type != "bridge" && c.Network.Type != "" {
157 157
 		privileges = append(privileges, types.PluginPrivilege{
... ...
@@ -207,7 +206,7 @@ func computePrivileges(c types.PluginConfig) (types.PluginPrivileges, error) {
207 207
 		})
208 208
 	}
209 209
 
210
-	return privileges, nil
210
+	return privileges
211 211
 }
212 212
 
213 213
 // Privileges pulls a plugin config and computes the privileges required to install it.
... ...
@@ -236,21 +235,21 @@ func (pm *Manager) Privileges(ctx context.Context, ref reference.Named, metaHead
236 236
 	}
237 237
 	var config types.PluginConfig
238 238
 	if err := json.Unmarshal(cs.config, &config); err != nil {
239
-		return nil, err
239
+		return nil, systemError{err}
240 240
 	}
241 241
 
242
-	return computePrivileges(config)
242
+	return computePrivileges(config), nil
243 243
 }
244 244
 
245 245
 // Upgrade upgrades a plugin
246 246
 func (pm *Manager) Upgrade(ctx context.Context, ref reference.Named, name string, metaHeader http.Header, authConfig *types.AuthConfig, privileges types.PluginPrivileges, outStream io.Writer) (err error) {
247 247
 	p, err := pm.config.Store.GetV2Plugin(name)
248 248
 	if err != nil {
249
-		return errors.Wrap(err, "plugin must be installed before upgrading")
249
+		return err
250 250
 	}
251 251
 
252 252
 	if p.IsEnabled() {
253
-		return fmt.Errorf("plugin must be disabled before upgrading")
253
+		return errors.Wrap(enabledError(p.Name()), "plugin must be disabled before upgrading")
254 254
 	}
255 255
 
256 256
 	pm.muGC.RLock()
... ...
@@ -258,12 +257,12 @@ func (pm *Manager) Upgrade(ctx context.Context, ref reference.Named, name string
258 258
 
259 259
 	// revalidate because Pull is public
260 260
 	if _, err := reference.ParseNormalizedNamed(name); err != nil {
261
-		return errors.Wrapf(err, "failed to parse %q", name)
261
+		return errors.Wrapf(validationError{err}, "failed to parse %q", name)
262 262
 	}
263 263
 
264 264
 	tmpRootFSDir, err := ioutil.TempDir(pm.tmpDir(), ".rootfs")
265 265
 	if err != nil {
266
-		return err
266
+		return errors.Wrap(systemError{err}, "error preparing upgrade")
267 267
 	}
268 268
 	defer os.RemoveAll(tmpRootFSDir)
269 269
 
... ...
@@ -305,17 +304,17 @@ func (pm *Manager) Pull(ctx context.Context, ref reference.Named, name string, m
305 305
 	// revalidate because Pull is public
306 306
 	nameref, err := reference.ParseNormalizedNamed(name)
307 307
 	if err != nil {
308
-		return errors.Wrapf(err, "failed to parse %q", name)
308
+		return errors.Wrapf(validationError{err}, "failed to parse %q", name)
309 309
 	}
310 310
 	name = reference.FamiliarString(reference.TagNameOnly(nameref))
311 311
 
312 312
 	if err := pm.config.Store.validateName(name); err != nil {
313
-		return err
313
+		return validationError{err}
314 314
 	}
315 315
 
316 316
 	tmpRootFSDir, err := ioutil.TempDir(pm.tmpDir(), ".rootfs")
317 317
 	if err != nil {
318
-		return err
318
+		return errors.Wrap(systemError{err}, "error preparing pull")
319 319
 	}
320 320
 	defer os.RemoveAll(tmpRootFSDir)
321 321
 
... ...
@@ -372,7 +371,7 @@ func (pm *Manager) List(pluginFilters filters.Args) ([]types.Plugin, error) {
372 372
 		} else if pluginFilters.ExactMatch("enabled", "false") {
373 373
 			disabledOnly = true
374 374
 		} else {
375
-			return nil, fmt.Errorf("Invalid filter 'enabled=%s'", pluginFilters.Get("enabled"))
375
+			return nil, invalidFilter{"enabled", pluginFilters.Get("enabled")}
376 376
 		}
377 377
 	}
378 378
 
... ...
@@ -615,10 +614,10 @@ func (pm *Manager) Remove(name string, config *types.PluginRmConfig) error {
615 615
 
616 616
 	if !config.ForceRemove {
617 617
 		if p.GetRefCount() > 0 {
618
-			return fmt.Errorf("plugin %s is in use", p.Name())
618
+			return inUseError(p.Name())
619 619
 		}
620 620
 		if p.IsEnabled() {
621
-			return fmt.Errorf("plugin %s is enabled", p.Name())
621
+			return enabledError(p.Name())
622 622
 		}
623 623
 	}
624 624
 
625 625
new file mode 100644
... ...
@@ -0,0 +1,94 @@
0
+package plugin
1
+
2
+import "fmt"
3
+
4
+type errNotFound string
5
+
6
+func (name errNotFound) Error() string {
7
+	return fmt.Sprintf("plugin %q not found", string(name))
8
+}
9
+
10
+func (errNotFound) NotFound() {}
11
+
12
+type errAmbiguous string
13
+
14
+func (name errAmbiguous) Error() string {
15
+	return fmt.Sprintf("multiple plugins found for %q", string(name))
16
+}
17
+
18
+func (name errAmbiguous) InvalidParameter() {}
19
+
20
+type errDisabled string
21
+
22
+func (name errDisabled) Error() string {
23
+	return fmt.Sprintf("plugin %s found but disabled", string(name))
24
+}
25
+
26
+func (name errDisabled) Conflict() {}
27
+
28
+type validationError struct {
29
+	cause error
30
+}
31
+
32
+func (e validationError) Error() string {
33
+	return e.cause.Error()
34
+}
35
+
36
+func (validationError) Conflict() {}
37
+
38
+func (e validationError) Cause() error {
39
+	return e.cause
40
+}
41
+
42
+type systemError struct {
43
+	cause error
44
+}
45
+
46
+func (e systemError) Error() string {
47
+	return e.cause.Error()
48
+}
49
+
50
+func (systemError) SystemError() {}
51
+
52
+func (e systemError) Cause() error {
53
+	return e.cause
54
+}
55
+
56
+type invalidFilter struct {
57
+	filter string
58
+	value  []string
59
+}
60
+
61
+func (e invalidFilter) Error() string {
62
+	msg := "Invalid filter '" + e.filter
63
+	if len(e.value) > 0 {
64
+		msg += fmt.Sprintf("=%s", e.value)
65
+	}
66
+	return msg + "'"
67
+}
68
+
69
+func (invalidFilter) InvalidParameter() {}
70
+
71
+type inUseError string
72
+
73
+func (e inUseError) Error() string {
74
+	return "plugin " + string(e) + " is in use"
75
+}
76
+
77
+func (inUseError) Conflict() {}
78
+
79
+type enabledError string
80
+
81
+func (e enabledError) Error() string {
82
+	return "plugin " + string(e) + " is enabled"
83
+}
84
+
85
+func (enabledError) Conflict() {}
86
+
87
+type alreadyExistsError string
88
+
89
+func (e alreadyExistsError) Error() string {
90
+	return "plugin " + string(e) + " already exists"
91
+}
92
+
93
+func (alreadyExistsError) Conflict() {}
... ...
@@ -4,7 +4,6 @@ package plugin
4 4
 
5 5
 import (
6 6
 	"encoding/json"
7
-	"fmt"
8 7
 	"net"
9 8
 	"os"
10 9
 	"path/filepath"
... ...
@@ -28,7 +27,7 @@ import (
28 28
 func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) error {
29 29
 	p.Rootfs = filepath.Join(pm.config.Root, p.PluginObj.ID, "rootfs")
30 30
 	if p.IsEnabled() && !force {
31
-		return fmt.Errorf("plugin %s is already enabled", p.Name())
31
+		return errors.Wrap(enabledError(p.Name()), "plugin already enabled")
32 32
 	}
33 33
 	spec, err := p.InitSpec(pm.config.ExecRoot)
34 34
 	if err != nil {
... ...
@@ -171,7 +170,7 @@ func setupRoot(root string) error {
171 171
 
172 172
 func (pm *Manager) disable(p *v2.Plugin, c *controller) error {
173 173
 	if !p.IsEnabled() {
174
-		return fmt.Errorf("plugin %s is already disabled", p.Name())
174
+		return errors.Wrap(errDisabled(p.Name()), "plugin is already disabled")
175 175
 	}
176 176
 
177 177
 	c.restart = false
... ...
@@ -213,12 +212,12 @@ func (pm *Manager) upgradePlugin(p *v2.Plugin, configDigest digest.Digest, blobs
213 213
 	// This could happen if the plugin was disabled with `-f` with active mounts.
214 214
 	// If there is anything in `orig` is still mounted, this should error out.
215 215
 	if err := mount.RecursiveUnmount(orig); err != nil {
216
-		return err
216
+		return systemError{err}
217 217
 	}
218 218
 
219 219
 	backup := orig + "-old"
220 220
 	if err := os.Rename(orig, backup); err != nil {
221
-		return errors.Wrap(err, "error backing up plugin data before upgrade")
221
+		return errors.Wrap(systemError{err}, "error backing up plugin data before upgrade")
222 222
 	}
223 223
 
224 224
 	defer func() {
... ...
@@ -244,7 +243,7 @@ func (pm *Manager) upgradePlugin(p *v2.Plugin, configDigest digest.Digest, blobs
244 244
 	}()
245 245
 
246 246
 	if err := os.Rename(tmpRootFSDir, orig); err != nil {
247
-		return errors.Wrap(err, "error upgrading")
247
+		return errors.Wrap(systemError{err}, "error upgrading")
248 248
 	}
249 249
 
250 250
 	p.PluginObj.Config = config
... ...
@@ -268,7 +267,7 @@ func (pm *Manager) setupNewPlugin(configDigest digest.Digest, blobsums []digest.
268 268
 		return types.PluginConfig{}, errors.New("invalid config json")
269 269
 	}
270 270
 
271
-	requiredPrivileges, err := computePrivileges(config)
271
+	requiredPrivileges := computePrivileges(config)
272 272
 	if err != nil {
273 273
 		return types.PluginConfig{}, err
274 274
 	}
... ...
@@ -284,7 +283,7 @@ func (pm *Manager) setupNewPlugin(configDigest digest.Digest, blobsums []digest.
284 284
 // createPlugin creates a new plugin. take lock before calling.
285 285
 func (pm *Manager) createPlugin(name string, configDigest digest.Digest, blobsums []digest.Digest, rootFSDir string, privileges *types.PluginPrivileges, opts ...CreateOpt) (p *v2.Plugin, err error) {
286 286
 	if err := pm.config.Store.validateName(name); err != nil { // todo: this check is wrong. remove store
287
-		return nil, err
287
+		return nil, validationError{err}
288 288
 	}
289 289
 
290 290
 	config, err := pm.setupNewPlugin(configDigest, blobsums, privileges)
... ...
@@ -24,25 +24,6 @@ const allowV1PluginsFallback bool = true
24 24
 */
25 25
 const defaultAPIVersion string = "1.0"
26 26
 
27
-// ErrNotFound indicates that a plugin was not found locally.
28
-type ErrNotFound string
29
-
30
-func (name ErrNotFound) Error() string { return fmt.Sprintf("plugin %q not found", string(name)) }
31
-
32
-// ErrAmbiguous indicates that more than one plugin was found
33
-type ErrAmbiguous string
34
-
35
-func (name ErrAmbiguous) Error() string {
36
-	return fmt.Sprintf("multiple plugins found for %q", string(name))
37
-}
38
-
39
-// ErrDisabled indicates that a plugin was found but it is disabled
40
-type ErrDisabled string
41
-
42
-func (name ErrDisabled) Error() string {
43
-	return fmt.Sprintf("plugin %s found but disabled", string(name))
44
-}
45
-
46 27
 // GetV2Plugin retrieves a plugin by name, id or partial ID.
47 28
 func (ps *Store) GetV2Plugin(refOrID string) (*v2.Plugin, error) {
48 29
 	ps.RLock()
... ...
@@ -55,7 +36,7 @@ func (ps *Store) GetV2Plugin(refOrID string) (*v2.Plugin, error) {
55 55
 
56 56
 	p, idOk := ps.plugins[id]
57 57
 	if !idOk {
58
-		return nil, errors.WithStack(ErrNotFound(id))
58
+		return nil, errors.WithStack(errNotFound(id))
59 59
 	}
60 60
 
61 61
 	return p, nil
... ...
@@ -65,7 +46,7 @@ func (ps *Store) GetV2Plugin(refOrID string) (*v2.Plugin, error) {
65 65
 func (ps *Store) validateName(name string) error {
66 66
 	for _, p := range ps.plugins {
67 67
 		if p.Name() == name {
68
-			return errors.Errorf("plugin %q already exists", name)
68
+			return alreadyExistsError(name)
69 69
 		}
70 70
 	}
71 71
 	return nil
... ...
@@ -145,9 +126,9 @@ func (ps *Store) Get(name, capability string, mode int) (plugingetter.CompatPlug
145 145
 			}
146 146
 			// Plugin was found but it is disabled, so we should not fall back to legacy plugins
147 147
 			// but we should error out right away
148
-			return nil, ErrDisabled(name)
148
+			return nil, errDisabled(name)
149 149
 		}
150
-		if _, ok := errors.Cause(err).(ErrNotFound); !ok {
150
+		if _, ok := errors.Cause(err).(errNotFound); !ok {
151 151
 			return nil, err
152 152
 		}
153 153
 	}
... ...
@@ -156,7 +137,10 @@ func (ps *Store) Get(name, capability string, mode int) (plugingetter.CompatPlug
156 156
 	if allowV1PluginsFallback {
157 157
 		p, err := plugins.Get(name, capability)
158 158
 		if err != nil {
159
-			return nil, fmt.Errorf("legacy plugin: %v", err)
159
+			if errors.Cause(err) == plugins.ErrNotFound {
160
+				return nil, errNotFound(name)
161
+			}
162
+			return nil, errors.Wrap(systemError{err}, "legacy plugin")
160 163
 		}
161 164
 		return p, nil
162 165
 	}
... ...
@@ -189,7 +173,7 @@ func (ps *Store) GetAllByCap(capability string) ([]plugingetter.CompatPlugin, er
189 189
 	if allowV1PluginsFallback {
190 190
 		pl, err := plugins.GetAll(capability)
191 191
 		if err != nil {
192
-			return nil, fmt.Errorf("legacy plugin: %v", err)
192
+			return nil, errors.Wrap(systemError{err}, "legacy plugin")
193 193
 		}
194 194
 		for _, p := range pl {
195 195
 			result = append(result, p)
... ...
@@ -239,11 +223,11 @@ func (ps *Store) resolvePluginID(idOrName string) (string, error) {
239 239
 
240 240
 	ref, err := reference.ParseNormalizedNamed(idOrName)
241 241
 	if err != nil {
242
-		return "", errors.WithStack(ErrNotFound(idOrName))
242
+		return "", errors.WithStack(errNotFound(idOrName))
243 243
 	}
244 244
 	if _, ok := ref.(reference.Canonical); ok {
245 245
 		logrus.Warnf("canonical references cannot be resolved: %v", reference.FamiliarString(ref))
246
-		return "", errors.WithStack(ErrNotFound(idOrName))
246
+		return "", errors.WithStack(errNotFound(idOrName))
247 247
 	}
248 248
 
249 249
 	ref = reference.TagNameOnly(ref)
... ...
@@ -258,13 +242,13 @@ func (ps *Store) resolvePluginID(idOrName string) (string, error) {
258 258
 	for id, p := range ps.plugins { // this can be optimized
259 259
 		if strings.HasPrefix(id, idOrName) {
260 260
 			if found != nil {
261
-				return "", errors.WithStack(ErrAmbiguous(idOrName))
261
+				return "", errors.WithStack(errAmbiguous(idOrName))
262 262
 			}
263 263
 			found = p
264 264
 		}
265 265
 	}
266 266
 	if found == nil {
267
-		return "", errors.WithStack(ErrNotFound(idOrName))
267
+		return "", errors.WithStack(errNotFound(idOrName))
268 268
 	}
269 269
 	return found.PluginObj.ID, nil
270 270
 }
271 271
new file mode 100644
... ...
@@ -0,0 +1,25 @@
0
+package reference
1
+
2
+type notFoundError string
3
+
4
+func (e notFoundError) Error() string {
5
+	return string(e)
6
+}
7
+
8
+func (notFoundError) NotFound() {}
9
+
10
+type invalidTagError string
11
+
12
+func (e invalidTagError) Error() string {
13
+	return string(e)
14
+}
15
+
16
+func (invalidTagError) InvalidParameter() {}
17
+
18
+type conflictingTagError string
19
+
20
+func (e conflictingTagError) Error() string {
21
+	return string(e)
22
+}
23
+
24
+func (conflictingTagError) Conflict() {}
... ...
@@ -2,7 +2,6 @@ package reference
2 2
 
3 3
 import (
4 4
 	"encoding/json"
5
-	"errors"
6 5
 	"fmt"
7 6
 	"os"
8 7
 	"path/filepath"
... ...
@@ -12,12 +11,13 @@ import (
12 12
 	"github.com/docker/distribution/reference"
13 13
 	"github.com/docker/docker/pkg/ioutils"
14 14
 	"github.com/opencontainers/go-digest"
15
+	"github.com/pkg/errors"
15 16
 )
16 17
 
17 18
 var (
18 19
 	// ErrDoesNotExist is returned if a reference is not found in the
19 20
 	// store.
20
-	ErrDoesNotExist = errors.New("reference does not exist")
21
+	ErrDoesNotExist notFoundError = "reference does not exist"
21 22
 )
22 23
 
23 24
 // An Association is a tuple associating a reference with an image ID.
... ...
@@ -100,7 +100,7 @@ func NewReferenceStore(jsonPath, platform string) (Store, error) {
100 100
 // references can be overwritten. This only works for tags, not digests.
101 101
 func (store *store) AddTag(ref reference.Named, id digest.Digest, force bool) error {
102 102
 	if _, isCanonical := ref.(reference.Canonical); isCanonical {
103
-		return errors.New("refusing to create a tag with a digest reference")
103
+		return errors.WithStack(invalidTagError("refusing to create a tag with a digest reference"))
104 104
 	}
105 105
 	return store.addReference(reference.TagNameOnly(ref), id, force)
106 106
 }
... ...
@@ -138,7 +138,7 @@ func (store *store) addReference(ref reference.Named, id digest.Digest, force bo
138 138
 	refStr := reference.FamiliarString(ref)
139 139
 
140 140
 	if refName == string(digest.Canonical) {
141
-		return errors.New("refusing to create an ambiguous tag using digest algorithm as name")
141
+		return errors.WithStack(invalidTagError("refusing to create an ambiguous tag using digest algorithm as name"))
142 142
 	}
143 143
 
144 144
 	store.mu.Lock()
... ...
@@ -155,11 +155,15 @@ func (store *store) addReference(ref reference.Named, id digest.Digest, force bo
155 155
 	if exists {
156 156
 		// force only works for tags
157 157
 		if digested, isDigest := ref.(reference.Canonical); isDigest {
158
-			return fmt.Errorf("Cannot overwrite digest %s", digested.Digest().String())
158
+			return errors.WithStack(conflictingTagError("Cannot overwrite digest " + digested.Digest().String()))
159 159
 		}
160 160
 
161 161
 		if !force {
162
-			return fmt.Errorf("Conflict: Tag %s is already set to image %s, if you want to replace it, please use -f option", refStr, oldID.String())
162
+			return errors.WithStack(
163
+				conflictingTagError(
164
+					fmt.Sprintf("Conflict: Tag %s is already set to image %s, if you want to replace it, please use the force option", refStr, oldID.String()),
165
+				),
166
+			)
163 167
 		}
164 168
 
165 169
 		if store.referencesByIDCache[oldID] != nil {
... ...
@@ -1,7 +1,6 @@
1 1
 package registry
2 2
 
3 3
 import (
4
-	"fmt"
5 4
 	"io/ioutil"
6 5
 	"net/http"
7 6
 	"net/url"
... ...
@@ -13,6 +12,7 @@ import (
13 13
 	"github.com/docker/distribution/registry/client/transport"
14 14
 	"github.com/docker/docker/api/types"
15 15
 	registrytypes "github.com/docker/docker/api/types/registry"
16
+	"github.com/pkg/errors"
16 17
 	"github.com/sirupsen/logrus"
17 18
 )
18 19
 
... ...
@@ -23,21 +23,15 @@ const (
23 23
 
24 24
 // loginV1 tries to register/login to the v1 registry server.
25 25
 func loginV1(authConfig *types.AuthConfig, apiEndpoint APIEndpoint, userAgent string) (string, string, error) {
26
-	registryEndpoint, err := apiEndpoint.ToV1Endpoint(userAgent, nil)
27
-	if err != nil {
28
-		return "", "", err
29
-	}
30
-
26
+	registryEndpoint := apiEndpoint.ToV1Endpoint(userAgent, nil)
31 27
 	serverAddress := registryEndpoint.String()
32 28
 
33 29
 	logrus.Debugf("attempting v1 login to registry endpoint %s", serverAddress)
34 30
 
35 31
 	if serverAddress == "" {
36
-		return "", "", fmt.Errorf("Server Error: Server Address not set.")
32
+		return "", "", systemError{errors.New("Server Error: Server Address not set.")}
37 33
 	}
38 34
 
39
-	loginAgainstOfficialIndex := serverAddress == IndexServer
40
-
41 35
 	req, err := http.NewRequest("GET", serverAddress+"users/", nil)
42 36
 	if err != nil {
43 37
 		return "", "", err
... ...
@@ -53,27 +47,23 @@ func loginV1(authConfig *types.AuthConfig, apiEndpoint APIEndpoint, userAgent st
53 53
 	defer resp.Body.Close()
54 54
 	body, err := ioutil.ReadAll(resp.Body)
55 55
 	if err != nil {
56
-		return "", "", err
56
+		return "", "", systemError{err}
57 57
 	}
58
-	if resp.StatusCode == http.StatusOK {
58
+
59
+	switch resp.StatusCode {
60
+	case http.StatusOK:
59 61
 		return "Login Succeeded", "", nil
60
-	} else if resp.StatusCode == http.StatusUnauthorized {
61
-		if loginAgainstOfficialIndex {
62
-			return "", "", fmt.Errorf("Wrong login/password, please try again. Haven't got a Docker ID? Create one at https://hub.docker.com")
63
-		}
64
-		return "", "", fmt.Errorf("Wrong login/password, please try again")
65
-	} else if resp.StatusCode == http.StatusForbidden {
66
-		if loginAgainstOfficialIndex {
67
-			return "", "", fmt.Errorf("Login: Account is not active. Please check your e-mail for a confirmation link.")
68
-		}
62
+	case http.StatusUnauthorized:
63
+		return "", "", unauthorizedError{errors.New("Wrong login/password, please try again")}
64
+	case http.StatusForbidden:
69 65
 		// *TODO: Use registry configuration to determine what this says, if anything?
70
-		return "", "", fmt.Errorf("Login: Account is not active. Please see the documentation of the registry %s for instructions how to activate it.", serverAddress)
71
-	} else if resp.StatusCode == http.StatusInternalServerError { // Issue #14326
66
+		return "", "", notActivatedError{errors.Errorf("Login: Account is not active. Please see the documentation of the registry %s for instructions how to activate it.", serverAddress)}
67
+	case http.StatusInternalServerError:
72 68
 		logrus.Errorf("%s returned status code %d. Response Body :\n%s", req.URL.String(), resp.StatusCode, body)
73
-		return "", "", fmt.Errorf("Internal Server Error")
69
+		return "", "", systemError{errors.New("Internal Server Error")}
74 70
 	}
75
-	return "", "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body,
76
-		resp.StatusCode, resp.Header)
71
+	return "", "", systemError{errors.Errorf("Login: %s (Code: %d; Headers: %s)", body,
72
+		resp.StatusCode, resp.Header)}
77 73
 }
78 74
 
79 75
 type loginCredentialStore struct {
... ...
@@ -159,24 +149,25 @@ func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent strin
159 159
 
160 160
 	resp, err := loginClient.Do(req)
161 161
 	if err != nil {
162
+		err = translateV2AuthError(err)
162 163
 		if !foundV2 {
163 164
 			err = fallbackError{err: err}
164 165
 		}
166
+
165 167
 		return "", "", err
166 168
 	}
167 169
 	defer resp.Body.Close()
168 170
 
169
-	if resp.StatusCode != http.StatusOK {
170
-		// TODO(dmcgowan): Attempt to further interpret result, status code and error code string
171
-		err := fmt.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode))
172
-		if !foundV2 {
173
-			err = fallbackError{err: err}
174
-		}
175
-		return "", "", err
171
+	if resp.StatusCode == http.StatusOK {
172
+		return "Login Succeeded", credentialAuthConfig.IdentityToken, nil
176 173
 	}
177 174
 
178
-	return "Login Succeeded", credentialAuthConfig.IdentityToken, nil
179
-
175
+	// TODO(dmcgowan): Attempt to further interpret result, status code and error code string
176
+	err = errors.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode))
177
+	if !foundV2 {
178
+		err = fallbackError{err: err}
179
+	}
180
+	return "", "", err
180 181
 }
181 182
 
182 183
 func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifiers []transport.RequestModifier, creds auth.CredentialStore, scopes []auth.Scope) (*http.Client, bool, error) {
... ...
@@ -67,7 +67,7 @@ func validateEndpoint(endpoint *V1Endpoint) error {
67 67
 	return nil
68 68
 }
69 69
 
70
-func newV1Endpoint(address url.URL, tlsConfig *tls.Config, userAgent string, metaHeaders http.Header) (*V1Endpoint, error) {
70
+func newV1Endpoint(address url.URL, tlsConfig *tls.Config, userAgent string, metaHeaders http.Header) *V1Endpoint {
71 71
 	endpoint := &V1Endpoint{
72 72
 		IsSecure: (tlsConfig == nil || !tlsConfig.InsecureSkipVerify),
73 73
 		URL:      new(url.URL),
... ...
@@ -78,7 +78,7 @@ func newV1Endpoint(address url.URL, tlsConfig *tls.Config, userAgent string, met
78 78
 	// TODO(tiborvass): make sure a ConnectTimeout transport is used
79 79
 	tr := NewTransport(tlsConfig)
80 80
 	endpoint.client = HTTPClient(transport.NewTransport(tr, DockerHeaders(userAgent, metaHeaders)...))
81
-	return endpoint, nil
81
+	return endpoint
82 82
 }
83 83
 
84 84
 // trimV1Address trims the version off the address and returns the
... ...
@@ -123,7 +123,7 @@ func newV1EndpointFromStr(address string, tlsConfig *tls.Config, userAgent strin
123 123
 		return nil, err
124 124
 	}
125 125
 
126
-	endpoint, err := newV1Endpoint(*uri, tlsConfig, userAgent, metaHeaders)
126
+	endpoint := newV1Endpoint(*uri, tlsConfig, userAgent, metaHeaders)
127 127
 	if err != nil {
128 128
 		return nil, err
129 129
 	}
130 130
new file mode 100644
... ...
@@ -0,0 +1,86 @@
0
+package registry
1
+
2
+import (
3
+	"net/url"
4
+
5
+	"github.com/docker/distribution/registry/api/errcode"
6
+)
7
+
8
+type notFoundError string
9
+
10
+func (e notFoundError) Error() string {
11
+	return string(e)
12
+}
13
+
14
+func (notFoundError) NotFound() {}
15
+
16
+type validationError struct {
17
+	cause error
18
+}
19
+
20
+func (e validationError) Error() string {
21
+	return e.cause.Error()
22
+}
23
+
24
+func (e validationError) InvalidParameter() {}
25
+
26
+func (e validationError) Cause() error {
27
+	return e.cause
28
+}
29
+
30
+type unauthorizedError struct {
31
+	cause error
32
+}
33
+
34
+func (e unauthorizedError) Error() string {
35
+	return e.cause.Error()
36
+}
37
+
38
+func (e unauthorizedError) Unauthorized() {}
39
+
40
+func (e unauthorizedError) Cause() error {
41
+	return e.cause
42
+}
43
+
44
+type systemError struct {
45
+	cause error
46
+}
47
+
48
+func (e systemError) Error() string {
49
+	return e.cause.Error()
50
+}
51
+
52
+func (e systemError) SystemError() {}
53
+
54
+func (e systemError) Cause() error {
55
+	return e.cause
56
+}
57
+
58
+type notActivatedError struct {
59
+	cause error
60
+}
61
+
62
+func (e notActivatedError) Error() string {
63
+	return e.cause.Error()
64
+}
65
+
66
+func (e notActivatedError) Forbidden() {}
67
+
68
+func (e notActivatedError) Cause() error {
69
+	return e.cause
70
+}
71
+
72
+func translateV2AuthError(err error) error {
73
+	switch e := err.(type) {
74
+	case *url.Error:
75
+		switch e2 := e.Err.(type) {
76
+		case errcode.Error:
77
+			switch e2.Code {
78
+			case errcode.ErrorCodeUnauthorized:
79
+				return unauthorizedError{err}
80
+			}
81
+		}
82
+	}
83
+
84
+	return err
85
+}
... ...
@@ -2,7 +2,6 @@ package registry
2 2
 
3 3
 import (
4 4
 	"crypto/tls"
5
-	"fmt"
6 5
 	"net/http"
7 6
 	"net/url"
8 7
 	"strings"
... ...
@@ -14,6 +13,7 @@ import (
14 14
 	"github.com/docker/distribution/registry/client/auth"
15 15
 	"github.com/docker/docker/api/types"
16 16
 	registrytypes "github.com/docker/docker/api/types/registry"
17
+	"github.com/pkg/errors"
17 18
 	"github.com/sirupsen/logrus"
18 19
 )
19 20
 
... ...
@@ -117,12 +117,12 @@ func (s *DefaultService) Auth(ctx context.Context, authConfig *types.AuthConfig,
117 117
 	}
118 118
 	u, err := url.Parse(serverAddress)
119 119
 	if err != nil {
120
-		return "", "", fmt.Errorf("unable to parse server address: %v", err)
120
+		return "", "", validationError{errors.Errorf("unable to parse server address: %v", err)}
121 121
 	}
122 122
 
123 123
 	endpoints, err := s.LookupPushEndpoints(u.Host)
124 124
 	if err != nil {
125
-		return "", "", err
125
+		return "", "", validationError{err}
126 126
 	}
127 127
 
128 128
 	for _, endpoint := range endpoints {
... ...
@@ -140,6 +140,7 @@ func (s *DefaultService) Auth(ctx context.Context, authConfig *types.AuthConfig,
140 140
 			logrus.Infof("Error logging in to %s endpoint, trying next endpoint: %v", endpoint.Version, err)
141 141
 			continue
142 142
 		}
143
+
143 144
 		return "", "", err
144 145
 	}
145 146
 
... ...
@@ -258,7 +259,7 @@ type APIEndpoint struct {
258 258
 }
259 259
 
260 260
 // ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint
261
-func (e APIEndpoint) ToV1Endpoint(userAgent string, metaHeaders http.Header) (*V1Endpoint, error) {
261
+func (e APIEndpoint) ToV1Endpoint(userAgent string, metaHeaders http.Header) *V1Endpoint {
262 262
 	return newV1Endpoint(*e.URL, e.TLSConfig, userAgent, metaHeaders)
263 263
 }
264 264
 
... ...
@@ -3,7 +3,6 @@ package registry
3 3
 import (
4 4
 	"bytes"
5 5
 	"crypto/sha256"
6
-	"errors"
7 6
 	"sync"
8 7
 	// this is required for some certificates
9 8
 	_ "crypto/sha512"
... ...
@@ -27,13 +26,14 @@ import (
27 27
 	"github.com/docker/docker/pkg/stringid"
28 28
 	"github.com/docker/docker/pkg/tarsum"
29 29
 	"github.com/docker/docker/registry/resumable"
30
+	"github.com/pkg/errors"
30 31
 	"github.com/sirupsen/logrus"
31 32
 )
32 33
 
33 34
 var (
34 35
 	// ErrRepoNotFound is returned if the repository didn't exist on the
35 36
 	// remote side
36
-	ErrRepoNotFound = errors.New("Repository not found")
37
+	ErrRepoNotFound notFoundError = "Repository not found"
37 38
 )
38 39
 
39 40
 // A Session is used to communicate with a V1 registry
... ...
@@ -734,27 +734,27 @@ func shouldRedirect(response *http.Response) bool {
734 734
 // SearchRepositories performs a search against the remote repository
735 735
 func (r *Session) SearchRepositories(term string, limit int) (*registrytypes.SearchResults, error) {
736 736
 	if limit < 1 || limit > 100 {
737
-		return nil, fmt.Errorf("Limit %d is outside the range of [1, 100]", limit)
737
+		return nil, validationError{errors.Errorf("Limit %d is outside the range of [1, 100]", limit)}
738 738
 	}
739 739
 	logrus.Debugf("Index server: %s", r.indexEndpoint)
740 740
 	u := r.indexEndpoint.String() + "search?q=" + url.QueryEscape(term) + "&n=" + url.QueryEscape(fmt.Sprintf("%d", limit))
741 741
 
742 742
 	req, err := http.NewRequest("GET", u, nil)
743 743
 	if err != nil {
744
-		return nil, fmt.Errorf("Error while getting from the server: %v", err)
744
+		return nil, errors.Wrap(validationError{err}, "Error building request")
745 745
 	}
746 746
 	// Have the AuthTransport send authentication, when logged in.
747 747
 	req.Header.Set("X-Docker-Token", "true")
748 748
 	res, err := r.client.Do(req)
749 749
 	if err != nil {
750
-		return nil, err
750
+		return nil, systemError{err}
751 751
 	}
752 752
 	defer res.Body.Close()
753 753
 	if res.StatusCode != 200 {
754 754
 		return nil, newJSONError(fmt.Sprintf("Unexpected status code %d", res.StatusCode), res)
755 755
 	}
756 756
 	result := new(registrytypes.SearchResults)
757
-	return result, json.NewDecoder(res.Body).Decode(result)
757
+	return result, errors.Wrap(json.NewDecoder(res.Body).Decode(result), "error decoding registry search results")
758 758
 }
759 759
 
760 760
 func isTimeout(err error) bool {
... ...
@@ -2,13 +2,13 @@ package runconfig
2 2
 
3 3
 import (
4 4
 	"encoding/json"
5
-	"fmt"
6 5
 	"io"
7 6
 
8 7
 	"github.com/docker/docker/api/types/container"
9 8
 	networktypes "github.com/docker/docker/api/types/network"
10 9
 	"github.com/docker/docker/pkg/sysinfo"
11 10
 	"github.com/docker/docker/volume"
11
+	"github.com/pkg/errors"
12 12
 )
13 13
 
14 14
 // ContainerDecoder implements httputils.ContainerDecoder
... ...
@@ -17,19 +17,27 @@ type ContainerDecoder struct{}
17 17
 
18 18
 // DecodeConfig makes ContainerDecoder to implement httputils.ContainerDecoder
19 19
 func (r ContainerDecoder) DecodeConfig(src io.Reader) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
20
-	return DecodeContainerConfig(src)
20
+	c, hc, nc, err := decodeContainerConfig(src)
21
+	if err != nil {
22
+		return nil, nil, nil, err
23
+	}
24
+	return c, hc, nc, nil
21 25
 }
22 26
 
23 27
 // DecodeHostConfig makes ContainerDecoder to implement httputils.ContainerDecoder
24 28
 func (r ContainerDecoder) DecodeHostConfig(src io.Reader) (*container.HostConfig, error) {
25
-	return DecodeHostConfig(src)
29
+	hc, err := decodeHostConfig(src)
30
+	if err != nil {
31
+		return nil, err
32
+	}
33
+	return hc, nil
26 34
 }
27 35
 
28
-// DecodeContainerConfig decodes a json encoded config into a ContainerConfigWrapper
36
+// decodeContainerConfig decodes a json encoded config into a ContainerConfigWrapper
29 37
 // struct and returns both a Config and a HostConfig struct
30 38
 // Be aware this function is not checking whether the resulted structs are nil,
31 39
 // it's your business to do so
32
-func DecodeContainerConfig(src io.Reader) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
40
+func decodeContainerConfig(src io.Reader) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
33 41
 	var w ContainerConfigWrapper
34 42
 
35 43
 	decoder := json.NewDecoder(src)
... ...
@@ -95,12 +103,12 @@ func validateMountSettings(c *container.Config, hc *container.HostConfig) error
95 95
 	// Ensure all volumes and binds are valid.
96 96
 	for spec := range c.Volumes {
97 97
 		if _, err := volume.ParseMountRaw(spec, hc.VolumeDriver); err != nil {
98
-			return fmt.Errorf("invalid volume spec %q: %v", spec, err)
98
+			return errors.Wrapf(err, "invalid volume spec %q", spec)
99 99
 		}
100 100
 	}
101 101
 	for _, spec := range hc.Binds {
102 102
 		if _, err := volume.ParseMountRaw(spec, hc.VolumeDriver); err != nil {
103
-			return fmt.Errorf("invalid bind mount spec %q: %v", spec, err)
103
+			return errors.Wrapf(err, "invalid bind mount spec %q", spec)
104 104
 		}
105 105
 	}
106 106
 
... ...
@@ -51,7 +51,7 @@ func TestDecodeContainerConfig(t *testing.T) {
51 51
 			t.Fatal(err)
52 52
 		}
53 53
 
54
-		c, h, _, err := DecodeContainerConfig(bytes.NewReader(b))
54
+		c, h, _, err := decodeContainerConfig(bytes.NewReader(b))
55 55
 		if err != nil {
56 56
 			t.Fatal(fmt.Errorf("Error parsing %s: %v", f, err))
57 57
 		}
... ...
@@ -135,5 +135,5 @@ func callDecodeContainerConfigIsolation(isolation string) (*container.Config, *c
135 135
 	if b, err = json.Marshal(w); err != nil {
136 136
 		return nil, nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
137 137
 	}
138
-	return DecodeContainerConfig(bytes.NewReader(b))
138
+	return decodeContainerConfig(bytes.NewReader(b))
139 139
 }
... ...
@@ -1,38 +1,42 @@
1 1
 package runconfig
2 2
 
3
-import (
4
-	"fmt"
5
-)
6
-
7
-var (
3
+const (
8 4
 	// ErrConflictContainerNetworkAndLinks conflict between --net=container and links
9
-	ErrConflictContainerNetworkAndLinks = fmt.Errorf("conflicting options: container type network can't be used with links. This would result in undefined behavior")
5
+	ErrConflictContainerNetworkAndLinks validationError = "conflicting options: container type network can't be used with links. This would result in undefined behavior"
10 6
 	// ErrConflictSharedNetwork conflict between private and other networks
11
-	ErrConflictSharedNetwork = fmt.Errorf("container sharing network namespace with another container or host cannot be connected to any other network")
7
+	ErrConflictSharedNetwork validationError = "container sharing network namespace with another container or host cannot be connected to any other network"
12 8
 	// ErrConflictHostNetwork conflict from being disconnected from host network or connected to host network.
13
-	ErrConflictHostNetwork = fmt.Errorf("container cannot be disconnected from host network or connected to host network")
9
+	ErrConflictHostNetwork validationError = "container cannot be disconnected from host network or connected to host network"
14 10
 	// ErrConflictNoNetwork conflict between private and other networks
15
-	ErrConflictNoNetwork = fmt.Errorf("container cannot be connected to multiple networks with one of the networks in private (none) mode")
11
+	ErrConflictNoNetwork validationError = "container cannot be connected to multiple networks with one of the networks in private (none) mode"
16 12
 	// ErrConflictNetworkAndDNS conflict between --dns and the network mode
17
-	ErrConflictNetworkAndDNS = fmt.Errorf("conflicting options: dns and the network mode")
13
+	ErrConflictNetworkAndDNS validationError = "conflicting options: dns and the network mode"
18 14
 	// ErrConflictNetworkHostname conflict between the hostname and the network mode
19
-	ErrConflictNetworkHostname = fmt.Errorf("conflicting options: hostname and the network mode")
15
+	ErrConflictNetworkHostname validationError = "conflicting options: hostname and the network mode"
20 16
 	// ErrConflictHostNetworkAndLinks conflict between --net=host and links
21
-	ErrConflictHostNetworkAndLinks = fmt.Errorf("conflicting options: host type networking can't be used with links. This would result in undefined behavior")
17
+	ErrConflictHostNetworkAndLinks validationError = "conflicting options: host type networking can't be used with links. This would result in undefined behavior"
22 18
 	// ErrConflictContainerNetworkAndMac conflict between the mac address and the network mode
23
-	ErrConflictContainerNetworkAndMac = fmt.Errorf("conflicting options: mac-address and the network mode")
19
+	ErrConflictContainerNetworkAndMac validationError = "conflicting options: mac-address and the network mode"
24 20
 	// ErrConflictNetworkHosts conflict between add-host and the network mode
25
-	ErrConflictNetworkHosts = fmt.Errorf("conflicting options: custom host-to-IP mapping and the network mode")
21
+	ErrConflictNetworkHosts validationError = "conflicting options: custom host-to-IP mapping and the network mode"
26 22
 	// ErrConflictNetworkPublishPorts conflict between the publish options and the network mode
27
-	ErrConflictNetworkPublishPorts = fmt.Errorf("conflicting options: port publishing and the container type network mode")
23
+	ErrConflictNetworkPublishPorts validationError = "conflicting options: port publishing and the container type network mode"
28 24
 	// ErrConflictNetworkExposePorts conflict between the expose option and the network mode
29
-	ErrConflictNetworkExposePorts = fmt.Errorf("conflicting options: port exposing and the container type network mode")
25
+	ErrConflictNetworkExposePorts validationError = "conflicting options: port exposing and the container type network mode"
30 26
 	// ErrUnsupportedNetworkAndIP conflict between network mode and requested ip address
31
-	ErrUnsupportedNetworkAndIP = fmt.Errorf("user specified IP address is supported on user defined networks only")
27
+	ErrUnsupportedNetworkAndIP validationError = "user specified IP address is supported on user defined networks only"
32 28
 	// ErrUnsupportedNetworkNoSubnetAndIP conflict between network with no configured subnet and requested ip address
33
-	ErrUnsupportedNetworkNoSubnetAndIP = fmt.Errorf("user specified IP address is supported only when connecting to networks with user configured subnets")
29
+	ErrUnsupportedNetworkNoSubnetAndIP validationError = "user specified IP address is supported only when connecting to networks with user configured subnets"
34 30
 	// ErrUnsupportedNetworkAndAlias conflict between network mode and alias
35
-	ErrUnsupportedNetworkAndAlias = fmt.Errorf("network-scoped alias is supported only for containers in user defined networks")
31
+	ErrUnsupportedNetworkAndAlias validationError = "network-scoped alias is supported only for containers in user defined networks"
36 32
 	// ErrConflictUTSHostname conflict between the hostname and the UTS mode
37
-	ErrConflictUTSHostname = fmt.Errorf("conflicting options: hostname and the UTS mode")
33
+	ErrConflictUTSHostname validationError = "conflicting options: hostname and the UTS mode"
38 34
 )
35
+
36
+type validationError string
37
+
38
+func (e validationError) Error() string {
39
+	return string(e)
40
+}
41
+
42
+func (e validationError) InvalidParameter() {}
... ...
@@ -2,7 +2,6 @@ package runconfig
2 2
 
3 3
 import (
4 4
 	"encoding/json"
5
-	"fmt"
6 5
 	"io"
7 6
 	"strings"
8 7
 
... ...
@@ -11,7 +10,7 @@ import (
11 11
 
12 12
 // DecodeHostConfig creates a HostConfig based on the specified Reader.
13 13
 // It assumes the content of the reader will be JSON, and decodes it.
14
-func DecodeHostConfig(src io.Reader) (*container.HostConfig, error) {
14
+func decodeHostConfig(src io.Reader) (*container.HostConfig, error) {
15 15
 	decoder := json.NewDecoder(src)
16 16
 
17 17
 	var w ContainerConfigWrapper
... ...
@@ -45,7 +44,7 @@ func validateNetContainerMode(c *container.Config, hc *container.HostConfig) err
45 45
 	parts := strings.Split(string(hc.NetworkMode), ":")
46 46
 	if parts[0] == "container" {
47 47
 		if len(parts) < 2 || parts[1] == "" {
48
-			return fmt.Errorf("Invalid network mode: invalid container format container:<name|id>")
48
+			return validationError("Invalid network mode: invalid container format container:<name|id>")
49 49
 		}
50 50
 	}
51 51
 
... ...
@@ -190,7 +190,7 @@ func TestDecodeHostConfig(t *testing.T) {
190 190
 			t.Fatal(err)
191 191
 		}
192 192
 
193
-		c, err := DecodeHostConfig(bytes.NewReader(b))
193
+		c, err := decodeHostConfig(bytes.NewReader(b))
194 194
 		if err != nil {
195 195
 			t.Fatal(fmt.Errorf("Error parsing %s: %v", f, err))
196 196
 		}
... ...
@@ -10,6 +10,7 @@ import (
10 10
 	"github.com/docker/docker/pkg/locker"
11 11
 	getter "github.com/docker/docker/pkg/plugingetter"
12 12
 	"github.com/docker/docker/volume"
13
+	"github.com/pkg/errors"
13 14
 )
14 15
 
15 16
 // currently created by hand. generation tool would generate this like:
... ...
@@ -99,6 +100,14 @@ func Unregister(name string) bool {
99 99
 	return true
100 100
 }
101 101
 
102
+type driverNotFoundError string
103
+
104
+func (e driverNotFoundError) Error() string {
105
+	return "volume driver not found: " + string(e)
106
+}
107
+
108
+func (driverNotFoundError) NotFound() {}
109
+
102 110
 // lookup returns the driver associated with the given name. If a
103 111
 // driver with the given name has not been registered it checks if
104 112
 // there is a VolumeDriver plugin available with the given name.
... ...
@@ -115,7 +124,7 @@ func lookup(name string, mode int) (volume.Driver, error) {
115 115
 	if drivers.plugingetter != nil {
116 116
 		p, err := drivers.plugingetter.Get(name, extName, mode)
117 117
 		if err != nil {
118
-			return nil, fmt.Errorf("Error looking up volume plugin %s: %v", name, err)
118
+			return nil, errors.Wrap(err, "error looking up volume plugin "+name)
119 119
 		}
120 120
 
121 121
 		d := NewVolumeDriver(p.Name(), p.BasePath(), p.Client())
... ...
@@ -130,7 +139,7 @@ func lookup(name string, mode int) (volume.Driver, error) {
130 130
 		}
131 131
 		return d, nil
132 132
 	}
133
-	return nil, fmt.Errorf("Error looking up volume plugin %s", name)
133
+	return nil, driverNotFoundError(name)
134 134
 }
135 135
 
136 136
 func validateDriver(vd volume.Driver) error {
... ...
@@ -13,12 +13,11 @@ import (
13 13
 	"strings"
14 14
 	"sync"
15 15
 
16
-	"github.com/pkg/errors"
17
-
18 16
 	"github.com/docker/docker/api"
19 17
 	"github.com/docker/docker/pkg/idtools"
20 18
 	"github.com/docker/docker/pkg/mount"
21 19
 	"github.com/docker/docker/volume"
20
+	"github.com/pkg/errors"
22 21
 	"github.com/sirupsen/logrus"
23 22
 )
24 23
 
... ...
@@ -39,14 +38,6 @@ var (
39 39
 	volumeNameRegex = api.RestrictedNamePattern
40 40
 )
41 41
 
42
-type validationError struct {
43
-	error
44
-}
45
-
46
-func (validationError) IsValidationError() bool {
47
-	return true
48
-}
49
-
50 42
 type activeMount struct {
51 43
 	count   uint64
52 44
 	mounted bool
... ...
@@ -148,6 +139,30 @@ func (r *Root) Name() string {
148 148
 	return volume.DefaultDriverName
149 149
 }
150 150
 
151
+type alreadyExistsError struct {
152
+	path string
153
+}
154
+
155
+func (e alreadyExistsError) Error() string {
156
+	return "local volume already exists under " + e.path
157
+}
158
+
159
+func (e alreadyExistsError) Conflict() {}
160
+
161
+type systemError struct {
162
+	err error
163
+}
164
+
165
+func (e systemError) Error() string {
166
+	return e.err.Error()
167
+}
168
+
169
+func (e systemError) SystemError() {}
170
+
171
+func (e systemError) Cause() error {
172
+	return e.err
173
+}
174
+
151 175
 // Create creates a new volume.Volume with the provided name, creating
152 176
 // the underlying directory tree required for this volume in the
153 177
 // process.
... ...
@@ -167,9 +182,9 @@ func (r *Root) Create(name string, opts map[string]string) (volume.Volume, error
167 167
 	path := r.DataPath(name)
168 168
 	if err := idtools.MkdirAllAndChown(path, 0755, r.rootIDs); err != nil {
169 169
 		if os.IsExist(err) {
170
-			return nil, fmt.Errorf("volume already exists under %s", filepath.Dir(path))
170
+			return nil, alreadyExistsError{filepath.Dir(path)}
171 171
 		}
172
-		return nil, errors.Wrapf(err, "error while creating volume path '%s'", path)
172
+		return nil, errors.Wrapf(systemError{err}, "error while creating volume path '%s'", path)
173 173
 	}
174 174
 
175 175
 	var err error
... ...
@@ -195,7 +210,7 @@ func (r *Root) Create(name string, opts map[string]string) (volume.Volume, error
195 195
 			return nil, err
196 196
 		}
197 197
 		if err = ioutil.WriteFile(filepath.Join(filepath.Dir(path), "opts.json"), b, 600); err != nil {
198
-			return nil, errors.Wrap(err, "error while persisting volume options")
198
+			return nil, errors.Wrap(systemError{err}, "error while persisting volume options")
199 199
 		}
200 200
 	}
201 201
 
... ...
@@ -213,11 +228,11 @@ func (r *Root) Remove(v volume.Volume) error {
213 213
 
214 214
 	lv, ok := v.(*localVolume)
215 215
 	if !ok {
216
-		return fmt.Errorf("unknown volume type %T", v)
216
+		return systemError{errors.Errorf("unknown volume type %T", v)}
217 217
 	}
218 218
 
219 219
 	if lv.active.count > 0 {
220
-		return fmt.Errorf("volume has active mounts")
220
+		return systemError{errors.Errorf("volume has active mounts")}
221 221
 	}
222 222
 
223 223
 	if err := lv.unmount(); err != nil {
... ...
@@ -233,7 +248,7 @@ func (r *Root) Remove(v volume.Volume) error {
233 233
 	}
234 234
 
235 235
 	if !r.scopedPath(realPath) {
236
-		return fmt.Errorf("Unable to remove a directory of out the Docker root %s: %s", r.scope, realPath)
236
+		return systemError{errors.Errorf("Unable to remove a directory of out the Docker root %s: %s", r.scope, realPath)}
237 237
 	}
238 238
 
239 239
 	if err := removePath(realPath); err != nil {
... ...
@@ -249,7 +264,7 @@ func removePath(path string) error {
249 249
 		if os.IsNotExist(err) {
250 250
 			return nil
251 251
 		}
252
-		return errors.Wrapf(err, "error removing volume path '%s'", path)
252
+		return errors.Wrapf(systemError{err}, "error removing volume path '%s'", path)
253 253
 	}
254 254
 	return nil
255 255
 }
... ...
@@ -270,12 +285,20 @@ func (r *Root) Scope() string {
270 270
 	return volume.LocalScope
271 271
 }
272 272
 
273
+type validationError string
274
+
275
+func (e validationError) Error() string {
276
+	return string(e)
277
+}
278
+
279
+func (e validationError) InvalidParameter() {}
280
+
273 281
 func (r *Root) validateName(name string) error {
274 282
 	if len(name) == 1 {
275
-		return validationError{fmt.Errorf("volume name is too short, names should be at least two alphanumeric characters")}
283
+		return validationError("volume name is too short, names should be at least two alphanumeric characters")
276 284
 	}
277 285
 	if !volumeNameRegex.MatchString(name) {
278
-		return validationError{fmt.Errorf("%q includes invalid characters for a local volume name, only %q are allowed. If you intended to pass a host directory, use absolute path", name, api.RestrictedNameChars)}
286
+		return validationError(fmt.Sprintf("%q includes invalid characters for a local volume name, only %q are allowed. If you intended to pass a host directory, use absolute path", name, api.RestrictedNameChars))
279 287
 	}
280 288
 	return nil
281 289
 }
... ...
@@ -319,7 +342,7 @@ func (v *localVolume) Mount(id string) (string, error) {
319 319
 	if v.opts != nil {
320 320
 		if !v.active.mounted {
321 321
 			if err := v.mount(); err != nil {
322
-				return "", err
322
+				return "", systemError{err}
323 323
 			}
324 324
 			v.active.mounted = true
325 325
 		}
... ...
@@ -353,7 +376,7 @@ func (v *localVolume) unmount() error {
353 353
 	if v.opts != nil {
354 354
 		if err := mount.Unmount(v.path); err != nil {
355 355
 			if mounted, mErr := mount.Mounted(v.path); mounted || mErr != nil {
356
-				return errors.Wrapf(err, "error while unmounting volume path '%s'", v.path)
356
+				return errors.Wrapf(systemError{err}, "error while unmounting volume path '%s'", v.path)
357 357
 			}
358 358
 		}
359 359
 		v.active.mounted = false
... ...
@@ -364,7 +387,7 @@ func (v *localVolume) unmount() error {
364 364
 func validateOpts(opts map[string]string) error {
365 365
 	for opt := range opts {
366 366
 		if !validOpts[opt] {
367
-			return validationError{fmt.Errorf("invalid option key: %q", opt)}
367
+			return validationError(fmt.Sprintf("invalid option key: %q", opt))
368 368
 		}
369 369
 	}
370 370
 	return nil
... ...
@@ -2,21 +2,41 @@ package store
2 2
 
3 3
 import (
4 4
 	"strings"
5
-
6
-	"github.com/pkg/errors"
7 5
 )
8 6
 
9
-var (
7
+const (
10 8
 	// errVolumeInUse is a typed error returned when trying to remove a volume that is currently in use by a container
11
-	errVolumeInUse = errors.New("volume is in use")
9
+	errVolumeInUse conflictError = "volume is in use"
12 10
 	// errNoSuchVolume is a typed error returned if the requested volume doesn't exist in the volume store
13
-	errNoSuchVolume = errors.New("no such volume")
11
+	errNoSuchVolume notFoundError = "no such volume"
14 12
 	// errInvalidName is a typed error returned when creating a volume with a name that is not valid on the platform
15
-	errInvalidName = errors.New("volume name is not valid on this platform")
13
+	errInvalidName invalidName = "volume name is not valid on this platform"
16 14
 	// errNameConflict is a typed error returned on create when a volume exists with the given name, but for a different driver
17
-	errNameConflict = errors.New("volume name must be unique")
15
+	errNameConflict conflictError = "volume name must be unique"
18 16
 )
19 17
 
18
+type conflictError string
19
+
20
+func (e conflictError) Error() string {
21
+	return string(e)
22
+}
23
+func (conflictError) Conflict() {}
24
+
25
+type notFoundError string
26
+
27
+func (e notFoundError) Error() string {
28
+	return string(e)
29
+}
30
+
31
+func (notFoundError) NotFound() {}
32
+
33
+type invalidName string
34
+
35
+func (e invalidName) Error() string {
36
+	return string(e)
37
+}
38
+func (invalidName) InvalidParameter() {}
39
+
20 40
 // OpErr is the error type returned by functions in the store package. It describes
21 41
 // the operation, volume name, and error.
22 42
 type OpErr struct {
... ...
@@ -47,6 +67,11 @@ func (e *OpErr) Error() string {
47 47
 	return s
48 48
 }
49 49
 
50
+// Cause returns the error the caused this error
51
+func (e *OpErr) Cause() error {
52
+	return e.Err
53
+}
54
+
50 55
 // IsInUse returns a boolean indicating whether the error indicates that a
51 56
 // volume is in use
52 57
 func IsInUse(err error) bool {
... ...
@@ -64,13 +89,16 @@ func IsNameConflict(err error) bool {
64 64
 	return isErr(err, errNameConflict)
65 65
 }
66 66
 
67
+type causal interface {
68
+	Cause() error
69
+}
70
+
67 71
 func isErr(err error, expected error) bool {
68
-	err = errors.Cause(err)
69 72
 	switch pe := err.(type) {
70 73
 	case nil:
71 74
 		return false
72
-	case *OpErr:
73
-		err = errors.Cause(pe.Err)
75
+	case causal:
76
+		return isErr(pe.Cause(), expected)
74 77
 	}
75 78
 	return err == expected
76 79
 }
... ...
@@ -1,12 +1,12 @@
1 1
 package volume
2 2
 
3 3
 import (
4
-	"errors"
5 4
 	"fmt"
6 5
 	"os"
7 6
 	"runtime"
8 7
 
9 8
 	"github.com/docker/docker/api/types/mount"
9
+	"github.com/pkg/errors"
10 10
 )
11 11
 
12 12
 var errBindNotExist = errors.New("bind source path does not exist")
... ...
@@ -135,10 +135,10 @@ func (e *errMountConfig) Error() string {
135 135
 }
136 136
 
137 137
 func errExtraField(name string) error {
138
-	return fmt.Errorf("field %s must not be specified", name)
138
+	return errors.Errorf("field %s must not be specified", name)
139 139
 }
140 140
 func errMissingField(name string) error {
141
-	return fmt.Errorf("field %s must not be empty", name)
141
+	return errors.Errorf("field %s must not be empty", name)
142 142
 }
143 143
 
144 144
 func validateAbsolute(p string) error {
... ...
@@ -146,7 +146,7 @@ func validateAbsolute(p string) error {
146 146
 	if isAbsPath(p) {
147 147
 		return nil
148 148
 	}
149
-	return fmt.Errorf("invalid mount path: '%s' mount path must be absolute", p)
149
+	return errors.Errorf("invalid mount path: '%s' mount path must be absolute", p)
150 150
 }
151 151
 
152 152
 // ValidateTmpfsMountDestination validates the destination of tmpfs mount.
... ...
@@ -160,3 +160,17 @@ func ValidateTmpfsMountDestination(dest string) error {
160 160
 	}
161 161
 	return validateAbsolute(dest)
162 162
 }
163
+
164
+type validationError struct {
165
+	err error
166
+}
167
+
168
+func (e validationError) Error() string {
169
+	return e.err.Error()
170
+}
171
+
172
+func (e validationError) InvalidParameter() {}
173
+
174
+func (e validationError) Cause() error {
175
+	return e.err
176
+}
... ...
@@ -310,7 +310,7 @@ func ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
310 310
 		mp.Mode = mode
311 311
 	}
312 312
 	if err != nil {
313
-		err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err)
313
+		err = errors.Wrap(err, errInvalidSpec(raw).Error())
314 314
 	}
315 315
 	return mp, err
316 316
 }
... ...
@@ -318,7 +318,7 @@ func ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
318 318
 // ParseMountSpec reads a mount config, validates it, and configures a mountpoint from it.
319 319
 func ParseMountSpec(cfg mounttypes.Mount, options ...func(*validateOpts)) (*MountPoint, error) {
320 320
 	if err := validateMountConfig(&cfg, options...); err != nil {
321
-		return nil, err
321
+		return nil, validationError{err}
322 322
 	}
323 323
 	mp := &MountPoint{
324 324
 		RW:          !cfg.ReadOnly,
... ...
@@ -360,9 +360,9 @@ func ParseMountSpec(cfg mounttypes.Mount, options ...func(*validateOpts)) (*Moun
360 360
 }
361 361
 
362 362
 func errInvalidMode(mode string) error {
363
-	return fmt.Errorf("invalid mode: %v", mode)
363
+	return validationError{errors.Errorf("invalid mode: %v", mode)}
364 364
 }
365 365
 
366 366
 func errInvalidSpec(spec string) error {
367
-	return fmt.Errorf("invalid volume specification: '%s'", spec)
367
+	return validationError{errors.Errorf("invalid volume specification: '%s'", spec)}
368 368
 }