mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-21 01:34:16 +00:00
clean up logging, consolidate presigned handlers
This commit is contained in:
+3
-3
@@ -35,9 +35,9 @@ func main() {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/health", service.HealthHandler)
|
||||
mux.HandleFunc("/register", service.HandleRegister)
|
||||
mux.HandleFunc("/get-presigned-url", service.HandleGetPresignedURL)
|
||||
mux.HandleFunc("/head-presigned-url", service.HandleHeadPresignedURL)
|
||||
mux.HandleFunc("/put-presigned-url", service.HandlePutPresignedURL)
|
||||
mux.HandleFunc("/get-presigned-url", service.HandlePresignedURL(hold.OperationGet))
|
||||
mux.HandleFunc("/head-presigned-url", service.HandlePresignedURL(hold.OperationHead))
|
||||
mux.HandleFunc("/put-presigned-url", service.HandlePresignedURL(hold.OperationPut))
|
||||
mux.HandleFunc("/move", service.HandleMove)
|
||||
|
||||
// Multipart upload endpoints
|
||||
|
||||
@@ -297,7 +297,7 @@ func (w *ProxyBlobWriter) Cancel(ctx context.Context) error {
|
||||
// Clear buffer to free memory
|
||||
w.buffer = nil
|
||||
|
||||
fmt.Printf("❌ [ProxyBlobWriter.Cancel] Upload cancelled: id=%s\n", w.id)
|
||||
fmt.Printf("[ProxyBlobWriter.Cancel] Upload cancelled: id=%s\n", w.id)
|
||||
return nil
|
||||
}
|
||||
```
|
||||
@@ -318,7 +318,7 @@ The current `getUploadURL()` implementation in `cmd/hold/main.go` (lines 528-587
|
||||
```go
|
||||
url, err := req.Presign(15 * time.Minute)
|
||||
if err != nil {
|
||||
log.Printf("❌ Failed to generate presigned upload URL: %v", err)
|
||||
log.Printf("Failed to generate presigned upload URL: %v", err)
|
||||
return s.getProxyUploadURL(digest, did), nil
|
||||
}
|
||||
|
||||
@@ -442,7 +442,7 @@ func (s *HoldService) getHeadURL(ctx context.Context, digest string) (string, er
|
||||
|
||||
url, err := req.Presign(15 * time.Minute)
|
||||
if err != nil {
|
||||
log.Printf("❌ [getHeadURL] Presign failed: %v", err)
|
||||
log.Printf("[getHeadURL] Presign failed: %v", err)
|
||||
// Fallback to proxy URL
|
||||
return s.getProxyHeadURL(digest), nil
|
||||
}
|
||||
|
||||
+54
-160
@@ -12,157 +12,64 @@ import (
|
||||
"atcr.io/pkg/atproto"
|
||||
)
|
||||
|
||||
// HandleGetPresignedURL handles requests for download URLs
|
||||
func (s *HoldService) HandleGetPresignedURL(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req GetPresignedURLRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, fmt.Sprintf("invalid request: %v", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("📨 [HandleGetPresignedURL] Request received:")
|
||||
log.Printf(" DID: %s", req.DID)
|
||||
log.Printf(" Digest: %s", req.Digest)
|
||||
log.Printf(" Remote: %s", r.RemoteAddr)
|
||||
log.Printf(" s3Client nil? %v", s.s3Client == nil)
|
||||
|
||||
// Validate DID authorization for READ
|
||||
if !s.isAuthorizedRead(req.DID) {
|
||||
log.Printf("❌ [HandleGetPresignedURL] Authorization FAILED")
|
||||
if req.DID == "" {
|
||||
// Anonymous request to private hold
|
||||
http.Error(w, "unauthorized: authentication required", http.StatusUnauthorized)
|
||||
} else {
|
||||
// Authenticated but not authorized
|
||||
http.Error(w, "forbidden: access denied", http.StatusForbidden)
|
||||
// HandlePresignedURL returns an HTTP handler for presigned URL requests (GET, HEAD, or PUT)
|
||||
// This consolidates the three separate handlers into a single parameterized implementation
|
||||
func (s *HoldService) HandlePresignedURL(operation PresignedURLOperation) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Generate presigned URL (15 minute expiry)
|
||||
ctx := context.Background()
|
||||
expiry := time.Now().Add(15 * time.Minute)
|
||||
|
||||
// For now, construct direct URL to blob
|
||||
// In production, this would use driver-specific presigned URLs
|
||||
url, err := s.getDownloadURL(ctx, req.Digest, req.DID)
|
||||
if err != nil {
|
||||
log.Printf("❌ [HandleGetPresignedURL] getDownloadURL failed: %v", err)
|
||||
http.Error(w, fmt.Sprintf("failed to generate URL: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("✅ [HandleGetPresignedURL] Returning URL to client")
|
||||
|
||||
resp := GetPresignedURLResponse{
|
||||
URL: url,
|
||||
ExpiresAt: expiry,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}
|
||||
|
||||
// HandleHeadPresignedURL handles requests for HEAD URLs
|
||||
func (s *HoldService) HandleHeadPresignedURL(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req HeadPresignedURLRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, fmt.Sprintf("invalid request: %v", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("📨 [HandleHeadPresignedURL] Request received:")
|
||||
log.Printf(" DID: %s", req.DID)
|
||||
log.Printf(" Digest: %s", req.Digest)
|
||||
log.Printf(" Remote: %s", r.RemoteAddr)
|
||||
|
||||
// Validate DID authorization for READ
|
||||
if !s.isAuthorizedRead(req.DID) {
|
||||
log.Printf("❌ [HandleHeadPresignedURL] Authorization FAILED")
|
||||
if req.DID == "" {
|
||||
// Anonymous request to private hold
|
||||
http.Error(w, "unauthorized: authentication required", http.StatusUnauthorized)
|
||||
} else {
|
||||
// Authenticated but not authorized
|
||||
http.Error(w, "forbidden: access denied", http.StatusForbidden)
|
||||
var req PresignedURLRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, fmt.Sprintf("invalid request: %v", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Generate presigned HEAD URL (15 minute expiry)
|
||||
ctx := context.Background()
|
||||
expiry := time.Now().Add(15 * time.Minute)
|
||||
|
||||
url, err := s.getHeadURL(ctx, req.Digest, req.DID)
|
||||
if err != nil {
|
||||
log.Printf("❌ [HandleHeadPresignedURL] getHeadURL failed: %v", err)
|
||||
http.Error(w, fmt.Sprintf("failed to generate URL: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("✅ [HandleHeadPresignedURL] Returning URL to client")
|
||||
|
||||
resp := HeadPresignedURLResponse{
|
||||
URL: url,
|
||||
ExpiresAt: expiry,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}
|
||||
|
||||
// HandlePutPresignedURL handles requests for upload URLs
|
||||
func (s *HoldService) HandlePutPresignedURL(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req PutPresignedURLRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, fmt.Sprintf("invalid request: %v", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate DID authorization for WRITE
|
||||
if !s.isAuthorizedWrite(req.DID) {
|
||||
if req.DID == "" {
|
||||
// Anonymous write attempt
|
||||
http.Error(w, "unauthorized: authentication required", http.StatusUnauthorized)
|
||||
} else {
|
||||
// Authenticated but not crew/owner
|
||||
http.Error(w, "forbidden: write access denied", http.StatusForbidden)
|
||||
// Validate DID authorization based on operation type
|
||||
var authorized bool
|
||||
switch operation {
|
||||
case OperationGet, OperationHead:
|
||||
authorized = s.isAuthorizedRead(req.DID)
|
||||
case OperationPut:
|
||||
authorized = s.isAuthorizedWrite(req.DID)
|
||||
default:
|
||||
http.Error(w, "unsupported operation", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
return
|
||||
|
||||
if !authorized {
|
||||
log.Printf("[HandlePresignedURL:%s] Authorization FAILED", operation)
|
||||
if req.DID == "" {
|
||||
http.Error(w, "unauthorized: authentication required", http.StatusUnauthorized)
|
||||
} else {
|
||||
http.Error(w, "forbidden: access denied", http.StatusForbidden)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Generate presigned URL (15 minute expiry)
|
||||
ctx := context.Background()
|
||||
expiry := time.Now().Add(15 * time.Minute)
|
||||
|
||||
url, err := s.getPresignedURL(ctx, operation, req.Digest, req.DID)
|
||||
if err != nil {
|
||||
log.Printf("[HandlePresignedURL:%s] getPresignedURL failed: %v", operation, err)
|
||||
http.Error(w, fmt.Sprintf("failed to generate URL: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[HandlePresignedURL:%s] Returning URL to client", operation)
|
||||
|
||||
resp := PresignedURLResponse{
|
||||
URL: url,
|
||||
ExpiresAt: expiry,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}
|
||||
|
||||
// Generate presigned upload URL (15 minute expiry)
|
||||
ctx := context.Background()
|
||||
expiry := time.Now().Add(15 * time.Minute)
|
||||
|
||||
url, err := s.getUploadURL(ctx, req.Digest, req.Size, req.DID)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to generate URL: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resp := PutPresignedURLResponse{
|
||||
URL: url,
|
||||
ExpiresAt: expiry,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}
|
||||
|
||||
// HandleProxyGet proxies a blob download through the service
|
||||
@@ -179,11 +86,6 @@ func (s *HoldService) HandleProxyGet(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("📥 [HandleProxyGet] Blob download request:")
|
||||
log.Printf(" Method: %s", r.Method)
|
||||
log.Printf(" Digest: %s", digest)
|
||||
log.Printf(" Remote: %s", r.RemoteAddr)
|
||||
|
||||
// Get DID from query param or header
|
||||
did := r.URL.Query().Get("did")
|
||||
if did == "" {
|
||||
@@ -193,7 +95,7 @@ func (s *HoldService) HandleProxyGet(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Authorize READ access
|
||||
if !s.isAuthorizedRead(did) {
|
||||
log.Printf("❌ [HandleProxyGet] Authorization FAILED")
|
||||
log.Printf("[HandleProxyGet] Authorization FAILED")
|
||||
if did == "" {
|
||||
http.Error(w, "unauthorized: authentication required", http.StatusUnauthorized)
|
||||
} else {
|
||||
@@ -201,7 +103,6 @@ func (s *HoldService) HandleProxyGet(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
return
|
||||
}
|
||||
log.Printf("✅ [HandleProxyGet] Authorization SUCCESS")
|
||||
|
||||
ctx := r.Context()
|
||||
path := blobPath(digest)
|
||||
@@ -290,14 +191,9 @@ func (s *HoldService) HandleProxyPut(w http.ResponseWriter, r *http.Request) {
|
||||
did = r.Header.Get("X-ATCR-DID")
|
||||
}
|
||||
|
||||
log.Printf("🔐 [HandleProxyPut] Authorization check:")
|
||||
log.Printf(" Path: %s", digest)
|
||||
log.Printf(" DID: %s", did)
|
||||
log.Printf(" Owner DID: %s", s.config.Registration.OwnerDID)
|
||||
|
||||
// Authorize WRITE access
|
||||
if !s.isAuthorizedWrite(did) {
|
||||
log.Printf("❌ [HandleProxyPut] Authorization FAILED")
|
||||
log.Printf("[HandleProxyPut] Authorization FAILED")
|
||||
if did == "" {
|
||||
http.Error(w, "unauthorized: authentication required", http.StatusUnauthorized)
|
||||
} else {
|
||||
@@ -306,8 +202,6 @@ func (s *HoldService) HandleProxyPut(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("✅ [HandleProxyPut] Authorization SUCCESS")
|
||||
|
||||
// Stream blob to storage (no buffering)
|
||||
ctx := r.Context()
|
||||
path := blobPath(digest)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -158,6 +159,11 @@ func (s *HoldService) completeMultipartUpload(ctx context.Context, digest, uploa
|
||||
s3Key = s.s3PathPrefix + "/" + s3Key
|
||||
}
|
||||
|
||||
// Sort parts by part number (S3 requires ascending order)
|
||||
sort.Slice(parts, func(i, j int) bool {
|
||||
return parts[i].PartNumber < parts[j].PartNumber
|
||||
})
|
||||
|
||||
// Convert to S3 CompletedPart format
|
||||
// IMPORTANT: S3 requires ETags to be quoted in the CompleteMultipartUpload XML
|
||||
s3Parts := make([]*s3.CompletedPart, len(parts))
|
||||
|
||||
+46
-124
@@ -39,79 +39,24 @@ func blobPath(digest string) string {
|
||||
return fmt.Sprintf("/docker/registry/v2/blobs/%s/%s/%s/data", algorithm, hash[:2], hash)
|
||||
}
|
||||
|
||||
// getDownloadURL generates a download URL for a blob
|
||||
func (s *HoldService) getDownloadURL(ctx context.Context, digest string, did string) (string, error) {
|
||||
// Check if blob exists
|
||||
// getPresignedURL generates a presigned URL for GET, HEAD, or PUT operations
|
||||
func (s *HoldService) getPresignedURL(ctx context.Context, operation PresignedURLOperation, digest string, did string) (string, error) {
|
||||
path := blobPath(digest)
|
||||
_, err := s.driver.Stat(ctx, path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("blob not found: %w", err)
|
||||
|
||||
// Check blob exists for GET/HEAD operations (not for PUT since blob doesn't exist yet)
|
||||
if operation == OperationGet || operation == OperationHead {
|
||||
if _, err := s.driver.Stat(ctx, path); err != nil {
|
||||
return "", fmt.Errorf("blob not found: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if presigned URLs are disabled for testing
|
||||
// Check if presigned URLs are disabled
|
||||
if s.config.Server.DisablePresignedURLs {
|
||||
log.Printf("Presigned URLs disabled, using proxy URL")
|
||||
return s.getProxyDownloadURL(digest, did), nil
|
||||
return s.getProxyURL(digest, did), nil
|
||||
}
|
||||
|
||||
// If S3 client available, generate presigned URL
|
||||
if s.s3Client != nil {
|
||||
// Build S3 key from blob path
|
||||
// blobPath returns paths like: /docker/registry/v2/blobs/sha256/ab/abc123.../data
|
||||
s3Key := strings.TrimPrefix(path, "/")
|
||||
if s.s3PathPrefix != "" {
|
||||
s3Key = s.s3PathPrefix + "/" + s3Key
|
||||
}
|
||||
|
||||
// Generate presigned GET URL
|
||||
// Note: Don't use ResponseContentType - not supported by all S3-compatible services
|
||||
req, _ := s.s3Client.GetObjectRequest(&s3.GetObjectInput{
|
||||
Bucket: aws.String(s.bucket),
|
||||
Key: aws.String(s3Key),
|
||||
})
|
||||
|
||||
log.Printf("🔍 [getDownloadURL] Before Presign:")
|
||||
log.Printf(" Digest: %s", digest)
|
||||
log.Printf(" S3 Key: %s", s3Key)
|
||||
log.Printf(" Bucket: %s", s.bucket)
|
||||
log.Printf(" Request Operation: %s", req.Operation.Name)
|
||||
log.Printf(" Request HTTPMethod: %s", req.Operation.HTTPMethod)
|
||||
|
||||
url, err := req.Presign(15 * time.Minute)
|
||||
if err != nil {
|
||||
log.Printf("❌ [getDownloadURL] Presign FAILED: %v", err)
|
||||
log.Printf(" Falling back to proxy URL")
|
||||
return s.getProxyDownloadURL(digest, did), nil
|
||||
}
|
||||
|
||||
log.Printf("✅ [getDownloadURL] Presigned URL generated successfully")
|
||||
log.Printf(" URL: %s", url)
|
||||
log.Printf(" URL Length: %d chars", len(url))
|
||||
log.Printf(" Expires: 15min")
|
||||
|
||||
return url, nil
|
||||
}
|
||||
|
||||
// Fallback: return proxy URL through this service
|
||||
return s.getProxyDownloadURL(digest, did), nil
|
||||
}
|
||||
|
||||
// getHeadURL generates a HEAD URL for a blob
|
||||
func (s *HoldService) getHeadURL(ctx context.Context, digest string, did string) (string, error) {
|
||||
// Check if blob exists
|
||||
path := blobPath(digest)
|
||||
_, err := s.driver.Stat(ctx, path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("blob not found: %w", err)
|
||||
}
|
||||
|
||||
// Check if presigned URLs are disabled for testing
|
||||
if s.config.Server.DisablePresignedURLs {
|
||||
log.Printf("Presigned URLs disabled, using proxy URL")
|
||||
return s.getProxyDownloadURL(digest, did), nil
|
||||
}
|
||||
|
||||
// If S3 client available, generate presigned HEAD URL
|
||||
// Generate presigned URL if S3 client is available
|
||||
if s.s3Client != nil {
|
||||
// Build S3 key from blob path
|
||||
s3Key := strings.TrimPrefix(path, "/")
|
||||
@@ -119,75 +64,52 @@ func (s *HoldService) getHeadURL(ctx context.Context, digest string, did string)
|
||||
s3Key = s.s3PathPrefix + "/" + s3Key
|
||||
}
|
||||
|
||||
// Generate presigned HEAD URL
|
||||
req, _ := s.s3Client.HeadObjectRequest(&s3.HeadObjectInput{
|
||||
Bucket: aws.String(s.bucket),
|
||||
Key: aws.String(s3Key),
|
||||
})
|
||||
// Create appropriate S3 request based on operation
|
||||
var req interface {
|
||||
Presign(time.Duration) (string, error)
|
||||
}
|
||||
switch operation {
|
||||
case OperationGet:
|
||||
// Note: Don't use ResponseContentType - not supported by all S3-compatible services
|
||||
req, _ = s.s3Client.GetObjectRequest(&s3.GetObjectInput{
|
||||
Bucket: aws.String(s.bucket),
|
||||
Key: aws.String(s3Key),
|
||||
})
|
||||
|
||||
case OperationHead:
|
||||
req, _ = s.s3Client.HeadObjectRequest(&s3.HeadObjectInput{
|
||||
Bucket: aws.String(s.bucket),
|
||||
Key: aws.String(s3Key),
|
||||
})
|
||||
|
||||
case OperationPut:
|
||||
req, _ = s.s3Client.PutObjectRequest(&s3.PutObjectInput{
|
||||
Bucket: aws.String(s.bucket),
|
||||
Key: aws.String(s3Key),
|
||||
ContentType: aws.String("application/octet-stream"),
|
||||
})
|
||||
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported operation: %s", operation)
|
||||
}
|
||||
|
||||
// Generate presigned URL with 15 minute expiry
|
||||
url, err := req.Presign(15 * time.Minute)
|
||||
if err != nil {
|
||||
log.Printf("❌ [getHeadURL] Presign FAILED: %v", err)
|
||||
log.Printf("[getPresignedURL] Presign FAILED for %s: %v", operation, err)
|
||||
log.Printf(" Falling back to proxy URL")
|
||||
return s.getProxyDownloadURL(digest, did), nil
|
||||
return s.getProxyURL(digest, did), nil
|
||||
}
|
||||
|
||||
log.Printf("✅ [getHeadURL] Presigned HEAD URL generated: digest=%s", digest)
|
||||
return url, nil
|
||||
}
|
||||
|
||||
// Fallback: return proxy URL through this service
|
||||
return s.getProxyDownloadURL(digest, did), nil
|
||||
return s.getProxyURL(digest, did), nil
|
||||
}
|
||||
|
||||
// getProxyDownloadURL returns a proxy URL for blob download (fallback when presigned URLs unavailable)
|
||||
func (s *HoldService) getProxyDownloadURL(digest, did string) string {
|
||||
return fmt.Sprintf("%s/blobs/%s?did=%s", s.config.Server.PublicURL, digest, did)
|
||||
}
|
||||
|
||||
// getUploadURL generates an upload URL for a blob
|
||||
// Note: This is called from HandlePutPresignedURL which has the DID in the request
|
||||
func (s *HoldService) getUploadURL(ctx context.Context, digest string, size int64, did string) (string, error) {
|
||||
// Check if presigned URLs are disabled for testing
|
||||
if s.config.Server.DisablePresignedURLs {
|
||||
log.Printf("Presigned URLs disabled, using proxy URL")
|
||||
return s.getProxyUploadURL(digest, did), nil
|
||||
}
|
||||
|
||||
// If S3 client available, generate presigned URL
|
||||
if s.s3Client != nil {
|
||||
// Build S3 key from blob path
|
||||
path := blobPath(digest)
|
||||
s3Key := strings.TrimPrefix(path, "/")
|
||||
if s.s3PathPrefix != "" {
|
||||
s3Key = s.s3PathPrefix + "/" + s3Key
|
||||
}
|
||||
|
||||
// Generate presigned PUT URL with Content-Type in signature
|
||||
req, _ := s.s3Client.PutObjectRequest(&s3.PutObjectInput{
|
||||
Bucket: aws.String(s.bucket),
|
||||
Key: aws.String(s3Key),
|
||||
ContentType: aws.String("application/octet-stream"),
|
||||
})
|
||||
|
||||
url, err := req.Presign(15 * time.Minute)
|
||||
if err != nil {
|
||||
log.Printf("WARN: Presigned URL generation failed for %s, falling back to proxy: %v", digest, err)
|
||||
return s.getProxyUploadURL(digest, did), nil
|
||||
}
|
||||
|
||||
log.Printf("🔑 Generated presigned upload URL for %s (expires in 15min)", digest)
|
||||
log.Printf(" S3 Key: %s", s3Key)
|
||||
log.Printf(" Bucket: %s", s.bucket)
|
||||
log.Printf(" Size: %d bytes", size)
|
||||
return url, nil
|
||||
}
|
||||
|
||||
// Fallback: return proxy URL through this service
|
||||
return s.getProxyUploadURL(digest, did), nil
|
||||
}
|
||||
|
||||
// getProxyUploadURL returns a proxy URL for blob upload (fallback when presigned URLs unavailable)
|
||||
func (s *HoldService) getProxyUploadURL(digest, did string) string {
|
||||
// getProxyURL returns a proxy URL for blob operations (fallback when presigned URLs unavailable)
|
||||
func (s *HoldService) getProxyURL(digest, did string) string {
|
||||
// All operations use the same proxy endpoint
|
||||
return fmt.Sprintf("%s/blobs/%s?did=%s", s.config.Server.PublicURL, digest, did)
|
||||
}
|
||||
|
||||
+14
-29
@@ -4,39 +4,24 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// GetPresignedURLRequest represents a request for a presigned download URL
|
||||
type GetPresignedURLRequest struct {
|
||||
// PresignedURLOperation defines the type of presigned URL operation
|
||||
type PresignedURLOperation string
|
||||
|
||||
const (
|
||||
OperationGet PresignedURLOperation = "GET"
|
||||
OperationHead PresignedURLOperation = "HEAD"
|
||||
OperationPut PresignedURLOperation = "PUT"
|
||||
)
|
||||
|
||||
// PresignedURLRequest represents a request for a presigned URL (GET, HEAD, or PUT)
|
||||
type PresignedURLRequest struct {
|
||||
DID string `json:"did"`
|
||||
Digest string `json:"digest"`
|
||||
Size int64 `json:"size,omitempty"` // Only required for PUT operations
|
||||
}
|
||||
|
||||
// GetPresignedURLResponse contains the presigned URL
|
||||
type GetPresignedURLResponse struct {
|
||||
URL string `json:"url"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
}
|
||||
|
||||
// HeadPresignedURLRequest represents a request for a presigned HEAD URL
|
||||
type HeadPresignedURLRequest struct {
|
||||
DID string `json:"did"`
|
||||
Digest string `json:"digest"`
|
||||
}
|
||||
|
||||
// HeadPresignedURLResponse contains the presigned HEAD URL
|
||||
type HeadPresignedURLResponse struct {
|
||||
URL string `json:"url"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
}
|
||||
|
||||
// PutPresignedURLRequest represents a request for a presigned upload URL
|
||||
type PutPresignedURLRequest struct {
|
||||
DID string `json:"did"`
|
||||
Digest string `json:"digest"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
// PutPresignedURLResponse contains the presigned upload URL
|
||||
type PutPresignedURLResponse struct {
|
||||
// PresignedURLResponse contains the presigned URL
|
||||
type PresignedURLResponse struct {
|
||||
URL string `json:"url"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
}
|
||||
|
||||
@@ -147,42 +147,32 @@ func (p *ProxyBlobStore) Put(ctx context.Context, mediaType string, content []by
|
||||
// Get upload URL
|
||||
url, err := p.getUploadURL(ctx, dgst, int64(len(content)))
|
||||
if err != nil {
|
||||
fmt.Printf("❌ [proxy_blob_store/Put] Failed to get upload URL: digest=%s, error=%v\n", dgst, err)
|
||||
fmt.Printf("[proxy_blob_store/Put] Failed to get upload URL: digest=%s, error=%v\n", dgst, err)
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
|
||||
fmt.Printf("📤 [proxy_blob_store/Put] Starting PUT request:\n")
|
||||
fmt.Printf(" Digest: %s\n", dgst)
|
||||
fmt.Printf(" Size: %d bytes\n", len(content))
|
||||
fmt.Printf(" MediaType: %s\n", mediaType)
|
||||
fmt.Printf(" URL: %s\n", url)
|
||||
fmt.Printf(" Headers: Content-Type=application/octet-stream\n")
|
||||
|
||||
// Upload the blob
|
||||
req, err := http.NewRequestWithContext(ctx, "PUT", url, bytes.NewReader(content))
|
||||
if err != nil {
|
||||
fmt.Printf("❌ [proxy_blob_store/Put] Failed to create request: %v\n", err)
|
||||
fmt.Printf("[proxy_blob_store/Put] Failed to create request: %v\n", err)
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/octet-stream")
|
||||
|
||||
resp, err := p.httpClient.Do(req)
|
||||
if err != nil {
|
||||
fmt.Printf("❌ [proxy_blob_store/Put] HTTP request failed: %v\n", err)
|
||||
fmt.Printf("[proxy_blob_store/Put] HTTP request failed: %v\n", err)
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
fmt.Printf("📥 [proxy_blob_store/Put] Response:\n")
|
||||
fmt.Printf(" Status: %d %s\n", resp.StatusCode, resp.Status)
|
||||
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||
fmt.Printf(" Error Body: %s\n", string(bodyBytes))
|
||||
return distribution.Descriptor{}, fmt.Errorf("upload failed with status %d: %s", resp.StatusCode, string(bodyBytes))
|
||||
}
|
||||
|
||||
fmt.Printf("✅ [proxy_blob_store/Put] Upload successful: digest=%s, size=%d\n", dgst, len(content))
|
||||
fmt.Printf("[proxy_blob_store/Put] Upload successful: digest=%s, size=%d\n", dgst, len(content))
|
||||
|
||||
return distribution.Descriptor{
|
||||
Digest: dgst,
|
||||
@@ -224,10 +214,6 @@ func (p *ProxyBlobStore) ServeBlob(ctx context.Context, w http.ResponseWriter, r
|
||||
|
||||
// Create returns a blob writer for uploading using multipart upload
|
||||
func (p *ProxyBlobStore) Create(ctx context.Context, options ...distribution.BlobCreateOption) (distribution.BlobWriter, error) {
|
||||
fmt.Printf("🔧 [proxy_blob_store/Create] Starting multipart upload\n")
|
||||
fmt.Printf(" Storage endpoint: %s\n", p.storageEndpoint)
|
||||
fmt.Printf(" Repository: %s\n", p.repository)
|
||||
|
||||
// Parse options
|
||||
var opts distribution.CreateOptions
|
||||
for _, option := range options {
|
||||
@@ -624,8 +610,6 @@ func (w *ProxyBlobWriter) flushPart() error {
|
||||
return fmt.Errorf("failed to get part presigned URL: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("📤 [flushPart] Uploading part %d: size=%d bytes\n", w.partNumber, w.buffer.Len())
|
||||
|
||||
// Upload part to S3
|
||||
req, err := http.NewRequestWithContext(ctx, "PUT", url, bytes.NewReader(w.buffer.Bytes()))
|
||||
if err != nil {
|
||||
@@ -655,7 +639,7 @@ func (w *ProxyBlobWriter) flushPart() error {
|
||||
ETag: etag,
|
||||
})
|
||||
|
||||
fmt.Printf("✅ [flushPart] Part %d uploaded successfully: ETag=%s\n", w.partNumber, etag)
|
||||
fmt.Printf("[flushPart] Part %d uploaded successfully: ETag=%s\n", w.partNumber, etag)
|
||||
|
||||
// Reset buffer and increment part number
|
||||
w.buffer.Reset()
|
||||
@@ -706,8 +690,6 @@ func (w *ProxyBlobWriter) Commit(ctx context.Context, desc distribution.Descript
|
||||
}
|
||||
w.closed = true
|
||||
|
||||
fmt.Printf("📝 [Commit] Starting commit: digest=%s, size=%d\n", desc.Digest, w.size)
|
||||
|
||||
// Remove from global uploads map
|
||||
globalUploadsMu.Lock()
|
||||
delete(globalUploads, w.id)
|
||||
@@ -715,7 +697,7 @@ func (w *ProxyBlobWriter) Commit(ctx context.Context, desc distribution.Descript
|
||||
|
||||
// Flush any remaining buffered data
|
||||
if w.buffer.Len() > 0 {
|
||||
fmt.Printf("📤 [Commit] Flushing final buffer: %d bytes\n", w.buffer.Len())
|
||||
fmt.Printf("[Commit] Flushing final buffer: %d bytes\n", w.buffer.Len())
|
||||
if err := w.flushPart(); err != nil {
|
||||
// Try to abort multipart on error
|
||||
tempDigest := fmt.Sprintf("uploads/temp-%s", w.id)
|
||||
@@ -735,7 +717,7 @@ func (w *ProxyBlobWriter) Commit(ctx context.Context, desc distribution.Descript
|
||||
tempPath := fmt.Sprintf("uploads/temp-%s", w.id)
|
||||
finalPath := desc.Digest.String()
|
||||
|
||||
fmt.Printf("🚚 [Commit] Moving blob: %s → %s\n", tempPath, finalPath)
|
||||
fmt.Printf("[Commit] Moving blob: %s → %s\n", tempPath, finalPath)
|
||||
moveURL := fmt.Sprintf("%s/move?from=%s&to=%s&did=%s",
|
||||
w.store.storageEndpoint, tempPath, finalPath, w.store.did)
|
||||
|
||||
@@ -755,7 +737,7 @@ func (w *ProxyBlobWriter) Commit(ctx context.Context, desc distribution.Descript
|
||||
return distribution.Descriptor{}, fmt.Errorf("move blob failed: status %d, body: %s", resp.StatusCode, string(bodyBytes))
|
||||
}
|
||||
|
||||
fmt.Printf("✅ [Commit] Upload completed successfully: digest=%s, size=%d, parts=%d\n", desc.Digest, w.size, len(w.parts))
|
||||
fmt.Printf("[Commit] Upload completed successfully: digest=%s, size=%d, parts=%d\n", desc.Digest, w.size, len(w.parts))
|
||||
|
||||
return distribution.Descriptor{
|
||||
Digest: desc.Digest,
|
||||
@@ -768,7 +750,7 @@ func (w *ProxyBlobWriter) Commit(ctx context.Context, desc distribution.Descript
|
||||
func (w *ProxyBlobWriter) Cancel(ctx context.Context) error {
|
||||
w.closed = true
|
||||
|
||||
fmt.Printf("❌ [Cancel] Cancelling upload: id=%s\n", w.id)
|
||||
fmt.Printf("[Cancel] Cancelling upload: id=%s\n", w.id)
|
||||
|
||||
// Remove from global uploads map
|
||||
globalUploadsMu.Lock()
|
||||
@@ -782,7 +764,7 @@ func (w *ProxyBlobWriter) Cancel(ctx context.Context) error {
|
||||
// Continue anyway - we want to mark upload as cancelled
|
||||
}
|
||||
|
||||
fmt.Printf("✅ [Cancel] Upload cancelled: id=%s\n", w.id)
|
||||
fmt.Printf("[Cancel] Upload cancelled: id=%s\n", w.id)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user