Skip to content

feat: Amazon S3 and Cloud Storage buckets - #24

Merged
Michael McQuade (giraffesyo) merged 9 commits into
canaryfrom
feat/buckets
Oct 21, 2026
Merged

Michael McQuade (giraffesyo) merged 9 commits into
canaryfrom
feat/buckets

Conversation

@giraffesyo

Copy link
Copy Markdown
Member

Summary

Bucket stand-ins for both clouds, so code that creates buckets, finds them again by their tags or labels, empties them and deletes them can be tested against partial failure.

Amazon S3 (aws/s3):

  • CreateBucket, HeadBucket, ListBuckets (prefix, bucket-region, max-buckets pages with each bucket's region) and DeleteBucket.
  • PutBucketTagging, GetBucketTagging and DeleteBucketTagging, with S3's tag rules and NoSuchTagSet for an untagged bucket.
  • PutBucketVersioning and GetBucketVersioning; PutBucketPolicy and PutBucketCors are checked and recorded.
  • PutObject, DeleteObject, DeleteObjects and ListObjectVersions, with "null" versions for unversioned and suspended buckets and delete markers for versioned ones. Its markers are positions, so the next page still follows one whose versions were just deleted.
  • The error codes callers branch on: BucketNotEmpty, NoSuchBucket (NotFound on HeadBucket), BucketAlreadyExists, BucketAlreadyOwnedByYou (and the us-east-1 create that succeeds instead), AccessDenied (Forbidden on HeadBucket), PermanentRedirect with x-amz-bucket-region for a bucket in another region, IllegalLocationConstraintException, InvalidLocationConstraint, InvalidBucketName and InvalidTag.
  • PutForeignBucket holds a name for another account. Bodies sent with aws-chunked encoding are decoded.

Cloud Storage (gcp/storage):

  • buckets.insert, get, list (prefix, pages), patch (labels, versioning, CORS, storage class, encryption) and delete, with metageneration preconditions.
  • objects.insert by multipart or media upload (checksums verified, generation preconditions), objects.list (prefix, offsets, versions, pages) and objects.delete (live or by generation). With versioning on, old generations become noncurrent and keep the bucket from being deleted.
  • 409 conflict for a name the caller has (which the client's retry of a lost create gets) and for one someone else has, 404 notFound (the client's ErrBucketNotExist and ErrObjectNotExist), 403 forbidden for a foreign bucket, 409 for a bucket that is not empty, and 400 for invalid names and labels.
  • Works with option.WithEndpoint(srv.URL + "/storage/v1/") or STORAGE_EMULATOR_HOST.

Both follow the existing packages: WithFaults, WithClock, Call.Resource naming the bucket or object (the one being created, for a create), accessors (Buckets, LookupBucket, PutBucket, PutObject, ObjectVersions / Objects, PutForeignBucket), and 404 for what they do not implement. Each package doc lists where it differs from the real API, and the README has a summary.

A lost response to a HEAD now breaks off inside the status line. A HEAD response is complete at its header, so sending one would have read as a successful answer.

Testing

make check passes. New tests in sdktest drive the AWS SDK for Go v2 (service/s3) and the Cloud Storage Go client (cloud.google.com/go/storage) against the stand-ins. They cover create, tag and label reads, listings and pages, emptying versioned and unversioned buckets page by page, deletes and every error code above. They also cover aws-chunked uploads over TLS, emulator-host setup, lost responses, failures after a call took effect, targeted rejections, and seeded random faults that a retried sweep converges through.

make bench (3 runs each). The only change to existing request handling is the HEAD case in the shared stub, and allocations are unchanged. Times were within noise on a loaded machine.

Benchmark canary allocs/op this branch allocs/op
ec2 AllocateDescribeRelease 237 237
ec2 DescribeInterfacesFiltered 1827 1827
compute InsertGetDelete 138 138
compute ListFiltered 244 244
s3 PutListDeleteVersions (new) 536
storage UploadListDelete (new) 205

Checklist

  • make check passes
  • New methods are tested through the official client library
  • Package documentation says where the stand-in differs from the real API

Comment thread gcp/storage/server.go Fixed
- An aws-chunked chunk size near the integer limit no longer panics the
  handler; sizes are parsed as bare hex.
- Bodies over the limit fail with EntityTooLarge for PutObject and
  MaxMessageLengthExceeded otherwise, and keys over 1024 bytes with
  KeyTooLongError.
- ListBuckets with any of its parameters pages, 10,000 at a time by
  default, and reports regions, as the API reference says, and rejects a
  bucket-region other than the request's.
- A denied HeadBucket carries x-amz-bucket-region.
- A version-id-marker of null still has a position after that version is
  deleted.
- The SDK tests share their error code helpers through sdktest/internal/sdkerr.
…with NUL

- An upload larger than the limit fails with 413 instead of being cut
  short while its resource was named.
- Listing generations pages correctly when one name extends another with
  a NUL byte.
- buckets.patch clears labels, versioning and encryption set to null.
- Conflict messages are the ones the JSON API documents.
…gs with control characters

DeleteObjects with an empty key made a delete marker no path could reach,
so the bucket could not be deleted; ObjectIdentifier keys have a minimum
length of 1. A key over 1024 bytes now fails on its own with
KeyTooLongError. Tag text follows the documented pattern of letters,
separators and numbers, so tabs and newlines fail with InvalidTag. Forgetting
a key also forgets its null version's position.
… ACME challenge names

cors that is not a list of objects was stored and served back to the client,
which then failed to decode the bucket. A body with data after its JSON value
was accepted. Object names under .well-known/acme-challenge/ are prohibited.
Paths ServeMux would redirect now answer 404 in the API's shape, and a
bucket insert peeks at no more than a bucket body.
@giraffesyo
Michael McQuade (giraffesyo) merged commit 4e7f106 into canary Oct 21, 2026
12 checks passed
@giraffesyo
Michael McQuade (giraffesyo) deleted the feat/buckets branch October 21, 2026 03:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants