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) }