Browse Source

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 8 years ago
parent
commit
ebcb7d6b40
100 changed files with 1314 additions and 676 deletions
  1. 54 0
      api/errdefs/defs.go
  2. 8 0
      api/errdefs/doc.go
  3. 86 0
      api/errdefs/is.go
  4. 0 47
      api/errors/errors.go
  5. 0 64
      api/errors/errors_test.go
  6. 36 42
      api/server/httputils/errors.go
  7. 16 8
      api/server/httputils/form.go
  8. 17 3
      api/server/httputils/httputils.go
  9. 11 2
      api/server/middleware/version.go
  10. 24 8
      api/server/router/build/build_routes.go
  11. 1 1
      api/server/router/container/backend.go
  12. 9 3
      api/server/router/container/container.go
  13. 22 29
      api/server/router/container/container_routes.go
  14. 9 11
      api/server/router/container/copy.go
  15. 11 3
      api/server/router/container/exec.go
  16. 9 7
      api/server/router/experimental.go
  17. 25 3
      api/server/router/image/image_routes.go
  18. 9 3
      api/server/router/network/filter.go
  19. 22 6
      api/server/router/network/network_routes.go
  20. 15 2
      api/server/router/session/session_routes.go
  21. 27 13
      api/server/router/swarm/cluster_routes.go
  22. 11 2
      api/server/router/system/system_routes.go
  23. 9 4
      api/server/server.go
  24. 0 4
      api/swagger.yaml
  25. 17 2
      api/types/filters/parse.go
  26. 4 4
      builder/dockerfile/builder.go
  27. 1 1
      builder/dockerfile/dispatchers.go
  28. 15 0
      builder/dockerfile/errors.go
  29. 5 5
      builder/dockerfile/evaluator.go
  30. 75 0
      builder/remotecontext/errors.go
  31. 25 6
      builder/remotecontext/remote.go
  32. 9 8
      container/container.go
  33. 14 7
      container/container_unix.go
  34. 43 12
      daemon/archive.go
  35. 5 5
      daemon/attach.go
  36. 3 18
      daemon/cluster/cluster.go
  37. 2 2
      daemon/cluster/controllers/plugin/controller.go
  38. 114 0
      daemon/cluster/errors.go
  39. 1 1
      daemon/cluster/executor/backend.go
  40. 1 1
      daemon/cluster/executor/container/adapter.go
  41. 13 13
      daemon/cluster/helpers.go
  42. 14 15
      daemon/cluster/listen_addr.go
  43. 5 7
      daemon/cluster/networks.go
  44. 1 2
      daemon/cluster/nodes.go
  45. 2 3
      daemon/cluster/services.go
  46. 12 13
      daemon/cluster/swarm.go
  47. 15 16
      daemon/container.go
  48. 1 1
      daemon/container_linux.go
  49. 1 2
      daemon/container_operations.go
  50. 1 1
      daemon/container_operations_unix.go
  51. 13 15
      daemon/create.go
  52. 5 4
      daemon/daemon_unix.go
  53. 2 2
      daemon/daemon_windows.go
  54. 3 4
      daemon/delete.go
  55. 187 24
      daemon/errors.go
  56. 6 6
      daemon/exec.go
  57. 5 0
      daemon/exec/exec.go
  58. 9 7
      daemon/image.go
  59. 6 4
      daemon/image_delete.go
  60. 3 3
      daemon/image_pull.go
  61. 1 1
      daemon/images.go
  62. 4 4
      daemon/import.go
  63. 6 2
      daemon/inspect.go
  64. 7 3
      daemon/kill.go
  65. 6 6
      daemon/list.go
  66. 9 3
      daemon/logger/logger.go
  67. 9 9
      daemon/logs.go
  68. 4 3
      daemon/names.go
  69. 5 6
      daemon/network.go
  70. 1 1
      daemon/oci_linux.go
  71. 1 1
      daemon/pause.go
  72. 1 1
      daemon/prune.go
  73. 5 6
      daemon/rename.go
  74. 1 1
      daemon/resize.go
  75. 3 4
      daemon/search.go
  76. 34 48
      daemon/start.go
  77. 2 3
      daemon/start_unix.go
  78. 2 2
      daemon/stats/collector.go
  79. 2 3
      daemon/stats_unix.go
  80. 3 6
      daemon/stop.go
  81. 1 1
      daemon/top_unix.go
  82. 5 3
      daemon/update.go
  83. 4 5
      daemon/volumes.go
  84. 70 14
      distribution/errors.go
  85. 2 1
      distribution/pull.go
  86. 1 5
      distribution/pull_v1.go
  87. 3 3
      distribution/pull_v2.go
  88. 1 5
      distribution/push_v1.go
  89. 10 2
      image/store.go
  90. 18 19
      integration-cli/docker_api_containers_test.go
  91. 5 5
      integration-cli/docker_api_create_test.go
  92. 1 1
      integration-cli/docker_api_exec_resize_test.go
  93. 3 3
      integration-cli/docker_api_exec_test.go
  94. 2 2
      integration-cli/docker_api_resize_test.go
  95. 2 2
      integration-cli/docker_api_test.go
  96. 4 6
      integration-cli/docker_cli_cp_from_container_test.go
  97. 4 3
      integration-cli/docker_cli_cp_to_container_test.go
  98. 1 1
      integration-cli/docker_cli_cp_utils_test.go
  99. 1 2
      integration-cli/docker_cli_inspect_test.go
  100. 1 1
      integration-cli/docker_cli_links_test.go

+ 54 - 0
api/errdefs/defs.go

@@ -0,0 +1,54 @@
+package errdefs
+
+// ErrNotFound signals that the requested object doesn't exist
+type ErrNotFound interface {
+	NotFound()
+}
+
+// ErrInvalidParameter signals that the user input is invalid
+type ErrInvalidParameter interface {
+	InvalidParameter()
+}
+
+// ErrConflict signals that some internal state conflicts with the requested action and can't be performed.
+// A change in state should be able to clear this error.
+type ErrConflict interface {
+	Conflict()
+}
+
+// ErrUnauthorized is used to signify that the user is not authorized to perform a specific action
+type ErrUnauthorized interface {
+	Unauthorized()
+}
+
+// ErrUnavailable signals that the requested action/subsystem is not available.
+type ErrUnavailable interface {
+	Unavailable()
+}
+
+// ErrForbidden signals that the requested action cannot be performed under any circumstances.
+// When a ErrForbidden is returned, the caller should never retry the action.
+type ErrForbidden interface {
+	Forbidden()
+}
+
+// ErrSystem signals that some internal error occurred.
+// An example of this would be a failed mount request.
+type ErrSystem interface {
+	ErrSystem()
+}
+
+// ErrNotModified signals that an action can't be performed because it's already in the desired state
+type ErrNotModified interface {
+	NotModified()
+}
+
+// ErrNotImplemented signals that the requested action/feature is not implemented on the system as configured.
+type ErrNotImplemented interface {
+	NotImplemented()
+}
+
+// ErrUnknown signals that the kind of error that occurred is not known.
+type ErrUnknown interface {
+	Unknown()
+}

+ 8 - 0
api/errdefs/doc.go

@@ -0,0 +1,8 @@
+// Package errdefs defines a set of error interfaces that packages should use for communicating classes of errors.
+// Errors that cross the package boundary should implement one (and only one) of these interfaces.
+//
+// Packages should not reference these interfaces directly, only implement them.
+// To check if a particular error implements one of these interfaces, there are helper
+// functions provided (e.g. `Is<SomeError>`) which can be used rather than asserting the interfaces directly.
+// If you must assert on these interfaces, be sure to check the causal chain (`err.Cause()`).
+package errdefs

+ 86 - 0
api/errdefs/is.go

@@ -0,0 +1,86 @@
+package errdefs
+
+type causer interface {
+	Cause() error
+}
+
+func getImplementer(err error) error {
+	switch e := err.(type) {
+	case
+		ErrNotFound,
+		ErrInvalidParameter,
+		ErrConflict,
+		ErrUnauthorized,
+		ErrUnavailable,
+		ErrForbidden,
+		ErrSystem,
+		ErrNotModified,
+		ErrNotImplemented,
+		ErrUnknown:
+		return e
+	case causer:
+		return getImplementer(e.Cause())
+	default:
+		return err
+	}
+}
+
+// IsNotFound returns if the passed in error is a ErrNotFound
+func IsNotFound(err error) bool {
+	_, ok := getImplementer(err).(ErrNotFound)
+	return ok
+}
+
+// IsInvalidParameter returns if the passed in error is an ErrInvalidParameter
+func IsInvalidParameter(err error) bool {
+	_, ok := getImplementer(err).(ErrInvalidParameter)
+	return ok
+}
+
+// IsConflict returns if the passed in error is a ErrConflict
+func IsConflict(err error) bool {
+	_, ok := getImplementer(err).(ErrConflict)
+	return ok
+}
+
+// IsUnauthorized returns if the the passed in error is an ErrUnauthorized
+func IsUnauthorized(err error) bool {
+	_, ok := getImplementer(err).(ErrUnauthorized)
+	return ok
+}
+
+// IsUnavailable returns if the passed in error is an ErrUnavailable
+func IsUnavailable(err error) bool {
+	_, ok := getImplementer(err).(ErrUnavailable)
+	return ok
+}
+
+// IsForbidden returns if the passed in error is a ErrForbidden
+func IsForbidden(err error) bool {
+	_, ok := getImplementer(err).(ErrForbidden)
+	return ok
+}
+
+// IsSystem returns if the passed in error is a ErrSystem
+func IsSystem(err error) bool {
+	_, ok := getImplementer(err).(ErrSystem)
+	return ok
+}
+
+// IsNotModified returns if the passed in error is a NotModified error
+func IsNotModified(err error) bool {
+	_, ok := getImplementer(err).(ErrNotModified)
+	return ok
+}
+
+// IsNotImplemented returns if the passed in error is a ErrNotImplemented
+func IsNotImplemented(err error) bool {
+	_, ok := getImplementer(err).(ErrNotImplemented)
+	return ok
+}
+
+// IsUnknown returns if the passed in error is an ErrUnknown
+func IsUnknown(err error) bool {
+	_, ok := getImplementer(err).(ErrUnknown)
+	return ok
+}

+ 0 - 47
api/errors/errors.go

@@ -1,47 +0,0 @@
-package errors
-
-import "net/http"
-
-// apiError is an error wrapper that also
-// holds information about response status codes.
-type apiError struct {
-	error
-	statusCode int
-}
-
-// HTTPErrorStatusCode returns a status code.
-func (e apiError) HTTPErrorStatusCode() int {
-	return e.statusCode
-}
-
-// NewErrorWithStatusCode allows you to associate
-// a specific HTTP Status Code to an error.
-// The server will take that code and set
-// it as the response status.
-func NewErrorWithStatusCode(err error, code int) error {
-	return apiError{err, code}
-}
-
-// NewBadRequestError creates a new API error
-// that has the 400 HTTP status code associated to it.
-func NewBadRequestError(err error) error {
-	return NewErrorWithStatusCode(err, http.StatusBadRequest)
-}
-
-// NewRequestForbiddenError creates a new API error
-// that has the 403 HTTP status code associated to it.
-func NewRequestForbiddenError(err error) error {
-	return NewErrorWithStatusCode(err, http.StatusForbidden)
-}
-
-// NewRequestNotFoundError creates a new API error
-// that has the 404 HTTP status code associated to it.
-func NewRequestNotFoundError(err error) error {
-	return NewErrorWithStatusCode(err, http.StatusNotFound)
-}
-
-// NewRequestConflictError creates a new API error
-// that has the 409 HTTP status code associated to it.
-func NewRequestConflictError(err error) error {
-	return NewErrorWithStatusCode(err, http.StatusConflict)
-}

+ 0 - 64
api/errors/errors_test.go

@@ -1,64 +0,0 @@
-package errors
-
-import (
-	"fmt"
-	"github.com/stretchr/testify/assert"
-	"net/http"
-	"testing"
-)
-
-func newError(errorname string) error {
-
-	return fmt.Errorf("test%v", errorname)
-}
-
-func TestErrors(t *testing.T) {
-	errmsg := newError("apiError")
-	err := apiError{
-		error:      errmsg,
-		statusCode: 0,
-	}
-	assert.Equal(t, err.HTTPErrorStatusCode(), err.statusCode)
-
-	errmsg = newError("ErrorWithStatusCode")
-	errcode := 1
-	serr := NewErrorWithStatusCode(errmsg, errcode)
-	apierr, ok := serr.(apiError)
-	if !ok {
-		t.Fatal("excepted err is apiError type")
-	}
-	assert.Equal(t, errcode, apierr.statusCode)
-
-	errmsg = newError("NewBadRequestError")
-	baderr := NewBadRequestError(errmsg)
-	apierr, ok = baderr.(apiError)
-	if !ok {
-		t.Fatal("excepted err is apiError type")
-	}
-	assert.Equal(t, http.StatusBadRequest, apierr.statusCode)
-
-	errmsg = newError("RequestForbiddenError")
-	ferr := NewRequestForbiddenError(errmsg)
-	apierr, ok = ferr.(apiError)
-	if !ok {
-		t.Fatal("excepted err is apiError type")
-	}
-	assert.Equal(t, http.StatusForbidden, apierr.statusCode)
-
-	errmsg = newError("RequestNotFoundError")
-	nerr := NewRequestNotFoundError(errmsg)
-	apierr, ok = nerr.(apiError)
-	if !ok {
-		t.Fatal("excepted err is apiError type")
-	}
-	assert.Equal(t, http.StatusNotFound, apierr.statusCode)
-
-	errmsg = newError("RequestConflictError")
-	cerr := NewRequestConflictError(errmsg)
-	apierr, ok = cerr.(apiError)
-	if !ok {
-		t.Fatal("excepted err is apiError type")
-	}
-	assert.Equal(t, http.StatusConflict, apierr.statusCode)
-
-}

+ 36 - 42
api/server/httputils/errors.go

@@ -1,9 +1,10 @@
 package httputils
 package httputils
 
 
 import (
 import (
+	"fmt"
 	"net/http"
 	"net/http"
-	"strings"
 
 
+	"github.com/docker/docker/api/errdefs"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/versions"
 	"github.com/docker/docker/api/types/versions"
 	"github.com/gorilla/mux"
 	"github.com/gorilla/mux"
@@ -20,13 +21,8 @@ type httpStatusError interface {
 	HTTPErrorStatusCode() int
 	HTTPErrorStatusCode() int
 }
 }
 
 
-// inputValidationError is an interface
-// that errors generated by invalid
-// inputs can implement to tell the
-// api layer to set a 400 status code
-// in the response.
-type inputValidationError interface {
-	IsValidationError() bool
+type causer interface {
+	Cause() error
 }
 }
 
 
 // GetHTTPErrorStatusCode retrieves status code from error message.
 // GetHTTPErrorStatusCode retrieves status code from error message.
@@ -37,49 +33,44 @@ func GetHTTPErrorStatusCode(err error) int {
 	}
 	}
 
 
 	var statusCode int
 	var statusCode int
-	errMsg := err.Error()
 
 
-	switch e := err.(type) {
-	case httpStatusError:
-		statusCode = e.HTTPErrorStatusCode()
-	case inputValidationError:
+	// Stop right there
+	// Are you sure you should be adding a new error class here? Do one of the existing ones work?
+
+	// Note that the below functions are already checking the error causal chain for matches.
+	switch {
+	case errdefs.IsNotFound(err):
+		statusCode = http.StatusNotFound
+	case errdefs.IsInvalidParameter(err):
 		statusCode = http.StatusBadRequest
 		statusCode = http.StatusBadRequest
+	case errdefs.IsConflict(err):
+		statusCode = http.StatusConflict
+	case errdefs.IsUnauthorized(err):
+		statusCode = http.StatusUnauthorized
+	case errdefs.IsUnavailable(err):
+		statusCode = http.StatusServiceUnavailable
+	case errdefs.IsForbidden(err):
+		statusCode = http.StatusForbidden
+	case errdefs.IsNotModified(err):
+		statusCode = http.StatusNotModified
+	case errdefs.IsNotImplemented(err):
+		statusCode = http.StatusNotImplemented
+	case errdefs.IsSystem(err) || errdefs.IsUnknown(err):
+		statusCode = http.StatusInternalServerError
 	default:
 	default:
 		statusCode = statusCodeFromGRPCError(err)
 		statusCode = statusCodeFromGRPCError(err)
 		if statusCode != http.StatusInternalServerError {
 		if statusCode != http.StatusInternalServerError {
 			return statusCode
 			return statusCode
 		}
 		}
 
 
-		// FIXME: this is brittle and should not be necessary, but we still need to identify if
-		// there are errors falling back into this logic.
-		// If we need to differentiate between different possible error types,
-		// we should create appropriate error types that implement the httpStatusError interface.
-		errStr := strings.ToLower(errMsg)
-
-		for _, status := range []struct {
-			keyword string
-			code    int
-		}{
-			{"not found", http.StatusNotFound},
-			{"cannot find", http.StatusNotFound},
-			{"no such", http.StatusNotFound},
-			{"bad parameter", http.StatusBadRequest},
-			{"no command", http.StatusBadRequest},
-			{"conflict", http.StatusConflict},
-			{"impossible", http.StatusNotAcceptable},
-			{"wrong login/password", http.StatusUnauthorized},
-			{"unauthorized", http.StatusUnauthorized},
-			{"hasn't been activated", http.StatusForbidden},
-			{"this node", http.StatusServiceUnavailable},
-			{"needs to be unlocked", http.StatusServiceUnavailable},
-			{"certificates have expired", http.StatusServiceUnavailable},
-			{"repository does not exist", http.StatusNotFound},
-		} {
-			if strings.Contains(errStr, status.keyword) {
-				statusCode = status.code
-				break
-			}
+		if e, ok := err.(causer); ok {
+			return GetHTTPErrorStatusCode(e.Cause())
 		}
 		}
+
+		logrus.WithFields(logrus.Fields{
+			"module":     "api",
+			"error_type": fmt.Sprintf("%T", err),
+		}).Debugf("FIXME: Got an API for which error does not match any expected type!!!: %+v", err)
 	}
 	}
 
 
 	if statusCode == 0 {
 	if statusCode == 0 {
@@ -133,6 +124,9 @@ func statusCodeFromGRPCError(err error) int {
 	case codes.Unavailable: // code 14
 	case codes.Unavailable: // code 14
 		return http.StatusServiceUnavailable
 		return http.StatusServiceUnavailable
 	default:
 	default:
+		if e, ok := err.(causer); ok {
+			return statusCodeFromGRPCError(e.Cause())
+		}
 		// codes.Canceled(1)
 		// codes.Canceled(1)
 		// codes.Unknown(2)
 		// codes.Unknown(2)
 		// codes.DeadlineExceeded(4)
 		// codes.DeadlineExceeded(4)

+ 16 - 8
api/server/httputils/form.go

@@ -1,7 +1,6 @@
 package httputils
 package httputils
 
 
 import (
 import (
-	"errors"
 	"net/http"
 	"net/http"
 	"path/filepath"
 	"path/filepath"
 	"strconv"
 	"strconv"
@@ -49,6 +48,16 @@ type ArchiveOptions struct {
 	Path string
 	Path string
 }
 }
 
 
+type badParameterError struct {
+	param string
+}
+
+func (e badParameterError) Error() string {
+	return "bad parameter: " + e.param + "cannot be empty"
+}
+
+func (e badParameterError) InvalidParameter() {}
+
 // ArchiveFormValues parses form values and turns them into ArchiveOptions.
 // ArchiveFormValues parses form values and turns them into ArchiveOptions.
 // It fails if the archive name and path are not in the request.
 // It fails if the archive name and path are not in the request.
 func ArchiveFormValues(r *http.Request, vars map[string]string) (ArchiveOptions, error) {
 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,
 	}
 	}
 
 
 	name := vars["name"]
 	name := vars["name"]
-	path := filepath.FromSlash(r.Form.Get("path"))
-
-	switch {
-	case name == "":
-		return ArchiveOptions{}, errors.New("bad parameter: 'name' cannot be empty")
-	case path == "":
-		return ArchiveOptions{}, errors.New("bad parameter: 'path' cannot be empty")
+	if name == "" {
+		return ArchiveOptions{}, badParameterError{"name"}
 	}
 	}
 
 
+	path := filepath.FromSlash(r.Form.Get("path"))
+	if path == "" {
+		return ArchiveOptions{}, badParameterError{"path"}
+	}
 	return ArchiveOptions{name, path}, nil
 	return ArchiveOptions{name, path}, nil
 }
 }

+ 17 - 3
api/server/httputils/httputils.go

@@ -1,12 +1,12 @@
 package httputils
 package httputils
 
 
 import (
 import (
-	"fmt"
 	"io"
 	"io"
 	"mime"
 	"mime"
 	"net/http"
 	"net/http"
 	"strings"
 	"strings"
 
 
+	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
@@ -43,6 +43,20 @@ func CloseStreams(streams ...interface{}) {
 	}
 	}
 }
 }
 
 
+type validationError struct {
+	cause error
+}
+
+func (e validationError) Error() string {
+	return e.cause.Error()
+}
+
+func (e validationError) Cause() error {
+	return e.cause
+}
+
+func (e validationError) InvalidParameter() {}
+
 // CheckForJSON makes sure that the request's Content-Type is application/json.
 // CheckForJSON makes sure that the request's Content-Type is application/json.
 func CheckForJSON(r *http.Request) error {
 func CheckForJSON(r *http.Request) error {
 	ct := r.Header.Get("Content-Type")
 	ct := r.Header.Get("Content-Type")
@@ -58,7 +72,7 @@ func CheckForJSON(r *http.Request) error {
 	if matchesContentType(ct, "application/json") {
 	if matchesContentType(ct, "application/json") {
 		return nil
 		return nil
 	}
 	}
-	return fmt.Errorf("Content-Type specified (%s) must be 'application/json'", ct)
+	return validationError{errors.Errorf("Content-Type specified (%s) must be 'application/json'", ct)}
 }
 }
 
 
 // ParseForm ensures the request form is parsed even with invalid content types.
 // ParseForm ensures the request form is parsed even with invalid content types.
@@ -68,7 +82,7 @@ func ParseForm(r *http.Request) error {
 		return nil
 		return nil
 	}
 	}
 	if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
 	if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
-		return err
+		return validationError{err}
 	}
 	}
 	return nil
 	return nil
 }
 }

+ 11 - 2
api/server/middleware/version.go

@@ -5,7 +5,6 @@ import (
 	"net/http"
 	"net/http"
 	"runtime"
 	"runtime"
 
 
-	"github.com/docker/docker/api/errors"
 	"github.com/docker/docker/api/types/versions"
 	"github.com/docker/docker/api/types/versions"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
@@ -28,6 +27,16 @@ func NewVersionMiddleware(s, d, m string) VersionMiddleware {
 	}
 	}
 }
 }
 
 
+type versionUnsupportedError struct {
+	version, minVersion string
+}
+
+func (e versionUnsupportedError) Error() string {
+	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)
+}
+
+func (e versionUnsupportedError) InvalidParameter() {}
+
 // WrapHandler returns a new handler function wrapping the previous one in the request chain.
 // WrapHandler returns a new handler function wrapping the previous one in the request chain.
 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 {
 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 {
 	return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	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.
 		}
 		}
 
 
 		if versions.LessThan(apiVersion, v.minVersion) {
 		if versions.LessThan(apiVersion, v.minVersion) {
-			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))
+			return versionUnsupportedError{apiVersion, v.minVersion}
 		}
 		}
 
 
 		header := fmt.Sprintf("Docker/%s (%s)", v.serverVersion, runtime.GOOS)
 		header := fmt.Sprintf("Docker/%s (%s)", v.serverVersion, runtime.GOOS)

+ 24 - 8
api/server/router/build/build_routes.go

