Skip to content

Add CopyObject, UploadPartCopy and DeleteObjects operations - #1

Merged
stut merged 4 commits into
mainfrom
feat/registry-copy-ops
Jul 20, 2026
Merged

stut merged 4 commits into
mainfrom
feat/registry-copy-ops

Conversation

@stut

@stut stut commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Summary

Implements the three S3 operations docker/distribution's s3-aws storage driver needs for blob commits, which previously returned 405 (small blobs) or crashed the registry with a nil-pointer dereference (blobs over 32MB, where the driver switches to multipart copy and dereferences CopyPartResult without a nil check).

  • CopyObject — PUT /{bucket}/{key} with x-amz-copy-source (URL-encoded, leading slash optional). Streamed server-side copy using the existing 32KB-buffer temp-file-and-rename pattern, computing the content MD5 in flight. Returns <CopyObjectResult> with quoted MD5 ETag. 404 NoSuchKey for a missing source.
  • UploadPartCopy — multipart PUT with x-amz-copy-source and optional x-amz-copy-source-range: bytes=start-end (inclusive). Feeds a seek + io.LimitReader of the source into the existing UploadPart path, so part storage and ETag handling are unchanged. Returns <CopyPartResult>; 416 InvalidRange for out-of-bounds ranges.
  • DeleteObjects — POST /{bucket}?delete batch delete with quiet mode. Missing keys count as deleted, matching AWS semantics.

All three respect read-only mode (403 AccessDenied).

Also fixes two issues surfaced during verification:

  • All XML timestamps are now UTC — local-zone offsets (+01:00) broke the AWS SDK's response parsing before any copy operation was even reached
  • Verbose logging now includes the query string, matching the patterns documented in CLAUDE.md

Testing

  • New handler-level tests (pkg/s3/copy_test.go): encoded/leading-slash/bare copy sources, key with encoded spaces, MD5 ETag correctness, range boundaries (first/last byte, middle, full, out-of-bounds, inverted), multi-part ranged copy reassembled and content-verified, mixed existing/missing batch delete, quiet mode, malformed XML, read-only rejections for all three operations
  • go test -race ./... green, golangci-lint clean
  • End-to-end: ran registry:2 backed by s3dir; docker push/docker pull of an image with a 40MB layer (exercises UploadPartCopy) plus small layers (CopyObject) round-trips with zero 405/500/panic in the registry log

stut added 4 commits July 20, 2026 20:22
Implements the three S3 operations docker/distribution needs for
blob commits:

- CopyObject: PUT with x-amz-copy-source (URL-encoded, leading slash
  optional), streamed server-side copy with MD5 ETag in the
  CopyObjectResult response
- UploadPartCopy: PUT with partNumber/uploadId and x-amz-copy-source,
  optional x-amz-copy-source-range (inclusive), returns CopyPartResult;
  416 InvalidRange for out-of-bounds ranges
- DeleteObjects: POST /{bucket}?delete batch delete with quiet mode;
  missing keys count as deleted per AWS semantics

Also fixes two issues surfaced during registry verification:
- All XML timestamps are now formatted in UTC; local-zone offsets
  broke the AWS SDK's response parsing
- Verbose logging now includes the query string

Verified end-to-end against registry:2: docker push/pull of an image
with a 40MB layer (multipart copy path) and small layers (CopyObject
path) round-trips with no errors in the registry log.
The v4 lint action installs golangci-lint v1.x, which is built with
Go 1.24 and refuses to target Go 1.25. Bump to golangci-lint-action@v8
(golangci-lint v2) and rewrite the config in the v2 schema, replacing
the deprecated run.skip-dirs/skip-files and issues.exclude-rules keys.
The v2 config schema requires version to be a string; the action's
config-verify step rejects a bare number.
The build step ran './s3dir --help', but s3dir has no flag handling -
the binary started the real server and blocked the job indefinitely.
(The step never ran before because the lint job it depends on was
failing.) Replace it with a bounded smoke test that starts the server,
checks the ListBuckets endpoint responds, and kills it.
@stut
stut merged commit 941f252 into main Jul 20, 2026
8 checks passed
@stut
stut deleted the feat/registry-copy-ops branch July 20, 2026 19:40
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.

1 participant