storage: add read-only Bucket backed by fs.FS#96
Merged
Conversation
NewBucketFromFS wraps a fs.FS as a read-only storage.Bucket, and NewBucketFromNewFS derives the fs.FS from a constructor that receives the per-operation context (useful for request/tenant-scoped filesystems). Writes return ErrBucketReadOnly and presigning is unsupported. ListObjects walks the tree lazily, one directory at a time, yielding in lexical key order (sorting directory entries as if their names had a trailing "/", so the traversal matches full-key ordering rather than fs.WalkDir's). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a read-only
storage.Bucketimplementation backed by a standardfs.FS.NewBucketFromNewFScalls the constructor once perBucketoperation, passing that call'sctx, so the underlyingfs.FScan be derived from the caller's context (request, transaction, tenant, …) instead of being fixed at construction time.NewBucketFromFSis the trivial constant wrapper over it.Behavior
HeadObject,GetObject(withBytesRangesupport viaio.Seeker, falling back to discarding leading bytes),ListObjects, andWatchObjects(emits the current listing once, then blocks until the context is canceled, sincefs.FShas no change notifications).Create,PutObject,DeleteObject,DeleteObjects,CopyObjectall returnErrBucketReadOnly.ErrPresignNotSupported; put/delete returnErrBucketReadOnly(mirroringEmptyBucket).fspaths; validation appliesValidObjectKeythen the stricterfs.ValidPath. Missing files and directories both reportErrObjectNotFound.Location()returns:fs:.Listing
ListObjectswalks the tree lazily, one directory at a time (fs.ReadDir), and never materializes the full listing.fs.WalkDiris deliberately not used: it visits a directory's contents before lexically-smaller sibling files (e.g.data/xbeforedata.txt, since'/'>'.'), which doesn't match S3/key ordering. The walker sorts each directory's entries as if directory names had a trailing"/", making traversal order identical to sorting full keys. Because the stream is globally sorted, delimiter common-prefixes are deduped with a single tracked value (O(1) memory), and subtrees outside the prefix — or fully collapsed by the delimiter — are skipped without descending.Tests
storage/fsbucket_test.gocovers reads, ranges, listing variants (prefix / delimiter / max-keys / start-after), read-only enforcement, per-call context capture, and a cross-directory ordering case that would fail with a naivefs.WalkDirtraversal.🤖 Generated with Claude Code