mirror of
https://github.com/versity/versitygw.git
synced 2026-08-29 04:06:56 +00:00
fix(posix): return empty config for GetBucketVersioning when unconfigured
Validate bucket existence before checking the versioning state so a missing bucket still yields 404 NoSuchBucket. When no versioning directory is configured, respond with 200 and an empty configuration instead of VersioningNotConfigured, matching AWS semantics. GUI clients (e.g. S3 Browser) poll this API while browsing buckets and were failing on default deployments. PutBucketVersioning still rejects with VersioningNotConfigured. Fix a swapped test body between the Get/Put "not configured" cases, add integration coverage for GetBucketVersioning on a non-existent bucket, and add backend/posix unit tests guarding the validation order and the empty 200 response. Ref: exastor/versitygw!3
This commit is contained in:
@@ -890,10 +890,6 @@ func (p *Posix) GetBucketVersioning(ctx context.Context, bucket string) (s3respo
|
||||
}
|
||||
defer release()
|
||||
|
||||
if !p.versioningEnabled() {
|
||||
return s3response.GetBucketVersioningOutput{}, s3err.GetAPIError(s3err.ErrVersioningNotConfigured)
|
||||
}
|
||||
|
||||
if !p.isBucketValid(bucket) {
|
||||
return s3response.GetBucketVersioningOutput{}, s3err.GetBucketErr(s3err.ErrInvalidBucketName, bucket)
|
||||
}
|
||||
@@ -906,6 +902,15 @@ func (p *Posix) GetBucketVersioning(ctx context.Context, bucket string) (s3respo
|
||||
return s3response.GetBucketVersioningOutput{}, fmt.Errorf("stat bucket: %w", err)
|
||||
}
|
||||
|
||||
if !p.versioningEnabled() {
|
||||
// No versioning directory is configured: report the bucket as
|
||||
// never-versioned (200 with an empty configuration) to match AWS,
|
||||
// instead of failing with VersioningNotConfigured. GUI clients
|
||||
// (S3 Browser, CloudBerry, ...) poll this API while browsing
|
||||
// buckets. PutBucketVersioning still rejects the request.
|
||||
return s3response.GetBucketVersioningOutput{}, nil
|
||||
}
|
||||
|
||||
vData, err := p.meta.RetrieveAttribute(nil, bucket, "", versioningKey)
|
||||
if errors.Is(err, meta.ErrNoSuchKey) {
|
||||
return s3response.GetBucketVersioningOutput{}, nil
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright 2026 Versity Software
|
||||
// This file is licensed under the Apache License, Version 2.0
|
||||
// (the "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package posix
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/versity/versitygw/backend/meta"
|
||||
"github.com/versity/versitygw/s3err"
|
||||
)
|
||||
|
||||
// newUnversionedGateway creates a Posix backend over a temporary root
|
||||
// directory with no versioning directory configured, i.e. gateway-level
|
||||
// versioning disabled, matching the default gateway configuration.
|
||||
func newUnversionedGateway(t *testing.T) *Posix {
|
||||
t.Helper()
|
||||
|
||||
p, err := New(t.TempDir(), meta.XattrMeta{}, PosixOpts{
|
||||
ValidateBucketNames: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("init posix backend: %v", err)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// TestVersioningUnconfigured covers the bucket versioning behavior when the
|
||||
// gateway has no versioning directory configured: bucket validation still
|
||||
// applies first, GetBucketVersioning returns an empty configuration, and
|
||||
// PutBucketVersioning is rejected.
|
||||
func TestVersioningUnconfigured(t *testing.T) {
|
||||
// New() chdirs into the gateway root; restore the original working
|
||||
// directory when the test completes.
|
||||
t.Chdir(t.TempDir())
|
||||
|
||||
t.Run("get bucket versioning invalid bucket name", func(t *testing.T) {
|
||||
p := newUnversionedGateway(t)
|
||||
|
||||
_, err := p.GetBucketVersioning(context.Background(), "bad/bucket")
|
||||
if !errors.Is(err, s3err.GetAPIError(s3err.ErrInvalidBucketName)) {
|
||||
t.Errorf("expected InvalidBucketName, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("get bucket versioning no such bucket", func(t *testing.T) {
|
||||
p := newUnversionedGateway(t)
|
||||
|
||||
_, err := p.GetBucketVersioning(context.Background(), "does-not-exist")
|
||||
if !errors.Is(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)) {
|
||||
t.Errorf("expected NoSuchBucket, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("get bucket versioning returns empty config", func(t *testing.T) {
|
||||
p := newUnversionedGateway(t)
|
||||
|
||||
err := os.Mkdir("bucket", 0o755)
|
||||
assert.NoError(t, err)
|
||||
|
||||
res, err := p.GetBucketVersioning(context.Background(), "bucket")
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, res.Status)
|
||||
})
|
||||
|
||||
t.Run("put bucket versioning invalid bucket name", func(t *testing.T) {
|
||||
p := newUnversionedGateway(t)
|
||||
|
||||
err := p.PutBucketVersioning(context.Background(), "bad/bucket", types.BucketVersioningStatusEnabled)
|
||||
if !errors.Is(err, s3err.GetAPIError(s3err.ErrInvalidBucketName)) {
|
||||
t.Errorf("expected InvalidBucketName, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("put bucket versioning not configured", func(t *testing.T) {
|
||||
p := newUnversionedGateway(t)
|
||||
|
||||
err := os.Mkdir("bucket", 0o755)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = p.PutBucketVersioning(context.Background(), "bucket", types.BucketVersioningStatusEnabled)
|
||||
if !errors.Is(err, s3err.GetAPIError(s3err.ErrVersioningNotConfigured)) {
|
||||
t.Errorf("expected VersioningNotConfigured, got %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1254,6 +1254,7 @@ func TestVersioning(ts *TestState) {
|
||||
|
||||
func TestVersioningDisabled(ts *TestState) {
|
||||
ts.Run(VersioningDisabled_GetBucketVersioning_not_configured)
|
||||
ts.Run(VersioningDisabled_GetBucketVersioning_no_such_bucket)
|
||||
ts.Run(VersioningDisabled_PutBucketVersioning_not_configured)
|
||||
}
|
||||
|
||||
|
||||
@@ -3620,8 +3620,32 @@ func Versioning_AccessControl_GetObjectAttributes_policy(s *S3Conf) error {
|
||||
func VersioningDisabled_GetBucketVersioning_not_configured(s *S3Conf) error {
|
||||
testName := "VersioningDisabled_GetBucketVersioning_not_configured"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
err := putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatusEnabled)
|
||||
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrVersioningNotConfigured)); err != nil {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
res, err := s3client.GetBucketVersioning(ctx, &s3.GetBucketVersioningInput{
|
||||
Bucket: &bucket,
|
||||
})
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.Status != "" {
|
||||
return fmt.Errorf("expected empty versioning status when versioning is not configured, instead got %v",
|
||||
res.Status)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func VersioningDisabled_GetBucketVersioning_no_such_bucket(s *S3Conf) error {
|
||||
testName := "VersioningDisabled_GetBucketVersioning_no_such_bucket"
|
||||
return actionHandlerNoSetup(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err := s3client.GetBucketVersioning(ctx, &s3.GetBucketVersioningInput{
|
||||
Bucket: &bucket,
|
||||
})
|
||||
cancel()
|
||||
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrNoSuchBucket)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -3632,11 +3656,7 @@ func VersioningDisabled_GetBucketVersioning_not_configured(s *S3Conf) error {
|
||||
func VersioningDisabled_PutBucketVersioning_not_configured(s *S3Conf) error {
|
||||
testName := "VersioningDisabled_PutBucketVersioning_not_configured"
|
||||
return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
|
||||
_, err := s3client.GetBucketVersioning(ctx, &s3.GetBucketVersioningInput{
|
||||
Bucket: &bucket,
|
||||
})
|
||||
cancel()
|
||||
err := putBucketVersioningStatus(s3client, bucket, types.BucketVersioningStatusEnabled)
|
||||
if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrVersioningNotConfigured)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user