A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go
80
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