A container registry that uses the AT Protocol for manifest storage and S3 for blob storage.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

clean up logging, consolidate presigned handlers

+122 -333
+3 -3
cmd/hold/main.go
··· 35 35 mux := http.NewServeMux() 36 36 mux.HandleFunc("/health", service.HealthHandler) 37 37 mux.HandleFunc("/register", service.HandleRegister) 38 - mux.HandleFunc("/get-presigned-url", service.HandleGetPresignedURL) 39 - mux.HandleFunc("/head-presigned-url", service.HandleHeadPresignedURL) 40 - mux.HandleFunc("/put-presigned-url", service.HandlePutPresignedURL) 38 + mux.HandleFunc("/get-presigned-url", service.HandlePresignedURL(hold.OperationGet)) 39 + mux.HandleFunc("/head-presigned-url", service.HandlePresignedURL(hold.OperationHead)) 40 + mux.HandleFunc("/put-presigned-url", service.HandlePresignedURL(hold.OperationPut)) 41 41 mux.HandleFunc("/move", service.HandleMove) 42 42 43 43 // Multipart upload endpoints
+3 -3
docs/PRESIGNED_UPLOADS.md
··· 297 297 // Clear buffer to free memory 298 298 w.buffer = nil 299 299 300 - fmt.Printf("❌ [ProxyBlobWriter.Cancel] Upload cancelled: id=%s\n", w.id) 300 + fmt.Printf("[ProxyBlobWriter.Cancel] Upload cancelled: id=%s\n", w.id) 301 301 return nil 302 302 } 303 303 ``` ··· 318 318 ```go 319 319 url, err := req.Presign(15 * time.Minute) 320 320 if err != nil { 321 - log.Printf("❌ Failed to generate presigned upload URL: %v", err) 321 + log.Printf("Failed to generate presigned upload URL: %v", err) 322 322 return s.getProxyUploadURL(digest, did), nil 323 323 } 324 324 ··· 442 442 443 443 url, err := req.Presign(15 * time.Minute) 444 444 if err != nil { 445 - log.Printf("❌ [getHeadURL] Presign failed: %v", err) 445 + log.Printf("[getHeadURL] Presign failed: %v", err) 446 446 // Fallback to proxy URL 447 447 return s.getProxyHeadURL(digest), nil 448 448 }
+48 -154
pkg/hold/handlers.go
··· 12 12 "atcr.io/pkg/atproto" 13 13 ) 14 14 15 - // HandleGetPresignedURL handles requests for download URLs 16 - func (s *HoldService) HandleGetPresignedURL(w http.ResponseWriter, r *http.Request) { 17 - if r.Method != http.MethodPost { 18 - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) 19 - return 20 - } 21 - 22 - var req GetPresignedURLRequest 23 - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 24 - http.Error(w, fmt.Sprintf("invalid request: %v", err), http.StatusBadRequest) 25 - return 26 - } 27 - 28 - log.Printf("📨 [HandleGetPresignedURL] Request received:") 29 - log.Printf(" DID: %s", req.DID) 30 - log.Printf(" Digest: %s", req.Digest) 31 - log.Printf(" Remote: %s", r.RemoteAddr) 32 - log.Printf(" s3Client nil? %v", s.s3Client == nil) 33 - 34 - // Validate DID authorization for READ 35 - if !s.isAuthorizedRead(req.DID) { 36 - log.Printf("❌ [HandleGetPresignedURL] Authorization FAILED") 37 - if req.DID == "" { 38 - // Anonymous request to private hold 39 - http.Error(w, "unauthorized: authentication required", http.StatusUnauthorized) 40 - } else { 41 - // Authenticated but not authorized 42 - http.Error(w, "forbidden: access denied", http.StatusForbidden) 15 + // HandlePresignedURL returns an HTTP handler for presigned URL requests (GET, HEAD, or PUT) 16 + // This consolidates the three separate handlers into a single parameterized implementation 17 + func (s *HoldService) HandlePresignedURL(operation PresignedURLOperation) http.HandlerFunc { 18 + return func(w http.ResponseWriter, r *http.Request) { 19 + if r.Method != http.MethodPost { 20 + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) 21 + return 43 22 } 44 - return 45 - } 46 23 47 - // Generate presigned URL (15 minute expiry) 48 - ctx := context.Background() 49 - expiry := time.Now().Add(15 * time.Minute) 50 - 51 - // For now, construct direct URL to blob 52 - // In production, this would use driver-specific presigned URLs 53 - url, err := s.getDownloadURL(ctx, req.Digest, req.DID) 54 - if err != nil { 55 - log.Printf("❌ [HandleGetPresignedURL] getDownloadURL failed: %v", err) 56 - http.Error(w, fmt.Sprintf("failed to generate URL: %v", err), http.StatusInternalServerError) 57 - return 58 - } 59 - 60 - log.Printf("✅ [HandleGetPresignedURL] Returning URL to client") 61 - 62 - resp := GetPresignedURLResponse{ 63 - URL: url, 64 - ExpiresAt: expiry, 65 - } 66 - 67 - w.Header().Set("Content-Type", "application/json") 68 - json.NewEncoder(w).Encode(resp) 69 - } 70 - 71 - // HandleHeadPresignedURL handles requests for HEAD URLs 72 - func (s *HoldService) HandleHeadPresignedURL(w http.ResponseWriter, r *http.Request) { 73 - if r.Method != http.MethodPost { 74 - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) 75 - return 76 - } 77 - 78 - var req HeadPresignedURLRequest 79 - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 80 - http.Error(w, fmt.Sprintf("invalid request: %v", err), http.StatusBadRequest) 81 - return 82 - } 83 - 84 - log.Printf("📨 [HandleHeadPresignedURL] Request received:") 85 - log.Printf(" DID: %s", req.DID) 86 - log.Printf(" Digest: %s", req.Digest) 87 - log.Printf(" Remote: %s", r.RemoteAddr) 88 - 89 - // Validate DID authorization for READ 90 - if !s.isAuthorizedRead(req.DID) { 91 - log.Printf("❌ [HandleHeadPresignedURL] Authorization FAILED") 92 - if req.DID == "" { 93 - // Anonymous request to private hold 94 - http.Error(w, "unauthorized: authentication required", http.StatusUnauthorized) 95 - } else { 96 - // Authenticated but not authorized 97 - http.Error(w, "forbidden: access denied", http.StatusForbidden) 24 + var req PresignedURLRequest 25 + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 26 + http.Error(w, fmt.Sprintf("invalid request: %v", err), http.StatusBadRequest) 27 + return 98 28 } 99 - return 100 - } 101 29 102 - // Generate presigned HEAD URL (15 minute expiry) 103 - ctx := context.Background() 104 - expiry := time.Now().Add(15 * time.Minute) 105 - 106 - url, err := s.getHeadURL(ctx, req.Digest, req.DID) 107 - if err != nil { 108 - log.Printf("❌ [HandleHeadPresignedURL] getHeadURL failed: %v", err) 109 - http.Error(w, fmt.Sprintf("failed to generate URL: %v", err), http.StatusInternalServerError) 110 - return 111 - } 30 + // Validate DID authorization based on operation type 31 + var authorized bool 32 + switch operation { 33 + case OperationGet, OperationHead: 34 + authorized = s.isAuthorizedRead(req.DID) 35 + case OperationPut: 36 + authorized = s.isAuthorizedWrite(req.DID) 37 + default: 38 + http.Error(w, "unsupported operation", http.StatusBadRequest) 39 + return 40 + } 112 41 113 - log.Printf("✅ [HandleHeadPresignedURL] Returning URL to client") 42 + if !authorized { 43 + log.Printf("[HandlePresignedURL:%s] Authorization FAILED", operation) 44 + if req.DID == "" { 45 + http.Error(w, "unauthorized: authentication required", http.StatusUnauthorized) 46 + } else { 47 + http.Error(w, "forbidden: access denied", http.StatusForbidden) 48 + } 49 + return 50 + } 114 51 115 - resp := HeadPresignedURLResponse{ 116 - URL: url, 117 - ExpiresAt: expiry, 118 - } 52 + // Generate presigned URL (15 minute expiry) 53 + ctx := context.Background() 54 + expiry := time.Now().Add(15 * time.Minute) 119 55 120 - w.Header().Set("Content-Type", "application/json") 121 - json.NewEncoder(w).Encode(resp) 122 - } 56 + url, err := s.getPresignedURL(ctx, operation, req.Digest, req.DID) 57 + if err != nil { 58 + log.Printf("[HandlePresignedURL:%s] getPresignedURL failed: %v", operation, err) 59 + http.Error(w, fmt.Sprintf("failed to generate URL: %v", err), http.StatusInternalServerError) 60 + return 61 + } 123 62 124 - // HandlePutPresignedURL handles requests for upload URLs 125 - func (s *HoldService) HandlePutPresignedURL(w http.ResponseWriter, r *http.Request) { 126 - if r.Method != http.MethodPost { 127 - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) 128 - return 129 - } 63 + log.Printf("[HandlePresignedURL:%s] Returning URL to client", operation) 130 64 131 - var req PutPresignedURLRequest 132 - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 133 - http.Error(w, fmt.Sprintf("invalid request: %v", err), http.StatusBadRequest) 134 - return 135 - } 136 - 137 - // Validate DID authorization for WRITE 138 - if !s.isAuthorizedWrite(req.DID) { 139 - if req.DID == "" { 140 - // Anonymous write attempt 141 - http.Error(w, "unauthorized: authentication required", http.StatusUnauthorized) 142 - } else { 143 - // Authenticated but not crew/owner 144 - http.Error(w, "forbidden: write access denied", http.StatusForbidden) 65 + resp := PresignedURLResponse{ 66 + URL: url, 67 + ExpiresAt: expiry, 145 68 } 146 - return 147 - } 148 - 149 - // Generate presigned upload URL (15 minute expiry) 150 - ctx := context.Background() 151 - expiry := time.Now().Add(15 * time.Minute) 152 - 153 - url, err := s.getUploadURL(ctx, req.Digest, req.Size, req.DID) 154 - if err != nil { 155 - http.Error(w, fmt.Sprintf("failed to generate URL: %v", err), http.StatusInternalServerError) 156 - return 157 - } 158 69 159 - resp := PutPresignedURLResponse{ 160 - URL: url, 161 - ExpiresAt: expiry, 70 + w.Header().Set("Content-Type", "application/json") 71 + json.NewEncoder(w).Encode(resp) 162 72 } 163 - 164 - w.Header().Set("Content-Type", "application/json") 165 - json.NewEncoder(w).Encode(resp) 166 73 } 167 74 168 75 // HandleProxyGet proxies a blob download through the service ··· 179 86 return 180 87 } 181 88 182 - log.Printf("📥 [HandleProxyGet] Blob download request:") 183 - log.Printf(" Method: %s", r.Method) 184 - log.Printf(" Digest: %s", digest) 185 - log.Printf(" Remote: %s", r.RemoteAddr) 186 - 187 89 // Get DID from query param or header 188 90 did := r.URL.Query().Get("did") 189 91 if did == "" { ··· 193 95 194 96 // Authorize READ access 195 97 if !s.isAuthorizedRead(did) { 196 - log.Printf("❌ [HandleProxyGet] Authorization FAILED") 98 + log.Printf("[HandleProxyGet] Authorization FAILED") 197 99 if did == "" { 198 100 http.Error(w, "unauthorized: authentication required", http.StatusUnauthorized) 199 101 } else { ··· 201 103 } 202 104 return 203 105 } 204 - log.Printf("✅ [HandleProxyGet] Authorization SUCCESS") 205 106 206 107 ctx := r.Context() 207 108 path := blobPath(digest) ··· 290 191 did = r.Header.Get("X-ATCR-DID") 291 192 } 292 193 293 - log.Printf("🔐 [HandleProxyPut] Authorization check:") 294 - log.Printf(" Path: %s", digest) 295 - log.Printf(" DID: %s", did) 296 - log.Printf(" Owner DID: %s", s.config.Registration.OwnerDID) 297 - 298 194 // Authorize WRITE access 299 195 if !s.isAuthorizedWrite(did) { 300 - log.Printf("❌ [HandleProxyPut] Authorization FAILED") 196 + log.Printf("[HandleProxyPut] Authorization FAILED") 301 197 if did == "" { 302 198 http.Error(w, "unauthorized: authentication required", http.StatusUnauthorized) 303 199 } else { ··· 305 201 } 306 202 return 307 203 } 308 - 309 - log.Printf("✅ [HandleProxyPut] Authorization SUCCESS") 310 204 311 205 // Stream blob to storage (no buffering) 312 206 ctx := r.Context()
+6
pkg/hold/s3.go
··· 4 4 "context" 5 5 "fmt" 6 6 "log" 7 + "sort" 7 8 "strings" 8 9 "time" 9 10 ··· 157 158 if s.s3PathPrefix != "" { 158 159 s3Key = s.s3PathPrefix + "/" + s3Key 159 160 } 161 + 162 + // Sort parts by part number (S3 requires ascending order) 163 + sort.Slice(parts, func(i, j int) bool { 164 + return parts[i].PartNumber < parts[j].PartNumber 165 + }) 160 166 161 167 // Convert to S3 CompletedPart format 162 168 // IMPORTANT: S3 requires ETags to be quoted in the CompleteMultipartUpload XML
+40 -118
pkg/hold/storage.go
··· 39 39 return fmt.Sprintf("/docker/registry/v2/blobs/%s/%s/%s/data", algorithm, hash[:2], hash) 40 40 } 41 41 42 - // getDownloadURL generates a download URL for a blob 43 - func (s *HoldService) getDownloadURL(ctx context.Context, digest string, did string) (string, error) { 44 - // Check if blob exists 42 + // getPresignedURL generates a presigned URL for GET, HEAD, or PUT operations 43 + func (s *HoldService) getPresignedURL(ctx context.Context, operation PresignedURLOperation, digest string, did string) (string, error) { 45 44 path := blobPath(digest) 46 - _, err := s.driver.Stat(ctx, path) 47 - if err != nil { 48 - return "", fmt.Errorf("blob not found: %w", err) 49 - } 50 45 51 - // Check if presigned URLs are disabled for testing 52 - if s.config.Server.DisablePresignedURLs { 53 - log.Printf("Presigned URLs disabled, using proxy URL") 54 - return s.getProxyDownloadURL(digest, did), nil 55 - } 56 - 57 - // If S3 client available, generate presigned URL 58 - if s.s3Client != nil { 59 - // Build S3 key from blob path 60 - // blobPath returns paths like: /docker/registry/v2/blobs/sha256/ab/abc123.../data 61 - s3Key := strings.TrimPrefix(path, "/") 62 - if s.s3PathPrefix != "" { 63 - s3Key = s.s3PathPrefix + "/" + s3Key 46 + // Check blob exists for GET/HEAD operations (not for PUT since blob doesn't exist yet) 47 + if operation == OperationGet || operation == OperationHead { 48 + if _, err := s.driver.Stat(ctx, path); err != nil { 49 + return "", fmt.Errorf("blob not found: %w", err) 64 50 } 65 - 66 - // Generate presigned GET URL 67 - // Note: Don't use ResponseContentType - not supported by all S3-compatible services 68 - req, _ := s.s3Client.GetObjectRequest(&s3.GetObjectInput{ 69 - Bucket: aws.String(s.bucket), 70 - Key: aws.String(s3Key), 71 - }) 72 - 73 - log.Printf("🔍 [getDownloadURL] Before Presign:") 74 - log.Printf(" Digest: %s", digest) 75 - log.Printf(" S3 Key: %s", s3Key) 76 - log.Printf(" Bucket: %s", s.bucket) 77 - log.Printf(" Request Operation: %s", req.Operation.Name) 78 - log.Printf(" Request HTTPMethod: %s", req.Operation.HTTPMethod) 79 - 80 - url, err := req.Presign(15 * time.Minute) 81 - if err != nil { 82 - log.Printf("❌ [getDownloadURL] Presign FAILED: %v", err) 83 - log.Printf(" Falling back to proxy URL") 84 - return s.getProxyDownloadURL(digest, did), nil 85 - } 86 - 87 - log.Printf("✅ [getDownloadURL] Presigned URL generated successfully") 88 - log.Printf(" URL: %s", url) 89 - log.Printf(" URL Length: %d chars", len(url)) 90 - log.Printf(" Expires: 15min") 91 - 92 - return url, nil 93 51 } 94 52 95 - // Fallback: return proxy URL through this service 96 - return s.getProxyDownloadURL(digest, did), nil 97 - } 98 - 99 - // getHeadURL generates a HEAD URL for a blob 100 - func (s *HoldService) getHeadURL(ctx context.Context, digest string, did string) (string, error) { 101 - // Check if blob exists 102 - path := blobPath(digest) 103 - _, err := s.driver.Stat(ctx, path) 104 - if err != nil { 105 - return "", fmt.Errorf("blob not found: %w", err) 106 - } 107 - 108 - // Check if presigned URLs are disabled for testing 53 + // Check if presigned URLs are disabled 109 54 if s.config.Server.DisablePresignedURLs { 110 55 log.Printf("Presigned URLs disabled, using proxy URL") 111 - return s.getProxyDownloadURL(digest, did), nil 56 + return s.getProxyURL(digest, did), nil 112 57 } 113 58 114 - // If S3 client available, generate presigned HEAD URL 59 + // Generate presigned URL if S3 client is available 115 60 if s.s3Client != nil { 116 61 // Build S3 key from blob path 117 62 s3Key := strings.TrimPrefix(path, "/") ··· 119 64 s3Key = s.s3PathPrefix + "/" + s3Key 120 65 } 121 66 122 - // Generate presigned HEAD URL 123 - req, _ := s.s3Client.HeadObjectRequest(&s3.HeadObjectInput{ 124 - Bucket: aws.String(s.bucket), 125 - Key: aws.String(s3Key), 126 - }) 127 - 128 - url, err := req.Presign(15 * time.Minute) 129 - if err != nil { 130 - log.Printf("❌ [getHeadURL] Presign FAILED: %v", err) 131 - log.Printf(" Falling back to proxy URL") 132 - return s.getProxyDownloadURL(digest, did), nil 67 + // Create appropriate S3 request based on operation 68 + var req interface { 69 + Presign(time.Duration) (string, error) 133 70 } 134 - 135 - log.Printf("✅ [getHeadURL] Presigned HEAD URL generated: digest=%s", digest) 136 - return url, nil 137 - } 71 + switch operation { 72 + case OperationGet: 73 + // Note: Don't use ResponseContentType - not supported by all S3-compatible services 74 + req, _ = s.s3Client.GetObjectRequest(&s3.GetObjectInput{ 75 + Bucket: aws.String(s.bucket), 76 + Key: aws.String(s3Key), 77 + }) 138 78 139 - // Fallback: return proxy URL through this service 140 - return s.getProxyDownloadURL(digest, did), nil 141 - } 79 + case OperationHead: 80 + req, _ = s.s3Client.HeadObjectRequest(&s3.HeadObjectInput{ 81 + Bucket: aws.String(s.bucket), 82 + Key: aws.String(s3Key), 83 + }) 142 84 143 - // getProxyDownloadURL returns a proxy URL for blob download (fallback when presigned URLs unavailable) 144 - func (s *HoldService) getProxyDownloadURL(digest, did string) string { 145 - return fmt.Sprintf("%s/blobs/%s?did=%s", s.config.Server.PublicURL, digest, did) 146 - } 85 + case OperationPut: 86 + req, _ = s.s3Client.PutObjectRequest(&s3.PutObjectInput{ 87 + Bucket: aws.String(s.bucket), 88 + Key: aws.String(s3Key), 89 + ContentType: aws.String("application/octet-stream"), 90 + }) 147 91 148 - // getUploadURL generates an upload URL for a blob 149 - // Note: This is called from HandlePutPresignedURL which has the DID in the request 150 - func (s *HoldService) getUploadURL(ctx context.Context, digest string, size int64, did string) (string, error) { 151 - // Check if presigned URLs are disabled for testing 152 - if s.config.Server.DisablePresignedURLs { 153 - log.Printf("Presigned URLs disabled, using proxy URL") 154 - return s.getProxyUploadURL(digest, did), nil 155 - } 156 - 157 - // If S3 client available, generate presigned URL 158 - if s.s3Client != nil { 159 - // Build S3 key from blob path 160 - path := blobPath(digest) 161 - s3Key := strings.TrimPrefix(path, "/") 162 - if s.s3PathPrefix != "" { 163 - s3Key = s.s3PathPrefix + "/" + s3Key 92 + default: 93 + return "", fmt.Errorf("unsupported operation: %s", operation) 164 94 } 165 95 166 - // Generate presigned PUT URL with Content-Type in signature 167 - req, _ := s.s3Client.PutObjectRequest(&s3.PutObjectInput{ 168 - Bucket: aws.String(s.bucket), 169 - Key: aws.String(s3Key), 170 - ContentType: aws.String("application/octet-stream"), 171 - }) 172 - 96 + // Generate presigned URL with 15 minute expiry 173 97 url, err := req.Presign(15 * time.Minute) 174 98 if err != nil { 175 - log.Printf("WARN: Presigned URL generation failed for %s, falling back to proxy: %v", digest, err) 176 - return s.getProxyUploadURL(digest, did), nil 99 + log.Printf("[getPresignedURL] Presign FAILED for %s: %v", operation, err) 100 + log.Printf(" Falling back to proxy URL") 101 + return s.getProxyURL(digest, did), nil 177 102 } 178 103 179 - log.Printf("🔑 Generated presigned upload URL for %s (expires in 15min)", digest) 180 - log.Printf(" S3 Key: %s", s3Key) 181 - log.Printf(" Bucket: %s", s.bucket) 182 - log.Printf(" Size: %d bytes", size) 183 104 return url, nil 184 105 } 185 106 186 107 // Fallback: return proxy URL through this service 187 - return s.getProxyUploadURL(digest, did), nil 108 + return s.getProxyURL(digest, did), nil 188 109 } 189 110 190 - // getProxyUploadURL returns a proxy URL for blob upload (fallback when presigned URLs unavailable) 191 - func (s *HoldService) getProxyUploadURL(digest, did string) string { 111 + // getProxyURL returns a proxy URL for blob operations (fallback when presigned URLs unavailable) 112 + func (s *HoldService) getProxyURL(digest, did string) string { 113 + // All operations use the same proxy endpoint 192 114 return fmt.Sprintf("%s/blobs/%s?did=%s", s.config.Server.PublicURL, digest, did) 193 115 }
+12 -27
pkg/hold/types.go
··· 4 4 "time" 5 5 ) 6 6 7 - // GetPresignedURLRequest represents a request for a presigned download URL 8 - type GetPresignedURLRequest struct { 9 - DID string `json:"did"` 10 - Digest string `json:"digest"` 11 - } 7 + // PresignedURLOperation defines the type of presigned URL operation 8 + type PresignedURLOperation string 12 9 13 - // GetPresignedURLResponse contains the presigned URL 14 - type GetPresignedURLResponse struct { 15 - URL string `json:"url"` 16 - ExpiresAt time.Time `json:"expires_at"` 17 - } 10 + const ( 11 + OperationGet PresignedURLOperation = "GET" 12 + OperationHead PresignedURLOperation = "HEAD" 13 + OperationPut PresignedURLOperation = "PUT" 14 + ) 18 15 19 - // HeadPresignedURLRequest represents a request for a presigned HEAD URL 20 - type HeadPresignedURLRequest struct { 16 + // PresignedURLRequest represents a request for a presigned URL (GET, HEAD, or PUT) 17 + type PresignedURLRequest struct { 21 18 DID string `json:"did"` 22 19 Digest string `json:"digest"` 23 - } 24 - 25 - // HeadPresignedURLResponse contains the presigned HEAD URL 26 - type HeadPresignedURLResponse struct { 27 - URL string `json:"url"` 28 - ExpiresAt time.Time `json:"expires_at"` 20 + Size int64 `json:"size,omitempty"` // Only required for PUT operations 29 21 } 30 22 31 - // PutPresignedURLRequest represents a request for a presigned upload URL 32 - type PutPresignedURLRequest struct { 33 - DID string `json:"did"` 34 - Digest string `json:"digest"` 35 - Size int64 `json:"size"` 36 - } 37 - 38 - // PutPresignedURLResponse contains the presigned upload URL 39 - type PutPresignedURLResponse struct { 23 + // PresignedURLResponse contains the presigned URL 24 + type PresignedURLResponse struct { 40 25 URL string `json:"url"` 41 26 ExpiresAt time.Time `json:"expires_at"` 42 27 }
+10 -28
pkg/storage/proxy_blob_store.go
··· 147 147 // Get upload URL 148 148 url, err := p.getUploadURL(ctx, dgst, int64(len(content))) 149 149 if err != nil { 150 - fmt.Printf("❌ [proxy_blob_store/Put] Failed to get upload URL: digest=%s, error=%v\n", dgst, err) 150 + fmt.Printf("[proxy_blob_store/Put] Failed to get upload URL: digest=%s, error=%v\n", dgst, err) 151 151 return distribution.Descriptor{}, err 152 152 } 153 - 154 - fmt.Printf("📤 [proxy_blob_store/Put] Starting PUT request:\n") 155 - fmt.Printf(" Digest: %s\n", dgst) 156 - fmt.Printf(" Size: %d bytes\n", len(content)) 157 - fmt.Printf(" MediaType: %s\n", mediaType) 158 - fmt.Printf(" URL: %s\n", url) 159 - fmt.Printf(" Headers: Content-Type=application/octet-stream\n") 160 153 161 154 // Upload the blob 162 155 req, err := http.NewRequestWithContext(ctx, "PUT", url, bytes.NewReader(content)) 163 156 if err != nil { 164 - fmt.Printf("❌ [proxy_blob_store/Put] Failed to create request: %v\n", err) 157 + fmt.Printf("[proxy_blob_store/Put] Failed to create request: %v\n", err) 165 158 return distribution.Descriptor{}, err 166 159 } 167 160 req.Header.Set("Content-Type", "application/octet-stream") 168 161 169 162 resp, err := p.httpClient.Do(req) 170 163 if err != nil { 171 - fmt.Printf("❌ [proxy_blob_store/Put] HTTP request failed: %v\n", err) 164 + fmt.Printf("[proxy_blob_store/Put] HTTP request failed: %v\n", err) 172 165 return distribution.Descriptor{}, err 173 166 } 174 167 defer resp.Body.Close() 175 168 176 - fmt.Printf("📥 [proxy_blob_store/Put] Response:\n") 177 - fmt.Printf(" Status: %d %s\n", resp.StatusCode, resp.Status) 178 - 179 169 if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { 180 170 bodyBytes, _ := io.ReadAll(resp.Body) 181 171 fmt.Printf(" Error Body: %s\n", string(bodyBytes)) 182 172 return distribution.Descriptor{}, fmt.Errorf("upload failed with status %d: %s", resp.StatusCode, string(bodyBytes)) 183 173 } 184 174 185 - fmt.Printf("✅ [proxy_blob_store/Put] Upload successful: digest=%s, size=%d\n", dgst, len(content)) 175 + fmt.Printf("[proxy_blob_store/Put] Upload successful: digest=%s, size=%d\n", dgst, len(content)) 186 176 187 177 return distribution.Descriptor{ 188 178 Digest: dgst, ··· 224 214 225 215 // Create returns a blob writer for uploading using multipart upload 226 216 func (p *ProxyBlobStore) Create(ctx context.Context, options ...distribution.BlobCreateOption) (distribution.BlobWriter, error) { 227 - fmt.Printf("🔧 [proxy_blob_store/Create] Starting multipart upload\n") 228 - fmt.Printf(" Storage endpoint: %s\n", p.storageEndpoint) 229 - fmt.Printf(" Repository: %s\n", p.repository) 230 - 231 217 // Parse options 232 218 var opts distribution.CreateOptions 233 219 for _, option := range options { ··· 624 610 return fmt.Errorf("failed to get part presigned URL: %w", err) 625 611 } 626 612 627 - fmt.Printf("📤 [flushPart] Uploading part %d: size=%d bytes\n", w.partNumber, w.buffer.Len()) 628 - 629 613 // Upload part to S3 630 614 req, err := http.NewRequestWithContext(ctx, "PUT", url, bytes.NewReader(w.buffer.Bytes())) 631 615 if err != nil { ··· 655 639 ETag: etag, 656 640 }) 657 641 658 - fmt.Printf("✅ [flushPart] Part %d uploaded successfully: ETag=%s\n", w.partNumber, etag) 642 + fmt.Printf("[flushPart] Part %d uploaded successfully: ETag=%s\n", w.partNumber, etag) 659 643 660 644 // Reset buffer and increment part number 661 645 w.buffer.Reset() ··· 706 690 } 707 691 w.closed = true 708 692 709 - fmt.Printf("📝 [Commit] Starting commit: digest=%s, size=%d\n", desc.Digest, w.size) 710 - 711 693 // Remove from global uploads map 712 694 globalUploadsMu.Lock() 713 695 delete(globalUploads, w.id) ··· 715 697 716 698 // Flush any remaining buffered data 717 699 if w.buffer.Len() > 0 { 718 - fmt.Printf("📤 [Commit] Flushing final buffer: %d bytes\n", w.buffer.Len()) 700 + fmt.Printf("[Commit] Flushing final buffer: %d bytes\n", w.buffer.Len()) 719 701 if err := w.flushPart(); err != nil { 720 702 // Try to abort multipart on error 721 703 tempDigest := fmt.Sprintf("uploads/temp-%s", w.id) ··· 735 717 tempPath := fmt.Sprintf("uploads/temp-%s", w.id) 736 718 finalPath := desc.Digest.String() 737 719 738 - fmt.Printf("🚚 [Commit] Moving blob: %s → %s\n", tempPath, finalPath) 720 + fmt.Printf("[Commit] Moving blob: %s → %s\n", tempPath, finalPath) 739 721 moveURL := fmt.Sprintf("%s/move?from=%s&to=%s&did=%s", 740 722 w.store.storageEndpoint, tempPath, finalPath, w.store.did) 741 723 ··· 755 737 return distribution.Descriptor{}, fmt.Errorf("move blob failed: status %d, body: %s", resp.StatusCode, string(bodyBytes)) 756 738 } 757 739 758 - fmt.Printf("✅ [Commit] Upload completed successfully: digest=%s, size=%d, parts=%d\n", desc.Digest, w.size, len(w.parts)) 740 + fmt.Printf("[Commit] Upload completed successfully: digest=%s, size=%d, parts=%d\n", desc.Digest, w.size, len(w.parts)) 759 741 760 742 return distribution.Descriptor{ 761 743 Digest: desc.Digest, ··· 768 750 func (w *ProxyBlobWriter) Cancel(ctx context.Context) error { 769 751 w.closed = true 770 752 771 - fmt.Printf("❌ [Cancel] Cancelling upload: id=%s\n", w.id) 753 + fmt.Printf("[Cancel] Cancelling upload: id=%s\n", w.id) 772 754 773 755 // Remove from global uploads map 774 756 globalUploadsMu.Lock() ··· 782 764 // Continue anyway - we want to mark upload as cancelled 783 765 } 784 766 785 - fmt.Printf("✅ [Cancel] Upload cancelled: id=%s\n", w.id) 767 + fmt.Printf("[Cancel] Upload cancelled: id=%s\n", w.id) 786 768 return nil 787 769 } 788 770