S3 API operations#

Operator + SDK-author index of every S3 operation Strata answers. The router dispatches ServeHTTPhandleBucket (bucket-scope) → handleObject (object-scope); leaf handlers live alongside each feature in the s3api package. The Handler file:line column is intentionally line-anchored — a lint test asserts every dispatched method shows up here (see the maintainer note above this section). AWS divergence column is empty when the operation is full-compat; one-liner otherwise.

The “Shipped in” column references the ROADMAP / PRD that landed the operation when known. Pre-roadmap MVP surface (most rows) shows .

See S3 compatibility for the headline pass-rate against the upstream Ceph s3-tests suite and the deliberate gaps.

Bucket lifecycle#

OperationHandler file:lineShipped inAWS gotchas
CreateBucketinternal/s3api/server.go:426x-amz-bucket-object-lock-enabled: true flips the bucket-level Object Lock setting on creation. x-amz-acl canned ACL header honoured. Idempotent for same-owner re-PUT (returns 200 + Location).
DeleteBucketinternal/s3api/server.go:474409 BucketNotEmpty when objects (incl. delete markers) remain. Operator force-empty available via POST /admin/v1/buckets/{name}/force-empty.
HeadBucketinternal/s3api/server.go:480x-amz-bucket-region echoed from the bucket row (default default).
ListBucketsinternal/s3api/server.go:207Anonymous principal returns empty <Buckets> element rather than 403.
GetBucketLocationinternal/s3api/location.go:42Returns <LocationConstraint> of the bucket’s stored region; empty for default region (matches AWS for us-east-1).

Object lifecycle#

OperationHandler file:lineShipped inAWS gotchas
PutObjectinternal/s3api/server.go:891 (→ putObject server.go:918)If-Match / If-None-Match evaluated pre-write. x-amz-storage-class falls back to bucket DefaultClass. Quota check before body read; SSE-S3/KMS/SSE-C wrap on body reader.
GetObjectinternal/s3api/server.go:902 (→ getObject server.go:1144)partNumber=N on non-multipart object returns whole body when N=1, InvalidPart otherwise (s3-tests parity). Range GET suppresses x-amz-checksum-* (boto3 1.36+ FlexibleChecksum mismatch).
HeadObjectinternal/s3api/server.go:907 (same getObject with body=false)Same code path as GetObject; partNumber=N returns part metadata + composite multipart ETag.
DeleteObjectinternal/s3api/server.go:916 (→ deleteObject server.go:1452)Suspended versioning: unversioned DELETE replaces null version. MFA-Delete enforced when ?versionId= set + bucket has MfaDelete enabled. Object Lock GOVERNANCE bypassable with x-amz-bypass-governance-retention: true.
DeleteObjects (POST ?delete)internal/s3api/delete_objects.go:44Quiet mode honoured. Per-key Object Lock checks; failures returned in <Errors> element, partial-success preserved.
CopyObjectinternal/s3api/copy_object.go:53Triggered by x-amz-copy-source on PUT. x-amz-metadata-directive=REPLACE swaps user metadata; COPY (default) preserves. SSE-C source key required when source is SSE-C-encrypted.
RestoreObject (POST ?restore)internal/s3api/restore.go:21Transition target restored to STANDARD per request body <Days>; sync restore (no async polling — restore completes in-handler).

Object metadata#

OperationHandler file:lineShipped inAWS gotchas
GetObjectAttributes (GET ?attributes)internal/s3api/object_attributes.go:65x-amz-object-attributes header drives which subfields render (ETag, Checksum, ObjectParts, StorageClass, ObjectSize).
GetObjectAclinternal/s3api/acl.go:238Returns owner + per-grantee <Grant> list; canned ACLs translated to explicit grants.
PutObjectAclinternal/s3api/acl.go:255x-amz-acl canned header OR XML body; mixed input returns 400 MalformedACLError.
GetObjectTagginginternal/s3api/tagging.go:37Empty <TagSet/> when no tags (200, not 404).
PutObjectTagginginternal/s3api/tagging.go:15Replaces full TagSet — no merge semantics.
DeleteObjectTagginginternal/s3api/tagging.go:50Idempotent — 204 even when no tags existed.

Multipart upload#

