From a3ce441ae05b2fcdda50104c9daec5dbe8c9bfae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= Date: Mon, 19 May 2025 14:16:41 +0200 Subject: [PATCH] client: Use containerd errdefs to convert http errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, we were using our own `FromStatusCode` function to map HTTP status codes to Docker error types. Switch to the containerd code. Signed-off-by: Paweł Gronowski --- client/errors.go | 42 ++++++++ client/request.go | 3 +- errdefs/http_helpers.go | 2 + errdefs/http_helpers_test.go | 1 + vendor.mod | 2 +- .../containerd/errdefs/pkg/errhttp/http.go | 96 +++++++++++++++++++ vendor/modules.txt | 1 + 7 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 vendor/github.com/containerd/errdefs/pkg/errhttp/http.go diff --git a/client/errors.go b/client/errors.go index d19d836208..7bd8593147 100644 --- a/client/errors.go +++ b/client/errors.go @@ -4,8 +4,10 @@ import ( "context" "errors" "fmt" + "net/http" cerrdefs "github.com/containerd/errdefs" + "github.com/containerd/errdefs/pkg/errhttp" "github.com/docker/docker/api/types/versions" ) @@ -85,3 +87,43 @@ func (cli *Client) NewVersionError(ctx context.Context, APIrequired, feature str } return nil } + +type httpError struct { + err error + errdef error +} + +func (e *httpError) Error() string { + return e.err.Error() +} + +func (e *httpError) Unwrap() error { + return e.err +} + +func (e *httpError) Is(target error) bool { + return errors.Is(e.errdef, target) +} + +// httpErrorFromStatusCode creates an errdef error, based on the provided HTTP status-code +func httpErrorFromStatusCode(err error, statusCode int) error { + if err == nil { + return nil + } + base := errhttp.ToNative(statusCode) + if base != nil { + return &httpError{err: err, errdef: base} + } + + switch { + case statusCode >= http.StatusOK && statusCode < http.StatusBadRequest: + // it's a client error + return err + case statusCode >= http.StatusBadRequest && statusCode < http.StatusInternalServerError: + return &httpError{err: err, errdef: cerrdefs.ErrInvalidArgument} + case statusCode >= http.StatusInternalServerError && statusCode < 600: + return &httpError{err: err, errdef: cerrdefs.ErrInternal} + default: + return &httpError{err: err, errdef: cerrdefs.ErrUnknown} + } +} diff --git a/client/request.go b/client/request.go index 80e6d03a57..d9074a736c 100644 --- a/client/request.go +++ b/client/request.go @@ -15,7 +15,6 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/versions" - "github.com/docker/docker/errdefs" "github.com/pkg/errors" ) @@ -197,7 +196,7 @@ func (cli *Client) checkResponseErr(serverResp *http.Response) (retErr error) { return nil } defer func() { - retErr = errdefs.FromStatusCode(retErr, serverResp.StatusCode) + retErr = httpErrorFromStatusCode(retErr, serverResp.StatusCode) }() var body []byte diff --git a/errdefs/http_helpers.go b/errdefs/http_helpers.go index 53c18f2fe0..823ff2d9fc 100644 --- a/errdefs/http_helpers.go +++ b/errdefs/http_helpers.go @@ -5,6 +5,8 @@ import ( ) // FromStatusCode creates an errdef error, based on the provided HTTP status-code +// +// Deprecated: Use [cerrdefs.ToNative] instead func FromStatusCode(err error, statusCode int) error { if err == nil { return nil diff --git a/errdefs/http_helpers_test.go b/errdefs/http_helpers_test.go index bf709fec70..8aeed3a74b 100644 --- a/errdefs/http_helpers_test.go +++ b/errdefs/http_helpers_test.go @@ -83,6 +83,7 @@ func TestFromStatusCode(t *testing.T) { for _, tc := range testCases { t.Run(http.StatusText(tc.status), func(t *testing.T) { + //nolint:staticcheck // ignore SA1019: FromStatusCode is deprecated err := FromStatusCode(tc.err, tc.status) if !tc.check(err) { t.Errorf("unexpected error-type %T", err) diff --git a/vendor.mod b/vendor.mod index f3b3e37a0a..c2de00d4f2 100644 --- a/vendor.mod +++ b/vendor.mod @@ -30,6 +30,7 @@ require ( github.com/containerd/containerd/v2 v2.0.5 github.com/containerd/continuity v0.4.5 github.com/containerd/errdefs v1.0.0 + github.com/containerd/errdefs/pkg v0.3.0 github.com/containerd/fifo v1.1.0 github.com/containerd/log v0.1.0 github.com/containerd/platforms v1.0.0-rc.1 @@ -148,7 +149,6 @@ require ( github.com/container-storage-interface/spec v1.5.0 // indirect github.com/containerd/accelerated-container-image v1.3.0 // indirect github.com/containerd/console v1.0.4 // indirect - github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/go-cni v1.1.12 // indirect github.com/containerd/go-runc v1.1.0 // indirect github.com/containerd/nydus-snapshotter v0.15.0 // indirect diff --git a/vendor/github.com/containerd/errdefs/pkg/errhttp/http.go b/vendor/github.com/containerd/errdefs/pkg/errhttp/http.go new file mode 100644 index 0000000000..d7cd2b8c1c --- /dev/null +++ b/vendor/github.com/containerd/errdefs/pkg/errhttp/http.go @@ -0,0 +1,96 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// Package errhttp provides utility functions for translating errors to +// and from a HTTP context. +// +// The functions ToHTTP and ToNative can be used to map server-side and +// client-side errors to the correct types. +package errhttp + +import ( + "errors" + "net/http" + + "github.com/containerd/errdefs" + "github.com/containerd/errdefs/pkg/internal/cause" +) + +// ToHTTP returns the best status code for the given error +func ToHTTP(err error) int { + switch { + case errdefs.IsNotFound(err): + return http.StatusNotFound + case errdefs.IsInvalidArgument(err): + return http.StatusBadRequest + case errdefs.IsConflict(err): + return http.StatusConflict + case errdefs.IsNotModified(err): + return http.StatusNotModified + case errdefs.IsFailedPrecondition(err): + return http.StatusPreconditionFailed + case errdefs.IsUnauthorized(err): + return http.StatusUnauthorized + case errdefs.IsPermissionDenied(err): + return http.StatusForbidden + case errdefs.IsResourceExhausted(err): + return http.StatusTooManyRequests + case errdefs.IsInternal(err): + return http.StatusInternalServerError + case errdefs.IsNotImplemented(err): + return http.StatusNotImplemented + case errdefs.IsUnavailable(err): + return http.StatusServiceUnavailable + case errdefs.IsUnknown(err): + var unexpected cause.ErrUnexpectedStatus + if errors.As(err, &unexpected) && unexpected.Status >= 200 && unexpected.Status < 600 { + return unexpected.Status + } + return http.StatusInternalServerError + default: + return http.StatusInternalServerError + } +} + +// ToNative returns the error best matching the HTTP status code +func ToNative(statusCode int) error { + switch statusCode { + case http.StatusNotFound: + return errdefs.ErrNotFound + case http.StatusBadRequest: + return errdefs.ErrInvalidArgument + case http.StatusConflict: + return errdefs.ErrConflict + case http.StatusPreconditionFailed: + return errdefs.ErrFailedPrecondition + case http.StatusUnauthorized: + return errdefs.ErrUnauthenticated + case http.StatusForbidden: + return errdefs.ErrPermissionDenied + case http.StatusNotModified: + return errdefs.ErrNotModified + case http.StatusTooManyRequests: + return errdefs.ErrResourceExhausted + case http.StatusInternalServerError: + return errdefs.ErrInternal + case http.StatusNotImplemented: + return errdefs.ErrNotImplemented + case http.StatusServiceUnavailable: + return errdefs.ErrUnavailable + default: + return cause.ErrUnexpectedStatus{Status: statusCode} + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 604238880a..cefe2a5b68 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -420,6 +420,7 @@ github.com/containerd/errdefs # github.com/containerd/errdefs/pkg v0.3.0 ## explicit; go 1.22 github.com/containerd/errdefs/pkg/errgrpc +github.com/containerd/errdefs/pkg/errhttp github.com/containerd/errdefs/pkg/internal/cause github.com/containerd/errdefs/pkg/internal/types # github.com/containerd/fifo v1.1.0