| package containerd |
| |
| import ( |
| "context" |
| "encoding/json" |
| "errors" |
| "fmt" |
| "net/http" |
| |
| "github.com/containerd/containerd/v2/core/remotes/docker" |
| remoteerrors "github.com/containerd/containerd/v2/core/remotes/errors" |
| cerrdefs "github.com/containerd/errdefs" |
| "github.com/containerd/log" |
| ) |
| |
| func translateRegistryError(ctx context.Context, err error) error { |
| if err == nil { |
| // Nothing to do |
| return nil |
| } |
| |
| // Check for registry specific error |
| var derrs docker.Errors |
| if !errors.As(err, &derrs) { |
| var remoteErr remoteerrors.ErrUnexpectedStatus |
| var derr docker.Error |
| if errors.As(err, &remoteErr) { |
| if jerr := json.Unmarshal(remoteErr.Body, &derrs); jerr != nil { |
| log.G(ctx).WithError(jerr).Debug("unable to unmarshal registry error") |
| return fmt.Errorf("%w: %w", cerrdefs.ErrUnknown, err) |
| } |
| if len(derrs) == 0 && (remoteErr.StatusCode == http.StatusUnauthorized || remoteErr.StatusCode == http.StatusForbidden) { |
| // Some registries or token servers may use an old deprecated error format |
| // which only has a "details" field and not the OCI defined "errors" array. |
| var tokenErr struct { |
| Details string `json:"details"` |
| } |
| if jerr := json.Unmarshal(remoteErr.Body, &tokenErr); jerr == nil && tokenErr.Details != "" { |
| if remoteErr.StatusCode == http.StatusUnauthorized { |
| return cerrdefs.ErrUnauthenticated.WithMessage(fmt.Sprintf("%s - %s", docker.ErrorCodeUnauthorized.Message(), tokenErr.Details)) |
| } |
| return cerrdefs.ErrPermissionDenied.WithMessage(fmt.Sprintf("%s - %s", docker.ErrorCodeDenied.Message(), tokenErr.Details)) |
| } |
| } |
| } else if errors.As(err, &derr) { |
| derrs = append(derrs, derr) |
| } else { |
| return err |
| } |
| } |
| var errs []error |
| for _, err := range derrs { |
| var derr docker.Error |
| if errors.As(err, &derr) { |
| var message string |
| |
| if derr.Message != "" { |
| message = derr.Message |
| } else { |
| message = derr.Code.Message() |
| } |
| |
| if detail, ok := derr.Detail.(string); ok { |
| message = fmt.Sprintf("%s - %s", message, detail) |
| } |
| |
| switch derr.Code { |
| case docker.ErrorCodeUnsupported: |
| err = cerrdefs.ErrNotImplemented.WithMessage(message) |
| case docker.ErrorCodeUnauthorized: |
| err = cerrdefs.ErrUnauthenticated.WithMessage(message) |
| case docker.ErrorCodeDenied: |
| err = cerrdefs.ErrPermissionDenied.WithMessage(message) |
| case docker.ErrorCodeUnavailable: |
| err = cerrdefs.ErrUnavailable.WithMessage(message) |
| case docker.ErrorCodeTooManyRequests: |
| err = cerrdefs.ErrResourceExhausted.WithMessage(message) |
| default: |
| err = cerrdefs.ErrUnknown.WithMessage(message) |
| } |
| } else { |
| errs = append(errs, cerrdefs.ErrUnknown.WithMessage(err.Error())) |
| } |
| errs = append(errs, err) |
| } |
| switch len(errs) { |
| case 0: |
| err = cerrdefs.ErrUnknown.WithMessage(err.Error()) |
| case 1: |
| err = errs[0] |
| default: |
| err = errors.Join(errs...) |
| } |
| return fmt.Errorf("error from registry: %w", err) |
| } |