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.

consolidate presigned url endpoint. fix manifest pull blob from pds

+95 -134
+1 -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.HandlePresignedURL(hold.OperationGet)) 39 - mux.HandleFunc("/head-presigned-url", service.HandlePresignedURL(hold.OperationHead)) 40 - mux.HandleFunc("/put-presigned-url", service.HandlePresignedURL(hold.OperationPut)) 38 + mux.HandleFunc("/presigned-url", service.HandlePresignedURL) 41 39 mux.HandleFunc("/move", service.HandleMove) 42 40 43 41 // Multipart upload endpoints
+21
pkg/atproto/client.go
··· 3 3 import ( 4 4 "bytes" 5 5 "context" 6 + "encoding/base64" 6 7 "encoding/json" 7 8 "fmt" 8 9 "io" ··· 343 344 return nil, fmt.Errorf("failed to read blob data: %w", err) 344 345 } 345 346 347 + // Check if PDS returned JSON-wrapped blob (Bluesky implementation) 348 + // PDS may wrap blobs as JSON-encoded base64 strings 349 + // Detection: Check if content starts with a quote (indicating JSON string) 350 + if len(data) > 0 && data[0] == '"' { 351 + // Blob is JSON-encoded - decode it 352 + var base64Str string 353 + if err := json.Unmarshal(data, &base64Str); err != nil { 354 + return nil, fmt.Errorf("failed to unmarshal JSON-wrapped blob: %w", err) 355 + } 356 + 357 + // Base64-decode the blob content 358 + decoded, err := base64.StdEncoding.DecodeString(base64Str) 359 + if err != nil { 360 + return nil, fmt.Errorf("failed to base64-decode blob: %w", err) 361 + } 362 + 363 + return decoded, nil 364 + } 365 + 366 + // Raw blob response (expected ATProto behavior) 346 367 return data, nil 347 368 } 348 369
+47 -49
pkg/hold/handlers.go
··· 12 12 "atcr.io/pkg/atproto" 13 13 ) 14 14 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 22 - } 15 + // HandlePresignedURL handles presigned URL requests (GET, HEAD, or PUT) 16 + // Operation type is specified in the request body 17 + func (s *HoldService) HandlePresignedURL(w http.ResponseWriter, r *http.Request) { 18 + if r.Method != http.MethodPost { 19 + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) 20 + return 21 + } 23 22 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 28 - } 23 + var req PresignedURLRequest 24 + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 25 + http.Error(w, fmt.Sprintf("invalid request: %v", err), http.StatusBadRequest) 26 + return 27 + } 29 28 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 - } 29 + // Validate DID authorization based on operation type 30 + var authorized bool 31 + switch req.Operation { 32 + case OperationGet, OperationHead: 33 + authorized = s.isAuthorizedRead(req.DID) 34 + case OperationPut: 35 + authorized = s.isAuthorizedWrite(req.DID) 36 + default: 37 + http.Error(w, "unsupported operation", http.StatusBadRequest) 38 + return 39 + } 41 40 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 41 + if !authorized { 42 + log.Printf("[HandlePresignedURL:%s] Authorization FAILED", req.Operation) 43 + if req.DID == "" { 44 + http.Error(w, "unauthorized: authentication required", http.StatusUnauthorized) 45 + } else { 46 + http.Error(w, "forbidden: access denied", http.StatusForbidden) 50 47 } 51 - 52 - // Generate presigned URL (15 minute expiry) 53 - ctx := context.Background() 54 - expiry := time.Now().Add(15 * time.Minute) 48 + return 49 + } 55 50 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 - } 51 + // Generate presigned URL (15 minute expiry) 52 + ctx := context.Background() 53 + expiry := time.Now().Add(15 * time.Minute) 62 54 63 - log.Printf("[HandlePresignedURL:%s] Returning URL to client", operation) 55 + url, err := s.getPresignedURL(ctx, req.Operation, req.Digest, req.DID) 56 + if err != nil { 57 + log.Printf("[HandlePresignedURL:%s] getPresignedURL failed: %v", req.Operation, err) 58 + http.Error(w, fmt.Sprintf("failed to generate URL: %v", err), http.StatusInternalServerError) 59 + return 60 + } 64 61 65 - resp := PresignedURLResponse{ 66 - URL: url, 67 - ExpiresAt: expiry, 68 - } 62 + log.Printf("[HandlePresignedURL:%s] Returning URL to client", req.Operation) 69 63 70 - w.Header().Set("Content-Type", "application/json") 71 - json.NewEncoder(w).Encode(resp) 64 + resp := PresignedURLResponse{ 65 + URL: url, 66 + ExpiresAt: expiry, 72 67 } 68 + 69 + w.Header().Set("Content-Type", "application/json") 70 + json.NewEncoder(w).Encode(resp) 73 71 } 74 72 75 73 // HandleProxyGet proxies a blob download through the service
+4 -3
pkg/hold/types.go
··· 15 15 16 16 // PresignedURLRequest represents a request for a presigned URL (GET, HEAD, or PUT) 17 17 type PresignedURLRequest struct { 18 - DID string `json:"did"` 19 - Digest string `json:"digest"` 20 - Size int64 `json:"size,omitempty"` // Only required for PUT operations 18 + Operation PresignedURLOperation `json:"operation"` 19 + DID string `json:"did"` 20 + Digest string `json:"digest"` 21 + Size int64 `json:"size,omitempty"` // Only required for PUT operations 21 22 } 22 23 23 24 // PresignedURLResponse contains the presigned URL
+22 -79
pkg/storage/proxy_blob_store.go
··· 270 270 return writer, nil 271 271 } 272 272 273 - // getDownloadURL requests a presigned download URL from the storage service 274 - func (p *ProxyBlobStore) getDownloadURL(ctx context.Context, dgst digest.Digest) (string, error) { 273 + // getPresignedURL requests a presigned URL from the storage service for any operation 274 + func (p *ProxyBlobStore) getPresignedURL(ctx context.Context, operation, dgst string, size int64) (string, error) { 275 275 reqBody := map[string]any{ 276 - "did": p.did, 277 - "digest": dgst.String(), 276 + "operation": operation, 277 + "did": p.did, 278 + "digest": dgst, 279 + } 280 + 281 + // Only include size for PUT operations 282 + if size > 0 { 283 + reqBody["size"] = size 278 284 } 279 285 280 286 body, err := json.Marshal(reqBody) ··· 282 288 return "", err 283 289 } 284 290 285 - url := fmt.Sprintf("%s/get-presigned-url", p.storageEndpoint) 291 + url := fmt.Sprintf("%s/presigned-url", p.storageEndpoint) 286 292 req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(body)) 287 293 if err != nil { 288 294 return "", err ··· 296 302 defer resp.Body.Close() 297 303 298 304 if resp.StatusCode != http.StatusOK { 299 - return "", fmt.Errorf("failed to get download URL: status %d", resp.StatusCode) 305 + return "", fmt.Errorf("failed to get presigned URL: status %d", resp.StatusCode) 300 306 } 301 307 302 308 var result struct { ··· 309 315 return result.URL, nil 310 316 } 311 317 318 + // getDownloadURL requests a presigned download URL from the storage service 319 + func (p *ProxyBlobStore) getDownloadURL(ctx context.Context, dgst digest.Digest) (string, error) { 320 + return p.getPresignedURL(ctx, "GET", dgst.String(), 0) 321 + } 322 + 312 323 // getHeadURL requests a presigned HEAD URL from the storage service 313 324 func (p *ProxyBlobStore) getHeadURL(ctx context.Context, dgst digest.Digest) (string, error) { 314 - reqBody := map[string]any{ 315 - "did": p.did, 316 - "digest": dgst.String(), 317 - } 318 - 319 - body, err := json.Marshal(reqBody) 320 - if err != nil { 321 - return "", err 322 - } 323 - 324 - url := fmt.Sprintf("%s/head-presigned-url", p.storageEndpoint) 325 - req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(body)) 326 - if err != nil { 327 - return "", err 328 - } 329 - req.Header.Set("Content-Type", "application/json") 330 - 331 - resp, err := p.httpClient.Do(req) 332 - if err != nil { 333 - return "", err 334 - } 335 - defer resp.Body.Close() 336 - 337 - if resp.StatusCode != http.StatusOK { 338 - return "", fmt.Errorf("failed to get HEAD URL: status %d", resp.StatusCode) 339 - } 340 - 341 - var result struct { 342 - URL string `json:"url"` 343 - } 344 - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { 345 - return "", err 346 - } 347 - 348 - return result.URL, nil 325 + return p.getPresignedURL(ctx, "HEAD", dgst.String(), 0) 349 326 } 350 327 351 328 // getUploadURL requests a presigned upload URL from the storage service 352 329 func (p *ProxyBlobStore) getUploadURL(ctx context.Context, dgst digest.Digest, size int64) (string, error) { 353 330 fmt.Printf("DEBUG [proxy_blob_store/getUploadURL]: storageEndpoint=%s, digest=%s\n", p.storageEndpoint, dgst) 354 - 355 - reqBody := map[string]any{ 356 - "did": p.did, 357 - "digest": dgst.String(), 358 - "size": size, 359 - } 360 - 361 - body, err := json.Marshal(reqBody) 362 - if err != nil { 363 - return "", err 364 - } 365 - 366 - url := fmt.Sprintf("%s/put-presigned-url", p.storageEndpoint) 367 - fmt.Printf("DEBUG [proxy_blob_store/getUploadURL]: Calling %s\n", url) 368 - req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(body)) 369 - if err != nil { 370 - return "", err 331 + url, err := p.getPresignedURL(ctx, "PUT", dgst.String(), size) 332 + if err == nil { 333 + fmt.Printf("DEBUG [proxy_blob_store/getUploadURL]: Got presigned URL=%s\n", url) 371 334 } 372 - req.Header.Set("Content-Type", "application/json") 373 - 374 - resp, err := p.httpClient.Do(req) 375 - if err != nil { 376 - return "", err 377 - } 378 - defer resp.Body.Close() 379 - 380 - if resp.StatusCode != http.StatusOK { 381 - return "", fmt.Errorf("failed to get upload URL: status %d", resp.StatusCode) 382 - } 383 - 384 - var result struct { 385 - URL string `json:"url"` 386 - } 387 - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { 388 - return "", err 389 - } 390 - 391 - fmt.Printf("DEBUG [proxy_blob_store/getUploadURL]: Got presigned URL=%s\n", result.URL) 392 - return result.URL, nil 335 + return url, err 393 336 } 394 337 395 338 // startMultipartUpload initiates a multipart upload via hold service