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.

add labels to the manifest annotations

+58 -5
+54 -4
pkg/atproto/manifest_store.go
··· 1 1 package atproto 2 2 3 3 import ( 4 + "maps" 4 5 "context" 5 6 "encoding/json" 6 7 "fmt" ··· 15 16 type ManifestStore struct { 16 17 client *Client 17 18 repository string 18 - holdEndpoint string // Hold service endpoint where blobs are stored (for push) 19 - did string // User's DID for cache key 20 - lastFetchedHoldEndpoint string // Hold endpoint from most recently fetched manifest (for pull) 19 + holdEndpoint string // Hold service endpoint where blobs are stored (for push) 20 + did string // User's DID for cache key 21 + lastFetchedHoldEndpoint string // Hold endpoint from most recently fetched manifest (for pull) 22 + blobStore distribution.BlobStore // Blob store for fetching config during push 21 23 } 22 24 23 25 // NewManifestStore creates a new ATProto-backed manifest store 24 - func NewManifestStore(client *Client, repository string, holdEndpoint string, did string) *ManifestStore { 26 + func NewManifestStore(client *Client, repository string, holdEndpoint string, did string, blobStore distribution.BlobStore) *ManifestStore { 25 27 return &ManifestStore{ 26 28 client: client, 27 29 repository: repository, 28 30 holdEndpoint: holdEndpoint, 29 31 did: did, 32 + blobStore: blobStore, 30 33 } 31 34 } 32 35 ··· 116 119 manifestRecord.ManifestBlob = blobRef 117 120 manifestRecord.HoldEndpoint = s.holdEndpoint 118 121 122 + // Extract Dockerfile labels from config blob and add to annotations 123 + if s.blobStore != nil && manifestRecord.Config.Digest != "" { 124 + labels, err := s.extractConfigLabels(ctx, manifestRecord.Config.Digest) 125 + if err != nil { 126 + // Log error but don't fail the push - labels are optional 127 + fmt.Printf("WARNING: Failed to extract config labels: %v\n", err) 128 + } else { 129 + // Initialize annotations map if needed 130 + if manifestRecord.Annotations == nil { 131 + manifestRecord.Annotations = make(map[string]string) 132 + } 133 + 134 + // Copy labels to annotations (Dockerfile LABELs → manifest annotations) 135 + maps.Copy(manifestRecord.Annotations, labels) 136 + 137 + fmt.Printf("DEBUG: Extracted %d labels from config blob\n", len(labels)) 138 + } 139 + } 140 + 119 141 // Store manifest record in ATProto 120 142 rkey := digestToRKey(dgst) 121 143 _, err = s.client.PutRecord(ctx, ManifestCollection, rkey, manifestRecord) ··· 185 207 func (m *rawManifest) Payload() (string, []byte, error) { 186 208 return m.mediaType, m.payload, nil 187 209 } 210 + 211 + // extractConfigLabels fetches the image config blob and extracts Dockerfile LABELs 212 + func (s *ManifestStore) extractConfigLabels(ctx context.Context, configDigestStr string) (map[string]string, error) { 213 + // Parse digest string 214 + configDigest, err := digest.Parse(configDigestStr) 215 + if err != nil { 216 + return nil, fmt.Errorf("invalid config digest: %w", err) 217 + } 218 + 219 + // Fetch config blob from storage 220 + configData, err := s.blobStore.Get(ctx, configDigest) 221 + if err != nil { 222 + return nil, fmt.Errorf("failed to fetch config blob: %w", err) 223 + } 224 + 225 + // Parse config JSON 226 + var configJSON struct { 227 + Config struct { 228 + Labels map[string]string `json:"Labels"` 229 + } `json:"config"` 230 + } 231 + 232 + if err := json.Unmarshal(configData, &configJSON); err != nil { 233 + return nil, fmt.Errorf("failed to parse config JSON: %w", err) 234 + } 235 + 236 + return configJSON.Config.Labels, nil 237 + }
+4 -1
pkg/storage/routing_repository.go
··· 42 42 func (r *RoutingRepository) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) { 43 43 // Create or return cached manifest store 44 44 if r.manifestStore == nil { 45 - r.manifestStore = atproto.NewManifestStore(r.atprotoClient, r.repositoryName, r.storageEndpoint, r.did) 45 + // Ensure blob store is created first (needed for label extraction during push) 46 + blobStore := r.Blobs(ctx) 47 + 48 + r.manifestStore = atproto.NewManifestStore(r.atprotoClient, r.repositoryName, r.storageEndpoint, r.did, blobStore) 46 49 } 47 50 48 51 // After any manifest operation, cache the hold endpoint for blob fetches