From 69ad449c44829b8b2c71bc1252e0e1ea21b045f1 Mon Sep 17 00:00:00 2001 From: Alec Thomas Date: Mon, 30 Mar 2026 12:28:15 +1100 Subject: [PATCH] fix: remove ListBuckets from s3client, add BucketExists check to NewS3Backend --- internal/metadatadb/s3.go | 12 ++++++++++-- internal/metadatadb/s3_test.go | 13 ++++++++++--- internal/s3client/s3client.go | 6 ------ 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/internal/metadatadb/s3.go b/internal/metadatadb/s3.go index b4f42b3..af962a9 100644 --- a/internal/metadatadb/s3.go +++ b/internal/metadatadb/s3.go @@ -32,7 +32,7 @@ type S3BackendConfig struct { LockTTL time.Duration } -func NewS3Backend(config S3BackendConfig) *S3Backend { +func NewS3Backend(ctx context.Context, config S3BackendConfig) (*S3Backend, error) { if config.Prefix == "" { config.Prefix = "_meta" } @@ -43,13 +43,21 @@ func NewS3Backend(config S3BackendConfig) *S3Backend { if logger == nil { logger = slog.Default() } + exists, err := config.Client.BucketExists(ctx, config.Bucket) + if err != nil { + return nil, errors.Errorf("failed to check if bucket exists: %w", err) + } + if !exists { + return nil, errors.Errorf("bucket %s does not exist", config.Bucket) + } + return &S3Backend{ client: config.Client, logger: logger, bucket: config.Bucket, prefix: config.Prefix, lockTTL: config.LockTTL, - } + }, nil } func (s *S3Backend) stateKey(namespace string) string { return s.prefix + "/" + namespace + ".json" } diff --git a/internal/metadatadb/s3_test.go b/internal/metadatadb/s3_test.go index 9cf2769..4244f2c 100644 --- a/internal/metadatadb/s3_test.go +++ b/internal/metadatadb/s3_test.go @@ -4,6 +4,8 @@ import ( "testing" "time" + "github.com/alecthomas/assert/v2" + "github.com/block/cachew/internal/metadatadb" "github.com/block/cachew/internal/metadatadb/metadatadbtest" "github.com/block/cachew/internal/s3client/s3clienttest" @@ -14,24 +16,29 @@ func TestS3Backend(t *testing.T) { metadatadbtest.Suite(t, func(t *testing.T) metadatadb.Backend { t.Helper() - return metadatadb.NewS3Backend(metadatadb.S3BackendConfig{ + b, err := metadatadb.NewS3Backend(t.Context(), metadatadb.S3BackendConfig{ Client: s3clienttest.Client(t), Bucket: bucket, Prefix: "_meta-" + t.Name(), LockTTL: 5 * time.Second, }) + assert.NoError(t, err) + return b }) } func TestS3BackendSoak(t *testing.T) { bucket := s3clienttest.Start(t) - metadatadbtest.Soak(t, metadatadb.NewS3Backend(metadatadb.S3BackendConfig{ + b, err := metadatadb.NewS3Backend(t.Context(), metadatadb.S3BackendConfig{ Client: s3clienttest.Client(t), Bucket: bucket, Prefix: "_meta-soak", LockTTL: 5 * time.Second, - }), metadatadbtest.SoakConfig{ + }) + assert.NoError(t, err) + + metadatadbtest.Soak(t, b, metadatadbtest.SoakConfig{ Duration: 5 * time.Second, Concurrency: 4, NumKeys: 10, diff --git a/internal/s3client/s3client.go b/internal/s3client/s3client.go index 6d10323..81d11f9 100644 --- a/internal/s3client/s3client.go +++ b/internal/s3client/s3client.go @@ -100,11 +100,5 @@ func NewClient(ctx context.Context, config Config) (*minio.Client, error) { return nil, errors.Errorf("failed to create minio client: %w", err) } - // Verify connectivity and credentials up front so misconfiguration is - // caught at startup rather than on the first cache request. - if _, err := mc.ListBuckets(ctx); err != nil { - return nil, errors.Errorf("failed to connect to S3 endpoint %s: %w", config.Endpoint, err) - } - return mc, nil }