mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-06-06 15:22:34 +00:00
allow fetch tags from pds
This commit is contained in:
@@ -108,9 +108,7 @@ func (r *RoutingRepository) Blobs(ctx context.Context) distribution.BlobStore {
|
||||
}
|
||||
|
||||
// Tags returns the tag service
|
||||
// Tags will be handled by ATProto as well
|
||||
// Tags are stored in ATProto as io.atcr.tag records
|
||||
func (r *RoutingRepository) Tags(ctx context.Context) distribution.TagService {
|
||||
// For now, delegate to the base repository
|
||||
// In a full implementation, this would also use ATProto
|
||||
return r.Repository.Tags(ctx)
|
||||
return atproto.NewTagStore(r.atprotoClient, r.repositoryName)
|
||||
}
|
||||
|
||||
@@ -135,7 +135,11 @@ func (c *Client) GetRecord(ctx context.Context, collection, rkey string) (*Recor
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+c.accessToken)
|
||||
// Only set Authorization header if we have a token
|
||||
// Empty Bearer tokens will be rejected by PDS
|
||||
if c.accessToken != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+c.accessToken)
|
||||
}
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
@@ -217,7 +221,11 @@ func (c *Client) ListRecords(ctx context.Context, collection string, limit int)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+c.accessToken)
|
||||
// Only set Authorization header if we have a token
|
||||
// Empty Bearer tokens will be rejected by PDS
|
||||
if c.accessToken != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+c.accessToken)
|
||||
}
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
|
||||
125
pkg/atproto/tag_store.go
Normal file
125
pkg/atproto/tag_store.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package atproto
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/distribution/distribution/v3"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// TagStore implements distribution.TagService
|
||||
// It stores tags in ATProto as records
|
||||
type TagStore struct {
|
||||
client *Client
|
||||
repository string
|
||||
}
|
||||
|
||||
// NewTagStore creates a new ATProto-backed tag store
|
||||
func NewTagStore(client *Client, repository string) *TagStore {
|
||||
return &TagStore{
|
||||
client: client,
|
||||
repository: repository,
|
||||
}
|
||||
}
|
||||
|
||||
// Get retrieves the descriptor for a tag
|
||||
func (s *TagStore) Get(ctx context.Context, tag string) (distribution.Descriptor, error) {
|
||||
// Build record key
|
||||
rkey := repositoryTagToRKey(s.repository, tag)
|
||||
|
||||
// Fetch tag record from ATProto
|
||||
record, err := s.client.GetRecord(ctx, TagCollection, rkey)
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, distribution.ErrTagUnknown{Tag: tag}
|
||||
}
|
||||
|
||||
var tagRecord TagRecord
|
||||
if err := json.Unmarshal(record.Value, &tagRecord); err != nil {
|
||||
return distribution.Descriptor{}, fmt.Errorf("failed to unmarshal tag record: %w", err)
|
||||
}
|
||||
|
||||
// Parse manifest digest
|
||||
dgst, err := digest.Parse(tagRecord.ManifestDigest)
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, fmt.Errorf("invalid manifest digest in tag record: %w", err)
|
||||
}
|
||||
|
||||
// Return descriptor pointing to the manifest
|
||||
return distribution.Descriptor{
|
||||
Digest: dgst,
|
||||
MediaType: "application/vnd.oci.image.manifest.v1+json",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Tag associates a tag with a descriptor (manifest digest)
|
||||
func (s *TagStore) Tag(ctx context.Context, tag string, desc distribution.Descriptor) error {
|
||||
// Create tag record
|
||||
tagRecord := NewTagRecord(s.repository, tag, desc.Digest.String())
|
||||
|
||||
// Store in ATProto
|
||||
rkey := repositoryTagToRKey(s.repository, tag)
|
||||
_, err := s.client.PutRecord(ctx, TagCollection, rkey, tagRecord)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to store tag in ATProto: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Untag removes a tag
|
||||
func (s *TagStore) Untag(ctx context.Context, tag string) error {
|
||||
rkey := repositoryTagToRKey(s.repository, tag)
|
||||
return s.client.DeleteRecord(ctx, TagCollection, rkey)
|
||||
}
|
||||
|
||||
// All returns all tags for this repository
|
||||
func (s *TagStore) All(ctx context.Context) ([]string, error) {
|
||||
// List all records in the tag collection
|
||||
records, err := s.client.ListRecords(ctx, TagCollection, 100)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list tags: %w", err)
|
||||
}
|
||||
|
||||
var tags []string
|
||||
for _, record := range records {
|
||||
var tagRecord TagRecord
|
||||
if err := json.Unmarshal(record.Value, &tagRecord); err != nil {
|
||||
// Skip invalid records
|
||||
continue
|
||||
}
|
||||
|
||||
// Only include tags for this repository
|
||||
if tagRecord.Repository == s.repository {
|
||||
tags = append(tags, tagRecord.Tag)
|
||||
}
|
||||
}
|
||||
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
// Lookup returns the set of tags for a given digest
|
||||
func (s *TagStore) Lookup(ctx context.Context, desc distribution.Descriptor) ([]string, error) {
|
||||
// List all records in the tag collection
|
||||
records, err := s.client.ListRecords(ctx, TagCollection, 100)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list tags: %w", err)
|
||||
}
|
||||
|
||||
var tags []string
|
||||
for _, record := range records {
|
||||
var tagRecord TagRecord
|
||||
if err := json.Unmarshal(record.Value, &tagRecord); err != nil {
|
||||
// Skip invalid records
|
||||
continue
|
||||
}
|
||||
|
||||
// Only include tags for this repository that match the digest
|
||||
if tagRecord.Repository == s.repository && tagRecord.ManifestDigest == desc.Digest.String() {
|
||||
tags = append(tags, tagRecord.Tag)
|
||||
}
|
||||
}
|
||||
|
||||
return tags, nil
|
||||
}
|
||||
Reference in New Issue
Block a user