Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change errors structure to handle them gracefully #390

Merged
merged 1 commit into from
Jan 15, 2018
Merged

Change errors structure to handle them gracefully #390

merged 1 commit into from
Jan 15, 2018

Conversation

boaz0
Copy link
Contributor

@boaz0 boaz0 commented Dec 13, 2017

Using errors.Cause one can understand what was the cause of the failure. Unfortunately, when the error is returned due to status code different than 203, 200 or 201 errors.Cause is a verbose string.

This patch suggests to set the cause of the error to be the staus text.

For example, if failed to upload a layer, instead of having the following:
Error initiating layer upload to /path/to/upload, status 401
This error message will be returned:
Error initiating layer upload to /path/to/upload": Unauthorized

It's then easier to handle this error by reading errors.Cause:

if errors.Cause(err) == http.StatusText(http.StatusUnauthorized) {
    return errors.New("You are not authorized. Missing credentials?")
}

This can help me to fix containers/buildah#254.

@boaz0
Copy link
Contributor Author

boaz0 commented Dec 14, 2017

/cc @rhatdan @TomSweeneyRedHat WDYT?

@@ -363,7 +363,7 @@ func (c *dockerClient) detectProperties(ctx context.Context) error {
defer resp.Body.Close()
logrus.Debugf("Ping %s status %d", url, resp.StatusCode)
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusUnauthorized {
return errors.Errorf("error pinging repository, response code %d", resp.StatusCode)
return errors.Wrapf(errors.New(http.StatusText(resp.StatusCode)), "Error pingin repository")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit "pingin" to "pinging"

@runcom
Copy link
Member

runcom commented Dec 14, 2017

LGTM

@TomSweeneyRedHat
Copy link
Member

The change LGTM with the exception of the nit, but I think I'd rather not remove anything from the original message. I personally like the status codes, but see the value in the text. I think I'd prefer from your example:

Error initiating layer upload to /path/to/upload, status 401: Unauthorized.

Also you didn't hit up all the occurrences in this project, should we? @nalind WDYT?

@rhatdan
Copy link
Member

rhatdan commented Dec 14, 2017

With the errors wrap won't we end up with the 401 anyways if you print out the error?

@rhatdan
Copy link
Member

rhatdan commented Dec 14, 2017

LGTM with the pingin fix.

@boaz0
Copy link
Contributor Author

boaz0 commented Dec 14, 2017

With the errors wrap won't we end up with the 401 anyways if you print out the error?

The http.StatusText function takes the res.StatusCode and returns a text for the HTTP status code so no.

Here are the texts for each status-code from golang source code

Maybe I should change it to
"Error initiating layer upload to /path/to/upload: HTTP response "
so it will be clear it is related to the request.

@TomSweeneyRedHat thank you for noticing! Working on it.

@runcom thanks for the review.

@nalind
Copy link
Member

nalind commented Dec 14, 2017

I like this.

@rhatdan
Copy link
Member

rhatdan commented Jan 2, 2018

@mtrmac PTAL and get this merged.

@mtrmac
Copy link
Collaborator

mtrmac commented Jan 2, 2018

It's then easier to handle this error by reading errors.Cause:

if errors.Cause(err) == http.StatusText(http.StatusUnauthorized) {
    return errors.New("You are not authorized. Missing credentials?")
}

This still relies on a string comparison, which is fragile (it is non-obvious that the string is an API commitment), and invisible to tools. I’d much prefer using a specific error value, or type, for this.

In fact, focusing on the 401 status which seems to be the core concern of containers/buildah#254 , there already are two plausible options:

  1. Many of the clients for the docker/distribution API (but not signature lookaside) can, and probably should, call github.com/docker/distribution/registry/client.HandleErrorResponse, to get nicer and more detailed error messages. That function can also return well-typed github.com/docker/distribution/registry/api/errcode.ErrorCodeUnauthorized and github.com/docker/distribution/registry/client.UnexpectedHTTPResponseError.
  2. We also have c/i/docker.ErrUnauthorizedForCredentials, which is returned if the server is using Authorization: Bearer. It seems very reasonable to return the same value for Authorization: Basic as well.

In the worst case, a new type c/i/docker.UnexpectedHTTPStatus int type implementing error would still be cleaner than the string comparison proposed above.

@TomSweeneyRedHat
Copy link
Member

@ripcurld0 this needs to be rebased.

@boaz0
Copy link
Contributor Author

boaz0 commented Jan 7, 2018

@mtrmac Thanks a lot for the review.
I decided to go with HandleErrorResponse as you suggested. Do you mind re-review it again?

By the way, we're not vendoring this package, and we're relying on other people to do that. Should I leave it as it is or we need to vendor this package?

@TomSweeneyRedHat I have rebased the patch. Thank you so much for noticing.

@boaz0
Copy link
Contributor Author

boaz0 commented Jan 11, 2018

ping @mtrmac 🖌 ♻️

@mtrmac
Copy link
Collaborator

mtrmac commented Jan 15, 2018

👍 Thanks! Please rebase once more.

@runcom you might want to take another look.

Approved with PullApprove

If commiting or reading Docker image fails the error message is verbose
and thus it's hard to handle the error gracefully.

This patch attempts to solve in the following way:
The `docker/distribution/registry/client.HandleErrorResponse` takes a
`http.Response` and returns a new error containing a human-readable
error message alongside with the status code.

In order to handle such errors, verify the error type and handle it:

if respErr, ok := errors.Cause(err).(errcode.Error); ok {
    // handle the error here.
}

This change is going to apply on docker images.

Signed-off-by: Boaz Shuster <[email protected]>
@runcom
Copy link
Member

runcom commented Jan 15, 2018

LGTM

@runcom runcom merged commit 46d75e3 into containers:master Jan 15, 2018
@boaz0
Copy link
Contributor Author

boaz0 commented Jan 15, 2018

Thank you so much.

@boaz0 boaz0 deleted the errors_cause_for_download branch January 15, 2018 23:41
@mtrmac
Copy link
Collaborator

mtrmac commented Jan 16, 2018

By the way, we're not vendoring this package, and we're relying on other people to do that. Should I leave it as it is or we need to vendor this package?

FWIW this needs to stay as it is because c/image is a library package; types in vendored packages are not visible to users. See e.g. opencontainers/image-spec#528 (comment) , and this would equally apply to the error types returned by HandleErrorResponse.

@rhatdan
Copy link
Member

rhatdan commented Jan 16, 2018

@ripcurld0 Could you vendor this into buildah?

@boaz0
Copy link
Contributor Author

boaz0 commented Jan 16, 2018

@rhatdan consider this done 😉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Commit has misleading error when creds are required but not supplied
6 participants