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.

at codeberg-source 124 lines 4.0 kB view raw
1// Package s3 provides S3 client initialization and presigned URL generation 2// for hold services. It supports S3, Storj, and Minio storage backends, 3// with fallback to buffered proxy mode when presigned URLs are unavailable. 4package s3 5 6import ( 7 "fmt" 8 "log/slog" 9 "strings" 10 11 "github.com/aws/aws-sdk-go/aws" 12 "github.com/aws/aws-sdk-go/aws/credentials" 13 "github.com/aws/aws-sdk-go/aws/session" 14 "github.com/aws/aws-sdk-go/service/s3" 15) 16 17type S3Service struct { 18 Client *s3.S3 // S3 client for presigned URLs (nil if not S3 storage) 19 Bucket string // S3 bucket name 20 PathPrefix string // S3 path prefix (if any) 21} 22 23// NewS3Service initializes the S3 client for presigned URL generation 24// Returns nil error if S3 client is successfully initialized 25// Returns error if storage is not S3 or if initialization fails (service will fall back to proxy mode) 26func NewS3Service(params map[string]any, disablePresigned bool, storageType string) (*S3Service, error) { 27 // Check if presigned URLs are explicitly disabled 28 if disablePresigned { 29 slog.Warn("S3 presigned URLs DISABLED by config", 30 "reason", "DISABLE_PRESIGNED_URLS=true", 31 "uploadMode", "buffered") 32 return &S3Service{}, nil 33 } 34 35 // Check if storage driver is S3 36 if storageType != "s3" { 37 slog.Info("Presigned URLs disabled for non-S3 storage", 38 "storageDriver", storageType) 39 return &S3Service{}, nil 40 } 41 42 // Extract required S3 configuration 43 region, _ := params["region"].(string) 44 if region == "" { 45 region = "us-east-1" // Default region 46 } 47 48 accessKey, _ := params["accesskey"].(string) 49 secretKey, _ := params["secretkey"].(string) 50 bucket, _ := params["bucket"].(string) 51 52 if bucket == "" { 53 return nil, fmt.Errorf("S3 bucket not configured") 54 } 55 56 // Build AWS config 57 awsConfig := &aws.Config{ 58 Region: &region, 59 } 60 61 // Add credentials if provided (allow IAM role auth if not provided) 62 if accessKey != "" && secretKey != "" { 63 awsConfig.Credentials = credentials.NewStaticCredentials(accessKey, secretKey, "") 64 } 65 66 // Add custom endpoint for S3-compatible services (Storj, MinIO, R2, etc.) 67 if endpoint, ok := params["regionendpoint"].(string); ok && endpoint != "" { 68 awsConfig.Endpoint = &endpoint 69 awsConfig.S3ForcePathStyle = aws.Bool(true) // Required for MinIO, Storj 70 } 71 72 // Create AWS session 73 sess, err := session.NewSession(awsConfig) 74 if err != nil { 75 return nil, fmt.Errorf("failed to create AWS session: %w", err) 76 } 77 78 var s3PathPrefix string 79 // Extract path prefix if configured (rootdirectory in S3 params) 80 if rootDir, ok := params["rootdirectory"].(string); ok && rootDir != "" { 81 s3PathPrefix = strings.TrimPrefix(rootDir, "/") 82 } 83 84 slog.Info("S3 presigned URLs enabled", 85 "bucket", bucket, 86 "region", region, 87 "pathPrefix", s3PathPrefix) 88 89 // Create S3 client 90 return &S3Service{ 91 Client: s3.New(sess), 92 Bucket: bucket, 93 PathPrefix: s3PathPrefix, 94 }, nil 95} 96 97// BlobPath converts a digest (e.g., "sha256:abc123...") or temp path to a storage path 98// Distribution stores blobs as: /docker/registry/v2/blobs/{algorithm}/{xx}/{hash}/data 99// where xx is the first 2 characters of the hash for directory sharding 100// NOTE: Path must start with / for filesystem driver 101// This is used for OCI container layers (content-addressed, globally deduplicated) 102func BlobPath(digest string) string { 103 // Handle temp paths (start with uploads/temp-) 104 if strings.HasPrefix(digest, "uploads/temp-") { 105 return fmt.Sprintf("/docker/registry/v2/%s/data", digest) 106 } 107 108 // Split digest into algorithm and hash 109 parts := strings.SplitN(digest, ":", 2) 110 if len(parts) != 2 { 111 // Fallback for malformed digest 112 return fmt.Sprintf("/docker/registry/v2/blobs/%s/data", digest) 113 } 114 115 algorithm := parts[0] 116 hash := parts[1] 117 118 // Use first 2 characters for sharding 119 if len(hash) < 2 { 120 return fmt.Sprintf("/docker/registry/v2/blobs/%s/%s/data", algorithm, hash) 121 } 122 123 return fmt.Sprintf("/docker/registry/v2/blobs/%s/%s/%s/data", algorithm, hash[:2], hash) 124}