@@ -12,7 +12,6 @@ import (
 	"strings"
 	"strings"
 	"sync"
 	"sync"
 
 
-	apierrors "github.com/docker/docker/api/errors"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/backend"
 	"github.com/docker/docker/api/types/backend"
@@ -27,6 +26,14 @@ import (
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
 
 
+type invalidIsolationError string
+
+func (e invalidIsolationError) Error() string {
+	return fmt.Sprintf("Unsupported isolation: %q", string(e))
+}
+
+func (e invalidIsolationError) InvalidParameter() {}
+
 func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBuildOptions, error) {
 func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBuildOptions, error) {
 	version := httputils.VersionFromContext(ctx)
 	version := httputils.VersionFromContext(ctx)
 	options := &types.ImageBuildOptions{}
 	options := &types.ImageBuildOptions{}
@@ -71,20 +78,20 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui
 
 
 	if i := container.Isolation(r.FormValue("isolation")); i != "" {
 	if i := container.Isolation(r.FormValue("isolation")); i != "" {
 		if !container.Isolation.IsValid(i) {
 		if !container.Isolation.IsValid(i) {
-			return nil, fmt.Errorf("Unsupported isolation: %q", i)
+			return nil, invalidIsolationError(i)
 		}
 		}
 		options.Isolation = i
 		options.Isolation = i
 	}
 	}
 
 
 	if runtime.GOOS != "windows" && options.SecurityOpt != nil {
 	if runtime.GOOS != "windows" && options.SecurityOpt != nil {
-		return nil, fmt.Errorf("The daemon on this platform does not support setting security options on build")
+		return nil, validationError{fmt.Errorf("The daemon on this platform does not support setting security options on build")}
 	}
 	}
 
 
 	var buildUlimits = []*units.Ulimit{}
 	var buildUlimits = []*units.Ulimit{}
 	ulimitsJSON := r.FormValue("ulimits")
 	ulimitsJSON := r.FormValue("ulimits")
 	if ulimitsJSON != "" {
 	if ulimitsJSON != "" {
 		if err := json.Unmarshal([]byte(ulimitsJSON), &buildUlimits); err != nil {
 		if err := json.Unmarshal([]byte(ulimitsJSON), &buildUlimits); err != nil {
-			return nil, err
+			return nil, errors.Wrap(validationError{err}, "error reading ulimit settings")
 		}
 		}
 		options.Ulimits = buildUlimits
 		options.Ulimits = buildUlimits
 	}
 	}
@@ -105,7 +112,7 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui
 	if buildArgsJSON != "" {
 	if buildArgsJSON != "" {
 		var buildArgs = map[string]*string{}
 		var buildArgs = map[string]*string{}
 		if err := json.Unmarshal([]byte(buildArgsJSON), &buildArgs); err != nil {
 		if err := json.Unmarshal([]byte(buildArgsJSON), &buildArgs); err != nil {
-			return nil, err
+			return nil, errors.Wrap(validationError{err}, "error reading build args")
 		}
 		}
 		options.BuildArgs = buildArgs
 		options.BuildArgs = buildArgs
 	}
 	}
@@ -114,7 +121,7 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui
 	if labelsJSON != "" {
 	if labelsJSON != "" {
 		var labels = map[string]string{}
 		var labels = map[string]string{}
 		if err := json.Unmarshal([]byte(labelsJSON), &labels); err != nil {
 		if err := json.Unmarshal([]byte(labelsJSON), &labels); err != nil {
-			return nil, err
+			return nil, errors.Wrap(validationError{err}, "error reading labels")
 		}
 		}
 		options.Labels = labels
 		options.Labels = labels
 	}
 	}
@@ -140,6 +147,16 @@ func (br *buildRouter) postPrune(ctx context.Context, w http.ResponseWriter, r *
 	return httputils.WriteJSON(w, http.StatusOK, report)
 	return httputils.WriteJSON(w, http.StatusOK, report)
 }
 }
 
 
+type validationError struct {
+	cause error
+}
+
+func (e validationError) Error() string {
+	return e.cause.Error()
+}
+
+func (e validationError) InvalidParameter() {}
+
 func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	var (
 	var (
 		notVerboseBuffer = bytes.NewBuffer(nil)
 		notVerboseBuffer = bytes.NewBuffer(nil)
@@ -173,8 +190,7 @@ func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *
 	buildOptions.AuthConfigs = getAuthConfigs(r.Header)
 	buildOptions.AuthConfigs = getAuthConfigs(r.Header)
 
 
 	if buildOptions.Squash && !br.daemon.HasExperimental() {
 	if buildOptions.Squash && !br.daemon.HasExperimental() {
-		return apierrors.NewBadRequestError(
-			errors.New("squash is only supported with experimental mode"))
+		return validationError{errors.New("squash is only supported with experimental mode")}
 	}
 	}
 
 
 	out := io.Writer(output)
 	out := io.Writer(output)

+ 1 - 1
api/server/router/container/backend.go

@@ -51,7 +51,7 @@ type stateBackend interface {
 type monitorBackend interface {
 type monitorBackend interface {
 	ContainerChanges(name string) ([]archive.Change, error)
 	ContainerChanges(name string) ([]archive.Change, error)
 	ContainerInspect(name string, size bool, version string) (interface{}, error)
 	ContainerInspect(name string, size bool, version string) (interface{}, error)
-	ContainerLogs(ctx context.Context, name string, config *types.ContainerLogsOptions) (<-chan *backend.LogMessage, error)
+	ContainerLogs(ctx context.Context, name string, config *types.ContainerLogsOptions) (msgs <-chan *backend.LogMessage, tty bool, err error)
 	ContainerStats(ctx context.Context, name string, config *backend.ContainerStatsConfig) error
 	ContainerStats(ctx context.Context, name string, config *backend.ContainerStatsConfig) error
 	ContainerTop(name string, psArgs string) (*container.ContainerTopOKBody, error)
 	ContainerTop(name string, psArgs string) (*container.ContainerTopOKBody, error)
 
 

+ 9 - 3
api/server/router/container/container.go

@@ -6,13 +6,19 @@ import (
 )
 )
 
 
 type validationError struct {
 type validationError struct {
-	error
+	cause error
 }
 }
 
 
-func (validationError) IsValidationError() bool {
-	return true
+func (e validationError) Error() string {
+	return e.cause.Error()
 }
 }
 
 
+func (e validationError) Cause() error {
+	return e.cause
+}
+
+func (e validationError) InvalidParameter() {}
+
 // containerRouter is a router to talk with the container controller
 // containerRouter is a router to talk with the container controller
 type containerRouter struct {
 type containerRouter struct {
 	backend Backend
 	backend Backend

+ 22 - 29
api/server/router/container/container_routes.go

@@ -8,7 +8,7 @@ import (
 	"strconv"
 	"strconv"
 	"syscall"
 	"syscall"
 
 
-	"github.com/docker/docker/api"
+	"github.com/docker/docker/api/errdefs"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/backend"
 	"github.com/docker/docker/api/types/backend"
@@ -18,6 +18,7 @@ import (
 	containerpkg "github.com/docker/docker/container"
 	containerpkg "github.com/docker/docker/container"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/ioutils"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/signal"
+	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 	"golang.org/x/net/websocket"
 	"golang.org/x/net/websocket"
@@ -87,7 +88,7 @@ func (s *containerRouter) getContainersLogs(ctx context.Context, w http.Response
 	// with the appropriate status code.
 	// with the appropriate status code.
 	stdout, stderr := httputils.BoolValue(r, "stdout"), httputils.BoolValue(r, "stderr")
 	stdout, stderr := httputils.BoolValue(r, "stdout"), httputils.BoolValue(r, "stderr")
 	if !(stdout || stderr) {
 	if !(stdout || stderr) {
-		return fmt.Errorf("Bad parameters: you must choose at least one stream")
+		return validationError{errors.New("Bad parameters: you must choose at least one stream")}
 	}
 	}
 
 
 	containerName := vars["name"]
 	containerName := vars["name"]
@@ -101,19 +102,7 @@ func (s *containerRouter) getContainersLogs(ctx context.Context, w http.Response
 		Details:    httputils.BoolValue(r, "details"),
 		Details:    httputils.BoolValue(r, "details"),
 	}
 	}
 
 
-	// doesn't matter what version the client is on, we're using this internally only
-	// also do we need size? i'm thinking no we don't
-	raw, err := s.backend.ContainerInspect(containerName, false, api.DefaultVersion)
-	if err != nil {
-		return err
-	}
-	container, ok := raw.(*types.ContainerJSON)
-	if !ok {
-		// %T prints the type. handy!
-		return fmt.Errorf("expected container to be *types.ContainerJSON but got %T", raw)
-	}
-
-	msgs, err := s.backend.ContainerLogs(ctx, containerName, logsConfig)
+	msgs, tty, err := s.backend.ContainerLogs(ctx, containerName, logsConfig)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -122,7 +111,7 @@ func (s *containerRouter) getContainersLogs(ctx context.Context, w http.Response
 	// this is the point of no return for writing a response. once we call
 	// this is the point of no return for writing a response. once we call
 	// WriteLogStream, the response has been started and errors will be
 	// WriteLogStream, the response has been started and errors will be
 	// returned in band by WriteLogStream
 	// returned in band by WriteLogStream
-	httputils.WriteLogStream(ctx, w, msgs, logsConfig, !container.Config.Tty)
+	httputils.WriteLogStream(ctx, w, msgs, logsConfig, !tty)
 	return nil
 	return nil
 }
 }
 
 
@@ -130,6 +119,14 @@ func (s *containerRouter) getContainersExport(ctx context.Context, w http.Respon
 	return s.backend.ContainerExport(vars["name"], w)
 	return s.backend.ContainerExport(vars["name"], w)
 }
 }
 
 
+type bodyOnStartError struct{}
+
+func (bodyOnStartError) Error() string {
+	return "starting container with non-empty request body was deprecated since API v1.22 and removed in v1.24"
+}
+
+func (bodyOnStartError) InvalidParameter() {}
+
 func (s *containerRouter) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func (s *containerRouter) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	// If contentLength is -1, we can assumed chunked encoding
 	// If contentLength is -1, we can assumed chunked encoding
 	// or more technically that the length is unknown
 	// or more technically that the length is unknown
@@ -143,7 +140,7 @@ func (s *containerRouter) postContainersStart(ctx context.Context, w http.Respon
 	// A non-nil json object is at least 7 characters.
 	// A non-nil json object is at least 7 characters.
 	if r.ContentLength > 7 || r.ContentLength == -1 {
 	if r.ContentLength > 7 || r.ContentLength == -1 {
 		if versions.GreaterThanOrEqualTo(version, "1.24") {
 		if versions.GreaterThanOrEqualTo(version, "1.24") {
-			return validationError{fmt.Errorf("starting container with non-empty request body was deprecated since v1.10 and removed in v1.12")}
+			return bodyOnStartError{}
 		}
 		}
 
 
 		if err := httputils.CheckForJSON(r); err != nil {
 		if err := httputils.CheckForJSON(r); err != nil {
@@ -193,10 +190,6 @@ func (s *containerRouter) postContainersStop(ctx context.Context, w http.Respons
 	return nil
 	return nil
 }
 }
 
 
-type errContainerIsRunning interface {
-	ContainerIsRunning() bool
-}
-
 func (s *containerRouter) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func (s *containerRouter) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := httputils.ParseForm(r); err != nil {
 	if err := httputils.ParseForm(r); err != nil {
 		return err
 		return err
@@ -209,14 +202,14 @@ func (s *containerRouter) postContainersKill(ctx context.Context, w http.Respons
 	if sigStr := r.Form.Get("signal"); sigStr != "" {
 	if sigStr := r.Form.Get("signal"); sigStr != "" {
 		var err error
 		var err error
 		if sig, err = signal.ParseSignal(sigStr); err != nil {
 		if sig, err = signal.ParseSignal(sigStr); err != nil {
-			return err
+			return validationError{err}
 		}
 		}
 	}
 	}
 
 
 	if err := s.backend.ContainerKill(name, uint64(sig)); err != nil {
 	if err := s.backend.ContainerKill(name, uint64(sig)); err != nil {
 		var isStopped bool
 		var isStopped bool
-		if e, ok := err.(errContainerIsRunning); ok {
-			isStopped = !e.ContainerIsRunning()
+		if errdefs.IsConflict(err) {
+			isStopped = true
 		}
 		}
 
 
 		// Return error that's not caused because the container is stopped.
 		// 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
 		// to keep backwards compatibility.
 		// to keep backwards compatibility.
 		version := httputils.VersionFromContext(ctx)
 		version := httputils.VersionFromContext(ctx)
 		if versions.GreaterThanOrEqualTo(version, "1.20") || !isStopped {
 		if versions.GreaterThanOrEqualTo(version, "1.20") || !isStopped {
-			return fmt.Errorf("Cannot kill container %s: %v", name, err)
+			return errors.Wrapf(err, "Cannot kill container: %s", name)
 		}
 		}
 	}
 	}
 
 
@@ -458,11 +451,11 @@ func (s *containerRouter) postContainersResize(ctx context.Context, w http.Respo
 
 
 	height, err := strconv.Atoi(r.Form.Get("h"))
 	height, err := strconv.Atoi(r.Form.Get("h"))
 	if err != nil {
 	if err != nil {
-		return err
+		return validationError{err}
 	}
 	}
 	width, err := strconv.Atoi(r.Form.Get("w"))
 	width, err := strconv.Atoi(r.Form.Get("w"))
 	if err != nil {
 	if err != nil {
-		return err
+		return validationError{err}
 	}
 	}
 
 
 	return s.backend.ContainerResize(vars["name"], height, width)
 	return s.backend.ContainerResize(vars["name"], height, width)
@@ -480,7 +473,7 @@ func (s *containerRouter) postContainersAttach(ctx context.Context, w http.Respo
 
 
 	hijacker, ok := w.(http.Hijacker)
 	hijacker, ok := w.(http.Hijacker)
 	if !ok {
 	if !ok {
-		return fmt.Errorf("error attaching to container %s, hijack connection missing", containerName)
+		return validationError{errors.Errorf("error attaching to container %s, hijack connection missing", containerName)}
 	}
 	}
 
 
 	setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) {
 	setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) {
@@ -597,7 +590,7 @@ func (s *containerRouter) postContainersPrune(ctx context.Context, w http.Respon
 
 
 	pruneFilters, err := filters.FromParam(r.Form.Get("filters"))
 	pruneFilters, err := filters.FromParam(r.Form.Get("filters"))
 	if err != nil {
 	if err != nil {
-		return err
+		return validationError{err}
 	}
 	}
 
 
 	pruneReport, err := s.backend.ContainersPrune(ctx, pruneFilters)
 	pruneReport, err := s.backend.ContainersPrune(ctx, pruneFilters)

+ 9 - 11
api/server/router/container/copy.go

@@ -3,11 +3,8 @@ package container
 import (
 import (
 	"encoding/base64"
 	"encoding/base64"
 	"encoding/json"
 	"encoding/json"
-	"fmt"
 	"io"
 	"io"
 	"net/http"
 	"net/http"
-	"os"
-	"strings"
 
 
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
@@ -15,6 +12,14 @@ import (
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
 
 
+type pathError struct{}
+
+func (pathError) Error() string {
+	return "Path cannot be empty"
+}
+
+func (pathError) InvalidParameter() {}
+
 // postContainersCopy is deprecated in favor of getContainersArchive.
 // postContainersCopy is deprecated in favor of getContainersArchive.
 func (s *containerRouter) postContainersCopy(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func (s *containerRouter) postContainersCopy(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	// Deprecated since 1.8, Errors out since 1.12
 	// Deprecated since 1.8, Errors out since 1.12
@@ -33,18 +38,11 @@ func (s *containerRouter) postContainersCopy(ctx context.Context, w http.Respons
 	}
 	}
 
 
 	if cfg.Resource == "" {
 	if cfg.Resource == "" {
-		return fmt.Errorf("Path cannot be empty")
+		return pathError{}
 	}
 	}
 
 
 	data, err := s.backend.ContainerCopy(vars["name"], cfg.Resource)
 	data, err := s.backend.ContainerCopy(vars["name"], cfg.Resource)
 	if err != nil {
 	if err != nil {
-		if strings.Contains(strings.ToLower(err.Error()), "no such container") {
-			w.WriteHeader(http.StatusNotFound)
-			return nil
-		}
-		if os.IsNotExist(err) {
-			return fmt.Errorf("Could not find the file %s in container %s", cfg.Resource, vars["name"])
-		}
 		return err
 		return err
 	}
 	}
 	defer data.Close()
 	defer data.Close()

+ 11 - 3
api/server/router/container/exec.go

@@ -24,6 +24,14 @@ func (s *containerRouter) getExecByID(ctx context.Context, w http.ResponseWriter
 	return httputils.WriteJSON(w, http.StatusOK, eConfig)
 	return httputils.WriteJSON(w, http.StatusOK, eConfig)
 }
 }
 
 
+type execCommandError struct{}
+
+func (execCommandError) Error() string {
+	return "No exec command specified"
+}
+
+func (execCommandError) InvalidParameter() {}
+
 func (s *containerRouter) postContainerExecCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func (s *containerRouter) postContainerExecCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := httputils.ParseForm(r); err != nil {
 	if err := httputils.ParseForm(r); err != nil {
 		return err
 		return err
@@ -39,7 +47,7 @@ func (s *containerRouter) postContainerExecCreate(ctx context.Context, w http.Re
 	}
 	}
 
 
 	if len(execConfig.Cmd) == 0 {
 	if len(execConfig.Cmd) == 0 {
-		return fmt.Errorf("No exec command specified")
+		return execCommandError{}
 	}
 	}
 
 
 	// Register an instance of Exec in container.
 	// Register an instance of Exec in container.
@@ -129,11 +137,11 @@ func (s *containerRouter) postContainerExecResize(ctx context.Context, w http.Re
 	}
 	}
 	height, err := strconv.Atoi(r.Form.Get("h"))
 	height, err := strconv.Atoi(r.Form.Get("h"))
 	if err != nil {
 	if err != nil {
-		return err
+		return validationError{err}
 	}
 	}
 	width, err := strconv.Atoi(r.Form.Get("w"))
 	width, err := strconv.Atoi(r.Form.Get("w"))
 	if err != nil {
 	if err != nil {
-		return err
+		return validationError{err}
 	}
 	}
 
 
 	return s.backend.ContainerExecResize(vars["name"], height, width)
 	return s.backend.ContainerExecResize(vars["name"], height, width)

+ 9 - 7
api/server/router/experimental.go

@@ -1,19 +1,13 @@
 package router
 package router
 
 
 import (
 import (
-	"errors"
 	"net/http"
 	"net/http"
 
 
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 
 
-	apierrors "github.com/docker/docker/api/errors"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/server/httputils"
 )
 )
 
 
-var (
-	errExperimentalFeature = errors.New("This experimental feature is disabled by default. Start the Docker daemon in experimental mode in order to enable it.")
-)
-
 // ExperimentalRoute defines an experimental API route that can be enabled or disabled.
 // ExperimentalRoute defines an experimental API route that can be enabled or disabled.
 type ExperimentalRoute interface {
 type ExperimentalRoute interface {
 	Route
 	Route
@@ -39,8 +33,16 @@ func (r *experimentalRoute) Disable() {
 	r.handler = experimentalHandler
 	r.handler = experimentalHandler
 }
 }
 
 
+type notImplementedError struct{}
+
+func (notImplementedError) Error() string {
+	return "This experimental feature is disabled by default. Start the Docker daemon in experimental mode in order to enable it."
+}
+
+func (notImplementedError) NotImplemented() {}
+
 func experimentalHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func experimentalHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
-	return apierrors.NewErrorWithStatusCode(errExperimentalFeature, http.StatusNotImplemented)
+	return notImplementedError{}
 }
 }
 
 
 // Handler returns returns the APIFunc to let the server wrap it in middlewares.
 // Handler returns returns the APIFunc to let the server wrap it in middlewares.

+ 25 - 3
api/server/router/image/image_routes.go

@@ -3,7 +3,6 @@ package image
 import (
 import (
 	"encoding/base64"
 	"encoding/base64"
 	"encoding/json"
 	"encoding/json"
-	"fmt"
 	"io"
 	"io"
 	"net/http"
 	"net/http"
 	"runtime"
 	"runtime"
@@ -20,6 +19,7 @@ import (
 	"github.com/docker/docker/pkg/streamformatter"
 	"github.com/docker/docker/pkg/streamformatter"
 	"github.com/docker/docker/pkg/system"
 	"github.com/docker/docker/pkg/system"
 	"github.com/docker/docker/registry"
 	"github.com/docker/docker/registry"
+	"github.com/pkg/errors"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
 
 
@@ -161,6 +161,20 @@ func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWrite
 	return nil
 	return nil
 }
 }
 
 
+type validationError struct {
+	cause error
+}
+
+func (e validationError) Error() string {
+	return e.cause.Error()
+}
+
+func (e validationError) Cause() error {
+	return e.cause
+}
+
+func (validationError) InvalidParameter() {}
+
 func (s *imageRouter) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func (s *imageRouter) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	metaHeaders := map[string][]string{}
 	metaHeaders := map[string][]string{}
 	for k, v := range r.Header {
 	for k, v := range r.Header {
@@ -184,7 +198,7 @@ func (s *imageRouter) postImagesPush(ctx context.Context, w http.ResponseWriter,
 	} else {
 	} else {
 		// the old format is supported for compatibility if there was no authConfig header
 		// the old format is supported for compatibility if there was no authConfig header
 		if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
 		if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
-			return fmt.Errorf("Bad parameters and missing X-Registry-Auth: %v", err)
+			return errors.Wrap(validationError{err}, "Bad parameters and missing X-Registry-Auth")
 		}
 		}
 	}
 	}
 
 
@@ -246,6 +260,14 @@ func (s *imageRouter) postImagesLoad(ctx context.Context, w http.ResponseWriter,
 	return nil
 	return nil
 }
 }
 
 
+type missingImageError struct{}
+
+func (missingImageError) Error() string {
+	return "image name cannot be blank"
+}
+
+func (missingImageError) InvalidParameter() {}
+
 func (s *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func (s *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := httputils.ParseForm(r); err != nil {
 	if err := httputils.ParseForm(r); err != nil {
 		return err
 		return err
@@ -254,7 +276,7 @@ func (s *imageRouter) deleteImages(ctx context.Context, w http.ResponseWriter, r
 	name := vars["name"]
 	name := vars["name"]
 
 
 	if strings.TrimSpace(name) == "" {
 	if strings.TrimSpace(name) == "" {
-		return fmt.Errorf("image name cannot be blank")
+		return missingImageError{}
 	}
 	}
 
 
 	force := httputils.BoolValue(r, "force")
 	force := httputils.BoolValue(r, "force")

+ 9 - 3
api/server/router/network/filter.go

@@ -1,8 +1,6 @@
 package network
 package network
 
 
 import (
 import (
-	"fmt"
-
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/runconfig"
@@ -24,11 +22,19 @@ func filterNetworkByType(nws []types.NetworkResource, netType string) ([]types.N
 			}
 			}
 		}
 		}
 	default:
 	default:
-		return nil, fmt.Errorf("Invalid filter: 'type'='%s'", netType)
+		return nil, invalidFilter(netType)
 	}
 	}
 	return retNws, nil
 	return retNws, nil
 }
 }
 
 
+type invalidFilter string
+
+func (e invalidFilter) Error() string {
+	return "Invalid filter: 'type'='" + string(e) + "'"
+}
+
+func (e invalidFilter) InvalidParameter() {}
+
 // filterNetworks filters network list according to user specified filter
 // filterNetworks filters network list according to user specified filter
 // and returns user chosen networks
 // and returns user chosen networks
 func filterNetworks(nws []types.NetworkResource, filter filters.Args) ([]types.NetworkResource, error) {
 func filterNetworks(nws []types.NetworkResource, filter filters.Args) ([]types.NetworkResource, error) {

+ 22 - 6
api/server/router/network/network_routes.go

@@ -2,14 +2,12 @@ package network
 
 
 import (
 import (
 	"encoding/json"
 	"encoding/json"
-	"fmt"
 	"net/http"
 	"net/http"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 
 
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 
 
-	"github.com/docker/docker/api/errors"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/filters"
@@ -18,6 +16,7 @@ import (
 	"github.com/docker/libnetwork"
 	"github.com/docker/libnetwork"
 	netconst "github.com/docker/libnetwork/datastore"
 	netconst "github.com/docker/libnetwork/datastore"
 	"github.com/docker/libnetwork/networkdb"
 	"github.com/docker/libnetwork/networkdb"
+	"github.com/pkg/errors"
 )
 )
 
 
 var (
 var (
@@ -83,6 +82,24 @@ SKIP:
 	return httputils.WriteJSON(w, http.StatusOK, list)
 	return httputils.WriteJSON(w, http.StatusOK, list)
 }
 }
 
 
+type invalidRequestError struct {
+	cause error
+}
+
+func (e invalidRequestError) Error() string {
+	return e.cause.Error()
+}
+
+func (e invalidRequestError) InvalidParameter() {}
+
+type ambigousResultsError string
+
+func (e ambigousResultsError) Error() string {
+	return "network " + string(e) + " is ambiguous"
+}
+
+func (ambigousResultsError) InvalidParameter() {}
+
 func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := httputils.ParseForm(r); err != nil {
 	if err := httputils.ParseForm(r); err != nil {
 		return err
 		return err
@@ -95,8 +112,7 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
 	)
 	)
 	if v := r.URL.Query().Get("verbose"); v != "" {
 	if v := r.URL.Query().Get("verbose"); v != "" {
 		if verbose, err = strconv.ParseBool(v); err != nil {
 		if verbose, err = strconv.ParseBool(v); err != nil {
-			err = fmt.Errorf("invalid value for verbose: %s", v)
-			return errors.NewBadRequestError(err)
+			return errors.Wrapf(invalidRequestError{err}, "invalid value for verbose: %s", v)
 		}
 		}
 	}
 	}
 	scope := r.URL.Query().Get("scope")
 	scope := r.URL.Query().Get("scope")
@@ -177,7 +193,7 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
 		}
 		}
 	}
 	}
 	if len(listByFullName) > 1 {
 	if len(listByFullName) > 1 {
-		return fmt.Errorf("network %s is ambiguous (%d matches found based on name)", term, len(listByFullName))
+		return errors.Wrapf(ambigousResultsError(term), "%d matches found based on name", len(listByFullName))
 	}
 	}
 
 
 	// Find based on partial ID, returns true only if no duplicates
 	// 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
 		}
 		}
 	}
 	}
 	if len(listByPartialID) > 1 {
 	if len(listByPartialID) > 1 {
-		return fmt.Errorf("network %s is ambiguous (%d matches found based on ID prefix)", term, len(listByPartialID))
+		return errors.Wrapf(ambigousResultsError(term), "%d matches found based on ID prefix", len(listByPartialID))
 	}
 	}
 
 
 	return libnetwork.ErrNoSuchNetwork(term)
 	return libnetwork.ErrNoSuchNetwork(term)

+ 15 - 2
api/server/router/session/session_routes.go

@@ -3,14 +3,27 @@ package session
 import (
 import (
 	"net/http"
 	"net/http"
 
 
-	apierrors "github.com/docker/docker/api/errors"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
 
 
+type invalidRequest struct {
+	cause error
+}
+
+func (e invalidRequest) Error() string {
+	return e.cause.Error()
+}
+
+func (e invalidRequest) Cause() error {
+	return e.cause
+}
+
+func (e invalidRequest) InvalidParameter() {}
+
 func (sr *sessionRouter) startSession(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func (sr *sessionRouter) startSession(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	err := sr.backend.HandleHTTPRequest(ctx, w, r)
 	err := sr.backend.HandleHTTPRequest(ctx, w, r)
 	if err != nil {
 	if err != nil {
-		return apierrors.NewBadRequestError(err)
+		return invalidRequest{err}
 	}
 	}
 	return nil
 	return nil
 }
 }

+ 27 - 13
api/server/router/swarm/cluster_routes.go

@@ -6,13 +6,13 @@ import (
 	"net/http"
 	"net/http"
 	"strconv"
 	"strconv"
 
 
-	"github.com/docker/docker/api/errors"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/server/httputils"
 	basictypes "github.com/docker/docker/api/types"
 	basictypes "github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/backend"
 	"github.com/docker/docker/api/types/backend"
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/filters"
 	types "github.com/docker/docker/api/types/swarm"
 	types "github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/api/types/versions"
 	"github.com/docker/docker/api/types/versions"
+	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
@@ -57,6 +57,20 @@ func (sr *swarmRouter) inspectCluster(ctx context.Context, w http.ResponseWriter
 	return httputils.WriteJSON(w, http.StatusOK, swarm)
 	return httputils.WriteJSON(w, http.StatusOK, swarm)
 }
 }
 
 
+type invalidRequestError struct {
+	err error
+}
+
+func (e invalidRequestError) Error() string {
+	return e.err.Error()
+}
+
+func (e invalidRequestError) Cause() error {
+	return e.err
+}
+
+func (e invalidRequestError) InvalidParameter() {}
+
 func (sr *swarmRouter) updateCluster(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func (sr *swarmRouter) updateCluster(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	var swarm types.Spec
 	var swarm types.Spec
 	if err := json.NewDecoder(r.Body).Decode(&swarm); err != nil {
 	if err := json.NewDecoder(r.Body).Decode(&swarm); err != nil {
@@ -67,7 +81,7 @@ func (sr *swarmRouter) updateCluster(ctx context.Context, w http.ResponseWriter,
 	version, err := strconv.ParseUint(rawVersion, 10, 64)
 	version, err := strconv.ParseUint(rawVersion, 10, 64)
 	if err != nil {
 	if err != nil {
 		err := fmt.Errorf("invalid swarm version '%s': %v", rawVersion, err)
 		err := fmt.Errorf("invalid swarm version '%s': %v", rawVersion, err)
-		return errors.NewBadRequestError(err)
+		return invalidRequestError{err}
 	}
 	}
 
 
 	var flags types.UpdateFlags
 	var flags types.UpdateFlags
@@ -76,7 +90,7 @@ func (sr *swarmRouter) updateCluster(ctx context.Context, w http.ResponseWriter,
 		rot, err := strconv.ParseBool(value)
 		rot, err := strconv.ParseBool(value)
 		if err != nil {
 		if err != nil {
 			err := fmt.Errorf("invalid value for rotateWorkerToken: %s", value)
 			err := fmt.Errorf("invalid value for rotateWorkerToken: %s", value)
-			return errors.NewBadRequestError(err)
+			return invalidRequestError{err}
 		}
 		}
 
 
 		flags.RotateWorkerToken = rot
 		flags.RotateWorkerToken = rot
@@ -86,7 +100,7 @@ func (sr *swarmRouter) updateCluster(ctx context.Context, w http.ResponseWriter,
 		rot, err := strconv.ParseBool(value)
 		rot, err := strconv.ParseBool(value)
 		if err != nil {
 		if err != nil {
 			err := fmt.Errorf("invalid value for rotateManagerToken: %s", value)
 			err := fmt.Errorf("invalid value for rotateManagerToken: %s", value)
-			return errors.NewBadRequestError(err)
+			return invalidRequestError{err}
 		}
 		}
 
 
 		flags.RotateManagerToken = rot
 		flags.RotateManagerToken = rot
@@ -95,7 +109,7 @@ func (sr *swarmRouter) updateCluster(ctx context.Context, w http.ResponseWriter,
 	if value := r.URL.Query().Get("rotateManagerUnlockKey"); value != "" {
 	if value := r.URL.Query().Get("rotateManagerUnlockKey"); value != "" {
 		rot, err := strconv.ParseBool(value)
 		rot, err := strconv.ParseBool(value)
 		if err != nil {
 		if err != nil {
-			return errors.NewBadRequestError(fmt.Errorf("invalid value for rotateManagerUnlockKey: %s", value))
+			return invalidRequestError{fmt.Errorf("invalid value for rotateManagerUnlockKey: %s", value)}
 		}
 		}
 
 
 		flags.RotateManagerUnlockKey = rot
 		flags.RotateManagerUnlockKey = rot
@@ -139,7 +153,7 @@ func (sr *swarmRouter) getServices(ctx context.Context, w http.ResponseWriter, r
 	}
 	}
 	filter, err := filters.FromParam(r.Form.Get("filters"))
 	filter, err := filters.FromParam(r.Form.Get("filters"))
 	if err != nil {
 	if err != nil {
-		return err
+		return invalidRequestError{err}
 	}
 	}
 
 
 	services, err := sr.backend.GetServices(basictypes.ServiceListOptions{Filters: filter})
 	services, err := sr.backend.GetServices(basictypes.ServiceListOptions{Filters: filter})
@@ -158,7 +172,7 @@ func (sr *swarmRouter) getService(ctx context.Context, w http.ResponseWriter, r
 		insertDefaults, err = strconv.ParseBool(value)
 		insertDefaults, err = strconv.ParseBool(value)
 		if err != nil {
 		if err != nil {
 			err := fmt.Errorf("invalid value for insertDefaults: %s", value)
 			err := fmt.Errorf("invalid value for insertDefaults: %s", value)
-			return errors.NewBadRequestError(err)
+			return errors.Wrapf(invalidRequestError{err}, "invalid value for insertDefaults: %s", value)
 		}
 		}
 	}
 	}
 
 
@@ -204,7 +218,7 @@ func (sr *swarmRouter) updateService(ctx context.Context, w http.ResponseWriter,
 	version, err := strconv.ParseUint(rawVersion, 10, 64)
 	version, err := strconv.ParseUint(rawVersion, 10, 64)
 	if err != nil {
 	if err != nil {
 		err := fmt.Errorf("invalid service version '%s': %v", rawVersion, err)
 		err := fmt.Errorf("invalid service version '%s': %v", rawVersion, err)
-		return errors.NewBadRequestError(err)
+		return invalidRequestError{err}
 	}
 	}
 
 
 	var flags basictypes.ServiceUpdateOptions
 	var flags basictypes.ServiceUpdateOptions
@@ -297,7 +311,7 @@ func (sr *swarmRouter) updateNode(ctx context.Context, w http.ResponseWriter, r
 	version, err := strconv.ParseUint(rawVersion, 10, 64)
 	version, err := strconv.ParseUint(rawVersion, 10, 64)
 	if err != nil {
 	if err != nil {
 		err := fmt.Errorf("invalid node version '%s': %v", rawVersion, err)
 		err := fmt.Errorf("invalid node version '%s': %v", rawVersion, err)
-		return errors.NewBadRequestError(err)
+		return invalidRequestError{err}
 	}
 	}
 
 
 	if err := sr.backend.UpdateNode(vars["id"], version, node); err != nil {
 	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 *
 func (sr *swarmRouter) updateSecret(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func (sr *swarmRouter) updateSecret(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	var secret types.SecretSpec
 	var secret types.SecretSpec
 	if err := json.NewDecoder(r.Body).Decode(&secret); err != nil {
 	if err := json.NewDecoder(r.Body).Decode(&secret); err != nil {
-		return errors.NewBadRequestError(err)
+		return invalidRequestError{err}
 	}
 	}
 
 
 	rawVersion := r.URL.Query().Get("version")
 	rawVersion := r.URL.Query().Get("version")
 	version, err := strconv.ParseUint(rawVersion, 10, 64)
 	version, err := strconv.ParseUint(rawVersion, 10, 64)
 	if err != nil {
 	if err != nil {
-		return errors.NewBadRequestError(fmt.Errorf("invalid secret version"))
+		return invalidRequestError{fmt.Errorf("invalid secret version")}
 	}
 	}
 
 
 	id := vars["id"]
 	id := vars["id"]
@@ -474,13 +488,13 @@ func (sr *swarmRouter) getConfig(ctx context.Context, w http.ResponseWriter, r *
 func (sr *swarmRouter) updateConfig(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func (sr *swarmRouter) updateConfig(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	var config types.ConfigSpec
 	var config types.ConfigSpec
 	if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
 	if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
-		return errors.NewBadRequestError(err)
+		return invalidRequestError{err}
 	}
 	}
 
 
 	rawVersion := r.URL.Query().Get("version")
 	rawVersion := r.URL.Query().Get("version")
 	version, err := strconv.ParseUint(rawVersion, 10, 64)
 	version, err := strconv.ParseUint(rawVersion, 10, 64)
 	if err != nil {
 	if err != nil {
-		return errors.NewBadRequestError(fmt.Errorf("invalid config version"))
+		return invalidRequestError{fmt.Errorf("invalid config version")}
 	}
 	}
 
 
 	id := vars["id"]
 	id := vars["id"]

+ 11 - 2
api/server/router/system/system_routes.go

@@ -7,7 +7,6 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/docker/docker/api"
 	"github.com/docker/docker/api"
-	"github.com/docker/docker/api/errors"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/events"
 	"github.com/docker/docker/api/types/events"
@@ -85,6 +84,16 @@ func (s *systemRouter) getDiskUsage(ctx context.Context, w http.ResponseWriter,
 	return httputils.WriteJSON(w, http.StatusOK, du)
 	return httputils.WriteJSON(w, http.StatusOK, du)
 }
 }
 
 
+type invalidRequestError struct {
+	Err error
+}
+
+func (e invalidRequestError) Error() string {
+	return e.Err.Error()
+}
+
+func (e invalidRequestError) InvalidParameter() {}
+
 func (s *systemRouter) getEvents(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func (s *systemRouter) getEvents(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := httputils.ParseForm(r); err != nil {
 	if err := httputils.ParseForm(r); err != nil {
 		return err
 		return err
@@ -105,7 +114,7 @@ func (s *systemRouter) getEvents(ctx context.Context, w http.ResponseWriter, r *
 	)
 	)
 	if !until.IsZero() {
 	if !until.IsZero() {
 		if until.Before(since) {
 		if until.Before(since) {
-			return errors.NewBadRequestError(fmt.Errorf("`since` time (%s) cannot be after `until` time (%s)", r.Form.Get("since"), r.Form.Get("until")))
+			return invalidRequestError{fmt.Errorf("`since` time (%s) cannot be after `until` time (%s)", r.Form.Get("since"), r.Form.Get("until"))}
 		}
 		}
 
 
 		now := time.Now()
 		now := time.Now()

+ 9 - 4
api/server/server.go

@@ -2,12 +2,10 @@ package server
 
 
 import (
 import (
 	"crypto/tls"
 	"crypto/tls"
-	"fmt"
 	"net"
 	"net"
 	"net/http"
 	"net/http"
 	"strings"
 	"strings"
 
 
-	"github.com/docker/docker/api/errors"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/server/httputils"
 	"github.com/docker/docker/api/server/middleware"
 	"github.com/docker/docker/api/server/middleware"
 	"github.com/docker/docker/api/server/router"
 	"github.com/docker/docker/api/server/router"
@@ -158,6 +156,14 @@ func (s *Server) InitRouter(routers ...router.Router) {
 	}
 	}
 }
 }
 
 
+type pageNotFoundError struct{}
+
+func (pageNotFoundError) Error() string {
+	return "page not found"
+}
+
+func (pageNotFoundError) NotFound() {}
+
 // createMux initializes the main router the server uses.
 // createMux initializes the main router the server uses.
 func (s *Server) createMux() *mux.Router {
 func (s *Server) createMux() *mux.Router {
 	m := mux.NewRouter()
 	m := mux.NewRouter()
@@ -180,8 +186,7 @@ func (s *Server) createMux() *mux.Router {
 		m.Path("/debug" + r.Path()).Handler(f)
 		m.Path("/debug" + r.Path()).Handler(f)
 	}
 	}
 
 
-	err := errors.NewRequestNotFoundError(fmt.Errorf("page not found"))
-	notFoundHandler := httputils.MakeErrorHandler(err)
+	notFoundHandler := httputils.MakeErrorHandler(pageNotFoundError{})
 	m.HandleFunc(versionMatcher+"/{path:.*}", notFoundHandler)
 	m.HandleFunc(versionMatcher+"/{path:.*}", notFoundHandler)
 	m.NotFoundHandler = notFoundHandler
 	m.NotFoundHandler = notFoundHandler
 
 

+ 0 - 4
api/swagger.yaml

@@ -3311,10 +3311,6 @@ paths:
           examples:
           examples:
             application/json:
             application/json:
               message: "No such container: c2ada9df5af8"
               message: "No such container: c2ada9df5af8"
-        406:
-          description: "impossible to attach"
-          schema:
-            $ref: "#/definitions/ErrorResponse"
         409:
         409:
           description: "conflict"
           description: "conflict"
           schema:
           schema:

+ 17 - 2
api/types/filters/parse.go

@@ -5,7 +5,6 @@ package filters
 import (
 import (
 	"encoding/json"
 	"encoding/json"
 	"errors"
 	"errors"
-	"fmt"
 	"regexp"
 	"regexp"
 	"strings"
 	"strings"
 
 
@@ -258,17 +257,33 @@ func (filters Args) Include(field string) bool {
 	return ok
 	return ok
 }
 }
 
 
+type invalidFilter string
+
+func (e invalidFilter) Error() string {
+	return "Invalid filter '" + string(e) + "'"
+}
+
+func (invalidFilter) InvalidParameter() {}
+
 // Validate ensures that all the fields in the filter are valid.
 // Validate ensures that all the fields in the filter are valid.
 // It returns an error as soon as it finds an invalid field.
 // It returns an error as soon as it finds an invalid field.
 func (filters Args) Validate(accepted map[string]bool) error {
 func (filters Args) Validate(accepted map[string]bool) error {
 	for name := range filters.fields {
 	for name := range filters.fields {
 		if !accepted[name] {
 		if !accepted[name] {
-			return fmt.Errorf("Invalid filter '%s'", name)
+			return invalidFilter(name)
 		}
 		}
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
+type invalidFilterError string
+
+func (e invalidFilterError) Error() string {
+	return "Invalid filter: '" + string(e) + "'"
+}
+
+func (invalidFilterError) InvalidParameter() {}
+
 // WalkValues iterates over the list of filtered values for a field.
 // WalkValues iterates over the list of filtered values for a field.
 // It stops the iteration if it finds an error and it returns that error.
 // It stops the iteration if it finds an error and it returns that error.
 func (filters Args) WalkValues(field string, op func(value string) error) error {
 func (filters Args) WalkValues(field string, op func(value string) error) error {

+ 4 - 4
builder/dockerfile/builder.go

@@ -241,7 +241,7 @@ func (b *Builder) build(source builder.Source, dockerfile *parser.Result) (*buil
 
 
 	if err := checkDispatchDockerfile(dockerfile.AST); err != nil {
 	if err := checkDispatchDockerfile(dockerfile.AST); err != nil {
 		buildsFailed.WithValues(metricsDockerfileSyntaxError).Inc()
 		buildsFailed.WithValues(metricsDockerfileSyntaxError).Inc()
-		return nil, err
+		return nil, validationError{err}
 	}
 	}
 
 
 	dispatchState, err := b.dispatchDockerfileWithCancellation(dockerfile, source)
 	dispatchState, err := b.dispatchDockerfileWithCancellation(dockerfile, source)
@@ -357,7 +357,7 @@ func BuildFromConfig(config *container.Config, changes []string) (*container.Con
 
 
 	dockerfile, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
 	dockerfile, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")))
 	if err != nil {
 	if err != nil {
-		return nil, err
+		return nil, validationError{err}
 	}
 	}
 
 
 	// TODO @jhowardmsft LCOW support. For now, if LCOW enabled, switch to linux.
 	// 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
 	// ensure that the commands are valid
 	// ensure that the commands are valid
 	for _, n := range dockerfile.AST.Children {
 	for _, n := range dockerfile.AST.Children {
 		if !validCommitCommands[n.Value] {
 		if !validCommitCommands[n.Value] {
-			return nil, fmt.Errorf("%s is not a valid change command", n.Value)
+			return nil, validationError{errors.Errorf("%s is not a valid change command", n.Value)}
 		}
 		}
 	}
 	}
 
 
@@ -383,7 +383,7 @@ func BuildFromConfig(config *container.Config, changes []string) (*container.Con
 	b.disableCommit = true
 	b.disableCommit = true
 
 
 	if err := checkDispatchDockerfile(dockerfile.AST); err != nil {
 	if err := checkDispatchDockerfile(dockerfile.AST); err != nil {
-		return nil, err
+		return nil, validationError{err}
 	}
 	}
 	dispatchState := newDispatchState()
 	dispatchState := newDispatchState()
 	dispatchState.runConfig = config
 	dispatchState.runConfig = config

+ 1 - 1
builder/dockerfile/dispatchers.go

@@ -784,7 +784,7 @@ func stopSignal(req dispatchRequest) error {
 	sig := req.args[0]
 	sig := req.args[0]
 	_, err := signal.ParseSignal(sig)
 	_, err := signal.ParseSignal(sig)
 	if err != nil {
 	if err != nil {
-		return err
+		return validationError{err}
 	}
 	}
 
 
 	req.state.runConfig.StopSignal = sig
 	req.state.runConfig.StopSignal = sig

+ 15 - 0
builder/dockerfile/errors.go

@@ -0,0 +1,15 @@
+package dockerfile
+
+type validationError struct {
+	err error
+}
+
+func (e validationError) Error() string {
+	return e.err.Error()
+}
+
+func (e validationError) InvalidParameter() {}
+
+func (e validationError) Cause() error {
+	return e.err
+}

+ 5 - 5
builder/dockerfile/evaluator.go

@@ -139,7 +139,7 @@ func (b *Builder) dispatch(options dispatchOptions) (*dispatchState, error) {
 	// on which the daemon is running does not support a builder command.
 	// on which the daemon is running does not support a builder command.
 	if err := platformSupports(strings.ToLower(cmd)); err != nil {
 	if err := platformSupports(strings.ToLower(cmd)); err != nil {
 		buildsFailed.WithValues(metricsCommandNotSupportedError).Inc()
 		buildsFailed.WithValues(metricsCommandNotSupportedError).Inc()
-		return nil, err
+		return nil, validationError{err}
 	}
 	}
 
 
 	msg := bytes.NewBufferString(fmt.Sprintf("Step %s : %s%s",
 	msg := bytes.NewBufferString(fmt.Sprintf("Step %s : %s%s",
@@ -151,7 +151,7 @@ func (b *Builder) dispatch(options dispatchOptions) (*dispatchState, error) {
 		var err error
 		var err error
 		ast, args, err = handleOnBuildNode(node, msg)
 		ast, args, err = handleOnBuildNode(node, msg)
 		if err != nil {
 		if err != nil {
-			return nil, err
+			return nil, validationError{err}
 		}
 		}
 	}
 	}
 
 
@@ -161,7 +161,7 @@ func (b *Builder) dispatch(options dispatchOptions) (*dispatchState, error) {
 	words, err := getDispatchArgsFromNode(ast, processFunc, msg)
 	words, err := getDispatchArgsFromNode(ast, processFunc, msg)
 	if err != nil {
 	if err != nil {
 		buildsFailed.WithValues(metricsErrorProcessingCommandsError).Inc()
 		buildsFailed.WithValues(metricsErrorProcessingCommandsError).Inc()
-		return nil, err
+		return nil, validationError{err}
 	}
 	}
 	args = append(args, words...)
 	args = append(args, words...)
 
 
@@ -170,7 +170,7 @@ func (b *Builder) dispatch(options dispatchOptions) (*dispatchState, error) {
 	f, ok := evaluateTable[cmd]
 	f, ok := evaluateTable[cmd]
 	if !ok {
 	if !ok {
 		buildsFailed.WithValues(metricsUnknownInstructionError).Inc()
 		buildsFailed.WithValues(metricsUnknownInstructionError).Inc()
-		return nil, fmt.Errorf("unknown instruction: %s", upperCasedCmd)
+		return nil, validationError{errors.Errorf("unknown instruction: %s", upperCasedCmd)}
 	}
 	}
 	options.state.updateRunConfig()
 	options.state.updateRunConfig()
 	err = f(newDispatchRequestFromOptions(options, b, args))
 	err = f(newDispatchRequestFromOptions(options, b, args))
@@ -247,7 +247,7 @@ func (s *dispatchState) setDefaultPath() {
 
 
 func handleOnBuildNode(ast *parser.Node, msg *bytes.Buffer) (*parser.Node, []string, error) {
 func handleOnBuildNode(ast *parser.Node, msg *bytes.Buffer) (*parser.Node, []string, error) {
 	if ast.Next == nil {
 	if ast.Next == nil {
-		return nil, nil, errors.New("ONBUILD requires at least one argument")
+		return nil, nil, validationError{errors.New("ONBUILD requires at least one argument")}
 	}
 	}
 	ast = ast.Next.Children[0]
 	ast = ast.Next.Children[0]
 	msg.WriteString(" " + ast.Value + formatFlags(ast.Flags))
 	msg.WriteString(" " + ast.Value + formatFlags(ast.Flags))

+ 75 - 0
builder/remotecontext/errors.go

@@ -0,0 +1,75 @@
+package remotecontext
+
+type notFoundError string
+
+func (e notFoundError) Error() string {
+	return string(e)
+}
+
+func (notFoundError) NotFound() {}
+
+type requestError string
+
+func (e requestError) Error() string {
+	return string(e)
+}
+
+func (e requestError) InvalidParameter() {}
+
+type unauthorizedError string
+
+func (e unauthorizedError) Error() string {
+	return string(e)
+}
+
+func (unauthorizedError) Unauthorized() {}
+
+type forbiddenError string
+
+func (e forbiddenError) Error() string {
+	return string(e)
+}
+
+func (forbiddenError) Forbidden() {}
+
+type dnsError struct {
+	cause error
+}
+
+func (e dnsError) Error() string {
+	return e.cause.Error()
+}
+
+func (e dnsError) NotFound() {}
+
+func (e dnsError) Cause() error {
+	return e.cause
+}
+
+type systemError struct {
+	cause error
+}
+
+func (e systemError) Error() string {
+	return e.cause.Error()
+}
+
+func (e systemError) SystemError() {}
+
+func (e systemError) Cause() error {
+	return e.cause
+}
+
+type unknownError struct {
+	cause error
+}
+
+func (e unknownError) Error() string {
+	return e.cause.Error()
+}
+
+func (unknownError) Unknown() {}
+
+func (e unknownError) Cause() error {
+	return e.cause
+}

+ 25 - 6
builder/remotecontext/remote.go

@@ -5,7 +5,9 @@ import (
 	"fmt"
 	"fmt"
 	"io"
 	"io"
 	"io/ioutil"
 	"io/ioutil"
+	"net"
 	"net/http"
 	"net/http"
+	"net/url"
 	"regexp"
 	"regexp"
 
 
 	"github.com/docker/docker/builder"
 	"github.com/docker/docker/builder"
@@ -68,20 +70,37 @@ func MakeRemoteContext(remoteURL string, contentTypeHandlers map[string]func(io.
 
 
 // GetWithStatusError does an http.Get() and returns an error if the
 // GetWithStatusError does an http.Get() and returns an error if the
 // status code is 4xx or 5xx.
 // status code is 4xx or 5xx.
-func GetWithStatusError(url string) (resp *http.Response, err error) {
-	if resp, err = http.Get(url); err != nil {
-		return nil, err
+func GetWithStatusError(address string) (resp *http.Response, err error) {
+	if resp, err = http.Get(address); err != nil {
+		if uerr, ok := err.(*url.Error); ok {
+			if derr, ok := uerr.Err.(*net.DNSError); ok && !derr.IsTimeout {
+				return nil, dnsError{err}
+			}
+		}
+		return nil, systemError{err}
 	}
 	}
 	if resp.StatusCode < 400 {
 	if resp.StatusCode < 400 {
 		return resp, nil
 		return resp, nil
 	}
 	}
-	msg := fmt.Sprintf("failed to GET %s with status %s", url, resp.Status)
+	msg := fmt.Sprintf("failed to GET %s with status %s", address, resp.Status)
 	body, err := ioutil.ReadAll(resp.Body)
 	body, err := ioutil.ReadAll(resp.Body)
 	resp.Body.Close()
 	resp.Body.Close()
 	if err != nil {
 	if err != nil {
-		return nil, errors.Wrapf(err, msg+": error reading body")
+		return nil, errors.Wrap(systemError{err}, msg+": error reading body")
+	}
+
+	msg += ": " + string(bytes.TrimSpace(body))
+	switch resp.StatusCode {
+	case http.StatusNotFound:
+		return nil, notFoundError(msg)
+	case http.StatusBadRequest:
+		return nil, requestError(msg)
+	case http.StatusUnauthorized:
+		return nil, unauthorizedError(msg)
+	case http.StatusForbidden:
+		return nil, forbiddenError(msg)
 	}
 	}
-	return nil, errors.Errorf(msg+": %s", bytes.TrimSpace(body))
+	return nil, unknownError{errors.New(msg)}
 }
 }
 
 
 // inspectResponse looks into the http response data at r to determine whether its
 // inspectResponse looks into the http response data at r to determine whether its

+ 9 - 8
container/container.go

@@ -43,6 +43,7 @@ import (
 	"github.com/docker/libnetwork/options"
 	"github.com/docker/libnetwork/options"
 	"github.com/docker/libnetwork/types"
 	"github.com/docker/libnetwork/types"
 	agentexec "github.com/docker/swarmkit/agent/exec"
 	agentexec "github.com/docker/swarmkit/agent/exec"
+	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
@@ -55,8 +56,8 @@ const (
 )
 )
 
 
 var (
 var (
-	errInvalidEndpoint = fmt.Errorf("invalid endpoint while building port map info")
-	errInvalidNetwork  = fmt.Errorf("invalid network settings while building port map info")
+	errInvalidEndpoint = errors.New("invalid endpoint while building port map info")
+	errInvalidNetwork  = errors.New("invalid network settings while building port map info")
 )
 )
 
 
 // Container holds the structure defining a container object.
 // Container holds the structure defining a container object.
@@ -274,7 +275,7 @@ func (container *Container) SetupWorkingDirectory(rootIDs idtools.IDPair) error
 	if err := idtools.MkdirAllAndChownNew(pth, 0755, rootIDs); err != nil {
 	if err := idtools.MkdirAllAndChownNew(pth, 0755, rootIDs); err != nil {
 		pthInfo, err2 := os.Stat(pth)
 		pthInfo, err2 := os.Stat(pth)
 		if err2 == nil && pthInfo != nil && !pthInfo.IsDir() {
 		if err2 == nil && pthInfo != nil && !pthInfo.IsDir() {
-			return fmt.Errorf("Cannot mkdir: %s is not a directory", container.Config.WorkingDir)
+			return errors.Errorf("Cannot mkdir: %s is not a directory", container.Config.WorkingDir)
 		}
 		}
 
 
 		return err
 		return err
@@ -357,7 +358,7 @@ func (container *Container) StartLogger() (logger.Logger, error) {
 	cfg := container.HostConfig.LogConfig
 	cfg := container.HostConfig.LogConfig
 	initDriver, err := logger.GetLogDriver(cfg.Type)
 	initDriver, err := logger.GetLogDriver(cfg.Type)
 	if err != nil {
 	if err != nil {
-		return nil, fmt.Errorf("failed to get logging factory: %v", err)
+		return nil, errors.Wrap(err, "failed to get logging factory")
 	}
 	}
 	info := logger.Info{
 	info := logger.Info{
 		Config:              cfg.Config,
 		Config:              cfg.Config,
@@ -723,18 +724,18 @@ func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network, epC
 
 
 			for _, ips := range ipam.LinkLocalIPs {
 			for _, ips := range ipam.LinkLocalIPs {
 				if linkip = net.ParseIP(ips); linkip == nil && ips != "" {
 				if linkip = net.ParseIP(ips); linkip == nil && ips != "" {
-					return nil, fmt.Errorf("Invalid link-local IP address:%s", ipam.LinkLocalIPs)
+					return nil, errors.Errorf("Invalid link-local IP address: %s", ipam.LinkLocalIPs)
 				}
 				}
 				ipList = append(ipList, linkip)
 				ipList = append(ipList, linkip)
 
 
 			}
 			}
 
 
 			if ip = net.ParseIP(ipam.IPv4Address); ip == nil && ipam.IPv4Address != "" {
 			if ip = net.ParseIP(ipam.IPv4Address); ip == nil && ipam.IPv4Address != "" {
-				return nil, fmt.Errorf("Invalid IPv4 address:%s)", ipam.IPv4Address)
+				return nil, errors.Errorf("Invalid IPv4 address: %s)", ipam.IPv4Address)
 			}
 			}
 
 
 			if ip6 = net.ParseIP(ipam.IPv6Address); ip6 == nil && ipam.IPv6Address != "" {
 			if ip6 = net.ParseIP(ipam.IPv6Address); ip6 == nil && ipam.IPv6Address != "" {
-				return nil, fmt.Errorf("Invalid IPv6 address:%s)", ipam.IPv6Address)
+				return nil, errors.Errorf("Invalid IPv6 address: %s)", ipam.IPv6Address)
 			}
 			}
 
 
 			createOptions = append(createOptions,
 			createOptions = append(createOptions,
@@ -838,7 +839,7 @@ func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network, epC
 				portStart, portEnd, err = newP.Range()
 				portStart, portEnd, err = newP.Range()
 			}
 			}
 			if err != nil {
 			if err != nil {
-				return nil, fmt.Errorf("Error parsing HostPort value(%s):%v", binding[i].HostPort, err)
+				return nil, errors.Wrapf(err, "Error parsing HostPort value (%s)", binding[i].HostPort)
 			}
 			}
 			pbCopy.HostPort = uint16(portStart)
 			pbCopy.HostPort = uint16(portStart)
 			pbCopy.HostPortEnd = uint16(portEnd)
 			pbCopy.HostPortEnd = uint16(portEnd)

+ 14 - 7
container/container_unix.go

@@ -3,7 +3,6 @@
 package container
 package container
 
 
 import (
 import (
-	"fmt"
 	"io/ioutil"
 	"io/ioutil"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
@@ -262,6 +261,14 @@ func (container *Container) ConfigMounts() []Mount {
 	return mounts
 	return mounts
 }
 }
 
 
+type conflictingUpdateOptions string
+
+func (e conflictingUpdateOptions) Error() string {
+	return string(e)
+}
+
+func (e conflictingUpdateOptions) Conflict() {}
+
 // UpdateContainer updates configuration of a container. Callers must hold a Lock on the Container.
 // UpdateContainer updates configuration of a container. Callers must hold a Lock on the Container.
 func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfig) error {
 func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfig) error {
 	// update resources of container
 	// update resources of container
@@ -273,16 +280,16 @@ func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfi
 	// once NanoCPU is already set, updating CPUPeriod/CPUQuota will be blocked, and vice versa.
 	// once NanoCPU is already set, updating CPUPeriod/CPUQuota will be blocked, and vice versa.
 	// In the following we make sure the intended update (resources) does not conflict with the existing (cResource).
 	// In the following we make sure the intended update (resources) does not conflict with the existing (cResource).
 	if resources.NanoCPUs > 0 && cResources.CPUPeriod > 0 {
 	if resources.NanoCPUs > 0 && cResources.CPUPeriod > 0 {
-		return fmt.Errorf("Conflicting options: Nano CPUs cannot be updated as CPU Period has already been set")
+		return conflictingUpdateOptions("Conflicting options: Nano CPUs cannot be updated as CPU Period has already been set")
 	}
 	}
 	if resources.NanoCPUs > 0 && cResources.CPUQuota > 0 {
 	if resources.NanoCPUs > 0 && cResources.CPUQuota > 0 {
-		return fmt.Errorf("Conflicting options: Nano CPUs cannot be updated as CPU Quota has already been set")
+		return conflictingUpdateOptions("Conflicting options: Nano CPUs cannot be updated as CPU Quota has already been set")
 	}
 	}
 	if resources.CPUPeriod > 0 && cResources.NanoCPUs > 0 {
 	if resources.CPUPeriod > 0 && cResources.NanoCPUs > 0 {
-		return fmt.Errorf("Conflicting options: CPU Period cannot be updated as NanoCPUs has already been set")
+		return conflictingUpdateOptions("Conflicting options: CPU Period cannot be updated as NanoCPUs has already been set")
 	}
 	}
 	if resources.CPUQuota > 0 && cResources.NanoCPUs > 0 {
 	if resources.CPUQuota > 0 && cResources.NanoCPUs > 0 {
-		return fmt.Errorf("Conflicting options: CPU Quota cannot be updated as NanoCPUs has already been set")
+		return conflictingUpdateOptions("Conflicting options: CPU Quota cannot be updated as NanoCPUs has already been set")
 	}
 	}
 
 
 	if resources.BlkioWeight != 0 {
 	if resources.BlkioWeight != 0 {
@@ -310,7 +317,7 @@ func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfi
 		// if memory limit smaller than already set memoryswap limit and doesn't
 		// if memory limit smaller than already set memoryswap limit and doesn't
 		// update the memoryswap limit, then error out.
 		// update the memoryswap limit, then error out.
 		if resources.Memory > cResources.MemorySwap && resources.MemorySwap == 0 {
 		if resources.Memory > cResources.MemorySwap && resources.MemorySwap == 0 {
-			return fmt.Errorf("Memory limit should be smaller than already set memoryswap limit, update the memoryswap at the same time")
+			return conflictingUpdateOptions("Memory limit should be smaller than already set memoryswap limit, update the memoryswap at the same time")
 		}
 		}
 		cResources.Memory = resources.Memory
 		cResources.Memory = resources.Memory
 	}
 	}
@@ -327,7 +334,7 @@ func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfi
 	// update HostConfig of container
 	// update HostConfig of container
 	if hostConfig.RestartPolicy.Name != "" {
 	if hostConfig.RestartPolicy.Name != "" {
 		if container.HostConfig.AutoRemove && !hostConfig.RestartPolicy.IsNone() {
 		if container.HostConfig.AutoRemove && !hostConfig.RestartPolicy.IsNone() {
-			return fmt.Errorf("Restart policy cannot be updated because AutoRemove is enabled for the container")
+			return conflictingUpdateOptions("Restart policy cannot be updated because AutoRemove is enabled for the container")
 		}
 		}
 		container.HostConfig.RestartPolicy = hostConfig.RestartPolicy
 		container.HostConfig.RestartPolicy = hostConfig.RestartPolicy
 	}
 	}

+ 43 - 12
daemon/archive.go

@@ -28,16 +28,20 @@ func (daemon *Daemon) ContainerCopy(name string, res string) (io.ReadCloser, err
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	if res[0] == '/' || res[0] == '\\' {
-		res = res[1:]
-	}
-
 	// Make sure an online file-system operation is permitted.
 	// Make sure an online file-system operation is permitted.
 	if err := daemon.isOnlineFSOperationPermitted(container); err != nil {
 	if err := daemon.isOnlineFSOperationPermitted(container); err != nil {
-		return nil, err
+		return nil, systemError{err}
+	}
+
+	data, err := daemon.containerCopy(container, res)
+	if err == nil {
+		return data, nil
 	}
 	}
 
 
-	return daemon.containerCopy(container, res)
+	if os.IsNotExist(err) {
+		return nil, containerFileNotFound{res, name}
+	}
+	return nil, systemError{err}
 }
 }
 
 
 // ContainerStatPath stats the filesystem resource at the specified path in the
 // 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
 
 
 	// Make sure an online file-system operation is permitted.
 	// Make sure an online file-system operation is permitted.
 	if err := daemon.isOnlineFSOperationPermitted(container); err != nil {
 	if err := daemon.isOnlineFSOperationPermitted(container); err != nil {
-		return nil, err
+		return nil, systemError{err}
 	}
 	}
 
 
-	return daemon.containerStatPath(container, path)
+	stat, err = daemon.containerStatPath(container, path)
+	if err == nil {
+		return stat, nil
+	}
+
+	if os.IsNotExist(err) {
+		return nil, containerFileNotFound{path, name}
+	}
+	return nil, systemError{err}
 }
 }
 
 
 // ContainerArchivePath creates an archive of the filesystem resource at the
 // ContainerArchivePath creates an archive of the filesystem resource at the
@@ -67,10 +79,18 @@ func (daemon *Daemon) ContainerArchivePath(name string, path string) (content io
 
 
 	// Make sure an online file-system operation is permitted.
 	// Make sure an online file-system operation is permitted.
 	if err := daemon.isOnlineFSOperationPermitted(container); err != nil {
 	if err := daemon.isOnlineFSOperationPermitted(container); err != nil {
-		return nil, nil, err
+		return nil, nil, systemError{err}
+	}
+
+	content, stat, err = daemon.containerArchivePath(container, path)
+	if err == nil {
+		return content, stat, nil
 	}
 	}
 
 
-	return daemon.containerArchivePath(container, path)
+	if os.IsNotExist(err) {
+		return nil, nil, containerFileNotFound{path, name}
+	}
+	return nil, nil, systemError{err}
 }
 }
 
 
 // ContainerExtractToDir extracts the given archive to the specified location
 // ContainerExtractToDir extracts the given archive to the specified location
@@ -87,10 +107,18 @@ func (daemon *Daemon) ContainerExtractToDir(name, path string, copyUIDGID, noOve
 
 
 	// Make sure an online file-system operation is permitted.
 	// Make sure an online file-system operation is permitted.
 	if err := daemon.isOnlineFSOperationPermitted(container); err != nil {
 	if err := daemon.isOnlineFSOperationPermitted(container); err != nil {
-		return err
+		return systemError{err}
 	}
 	}
 
 
-	return daemon.containerExtractToDir(container, path, copyUIDGID, noOverwriteDirNonDir, content)
+	err = daemon.containerExtractToDir(container, path, copyUIDGID, noOverwriteDirNonDir, content)
+	if err == nil {
+		return nil
+	}
+
+	if os.IsNotExist(err) {
+		return containerFileNotFound{path, name}
+	}
+	return systemError{err}
 }
 }
 
 
 // containerStatPath stats the filesystem resource at the specified path in this
 // containerStatPath stats the filesystem resource at the specified path in this
@@ -297,6 +325,9 @@ func (daemon *Daemon) containerExtractToDir(container *container.Container, path
 }
 }
 
 
 func (daemon *Daemon) containerCopy(container *container.Container, resource string) (rc io.ReadCloser, err error) {
 func (daemon *Daemon) containerCopy(container *container.Container, resource string) (rc io.ReadCloser, err error) {
+	if resource[0] == '/' || resource[0] == '\\' {
+		resource = resource[1:]
+	}
 	container.Lock()
 	container.Lock()
 
 
 	defer func() {
 	defer func() {

+ 5 - 5
daemon/attach.go

@@ -5,13 +5,13 @@ import (
 	"fmt"
 	"fmt"
 	"io"
 	"io"
 
 
-	"github.com/docker/docker/api/errors"
 	"github.com/docker/docker/api/types/backend"
 	"github.com/docker/docker/api/types/backend"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/container/stream"
 	"github.com/docker/docker/container/stream"
 	"github.com/docker/docker/daemon/logger"
 	"github.com/docker/docker/daemon/logger"
 	"github.com/docker/docker/pkg/stdcopy"
 	"github.com/docker/docker/pkg/stdcopy"
 	"github.com/docker/docker/pkg/term"
 	"github.com/docker/docker/pkg/term"
+	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 )
 )
 
 
@@ -22,7 +22,7 @@ func (daemon *Daemon) ContainerAttach(prefixOrName string, c *backend.ContainerA
 	if c.DetachKeys != "" {
 	if c.DetachKeys != "" {
 		keys, err = term.ToBytes(c.DetachKeys)
 		keys, err = term.ToBytes(c.DetachKeys)
 		if err != nil {
 		if err != nil {
-			return fmt.Errorf("Invalid detach keys (%s) provided", c.DetachKeys)
+			return validationError{errors.Errorf("Invalid detach keys (%s) provided", c.DetachKeys)}
 		}
 		}
 	}
 	}
 
 
@@ -32,11 +32,11 @@ func (daemon *Daemon) ContainerAttach(prefixOrName string, c *backend.ContainerA
 	}
 	}
 	if container.IsPaused() {
 	if container.IsPaused() {
 		err := fmt.Errorf("Container %s is paused, unpause the container before attach.", prefixOrName)
 		err := fmt.Errorf("Container %s is paused, unpause the container before attach.", prefixOrName)
-		return errors.NewRequestConflictError(err)
+		return stateConflictError{err}
 	}
 	}
 	if container.IsRestarting() {
 	if container.IsRestarting() {
 		err := fmt.Errorf("Container %s is restarting, wait until the container is running.", prefixOrName)
 		err := fmt.Errorf("Container %s is restarting, wait until the container is running.", prefixOrName)
-		return errors.NewRequestConflictError(err)
+		return stateConflictError{err}
 	}
 	}
 
 
 	cfg := stream.AttachConfig{
 	cfg := stream.AttachConfig{
@@ -119,7 +119,7 @@ func (daemon *Daemon) containerAttach(c *container.Container, cfg *stream.Attach
 		}
 		}
 		cLog, ok := logDriver.(logger.LogReader)
 		cLog, ok := logDriver.(logger.LogReader)
 		if !ok {
 		if !ok {
-			return logger.ErrReadLogsNotSupported
+			return logger.ErrReadLogsNotSupported{}
 		}
 		}
 		logs := cLog.ReadLogs(logger.ReadConfig{Tail: -1})
 		logs := cLog.ReadLogs(logger.ReadConfig{Tail: -1})
 		defer logs.Close()
 		defer logs.Close()

+ 3 - 18
daemon/cluster/cluster.go

@@ -72,21 +72,6 @@ const (
 	contextPrefix         = "com.docker.swarm"
 	contextPrefix         = "com.docker.swarm"
 )
 )
 
 
-// errNoSwarm is returned on leaving a cluster that was never initialized
-var errNoSwarm = errors.New("This node is not part of a swarm")
-
-// errSwarmExists is returned on initialize or join request for a cluster that has already been activated
-var errSwarmExists = errors.New("This node is already part of a swarm. Use \"docker swarm leave\" to leave this swarm and join another one.")
-
-// errSwarmJoinTimeoutReached is returned when cluster join could not complete before timeout was reached.
-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.")
-
-// errSwarmLocked is returned if the swarm is encrypted and needs a key to unlock it.
-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.")
-
-// errSwarmCertificatesExpired is returned if docker was not started for the whole validity period and they had no chance to renew automatically.
-var errSwarmCertificatesExpired = errors.New("Swarm certificates have expired. To replace them, leave the swarm and join again.")
-
 // NetworkSubnetsProvider exposes functions for retrieving the subnets
 // NetworkSubnetsProvider exposes functions for retrieving the subnets
 // of networks managed by Docker, so they can be filtered.
 // of networks managed by Docker, so they can be filtered.
 type NetworkSubnetsProvider interface {
 type NetworkSubnetsProvider interface {
@@ -343,12 +328,12 @@ func (c *Cluster) errNoManager(st nodeState) error {
 		if st.err == errSwarmCertificatesExpired {
 		if st.err == errSwarmCertificatesExpired {
 			return errSwarmCertificatesExpired
 			return errSwarmCertificatesExpired
 		}
 		}
-		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.")
+		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."))
 	}
 	}
 	if st.swarmNode.Manager() != nil {
 	if st.swarmNode.Manager() != nil {
-		return errors.New("This node is not a swarm manager. Manager is being prepared or has trouble connecting to the cluster.")
+		return errors.WithStack(notAvailableError("This node is not a swarm manager. Manager is being prepared or has trouble connecting to the cluster."))
 	}
 	}
-	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.")
+	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."))
 }
 }
 
 
 // Cleanup stops active swarm node. This is run before daemon shutdown.
 // Cleanup stops active swarm node. This is run before daemon shutdown.

+ 2 - 2
daemon/cluster/controllers/plugin/controller.go

@@ -6,6 +6,7 @@ import (
 	"net/http"
 	"net/http"
 
 
 	"github.com/docker/distribution/reference"
 	"github.com/docker/distribution/reference"
+	"github.com/docker/docker/api/errdefs"
 	enginetypes "github.com/docker/docker/api/types"
 	enginetypes "github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/swarm/runtime"
 	"github.com/docker/docker/api/types/swarm/runtime"
 	"github.com/docker/docker/plugin"
 	"github.com/docker/docker/plugin"
@@ -198,8 +199,7 @@ func (p *Controller) Wait(ctx context.Context) error {
 }
 }
 
 
 func isNotFound(err error) bool {
 func isNotFound(err error) bool {
-	_, ok := errors.Cause(err).(plugin.ErrNotFound)
-	return ok
+	return errdefs.IsNotFound(err)
 }
 }
 
 
 // Shutdown is the shutdown phase from swarmkit
 // Shutdown is the shutdown phase from swarmkit

+ 114 - 0
daemon/cluster/errors.go

@@ -0,0 +1,114 @@
+package cluster
+
+const (
+	// errNoSwarm is returned on leaving a cluster that was never initialized
+	errNoSwarm notAvailableError = "This node is not part of a swarm"
+
+	// errSwarmExists is returned on initialize or join request for a cluster that has already been activated
+	errSwarmExists notAvailableError = "This node is already part of a swarm. Use \"docker swarm leave\" to leave this swarm and join another one."
+
+	// errSwarmJoinTimeoutReached is returned when cluster join could not complete before timeout was reached.
+	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."
+
+	// errSwarmLocked is returned if the swarm is encrypted and needs a key to unlock it.
+	errSwarmLocked notAvailableError = "Swarm is encrypted and needs to be unlocked before it can be used. Please use \"docker swarm unlock\" to unlock it."
+
+	// errSwarmCertificatesExpired is returned if docker was not started for the whole validity period and they had no chance to renew automatically.
+	errSwarmCertificatesExpired notAvailableError = "Swarm certificates have expired. To replace them, leave the swarm and join again."
+)
+
+type notFoundError struct {
+	cause error
+}
+
+func (e notFoundError) Error() string {
+	return e.cause.Error()
+}
+
+func (e notFoundError) NotFound() {}
+
+func (e notFoundError) Cause() error {
+	return e.cause
+}
+
+type ambiguousResultsError struct {
+	cause error
+}
+
+func (e ambiguousResultsError) Error() string {
+	return e.cause.Error()
+}
+
+func (e ambiguousResultsError) InvalidParameter() {}
+
+func (e ambiguousResultsError) Cause() error {
+	return e.cause
+}
+
+type convertError struct {
+	cause error
+}
+
+func (e convertError) Error() string {
+	return e.cause.Error()
+}
+
+func (e convertError) InvalidParameter() {}
+
+func (e convertError) Cause() error {
+	return e.cause
+}
+
+type notAllowedError string
+
+func (e notAllowedError) Error() string {
+	return string(e)
+}
+
+func (e notAllowedError) Forbidden() {}
+
+type validationError struct {
+	cause error
+}
+
+func (e validationError) Error() string {
+	return e.cause.Error()
+}
+
+func (e validationError) InvalidParameter() {}
+
+func (e validationError) Cause() error {
+	return e.cause
+}
+
+type notAvailableError string
+
+func (e notAvailableError) Error() string {
+	return string(e)
+}
+
+func (e notAvailableError) Unavailable() {}
+
+type configError string
+
+func (e configError) Error() string {
+	return string(e)
+}
+
+func (e configError) InvalidParameter() {}
+
+type invalidUnlockKey struct{}
+
+func (invalidUnlockKey) Error() string {
+	return "swarm could not be unlocked: invalid key provided"
+}
+
+func (invalidUnlockKey) Unauthorized() {}
+
+type notLockedError struct{}
+
+func (notLockedError) Error() string {
+	return "swarm is not locked"
+}
+
+func (notLockedError) Conflict() {}

+ 1 - 1
daemon/cluster/executor/backend.go

@@ -34,7 +34,7 @@ type Backend interface {
 	CreateManagedContainer(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error)
 	CreateManagedContainer(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error)
 	ContainerStart(name string, hostConfig *container.HostConfig, checkpoint string, checkpointDir string) error
 	ContainerStart(name string, hostConfig *container.HostConfig, checkpoint string, checkpointDir string) error
 	ContainerStop(name string, seconds *int) error
 	ContainerStop(name string, seconds *int) error
-	ContainerLogs(context.Context, string, *types.ContainerLogsOptions) (<-chan *backend.LogMessage, error)
+	ContainerLogs(context.Context, string, *types.ContainerLogsOptions) (msgs <-chan *backend.LogMessage, tty bool, err error)
 	ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error
 	ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error
 	ActivateContainerServiceBinding(containerName string) error
 	ActivateContainerServiceBinding(containerName string) error
 	DeactivateContainerServiceBinding(containerName string) error
 	DeactivateContainerServiceBinding(containerName string) error

+ 1 - 1
daemon/cluster/executor/container/adapter.go

@@ -451,7 +451,7 @@ func (c *containerAdapter) logs(ctx context.Context, options api.LogSubscription
 			}
 			}
 		}
 		}
 	}
 	}
-	msgs, err := c.backend.ContainerLogs(ctx, c.container.name(), apiOptions)
+	msgs, _, err := c.backend.ContainerLogs(ctx, c.container.name(), apiOptions)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}

+ 13 - 13
daemon/cluster/helpers.go

@@ -3,8 +3,8 @@ package cluster
 import (
 import (
 	"fmt"
 	"fmt"
 
 
-	"github.com/docker/docker/api/errors"
 	swarmapi "github.com/docker/swarmkit/api"
 	swarmapi "github.com/docker/swarmkit/api"
+	"github.com/pkg/errors"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
 
 
@@ -15,7 +15,7 @@ func getSwarm(ctx context.Context, c swarmapi.ControlClient) (*swarmapi.Cluster,
 	}
 	}
 
 
 	if len(rl.Clusters) == 0 {
 	if len(rl.Clusters) == 0 {
-		return nil, errors.NewRequestNotFoundError(errNoSwarm)
+		return nil, errors.WithStack(errNoSwarm)
 	}
 	}
 
 
 	// TODO: assume one cluster only
 	// TODO: assume one cluster only
@@ -48,11 +48,11 @@ func getNode(ctx context.Context, c swarmapi.ControlClient, input string) (*swar
 
 
 	if len(rl.Nodes) == 0 {
 	if len(rl.Nodes) == 0 {
 		err := fmt.Errorf("node %s not found", input)
 		err := fmt.Errorf("node %s not found", input)
-		return nil, errors.NewRequestNotFoundError(err)
+		return nil, notFoundError{err}
 	}
 	}
 
 
 	if l := len(rl.Nodes); l > 1 {
 	if l := len(rl.Nodes); l > 1 {
-		return nil, fmt.Errorf("node %s is ambiguous (%d matches found)", input, l)
+		return nil, ambiguousResultsError{fmt.Errorf("node %s is ambiguous (%d matches found)", input, l)}
 	}
 	}
 
 
 	return rl.Nodes[0], nil
 	return rl.Nodes[0], nil
@@ -84,11 +84,11 @@ func getService(ctx context.Context, c swarmapi.ControlClient, input string, ins
 
 
 	if len(rl.Services) == 0 {
 	if len(rl.Services) == 0 {
 		err := fmt.Errorf("service %s not found", input)
 		err := fmt.Errorf("service %s not found", input)
-		return nil, errors.NewRequestNotFoundError(err)
+		return nil, notFoundError{err}
 	}
 	}
 
 
 	if l := len(rl.Services); l > 1 {
 	if l := len(rl.Services); l > 1 {
-		return nil, fmt.Errorf("service %s is ambiguous (%d matches found)", input, l)
+		return nil, ambiguousResultsError{fmt.Errorf("service %s is ambiguous (%d matches found)", input, l)}
 	}
 	}
 
 
 	if !insertDefaults {
 	if !insertDefaults {
@@ -128,11 +128,11 @@ func getTask(ctx context.Context, c swarmapi.ControlClient, input string) (*swar
 
 
 	if len(rl.Tasks) == 0 {
 	if len(rl.Tasks) == 0 {
 		err := fmt.Errorf("task %s not found", input)
 		err := fmt.Errorf("task %s not found", input)
-		return nil, errors.NewRequestNotFoundError(err)
+		return nil, notFoundError{err}
 	}
 	}
 
 
 	if l := len(rl.Tasks); l > 1 {
 	if l := len(rl.Tasks); l > 1 {
-		return nil, fmt.Errorf("task %s is ambiguous (%d matches found)", input, l)
+		return nil, ambiguousResultsError{fmt.Errorf("task %s is ambiguous (%d matches found)", input, l)}
 	}
 	}
 
 
 	return rl.Tasks[0], nil
 	return rl.Tasks[0], nil
@@ -164,11 +164,11 @@ func getSecret(ctx context.Context, c swarmapi.ControlClient, input string) (*sw
 
 
 	if len(rl.Secrets) == 0 {
 	if len(rl.Secrets) == 0 {
 		err := fmt.Errorf("secret %s not found", input)
 		err := fmt.Errorf("secret %s not found", input)
-		return nil, errors.NewRequestNotFoundError(err)
+		return nil, notFoundError{err}
 	}
 	}
 
 
 	if l := len(rl.Secrets); l > 1 {
 	if l := len(rl.Secrets); l > 1 {
-		return nil, fmt.Errorf("secret %s is ambiguous (%d matches found)", input, l)
+		return nil, ambiguousResultsError{fmt.Errorf("secret %s is ambiguous (%d matches found)", input, l)}
 	}
 	}
 
 
 	return rl.Secrets[0], nil
 	return rl.Secrets[0], nil
@@ -200,11 +200,11 @@ func getConfig(ctx context.Context, c swarmapi.ControlClient, input string) (*sw
 
 
 	if len(rl.Configs) == 0 {
 	if len(rl.Configs) == 0 {
 		err := fmt.Errorf("config %s not found", input)
 		err := fmt.Errorf("config %s not found", input)
-		return nil, errors.NewRequestNotFoundError(err)
+		return nil, notFoundError{err}
 	}
 	}
 
 
 	if l := len(rl.Configs); l > 1 {
 	if l := len(rl.Configs); l > 1 {
-		return nil, fmt.Errorf("config %s is ambiguous (%d matches found)", input, l)
+		return nil, ambiguousResultsError{fmt.Errorf("config %s is ambiguous (%d matches found)", input, l)}
 	}
 	}
 
 
 	return rl.Configs[0], nil
 	return rl.Configs[0], nil
@@ -238,7 +238,7 @@ func getNetwork(ctx context.Context, c swarmapi.ControlClient, input string) (*s
 	}
 	}
 
 
 	if l := len(rl.Networks); l > 1 {
 	if l := len(rl.Networks); l > 1 {
-		return nil, fmt.Errorf("network %s is ambiguous (%d matches found)", input, l)
+		return nil, ambiguousResultsError{fmt.Errorf("network %s is ambiguous (%d matches found)", input, l)}
 	}
 	}
 
 
 	return rl.Networks[0], nil
 	return rl.Networks[0], nil

+ 14 - 15
daemon/cluster/listen_addr.go

@@ -1,20 +1,19 @@
 package cluster
 package cluster
 
 
 import (
 import (
-	"errors"
 	"fmt"
 	"fmt"
 	"net"
 	"net"
 )
 )
 
 
-var (
-	errNoSuchInterface         = errors.New("no such interface")
-	errNoIP                    = errors.New("could not find the system's IP address")
-	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")
-	errBadNetworkIdentifier    = errors.New("must specify a valid IP address or interface name")
-	errBadListenAddr           = errors.New("listen address must be an IP address or network interface (with optional port number)")
-	errBadAdvertiseAddr        = errors.New("advertise address must be a non-zero IP address or network interface (with optional port number)")
-	errBadDataPathAddr         = errors.New("data path address must be a non-zero IP address or network interface (without a port number)")
-	errBadDefaultAdvertiseAddr = errors.New("default advertise address must be a non-zero IP address or network interface (without a port number)")
+const (
+	errNoSuchInterface         configError = "no such interface"
+	errNoIP                    configError = "could not find the system's IP address"
+	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"
+	errBadNetworkIdentifier    configError = "must specify a valid IP address or interface name"
+	errBadListenAddr           configError = "listen address must be an IP address or network interface (with optional port number)"
+	errBadAdvertiseAddr        configError = "advertise address must be a non-zero IP address or network interface (with optional port number)"
+	errBadDataPathAddr         configError = "data path address must be a non-zero IP address or network interface (without a port number)"
+	errBadDefaultAdvertiseAddr configError = "default advertise address must be a non-zero IP address or network interface (without a port number)"
 )
 )
 
 
 func resolveListenAddr(specifiedAddr string) (string, string, error) {
 func resolveListenAddr(specifiedAddr string) (string, string, error) {
@@ -125,13 +124,13 @@ func resolveInterfaceAddr(specifiedInterface string) (net.IP, error) {
 			if ipAddr.IP.To4() != nil {
 			if ipAddr.IP.To4() != nil {
 				// IPv4
 				// IPv4
 				if interfaceAddr4 != nil {
 				if interfaceAddr4 != nil {
-					return nil, fmt.Errorf("interface %s has more than one IPv4 address (%s and %s)", specifiedInterface, interfaceAddr4, ipAddr.IP)
+					return nil, configError(fmt.Sprintf("interface %s has more than one IPv4 address (%s and %s)", specifiedInterface, interfaceAddr4, ipAddr.IP))
 				}
 				}
 				interfaceAddr4 = ipAddr.IP
 				interfaceAddr4 = ipAddr.IP
 			} else {
 			} else {
 				// IPv6
 				// IPv6
 				if interfaceAddr6 != nil {
 				if interfaceAddr6 != nil {
-					return nil, fmt.Errorf("interface %s has more than one IPv6 address (%s and %s)", specifiedInterface, interfaceAddr6, ipAddr.IP)
+					return nil, configError(fmt.Sprintf("interface %s has more than one IPv6 address (%s and %s)", specifiedInterface, interfaceAddr6, ipAddr.IP))
 				}
 				}
 				interfaceAddr6 = ipAddr.IP
 				interfaceAddr6 = ipAddr.IP
 			}
 			}
@@ -139,7 +138,7 @@ func resolveInterfaceAddr(specifiedInterface string) (net.IP, error) {
 	}
 	}
 
 
 	if interfaceAddr4 == nil && interfaceAddr6 == nil {
 	if interfaceAddr4 == nil && interfaceAddr6 == nil {
-		return nil, fmt.Errorf("interface %s has no usable IPv4 or IPv6 address", specifiedInterface)
+		return nil, configError(fmt.Sprintf("interface %s has no usable IPv4 or IPv6 address", specifiedInterface))
 	}
 	}
 
 
 	// In the case that there's exactly one IPv4 address
 	// In the case that there's exactly one IPv4 address
@@ -296,7 +295,7 @@ func listSystemIPs() []net.IP {
 
 
 func errMultipleIPs(interfaceA, interfaceB string, addrA, addrB net.IP) error {
 func errMultipleIPs(interfaceA, interfaceB string, addrA, addrB net.IP) error {
 	if interfaceA == interfaceB {
 	if interfaceA == interfaceB {
-		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)
+		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))
 	}
 	}
-	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)
+	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))
 }
 }

+ 5 - 7
daemon/cluster/networks.go

@@ -3,7 +3,6 @@ package cluster
 import (
 import (
 	"fmt"
 	"fmt"
 
 
-	apierrors "github.com/docker/docker/api/errors"
 	apitypes "github.com/docker/docker/api/types"
 	apitypes "github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/network"
 	"github.com/docker/docker/api/types/network"
 	types "github.com/docker/docker/api/types/swarm"
 	types "github.com/docker/docker/api/types/swarm"
@@ -250,8 +249,8 @@ func (c *Cluster) DetachNetwork(target string, containerID string) error {
 // CreateNetwork creates a new cluster managed network.
 // CreateNetwork creates a new cluster managed network.
 func (c *Cluster) CreateNetwork(s apitypes.NetworkCreateRequest) (string, error) {
 func (c *Cluster) CreateNetwork(s apitypes.NetworkCreateRequest) (string, error) {
 	if runconfig.IsPreDefinedNetwork(s.Name) {
 	if runconfig.IsPreDefinedNetwork(s.Name) {
-		err := fmt.Errorf("%s is a pre-defined network and cannot be created", s.Name)
-		return "", apierrors.NewRequestForbiddenError(err)
+		err := notAllowedError(fmt.Sprintf("%s is a pre-defined network and cannot be created", s.Name))
+		return "", errors.WithStack(err)
 	}
 	}
 
 
 	var resp *swarmapi.CreateNetworkResponse
 	var resp *swarmapi.CreateNetworkResponse
@@ -299,14 +298,13 @@ func (c *Cluster) populateNetworkID(ctx context.Context, client swarmapi.Control
 				// and use its id for the request.
 				// and use its id for the request.
 				apiNetwork, err = getNetwork(ctx, client, ln.Name())
 				apiNetwork, err = getNetwork(ctx, client, ln.Name())
 				if err != nil {
 				if err != nil {
-					err = fmt.Errorf("could not find the corresponding predefined swarm network: %v", err)
-					return apierrors.NewRequestNotFoundError(err)
+					return errors.Wrap(notFoundError{err}, "could not find the corresponding predefined swarm network")
 				}
 				}
 				goto setid
 				goto setid
 			}
 			}
 			if ln != nil && !ln.Info().Dynamic() {
 			if ln != nil && !ln.Info().Dynamic() {
-				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())
-				return apierrors.NewRequestForbiddenError(err)
+				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())
+				return errors.WithStack(notAllowedError(errMsg))
 			}
 			}
 			return err
 			return err
 		}
 		}

+ 1 - 2
daemon/cluster/nodes.go

@@ -1,7 +1,6 @@
 package cluster
 package cluster
 
 
 import (
 import (
-	apierrors "github.com/docker/docker/api/errors"
 	apitypes "github.com/docker/docker/api/types"
 	apitypes "github.com/docker/docker/api/types"
 	types "github.com/docker/docker/api/types/swarm"
 	types "github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/daemon/cluster/convert"
 	"github.com/docker/docker/daemon/cluster/convert"
@@ -65,7 +64,7 @@ func (c *Cluster) UpdateNode(input string, version uint64, spec types.NodeSpec)
 	return c.lockedManagerAction(func(ctx context.Context, state nodeState) error {
 	return c.lockedManagerAction(func(ctx context.Context, state nodeState) error {
 		nodeSpec, err := convert.NodeSpecToGRPC(spec)
 		nodeSpec, err := convert.NodeSpecToGRPC(spec)
 		if err != nil {
 		if err != nil {
-			return apierrors.NewBadRequestError(err)
+			return convertError{err}
 		}
 		}
 
 
 		ctx, cancel := c.getRequestContext()
 		ctx, cancel := c.getRequestContext()

+ 2 - 3
daemon/cluster/services.go

@@ -11,7 +11,6 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/docker/distribution/reference"
 	"github.com/docker/distribution/reference"
-	apierrors "github.com/docker/docker/api/errors"
 	apitypes "github.com/docker/docker/api/types"
 	apitypes "github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/backend"
 	"github.com/docker/docker/api/types/backend"
 	types "github.com/docker/docker/api/types/swarm"
 	types "github.com/docker/docker/api/types/swarm"
@@ -129,7 +128,7 @@ func (c *Cluster) CreateService(s types.ServiceSpec, encodedAuth string, queryRe
 
 
 		serviceSpec, err := convert.ServiceSpecToGRPC(s)
 		serviceSpec, err := convert.ServiceSpecToGRPC(s)
 		if err != nil {
 		if err != nil {
-			return apierrors.NewBadRequestError(err)
+			return convertError{err}
 		}
 		}
 
 
 		resp = &apitypes.ServiceCreateResponse{}
 		resp = &apitypes.ServiceCreateResponse{}
@@ -233,7 +232,7 @@ func (c *Cluster) UpdateService(serviceIDOrName string, version uint64, spec typ
 
 
 		serviceSpec, err := convert.ServiceSpecToGRPC(spec)
 		serviceSpec, err := convert.ServiceSpecToGRPC(spec)
 		if err != nil {
 		if err != nil {
-			return apierrors.NewBadRequestError(err)
+			return convertError{err}
 		}
 		}
 
 
 		currentService, err := getService(ctx, state.controlClient, serviceIDOrName, false)
 		currentService, err := getService(ctx, state.controlClient, serviceIDOrName, false)

+ 12 - 13
daemon/cluster/swarm.go

@@ -6,7 +6,6 @@ import (
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
-	apierrors "github.com/docker/docker/api/errors"
 	apitypes "github.com/docker/docker/api/types"
 	apitypes "github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/filters"
 	types "github.com/docker/docker/api/types/swarm"
 	types "github.com/docker/docker/api/types/swarm"
@@ -41,7 +40,7 @@ func (c *Cluster) Init(req types.InitRequest) (string, error) {
 	}
 	}
 
 
 	if err := validateAndSanitizeInitRequest(&req); err != nil {
 	if err := validateAndSanitizeInitRequest(&req); err != nil {
-		return "", apierrors.NewBadRequestError(err)
+		return "", validationError{err}
 	}
 	}
 
 
 	listenHost, listenPort, err := resolveListenAddr(req.ListenAddr)
 	listenHost, listenPort, err := resolveListenAddr(req.ListenAddr)
@@ -132,12 +131,12 @@ func (c *Cluster) Join(req types.JoinRequest) error {
 	c.mu.Lock()
 	c.mu.Lock()
 	if c.nr != nil {
 	if c.nr != nil {
 		c.mu.Unlock()
 		c.mu.Unlock()
-		return errSwarmExists
+		return errors.WithStack(errSwarmExists)
 	}
 	}
 	c.mu.Unlock()
 	c.mu.Unlock()
 
 
 	if err := validateAndSanitizeJoinRequest(&req); err != nil {
 	if err := validateAndSanitizeJoinRequest(&req); err != nil {
-		return apierrors.NewBadRequestError(err)
+		return validationError{err}
 	}
 	}
 
 
 	listenHost, listenPort, err := resolveListenAddr(req.ListenAddr)
 	listenHost, listenPort, err := resolveListenAddr(req.ListenAddr)
@@ -222,7 +221,7 @@ func (c *Cluster) Update(version uint64, spec types.Spec, flags types.UpdateFlag
 		// will be used to swarmkit.
 		// will be used to swarmkit.
 		clusterSpec, err := convert.SwarmSpecToGRPC(spec)
 		clusterSpec, err := convert.SwarmSpecToGRPC(spec)
 		if err != nil {
 		if err != nil {
-			return apierrors.NewBadRequestError(err)
+			return convertError{err}
 		}
 		}
 
 
 		_, err = state.controlClient.UpdateCluster(
 		_, err = state.controlClient.UpdateCluster(
@@ -284,7 +283,7 @@ func (c *Cluster) UnlockSwarm(req types.UnlockRequest) error {
 	} else {
 	} else {
 		// when manager is active, return an error of "not locked"
 		// when manager is active, return an error of "not locked"
 		c.mu.RUnlock()
 		c.mu.RUnlock()
-		return errors.New("swarm is not locked")
+		return notLockedError{}
 	}
 	}
 
 
 	// only when swarm is locked, code running reaches here
 	// only when swarm is locked, code running reaches here
@@ -293,7 +292,7 @@ func (c *Cluster) UnlockSwarm(req types.UnlockRequest) error {
 
 
 	key, err := encryption.ParseHumanReadableKey(req.UnlockKey)
 	key, err := encryption.ParseHumanReadableKey(req.UnlockKey)
 	if err != nil {
 	if err != nil {
-		return err
+		return validationError{err}
 	}
 	}
 
 
 	config := nr.config
 	config := nr.config
@@ -312,9 +311,9 @@ func (c *Cluster) UnlockSwarm(req types.UnlockRequest) error {
 
 
 	if err := <-nr.Ready(); err != nil {
 	if err := <-nr.Ready(); err != nil {
 		if errors.Cause(err) == errSwarmLocked {
 		if errors.Cause(err) == errSwarmLocked {
-			return errors.New("swarm could not be unlocked: invalid key provided")
+			return invalidUnlockKey{}
 		}
 		}
-		return fmt.Errorf("swarm component could not be started: %v", err)
+		return errors.Errorf("swarm component could not be started: %v", err)
 	}
 	}
 	return nil
 	return nil
 }
 }
@@ -328,7 +327,7 @@ func (c *Cluster) Leave(force bool) error {
 	nr := c.nr
 	nr := c.nr
 	if nr == nil {
 	if nr == nil {
 		c.mu.Unlock()
 		c.mu.Unlock()
-		return errNoSwarm
+		return errors.WithStack(errNoSwarm)
 	}
 	}
 
 
 	state := c.currentNodeState()
 	state := c.currentNodeState()
@@ -337,7 +336,7 @@ func (c *Cluster) Leave(force bool) error {
 
 
 	if errors.Cause(state.err) == errSwarmLocked && !force {
 	if errors.Cause(state.err) == errSwarmLocked && !force {
 		// leave a locked swarm without --force is not allowed
 		// leave a locked swarm without --force is not allowed
-		return errors.New("Swarm is encrypted and locked. Please unlock it first or use `--force` to ignore this message.")
+		return errors.WithStack(notAvailableError("Swarm is encrypted and locked. Please unlock it first or use `--force` to ignore this message."))
 	}
 	}
 
 
 	if state.IsManager() && !force {
 	if state.IsManager() && !force {
@@ -348,7 +347,7 @@ func (c *Cluster) Leave(force bool) error {
 				if active && removingManagerCausesLossOfQuorum(reachable, unreachable) {
 				if active && removingManagerCausesLossOfQuorum(reachable, unreachable) {
 					if isLastManager(reachable, unreachable) {
 					if isLastManager(reachable, unreachable) {
 						msg += "Removing the last manager erases all current state of the swarm. Use `--force` to ignore this message. "
 						msg += "Removing the last manager erases all current state of the swarm. Use `--force` to ignore this message. "
-						return errors.New(msg)
+						return errors.WithStack(notAvailableError(msg))
 					}
 					}
 					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)
 					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)
 				}
 				}
@@ -358,7 +357,7 @@ func (c *Cluster) Leave(force bool) error {
 		}
 		}
 
 
 		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."
 		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."
-		return errors.New(msg)
+		return errors.WithStack(notAvailableError(msg))
 	}
 	}
 	// release readers in here
 	// release readers in here
 	if err := nr.Stop(); err != nil {
 	if err := nr.Stop(); err != nil {

+ 15 - 16
daemon/container.go

@@ -6,7 +6,6 @@ import (
 	"path/filepath"
 	"path/filepath"
 	"time"
 	"time"
 
 
-	"github.com/docker/docker/api/errors"
 	containertypes "github.com/docker/docker/api/types/container"
 	containertypes "github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/strslice"
 	"github.com/docker/docker/api/types/strslice"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/container"
@@ -19,6 +18,7 @@ import (
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/go-connections/nat"
 	"github.com/docker/go-connections/nat"
 	"github.com/opencontainers/selinux/go-selinux/label"
 	"github.com/opencontainers/selinux/go-selinux/label"
+	"github.com/pkg/errors"
 )
 )
 
 
 // GetContainer looks for a container using the provided information, which could be
 // GetContainer looks for a container using the provided information, which could be
@@ -30,7 +30,7 @@ import (
 //  If none of these searches succeed, an error is returned
 //  If none of these searches succeed, an error is returned
 func (daemon *Daemon) GetContainer(prefixOrName string) (*container.Container, error) {
 func (daemon *Daemon) GetContainer(prefixOrName string) (*container.Container, error) {
 	if len(prefixOrName) == 0 {
 	if len(prefixOrName) == 0 {
-		return nil, errors.NewBadRequestError(fmt.Errorf("No container name or ID supplied"))
+		return nil, errors.WithStack(invalidIdentifier(prefixOrName))
 	}
 	}
 
 
 	if containerByID := daemon.containers.Get(prefixOrName); containerByID != nil {
 	if containerByID := daemon.containers.Get(prefixOrName); containerByID != nil {
@@ -48,10 +48,9 @@ func (daemon *Daemon) GetContainer(prefixOrName string) (*container.Container, e
 	if indexError != nil {
 	if indexError != nil {
 		// When truncindex defines an error type, use that instead
 		// When truncindex defines an error type, use that instead
 		if indexError == truncindex.ErrNotExist {
 		if indexError == truncindex.ErrNotExist {
-			err := fmt.Errorf("No such container: %s", prefixOrName)
-			return nil, errors.NewRequestNotFoundError(err)
+			return nil, containerNotFound(prefixOrName)
 		}
 		}
-		return nil, indexError
+		return nil, systemError{indexError}
 	}
 	}
 	return daemon.containers.Get(containerID), nil
 	return daemon.containers.Get(containerID), nil
 }
 }
@@ -136,7 +135,7 @@ func (daemon *Daemon) newContainer(name string, platform string, config *contain
 		if config.Hostname == "" {
 		if config.Hostname == "" {
 			config.Hostname, err = os.Hostname()
 			config.Hostname, err = os.Hostname()
 			if err != nil {
 			if err != nil {
-				return nil, err
+				return nil, systemError{err}
 			}
 			}
 		}
 		}
 	} else {
 	} else {
@@ -255,19 +254,19 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *containertypes.HostCon
 		// Validate the healthcheck params of Config
 		// Validate the healthcheck params of Config
 		if config.Healthcheck != nil {
 		if config.Healthcheck != nil {
 			if config.Healthcheck.Interval != 0 && config.Healthcheck.Interval < containertypes.MinimumDuration {
 			if config.Healthcheck.Interval != 0 && config.Healthcheck.Interval < containertypes.MinimumDuration {
-				return nil, fmt.Errorf("Interval in Healthcheck cannot be less than %s", containertypes.MinimumDuration)
+				return nil, errors.Errorf("Interval in Healthcheck cannot be less than %s", containertypes.MinimumDuration)
 			}
 			}
 
 
 			if config.Healthcheck.Timeout != 0 && config.Healthcheck.Timeout < containertypes.MinimumDuration {
 			if config.Healthcheck.Timeout != 0 && config.Healthcheck.Timeout < containertypes.MinimumDuration {
-				return nil, fmt.Errorf("Timeout in Healthcheck cannot be less than %s", containertypes.MinimumDuration)
+				return nil, errors.Errorf("Timeout in Healthcheck cannot be less than %s", containertypes.MinimumDuration)
 			}
 			}
 
 
 			if config.Healthcheck.Retries < 0 {
 			if config.Healthcheck.Retries < 0 {
-				return nil, fmt.Errorf("Retries in Healthcheck cannot be negative")
+				return nil, errors.Errorf("Retries in Healthcheck cannot be negative")
 			}
 			}
 
 
 			if config.Healthcheck.StartPeriod != 0 && config.Healthcheck.StartPeriod < containertypes.MinimumDuration {
 			if config.Healthcheck.StartPeriod != 0 && config.Healthcheck.StartPeriod < containertypes.MinimumDuration {
-				return nil, fmt.Errorf("StartPeriod in Healthcheck cannot be less than %s", containertypes.MinimumDuration)
+				return nil, errors.Errorf("StartPeriod in Healthcheck cannot be less than %s", containertypes.MinimumDuration)
 			}
 			}
 		}
 		}
 	}
 	}
@@ -277,7 +276,7 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *containertypes.HostCon
 	}
 	}
 
 
 	if hostConfig.AutoRemove && !hostConfig.RestartPolicy.IsNone() {
 	if hostConfig.AutoRemove && !hostConfig.RestartPolicy.IsNone() {
-		return nil, fmt.Errorf("can't create 'AutoRemove' container with restart policy")
+		return nil, errors.Errorf("can't create 'AutoRemove' container with restart policy")
 	}
 	}
 
 
 	for _, extraHost := range hostConfig.ExtraHosts {
 	for _, extraHost := range hostConfig.ExtraHosts {
@@ -289,12 +288,12 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *containertypes.HostCon
 	for port := range hostConfig.PortBindings {
 	for port := range hostConfig.PortBindings {
 		_, portStr := nat.SplitProtoPort(string(port))
 		_, portStr := nat.SplitProtoPort(string(port))
 		if _, err := nat.ParsePort(portStr); err != nil {
 		if _, err := nat.ParsePort(portStr); err != nil {
-			return nil, fmt.Errorf("invalid port specification: %q", portStr)
+			return nil, errors.Errorf("invalid port specification: %q", portStr)
 		}
 		}
 		for _, pb := range hostConfig.PortBindings[port] {
 		for _, pb := range hostConfig.PortBindings[port] {
 			_, err := nat.NewPort(nat.SplitProtoPort(pb.HostPort))
 			_, err := nat.NewPort(nat.SplitProtoPort(pb.HostPort))
 			if err != nil {
 			if err != nil {
-				return nil, fmt.Errorf("invalid port specification: %q", pb.HostPort)
+				return nil, errors.Errorf("invalid port specification: %q", pb.HostPort)
 			}
 			}
 		}
 		}
 	}
 	}
@@ -304,16 +303,16 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *containertypes.HostCon
 	switch p.Name {
 	switch p.Name {
 	case "always", "unless-stopped", "no":
 	case "always", "unless-stopped", "no":
 		if p.MaximumRetryCount != 0 {
 		if p.MaximumRetryCount != 0 {
-			return nil, fmt.Errorf("maximum retry count cannot be used with restart policy '%s'", p.Name)
+			return nil, errors.Errorf("maximum retry count cannot be used with restart policy '%s'", p.Name)
 		}
 		}
 	case "on-failure":
 	case "on-failure":
 		if p.MaximumRetryCount < 0 {
 		if p.MaximumRetryCount < 0 {
-			return nil, fmt.Errorf("maximum retry count cannot be negative")
+			return nil, errors.Errorf("maximum retry count cannot be negative")
 		}
 		}
 	case "":
 	case "":
 		// do nothing
 		// do nothing
 	default:
 	default:
-		return nil, fmt.Errorf("invalid restart policy '%s'", p.Name)
+		return nil, errors.Errorf("invalid restart policy '%s'", p.Name)
 	}
 	}
 
 
 	// Now do platform-specific verification
 	// Now do platform-specific verification

+ 1 - 1
daemon/container_linux.go

@@ -14,7 +14,7 @@ func (daemon *Daemon) saveApparmorConfig(container *container.Container) error {
 	}
 	}
 
 
 	if err := parseSecurityOpt(container, container.HostConfig); err != nil {
 	if err := parseSecurityOpt(container, container.HostConfig); err != nil {
-		return err
+		return validationError{err}
 	}
 	}
 
 
 	if !container.HostConfig.Privileged {
 	if !container.HostConfig.Privileged {

+ 1 - 2
daemon/container_operations.go

@@ -10,7 +10,6 @@ import (
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
-	derr "github.com/docker/docker/api/errors"
 	containertypes "github.com/docker/docker/api/types/container"
 	containertypes "github.com/docker/docker/api/types/container"
 	networktypes "github.com/docker/docker/api/types/network"
 	networktypes "github.com/docker/docker/api/types/network"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/container"
@@ -923,7 +922,7 @@ func (daemon *Daemon) getNetworkedContainer(containerID, connectedContainerID st
 	}
 	}
 	if !nc.IsRunning() {
 	if !nc.IsRunning() {
 		err := fmt.Errorf("cannot join network of a non running container: %s", connectedContainerID)
 		err := fmt.Errorf("cannot join network of a non running container: %s", connectedContainerID)
-		return nil, derr.NewRequestConflictError(err)
+		return nil, stateConflictError{err}
 	}
 	}
 	if nc.IsRestarting() {
 	if nc.IsRestarting() {
 		return nil, errContainerIsRestarting(connectedContainerID)
 		return nil, errContainerIsRestarting(connectedContainerID)

+ 1 - 1
daemon/container_operations_unix.go

@@ -91,7 +91,7 @@ func (daemon *Daemon) getPidContainer(container *container.Container) (*containe
 
 
 func containerIsRunning(c *container.Container) error {
 func containerIsRunning(c *container.Container) error {
 	if !c.IsRunning() {
 	if !c.IsRunning() {
-		return errors.Errorf("container %s is not running", c.ID)
+		return stateConflictError{errors.Errorf("container %s is not running", c.ID)}
 	}
 	}
 	return nil
 	return nil
 }
 }

+ 13 - 15
daemon/create.go

@@ -9,7 +9,6 @@ import (
 
 
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 
 
-	apierrors "github.com/docker/docker/api/errors"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	containertypes "github.com/docker/docker/api/types/container"
 	containertypes "github.com/docker/docker/api/types/container"
 	networktypes "github.com/docker/docker/api/types/network"
 	networktypes "github.com/docker/docker/api/types/network"
@@ -37,17 +36,17 @@ func (daemon *Daemon) ContainerCreate(params types.ContainerCreateConfig) (conta
 func (daemon *Daemon) containerCreate(params types.ContainerCreateConfig, managed bool) (containertypes.ContainerCreateCreatedBody, error) {
 func (daemon *Daemon) containerCreate(params types.ContainerCreateConfig, managed bool) (containertypes.ContainerCreateCreatedBody, error) {
 	start := time.Now()
 	start := time.Now()
 	if params.Config == nil {
 	if params.Config == nil {
-		return containertypes.ContainerCreateCreatedBody{}, fmt.Errorf("Config cannot be empty in order to create a container")
+		return containertypes.ContainerCreateCreatedBody{}, validationError{errors.New("Config cannot be empty in order to create a container")}
 	}
 	}
 
 
 	warnings, err := daemon.verifyContainerSettings(params.HostConfig, params.Config, false)
 	warnings, err := daemon.verifyContainerSettings(params.HostConfig, params.Config, false)
 	if err != nil {
 	if err != nil {
-		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, err
+		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, validationError{err}
 	}
 	}
 
 
-	err = daemon.verifyNetworkingConfig(params.NetworkingConfig)
+	err = verifyNetworkingConfig(params.NetworkingConfig)
 	if err != nil {
 	if err != nil {
-		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, err
+		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, validationError{err}
 	}
 	}
 
 
 	if params.HostConfig == nil {
 	if params.HostConfig == nil {
@@ -55,12 +54,12 @@ func (daemon *Daemon) containerCreate(params types.ContainerCreateConfig, manage
 	}
 	}
 	err = daemon.adaptContainerSettings(params.HostConfig, params.AdjustCPUShares)
 	err = daemon.adaptContainerSettings(params.HostConfig, params.AdjustCPUShares)
 	if err != nil {
 	if err != nil {
-		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, err
+		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, validationError{err}
 	}
 	}
 
 
 	container, err := daemon.create(params, managed)
 	container, err := daemon.create(params, managed)
 	if err != nil {
 	if err != nil {
-		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, daemon.imageNotExistToErrcode(err)
+		return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, err
 	}
 	}
 	containerActions.WithValues("create").UpdateSince(start)
 	containerActions.WithValues("create").UpdateSince(start)
 
 
@@ -113,11 +112,11 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig, managed bool) (
 	}
 	}
 
 
 	if err := daemon.mergeAndVerifyConfig(params.Config, img); err != nil {
 	if err := daemon.mergeAndVerifyConfig(params.Config, img); err != nil {
-		return nil, err
+		return nil, validationError{err}
 	}
 	}
 
 
 	if err := daemon.mergeAndVerifyLogConfig(&params.HostConfig.LogConfig); err != nil {
 	if err := daemon.mergeAndVerifyLogConfig(&params.HostConfig.LogConfig); err != nil {
-		return nil, err
+		return nil, validationError{err}
 	}
 	}
 
 
 	if container, err = daemon.newContainer(params.Name, params.Platform, params.Config, params.HostConfig, imgID, managed); err != nil {
 	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) (
 
 
 	// Set RWLayer for container after mount labels have been set
 	// Set RWLayer for container after mount labels have been set
 	if err := daemon.setRWLayer(container); err != nil {
 	if err := daemon.setRWLayer(container); err != nil {
-		return nil, err
+		return nil, systemError{err}
 	}
 	}
 
 
 	rootIDs := daemon.idMappings.RootPair()
 	rootIDs := daemon.idMappings.RootPair()
@@ -295,7 +294,7 @@ func (daemon *Daemon) mergeAndVerifyConfig(config *containertypes.Config, img *i
 
 
 // Checks if the client set configurations for more than one network while creating a container
 // Checks if the client set configurations for more than one network while creating a container
 // Also checks if the IPAMConfig is valid
 // Also checks if the IPAMConfig is valid
-func (daemon *Daemon) verifyNetworkingConfig(nwConfig *networktypes.NetworkingConfig) error {
+func verifyNetworkingConfig(nwConfig *networktypes.NetworkingConfig) error {
 	if nwConfig == nil || len(nwConfig.EndpointsConfig) == 0 {
 	if nwConfig == nil || len(nwConfig.EndpointsConfig) == 0 {
 		return nil
 		return nil
 	}
 	}
@@ -303,14 +302,14 @@ func (daemon *Daemon) verifyNetworkingConfig(nwConfig *networktypes.NetworkingCo
 		for _, v := range nwConfig.EndpointsConfig {
 		for _, v := range nwConfig.EndpointsConfig {
 			if v != nil && v.IPAMConfig != nil {
 			if v != nil && v.IPAMConfig != nil {
 				if v.IPAMConfig.IPv4Address != "" && net.ParseIP(v.IPAMConfig.IPv4Address).To4() == nil {
 				if v.IPAMConfig.IPv4Address != "" && net.ParseIP(v.IPAMConfig.IPv4Address).To4() == nil {
-					return apierrors.NewBadRequestError(fmt.Errorf("invalid IPv4 address: %s", v.IPAMConfig.IPv4Address))
+					return errors.Errorf("invalid IPv4 address: %s", v.IPAMConfig.IPv4Address)
 				}
 				}
 				if v.IPAMConfig.IPv6Address != "" {
 				if v.IPAMConfig.IPv6Address != "" {
 					n := net.ParseIP(v.IPAMConfig.IPv6Address)
 					n := net.ParseIP(v.IPAMConfig.IPv6Address)
 					// if the address is an invalid network address (ParseIP == nil) or if it is
 					// if the address is an invalid network address (ParseIP == nil) or if it is
 					// an IPv4 address (To4() != nil), then it is an invalid IPv6 address
 					// an IPv4 address (To4() != nil), then it is an invalid IPv6 address
 					if n == nil || n.To4() != nil {
 					if n == nil || n.To4() != nil {
-						return apierrors.NewBadRequestError(fmt.Errorf("invalid IPv6 address: %s", v.IPAMConfig.IPv6Address))
+						return errors.Errorf("invalid IPv6 address: %s", v.IPAMConfig.IPv6Address)
 					}
 					}
 				}
 				}
 			}
 			}
@@ -321,6 +320,5 @@ func (daemon *Daemon) verifyNetworkingConfig(nwConfig *networktypes.NetworkingCo
 	for k := range nwConfig.EndpointsConfig {
 	for k := range nwConfig.EndpointsConfig {
 		l = append(l, k)
 		l = append(l, k)
 	}
 	}
-	err := fmt.Errorf("Container cannot be connected to network endpoints: %s", strings.Join(l, ", "))
-	return apierrors.NewBadRequestError(err)
+	return errors.Errorf("Container cannot be connected to network endpoints: %s", strings.Join(l, ", "))
 }
 }

+ 5 - 4
daemon/daemon_unix.go

@@ -470,6 +470,7 @@ func verifyContainerResources(resources *containertypes.Resources, sysInfo *sysi
 		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.")
 		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.")
 		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.")
 		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.")
 		resources.BlkioDeviceWriteBps = []*pblkiodev.ThrottleDevice{}
 		resources.BlkioDeviceWriteBps = []*pblkiodev.ThrottleDevice{}
+
 	}
 	}
 	if len(resources.BlkioDeviceReadIOps) > 0 && !sysInfo.BlkioReadIOpsDevice {
 	if len(resources.BlkioDeviceReadIOps) > 0 && !sysInfo.BlkioReadIOpsDevice {
 		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.")
 		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 *
 		}
 		}
 		child, err := daemon.GetContainer(name)
 		child, err := daemon.GetContainer(name)
 		if err != nil {
 		if err != nil {
-			return fmt.Errorf("Could not get container for %s", name)
+			return errors.Wrapf(err, "could not get container for %s", name)
 		}
 		}
 		for child.HostConfig.NetworkMode.IsContainer() {
 		for child.HostConfig.NetworkMode.IsContainer() {
 			parts := strings.SplitN(string(child.HostConfig.NetworkMode), ":", 2)
 			parts := strings.SplitN(string(child.HostConfig.NetworkMode), ":", 2)
 			child, err = daemon.GetContainer(parts[1])
 			child, err = daemon.GetContainer(parts[1])
 			if err != nil {
 			if err != nil {
-				return fmt.Errorf("Could not get container for %s", parts[1])
+				return errors.Wrapf(err, "Could not get container for %s", parts[1])
 			}
 			}
 		}
 		}
 		if child.HostConfig.NetworkMode.IsHost() {
 		if child.HostConfig.NetworkMode.IsHost() {
@@ -1181,12 +1182,12 @@ func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container
 
 
 func (daemon *Daemon) stats(c *container.Container) (*types.StatsJSON, error) {
 func (daemon *Daemon) stats(c *container.Container) (*types.StatsJSON, error) {
 	if !c.IsRunning() {
 	if !c.IsRunning() {
-		return nil, errNotRunning{c.ID}
+		return nil, errNotRunning(c.ID)
 	}
 	}
 	stats, err := daemon.containerd.Stats(c.ID)
 	stats, err := daemon.containerd.Stats(c.ID)
 	if err != nil {
 	if err != nil {
 		if strings.Contains(err.Error(), "container not found") {
 		if strings.Contains(err.Error(), "container not found") {
-			return nil, errNotFound{c.ID}
+			return nil, containerNotFound(c.ID)
 		}
 		}
 		return nil, err
 		return nil, err
 	}
 	}

+ 2 - 2
daemon/daemon_windows.go

@@ -520,14 +520,14 @@ func driverOptions(config *config.Config) []nwconfig.Option {
 
 
 func (daemon *Daemon) stats(c *container.Container) (*types.StatsJSON, error) {
 func (daemon *Daemon) stats(c *container.Container) (*types.StatsJSON, error) {
 	if !c.IsRunning() {
 	if !c.IsRunning() {
-		return nil, errNotRunning{c.ID}
+		return nil, errNotRunning(c.ID)
 	}
 	}
 
 
 	// Obtain the stats from HCS via libcontainerd
 	// Obtain the stats from HCS via libcontainerd
 	stats, err := daemon.containerd.Stats(c.ID)
 	stats, err := daemon.containerd.Stats(c.ID)
 	if err != nil {
 	if err != nil {
 		if strings.Contains(err.Error(), "container not found") {
 		if strings.Contains(err.Error(), "container not found") {
-			return nil, errNotFound{c.ID}
+			return nil, containerNotFound(c.ID)
 		}
 		}
 		return nil, err
 		return nil, err
 	}
 	}

+ 3 - 4
daemon/delete.go

@@ -7,7 +7,6 @@ import (
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
-	apierrors "github.com/docker/docker/api/errors"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/layer"
 	"github.com/docker/docker/layer"
@@ -31,7 +30,7 @@ func (daemon *Daemon) ContainerRm(name string, config *types.ContainerRmConfig)
 	// Container state RemovalInProgress should be used to avoid races.
 	// Container state RemovalInProgress should be used to avoid races.
 	if inProgress := container.SetRemovalInProgress(); inProgress {
 	if inProgress := container.SetRemovalInProgress(); inProgress {
 		err := fmt.Errorf("removal of container %s is already in progress", name)
 		err := fmt.Errorf("removal of container %s is already in progress", name)
-		return apierrors.NewBadRequestError(err)
+		return stateConflictError{err}
 	}
 	}
 	defer container.ResetRemovalInProgress()
 	defer container.ResetRemovalInProgress()
 
 
@@ -87,7 +86,7 @@ func (daemon *Daemon) cleanupContainer(container *container.Container, forceRemo
 				procedure = "Unpause and then " + strings.ToLower(procedure)
 				procedure = "Unpause and then " + strings.ToLower(procedure)
 			}
 			}
 			err := fmt.Errorf("You cannot remove a %s container %s. %s", state, container.ID, procedure)
 			err := fmt.Errorf("You cannot remove a %s container %s. %s", state, container.ID, procedure)
-			return apierrors.NewRequestConflictError(err)
+			return stateConflictError{err}
 		}
 		}
 		if err := daemon.Kill(container); err != nil {
 		if err := daemon.Kill(container); err != nil {
 			return fmt.Errorf("Could not kill running container %s, cannot remove - %v", container.ID, err)
 			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
 func (daemon *Daemon) VolumeRm(name string, force bool) error {
 func (daemon *Daemon) VolumeRm(name string, force bool) error {
 	err := daemon.volumeRm(name)
 	err := daemon.volumeRm(name)
 	if err != nil && volumestore.IsInUse(err) {
 	if err != nil && volumestore.IsInUse(err) {
-		return apierrors.NewRequestConflictError(err)
+		return stateConflictError{err}
 	}
 	}
 	if err == nil || force {
 	if err == nil || force {
 		daemon.volumes.Purge(name)
 		daemon.volumes.Purge(name)

+ 187 - 24
daemon/errors.go

@@ -2,52 +2,215 @@ package daemon
 
 
 import (
 import (
 	"fmt"
 	"fmt"
+	"strings"
+	"syscall"
 
 
-	"github.com/docker/docker/api/errors"
+	"github.com/pkg/errors"
+	"google.golang.org/grpc"
 )
 )
 
 
-func (d *Daemon) imageNotExistToErrcode(err error) error {
-	if dne, isDNE := err.(ErrImageDoesNotExist); isDNE {
-		return errors.NewRequestNotFoundError(dne)
-	}
-	return err
+func errNotRunning(id string) error {
+	return stateConflictError{errors.Errorf("Container %s is not running", id)}
+}
+
+func containerNotFound(id string) error {
+	return objNotFoundError{"container", id}
+}
+
+func volumeNotFound(id string) error {
+	return objNotFoundError{"volume", id}
+}
+
+func networkNotFound(id string) error {
+	return objNotFoundError{"network", id}
+}
+
+type objNotFoundError struct {
+	object string
+	id     string
 }
 }
 
 
-type errNotRunning struct {
-	containerID string
+func (e objNotFoundError) Error() string {
+	return "No such " + e.object + ": " + e.id
 }
 }
 
 
-func (e errNotRunning) Error() string {
-	return fmt.Sprintf("Container %s is not running", e.containerID)
+func (e objNotFoundError) NotFound() {}
+
+type stateConflictError struct {
+	cause error
+}
+
+func (e stateConflictError) Error() string {
+	return e.cause.Error()
 }
 }
 
 
-func (e errNotRunning) ContainerIsRunning() bool {
-	return false
+func (e stateConflictError) Cause() error {
+	return e.cause
 }
 }
 
 
+func (e stateConflictError) Conflict() {}
+
 func errContainerIsRestarting(containerID string) error {
 func errContainerIsRestarting(containerID string) error {
-	err := fmt.Errorf("Container %s is restarting, wait until the container is running", containerID)
-	return errors.NewRequestConflictError(err)
+	cause := errors.Errorf("Container %s is restarting, wait until the container is running", containerID)
+	return stateConflictError{cause}
 }
 }
 
 
 func errExecNotFound(id string) error {
 func errExecNotFound(id string) error {
-	err := fmt.Errorf("No such exec instance '%s' found in daemon", id)
-	return errors.NewRequestNotFoundError(err)
+	return objNotFoundError{"exec instance", id}
 }
 }
 
 
 func errExecPaused(id string) error {
 func errExecPaused(id string) error {
-	err := fmt.Errorf("Container %s is paused, unpause the container before exec", id)
-	return errors.NewRequestConflictError(err)
+	cause := errors.Errorf("Container %s is paused, unpause the container before exec", id)
+	return stateConflictError{cause}
+}
+
+type validationError struct {
+	cause error
+}
+
+func (e validationError) Error() string {
+	return e.cause.Error()
+}
+
+func (e validationError) InvalidParameter() {}
+
+func (e validationError) Cause() error {
+	return e.cause
+}
+
+type notAllowedError struct {
+	cause error
+}
+
+func (e notAllowedError) Error() string {
+	return e.cause.Error()
+}
+
+func (e notAllowedError) Forbidden() {}
+
+func (e notAllowedError) Cause() error {
+	return e.cause
 }
 }
 
 
-type errNotFound struct {
-	containerID string
+type containerNotModifiedError struct {
+	running bool
 }
 }
 
 
-func (e errNotFound) Error() string {
-	return fmt.Sprintf("Container %s is not found", e.containerID)
+func (e containerNotModifiedError) Error() string {
+	if e.running {
+		return "Container is already started"
+	}
+	return "Container is already stopped"
+}
+
+func (e containerNotModifiedError) NotModified() {}
+
+type systemError struct {
+	cause error
+}
+
+func (e systemError) Error() string {
+	return e.cause.Error()
+}
+
+func (e systemError) SystemError() {}
+
+func (e systemError) Cause() error {
+	return e.cause
 }
 }
 
 
-func (e errNotFound) ContainerNotFound() bool {
-	return true
+type invalidIdentifier string
+
+func (e invalidIdentifier) Error() string {
+	return fmt.Sprintf("invalid name or ID supplied: %q", string(e))
+}
+
+func (invalidIdentifier) InvalidParameter() {}
+
+type duplicateMountPointError string
+
+func (e duplicateMountPointError) Error() string {
+	return "Duplicate mount point: " + string(e)
+}
+func (duplicateMountPointError) InvalidParameter() {}
+
+type containerFileNotFound struct {
+	file      string
+	container string
+}
+
+func (e containerFileNotFound) Error() string {
+	return "Could not find the file " + e.file + " in container " + e.container
+}
+
+func (containerFileNotFound) NotFound() {}
+
+type invalidFilter struct {
+	filter string
+	value  interface{}
+}
+
+func (e invalidFilter) Error() string {
+	msg := "Invalid filter '" + e.filter
+	if e.value != nil {
+		msg += fmt.Sprintf("=%s", e.value)
+	}
+	return msg + "'"
+}
+
+func (e invalidFilter) InvalidParameter() {}
+
+type unknownError struct {
+	cause error
+}
+
+func (e unknownError) Error() string {
+	return e.cause.Error()
+}
+
+func (unknownError) Unknown() {}
+
+func (e unknownError) Cause() error {
+	return e.cause
+}
+
+type startInvalidConfigError string
+
+func (e startInvalidConfigError) Error() string {
+	return string(e)
+}
+
+func (e startInvalidConfigError) InvalidParameter() {} // Is this right???
+
+func translateContainerdStartErr(cmd string, setExitCode func(int), err error) error {
+	errDesc := grpc.ErrorDesc(err)
+	contains := func(s1, s2 string) bool {
+		return strings.Contains(strings.ToLower(s1), s2)
+	}
+	var retErr error = unknownError{errors.New(errDesc)}
+	// if we receive an internal error from the initial start of a container then lets
+	// return it instead of entering the restart loop
+	// set to 127 for container cmd not found/does not exist)
+	if contains(errDesc, cmd) &&
+		(contains(errDesc, "executable file not found") ||
+			contains(errDesc, "no such file or directory") ||
+			contains(errDesc, "system cannot find the file specified")) {
+		setExitCode(127)
+		retErr = startInvalidConfigError(errDesc)
+	}
+	// set to 126 for container cmd can't be invoked errors
+	if contains(errDesc, syscall.EACCES.Error()) {
+		setExitCode(126)
+		retErr = startInvalidConfigError(errDesc)
+	}
+
+	// attempted to mount a file onto a directory, or a directory onto a file, maybe from user specified bind mounts
+	if contains(errDesc, syscall.ENOTDIR.Error()) {
+		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"
+		setExitCode(127)
+		retErr = startInvalidConfigError(errDesc)
+	}
+
+	// TODO: it would be nice to get some better errors from containerd so we can return better errors here
+	return retErr
 }
 }

+ 6 - 6
daemon/exec.go

@@ -8,7 +8,6 @@ import (
 
 
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 
 
-	"github.com/docker/docker/api/errors"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/strslice"
 	"github.com/docker/docker/api/types/strslice"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/container"
@@ -18,6 +17,7 @@ import (
 	"github.com/docker/docker/pkg/pools"
 	"github.com/docker/docker/pkg/pools"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/term"
 	"github.com/docker/docker/pkg/term"
+	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 )
 )
 
 
@@ -81,7 +81,7 @@ func (d *Daemon) getActiveContainer(name string) (*container.Container, error) {
 	}
 	}
 
 
 	if !container.IsRunning() {
 	if !container.IsRunning() {
-		return nil, errNotRunning{container.ID}
+		return nil, errNotRunning(container.ID)
 	}
 	}
 	if container.IsPaused() {
 	if container.IsPaused() {
 		return nil, errExecPaused(name)
 		return nil, errExecPaused(name)
@@ -157,12 +157,12 @@ func (d *Daemon) ContainerExecStart(ctx context.Context, name string, stdin io.R
 	if ec.ExitCode != nil {
 	if ec.ExitCode != nil {
 		ec.Unlock()
 		ec.Unlock()
 		err := fmt.Errorf("Error: Exec command %s has already run", ec.ID)
 		err := fmt.Errorf("Error: Exec command %s has already run", ec.ID)
-		return errors.NewRequestConflictError(err)
+		return stateConflictError{err}
 	}
 	}
 
 
 	if ec.Running {
 	if ec.Running {
 		ec.Unlock()
 		ec.Unlock()
-		return fmt.Errorf("Error: Exec command %s is already running", ec.ID)
+		return stateConflictError{fmt.Errorf("Error: Exec command %s is already running", ec.ID)}
 	}
 	}
 	ec.Running = true
 	ec.Running = true
 	ec.Unlock()
 	ec.Unlock()
@@ -233,7 +233,7 @@ func (d *Daemon) ContainerExecStart(ctx context.Context, name string, stdin io.R
 
 
 	systemPid, err := d.containerd.AddProcess(ctx, c.ID, name, p, ec.InitializeStdio)
 	systemPid, err := d.containerd.AddProcess(ctx, c.ID, name, p, ec.InitializeStdio)
 	if err != nil {
 	if err != nil {
-		return err
+		return translateContainerdStartErr(ec.Entrypoint, ec.SetExitCode, err)
 	}
 	}
 	ec.Lock()
 	ec.Lock()
 	ec.Pid = systemPid
 	ec.Pid = systemPid
@@ -254,7 +254,7 @@ func (d *Daemon) ContainerExecStart(ctx context.Context, name string, stdin io.R
 	case err := <-attachErr:
 	case err := <-attachErr:
 		if err != nil {
 		if err != nil {
 			if _, ok := err.(term.EscapeError); !ok {
 			if _, ok := err.(term.EscapeError); !ok {
-				return fmt.Errorf("exec attach failed with error: %v", err)
+				return errors.Wrap(systemError{err}, "exec attach failed")
 			}
 			}
 			d.LogContainerEvent(c, "exec_detach")
 			d.LogContainerEvent(c, "exec_detach")
 		}
 		}

+ 5 - 0
daemon/exec/exec.go

@@ -62,6 +62,11 @@ func (c *Config) CloseStreams() error {
 	return c.StreamConfig.CloseStreams()
 	return c.StreamConfig.CloseStreams()
 }
 }
 
 
+// SetExitCode sets the exec config's exit code
+func (c *Config) SetExitCode(code int) {
+	c.ExitCode = &code
+}
+
 // Store keeps track of the exec configurations.
 // Store keeps track of the exec configurations.
 type Store struct {
 type Store struct {
 	commands map[string]*Config
 	commands map[string]*Config

+ 9 - 7
daemon/image.go

@@ -8,12 +8,12 @@ import (
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/docker/pkg/stringid"
 )
 )
 
 
-// ErrImageDoesNotExist is error returned when no image can be found for a reference.
-type ErrImageDoesNotExist struct {
+// errImageDoesNotExist is error returned when no image can be found for a reference.
+type errImageDoesNotExist struct {
 	ref reference.Reference
 	ref reference.Reference
 }
 }
 
 
-func (e ErrImageDoesNotExist) Error() string {
+func (e errImageDoesNotExist) Error() string {
 	ref := e.ref
 	ref := e.ref
 	if named, ok := ref.(reference.Named); ok {
 	if named, ok := ref.(reference.Named); ok {
 		ref = reference.TagNameOnly(named)
 		ref = reference.TagNameOnly(named)
@@ -21,18 +21,20 @@ func (e ErrImageDoesNotExist) Error() string {
 	return fmt.Sprintf("No such image: %s", reference.FamiliarString(ref))
 	return fmt.Sprintf("No such image: %s", reference.FamiliarString(ref))
 }
 }
 
 
+func (e errImageDoesNotExist) NotFound() {}
+
 // GetImageIDAndPlatform returns an image ID and platform corresponding to the image referred to by
 // GetImageIDAndPlatform returns an image ID and platform corresponding to the image referred to by
 // refOrID.
 // refOrID.
 func (daemon *Daemon) GetImageIDAndPlatform(refOrID string) (image.ID, string, error) {
 func (daemon *Daemon) GetImageIDAndPlatform(refOrID string) (image.ID, string, error) {
 	ref, err := reference.ParseAnyReference(refOrID)
 	ref, err := reference.ParseAnyReference(refOrID)
 	if err != nil {
 	if err != nil {
-		return "", "", err
+		return "", "", validationError{err}
 	}
 	}
 	namedRef, ok := ref.(reference.Named)
 	namedRef, ok := ref.(reference.Named)
 	if !ok {
 	if !ok {
 		digested, ok := ref.(reference.Digested)
 		digested, ok := ref.(reference.Digested)
 		if !ok {
 		if !ok {
-			return "", "", ErrImageDoesNotExist{ref}
+			return "", "", errImageDoesNotExist{ref}
 		}
 		}
 		id := image.IDFromDigest(digested.Digest())
 		id := image.IDFromDigest(digested.Digest())
 		for platform := range daemon.stores {
 		for platform := range daemon.stores {
@@ -40,7 +42,7 @@ func (daemon *Daemon) GetImageIDAndPlatform(refOrID string) (image.ID, string, e
 				return id, platform, nil
 				return id, platform, nil
 			}
 			}
 		}
 		}
-		return "", "", ErrImageDoesNotExist{ref}
+		return "", "", errImageDoesNotExist{ref}
 	}
 	}
 
 
 	for platform := range daemon.stores {
 	for platform := range daemon.stores {
@@ -71,7 +73,7 @@ func (daemon *Daemon) GetImageIDAndPlatform(refOrID string) (image.ID, string, e
 		}
 		}
 	}
 	}
 
 
-	return "", "", ErrImageDoesNotExist{ref}
+	return "", "", errImageDoesNotExist{ref}
 }
 }
 
 
 // GetImage returns an image corresponding to the image referred to by refOrID.
 // GetImage returns an image corresponding to the image referred to by refOrID.

+ 6 - 4
daemon/image_delete.go

@@ -6,11 +6,11 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/docker/distribution/reference"
 	"github.com/docker/distribution/reference"
-	"github.com/docker/docker/api/errors"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/docker/pkg/stringid"
+	"github.com/pkg/errors"
 )
 )
 
 
 type conflictType int
 type conflictType int
@@ -67,7 +67,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
 
 
 	imgID, platform, err := daemon.GetImageIDAndPlatform(imageRef)
 	imgID, platform, err := daemon.GetImageIDAndPlatform(imageRef)
 	if err != nil {
 	if err != nil {
-		return nil, daemon.imageNotExistToErrcode(err)
+		return nil, err
 	}
 	}
 
 
 	repoRefs := daemon.stores[platform].referenceStore.References(imgID.Digest())
 	repoRefs := daemon.stores[platform].referenceStore.References(imgID.Digest())
@@ -84,8 +84,8 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
 				// this image would remain "dangling" and since
 				// this image would remain "dangling" and since
 				// we really want to avoid that the client must
 				// we really want to avoid that the client must
 				// explicitly force its removal.
 				// explicitly force its removal.
-				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()))
-				return nil, errors.NewRequestConflictError(err)
+				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()))
+				return nil, stateConflictError{err}
 			}
 			}
 		}
 		}
 
 
@@ -285,6 +285,8 @@ func (idc *imageDeleteConflict) Error() string {
 	return fmt.Sprintf("conflict: unable to delete %s (%s) - %s", stringid.TruncateID(idc.imgID.String()), forceMsg, idc.message)
 	return fmt.Sprintf("conflict: unable to delete %s (%s) - %s", stringid.TruncateID(idc.imgID.String()), forceMsg, idc.message)
 }
 }
 
 
+func (idc *imageDeleteConflict) Conflict() {}
+
 // imageDeleteHelper attempts to delete the given image from this daemon. If
 // imageDeleteHelper attempts to delete the given image from this daemon. If
 // the image has any hard delete conflicts (child images or running containers
 // the image has any hard delete conflicts (child images or running containers
 // using the image) then it cannot be deleted. If the image has any soft delete
 // using the image) then it cannot be deleted. If the image has any soft delete

+ 3 - 3
daemon/image_pull.go

@@ -26,7 +26,7 @@ func (daemon *Daemon) PullImage(ctx context.Context, image, tag, platform string
 
 
 	ref, err := reference.ParseNormalizedNamed(image)
 	ref, err := reference.ParseNormalizedNamed(image)
 	if err != nil {
 	if err != nil {
-		return err
+		return validationError{err}
 	}
 	}
 
 
 	if tag != "" {
 	if tag != "" {
@@ -39,7 +39,7 @@ func (daemon *Daemon) PullImage(ctx context.Context, image, tag, platform string
 			ref, err = reference.WithTag(ref, tag)
 			ref, err = reference.WithTag(ref, tag)
 		}
 		}
 		if err != nil {
 		if err != nil {
-			return err
+			return validationError{err}
 		}
 		}
 	}
 	}
 
 
@@ -96,7 +96,7 @@ func (daemon *Daemon) GetRepository(ctx context.Context, ref reference.Named, au
 	}
 	}
 	// makes sure name is not empty or `scratch`
 	// makes sure name is not empty or `scratch`
 	if err := distribution.ValidateRepoName(repoInfo.Name); err != nil {
 	if err := distribution.ValidateRepoName(repoInfo.Name); err != nil {
-		return nil, false, err
+		return nil, false, validationError{err}
 	}
 	}
 
 
 	// get endpoints
 	// get endpoints

+ 1 - 1
daemon/images.go

@@ -71,7 +71,7 @@ func (daemon *Daemon) Images(imageFilters filters.Args, all bool, withExtraAttrs
 		if imageFilters.ExactMatch("dangling", "true") {
 		if imageFilters.ExactMatch("dangling", "true") {
 			danglingOnly = true
 			danglingOnly = true
 		} else if !imageFilters.ExactMatch("dangling", "false") {
 		} else if !imageFilters.ExactMatch("dangling", "false") {
-			return nil, fmt.Errorf("Invalid filter 'dangling=%s'", imageFilters.Get("dangling"))
+			return nil, invalidFilter{"dangling", imageFilters.Get("dangling")}
 		}
 		}
 	}
 	}
 	if danglingOnly {
 	if danglingOnly {

+ 4 - 4
daemon/import.go

@@ -42,16 +42,16 @@ func (daemon *Daemon) ImportImage(src string, repository, platform string, tag s
 		var err error
 		var err error
 		newRef, err = reference.ParseNormalizedNamed(repository)
 		newRef, err = reference.ParseNormalizedNamed(repository)
 		if err != nil {
 		if err != nil {
-			return err
+			return validationError{err}
 		}
 		}
 		if _, isCanonical := newRef.(reference.Canonical); isCanonical {
 		if _, isCanonical := newRef.(reference.Canonical); isCanonical {
-			return errors.New("cannot import digest reference")
+			return validationError{errors.New("cannot import digest reference")}
 		}
 		}
 
 
 		if tag != "" {
 		if tag != "" {
 			newRef, err = reference.WithTag(newRef, tag)
 			newRef, err = reference.WithTag(newRef, tag)
 			if err != nil {
 			if err != nil {
-				return err
+				return validationError{err}
 			}
 			}
 		}
 		}
 	}
 	}
@@ -69,7 +69,7 @@ func (daemon *Daemon) ImportImage(src string, repository, platform string, tag s
 		}
 		}
 		u, err := url.Parse(src)
 		u, err := url.Parse(src)
 		if err != nil {
 		if err != nil {
-			return err
+			return validationError{err}
 		}
 		}
 
 
 		resp, err = remotecontext.GetWithStatusError(u.String())
 		resp, err = remotecontext.GetWithStatusError(u.String())

+ 6 - 2
daemon/inspect.go

@@ -11,6 +11,7 @@ import (
 	"github.com/docker/docker/api/types/versions/v1p20"
 	"github.com/docker/docker/api/types/versions/v1p20"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/daemon/network"
 	"github.com/docker/docker/daemon/network"
+	volumestore "github.com/docker/docker/volume/store"
 	"github.com/docker/go-connections/nat"
 	"github.com/docker/go-connections/nat"
 )
 )
 
 
@@ -187,7 +188,7 @@ func (daemon *Daemon) getInspectData(container *container.Container) (*types.Con
 	// could have been removed, it will cause error if we try to get the metadata,
 	// could have been removed, it will cause error if we try to get the metadata,
 	// we can ignore the error if the container is dead.
 	// we can ignore the error if the container is dead.
 	if err != nil && !container.Dead {
 	if err != nil && !container.Dead {
-		return nil, err
+		return nil, systemError{err}
 	}
 	}
 	contJSONBase.GraphDriver.Data = graphDriverData
 	contJSONBase.GraphDriver.Data = graphDriverData
 
 
@@ -228,7 +229,10 @@ func (daemon *Daemon) ContainerExecInspect(id string) (*backend.ExecInspect, err
 func (daemon *Daemon) VolumeInspect(name string) (*types.Volume, error) {
 func (daemon *Daemon) VolumeInspect(name string) (*types.Volume, error) {
 	v, err := daemon.volumes.Get(name)
 	v, err := daemon.volumes.Get(name)
 	if err != nil {
 	if err != nil {
-		return nil, err
+		if volumestore.IsNotExist(err) {
+			return nil, volumeNotFound(name)
+		}
+		return nil, systemError{err}
 	}
 	}
 	apiV := volumeToAPIType(v)
 	apiV := volumeToAPIType(v)
 	apiV.Mountpoint = v.Path()
 	apiV.Mountpoint = v.Path()

+ 7 - 3
daemon/kill.go

@@ -10,6 +10,7 @@ import (
 
 
 	containerpkg "github.com/docker/docker/container"
 	containerpkg "github.com/docker/docker/container"
 	"github.com/docker/docker/pkg/signal"
 	"github.com/docker/docker/pkg/signal"
+	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 )
 )
 
 
@@ -22,6 +23,8 @@ func (e errNoSuchProcess) Error() string {
 	return fmt.Sprintf("Cannot kill process (pid=%d) with signal %d: no such process.", e.pid, e.signal)
 	return fmt.Sprintf("Cannot kill process (pid=%d) with signal %d: no such process.", e.pid, e.signal)
 }
 }
 
 
+func (errNoSuchProcess) NotFound() {}
+
 // isErrNoSuchProcess returns true if the error
 // isErrNoSuchProcess returns true if the error
 // is an instance of errNoSuchProcess.
 // is an instance of errNoSuchProcess.
 func isErrNoSuchProcess(err error) bool {
 func isErrNoSuchProcess(err error) bool {
@@ -61,7 +64,7 @@ func (daemon *Daemon) killWithSignal(container *containerpkg.Container, sig int)
 	defer container.Unlock()
 	defer container.Unlock()
 
 
 	if !container.Running {
 	if !container.Running {
-		return errNotRunning{container.ID}
+		return errNotRunning(container.ID)
 	}
 	}
 
 
 	var unpause bool
 	var unpause bool
@@ -91,8 +94,9 @@ func (daemon *Daemon) killWithSignal(container *containerpkg.Container, sig int)
 	}
 	}
 
 
 	if err := daemon.kill(container, sig); err != nil {
 	if err := daemon.kill(container, sig); err != nil {
-		err = fmt.Errorf("Cannot kill container %s: %s", container.ID, err)
+		err = errors.Wrapf(err, "Cannot kill container %s", container.ID)
 		// if container or process not exists, ignore the error
 		// if container or process not exists, ignore the error
+		// TODO: we shouldn't have to parse error strings from containerd
 		if strings.Contains(err.Error(), "container not found") ||
 		if strings.Contains(err.Error(), "container not found") ||
 			strings.Contains(err.Error(), "no such process") {
 			strings.Contains(err.Error(), "no such process") {
 			logrus.Warnf("container kill failed because of 'container not found' or 'no such process': %s", err.Error())
 			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)
 // Kill forcefully terminates a container.
 // Kill forcefully terminates a container.
 func (daemon *Daemon) Kill(container *containerpkg.Container) error {
 func (daemon *Daemon) Kill(container *containerpkg.Container) error {
 	if !container.IsRunning() {
 	if !container.IsRunning() {
-		return errNotRunning{container.ID}
+		return errNotRunning(container.ID)
 	}
 	}
 
 
 	// 1. Send SIGKILL
 	// 1. Send SIGKILL

+ 6 - 6
daemon/list.go

@@ -1,7 +1,6 @@
 package daemon
 package daemon
 
 
 import (
 import (
-	"errors"
 	"fmt"
 	"fmt"
 	"sort"
 	"sort"
 	"strconv"
 	"strconv"
@@ -13,6 +12,7 @@ import (
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/volume"
 	"github.com/docker/docker/volume"
 	"github.com/docker/go-connections/nat"
 	"github.com/docker/go-connections/nat"
+	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 )
 )
 
 
@@ -265,7 +265,7 @@ func (daemon *Daemon) foldFilter(view container.View, config *types.ContainerLis
 
 
 	err = psFilters.WalkValues("status", func(value string) error {
 	err = psFilters.WalkValues("status", func(value string) error {
 		if !container.IsValidStateString(value) {
 		if !container.IsValidStateString(value) {
-			return fmt.Errorf("Unrecognised filter value for status: %s", value)
+			return invalidFilter{"status", value}
 		}
 		}
 
 
 		config.All = true
 		config.All = true
@@ -284,13 +284,13 @@ func (daemon *Daemon) foldFilter(view container.View, config *types.ContainerLis
 			taskFilter = true
 			taskFilter = true
 			isTask = false
 			isTask = false
 		} else {
 		} else {
-			return nil, fmt.Errorf("Invalid filter 'is-task=%s'", psFilters.Get("is-task"))
+			return nil, invalidFilter{"is-task", psFilters.Get("is-task")}
 		}
 		}
 	}
 	}
 
 
 	err = psFilters.WalkValues("health", func(value string) error {
 	err = psFilters.WalkValues("health", func(value string) error {
 		if !container.IsValidHealthString(value) {
 		if !container.IsValidHealthString(value) {
-			return fmt.Errorf("Unrecognised filter value for health: %s", value)
+			return validationError{errors.Errorf("Unrecognised filter value for health: %s", value)}
 		}
 		}
 
 
 		return nil
 		return nil
@@ -567,7 +567,7 @@ func (daemon *Daemon) refreshImage(s *container.Snapshot, ctx *listContext) (*ty
 	image := s.Image // keep the original ref if still valid (hasn't changed)
 	image := s.Image // keep the original ref if still valid (hasn't changed)
 	if image != s.ImageID {
 	if image != s.ImageID {
 		id, _, err := daemon.GetImageIDAndPlatform(image)
 		id, _, err := daemon.GetImageIDAndPlatform(image)
-		if _, isDNE := err.(ErrImageDoesNotExist); err != nil && !isDNE {
+		if _, isDNE := err.(errImageDoesNotExist); err != nil && !isDNE {
 			return nil, err
 			return nil, err
 		}
 		}
 		if err != nil || id.String() != s.ImageID {
 		if err != nil || id.String() != s.ImageID {
@@ -653,7 +653,7 @@ func (daemon *Daemon) filterVolumes(vols []volume.Volume, filter filters.Args) (
 		if filter.ExactMatch("dangling", "true") || filter.ExactMatch("dangling", "1") {
 		if filter.ExactMatch("dangling", "true") || filter.ExactMatch("dangling", "1") {
 			danglingOnly = true
 			danglingOnly = true
 		} else if !filter.ExactMatch("dangling", "false") && !filter.ExactMatch("dangling", "0") {
 		} else if !filter.ExactMatch("dangling", "false") && !filter.ExactMatch("dangling", "0") {
-			return nil, fmt.Errorf("Invalid filter 'dangling=%s'", filter.Get("dangling"))
+			return nil, invalidFilter{"dangling", filter.Get("dangling")}
 		}
 		}
 		retVols = daemon.volumes.FilterByUsed(retVols, !danglingOnly)
 		retVols = daemon.volumes.FilterByUsed(retVols, !danglingOnly)
 	}
 	}

+ 9 - 3
daemon/logger/logger.go

@@ -8,7 +8,6 @@
 package logger
 package logger
 
 
 import (
 import (
-	"errors"
 	"sync"
 	"sync"
 	"time"
 	"time"
 
 
@@ -16,8 +15,15 @@ import (
 	"github.com/docker/docker/pkg/jsonlog"
 	"github.com/docker/docker/pkg/jsonlog"
 )
 )
 
 
-// ErrReadLogsNotSupported is returned when the logger does not support reading logs.
-var ErrReadLogsNotSupported = errors.New("configured logging driver does not support reading")
+// ErrReadLogsNotSupported is returned when the underlying log driver does not support reading
+type ErrReadLogsNotSupported struct{}
+
+func (ErrReadLogsNotSupported) Error() string {
+	return "configured logging driver does not support reading"
+}
+
+// NotImplemented makes this error implement the `NotImplemented` interface from api/errdefs
+func (ErrReadLogsNotSupported) NotImplemented() {}
 
 
 const (
 const (
 	// TimeFormat is the time format used for timestamps sent to log readers.
 	// TimeFormat is the time format used for timestamps sent to log readers.

+ 9 - 9
daemon/logs.go

@@ -22,7 +22,7 @@ import (
 //
 //
 // if it returns nil, the config channel will be active and return log
 // if it returns nil, the config channel will be active and return log
 // messages until it runs out or the context is canceled.
 // messages until it runs out or the context is canceled.
-func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, config *types.ContainerLogsOptions) (<-chan *backend.LogMessage, error) {
+func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, config *types.ContainerLogsOptions) (<-chan *backend.LogMessage, bool, error) {
 	lg := logrus.WithFields(logrus.Fields{
 	lg := logrus.WithFields(logrus.Fields{
 		"module":    "daemon",
 		"module":    "daemon",
 		"method":    "(*Daemon).ContainerLogs",
 		"method":    "(*Daemon).ContainerLogs",
@@ -30,24 +30,24 @@ func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, c
 	})
 	})
 
 
 	if !(config.ShowStdout || config.ShowStderr) {
 	if !(config.ShowStdout || config.ShowStderr) {
-		return nil, errors.New("You must choose at least one stream")
+		return nil, false, validationError{errors.New("You must choose at least one stream")}
 	}
 	}
 	container, err := daemon.GetContainer(containerName)
 	container, err := daemon.GetContainer(containerName)
 	if err != nil {
 	if err != nil {
-		return nil, err
+		return nil, false, err
 	}
 	}
 
 
 	if container.RemovalInProgress || container.Dead {
 	if container.RemovalInProgress || container.Dead {
-		return nil, errors.New("can not get logs from container which is dead or marked for removal")
+		return nil, false, stateConflictError{errors.New("can not get logs from container which is dead or marked for removal")}
 	}
 	}
 
 
 	if container.HostConfig.LogConfig.Type == "none" {
 	if container.HostConfig.LogConfig.Type == "none" {
-		return nil, logger.ErrReadLogsNotSupported
+		return nil, false, logger.ErrReadLogsNotSupported{}
 	}
 	}
 
 
 	cLog, cLogCreated, err := daemon.getLogger(container)
 	cLog, cLogCreated, err := daemon.getLogger(container)
 	if err != nil {
 	if err != nil {
-		return nil, err
+		return nil, false, err
 	}
 	}
 	if cLogCreated {
 	if cLogCreated {
 		defer func() {
 		defer func() {
@@ -59,7 +59,7 @@ func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, c
 
 
 	logReader, ok := cLog.(logger.LogReader)
 	logReader, ok := cLog.(logger.LogReader)
 	if !ok {
 	if !ok {
-		return nil, logger.ErrReadLogsNotSupported
+		return nil, false, logger.ErrReadLogsNotSupported{}
 	}
 	}
 
 
 	follow := config.Follow && !cLogCreated
 	follow := config.Follow && !cLogCreated
@@ -72,7 +72,7 @@ func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, c
 	if config.Since != "" {
 	if config.Since != "" {
 		s, n, err := timetypes.ParseTimestamps(config.Since, 0)
 		s, n, err := timetypes.ParseTimestamps(config.Since, 0)
 		if err != nil {
 		if err != nil {
-			return nil, err
+			return nil, false, err
 		}
 		}
 		since = time.Unix(s, n)
 		since = time.Unix(s, n)
 	}
 	}
@@ -137,7 +137,7 @@ func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, c
 			}
 			}
 		}
 		}
 	}()
 	}()
-	return messageChan, nil
+	return messageChan, container.Config.Tty, nil
 }
 }
 
 
 func (daemon *Daemon) getLogger(container *container.Container) (l logger.Logger, created bool, err error) {
 func (daemon *Daemon) getLogger(container *container.Container) (l logger.Logger, created bool, err error) {

+ 4 - 3
daemon/names.go

@@ -8,6 +8,7 @@ import (
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/pkg/namesgenerator"
 	"github.com/docker/docker/pkg/namesgenerator"
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/docker/pkg/stringid"
+	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 )
 )
 
 
@@ -55,7 +56,7 @@ func (daemon *Daemon) generateIDAndName(name string) (string, string, error) {
 
 
 func (daemon *Daemon) reserveName(id, name string) (string, error) {
 func (daemon *Daemon) reserveName(id, name string) (string, error) {
 	if !validContainerNamePattern.MatchString(strings.TrimPrefix(name, "/")) {
 	if !validContainerNamePattern.MatchString(strings.TrimPrefix(name, "/")) {
-		return "", fmt.Errorf("Invalid container name (%s), only %s are allowed", name, validContainerNameChars)
+		return "", validationError{errors.Errorf("Invalid container name (%s), only %s are allowed", name, validContainerNameChars)}
 	}
 	}
 	if name[0] != '/' {
 	if name[0] != '/' {
 		name = "/" + name
 		name = "/" + name
@@ -68,9 +69,9 @@ func (daemon *Daemon) reserveName(id, name string) (string, error) {
 				logrus.Errorf("got unexpected error while looking up reserved name: %v", err)
 				logrus.Errorf("got unexpected error while looking up reserved name: %v", err)
 				return "", err
 				return "", err
 			}
 			}
-			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)
+			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)}
 		}
 		}
-		return "", fmt.Errorf("error reserving name: %q, error: %v", name, err)
+		return "", errors.Wrapf(err, "error reserving name: %q", name)
 	}
 	}
 	return name, nil
 	return name, nil
 }
 }

+ 5 - 6
daemon/network.go

@@ -8,7 +8,6 @@ import (
 	"strings"
 	"strings"
 	"sync"
 	"sync"
 
 
-	apierrors "github.com/docker/docker/api/errors"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/network"
 	"github.com/docker/docker/api/types/network"
 	clustertypes "github.com/docker/docker/daemon/cluster/provider"
 	clustertypes "github.com/docker/docker/daemon/cluster/provider"
@@ -57,10 +56,10 @@ func (daemon *Daemon) GetNetworkByID(partialID string) (libnetwork.Network, erro
 	list := daemon.GetNetworksByID(partialID)
 	list := daemon.GetNetworksByID(partialID)
 
 
 	if len(list) == 0 {
 	if len(list) == 0 {
-		return nil, libnetwork.ErrNoSuchNetwork(partialID)
+		return nil, errors.WithStack(networkNotFound(partialID))
 	}
 	}
 	if len(list) > 1 {
 	if len(list) > 1 {
-		return nil, libnetwork.ErrInvalidID(partialID)
+		return nil, errors.WithStack(invalidIdentifier(partialID))
 	}
 	}
 	return list[0], nil
 	return list[0], nil
 }
 }
@@ -287,7 +286,7 @@ func (daemon *Daemon) CreateNetwork(create types.NetworkCreateRequest) (*types.N
 func (daemon *Daemon) createNetwork(create types.NetworkCreateRequest, id string, agent bool) (*types.NetworkCreateResponse, error) {
 func (daemon *Daemon) createNetwork(create types.NetworkCreateRequest, id string, agent bool) (*types.NetworkCreateResponse, error) {
 	if runconfig.IsPreDefinedNetwork(create.Name) && !agent {
 	if runconfig.IsPreDefinedNetwork(create.Name) && !agent {
 		err := fmt.Errorf("%s is a pre-defined network and cannot be created", create.Name)
 		err := fmt.Errorf("%s is a pre-defined network and cannot be created", create.Name)
-		return nil, apierrors.NewRequestForbiddenError(err)
+		return nil, notAllowedError{err}
 	}
 	}
 
 
 	var warning string
 	var warning string
@@ -504,7 +503,7 @@ func (daemon *Daemon) deleteNetwork(networkID string, dynamic bool) error {
 
 
 	if runconfig.IsPreDefinedNetwork(nw.Name()) && !dynamic {
 	if runconfig.IsPreDefinedNetwork(nw.Name()) && !dynamic {
 		err := fmt.Errorf("%s is a pre-defined network and cannot be removed", nw.Name())
 		err := fmt.Errorf("%s is a pre-defined network and cannot be removed", nw.Name())
-		return apierrors.NewRequestForbiddenError(err)
+		return notAllowedError{err}
 	}
 	}
 
 
 	if dynamic && !nw.Info().Dynamic() {
 	if dynamic && !nw.Info().Dynamic() {
@@ -514,7 +513,7 @@ func (daemon *Daemon) deleteNetwork(networkID string, dynamic bool) error {
 			return nil
 			return nil
 		}
 		}
 		err := fmt.Errorf("%s is not a dynamic network", nw.Name())
 		err := fmt.Errorf("%s is not a dynamic network", nw.Name())
-		return apierrors.NewRequestForbiddenError(err)
+		return notAllowedError{err}
 	}
 	}
 
 
 	if err := nw.Delete(); err != nil {
 	if err := nw.Delete(); err != nil {

+ 1 - 1
daemon/oci_linux.go

@@ -518,7 +518,7 @@ func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []c
 	for _, m := range mounts {
 	for _, m := range mounts {
 		for _, cm := range s.Mounts {
 		for _, cm := range s.Mounts {
 			if cm.Destination == m.Destination {
 			if cm.Destination == m.Destination {
-				return fmt.Errorf("Duplicate mount point '%s'", m.Destination)
+				return duplicateMountPointError(m.Destination)
 			}
 			}
 		}
 		}
 
 

+ 1 - 1
daemon/pause.go

@@ -28,7 +28,7 @@ func (daemon *Daemon) containerPause(container *container.Container) error {
 
 
 	// We cannot Pause the container which is not running
 	// We cannot Pause the container which is not running
 	if !container.Running {
 	if !container.Running {
-		return errNotRunning{container.ID}
+		return errNotRunning(container.ID)
 	}
 	}
 
 
 	// We cannot Pause the container which is already paused
 	// We cannot Pause the container which is already paused

+ 1 - 1
daemon/prune.go

@@ -186,7 +186,7 @@ func (daemon *Daemon) ImagesPrune(ctx context.Context, pruneFilters filters.Args
 		if pruneFilters.ExactMatch("dangling", "false") || pruneFilters.ExactMatch("dangling", "0") {
 		if pruneFilters.ExactMatch("dangling", "false") || pruneFilters.ExactMatch("dangling", "0") {
 			danglingOnly = false
 			danglingOnly = false
 		} else if !pruneFilters.ExactMatch("dangling", "true") && !pruneFilters.ExactMatch("dangling", "1") {
 		} else if !pruneFilters.ExactMatch("dangling", "true") && !pruneFilters.ExactMatch("dangling", "1") {
-			return nil, fmt.Errorf("Invalid filter 'dangling=%s'", pruneFilters.Get("dangling"))
+			return nil, invalidFilter{"dangling", pruneFilters.Get("dangling")}
 		}
 		}
 	}
 	}
 
 

+ 5 - 6
daemon/rename.go

@@ -1,12 +1,11 @@
 package daemon
 package daemon
 
 
 import (
 import (
-	"errors"
-	"fmt"
 	"strings"
 	"strings"
 
 
 	dockercontainer "github.com/docker/docker/container"
 	dockercontainer "github.com/docker/docker/container"
 	"github.com/docker/libnetwork"
 	"github.com/docker/libnetwork"
+	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 )
 )
 
 
@@ -20,7 +19,7 @@ func (daemon *Daemon) ContainerRename(oldName, newName string) error {
 	)
 	)
 
 
 	if oldName == "" || newName == "" {
 	if oldName == "" || newName == "" {
-		return errors.New("Neither old nor new names may be empty")
+		return validationError{errors.New("Neither old nor new names may be empty")}
 	}
 	}
 
 
 	if newName[0] != '/' {
 	if newName[0] != '/' {
@@ -39,19 +38,19 @@ func (daemon *Daemon) ContainerRename(oldName, newName string) error {
 	oldIsAnonymousEndpoint := container.NetworkSettings.IsAnonymousEndpoint
 	oldIsAnonymousEndpoint := container.NetworkSettings.IsAnonymousEndpoint
 
 
 	if oldName == newName {
 	if oldName == newName {
-		return errors.New("Renaming a container with the same name as its current name")
+		return validationError{errors.New("Renaming a container with the same name as its current name")}
 	}
 	}
 
 
 	links := map[string]*dockercontainer.Container{}
 	links := map[string]*dockercontainer.Container{}
 	for k, v := range daemon.linkIndex.children(container) {
 	for k, v := range daemon.linkIndex.children(container) {
 		if !strings.HasPrefix(k, oldName) {
 		if !strings.HasPrefix(k, oldName) {
-			return fmt.Errorf("Linked container %s does not match parent %s", k, oldName)
+			return validationError{errors.Errorf("Linked container %s does not match parent %s", k, oldName)}
 		}
 		}
 		links[strings.TrimPrefix(k, oldName)] = v
 		links[strings.TrimPrefix(k, oldName)] = v
 	}
 	}
 
 
 	if newName, err = daemon.reserveName(container.ID, newName); err != nil {
 	if newName, err = daemon.reserveName(container.ID, newName); err != nil {
-		return fmt.Errorf("Error when allocating new name: %v", err)
+		return errors.Wrap(err, "Error when allocating new name")
 	}
 	}
 
 
 	for k, v := range links {
 	for k, v := range links {

+ 1 - 1
daemon/resize.go

@@ -15,7 +15,7 @@ func (daemon *Daemon) ContainerResize(name string, height, width int) error {
 	}
 	}
 
 
 	if !container.IsRunning() {
 	if !container.IsRunning() {
-		return errNotRunning{container.ID}
+		return errNotRunning(container.ID)
 	}
 	}
 
 
 	if err = daemon.containerd.Resize(container.ID, libcontainerd.InitFriendlyName, width, height); err == nil {
 	if err = daemon.containerd.Resize(container.ID, libcontainerd.InitFriendlyName, width, height); err == nil {

+ 3 - 4
daemon/search.go

@@ -1,7 +1,6 @@
 package daemon
 package daemon
 
 
 import (
 import (
-	"fmt"
 	"strconv"
 	"strconv"
 
 
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
@@ -38,14 +37,14 @@ func (daemon *Daemon) SearchRegistryForImages(ctx context.Context, filtersArgs s
 		if searchFilters.UniqueExactMatch("is-automated", "true") {
 		if searchFilters.UniqueExactMatch("is-automated", "true") {
 			isAutomated = true
 			isAutomated = true
 		} else if !searchFilters.UniqueExactMatch("is-automated", "false") {
 		} else if !searchFilters.UniqueExactMatch("is-automated", "false") {
-			return nil, fmt.Errorf("Invalid filter 'is-automated=%s'", searchFilters.Get("is-automated"))
+			return nil, invalidFilter{"is-automated", searchFilters.Get("is-automated")}
 		}
 		}
 	}
 	}
 	if searchFilters.Include("is-official") {
 	if searchFilters.Include("is-official") {
 		if searchFilters.UniqueExactMatch("is-official", "true") {
 		if searchFilters.UniqueExactMatch("is-official", "true") {
 			isOfficial = true
 			isOfficial = true
 		} else if !searchFilters.UniqueExactMatch("is-official", "false") {
 		} else if !searchFilters.UniqueExactMatch("is-official", "false") {
-			return nil, fmt.Errorf("Invalid filter 'is-official=%s'", searchFilters.Get("is-official"))
+			return nil, invalidFilter{"is-official", searchFilters.Get("is-official")}
 		}
 		}
 	}
 	}
 	if searchFilters.Include("stars") {
 	if searchFilters.Include("stars") {
@@ -53,7 +52,7 @@ func (daemon *Daemon) SearchRegistryForImages(ctx context.Context, filtersArgs s
 		for _, hasStar := range hasStars {
 		for _, hasStar := range hasStars {
 			iHasStar, err := strconv.Atoi(hasStar)
 			iHasStar, err := strconv.Atoi(hasStar)
 			if err != nil {
 			if err != nil {
-				return nil, fmt.Errorf("Invalid filter 'stars=%s'", hasStar)
+				return nil, invalidFilter{"stars", hasStar}
 			}
 			}
 			if iHasStar > hasStarFilter {
 			if iHasStar > hasStarFilter {
 				hasStarFilter = iHasStar
 				hasStarFilter = iHasStar

+ 34 - 48
daemon/start.go

@@ -1,26 +1,20 @@
 package daemon
 package daemon
 
 
 import (
 import (
-	"fmt"
-	"net/http"
 	"runtime"
 	"runtime"
-	"strings"
-	"syscall"
 	"time"
 	"time"
 
 
-	"google.golang.org/grpc"
-
-	apierrors "github.com/docker/docker/api/errors"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	containertypes "github.com/docker/docker/api/types/container"
 	containertypes "github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/container"
+	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 )
 )
 
 
 // ContainerStart starts a container.
 // ContainerStart starts a container.
 func (daemon *Daemon) ContainerStart(name string, hostConfig *containertypes.HostConfig, checkpoint string, checkpointDir string) error {
 func (daemon *Daemon) ContainerStart(name string, hostConfig *containertypes.HostConfig, checkpoint string, checkpointDir string) error {
 	if checkpoint != "" && !daemon.HasExperimental() {
 	if checkpoint != "" && !daemon.HasExperimental() {
-		return apierrors.NewBadRequestError(fmt.Errorf("checkpoint is only supported in experimental mode"))
+		return validationError{errors.New("checkpoint is only supported in experimental mode")}
 	}
 	}
 
 
 	container, err := daemon.GetContainer(name)
 	container, err := daemon.GetContainer(name)
@@ -28,13 +22,26 @@ func (daemon *Daemon) ContainerStart(name string, hostConfig *containertypes.Hos
 		return err
 		return err
 	}
 	}
 
 
-	if container.IsPaused() {
-		return fmt.Errorf("Cannot start a paused container, try unpause instead.")
+	validateState := func() error {
+		container.Lock()
+		defer container.Unlock()
+
+		if container.Paused {
+			return stateConflictError{errors.New("Cannot start a paused container, try unpause instead.")}
+		}
+
+		if container.Running {
+			return containerNotModifiedError{running: true}
+		}
+
+		if container.RemovalInProgress || container.Dead {
+			return stateConflictError{errors.New("Container is marked for removal and cannot be started.")}
+		}
+		return nil
 	}
 	}
 
 
-	if container.IsRunning() {
-		err := fmt.Errorf("Container already started")
-		return apierrors.NewErrorWithStatusCode(err, http.StatusNotModified)
+	if err := validateState(); err != nil {
+		return err
 	}
 	}
 
 
 	// Windows does not have the backwards compatibility issue here.
 	// Windows does not have the backwards compatibility issue here.
@@ -45,13 +52,13 @@ func (daemon *Daemon) ContainerStart(name string, hostConfig *containertypes.Hos
 			logrus.Warn("DEPRECATED: Setting host configuration options when the container starts is deprecated and has been removed in Docker 1.12")
 			logrus.Warn("DEPRECATED: Setting host configuration options when the container starts is deprecated and has been removed in Docker 1.12")
 			oldNetworkMode := container.HostConfig.NetworkMode
 			oldNetworkMode := container.HostConfig.NetworkMode
 			if err := daemon.setSecurityOptions(container, hostConfig); err != nil {
 			if err := daemon.setSecurityOptions(container, hostConfig); err != nil {
-				return err
+				return validationError{err}
 			}
 			}
 			if err := daemon.mergeAndVerifyLogConfig(&hostConfig.LogConfig); err != nil {
 			if err := daemon.mergeAndVerifyLogConfig(&hostConfig.LogConfig); err != nil {
-				return err
+				return validationError{err}
 			}
 			}
 			if err := daemon.setHostConfig(container, hostConfig); err != nil {
 			if err := daemon.setHostConfig(container, hostConfig); err != nil {
-				return err
+				return validationError{err}
 			}
 			}
 			newNetworkMode := container.HostConfig.NetworkMode
 			newNetworkMode := container.HostConfig.NetworkMode
 			if string(oldNetworkMode) != string(newNetworkMode) {
 			if string(oldNetworkMode) != string(newNetworkMode) {
@@ -59,31 +66,34 @@ func (daemon *Daemon) ContainerStart(name string, hostConfig *containertypes.Hos
 				// old networks. It is a deprecated feature and has been removed in Docker 1.12
 				// old networks. It is a deprecated feature and has been removed in Docker 1.12
 				container.NetworkSettings.Networks = nil
 				container.NetworkSettings.Networks = nil
 				if err := container.CheckpointTo(daemon.containersReplica); err != nil {
 				if err := container.CheckpointTo(daemon.containersReplica); err != nil {
-					return err
+					return systemError{err}
 				}
 				}
 			}
 			}
 			container.InitDNSHostConfig()
 			container.InitDNSHostConfig()
 		}
 		}
 	} else {
 	} else {
 		if hostConfig != nil {
 		if hostConfig != nil {
-			return fmt.Errorf("Supplying a hostconfig on start is not supported. It should be supplied on create")
+			return validationError{errors.New("Supplying a hostconfig on start is not supported. It should be supplied on create")}
 		}
 		}
 	}
 	}
 
 
 	// check if hostConfig is in line with the current system settings.
 	// check if hostConfig is in line with the current system settings.
 	// It may happen cgroups are umounted or the like.
 	// It may happen cgroups are umounted or the like.
 	if _, err = daemon.verifyContainerSettings(container.HostConfig, nil, false); err != nil {
 	if _, err = daemon.verifyContainerSettings(container.HostConfig, nil, false); err != nil {
-		return err
+		return validationError{err}
 	}
 	}
 	// Adapt for old containers in case we have updates in this function and
 	// Adapt for old containers in case we have updates in this function and
 	// old containers never have chance to call the new function in create stage.
 	// old containers never have chance to call the new function in create stage.
 	if hostConfig != nil {
 	if hostConfig != nil {
 		if err := daemon.adaptContainerSettings(container.HostConfig, false); err != nil {
 		if err := daemon.adaptContainerSettings(container.HostConfig, false); err != nil {
-			return err
+			return validationError{err}
 		}
 		}
 	}
 	}
 
 
-	return daemon.containerStart(container, checkpoint, checkpointDir, true)
+	if err := daemon.containerStart(container, checkpoint, checkpointDir, true); err != nil {
+		return err
+	}
+	return nil
 }
 }
 
 
 // containerStart prepares the container to run by setting up everything the
 // containerStart prepares the container to run by setting up everything the
@@ -100,7 +110,7 @@ func (daemon *Daemon) containerStart(container *container.Container, checkpoint
 	}
 	}
 
 
 	if container.RemovalInProgress || container.Dead {
 	if container.RemovalInProgress || container.Dead {
-		return fmt.Errorf("Container is marked for removal and cannot be started.")
+		return stateConflictError{errors.New("Container is marked for removal and cannot be started.")}
 	}
 	}
 
 
 	// if we encounter an error during start we need to ensure that any other
 	// 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
 
 
 	spec, err := daemon.createSpec(container)
 	spec, err := daemon.createSpec(container)
 	if err != nil {
 	if err != nil {
-		return err
+		return systemError{err}
 	}
 	}
 
 
 	createOptions, err := daemon.getLibcontainerdCreateOptions(container)
 	createOptions, err := daemon.getLibcontainerdCreateOptions(container)
@@ -160,32 +170,8 @@ func (daemon *Daemon) containerStart(container *container.Container, checkpoint
 	}
 	}
 
 
 	if err := daemon.containerd.Create(container.ID, checkpoint, checkpointDir, *spec, container.InitializeStdio, createOptions...); err != nil {
 	if err := daemon.containerd.Create(container.ID, checkpoint, checkpointDir, *spec, container.InitializeStdio, createOptions...); err != nil {
-		errDesc := grpc.ErrorDesc(err)
-		contains := func(s1, s2 string) bool {
-			return strings.Contains(strings.ToLower(s1), s2)
-		}
-		logrus.Errorf("Create container failed with error: %s", errDesc)
-		// if we receive an internal error from the initial start of a container then lets
-		// return it instead of entering the restart loop
-		// set to 127 for container cmd not found/does not exist)
-		if contains(errDesc, container.Path) &&
-			(contains(errDesc, "executable file not found") ||
-				contains(errDesc, "no such file or directory") ||
-				contains(errDesc, "system cannot find the file specified")) {
-			container.SetExitCode(127)
-		}
-		// set to 126 for container cmd can't be invoked errors
-		if contains(errDesc, syscall.EACCES.Error()) {
-			container.SetExitCode(126)
-		}
-
-		// attempted to mount a file onto a directory, or a directory onto a file, maybe from user specified bind mounts
-		if contains(errDesc, syscall.ENOTDIR.Error()) {
-			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"
-			container.SetExitCode(127)
-		}
+		return translateContainerdStartErr(container.Path, container.SetExitCode, err)
 
 
-		return fmt.Errorf("%s", errDesc)
 	}
 	}
 
 
 	containerActions.WithValues("start").UpdateSince(start)
 	containerActions.WithValues("start").UpdateSince(start)

+ 2 - 3
daemon/start_unix.go

@@ -3,10 +3,9 @@
 package daemon
 package daemon
 
 
 import (
 import (
-	"fmt"
-
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/libcontainerd"
 	"github.com/docker/docker/libcontainerd"
+	"github.com/pkg/errors"
 )
 )
 
 
 // getLibcontainerdCreateOptions callers must hold a lock on the container
 // getLibcontainerdCreateOptions callers must hold a lock on the container
@@ -21,7 +20,7 @@ func (daemon *Daemon) getLibcontainerdCreateOptions(container *container.Contain
 
 
 	rt := daemon.configStore.GetRuntime(container.HostConfig.Runtime)
 	rt := daemon.configStore.GetRuntime(container.HostConfig.Runtime)
 	if rt == nil {
 	if rt == nil {
-		return nil, fmt.Errorf("no such runtime '%s'", container.HostConfig.Runtime)
+		return nil, validationError{errors.Errorf("no such runtime '%s'", container.HostConfig.Runtime)}
 	}
 	}
 	if UsingSystemd(daemon.configStore) {
 	if UsingSystemd(daemon.configStore) {
 		rt.Args = append(rt.Args, "--systemd-cgroup=true")
 		rt.Args = append(rt.Args, "--systemd-cgroup=true")

+ 2 - 2
daemon/stats/collector.go

@@ -113,10 +113,10 @@ func (s *Collector) Run() {
 
 
 type notRunningErr interface {
 type notRunningErr interface {
 	error
 	error
-	ContainerIsRunning() bool
+	Conflict()
 }
 }
 
 
 type notFoundErr interface {
 type notFoundErr interface {
 	error
 	error
-	ContainerNotFound() bool
+	NotFound()
 }
 }

+ 2 - 3
daemon/stats_unix.go

@@ -3,10 +3,9 @@
 package daemon
 package daemon
 
 
 import (
 import (
-	"fmt"
-
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/container"
+	"github.com/pkg/errors"
 )
 )
 
 
 // Resolve Network SandboxID in case the container reuse another container's network stack
 // 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
 		containerID := curr.HostConfig.NetworkMode.ConnectedContainer()
 		containerID := curr.HostConfig.NetworkMode.ConnectedContainer()
 		connected, err := daemon.GetContainer(containerID)
 		connected, err := daemon.GetContainer(containerID)
 		if err != nil {
 		if err != nil {
-			return "", fmt.Errorf("Could not get container for %s", containerID)
+			return "", errors.Wrapf(err, "Could not get container for %s", containerID)
 		}
 		}
 		curr = connected
 		curr = connected
 	}
 	}

+ 3 - 6
daemon/stop.go

@@ -2,12 +2,10 @@ package daemon
 
 
 import (
 import (
 	"context"
 	"context"
-	"fmt"
-	"net/http"
 	"time"
 	"time"
 
 
-	"github.com/docker/docker/api/errors"
 	containerpkg "github.com/docker/docker/container"
 	containerpkg "github.com/docker/docker/container"
+	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 )
 )
 
 
@@ -23,15 +21,14 @@ func (daemon *Daemon) ContainerStop(name string, seconds *int) error {
 		return err
 		return err
 	}
 	}
 	if !container.IsRunning() {
 	if !container.IsRunning() {
-		err := fmt.Errorf("Container %s is already stopped", name)
-		return errors.NewErrorWithStatusCode(err, http.StatusNotModified)
+		return containerNotModifiedError{running: false}
 	}
 	}
 	if seconds == nil {
 	if seconds == nil {
 		stopTimeout := container.StopTimeout()
 		stopTimeout := container.StopTimeout()
 		seconds = &stopTimeout
 		seconds = &stopTimeout
 	}
 	}
 	if err := daemon.containerStop(container, *seconds); err != nil {
 	if err := daemon.containerStop(container, *seconds); err != nil {
-		return fmt.Errorf("Cannot stop container %s: %v", name, err)
+		return errors.Wrapf(systemError{err}, "cannot stop container: %s", name)
 	}
 	}
 	return nil
 	return nil
 }
 }

+ 1 - 1
daemon/top_unix.go

@@ -130,7 +130,7 @@ func (daemon *Daemon) ContainerTop(name string, psArgs string) (*container.Conta
 	}
 	}
 
 
 	if !container.IsRunning() {
 	if !container.IsRunning() {
-		return nil, errNotRunning{container.ID}
+		return nil, errNotRunning(container.ID)
 	}
 	}
 
 
 	if container.IsRestarting() {
 	if container.IsRestarting() {

+ 5 - 3
daemon/update.go

@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"fmt"
 
 
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/container"
+	"github.com/pkg/errors"
 )
 )
 
 
 // ContainerUpdate updates configuration of the container
 // ContainerUpdate updates configuration of the container
@@ -12,7 +13,7 @@ func (daemon *Daemon) ContainerUpdate(name string, hostConfig *container.HostCon
 
 
 	warnings, err := daemon.verifyContainerSettings(hostConfig, nil, true)
 	warnings, err := daemon.verifyContainerSettings(hostConfig, nil, true)
 	if err != nil {
 	if err != nil {
-		return container.ContainerUpdateOKBody{Warnings: warnings}, err
+		return container.ContainerUpdateOKBody{Warnings: warnings}, validationError{err}
 	}
 	}
 
 
 	if err := daemon.update(name, hostConfig); err != nil {
 	if err := daemon.update(name, hostConfig); err != nil {
@@ -72,7 +73,8 @@ func (daemon *Daemon) update(name string, hostConfig *container.HostConfig) erro
 	if container.IsRunning() && !container.IsRestarting() {
 	if container.IsRunning() && !container.IsRestarting() {
 		if err := daemon.containerd.UpdateResources(container.ID, toContainerdResources(hostConfig.Resources)); err != nil {
 		if err := daemon.containerd.UpdateResources(container.ID, toContainerdResources(hostConfig.Resources)); err != nil {
 			restoreConfig = true
 			restoreConfig = true
-			return errCannotUpdate(container.ID, err)
+			// TODO: it would be nice if containerd responded with better errors here so we can classify this better.
+			return errCannotUpdate(container.ID, systemError{err})
 		}
 		}
 	}
 	}
 
 
@@ -82,5 +84,5 @@ func (daemon *Daemon) update(name string, hostConfig *container.HostConfig) erro
 }
 }
 
 
 func errCannotUpdate(containerID string, err error) error {
 func errCannotUpdate(containerID string, err error) error {
-	return fmt.Errorf("Cannot update container %s: %v", containerID, err)
+	return errors.Wrap(err, "Cannot update container "+containerID)
 }
 }

+ 4 - 5
daemon/volumes.go

@@ -1,7 +1,6 @@
 package daemon
 package daemon
 
 
 import (
 import (
-	"errors"
 	"fmt"
 	"fmt"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
@@ -9,13 +8,13 @@ import (
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
-	dockererrors "github.com/docker/docker/api/errors"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	containertypes "github.com/docker/docker/api/types/container"
 	containertypes "github.com/docker/docker/api/types/container"
 	mounttypes "github.com/docker/docker/api/types/mount"
 	mounttypes "github.com/docker/docker/api/types/mount"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/volume"
 	"github.com/docker/docker/volume"
 	"github.com/docker/docker/volume/drivers"
 	"github.com/docker/docker/volume/drivers"
+	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 )
 )
 
 
@@ -149,7 +148,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
 		// #10618
 		// #10618
 		_, tmpfsExists := hostConfig.Tmpfs[bind.Destination]
 		_, tmpfsExists := hostConfig.Tmpfs[bind.Destination]
 		if binds[bind.Destination] || tmpfsExists {
 		if binds[bind.Destination] || tmpfsExists {
-			return fmt.Errorf("Duplicate mount point '%s'", bind.Destination)
+			return duplicateMountPointError(bind.Destination)
 		}
 		}
 
 
 		if bind.Type == mounttypes.TypeVolume {
 		if bind.Type == mounttypes.TypeVolume {
@@ -175,11 +174,11 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
 	for _, cfg := range hostConfig.Mounts {
 	for _, cfg := range hostConfig.Mounts {
 		mp, err := volume.ParseMountSpec(cfg)
 		mp, err := volume.ParseMountSpec(cfg)
 		if err != nil {
 		if err != nil {
-			return dockererrors.NewBadRequestError(err)
+			return validationError{err}
 		}
 		}
 
 
 		if binds[mp.Destination] {
 		if binds[mp.Destination] {
-			return fmt.Errorf("Duplicate mount point '%s'", cfg.Target)
+			return duplicateMountPointError(cfg.Target)
 		}
 		}
 
 
 		if mp.Type == mounttypes.TypeVolume {
 		if mp.Type == mounttypes.TypeVolume {

+ 70 - 14
distribution/errors.go

@@ -1,6 +1,7 @@
 package distribution
 package distribution
 
 
 import (
 import (
+	"fmt"
 	"net/url"
 	"net/url"
 	"strings"
 	"strings"
 	"syscall"
 	"syscall"
@@ -12,7 +13,6 @@ import (
 	"github.com/docker/distribution/registry/client"
 	"github.com/docker/distribution/registry/client"
 	"github.com/docker/distribution/registry/client/auth"
 	"github.com/docker/distribution/registry/client/auth"
 	"github.com/docker/docker/distribution/xfer"
 	"github.com/docker/docker/distribution/xfer"
-	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 )
 )
 
 
@@ -60,6 +60,45 @@ func shouldV2Fallback(err errcode.Error) bool {
 	return false
 	return false
 }
 }
 
 
+type notFoundError struct {
+	cause errcode.Error
+	ref   reference.Named
+}
+
+func (e notFoundError) Error() string {
+	switch e.cause.Code {
+	case errcode.ErrorCodeDenied:
+		// ErrorCodeDenied is used when access to the repository was denied
+		return fmt.Sprintf("pull access denied for %s, repository does not exist or may require 'docker login'", reference.FamiliarName(e.ref))
+	case v2.ErrorCodeManifestUnknown:
+		return fmt.Sprintf("manifest for %s not found", reference.FamiliarString(e.ref))
+	case v2.ErrorCodeNameUnknown:
+		return fmt.Sprintf("repository %s not found", reference.FamiliarName(e.ref))
+	}
+	// Shouldn't get here, but this is better than returning an empty string
+	return e.cause.Message
+}
+
+func (e notFoundError) NotFound() {}
+
+func (e notFoundError) Cause() error {
+	return e.cause
+}
+
+type unknownError struct {
+	cause error
+}
+
+func (e unknownError) Error() string {
+	return e.cause.Error()
+}
+
+func (e unknownError) Cause() error {
+	return e.cause
+}
+
+func (e unknownError) Unknown() {}
+
 // TranslatePullError is used to convert an error from a registry pull
 // TranslatePullError is used to convert an error from a registry pull
 // operation to an error representing the entire pull operation. Any error
 // operation to an error representing the entire pull operation. Any error
 // information which is not used by the returned error gets output to
 // information which is not used by the returned error gets output to
@@ -74,25 +113,15 @@ func TranslatePullError(err error, ref reference.Named) error {
 			return TranslatePullError(v[0], ref)
 			return TranslatePullError(v[0], ref)
 		}
 		}
 	case errcode.Error:
 	case errcode.Error:
-		var newErr error
 		switch v.Code {
 		switch v.Code {
-		case errcode.ErrorCodeDenied:
-			// ErrorCodeDenied is used when access to the repository was denied
-			newErr = errors.Errorf("pull access denied for %s, repository does not exist or may require 'docker login'", reference.FamiliarName(ref))
-		case v2.ErrorCodeManifestUnknown:
-			newErr = errors.Errorf("manifest for %s not found", reference.FamiliarString(ref))
-		case v2.ErrorCodeNameUnknown:
-			newErr = errors.Errorf("repository %s not found", reference.FamiliarName(ref))
-		}
-		if newErr != nil {
-			logrus.Infof("Translating %q to %q", err, newErr)
-			return newErr
+		case errcode.ErrorCodeDenied, v2.ErrorCodeManifestUnknown, v2.ErrorCodeNameUnknown:
+			return notFoundError{v, ref}
 		}
 		}
 	case xfer.DoNotRetry:
 	case xfer.DoNotRetry:
 		return TranslatePullError(v.Err, ref)
 		return TranslatePullError(v.Err, ref)
 	}
 	}
 
 
-	return err
+	return unknownError{err}
 }
 }
 
 
 // continueOnError returns true if we should fallback to the next endpoint
 // continueOnError returns true if we should fallback to the next endpoint
@@ -157,3 +186,30 @@ func retryOnError(err error) error {
 	// add them to the switch above.
 	// add them to the switch above.
 	return err
 	return err
 }
 }
+
+type invalidManifestClassError struct {
+	mediaType string
+	class     string
+}
+
+func (e invalidManifestClassError) Error() string {
+	return fmt.Sprintf("Encountered remote %q(%s) when fetching", e.mediaType, e.class)
+}
+
+func (e invalidManifestClassError) InvalidParameter() {}
+
+type invalidManifestFormatError struct{}
+
+func (invalidManifestFormatError) Error() string {
+	return "unsupported manifest format"
+}
+
+func (invalidManifestFormatError) InvalidParameter() {}
+
+type reservedNameError string
+
+func (e reservedNameError) Error() string {
+	return "'" + string(e) + "' is a reserved name"
+}
+
+func (e reservedNameError) Forbidden() {}

+ 2 - 1
distribution/pull.go

@@ -10,6 +10,7 @@ import (
 	refstore "github.com/docker/docker/reference"
 	refstore "github.com/docker/docker/reference"
 	"github.com/docker/docker/registry"
 	"github.com/docker/docker/registry"
 	"github.com/opencontainers/go-digest"
 	"github.com/opencontainers/go-digest"
+	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
@@ -173,7 +174,7 @@ func writeStatus(requestedTag string, out progress.Output, layersDownloaded bool
 // ValidateRepoName validates the name of a repository.
 // ValidateRepoName validates the name of a repository.
 func ValidateRepoName(name reference.Named) error {
 func ValidateRepoName(name reference.Named) error {
 	if reference.FamiliarName(name) == api.NoBaseImageSpecifier {
 	if reference.FamiliarName(name) == api.NoBaseImageSpecifier {
-		return fmt.Errorf("'%s' is a reserved name", api.NoBaseImageSpecifier)
+		return errors.WithStack(reservedNameError(api.NoBaseImageSpecifier))
 	}
 	}
 	return nil
 	return nil
 }
 }

+ 1 - 5
distribution/pull_v1.go

@@ -52,11 +52,7 @@ func (p *v1Puller) Pull(ctx context.Context, ref reference.Named) error {
 		registry.DockerHeaders(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders)...,
 		registry.DockerHeaders(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders)...,
 	)
 	)
 	client := registry.HTTPClient(tr)
 	client := registry.HTTPClient(tr)
-	v1Endpoint, err := p.endpoint.ToV1Endpoint(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders)
-	if err != nil {
-		logrus.Debugf("Could not get v1 endpoint: %v", err)
-		return fallbackError{err: err}
-	}
+	v1Endpoint := p.endpoint.ToV1Endpoint(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders)
 	p.session, err = registry.NewSession(client, p.config.AuthConfig, v1Endpoint)
 	p.session, err = registry.NewSession(client, p.config.AuthConfig, v1Endpoint)
 	if err != nil {
 	if err != nil {
 		// TODO(dmcgowan): Check if should fallback
 		// TODO(dmcgowan): Check if should fallback

+ 3 - 3
distribution/pull_v2.go

@@ -2,7 +2,6 @@ package distribution
 
 
 import (
 import (
 	"encoding/json"
 	"encoding/json"
-	"errors"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
 	"io/ioutil"
 	"io/ioutil"
@@ -30,6 +29,7 @@ import (
 	refstore "github.com/docker/docker/reference"
 	refstore "github.com/docker/docker/reference"
 	"github.com/docker/docker/registry"
 	"github.com/docker/docker/registry"
 	"github.com/opencontainers/go-digest"
 	"github.com/opencontainers/go-digest"
+	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
@@ -368,7 +368,7 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat
 			if configClass == "" {
 			if configClass == "" {
 				configClass = "unknown"
 				configClass = "unknown"
 			}
 			}
-			return false, fmt.Errorf("Encountered remote %q(%s) when fetching", m.Manifest.Config.MediaType, configClass)
+			return false, invalidManifestClassError{m.Manifest.Config.MediaType, configClass}
 		}
 		}
 	}
 	}
 
 
@@ -404,7 +404,7 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat
 			return false, err
 			return false, err
 		}
 		}
 	default:
 	default:
-		return false, errors.New("unsupported manifest format")
+		return false, invalidManifestFormatError{}
 	}
 	}
 
 
 	progress.Message(p.config.ProgressOutput, "", "Digest: "+manifestDigest.String())
 	progress.Message(p.config.ProgressOutput, "", "Digest: "+manifestDigest.String())

+ 1 - 5
distribution/push_v1.go

@@ -41,11 +41,7 @@ func (p *v1Pusher) Push(ctx context.Context) error {
 		registry.DockerHeaders(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders)...,
 		registry.DockerHeaders(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders)...,
 	)
 	)
 	client := registry.HTTPClient(tr)
 	client := registry.HTTPClient(tr)
-	v1Endpoint, err := p.endpoint.ToV1Endpoint(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders)
-	if err != nil {
-		logrus.Debugf("Could not get v1 endpoint: %v", err)
-		return fallbackError{err: err}
-	}
+	v1Endpoint := p.endpoint.ToV1Endpoint(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders)
 	p.session, err = registry.NewSession(client, p.config.AuthConfig, v1Endpoint)
 	p.session, err = registry.NewSession(client, p.config.AuthConfig, v1Endpoint)
 	if err != nil {
 	if err != nil {
 		// TODO(dmcgowan): Check if should fallback
 		// TODO(dmcgowan): Check if should fallback

+ 10 - 2
image/store.go

@@ -180,13 +180,21 @@ func (is *store) Create(config []byte) (ID, error) {
 	return imageID, nil
 	return imageID, nil
 }
 }
 
 
+type imageNotFoundError string
+
+func (e imageNotFoundError) Error() string {
+	return "No such image: " + string(e)
+}
+
+func (imageNotFoundError) NotFound() {}
+
 func (is *store) Search(term string) (ID, error) {
 func (is *store) Search(term string) (ID, error) {
 	dgst, err := is.digestSet.Lookup(term)
 	dgst, err := is.digestSet.Lookup(term)
 	if err != nil {
 	if err != nil {
 		if err == digestset.ErrDigestNotFound {
 		if err == digestset.ErrDigestNotFound {
-			err = fmt.Errorf("No such image: %s", term)
+			err = imageNotFoundError(term)
 		}
 		}
-		return "", err
+		return "", errors.WithStack(err)
 	}
 	}
 	return IDFromDigest(dgst), nil
 	return IDFromDigest(dgst), nil
 }
 }

+ 18 - 19
integration-cli/docker_api_containers_test.go

@@ -508,7 +508,7 @@ func (s *DockerSuite) TestContainerAPIBadPort(c *check.C) {
 
 
 	status, body, err := request.SockRequest("POST", "/containers/create", config, daemonHost())
 	status, body, err := request.SockRequest("POST", "/containers/create", config, daemonHost())
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
-	c.Assert(status, checker.Equals, http.StatusInternalServerError)
+	c.Assert(status, checker.Equals, http.StatusBadRequest)
 	c.Assert(getErrorMessage(c, body), checker.Equals, `invalid port specification: "aa80"`, check.Commentf("Incorrect error msg: %s", body))
 	c.Assert(getErrorMessage(c, body), checker.Equals, `invalid port specification: "aa80"`, check.Commentf("Incorrect error msg: %s", body))
 }
 }
 
 
@@ -537,7 +537,7 @@ func (s *DockerSuite) TestContainerAPICreateEmptyConfig(c *check.C) {
 
 
 	status, body, err := request.SockRequest("POST", "/containers/create", config, daemonHost())
 	status, body, err := request.SockRequest("POST", "/containers/create", config, daemonHost())
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
-	c.Assert(status, checker.Equals, http.StatusInternalServerError)
+	c.Assert(status, checker.Equals, http.StatusBadRequest)
 
 
 	expected := "Config cannot be empty in order to create a container"
 	expected := "Config cannot be empty in order to create a container"
 	c.Assert(getErrorMessage(c, body), checker.Equals, expected)
 	c.Assert(getErrorMessage(c, body), checker.Equals, expected)
@@ -673,13 +673,13 @@ func (s *DockerSuite) TestContainerAPIVerifyHeader(c *check.C) {
 	// Try with no content-type
 	// Try with no content-type
 	res, body, err := create("")
 	res, body, err := create("")
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
-	c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError)
+	c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest)
 	body.Close()
 	body.Close()
 
 
 	// Try with wrong content-type
 	// Try with wrong content-type
 	res, body, err = create("application/xml")
 	res, body, err = create("application/xml")
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
-	c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError)
+	c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest)
 	body.Close()
 	body.Close()
 
 
 	// now application/json
 	// now application/json
@@ -705,7 +705,7 @@ func (s *DockerSuite) TestContainerAPIInvalidPortSyntax(c *check.C) {
 
 
 	res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON)
 	res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON)
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
-	c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError)
+	c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest)
 
 
 	b, err := testutil.ReadBody(body)
 	b, err := testutil.ReadBody(body)
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
@@ -725,7 +725,7 @@ func (s *DockerSuite) TestContainerAPIRestartPolicyInvalidPolicyName(c *check.C)
 
 
 	res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON)
 	res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON)
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
-	c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError)
+	c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest)
 
 
 	b, err := testutil.ReadBody(body)
 	b, err := testutil.ReadBody(body)
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
@@ -745,7 +745,7 @@ func (s *DockerSuite) TestContainerAPIRestartPolicyRetryMismatch(c *check.C) {
 
 
 	res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON)
 	res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON)
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
-	c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError)
+	c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest)
 
 
 	b, err := testutil.ReadBody(body)
 	b, err := testutil.ReadBody(body)
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
@@ -765,7 +765,7 @@ func (s *DockerSuite) TestContainerAPIRestartPolicyNegativeRetryCount(c *check.C
 
 
 	res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON)
 	res, body, err := request.Post("/containers/create", request.RawString(config), request.JSON)
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
-	c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError)
+	c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest)
 
 
 	b, err := testutil.ReadBody(body)
 	b, err := testutil.ReadBody(body)
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
@@ -850,7 +850,7 @@ func (s *DockerSuite) TestCreateWithTooLowMemoryLimit(c *check.C) {
 	b, err2 := testutil.ReadBody(body)
 	b, err2 := testutil.ReadBody(body)
 	c.Assert(err2, checker.IsNil)
 	c.Assert(err2, checker.IsNil)
 
 
-	c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError)
+	c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest)
 	c.Assert(string(b), checker.Contains, "Minimum memory limit allowed is 4MB")
 	c.Assert(string(b), checker.Contains, "Minimum memory limit allowed is 4MB")
 }
 }
 
 
@@ -1005,7 +1005,7 @@ func (s *DockerSuite) TestContainerAPICopyPre124(c *check.C) {
 	c.Assert(found, checker.True)
 	c.Assert(found, checker.True)
 }
 }
 
 
-func (s *DockerSuite) TestContainerAPICopyResourcePathEmptyPr124(c *check.C) {
+func (s *DockerSuite) TestContainerAPICopyResourcePathEmptyPre124(c *check.C) {
 	testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
 	testRequires(c, DaemonIsLinux) // Windows only supports 1.25 or later
 	name := "test-container-api-copy-resource-empty"
 	name := "test-container-api-copy-resource-empty"
 	dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt")
 	dockerCmd(c, "run", "--name", name, "busybox", "touch", "/test.txt")
@@ -1016,7 +1016,7 @@ func (s *DockerSuite) TestContainerAPICopyResourcePathEmptyPr124(c *check.C) {
 
 
 	status, body, err := request.SockRequest("POST", "/v1.23/containers/"+name+"/copy", postData, daemonHost())
 	status, body, err := request.SockRequest("POST", "/v1.23/containers/"+name+"/copy", postData, daemonHost())
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
-	c.Assert(status, checker.Equals, http.StatusInternalServerError)
+	c.Assert(status, checker.Equals, http.StatusBadRequest)
 	c.Assert(string(body), checker.Matches, "Path cannot be empty\n")
 	c.Assert(string(body), checker.Matches, "Path cannot be empty\n")
 }
 }
 
 
@@ -1031,7 +1031,7 @@ func (s *DockerSuite) TestContainerAPICopyResourcePathNotFoundPre124(c *check.C)
 
 
 	status, body, err := request.SockRequest("POST", "/v1.23/containers/"+name+"/copy", postData, daemonHost())
 	status, body, err := request.SockRequest("POST", "/v1.23/containers/"+name+"/copy", postData, daemonHost())
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
-	c.Assert(status, checker.Equals, http.StatusInternalServerError)
+	c.Assert(status, checker.Equals, http.StatusNotFound)
 	c.Assert(string(body), checker.Matches, "Could not find the file /notexist in container "+name+"\n")
 	c.Assert(string(body), checker.Matches, "Could not find the file /notexist in container "+name+"\n")
 }
 }
 
 
@@ -1301,7 +1301,7 @@ func (s *DockerSuite) TestPostContainersCreateWithWrongCpusetValues(c *check.C)
 	name := "wrong-cpuset-cpus"
 	name := "wrong-cpuset-cpus"
 	status, body, err := request.SockRequest("POST", "/containers/create?name="+name, c1, daemonHost())
 	status, body, err := request.SockRequest("POST", "/containers/create?name="+name, c1, daemonHost())
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
-	c.Assert(status, checker.Equals, http.StatusInternalServerError)
+	c.Assert(status, checker.Equals, http.StatusBadRequest)
 	expected := "Invalid value 1-42,, for cpuset cpus"
 	expected := "Invalid value 1-42,, for cpuset cpus"
 	c.Assert(getErrorMessage(c, body), checker.Equals, expected)
 	c.Assert(getErrorMessage(c, body), checker.Equals, expected)
 
 
@@ -1312,7 +1312,7 @@ func (s *DockerSuite) TestPostContainersCreateWithWrongCpusetValues(c *check.C)
 	name = "wrong-cpuset-mems"
 	name = "wrong-cpuset-mems"
 	status, body, err = request.SockRequest("POST", "/containers/create?name="+name, c2, daemonHost())
 	status, body, err = request.SockRequest("POST", "/containers/create?name="+name, c2, daemonHost())
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
-	c.Assert(status, checker.Equals, http.StatusInternalServerError)
+	c.Assert(status, checker.Equals, http.StatusBadRequest)
 	expected = "Invalid value 42-3,1-- for cpuset mems"
 	expected = "Invalid value 42-3,1-- for cpuset mems"
 	c.Assert(getErrorMessage(c, body), checker.Equals, expected)
 	c.Assert(getErrorMessage(c, body), checker.Equals, expected)
 }
 }
@@ -1327,7 +1327,7 @@ func (s *DockerSuite) TestPostContainersCreateShmSizeNegative(c *check.C) {
 
 
 	status, body, err := request.SockRequest("POST", "/containers/create", config, daemonHost())
 	status, body, err := request.SockRequest("POST", "/containers/create", config, daemonHost())
 	c.Assert(err, check.IsNil)
 	c.Assert(err, check.IsNil)
-	c.Assert(status, check.Equals, http.StatusInternalServerError)
+	c.Assert(status, check.Equals, http.StatusBadRequest)
 	c.Assert(getErrorMessage(c, body), checker.Contains, "SHM size can not be less than 0")
 	c.Assert(getErrorMessage(c, body), checker.Contains, "SHM size can not be less than 0")
 }
 }
 
 
@@ -1463,7 +1463,7 @@ func (s *DockerSuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *che
 	name := "oomscoreadj-over"
 	name := "oomscoreadj-over"
 	status, b, err := request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
 	status, b, err := request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
 	c.Assert(err, check.IsNil)
 	c.Assert(err, check.IsNil)
-	c.Assert(status, check.Equals, http.StatusInternalServerError)
+	c.Assert(status, check.Equals, http.StatusBadRequest)
 
 
 	expected := "Invalid value 1001, range for oom score adj is [-1000, 1000]"
 	expected := "Invalid value 1001, range for oom score adj is [-1000, 1000]"
 	msg := getErrorMessage(c, b)
 	msg := getErrorMessage(c, b)
@@ -1478,7 +1478,7 @@ func (s *DockerSuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *che
 	name = "oomscoreadj-low"
 	name = "oomscoreadj-low"
 	status, b, err = request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
 	status, b, err = request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
 	c.Assert(err, check.IsNil)
 	c.Assert(err, check.IsNil)
-	c.Assert(status, check.Equals, http.StatusInternalServerError)
+	c.Assert(status, check.Equals, http.StatusBadRequest)
 	expected = "Invalid value -1001, range for oom score adj is [-1000, 1000]"
 	expected = "Invalid value -1001, range for oom score adj is [-1000, 1000]"
 	msg = getErrorMessage(c, b)
 	msg = getErrorMessage(c, b)
 	if !strings.Contains(msg, expected) {
 	if !strings.Contains(msg, expected) {
@@ -1488,10 +1488,9 @@ func (s *DockerSuite) TestPostContainersCreateWithOomScoreAdjInvalidRange(c *che
 
 
 // test case for #22210 where an empty container name caused panic.
 // test case for #22210 where an empty container name caused panic.
 func (s *DockerSuite) TestContainerAPIDeleteWithEmptyName(c *check.C) {
 func (s *DockerSuite) TestContainerAPIDeleteWithEmptyName(c *check.C) {
-	status, out, err := request.SockRequest("DELETE", "/containers/", nil, daemonHost())
+	status, _, err := request.SockRequest("DELETE", "/containers/", nil, daemonHost())
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
 	c.Assert(status, checker.Equals, http.StatusBadRequest)
 	c.Assert(status, checker.Equals, http.StatusBadRequest)
-	c.Assert(string(out), checker.Contains, "No container name or ID supplied")
 }
 }
 
 
 func (s *DockerSuite) TestContainerAPIStatsWithNetworkDisabled(c *check.C) {
 func (s *DockerSuite) TestContainerAPIStatsWithNetworkDisabled(c *check.C) {

+ 5 - 5
integration-cli/docker_api_create_test.go

@@ -25,7 +25,7 @@ func (s *DockerSuite) TestAPICreateWithInvalidHealthcheckParams(c *check.C) {
 
 
 	status, body, err := request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
 	status, body, err := request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
 	c.Assert(err, check.IsNil)
 	c.Assert(err, check.IsNil)
-	c.Assert(status, check.Equals, http.StatusInternalServerError)
+	c.Assert(status, check.Equals, http.StatusBadRequest)
 	expected := fmt.Sprintf("Interval in Healthcheck cannot be less than %s", container.MinimumDuration)
 	expected := fmt.Sprintf("Interval in Healthcheck cannot be less than %s", container.MinimumDuration)
 	c.Assert(getErrorMessage(c, body), checker.Contains, expected)
 	c.Assert(getErrorMessage(c, body), checker.Contains, expected)
 
 
@@ -41,7 +41,7 @@ func (s *DockerSuite) TestAPICreateWithInvalidHealthcheckParams(c *check.C) {
 	}
 	}
 	status, body, err = request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
 	status, body, err = request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
 	c.Assert(err, check.IsNil)
 	c.Assert(err, check.IsNil)
-	c.Assert(status, check.Equals, http.StatusInternalServerError)
+	c.Assert(status, check.Equals, http.StatusBadRequest)
 	c.Assert(getErrorMessage(c, body), checker.Contains, expected)
 	c.Assert(getErrorMessage(c, body), checker.Contains, expected)
 
 
 	// test invalid Timeout in Healthcheck: less than 1ms
 	// test invalid Timeout in Healthcheck: less than 1ms
@@ -56,7 +56,7 @@ func (s *DockerSuite) TestAPICreateWithInvalidHealthcheckParams(c *check.C) {
 	}
 	}
 	status, body, err = request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
 	status, body, err = request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
 	c.Assert(err, check.IsNil)
 	c.Assert(err, check.IsNil)
-	c.Assert(status, check.Equals, http.StatusInternalServerError)
+	c.Assert(status, check.Equals, http.StatusBadRequest)
 	expected = fmt.Sprintf("Timeout in Healthcheck cannot be less than %s", container.MinimumDuration)
 	expected = fmt.Sprintf("Timeout in Healthcheck cannot be less than %s", container.MinimumDuration)
 	c.Assert(getErrorMessage(c, body), checker.Contains, expected)
 	c.Assert(getErrorMessage(c, body), checker.Contains, expected)
 
 
@@ -72,7 +72,7 @@ func (s *DockerSuite) TestAPICreateWithInvalidHealthcheckParams(c *check.C) {
 	}
 	}
 	status, body, err = request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
 	status, body, err = request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
 	c.Assert(err, check.IsNil)
 	c.Assert(err, check.IsNil)
-	c.Assert(status, check.Equals, http.StatusInternalServerError)
+	c.Assert(status, check.Equals, http.StatusBadRequest)
 	expected = "Retries in Healthcheck cannot be negative"
 	expected = "Retries in Healthcheck cannot be negative"
 	c.Assert(getErrorMessage(c, body), checker.Contains, expected)
 	c.Assert(getErrorMessage(c, body), checker.Contains, expected)
 
 
@@ -89,7 +89,7 @@ func (s *DockerSuite) TestAPICreateWithInvalidHealthcheckParams(c *check.C) {
 	}
 	}
 	status, body, err = request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
 	status, body, err = request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
 	c.Assert(err, check.IsNil)
 	c.Assert(err, check.IsNil)
-	c.Assert(status, check.Equals, http.StatusInternalServerError)
+	c.Assert(status, check.Equals, http.StatusBadRequest)
 	expected = fmt.Sprintf("StartPeriod in Healthcheck cannot be less than %s", container.MinimumDuration)
 	expected = fmt.Sprintf("StartPeriod in Healthcheck cannot be less than %s", container.MinimumDuration)
 	c.Assert(getErrorMessage(c, body), checker.Contains, expected)
 	c.Assert(getErrorMessage(c, body), checker.Contains, expected)
 }
 }

+ 1 - 1
integration-cli/docker_api_exec_resize_test.go

@@ -22,7 +22,7 @@ func (s *DockerSuite) TestExecResizeAPIHeightWidthNoInt(c *check.C) {
 	endpoint := "/exec/" + cleanedContainerID + "/resize?h=foo&w=bar"
 	endpoint := "/exec/" + cleanedContainerID + "/resize?h=foo&w=bar"
 	status, _, err := request.SockRequest("POST", endpoint, nil, daemonHost())
 	status, _, err := request.SockRequest("POST", endpoint, nil, daemonHost())
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
-	c.Assert(status, checker.Equals, http.StatusInternalServerError)
+	c.Assert(status, checker.Equals, http.StatusBadRequest)
 }
 }
 
 
 // Part of #14845
 // Part of #14845

+ 3 - 3
integration-cli/docker_api_exec_test.go

@@ -23,7 +23,7 @@ func (s *DockerSuite) TestExecAPICreateNoCmd(c *check.C) {
 
 
 	status, body, err := request.SockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": nil}, daemonHost())
 	status, body, err := request.SockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": nil}, daemonHost())
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
-	c.Assert(status, checker.Equals, http.StatusInternalServerError)
+	c.Assert(status, checker.Equals, http.StatusBadRequest)
 
 
 	comment := check.Commentf("Expected message when creating exec command with no Cmd specified")
 	comment := check.Commentf("Expected message when creating exec command with no Cmd specified")
 	c.Assert(getErrorMessage(c, body), checker.Contains, "No exec command specified", comment)
 	c.Assert(getErrorMessage(c, body), checker.Contains, "No exec command specified", comment)
@@ -40,7 +40,7 @@ func (s *DockerSuite) TestExecAPICreateNoValidContentType(c *check.C) {
 
 
 	res, body, err := request.Post(fmt.Sprintf("/containers/%s/exec", name), request.RawContent(ioutil.NopCloser(jsonData)), request.ContentType("test/plain"))
 	res, body, err := request.Post(fmt.Sprintf("/containers/%s/exec", name), request.RawContent(ioutil.NopCloser(jsonData)), request.ContentType("test/plain"))
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
-	c.Assert(res.StatusCode, checker.Equals, http.StatusInternalServerError)
+	c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest)
 
 
 	b, err := testutil.ReadBody(body)
 	b, err := testutil.ReadBody(body)
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
@@ -177,7 +177,7 @@ func (s *DockerSuite) TestExecAPIStartInvalidCommand(c *check.C) {
 	dockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
 	dockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
 
 
 	id := createExecCmd(c, name, "invalid")
 	id := createExecCmd(c, name, "invalid")
-	startExec(c, id, http.StatusNotFound)
+	startExec(c, id, http.StatusBadRequest)
 	waitForExec(c, id)
 	waitForExec(c, id)
 
 
 	var inspectJSON struct{ ExecIDs []string }
 	var inspectJSON struct{ ExecIDs []string }

+ 2 - 2
integration-cli/docker_api_resize_test.go

@@ -25,7 +25,7 @@ func (s *DockerSuite) TestResizeAPIHeightWidthNoInt(c *check.C) {
 
 
 	endpoint := "/containers/" + cleanedContainerID + "/resize?h=foo&w=bar"
 	endpoint := "/containers/" + cleanedContainerID + "/resize?h=foo&w=bar"
 	status, _, err := request.SockRequest("POST", endpoint, nil, daemonHost())
 	status, _, err := request.SockRequest("POST", endpoint, nil, daemonHost())
-	c.Assert(status, check.Equals, http.StatusInternalServerError)
+	c.Assert(status, check.Equals, http.StatusBadRequest)
 	c.Assert(err, check.IsNil)
 	c.Assert(err, check.IsNil)
 }
 }
 
 
@@ -38,7 +38,7 @@ func (s *DockerSuite) TestResizeAPIResponseWhenContainerNotStarted(c *check.C) {
 
 
 	endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40"
 	endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40"
 	status, body, err := request.SockRequest("POST", endpoint, nil, daemonHost())
 	status, body, err := request.SockRequest("POST", endpoint, nil, daemonHost())
-	c.Assert(status, check.Equals, http.StatusInternalServerError)
+	c.Assert(status, check.Equals, http.StatusConflict)
 	c.Assert(err, check.IsNil)
 	c.Assert(err, check.IsNil)
 
 
 	c.Assert(getErrorMessage(c, body), checker.Contains, "is not running", check.Commentf("resize should fail with message 'Container is not running'"))
 	c.Assert(getErrorMessage(c, body), checker.Contains, "is not running", check.Commentf("resize should fail with message 'Container is not running'"))

+ 2 - 2
integration-cli/docker_api_test.go

@@ -80,7 +80,7 @@ func (s *DockerSuite) TestAPIDockerAPIVersion(c *check.C) {
 func (s *DockerSuite) TestAPIErrorJSON(c *check.C) {
 func (s *DockerSuite) TestAPIErrorJSON(c *check.C) {
 	httpResp, body, err := request.Post("/containers/create", request.JSONBody(struct{}{}))
 	httpResp, body, err := request.Post("/containers/create", request.JSONBody(struct{}{}))
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
-	c.Assert(httpResp.StatusCode, checker.Equals, http.StatusInternalServerError)
+	c.Assert(httpResp.StatusCode, checker.Equals, http.StatusBadRequest)
 	c.Assert(httpResp.Header.Get("Content-Type"), checker.Equals, "application/json")
 	c.Assert(httpResp.Header.Get("Content-Type"), checker.Equals, "application/json")
 	b, err := testutil.ReadBody(body)
 	b, err := testutil.ReadBody(body)
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
@@ -93,7 +93,7 @@ func (s *DockerSuite) TestAPIErrorPlainText(c *check.C) {
 	testRequires(c, DaemonIsLinux)
 	testRequires(c, DaemonIsLinux)
 	httpResp, body, err := request.Post("/v1.23/containers/create", request.JSONBody(struct{}{}))
 	httpResp, body, err := request.Post("/v1.23/containers/create", request.JSONBody(struct{}{}))
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
-	c.Assert(httpResp.StatusCode, checker.Equals, http.StatusInternalServerError)
+	c.Assert(httpResp.StatusCode, checker.Equals, http.StatusBadRequest)
 	c.Assert(httpResp.Header.Get("Content-Type"), checker.Contains, "text/plain")
 	c.Assert(httpResp.Header.Get("Content-Type"), checker.Contains, "text/plain")
 	b, err := testutil.ReadBody(body)
 	b, err := testutil.ReadBody(body)
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)

+ 4 - 6
integration-cli/docker_cli_cp_from_container_test.go

@@ -64,19 +64,17 @@ func (s *DockerSuite) TestCpFromErrDstParentNotExists(c *check.C) {
 	// Try with a file source.
 	// Try with a file source.
 	srcPath := containerCpPath(containerID, "/file1")
 	srcPath := containerCpPath(containerID, "/file1")
 	dstPath := cpPath(tmpDir, "notExists", "file1")
 	dstPath := cpPath(tmpDir, "notExists", "file1")
+	_, dstStatErr := os.Lstat(filepath.Dir(dstPath))
+	c.Assert(os.IsNotExist(dstStatErr), checker.True)
 
 
 	err := runDockerCp(c, srcPath, dstPath, nil)
 	err := runDockerCp(c, srcPath, dstPath, nil)
-	c.Assert(err, checker.NotNil)
-
-	c.Assert(isCpNotExist(err), checker.True, check.Commentf("expected IsNotExist error, but got %T: %s", err, err))
+	c.Assert(err.Error(), checker.Contains, dstStatErr.Error())
 
 
 	// Try with a directory source.
 	// Try with a directory source.
 	srcPath = containerCpPath(containerID, "/dir1")
 	srcPath = containerCpPath(containerID, "/dir1")
 
 
 	err = runDockerCp(c, srcPath, dstPath, nil)
 	err = runDockerCp(c, srcPath, dstPath, nil)
-	c.Assert(err, checker.NotNil)
-
-	c.Assert(isCpNotExist(err), checker.True, check.Commentf("expected IsNotExist error, but got %T: %s", err, err))
+	c.Assert(err.Error(), checker.Contains, dstStatErr.Error())
 }
 }
 
 
 // Test for error when DST ends in a trailing
 // Test for error when DST ends in a trailing

+ 4 - 3
integration-cli/docker_cli_cp_to_container_test.go

@@ -2,6 +2,7 @@ package main
 
 
 import (
 import (
 	"os"
 	"os"
+	"strings"
 
 
 	"github.com/docker/docker/integration-cli/checker"
 	"github.com/docker/docker/integration-cli/checker"
 	"github.com/go-check/check"
 	"github.com/go-check/check"
@@ -30,11 +31,11 @@ func (s *DockerSuite) TestCpToErrSrcNotExists(c *check.C) {
 
 
 	srcPath := cpPath(tmpDir, "file1")
 	srcPath := cpPath(tmpDir, "file1")
 	dstPath := containerCpPath(containerID, "file1")
 	dstPath := containerCpPath(containerID, "file1")
+	_, srcStatErr := os.Stat(srcPath)
+	c.Assert(os.IsNotExist(srcStatErr), checker.True)
 
 
 	err := runDockerCp(c, srcPath, dstPath, nil)
 	err := runDockerCp(c, srcPath, dstPath, nil)
-	c.Assert(err, checker.NotNil)
-
-	c.Assert(isCpNotExist(err), checker.True, check.Commentf("expected IsNotExist error, but got %T: %s", err, err))
+	c.Assert(strings.ToLower(err.Error()), checker.Contains, strings.ToLower(srcStatErr.Error()))
 }
 }
 
 
 // Test for error when SRC ends in a trailing
 // Test for error when SRC ends in a trailing

+ 1 - 1
integration-cli/docker_cli_cp_utils_test.go

@@ -231,7 +231,7 @@ func getTestDir(c *check.C, label string) (tmpDir string) {
 }
 }
 
 
 func isCpNotExist(err error) bool {
 func isCpNotExist(err error) bool {
-	return strings.Contains(err.Error(), "no such file or directory") || strings.Contains(err.Error(), "cannot find the file specified")
+	return strings.Contains(strings.ToLower(err.Error()), "could not find the file")
 }
 }
 
 
 func isCpDirNotExist(err error) bool {
 func isCpDirNotExist(err error) bool {

+ 1 - 2
integration-cli/docker_cli_inspect_test.go

@@ -463,6 +463,5 @@ func (s *DockerSuite) TestInspectInvalidReference(c *check.C) {
 	// This test should work on both Windows and Linux
 	// This test should work on both Windows and Linux
 	out, _, err := dockerCmdWithError("inspect", "FooBar")
 	out, _, err := dockerCmdWithError("inspect", "FooBar")
 	c.Assert(err, checker.NotNil)
 	c.Assert(err, checker.NotNil)
-	c.Assert(out, checker.Contains, "Error: No such object: FooBar")
-	c.Assert(err.Error(), checker.Contains, "Error: No such object: FooBar")
+	c.Assert(out, checker.Contains, "no such image: FooBar")
 }
 }

+ 1 - 1
integration-cli/docker_cli_links_test.go

@@ -28,7 +28,7 @@ func (s *DockerSuite) TestLinksInvalidContainerTarget(c *check.C) {
 	// an invalid container target should produce an error
 	// an invalid container target should produce an error
 	c.Assert(err, checker.NotNil, check.Commentf("out: %s", out))
 	c.Assert(err, checker.NotNil, check.Commentf("out: %s", out))
 	// an invalid container target should produce an error
 	// an invalid container target should produce an error
-	c.Assert(out, checker.Contains, "Could not get container")
+	c.Assert(out, checker.Contains, "could not get container")
 }
 }
 
 
 func (s *DockerSuite) TestLinksPingLinkedContainers(c *check.C) {
 func (s *DockerSuite) TestLinksPingLinkedContainers(c *check.C) {

Some files were not shown because too many files changed in this diff