-
Notifications
You must be signed in to change notification settings - Fork 365
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
Return an informative error message when using 'api/v1' for S3 GW endpoint #7828
Return an informative error message when using 'api/v1' for S3 GW endpoint #7828
Conversation
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.
Thanks, great improvement! Requesting changes to help users who legitimately have a bucket "api" and a tag "v1".
pkg/gateway/errors/errors.go
Outdated
@@ -341,6 +342,11 @@ var Codes = errorCodeMap{ | |||
Description: "The bucket lifecycle configuration does not exist", | |||
HTTPStatusCode: http.StatusNotFound, | |||
}, | |||
ErrNoSuchBucketPossibleAPIEndpoint: { | |||
Code: "ErrNoSuchBucketPossibleAPIEndpoint", | |||
Description: "Using lakeFS API URI as the endpoint. Try removing the 'api/v1/' part from the endpoint.", |
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.
Perhaps afford people an explanation? I'm thinking of the poor user who has a repo named "api" and manages to mitype a branch name "v1"
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.
Can you squeeze more into an error message?
If so - any suggestions on how to improve this?
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.
Hmmm... good thing they don't deduct bandwidth for errors from my paycheck. I'd go with a definitive error message, followed by a suggestion:
Description: "Using lakeFS API URI as the endpoint. Try removing the 'api/v1/' part from the endpoint.", | |
Description: `Repository "api" not found; this can happen if your endpoint URL mistakenly ends in "` + apiutil.BaseURL + `".`, |
(the `
is an alternative quotation mark that reduces the number of backslashes needed here.)
pkg/gateway/middleware.go
Outdated
@@ -184,6 +184,13 @@ func EnrichWithRepositoryOrFallback(c *catalog.Catalog, authService auth.Gateway | |||
fallbackProxy.ServeHTTP(w, req) | |||
return | |||
} | |||
|
|||
// see: github.com/treeverse/lakeFS/issues/3082 |
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.
Or just state what this is. If I want to know who added this code, GitHub has excellent blame.
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.
Makes sense. Done.
pkg/gateway/middleware.go
Outdated
@@ -184,6 +184,13 @@ func EnrichWithRepositoryOrFallback(c *catalog.Catalog, authService auth.Gateway | |||
fallbackProxy.ServeHTTP(w, req) | |||
return | |||
} | |||
|
|||
// see: github.com/treeverse/lakeFS/issues/3082 | |||
if strings.HasPrefix(req.RequestURI, "/api/v1") { |
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.
This has to be defined on some constant. Can we use that constant here?
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.
You are right. Done.
pkg/gateway/middleware.go
Outdated
@@ -184,6 +184,13 @@ func EnrichWithRepositoryOrFallback(c *catalog.Catalog, authService auth.Gateway | |||
fallbackProxy.ServeHTTP(w, req) | |||
return | |||
} | |||
|
|||
// see: github.com/treeverse/lakeFS/issues/3082 | |||
if strings.HasPrefix(req.RequestURI, "/api/v1") { |
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.
Don't we need to fail to find a bucket first?
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.
Nope. As far as I understand this, it's the last line before turning to the ErrNoSuchBucket
error.
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.
Thanks!
How was this tested? OK to pull with only a manual test, but if you do then please also add a test in a later PR.
pkg/gateway/errors/errors.go
Outdated
@@ -341,6 +342,11 @@ var Codes = errorCodeMap{ | |||
Description: "The bucket lifecycle configuration does not exist", | |||
HTTPStatusCode: http.StatusNotFound, | |||
}, | |||
ErrNoSuchBucketPossibleAPIEndpoint: { | |||
Code: "ErrNoSuchBucketPossibleAPIEndpoint", | |||
Description: "Using lakeFS API URI as the endpoint. Try removing the 'api/v1/' part from the endpoint.", |
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.
Hmmm... good thing they don't deduct bandwidth for errors from my paycheck. I'd go with a definitive error message, followed by a suggestion:
Description: "Using lakeFS API URI as the endpoint. Try removing the 'api/v1/' part from the endpoint.", | |
Description: `Repository "api" not found; this can happen if your endpoint URL mistakenly ends in "` + apiutil.BaseURL + `".`, |
(the `
is an alternative quotation mark that reduces the number of backslashes needed here.)
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.
You added the test -- yay! -- but I suspect that it is broken because /api/v1
needs to be a prefix of the path of the URL. So now I'm requesting that change.
esti/s3_gateway_test.go
Outdated
|
||
accessKeyID := viper.GetString("access_key_id") | ||
secretAccessKey := viper.GetString("secret_access_key") | ||
endpoint := "/api/v1" + viper.GetString("s3_endpoint") |
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 that this is backwards!?
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.
Thanks!
The code looks good, some comments on the test
esti/s3_gateway_test.go
Outdated
|
||
accessKeyID := viper.GetString("access_key_id") | ||
secretAccessKey := viper.GetString("secret_access_key") | ||
endpoint := "/api/v1" + viper.GetString("s3_endpoint") |
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.
endpoint := "/api/v1" + viper.GetString("s3_endpoint") | |
endpoint := viper.GetString("s3_endpoint") + apiutil.BaseURL |
esti/s3_gateway_test.go
Outdated
|
||
t.Run("use_api_v1_for_client_endpoint", func(t *testing.T) { | ||
_, err := minioClient.GetObject(ctx, repo, "main/some", minio.GetObjectOptions{}) | ||
require.Contains(t, err.Error(), "Using lakeFS API URI as the endpoint") |
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.
This will panic if err is nil. You need to check
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.
also, not sure but I think you can use require.ErrorIs
esti/s3_gateway_test.go
Outdated
t.Fatalf("minio.New: %s", err) | ||
} | ||
|
||
t.Run("use_api_v1_for_client_endpoint", func(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.
Better for forward compatibility
t.Run("use_api_v1_for_client_endpoint", func(t *testing.T) { | |
t.Run("use_open_api_for_client_endpoint", func(t *testing.T) { |
Well, the test I've added fails, for:
I'm using @arielshaqed @N-o-Z Can you think of another way to test this, or should I release it without the test? |
One possible option is not using a minio client. |
Yeah this makes sense - |
If there's a scenario, we need to test 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.
Some additional comments
esti/s3_gateway_test.go
Outdated
require.NotNil(t, listErr) | ||
require.Contains(t, listErr.Error(), `Repository "api" not found`) |
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.
require.NotNil(t, listErr) | |
require.Contains(t, listErr.Error(), `Repository "api" not found`) | |
require.ErrorContains(t, listErr, gtwerrors.ErrNoSuchBucketPossibleAPIEndpoint.Error()) |
esti/s3_gateway_test.go
Outdated
s3Client, err := testutil.SetupTestS3Client(endpoint, accessKeyID, secretAccessKey) | ||
require.NoError(t, err, "failed creating s3 client") | ||
|
||
_, listErr := s3Client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{Bucket: aws.String("test")}) |
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.
_, listErr := s3Client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{Bucket: aws.String("test")}) | |
_, listErr := s3Client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{Bucket: aws.String("not-exists")}) |
esti/s3_gateway_test.go
Outdated
_, listErr := s3Client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{Bucket: aws.String("test")}) | ||
require.NotNil(t, listErr) | ||
require.NotContains(t, listErr.Error(), `Repository "api" not found`) |
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.
_, listErr := s3Client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{Bucket: aws.String("test")}) | |
require.NotNil(t, listErr) | |
require.NotContains(t, listErr.Error(), `Repository "api" not found`) | |
_, listErr := s3Client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{Bucket: aws.String("not-exist")}) | |
require.ErrorContains(t, listErr, gtwerrors.ErrNoSuchBucket.Error()) |
Rewrote the tests, PTAL 🙏 |
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.
Excellent work! I have one suggestion but not blocking
pkg/testutil/setup.go
Outdated
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.
Nice one
@@ -658,30 +658,27 @@ func TestS3ReadObjectRedirect(t *testing.T) { | |||
}) | |||
} | |||
|
|||
func createS3Client(endpoint string, t *testing.T) *s3.Client { |
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.
Just as thought,
There are several places who use testutil.SetupTestS3Client
to change the endpoint (as you already noticed).
Suggest to put this func under the testutil package and use it in all the places where testutil.SetupTestS3Client
is called.
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 would prefer not to do this. viper
state is, by definition, global. So I want all uses of viper to appear in the test, rather than in a "don't worry about it" setup function.
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.
Thanks!
Would really like not to read config in testutil.
esti/catalog_export_test.go
Outdated
@@ -177,7 +177,7 @@ func testSymlinkS3Exporter(t *testing.T, ctx context.Context, repo string, tmplD | |||
storageURL, err := url.Parse(symlinksPrefix) | |||
require.NoError(t, err, "failed extracting bucket name") | |||
|
|||
s3Client, err := testutil.SetupTestS3Client("https://s3.amazonaws.com", testData.AWSAccessKeyID, testData.AWSSecretAccessKey) | |||
s3Client, err := testutil.SetupTestS3Client("https://s3.amazonaws.com", testData.AWSAccessKeyID, testData.AWSSecretAccessKey, viper.GetBool("force_path_style")) |
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'd prefer the same style as in delete_objects_test.go, below. But what I really want is for both calls to look the same.
@@ -658,30 +658,27 @@ func TestS3ReadObjectRedirect(t *testing.T) { | |||
}) | |||
} | |||
|
|||
func createS3Client(endpoint string, t *testing.T) *s3.Client { |
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 would prefer not to do this. viper
state is, by definition, global. So I want all uses of viper to appear in the test, rather than in a "don't worry about it" setup function.
Closes #3082
Change Description
Quite a few users are trying sometimes to access the S3 Gateway using an endpoint starting with
/api/v1
.This used to lead to a
NoSuchBucket
404 error.After the fix, such scenario leads to a 404 error, but with a different message: