detached fce0810

fix(registry) stream blob uploads and lower default chunk to 95MB

commit
fce0810071fe09068787a1302b91b09ccef61fc8
author
bnema · · 2026-02-06 20:42 UTC
parent
28e7d52
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
9 files · +67 -63
M internal/adapters/in/http/registry/handler.go +8 -14
@@ -23,9 +23,9 @@ const (
2323 // MaxManifestSize limits manifest uploads to 10MB.
2424 MaxManifestSize = 10 * 1024 * 1024
2525 // DefaultMaxBlobChunkSize is the default maximum size for a single blob upload chunk.
26- // Docker/Podman typically send a full layer as one request body, so this default
27- // must handle common base image layer sizes.
28- DefaultMaxBlobChunkSize = 512 * 1024 * 1024
26+ // Kept at 95MB to stay within Cloudflare's 100MB per-request limit.
27+ // Users who need larger chunks can configure it explicitly via max_blob_chunk_size.
28+ DefaultMaxBlobChunkSize = 95 * 1024 * 1024
2929 )
3030
3131 // Handler implements the HTTP handler for Docker Registry API v2.
@@ -407,11 +407,13 @@ func (h *Handler) handleBlobUpload(w http.ResponseWriter, r *http.Request) {
407407 }
408408 }
409409
410- // Limit blob chunk size to prevent memory exhaustion
410+ // Limit blob chunk size to prevent excessive uploads.
411+ // MaxBytesReader wraps the body so the downstream io.Copy stops at the limit.
411412 r.Body = http.MaxBytesReader(w, r.Body, h.maxBlobChunkSize)
412413
413- // Read the chunk from the request body
414- chunk, err := io.ReadAll(r.Body)
414+ // Stream the body directly to storage — memory usage is bounded to a small
415+ // copy buffer (~32KB) regardless of chunk size.
416+ length, err := h.registrySvc.AppendBlobChunk(ctx, name, uuid, r.Body)
415417 if err != nil {
416418 var maxBytesErr *http.MaxBytesError
417419 if errors.As(err, &maxBytesErr) {
@@ -419,14 +421,6 @@ func (h *Handler) handleBlobUpload(w http.ResponseWriter, r *http.Request) {
419421 h.sendRegistryError(w, http.StatusRequestEntityTooLarge, "SIZE_INVALID", "blob chunk exceeds maximum size")
420422 return
421423 }
422- log.Error().Err(err).Msg("failed to read blob chunk")
423- h.sendRegistryError(w, http.StatusBadRequest, "BLOB_UPLOAD_INVALID", "invalid blob chunk")
424- return
425- }
426-
427- // Append the chunk to the upload
428- length, err := h.registrySvc.AppendBlobChunk(ctx, name, uuid, chunk)
429- if err != nil {
430424 log.Error().Err(err).Msg("failed to append blob chunk")
431425 h.sendRegistryError(w, http.StatusInternalServerError, "BLOB_UPLOAD_UNKNOWN", "failed to append blob chunk")
432426 return
@@ -23,9 +23,9 @@ const (
23 // MaxManifestSize limits manifest uploads to 10MB.23 // MaxManifestSize limits manifest uploads to 10MB.
24 MaxManifestSize = 10 * 1024 * 102424 MaxManifestSize = 10 * 1024 * 1024
25 // DefaultMaxBlobChunkSize is the default maximum size for a single blob upload chunk.25 // DefaultMaxBlobChunkSize is the default maximum size for a single blob upload chunk.
26- // Docker/Podman typically send a full layer as one request body, so this default26+ // Kept at 95MB to stay within Cloudflare's 100MB per-request limit.
27- // must handle common base image layer sizes.27+ // Users who need larger chunks can configure it explicitly via max_blob_chunk_size.
28- DefaultMaxBlobChunkSize = 512 * 1024 * 102428+ DefaultMaxBlobChunkSize = 95 * 1024 * 1024
29 )29 )
30 30
31 // Handler implements the HTTP handler for Docker Registry API v2.31 // Handler implements the HTTP handler for Docker Registry API v2.
@@ -407,11 +407,13 @@ func (h *Handler) handleBlobUpload(w http.ResponseWriter, r *http.Request) {
407 }407 }
408 }408 }
409 409
410- // Limit blob chunk size to prevent memory exhaustion410+ // Limit blob chunk size to prevent excessive uploads.
411+ // MaxBytesReader wraps the body so the downstream io.Copy stops at the limit.
411 r.Body = http.MaxBytesReader(w, r.Body, h.maxBlobChunkSize)412 r.Body = http.MaxBytesReader(w, r.Body, h.maxBlobChunkSize)
412 413
413- // Read the chunk from the request body414+ // Stream the body directly to storage — memory usage is bounded to a small
414- chunk, err := io.ReadAll(r.Body)415+ // copy buffer (~32KB) regardless of chunk size.
416+ length, err := h.registrySvc.AppendBlobChunk(ctx, name, uuid, r.Body)
415 if err != nil {417 if err != nil {
416 var maxBytesErr *http.MaxBytesError418 var maxBytesErr *http.MaxBytesError
417 if errors.As(err, &maxBytesErr) {419 if errors.As(err, &maxBytesErr) {
@@ -419,14 +421,6 @@ func (h *Handler) handleBlobUpload(w http.ResponseWriter, r *http.Request) {
419 h.sendRegistryError(w, http.StatusRequestEntityTooLarge, "SIZE_INVALID", "blob chunk exceeds maximum size")421 h.sendRegistryError(w, http.StatusRequestEntityTooLarge, "SIZE_INVALID", "blob chunk exceeds maximum size")
420 return422 return
421 }423 }
422- log.Error().Err(err).Msg("failed to read blob chunk")
423- h.sendRegistryError(w, http.StatusBadRequest, "BLOB_UPLOAD_INVALID", "invalid blob chunk")
424- return
425- }
426-
427- // Append the chunk to the upload
428- length, err := h.registrySvc.AppendBlobChunk(ctx, name, uuid, chunk)
429- if err != nil {
430 log.Error().Err(err).Msg("failed to append blob chunk")424 log.Error().Err(err).Msg("failed to append blob chunk")
431 h.sendRegistryError(w, http.StatusInternalServerError, "BLOB_UPLOAD_UNKNOWN", "failed to append blob chunk")425 h.sendRegistryError(w, http.StatusInternalServerError, "BLOB_UPLOAD_UNKNOWN", "failed to append blob chunk")
432 return426 return
M internal/adapters/in/http/registry/handler_test.go +17 -6
@@ -2,7 +2,9 @@ package registry
22
33 import (
44 "bytes"
5+ "context"
56 "encoding/json"
7+ "io"
68 "net/http"
79 "net/http/httptest"
810 "os"
@@ -291,7 +293,7 @@ func TestHandler_BlobUpload_PATCH(t *testing.T) {
291293 handler := NewHandler(registrySvc, testLogger(), DefaultMaxBlobChunkSize)
292294
293295 chunkData := []byte("chunk content")
294- registrySvc.EXPECT().AppendBlobChunk(mock.Anything, "myapp", "550e8400-e29b-41d4-a716-446655440000", chunkData).Return(int64(len(chunkData)), nil)
296+ registrySvc.EXPECT().AppendBlobChunk(mock.Anything, "myapp", "550e8400-e29b-41d4-a716-446655440000", mock.Anything).Return(int64(len(chunkData)), nil)
295297
296298 req := httptest.NewRequest("PATCH", "/v2/myapp/blobs/uploads/550e8400-e29b-41d4-a716-446655440000", bytes.NewReader(chunkData))
297299 rec := httptest.NewRecorder()
@@ -308,7 +310,7 @@ func TestHandler_BlobUpload_PUT_Finalize(t *testing.T) {
308310 handler := NewHandler(registrySvc, testLogger(), DefaultMaxBlobChunkSize)
309311
310312 chunkData := []byte("final chunk")
311- registrySvc.EXPECT().AppendBlobChunk(mock.Anything, "myapp", "550e8400-e29b-41d4-a716-446655440000", chunkData).Return(int64(len(chunkData)), nil)
313+ registrySvc.EXPECT().AppendBlobChunk(mock.Anything, "myapp", "550e8400-e29b-41d4-a716-446655440000", mock.Anything).Return(int64(len(chunkData)), nil)
312314 registrySvc.EXPECT().FinishUpload(mock.Anything, "550e8400-e29b-41d4-a716-446655440000", "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4").Return(nil)
313315
314316 req := httptest.NewRequest("PUT", "/v2/myapp/blobs/uploads/550e8400-e29b-41d4-a716-446655440000?digest=sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4", bytes.NewReader(chunkData))
@@ -430,7 +432,7 @@ func TestHandler_BlobUpload_PATCH_ReturnsDockerUploadUUID(t *testing.T) {
430432 handler := NewHandler(registrySvc, testLogger(), DefaultMaxBlobChunkSize)
431433
432434 chunkData := []byte("chunk content")
433- registrySvc.EXPECT().AppendBlobChunk(mock.Anything, "myapp", "550e8400-e29b-41d4-a716-446655440000", chunkData).Return(int64(len(chunkData)), nil)
435+ registrySvc.EXPECT().AppendBlobChunk(mock.Anything, "myapp", "550e8400-e29b-41d4-a716-446655440000", mock.Anything).Return(int64(len(chunkData)), nil)
434436
435437 req := httptest.NewRequest("PATCH", "/v2/myapp/blobs/uploads/550e8400-e29b-41d4-a716-446655440000", bytes.NewReader(chunkData))
436438 rec := httptest.NewRecorder()
@@ -441,14 +443,23 @@ func TestHandler_BlobUpload_PATCH_ReturnsDockerUploadUUID(t *testing.T) {
441443 assert.Equal(t, "550e8400-e29b-41d4-a716-446655440000", rec.Header().Get("Docker-Upload-UUID"))
442444 }
443445
444-func TestDefaultMaxBlobChunkSize_Is512MB(t *testing.T) {
445- assert.Equal(t, int64(512*1024*1024), int64(DefaultMaxBlobChunkSize))
446+func TestDefaultMaxBlobChunkSize_Is95MB(t *testing.T) {
447+ assert.Equal(t, int64(95*1024*1024), int64(DefaultMaxBlobChunkSize))
446448 }
447449
448450 func TestHandler_BlobUpload_PATCH_RespectsConfiguredMaxBlobChunkSize(t *testing.T) {
449451 registrySvc := inmocks.NewMockRegistryService(t)
450452 handler := NewHandler(registrySvc, testLogger(), 5)
451453
454+ // The handler wraps r.Body in MaxBytesReader and passes it to AppendBlobChunk.
455+ // The mock must read the body to trigger MaxBytesError, which the handler detects.
456+ registrySvc.EXPECT().AppendBlobChunk(mock.Anything, "myapp", "550e8400-e29b-41d4-a716-446655440000", mock.Anything).
457+ Run(func(_ context.Context, _ string, _ string, data io.Reader) {
458+ // Drain the reader to trigger MaxBytesError
459+ _, _ = io.ReadAll(data)
460+ }).
461+ Return(int64(0), &http.MaxBytesError{Limit: 5})
462+
452463 req := httptest.NewRequest("PATCH", "/v2/myapp/blobs/uploads/550e8400-e29b-41d4-a716-446655440000", bytes.NewReader([]byte("chunk content")))
453464 rec := httptest.NewRecorder()
454465
@@ -462,7 +473,7 @@ func TestHandler_BlobUpload_PATCH_ReturnsRangeHeader(t *testing.T) {
462473 handler := NewHandler(registrySvc, testLogger(), DefaultMaxBlobChunkSize)
463474
464475 chunkData := []byte("chunk content")
465- registrySvc.EXPECT().AppendBlobChunk(mock.Anything, "myapp", "550e8400-e29b-41d4-a716-446655440000", chunkData).Return(int64(len(chunkData)), nil)
476+ registrySvc.EXPECT().AppendBlobChunk(mock.Anything, "myapp", "550e8400-e29b-41d4-a716-446655440000", mock.Anything).Return(int64(len(chunkData)), nil)
466477
467478 req := httptest.NewRequest("PATCH", "/v2/myapp/blobs/uploads/550e8400-e29b-41d4-a716-446655440000", bytes.NewReader(chunkData))
468479 rec := httptest.NewRecorder()
@@ -2,7 +2,9 @@ package registry
2 2
3 import (3 import (
4 "bytes"4 "bytes"
5+ "context"
5 "encoding/json"6 "encoding/json"
7+ "io"
6 "net/http"8 "net/http"
7 "net/http/httptest"9 "net/http/httptest"
8 "os"10 "os"
@@ -291,7 +293,7 @@ func TestHandler_BlobUpload_PATCH(t *testing.T) {
291 handler := NewHandler(registrySvc, testLogger(), DefaultMaxBlobChunkSize)293 handler := NewHandler(registrySvc, testLogger(), DefaultMaxBlobChunkSize)
292 294
293 chunkData := []byte("chunk content")295 chunkData := []byte("chunk content")
294- registrySvc.EXPECT().AppendBlobChunk(mock.Anything, "myapp", "550e8400-e29b-41d4-a716-446655440000", chunkData).Return(int64(len(chunkData)), nil)296+ registrySvc.EXPECT().AppendBlobChunk(mock.Anything, "myapp", "550e8400-e29b-41d4-a716-446655440000", mock.Anything).Return(int64(len(chunkData)), nil)
295 297
296 req := httptest.NewRequest("PATCH", "/v2/myapp/blobs/uploads/550e8400-e29b-41d4-a716-446655440000", bytes.NewReader(chunkData))298 req := httptest.NewRequest("PATCH", "/v2/myapp/blobs/uploads/550e8400-e29b-41d4-a716-446655440000", bytes.NewReader(chunkData))
297 rec := httptest.NewRecorder()299 rec := httptest.NewRecorder()
@@ -308,7 +310,7 @@ func TestHandler_BlobUpload_PUT_Finalize(t *testing.T) {
308 handler := NewHandler(registrySvc, testLogger(), DefaultMaxBlobChunkSize)310 handler := NewHandler(registrySvc, testLogger(), DefaultMaxBlobChunkSize)
309 311
310 chunkData := []byte("final chunk")312 chunkData := []byte("final chunk")
311- registrySvc.EXPECT().AppendBlobChunk(mock.Anything, "myapp", "550e8400-e29b-41d4-a716-446655440000", chunkData).Return(int64(len(chunkData)), nil)313+ registrySvc.EXPECT().AppendBlobChunk(mock.Anything, "myapp", "550e8400-e29b-41d4-a716-446655440000", mock.Anything).Return(int64(len(chunkData)), nil)
312 registrySvc.EXPECT().FinishUpload(mock.Anything, "550e8400-e29b-41d4-a716-446655440000", "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4").Return(nil)314 registrySvc.EXPECT().FinishUpload(mock.Anything, "550e8400-e29b-41d4-a716-446655440000", "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4").Return(nil)
313 315
314 req := httptest.NewRequest("PUT", "/v2/myapp/blobs/uploads/550e8400-e29b-41d4-a716-446655440000?digest=sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4", bytes.NewReader(chunkData))316 req := httptest.NewRequest("PUT", "/v2/myapp/blobs/uploads/550e8400-e29b-41d4-a716-446655440000?digest=sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4", bytes.NewReader(chunkData))
@@ -430,7 +432,7 @@ func TestHandler_BlobUpload_PATCH_ReturnsDockerUploadUUID(t *testing.T) {
430 handler := NewHandler(registrySvc, testLogger(), DefaultMaxBlobChunkSize)432 handler := NewHandler(registrySvc, testLogger(), DefaultMaxBlobChunkSize)
431 433
432 chunkData := []byte("chunk content")434 chunkData := []byte("chunk content")
433- registrySvc.EXPECT().AppendBlobChunk(mock.Anything, "myapp", "550e8400-e29b-41d4-a716-446655440000", chunkData).Return(int64(len(chunkData)), nil)435+ registrySvc.EXPECT().AppendBlobChunk(mock.Anything, "myapp", "550e8400-e29b-41d4-a716-446655440000", mock.Anything).Return(int64(len(chunkData)), nil)
434 436
435 req := httptest.NewRequest("PATCH", "/v2/myapp/blobs/uploads/550e8400-e29b-41d4-a716-446655440000", bytes.NewReader(chunkData))437 req := httptest.NewRequest("PATCH", "/v2/myapp/blobs/uploads/550e8400-e29b-41d4-a716-446655440000", bytes.NewReader(chunkData))
436 rec := httptest.NewRecorder()438 rec := httptest.NewRecorder()
@@ -441,14 +443,23 @@ func TestHandler_BlobUpload_PATCH_ReturnsDockerUploadUUID(t *testing.T) {
441 assert.Equal(t, "550e8400-e29b-41d4-a716-446655440000", rec.Header().Get("Docker-Upload-UUID"))443 assert.Equal(t, "550e8400-e29b-41d4-a716-446655440000", rec.Header().Get("Docker-Upload-UUID"))
442 }444 }
443 445
444-func TestDefaultMaxBlobChunkSize_Is512MB(t *testing.T) {446+func TestDefaultMaxBlobChunkSize_Is95MB(t *testing.T) {
445- assert.Equal(t, int64(512*1024*1024), int64(DefaultMaxBlobChunkSize))447+ assert.Equal(t, int64(95*1024*1024), int64(DefaultMaxBlobChunkSize))
446 }448 }
447 449
448 func TestHandler_BlobUpload_PATCH_RespectsConfiguredMaxBlobChunkSize(t *testing.T) {450 func TestHandler_BlobUpload_PATCH_RespectsConfiguredMaxBlobChunkSize(t *testing.T) {
449 registrySvc := inmocks.NewMockRegistryService(t)451 registrySvc := inmocks.NewMockRegistryService(t)
450 handler := NewHandler(registrySvc, testLogger(), 5)452 handler := NewHandler(registrySvc, testLogger(), 5)
451 453
454+ // The handler wraps r.Body in MaxBytesReader and passes it to AppendBlobChunk.
455+ // The mock must read the body to trigger MaxBytesError, which the handler detects.
456+ registrySvc.EXPECT().AppendBlobChunk(mock.Anything, "myapp", "550e8400-e29b-41d4-a716-446655440000", mock.Anything).
457+ Run(func(_ context.Context, _ string, _ string, data io.Reader) {
458+ // Drain the reader to trigger MaxBytesError
459+ _, _ = io.ReadAll(data)
460+ }).
461+ Return(int64(0), &http.MaxBytesError{Limit: 5})
462+
452 req := httptest.NewRequest("PATCH", "/v2/myapp/blobs/uploads/550e8400-e29b-41d4-a716-446655440000", bytes.NewReader([]byte("chunk content")))463 req := httptest.NewRequest("PATCH", "/v2/myapp/blobs/uploads/550e8400-e29b-41d4-a716-446655440000", bytes.NewReader([]byte("chunk content")))
453 rec := httptest.NewRecorder()464 rec := httptest.NewRecorder()
454 465
@@ -462,7 +473,7 @@ func TestHandler_BlobUpload_PATCH_ReturnsRangeHeader(t *testing.T) {
462 handler := NewHandler(registrySvc, testLogger(), DefaultMaxBlobChunkSize)473 handler := NewHandler(registrySvc, testLogger(), DefaultMaxBlobChunkSize)
463 474
464 chunkData := []byte("chunk content")475 chunkData := []byte("chunk content")
465- registrySvc.EXPECT().AppendBlobChunk(mock.Anything, "myapp", "550e8400-e29b-41d4-a716-446655440000", chunkData).Return(int64(len(chunkData)), nil)476+ registrySvc.EXPECT().AppendBlobChunk(mock.Anything, "myapp", "550e8400-e29b-41d4-a716-446655440000", mock.Anything).Return(int64(len(chunkData)), nil)
466 477
467 req := httptest.NewRequest("PATCH", "/v2/myapp/blobs/uploads/550e8400-e29b-41d4-a716-446655440000", bytes.NewReader(chunkData))478 req := httptest.NewRequest("PATCH", "/v2/myapp/blobs/uploads/550e8400-e29b-41d4-a716-446655440000", bytes.NewReader(chunkData))
468 rec := httptest.NewRecorder()479 rec := httptest.NewRecorder()
M internal/adapters/out/filesystem/blob.go +3 -3
@@ -197,7 +197,7 @@ func (s *BlobStorage) StartBlobUpload(name string) (string, error) {
197197 }
198198
199199 // AppendBlobChunk appends data to an in-progress upload.
200-func (s *BlobStorage) AppendBlobChunk(name, uuid string, chunk []byte) (int64, error) {
200+func (s *BlobStorage) AppendBlobChunk(name, uuid string, data io.Reader) (int64, error) {
201201 uploadPath, err := s.getUploadPath(uuid)
202202 if err != nil {
203203 return 0, fmt.Errorf("invalid upload path: %w", err)
@@ -212,7 +212,7 @@ func (s *BlobStorage) AppendBlobChunk(name, uuid string, chunk []byte) (int64, e
212212 }
213213 defer file.Close()
214214
215- written, err := file.Write(chunk)
215+ written, err := io.Copy(file, data)
216216 if err != nil {
217217 return 0, fmt.Errorf("failed to write chunk to upload file: %w", err)
218218 }
@@ -227,7 +227,7 @@ func (s *BlobStorage) AppendBlobChunk(name, uuid string, chunk []byte) (int64, e
227227 Str(zerowrap.FieldAdapter, "filesystem").
228228 Str("uuid", uuid).
229229 Str("name", name).
230- Int("chunk_size", written).
230+ Int64("chunk_size", written).
231231 Int64("total_size", fi.Size()).
232232 Msg("appended chunk to blob upload")
233233
@@ -197,7 +197,7 @@ func (s *BlobStorage) StartBlobUpload(name string) (string, error) {
197 }197 }
198 198
199 // AppendBlobChunk appends data to an in-progress upload.199 // AppendBlobChunk appends data to an in-progress upload.
200-func (s *BlobStorage) AppendBlobChunk(name, uuid string, chunk []byte) (int64, error) {200+func (s *BlobStorage) AppendBlobChunk(name, uuid string, data io.Reader) (int64, error) {
201 uploadPath, err := s.getUploadPath(uuid)201 uploadPath, err := s.getUploadPath(uuid)
202 if err != nil {202 if err != nil {
203 return 0, fmt.Errorf("invalid upload path: %w", err)203 return 0, fmt.Errorf("invalid upload path: %w", err)
@@ -212,7 +212,7 @@ func (s *BlobStorage) AppendBlobChunk(name, uuid string, chunk []byte) (int64, e
212 }212 }
213 defer file.Close()213 defer file.Close()
214 214
215- written, err := file.Write(chunk)215+ written, err := io.Copy(file, data)
216 if err != nil {216 if err != nil {
217 return 0, fmt.Errorf("failed to write chunk to upload file: %w", err)217 return 0, fmt.Errorf("failed to write chunk to upload file: %w", err)
218 }218 }
@@ -227,7 +227,7 @@ func (s *BlobStorage) AppendBlobChunk(name, uuid string, chunk []byte) (int64, e
227 Str(zerowrap.FieldAdapter, "filesystem").227 Str(zerowrap.FieldAdapter, "filesystem").
228 Str("uuid", uuid).228 Str("uuid", uuid).
229 Str("name", name).229 Str("name", name).
230- Int("chunk_size", written).230+ Int64("chunk_size", written).
231 Int64("total_size", fi.Size()).231 Int64("total_size", fi.Size()).
232 Msg("appended chunk to blob upload")232 Msg("appended chunk to blob upload")
233 233
M internal/adapters/out/filesystem/blob_test.go +5 -5
@@ -242,13 +242,13 @@ func TestBlobStorage_AppendBlobChunk(t *testing.T) {
242242
243243 // Append first chunk
244244 chunk1 := []byte("first chunk")
245- size1, err := storage.AppendBlobChunk("myapp", uuid, chunk1)
245+ size1, err := storage.AppendBlobChunk("myapp", uuid, bytes.NewReader(chunk1))
246246 require.NoError(t, err)
247247 assert.Equal(t, int64(len(chunk1)), size1)
248248
249249 // Append second chunk
250250 chunk2 := []byte(" second chunk")
251- size2, err := storage.AppendBlobChunk("myapp", uuid, chunk2)
251+ size2, err := storage.AppendBlobChunk("myapp", uuid, bytes.NewReader(chunk2))
252252 require.NoError(t, err)
253253 assert.Equal(t, int64(len(chunk1)+len(chunk2)), size2)
254254 }
@@ -260,7 +260,7 @@ func TestBlobStorage_AppendBlobChunk_NotFound(t *testing.T) {
260260 storage, err := NewBlobStorage(tmpDir, log)
261261 require.NoError(t, err)
262262
263- _, err = storage.AppendBlobChunk("myapp", "00000000-0000-4000-a000-000000000000", []byte("data"))
263+ _, err = storage.AppendBlobChunk("myapp", "00000000-0000-4000-a000-000000000000", bytes.NewReader([]byte("data")))
264264
265265 assert.Error(t, err)
266266 assert.Contains(t, err.Error(), "upload not found")
@@ -312,7 +312,7 @@ func TestBlobStorage_FinishBlobUpload(t *testing.T) {
312312
313313 // Append data - content that hashes to testDigest1
314314 blobData := []byte("complete blob content")
315- _, err = storage.AppendBlobChunk("myapp", uuid, blobData)
315+ _, err = storage.AppendBlobChunk("myapp", uuid, bytes.NewReader(blobData))
316316 require.NoError(t, err)
317317
318318 // Finish upload - use testDigest1 (validation will pass, but hash won't match)
@@ -439,7 +439,7 @@ func TestBlobStorage_CompleteUploadFlow(t *testing.T) {
439439
440440 var totalSize int64
441441 for _, chunk := range chunks {
442- size, err := storage.AppendBlobChunk(repoName, uuid, chunk)
442+ size, err := storage.AppendBlobChunk(repoName, uuid, bytes.NewReader(chunk))
443443 require.NoError(t, err)
444444 totalSize = size
445445 }
@@ -242,13 +242,13 @@ func TestBlobStorage_AppendBlobChunk(t *testing.T) {
242 242
243 // Append first chunk243 // Append first chunk
244 chunk1 := []byte("first chunk")244 chunk1 := []byte("first chunk")
245- size1, err := storage.AppendBlobChunk("myapp", uuid, chunk1)245+ size1, err := storage.AppendBlobChunk("myapp", uuid, bytes.NewReader(chunk1))
246 require.NoError(t, err)246 require.NoError(t, err)
247 assert.Equal(t, int64(len(chunk1)), size1)247 assert.Equal(t, int64(len(chunk1)), size1)
248 248
249 // Append second chunk249 // Append second chunk
250 chunk2 := []byte(" second chunk")250 chunk2 := []byte(" second chunk")
251- size2, err := storage.AppendBlobChunk("myapp", uuid, chunk2)251+ size2, err := storage.AppendBlobChunk("myapp", uuid, bytes.NewReader(chunk2))
252 require.NoError(t, err)252 require.NoError(t, err)
253 assert.Equal(t, int64(len(chunk1)+len(chunk2)), size2)253 assert.Equal(t, int64(len(chunk1)+len(chunk2)), size2)
254 }254 }
@@ -260,7 +260,7 @@ func TestBlobStorage_AppendBlobChunk_NotFound(t *testing.T) {
260 storage, err := NewBlobStorage(tmpDir, log)260 storage, err := NewBlobStorage(tmpDir, log)
261 require.NoError(t, err)261 require.NoError(t, err)
262 262
263- _, err = storage.AppendBlobChunk("myapp", "00000000-0000-4000-a000-000000000000", []byte("data"))263+ _, err = storage.AppendBlobChunk("myapp", "00000000-0000-4000-a000-000000000000", bytes.NewReader([]byte("data")))
264 264
265 assert.Error(t, err)265 assert.Error(t, err)
266 assert.Contains(t, err.Error(), "upload not found")266 assert.Contains(t, err.Error(), "upload not found")
@@ -312,7 +312,7 @@ func TestBlobStorage_FinishBlobUpload(t *testing.T) {
312 312
313 // Append data - content that hashes to testDigest1313 // Append data - content that hashes to testDigest1
314 blobData := []byte("complete blob content")314 blobData := []byte("complete blob content")
315- _, err = storage.AppendBlobChunk("myapp", uuid, blobData)315+ _, err = storage.AppendBlobChunk("myapp", uuid, bytes.NewReader(blobData))
316 require.NoError(t, err)316 require.NoError(t, err)
317 317
318 // Finish upload - use testDigest1 (validation will pass, but hash won't match)318 // Finish upload - use testDigest1 (validation will pass, but hash won't match)
@@ -439,7 +439,7 @@ func TestBlobStorage_CompleteUploadFlow(t *testing.T) {
439 439
440 var totalSize int64440 var totalSize int64
441 for _, chunk := range chunks {441 for _, chunk := range chunks {
442- size, err := storage.AppendBlobChunk(repoName, uuid, chunk)442+ size, err := storage.AppendBlobChunk(repoName, uuid, bytes.NewReader(chunk))
443 require.NoError(t, err)443 require.NoError(t, err)
444 totalSize = size444 totalSize = size
445 }445 }
M internal/boundaries/in/mocks/mock_registry_service.go +15 -15
@@ -40,8 +40,8 @@ func (_m *MockRegistryService) EXPECT() *MockRegistryService_Expecter {
4040 }
4141
4242 // AppendBlobChunk provides a mock function for the type MockRegistryService
43-func (_mock *MockRegistryService) AppendBlobChunk(ctx context.Context, name string, uuid string, chunk []byte) (int64, error) {
44- ret := _mock.Called(ctx, name, uuid, chunk)
43+func (_mock *MockRegistryService) AppendBlobChunk(ctx context.Context, name string, uuid string, data io.Reader) (int64, error) {
44+ ret := _mock.Called(ctx, name, uuid, data)
4545
4646 if len(ret) == 0 {
4747 panic("no return value specified for AppendBlobChunk")
@@ -49,16 +49,16 @@ func (_mock *MockRegistryService) AppendBlobChunk(ctx context.Context, name stri
4949
5050 var r0 int64
5151 var r1 error
52- if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, []byte) (int64, error)); ok {
53- return returnFunc(ctx, name, uuid, chunk)
52+ if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, io.Reader) (int64, error)); ok {
53+ return returnFunc(ctx, name, uuid, data)
5454 }
55- if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, []byte) int64); ok {
56- r0 = returnFunc(ctx, name, uuid, chunk)
55+ if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, io.Reader) int64); ok {
56+ r0 = returnFunc(ctx, name, uuid, data)
5757 } else {
5858 r0 = ret.Get(0).(int64)
5959 }
60- if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, []byte) error); ok {
61- r1 = returnFunc(ctx, name, uuid, chunk)
60+ if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, io.Reader) error); ok {
61+ r1 = returnFunc(ctx, name, uuid, data)
6262 } else {
6363 r1 = ret.Error(1)
6464 }
@@ -74,12 +74,12 @@ type MockRegistryService_AppendBlobChunk_Call struct {
7474 // - ctx context.Context
7575 // - name string
7676 // - uuid string
77-// - chunk []byte
78-func (_e *MockRegistryService_Expecter) AppendBlobChunk(ctx interface{}, name interface{}, uuid interface{}, chunk interface{}) *MockRegistryService_AppendBlobChunk_Call {
79- return &MockRegistryService_AppendBlobChunk_Call{Call: _e.mock.On("AppendBlobChunk", ctx, name, uuid, chunk)}
77+// - data io.Reader
78+func (_e *MockRegistryService_Expecter) AppendBlobChunk(ctx interface{}, name interface{}, uuid interface{}, data interface{}) *MockRegistryService_AppendBlobChunk_Call {
79+ return &MockRegistryService_AppendBlobChunk_Call{Call: _e.mock.On("AppendBlobChunk", ctx, name, uuid, data)}
8080 }
8181
82-func (_c *MockRegistryService_AppendBlobChunk_Call) Run(run func(ctx context.Context, name string, uuid string, chunk []byte)) *MockRegistryService_AppendBlobChunk_Call {
82+func (_c *MockRegistryService_AppendBlobChunk_Call) Run(run func(ctx context.Context, name string, uuid string, data io.Reader)) *MockRegistryService_AppendBlobChunk_Call {
8383 _c.Call.Run(func(args mock.Arguments) {
8484 var arg0 context.Context
8585 if args[0] != nil {
@@ -93,9 +93,9 @@ func (_c *MockRegistryService_AppendBlobChunk_Call) Run(run func(ctx context.Con
9393 if args[2] != nil {
9494 arg2 = args[2].(string)
9595 }
96- var arg3 []byte
96+ var arg3 io.Reader
9797 if args[3] != nil {
98- arg3 = args[3].([]byte)
98+ arg3 = args[3].(io.Reader)
9999 }
100100 run(
101101 arg0,
@@ -112,7 +112,7 @@ func (_c *MockRegistryService_AppendBlobChunk_Call) Return(n int64, err error) *
112112 return _c
113113 }
114114
115-func (_c *MockRegistryService_AppendBlobChunk_Call) RunAndReturn(run func(ctx context.Context, name string, uuid string, chunk []byte) (int64, error)) *MockRegistryService_AppendBlobChunk_Call {
115+func (_c *MockRegistryService_AppendBlobChunk_Call) RunAndReturn(run func(ctx context.Context, name string, uuid string, data io.Reader) (int64, error)) *MockRegistryService_AppendBlobChunk_Call {
116116 _c.Call.Return(run)
117117 return _c
118118 }
@@ -40,8 +40,8 @@ func (_m *MockRegistryService) EXPECT() *MockRegistryService_Expecter {
40 }40 }
41 41
42 // AppendBlobChunk provides a mock function for the type MockRegistryService42 // AppendBlobChunk provides a mock function for the type MockRegistryService
43-func (_mock *MockRegistryService) AppendBlobChunk(ctx context.Context, name string, uuid string, chunk []byte) (int64, error) {43+func (_mock *MockRegistryService) AppendBlobChunk(ctx context.Context, name string, uuid string, data io.Reader) (int64, error) {
44- ret := _mock.Called(ctx, name, uuid, chunk)44+ ret := _mock.Called(ctx, name, uuid, data)
45 45
46 if len(ret) == 0 {46 if len(ret) == 0 {
47 panic("no return value specified for AppendBlobChunk")47 panic("no return value specified for AppendBlobChunk")
@@ -49,16 +49,16 @@ func (_mock *MockRegistryService) AppendBlobChunk(ctx context.Context, name stri
49 49
50 var r0 int6450 var r0 int64
51 var r1 error51 var r1 error
52- if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, []byte) (int64, error)); ok {52+ if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, io.Reader) (int64, error)); ok {
53- return returnFunc(ctx, name, uuid, chunk)53+ return returnFunc(ctx, name, uuid, data)
54 }54 }
55- if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, []byte) int64); ok {55+ if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, io.Reader) int64); ok {
56- r0 = returnFunc(ctx, name, uuid, chunk)56+ r0 = returnFunc(ctx, name, uuid, data)
57 } else {57 } else {
58 r0 = ret.Get(0).(int64)58 r0 = ret.Get(0).(int64)
59 }59 }
60- if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, []byte) error); ok {60+ if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, io.Reader) error); ok {
61- r1 = returnFunc(ctx, name, uuid, chunk)61+ r1 = returnFunc(ctx, name, uuid, data)
62 } else {62 } else {
63 r1 = ret.Error(1)63 r1 = ret.Error(1)
64 }64 }
@@ -74,12 +74,12 @@ type MockRegistryService_AppendBlobChunk_Call struct {
74 // - ctx context.Context74 // - ctx context.Context
75 // - name string75 // - name string
76 // - uuid string76 // - uuid string
77-// - chunk []byte77+// - data io.Reader
78-func (_e *MockRegistryService_Expecter) AppendBlobChunk(ctx interface{}, name interface{}, uuid interface{}, chunk interface{}) *MockRegistryService_AppendBlobChunk_Call {78+func (_e *MockRegistryService_Expecter) AppendBlobChunk(ctx interface{}, name interface{}, uuid interface{}, data interface{}) *MockRegistryService_AppendBlobChunk_Call {
79- return &MockRegistryService_AppendBlobChunk_Call{Call: _e.mock.On("AppendBlobChunk", ctx, name, uuid, chunk)}79+ return &MockRegistryService_AppendBlobChunk_Call{Call: _e.mock.On("AppendBlobChunk", ctx, name, uuid, data)}
80 }80 }
81 81
82-func (_c *MockRegistryService_AppendBlobChunk_Call) Run(run func(ctx context.Context, name string, uuid string, chunk []byte)) *MockRegistryService_AppendBlobChunk_Call {82+func (_c *MockRegistryService_AppendBlobChunk_Call) Run(run func(ctx context.Context, name string, uuid string, data io.Reader)) *MockRegistryService_AppendBlobChunk_Call {
83 _c.Call.Run(func(args mock.Arguments) {83 _c.Call.Run(func(args mock.Arguments) {
84 var arg0 context.Context84 var arg0 context.Context
85 if args[0] != nil {85 if args[0] != nil {
@@ -93,9 +93,9 @@ func (_c *MockRegistryService_AppendBlobChunk_Call) Run(run func(ctx context.Con
93 if args[2] != nil {93 if args[2] != nil {
94 arg2 = args[2].(string)94 arg2 = args[2].(string)
95 }95 }
96- var arg3 []byte96+ var arg3 io.Reader
97 if args[3] != nil {97 if args[3] != nil {
98- arg3 = args[3].([]byte)98+ arg3 = args[3].(io.Reader)
99 }99 }
100 run(100 run(
101 arg0,101 arg0,
@@ -112,7 +112,7 @@ func (_c *MockRegistryService_AppendBlobChunk_Call) Return(n int64, err error) *
112 return _c112 return _c
113 }113 }
114 114
115-func (_c *MockRegistryService_AppendBlobChunk_Call) RunAndReturn(run func(ctx context.Context, name string, uuid string, chunk []byte) (int64, error)) *MockRegistryService_AppendBlobChunk_Call {115+func (_c *MockRegistryService_AppendBlobChunk_Call) RunAndReturn(run func(ctx context.Context, name string, uuid string, data io.Reader) (int64, error)) *MockRegistryService_AppendBlobChunk_Call {
116 _c.Call.Return(run)116 _c.Call.Return(run)
117 return _c117 return _c
118 }118 }
M internal/boundaries/in/registry.go +1 -1
@@ -22,7 +22,7 @@ type RegistryService interface {
2222
2323 // Upload operations
2424 StartUpload(ctx context.Context, name string) (string, error)
25- AppendBlobChunk(ctx context.Context, name, uuid string, chunk []byte) (int64, error)
25+ AppendBlobChunk(ctx context.Context, name, uuid string, data io.Reader) (int64, error)
2626 FinishUpload(ctx context.Context, uuid, digest string) error
2727 CancelUpload(ctx context.Context, uuid string) error
2828
@@ -22,7 +22,7 @@ type RegistryService interface {
22 22
23 // Upload operations23 // Upload operations
24 StartUpload(ctx context.Context, name string) (string, error)24 StartUpload(ctx context.Context, name string) (string, error)
25- AppendBlobChunk(ctx context.Context, name, uuid string, chunk []byte) (int64, error)25+ AppendBlobChunk(ctx context.Context, name, uuid string, data io.Reader) (int64, error)
26 FinishUpload(ctx context.Context, uuid, digest string) error26 FinishUpload(ctx context.Context, uuid, digest string) error
27 CancelUpload(ctx context.Context, uuid string) error27 CancelUpload(ctx context.Context, uuid string) error
28 28
M internal/boundaries/out/mocks/mock_blob_storage.go +15 -15
@@ -38,8 +38,8 @@ func (_m *MockBlobStorage) EXPECT() *MockBlobStorage_Expecter {
3838 }
3939
4040 // AppendBlobChunk provides a mock function for the type MockBlobStorage
41-func (_mock *MockBlobStorage) AppendBlobChunk(name string, uuid string, chunk []byte) (int64, error) {
42- ret := _mock.Called(name, uuid, chunk)
41+func (_mock *MockBlobStorage) AppendBlobChunk(name string, uuid string, data io.Reader) (int64, error) {
42+ ret := _mock.Called(name, uuid, data)
4343
4444 if len(ret) == 0 {
4545 panic("no return value specified for AppendBlobChunk")
@@ -47,16 +47,16 @@ func (_mock *MockBlobStorage) AppendBlobChunk(name string, uuid string, chunk []
4747
4848 var r0 int64
4949 var r1 error
50- if returnFunc, ok := ret.Get(0).(func(string, string, []byte) (int64, error)); ok {
51- return returnFunc(name, uuid, chunk)
50+ if returnFunc, ok := ret.Get(0).(func(string, string, io.Reader) (int64, error)); ok {
51+ return returnFunc(name, uuid, data)
5252 }
53- if returnFunc, ok := ret.Get(0).(func(string, string, []byte) int64); ok {
54- r0 = returnFunc(name, uuid, chunk)
53+ if returnFunc, ok := ret.Get(0).(func(string, string, io.Reader) int64); ok {
54+ r0 = returnFunc(name, uuid, data)
5555 } else {
5656 r0 = ret.Get(0).(int64)
5757 }
58- if returnFunc, ok := ret.Get(1).(func(string, string, []byte) error); ok {
59- r1 = returnFunc(name, uuid, chunk)
58+ if returnFunc, ok := ret.Get(1).(func(string, string, io.Reader) error); ok {
59+ r1 = returnFunc(name, uuid, data)
6060 } else {
6161 r1 = ret.Error(1)
6262 }
@@ -71,12 +71,12 @@ type MockBlobStorage_AppendBlobChunk_Call struct {
7171 // AppendBlobChunk is a helper method to define mock.On call
7272 // - name string
7373 // - uuid string
74-// - chunk []byte
75-func (_e *MockBlobStorage_Expecter) AppendBlobChunk(name interface{}, uuid interface{}, chunk interface{}) *MockBlobStorage_AppendBlobChunk_Call {
76- return &MockBlobStorage_AppendBlobChunk_Call{Call: _e.mock.On("AppendBlobChunk", name, uuid, chunk)}
74+// - data io.Reader
75+func (_e *MockBlobStorage_Expecter) AppendBlobChunk(name interface{}, uuid interface{}, data interface{}) *MockBlobStorage_AppendBlobChunk_Call {
76+ return &MockBlobStorage_AppendBlobChunk_Call{Call: _e.mock.On("AppendBlobChunk", name, uuid, data)}
7777 }
7878
79-func (_c *MockBlobStorage_AppendBlobChunk_Call) Run(run func(name string, uuid string, chunk []byte)) *MockBlobStorage_AppendBlobChunk_Call {
79+func (_c *MockBlobStorage_AppendBlobChunk_Call) Run(run func(name string, uuid string, data io.Reader)) *MockBlobStorage_AppendBlobChunk_Call {
8080 _c.Call.Run(func(args mock.Arguments) {
8181 var arg0 string
8282 if args[0] != nil {
@@ -86,9 +86,9 @@ func (_c *MockBlobStorage_AppendBlobChunk_Call) Run(run func(name string, uuid s
8686 if args[1] != nil {
8787 arg1 = args[1].(string)
8888 }
89- var arg2 []byte
89+ var arg2 io.Reader
9090 if args[2] != nil {
91- arg2 = args[2].([]byte)
91+ arg2 = args[2].(io.Reader)
9292 }
9393 run(
9494 arg0,
@@ -104,7 +104,7 @@ func (_c *MockBlobStorage_AppendBlobChunk_Call) Return(n int64, err error) *Mock
104104 return _c
105105 }
106106
107-func (_c *MockBlobStorage_AppendBlobChunk_Call) RunAndReturn(run func(name string, uuid string, chunk []byte) (int64, error)) *MockBlobStorage_AppendBlobChunk_Call {
107+func (_c *MockBlobStorage_AppendBlobChunk_Call) RunAndReturn(run func(name string, uuid string, data io.Reader) (int64, error)) *MockBlobStorage_AppendBlobChunk_Call {
108108 _c.Call.Return(run)
109109 return _c
110110 }
@@ -38,8 +38,8 @@ func (_m *MockBlobStorage) EXPECT() *MockBlobStorage_Expecter {
38 }38 }
39 39
40 // AppendBlobChunk provides a mock function for the type MockBlobStorage40 // AppendBlobChunk provides a mock function for the type MockBlobStorage
41-func (_mock *MockBlobStorage) AppendBlobChunk(name string, uuid string, chunk []byte) (int64, error) {41+func (_mock *MockBlobStorage) AppendBlobChunk(name string, uuid string, data io.Reader) (int64, error) {
42- ret := _mock.Called(name, uuid, chunk)42+ ret := _mock.Called(name, uuid, data)
43 43
44 if len(ret) == 0 {44 if len(ret) == 0 {
45 panic("no return value specified for AppendBlobChunk")45 panic("no return value specified for AppendBlobChunk")
@@ -47,16 +47,16 @@ func (_mock *MockBlobStorage) AppendBlobChunk(name string, uuid string, chunk []
47 47
48 var r0 int6448 var r0 int64
49 var r1 error49 var r1 error
50- if returnFunc, ok := ret.Get(0).(func(string, string, []byte) (int64, error)); ok {50+ if returnFunc, ok := ret.Get(0).(func(string, string, io.Reader) (int64, error)); ok {
51- return returnFunc(name, uuid, chunk)51+ return returnFunc(name, uuid, data)
52 }52 }
53- if returnFunc, ok := ret.Get(0).(func(string, string, []byte) int64); ok {53+ if returnFunc, ok := ret.Get(0).(func(string, string, io.Reader) int64); ok {
54- r0 = returnFunc(name, uuid, chunk)54+ r0 = returnFunc(name, uuid, data)
55 } else {55 } else {
56 r0 = ret.Get(0).(int64)56 r0 = ret.Get(0).(int64)
57 }57 }
58- if returnFunc, ok := ret.Get(1).(func(string, string, []byte) error); ok {58+ if returnFunc, ok := ret.Get(1).(func(string, string, io.Reader) error); ok {
59- r1 = returnFunc(name, uuid, chunk)59+ r1 = returnFunc(name, uuid, data)
60 } else {60 } else {
61 r1 = ret.Error(1)61 r1 = ret.Error(1)
62 }62 }
@@ -71,12 +71,12 @@ type MockBlobStorage_AppendBlobChunk_Call struct {
71 // AppendBlobChunk is a helper method to define mock.On call71 // AppendBlobChunk is a helper method to define mock.On call
72 // - name string72 // - name string
73 // - uuid string73 // - uuid string
74-// - chunk []byte74+// - data io.Reader
75-func (_e *MockBlobStorage_Expecter) AppendBlobChunk(name interface{}, uuid interface{}, chunk interface{}) *MockBlobStorage_AppendBlobChunk_Call {75+func (_e *MockBlobStorage_Expecter) AppendBlobChunk(name interface{}, uuid interface{}, data interface{}) *MockBlobStorage_AppendBlobChunk_Call {
76- return &MockBlobStorage_AppendBlobChunk_Call{Call: _e.mock.On("AppendBlobChunk", name, uuid, chunk)}76+ return &MockBlobStorage_AppendBlobChunk_Call{Call: _e.mock.On("AppendBlobChunk", name, uuid, data)}
77 }77 }
78 78
79-func (_c *MockBlobStorage_AppendBlobChunk_Call) Run(run func(name string, uuid string, chunk []byte)) *MockBlobStorage_AppendBlobChunk_Call {79+func (_c *MockBlobStorage_AppendBlobChunk_Call) Run(run func(name string, uuid string, data io.Reader)) *MockBlobStorage_AppendBlobChunk_Call {
80 _c.Call.Run(func(args mock.Arguments) {80 _c.Call.Run(func(args mock.Arguments) {
81 var arg0 string81 var arg0 string
82 if args[0] != nil {82 if args[0] != nil {
@@ -86,9 +86,9 @@ func (_c *MockBlobStorage_AppendBlobChunk_Call) Run(run func(name string, uuid s
86 if args[1] != nil {86 if args[1] != nil {
87 arg1 = args[1].(string)87 arg1 = args[1].(string)
88 }88 }
89- var arg2 []byte89+ var arg2 io.Reader
90 if args[2] != nil {90 if args[2] != nil {
91- arg2 = args[2].([]byte)91+ arg2 = args[2].(io.Reader)
92 }92 }
93 run(93 run(
94 arg0,94 arg0,
@@ -104,7 +104,7 @@ func (_c *MockBlobStorage_AppendBlobChunk_Call) Return(n int64, err error) *Mock
104 return _c104 return _c
105 }105 }
106 106
107-func (_c *MockBlobStorage_AppendBlobChunk_Call) RunAndReturn(run func(name string, uuid string, chunk []byte) (int64, error)) *MockBlobStorage_AppendBlobChunk_Call {107+func (_c *MockBlobStorage_AppendBlobChunk_Call) RunAndReturn(run func(name string, uuid string, data io.Reader) (int64, error)) *MockBlobStorage_AppendBlobChunk_Call {
108 _c.Call.Return(run)108 _c.Call.Return(run)
109 return _c109 return _c
110 }110 }
M internal/boundaries/out/storage.go +1 -1
@@ -25,7 +25,7 @@ type BlobStorage interface {
2525 StartBlobUpload(name string) (string, error)
2626
2727 // AppendBlobChunk appends data to an in-progress upload.
28- AppendBlobChunk(name, uuid string, chunk []byte) (int64, error)
28+ AppendBlobChunk(name, uuid string, data io.Reader) (int64, error)
2929
3030 // GetBlobUpload returns a writer for the upload.
3131 GetBlobUpload(uuid string) (io.WriteCloser, error)
@@ -25,7 +25,7 @@ type BlobStorage interface {
25 StartBlobUpload(name string) (string, error)25 StartBlobUpload(name string) (string, error)
26 26
27 // AppendBlobChunk appends data to an in-progress upload.27 // AppendBlobChunk appends data to an in-progress upload.
28- AppendBlobChunk(name, uuid string, chunk []byte) (int64, error)28+ AppendBlobChunk(name, uuid string, data io.Reader) (int64, error)
29 29
30 // GetBlobUpload returns a writer for the upload.30 // GetBlobUpload returns a writer for the upload.
31 GetBlobUpload(uuid string) (io.WriteCloser, error)31 GetBlobUpload(uuid string) (io.WriteCloser, error)
M internal/usecase/registry/service.go +2 -3
@@ -184,17 +184,16 @@ func (s *Service) StartUpload(ctx context.Context, name string) (string, error)
184184 }
185185
186186 // AppendBlobChunk appends data to an in-progress blob upload.
187-func (s *Service) AppendBlobChunk(ctx context.Context, name, uuid string, chunk []byte) (int64, error) {
187+func (s *Service) AppendBlobChunk(ctx context.Context, name, uuid string, data io.Reader) (int64, error) {
188188 ctx = zerowrap.CtxWithFields(ctx, map[string]any{
189189 zerowrap.FieldLayer: "usecase",
190190 zerowrap.FieldUseCase: "AppendBlobChunk",
191191 "name": name,
192192 "uuid": uuid,
193- zerowrap.FieldSize: len(chunk),
194193 })
195194 log := zerowrap.FromCtx(ctx)
196195
197- length, err := s.blobStorage.AppendBlobChunk(name, uuid, chunk)
196+ length, err := s.blobStorage.AppendBlobChunk(name, uuid, data)
198197 if err != nil {
199198 return 0, log.WrapErr(err, "failed to append blob chunk")
200199 }
@@ -184,17 +184,16 @@ func (s *Service) StartUpload(ctx context.Context, name string) (string, error)
184 }184 }
185 185
186 // AppendBlobChunk appends data to an in-progress blob upload.186 // AppendBlobChunk appends data to an in-progress blob upload.
187-func (s *Service) AppendBlobChunk(ctx context.Context, name, uuid string, chunk []byte) (int64, error) {187+func (s *Service) AppendBlobChunk(ctx context.Context, name, uuid string, data io.Reader) (int64, error) {
188 ctx = zerowrap.CtxWithFields(ctx, map[string]any{188 ctx = zerowrap.CtxWithFields(ctx, map[string]any{
189 zerowrap.FieldLayer: "usecase",189 zerowrap.FieldLayer: "usecase",
190 zerowrap.FieldUseCase: "AppendBlobChunk",190 zerowrap.FieldUseCase: "AppendBlobChunk",
191 "name": name,191 "name": name,
192 "uuid": uuid,192 "uuid": uuid,
193- zerowrap.FieldSize: len(chunk),
194 })193 })
195 log := zerowrap.FromCtx(ctx)194 log := zerowrap.FromCtx(ctx)
196 195
197- length, err := s.blobStorage.AppendBlobChunk(name, uuid, chunk)196+ length, err := s.blobStorage.AppendBlobChunk(name, uuid, data)
198 if err != nil {197 if err != nil {
199 return 0, log.WrapErr(err, "failed to append blob chunk")198 return 0, log.WrapErr(err, "failed to append blob chunk")
200 }199 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close