OperationHandler file:lineShipped inAWS gotchas
CreateMultipartUpload (POST ?uploads)internal/s3api/server.go:788 (→ initiateMultipart multipart.go:41)SSE headers persisted on the multipart session; per-part SSE-C key required on every UploadPart (AWS parity). BackendUploadID opaque handle binds the session to the originating cluster (drain-survival, US-004 drain-followup).
UploadPart (PUT ?uploadId=&partNumber=)internal/s3api/server.go:855 (→ uploadPart multipart.go:178)Content-MD5 accepted but not validated against streaming body (P2 ROADMAP gap). Part-checksum headers (x-amz-checksum-*) persisted per part for Complete verification.
UploadPartCopy (PUT ?uploadId=&partNumber= + x-amz-copy-source)internal/s3api/multipart.go:312 (dispatched from uploadPart at multipart.go:191)x-amz-copy-source-range: bytes=lo-hi honoured with strict lo<=hi<size parse.
CompleteMultipartUpload (POST ?uploadId=)internal/s3api/server.go:862 (→ completeMultipart multipart.go:449)LWT IF status='uploading' → completing blocks concurrent retries with NoSuchUpload. Composite ETag = md5(concat(part-md5s))-<count>. Per-part x-amz-checksum-* aggregated into object-level ChecksumType=COMPOSITE.
AbortMultipartUpload (DELETE ?uploadId=)internal/s3api/server.go:868 (→ abortMultipart multipart.go:745)Uploaded parts queue into the GC worker via enqueueChunks. Idempotent — 204 even when upload already aborted.
ListParts (GET ?uploadId=)internal/s3api/server.go:874 (→ listParts multipart.go:760)Pagination via part-number-marker + max-parts.
ListMultipartUploads (GET ?uploads)internal/s3api/server.go:391 (→ listMultipartUploads multipart.go:791)Pagination via key-marker + upload-id-marker.

Listing#

OperationHandler file:lineShipped inAWS gotchas
ListObjects (v1, GET no list-type)internal/s3api/server.go:488 (→ listObjects server.go:497)Sharded fan-out across STRATA_BUCKET_SHARDS partitions (Cassandra) OR ordered range scan (TiKV via RangeScanStore). marker is the literal last-key from prior page.
ListObjectsV2 (GET list-type=2)internal/s3api/server.go:488 (same listObjects, v2=true)ContinuationToken is opaque base64-URL JSON (listV2Token) — clients carrying older literal-marker tokens still resume via decode fallback (US-006).
ListObjectVersions (GET ?versions)internal/s3api/versioning.go:95Heap-merge of (key ASC, version_id DESC) across shards. Delete markers interleaved per AWS shape.

Versioning#

OperationHandler file:lineShipped inAWS gotchas
GetBucketVersioninginternal/s3api/versioning.go:13Suspended rows persist IsNull=true on subsequent PUTs (null-version sentinel).
PutBucketVersioninginternal/s3api/versioning.go:26LWT IF EXISTS guarantees Paxos coherence on the next quorum read. MFA-Delete toggle requires x-amz-mfa header.

ACL / Policy#

OperationHandler file:lineShipped inAWS gotchas
GetBucketAclinternal/s3api/acl.go:194
PutBucketAclinternal/s3api/acl.go:208Canned ACL x-amz-acl OR XML body; mixed input rejected.
GetBucketPolicyinternal/s3api/policy.go:35404 NoSuchBucketPolicy when unset (AWS parity).
PutBucketPolicyinternal/s3api/policy.go:12JSON body validated for required Version + Statement fields.
DeleteBucketPolicyinternal/s3api/policy.go:55Idempotent.
GetPublicAccessBlockinternal/s3api/public_access_block.go:67
PutPublicAccessBlockinternal/s3api/public_access_block.go:44
DeletePublicAccessBlockinternal/s3api/public_access_block.go:87
GetBucketOwnershipControlsinternal/s3api/ownership.go:53
PutBucketOwnershipControlsinternal/s3api/ownership.go:22
DeleteBucketOwnershipControlsinternal/s3api/ownership.go:73

CORS#

OperationHandler file:lineShipped inAWS gotchas
GetBucketCorsinternal/s3api/cors.go:103
PutBucketCorsinternal/s3api/cors.go:47
DeleteBucketCorsinternal/s3api/cors.go:209
CORS Preflight (OPTIONS)internal/s3api/cors.go:233Dispatched on OPTIONS to bucket OR object scope; non-S3 RPC (browser-only).

Encryption (SSE-S3 / SSE-KMS / SSE-C)#

OperationHandler file:lineShipped inAWS gotchas
GetBucketEncryptioninternal/s3api/encryption.go:147
PutBucketEncryptioninternal/s3api/encryption.go:109Default <ApplyServerSideEncryptionByDefault> resolved at every PUT when client omits SSE headers.
DeleteBucketEncryptioninternal/s3api/encryption.go:167

Replication#

