From 261f1d65476dfa0ad67229dd59108b61b51ee423 Mon Sep 17 00:00:00 2001 From: Evan Jarrett Date: Tue, 7 Oct 2025 21:09:33 -0500 Subject: [PATCH] add labels to the manifest annotations --- pkg/atproto/manifest_store.go | 58 ++++++++++++++++++++++++++++--- pkg/storage/routing_repository.go | 5 ++- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/pkg/atproto/manifest_store.go b/pkg/atproto/manifest_store.go index 66df576..bfc763e 100644 --- a/pkg/atproto/manifest_store.go +++ b/pkg/atproto/manifest_store.go @@ -1,6 +1,7 @@ package atproto import ( + "maps" "context" "encoding/json" "fmt" @@ -15,18 +16,20 @@ import ( type ManifestStore struct { client *Client repository string - holdEndpoint string // Hold service endpoint where blobs are stored (for push) - did string // User's DID for cache key - lastFetchedHoldEndpoint string // Hold endpoint from most recently fetched manifest (for pull) + holdEndpoint string // Hold service endpoint where blobs are stored (for push) + did string // User's DID for cache key + lastFetchedHoldEndpoint string // Hold endpoint from most recently fetched manifest (for pull) + blobStore distribution.BlobStore // Blob store for fetching config during push } // NewManifestStore creates a new ATProto-backed manifest store -func NewManifestStore(client *Client, repository string, holdEndpoint string, did string) *ManifestStore { +func NewManifestStore(client *Client, repository string, holdEndpoint string, did string, blobStore distribution.BlobStore) *ManifestStore { return &ManifestStore{ client: client, repository: repository, holdEndpoint: holdEndpoint, did: did, + blobStore: blobStore, } } @@ -116,6 +119,25 @@ func (s *ManifestStore) Put(ctx context.Context, manifest distribution.Manifest, manifestRecord.ManifestBlob = blobRef manifestRecord.HoldEndpoint = s.holdEndpoint + // Extract Dockerfile labels from config blob and add to annotations + if s.blobStore != nil && manifestRecord.Config.Digest != "" { + labels, err := s.extractConfigLabels(ctx, manifestRecord.Config.Digest) + if err != nil { + // Log error but don't fail the push - labels are optional + fmt.Printf("WARNING: Failed to extract config labels: %v\n", err) + } else { + // Initialize annotations map if needed + if manifestRecord.Annotations == nil { + manifestRecord.Annotations = make(map[string]string) + } + + // Copy labels to annotations (Dockerfile LABELs → manifest annotations) + maps.Copy(manifestRecord.Annotations, labels) + + fmt.Printf("DEBUG: Extracted %d labels from config blob\n", len(labels)) + } + } + // Store manifest record in ATProto rkey := digestToRKey(dgst) _, err = s.client.PutRecord(ctx, ManifestCollection, rkey, manifestRecord) @@ -185,3 +207,31 @@ func (m *rawManifest) References() []distribution.Descriptor { func (m *rawManifest) Payload() (string, []byte, error) { return m.mediaType, m.payload, nil } + +// extractConfigLabels fetches the image config blob and extracts Dockerfile LABELs +func (s *ManifestStore) extractConfigLabels(ctx context.Context, configDigestStr string) (map[string]string, error) { + // Parse digest string + configDigest, err := digest.Parse(configDigestStr) + if err != nil { + return nil, fmt.Errorf("invalid config digest: %w", err) + } + + // Fetch config blob from storage + configData, err := s.blobStore.Get(ctx, configDigest) + if err != nil { + return nil, fmt.Errorf("failed to fetch config blob: %w", err) + } + + // Parse config JSON + var configJSON struct { + Config struct { + Labels map[string]string `json:"Labels"` + } `json:"config"` + } + + if err := json.Unmarshal(configData, &configJSON); err != nil { + return nil, fmt.Errorf("failed to parse config JSON: %w", err) + } + + return configJSON.Config.Labels, nil +} diff --git a/pkg/storage/routing_repository.go b/pkg/storage/routing_repository.go index bbf4242..771b085 100644 --- a/pkg/storage/routing_repository.go +++ b/pkg/storage/routing_repository.go @@ -42,7 +42,10 @@ func NewRoutingRepository( func (r *RoutingRepository) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) { // Create or return cached manifest store if r.manifestStore == nil { - r.manifestStore = atproto.NewManifestStore(r.atprotoClient, r.repositoryName, r.storageEndpoint, r.did) + // Ensure blob store is created first (needed for label extraction during push) + blobStore := r.Blobs(ctx) + + r.manifestStore = atproto.NewManifestStore(r.atprotoClient, r.repositoryName, r.storageEndpoint, r.did, blobStore) } // After any manifest operation, cache the hold endpoint for blob fetches