registryHTTPResponseToError creates a Go error from an HTTP error response of a docker/distribution registry. WARNING: The OCI distribution spec says “A `4XX` response code from the registry MAY return a body in any format.”; but if it is JSON, it MUST use the errcode.Error structure. So, callers s
(res *http.Response)
| 55 | // JSON, it MUST use the errcode.Error structure. |
| 56 | // So, callers should primarily decide based on HTTP StatusCode, not based on error type here. |
| 57 | func registryHTTPResponseToError(res *http.Response) error { |
| 58 | err := handleErrorResponse(res) |
| 59 | // len(errs) == 0 should never be returned by handleErrorResponse; if it does, we don't modify it and let the caller report it as is. |
| 60 | if errs, ok := err.(errcode.Errors); ok && len(errs) > 0 { |
| 61 | // The docker/distribution registry implementation almost never returns |
| 62 | // more than one error in the HTTP body; it seems there is only one |
| 63 | // possible instance, where the second error reports a cleanup failure |
| 64 | // we don't really care about. |
| 65 | // |
| 66 | // The only _common_ case where a multi-element error is returned is |
| 67 | // created by the handleErrorResponse parser when OAuth authorization fails: |
| 68 | // the first element contains errors from a WWW-Authenticate header, the second |
| 69 | // element contains errors from the response body. |
| 70 | // |
| 71 | // In that case the first one is currently _slightly_ more informative (ErrorCodeUnauthorized |
| 72 | // for invalid tokens, ErrorCodeDenied for permission denied with a valid token |
| 73 | // for the first error, vs. ErrorCodeUnauthorized for both cases for the second error.) |
| 74 | // |
| 75 | // Also, docker/docker similarly only logs the other errors and returns the |
| 76 | // first one. |
| 77 | if len(errs) > 1 { |
| 78 | logrus.Debugf("Discarding non-primary errors:") |
| 79 | for _, err := range errs[1:] { |
| 80 | logrus.Debugf(" %s", err.Error()) |
| 81 | } |
| 82 | } |
| 83 | err = errs[0] |
| 84 | } |
| 85 | switch e := err.(type) { |
| 86 | case *unexpectedHTTPResponseError: |
| 87 | response := string(e.Response) |
| 88 | if len(response) > 50 { |
| 89 | response = response[:50] + "..." |
| 90 | } |
| 91 | // %.0w makes e visible to error.Unwrap() without including any text |
| 92 | err = fmt.Errorf("StatusCode: %d, %q%.0w", e.StatusCode, response, e) |
| 93 | case errcode.Error: |
| 94 | // e.Error() is fmt.Sprintf("%s: %s", e.Code.Error(), e.Message, which is usually |
| 95 | // rather redundant. So reword it without using e.Code.Error() if e.Message is the default. |
| 96 | if e.Message == e.Code.Message() { |
| 97 | // %.0w makes e visible to error.Unwrap() without including any text |
| 98 | err = fmt.Errorf("%s%.0w", e.Message, e) |
| 99 | } |
| 100 | } |
| 101 | return err |
| 102 | } |
searching dependent graphs…