-
Notifications
You must be signed in to change notification settings - Fork 89
CLOUDP-330561: Moves unauth error check to common errors #4043
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
base: SA_refactor_feature_branch
Are you sure you want to change the base?
CLOUDP-330561: Moves unauth error check to common errors #4043
Conversation
type AuthRequiredRoundTripper struct { | ||
Base http.RoundTripper | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
transport wrapper no longer needed since its primary purpose, to check for unauth, is now done after the command executes
internal/cli/commonerrors/errors.go
Outdated
@@ -49,6 +54,13 @@ func Check(err error) error { | |||
return err | |||
} | |||
|
|||
func CheckHTTPErrors(err error) error { | |||
if strings.Contains(err.Error(), "401") && strings.Contains(err.Error(), "Unauthorized") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is the proper way to check the error code coming from the API.
I think you'll have to use:
admin.AsError(err)
I was first going to recommend using the errorcode: UNAUTHORIZED
, but it looks like we have so many variations.
It's better to check the HTTP status code instead (through the error).
Unfortunately I don't fully remember how to get the status code from the SDK, let me know if you can't find it and want to look into it together
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately I don't fully remember how to get the status code from the SDK, let me know if you can't find it and want to look into it together
AsError
returns the ApiError
struct which contains the error code as param
https://github.com/mongodb/atlas-sdk-go/blob/main/admin/model_api_error.go#L6
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes i tried this initially, but these unauth errors aren't GenericOpenAPIError types so admin.AsError(err) returns !ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need a custom error, perhaps should we introduce error handling on transport and create an ErrUnauthorized? at least a generic http error that has code as a property
cmd/atlas/atlas.go
Outdated
if errMsg != nil { | ||
fmt.Println(errMsg) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't we print this to stderr?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this prints it twice because cobra prints it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we are moving the error check here we should remove each individual usage of it and move it here
f83fb70
to
6bc5ad3
Compare
@@ -169,7 +169,7 @@ func TestCredentials(t *testing.T) { | |||
}) | |||
} | |||
|
|||
func TestNoAPIKeyss(t *testing.T) { | |||
func TestNoAPIKeys(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Drive-by fix
e2e failure is plugin e2e tests due to GitHub rate limiting. These tests pass locally and should not be affected by the changes in this PR. Will rerun next week once rate limiting resets. |
@@ -36,6 +37,10 @@ func execute(rootCmd *cobra.Command) { | |||
|
|||
To learn more, see our documentation: https://www.mongodb.com/docs/atlas/cli/stable/connect-atlas-cli/` | |||
if cmd, err := rootCmd.ExecuteContextC(ctx); err != nil { | |||
errMsg := commonerrors.GetHumanFriendlyErrorMessage(err) | |||
if errMsg != nil { | |||
fmt.Fprintln(os.Stderr, errMsg) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we did not have this print before, how was cobra printing it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think errors might be printing twice, there is a SilenceErrors property in cobra, double check if you need it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cobra prints to stderr but only prints response error. errMsg only prints custom error (ie. "this action requires authentication ...").
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since errMsg has different content than err printed by cobra, we don't get duplication.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
running this code this is what I get after my browser login is expired
Error: Get "https://cloud-dev.mongodb.com/api/atlas/v2/groups/686f96db2f08ee49a6da7ca8/clusters?includeCount=true&includeDeletedWithRetainedBackups=false&itemsPerPage=100&pageNum=1": POST https://cloud-dev.mongodb.com/api/private/unauth/account/device/token: 400 (request "INVALID_REFRESH_TOKEN") The refresh token is invalid or expired.
exit status 1
on master I get
Error: session expired
Please note that your session expires periodically.
If you use Atlas CLI for automation, see https://www.mongodb.com/docs/atlas/cli/stable/atlas-cli-automate/ for best practices.
To login, run: atlas auth login
exit status 1
I prefer master's experience, can we keep it?
tested by running atlas clusters list
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know why that is, the error of not being able to refresh token is a 400 not a 401. I think this is a sort of chicken and egg problem.
I've managed to go back a to a good UX by:
- updated
internal/cli/commonerrors/errors.go
func Check(err error) error {
if err == nil {
return nil
}
+
+ apiErrorCode := "UNKNOWN"
+
+ var sdkError *atlas.ErrorResponse
+ if ok := errors.As(err, &sdkError); ok {
+ apiErrorCode = sdkError.ErrorCode
+ }
apiError, ok := atlasv2.AsError(err)
if ok {
- switch apiError.GetErrorCode() {
+ apiErrorCode = apiError.GetErrorCode()
+ }
+
+ apiPinnedError, ok := atlasClustersPinned.AsError(err)
+ if ok {
+ apiErrorCode = apiPinnedError.GetErrorCode()
+ }
+
+ switch apiErrorCode {
case "TENANT_CLUSTER_UPDATE_UNSUPPORTED":
return errClusterUnsupported
case "GLOBAL_USER_OUTSIDE_SUBNET":
return errOutsideVPN
case asymmetricShardUnsupportedErrorCode:
return errAsymmetricShardUnsupported
+ case "INVALID_REFRESH_TOKEN":
+ return ErrUnauthorized
- }
}
+
return err
}
- updated
cmd/atlas/atlas.go
if cmd, err := rootCmd.ExecuteContextC(ctx); err != nil {
- errMsg := commonerrors.GetHumanFriendlyErrorMessage(err)
+ errMsg := commonerrors.Check(err)
if errMsg != nil {
- updated
internal/cli/root/builder.go
Example: ` # Display the help menu for the config command:
atlas config --help
`,
SilenceUsage: true,
+ SilenceErrors: true,
Annotations: map[string]string{
"toc": "true",
},
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check my comment on the thread unfortunately the UX here needs some more work, good news is I've managed the find the way I think we should move forward, take a look at my comments and let me know.
Proposed changes
Issues
Initial work to move unauth check from builder.go to transport.go (#4034) neglected digest and access token transport clients. This meant that if either of these transport methods received a 401 response, the error would not be wrapped with any additional messaging. For example, when a user's access token expires.
Solution
To fix this, I have moved the unauth check to atlas.go.
The flow now is:
Note that the raw transport error message will be printed to terminal before the command is fully complete. In practice, on terminal this looks much the same as the previous implementation:
Testing
Unit tests have been added.
As oauth authentication (Access Tokens) requires a browser-based flow, e2e testing could not be created to verify that access tokens are refreshed on expiry.
Manual testing was done instead. Access and Refresh Tokens are successfully refreshed when they expire.
Future work: E2E testing will be created once Service Accounts are implemented (CLOUDP-329808). An e2e test will be created to verify tokens are refreshed on expiry.
Jira ticket: CLOUDP-330561
Checklist
make fmt
and formatted my code