OperationHandler file:lineShipped inAWS gotchas
GetBucketReplicationinternal/s3api/replication.go:100
PutBucketReplicationinternal/s3api/replication.go:59Replicator worker (STRATA_WORKERS=replicator) drains replication_queue via HTTP PUT to peer Strata; one rule per bucket.
DeleteBucketReplicationinternal/s3api/replication.go:120

Tagging#

OperationHandler file:lineShipped inAWS gotchas
GetBucketTagginginternal/s3api/tagging.go:105404 NoSuchTagSet when unset.
PutBucketTagginginternal/s3api/tagging.go:58Replace-all semantics.
DeleteBucketTagginginternal/s3api/tagging.go:125

Lifecycle#

OperationHandler file:lineShipped inAWS gotchas
GetBucketLifecycleConfigurationinternal/s3api/lifecycle.go:91
PutBucketLifecycleConfigurationinternal/s3api/lifecycle.go:14Lifecycle worker (STRATA_WORKERS=lifecycle) executes rules. CAS on SetObjectStorage — concurrent client write wins, worker discards tier-2 chunks via GC queue.
DeleteBucketLifecycleConfigurationinternal/s3api/lifecycle.go:111

Object Lock#

OperationHandler file:lineShipped inAWS gotchas
GetObjectLockConfigurationinternal/s3api/objectlock.go:72
PutObjectLockConfigurationinternal/s3api/objectlock.go:26Bucket-level Object Lock must be enabled at CreateBucket (x-amz-bucket-object-lock-enabled: true) or via this op.
GetObjectRetentioninternal/s3api/objectlock.go:186
PutObjectRetentioninternal/s3api/objectlock.go:125GOVERNANCE downgrades require x-amz-bypass-governance-retention: true. COMPLIANCE retention reductions are rejected with AccessDenied (AWS parity, US-006).
GetObjectLegalHoldinternal/s3api/objectlock.go:218
PutObjectLegalHoldinternal/s3api/objectlock.go:199

Inventory#

OperationHandler file:lineShipped inAWS gotchas
GetBucketInventoryConfiguration (GET ?inventory&id=)internal/s3api/inventory.go:144
PutBucketInventoryConfiguration (PUT ?inventory&id=)internal/s3api/inventory.go:77URL id must match <Id> element; otherwise 400 InvalidArgument. Inventory worker (STRATA_WORKERS=inventory) writes manifest.json + CSV.gz pairs.
DeleteBucketInventoryConfiguration (DELETE ?inventory&id=)internal/s3api/inventory.go:164
ListBucketInventoryConfigurations (GET ?inventory no id)internal/s3api/inventory.go:185

Notification#

OperationHandler file:lineShipped inAWS gotchas
GetBucketNotificationConfigurationinternal/s3api/notification.go:92
PutBucketNotificationConfigurationinternal/s3api/notification.go:57Notify worker (STRATA_WORKERS=notify) drains notify_queue to webhook / SQS sinks per STRATA_NOTIFY_TARGETS.

Logging#

OperationHandler file:lineShipped inAWS gotchas
GetBucketLogginginternal/s3api/logging.go:71
PutBucketLogginginternal/s3api/logging.go:26Access-log worker (STRATA_WORKERS=access-log) drains access_log_buffer and writes AWS-format log objects into the configured target bucket.

Website#

OperationHandler file:lineShipped inAWS gotchas
GetBucketWebsiteinternal/s3api/website.go:106
PutBucketWebsiteinternal/s3api/website.go:57Index + error documents persisted; GET on the bucket root serves index.html when configured.
DeleteBucketWebsiteinternal/s3api/website.go:126

Non-S3 RPC surface (routed alongside S3)#

OperationHandler file:lineNotes
IAM ?Action= queries (CreateUser, AssumeRole, …)internal/s3api/iam.go:62Routed on bucket-less requests when extractIAMAction(r) matches. Full action set: see the Admin API surface page’s IAM section.
Audit query (GET ?audit)internal/s3api/audit_query.go:41Strata extension — operator read of the audit_log table.
Notification DLQ query (GET ?notify-dlq)internal/s3api/notification_dlq.go:43Strata extension — operator read of notify-worker DLQ rows.

Out of scope this cycle#

The following AWS S3 surface is intentionally not implemented or not documented here:

  • Analytics, Metrics, Intelligent-Tiering, Accelerate — analytics surface not implemented (no row in handleBucket).
  • MultiRegionAccessPoint, MultipartUploadV2, Select — not implemented.
  • PutObjectLockConfiguration on a bucket without x-amz-bucket-object-lock-enabled — returns the AWS-spec InvalidBucketState shape.