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.

allow fetch tags from pds

+137 -6
+2 -4
pkg/appview/storage/routing_repository.go
··· 108 108 } 109 109 110 110 // Tags returns the tag service 111 - // Tags will be handled by ATProto as well 111 + // Tags are stored in ATProto as io.atcr.tag records 112 112 func (r *RoutingRepository) Tags(ctx context.Context) distribution.TagService { 113 - // For now, delegate to the base repository 114 - // In a full implementation, this would also use ATProto 115 - return r.Repository.Tags(ctx) 113 + return atproto.NewTagStore(r.atprotoClient, r.repositoryName) 116 114 }
+10 -2
pkg/atproto/client.go
··· 135 135 return nil, err 136 136 } 137 137 138 - req.Header.Set("Authorization", "Bearer "+c.accessToken) 138 + // Only set Authorization header if we have a token 139 + // Empty Bearer tokens will be rejected by PDS 140 + if c.accessToken != "" { 141 + req.Header.Set("Authorization", "Bearer "+c.accessToken) 142 + } 139 143 140 144 resp, err := c.httpClient.Do(req) 141 145 if err != nil { ··· 217 221 return nil, err 218 222 } 219 223 220 - req.Header.Set("Authorization", "Bearer "+c.accessToken) 224 + // Only set Authorization header if we have a token 225 + // Empty Bearer tokens will be rejected by PDS 226 + if c.accessToken != "" { 227 + req.Header.Set("Authorization", "Bearer "+c.accessToken) 228 + } 221 229 222 230 resp, err := c.httpClient.Do(req) 223 231 if err != nil {
+125
pkg/atproto/tag_store.go
··· 1 + package atproto 2 + 3 + import ( 4 + "context" 5 + "encoding/json" 6 + "fmt" 7 + 8 + "github.com/distribution/distribution/v3" 9 + "github.com/opencontainers/go-digest" 10 + ) 11 + 12 + // TagStore implements distribution.TagService 13 + // It stores tags in ATProto as records 14 + type TagStore struct { 15 + client *Client 16 + repository string 17 + } 18 + 19 + // NewTagStore creates a new ATProto-backed tag store 20 + func NewTagStore(client *Client, repository string) *TagStore { 21 + return &TagStore{ 22 + client: client, 23 + repository: repository, 24 + } 25 + } 26 + 27 + // Get retrieves the descriptor for a tag 28 + func (s *TagStore) Get(ctx context.Context, tag string) (distribution.Descriptor, error) { 29 + // Build record key 30 + rkey := repositoryTagToRKey(s.repository, tag) 31 + 32 + // Fetch tag record from ATProto 33 + record, err := s.client.GetRecord(ctx, TagCollection, rkey) 34 + if err != nil { 35 + return distribution.Descriptor{}, distribution.ErrTagUnknown{Tag: tag} 36 + } 37 + 38 + var tagRecord TagRecord 39 + if err := json.Unmarshal(record.Value, &tagRecord); err != nil { 40 + return distribution.Descriptor{}, fmt.Errorf("failed to unmarshal tag record: %w", err) 41 + } 42 + 43 + // Parse manifest digest 44 + dgst, err := digest.Parse(tagRecord.ManifestDigest) 45 + if err != nil { 46 + return distribution.Descriptor{}, fmt.Errorf("invalid manifest digest in tag record: %w", err) 47 + } 48 + 49 + // Return descriptor pointing to the manifest 50 + return distribution.Descriptor{ 51 + Digest: dgst, 52 + MediaType: "application/vnd.oci.image.manifest.v1+json", 53 + }, nil 54 + } 55 + 56 + // Tag associates a tag with a descriptor (manifest digest) 57 + func (s *TagStore) Tag(ctx context.Context, tag string, desc distribution.Descriptor) error { 58 + // Create tag record 59 + tagRecord := NewTagRecord(s.repository, tag, desc.Digest.String()) 60 + 61 + // Store in ATProto 62 + rkey := repositoryTagToRKey(s.repository, tag) 63 + _, err := s.client.PutRecord(ctx, TagCollection, rkey, tagRecord) 64 + if err != nil { 65 + return fmt.Errorf("failed to store tag in ATProto: %w", err) 66 + } 67 + 68 + return nil 69 + } 70 + 71 + // Untag removes a tag 72 + func (s *TagStore) Untag(ctx context.Context, tag string) error { 73 + rkey := repositoryTagToRKey(s.repository, tag) 74 + return s.client.DeleteRecord(ctx, TagCollection, rkey) 75 + } 76 + 77 + // All returns all tags for this repository 78 + func (s *TagStore) All(ctx context.Context) ([]string, error) { 79 + // List all records in the tag collection 80 + records, err := s.client.ListRecords(ctx, TagCollection, 100) 81 + if err != nil { 82 + return nil, fmt.Errorf("failed to list tags: %w", err) 83 + } 84 + 85 + var tags []string 86 + for _, record := range records { 87 + var tagRecord TagRecord 88 + if err := json.Unmarshal(record.Value, &tagRecord); err != nil { 89 + // Skip invalid records 90 + continue 91 + } 92 + 93 + // Only include tags for this repository 94 + if tagRecord.Repository == s.repository { 95 + tags = append(tags, tagRecord.Tag) 96 + } 97 + } 98 + 99 + return tags, nil 100 + } 101 + 102 + // Lookup returns the set of tags for a given digest 103 + func (s *TagStore) Lookup(ctx context.Context, desc distribution.Descriptor) ([]string, error) { 104 + // List all records in the tag collection 105 + records, err := s.client.ListRecords(ctx, TagCollection, 100) 106 + if err != nil { 107 + return nil, fmt.Errorf("failed to list tags: %w", err) 108 + } 109 + 110 + var tags []string 111 + for _, record := range records { 112 + var tagRecord TagRecord 113 + if err := json.Unmarshal(record.Value, &tagRecord); err != nil { 114 + // Skip invalid records 115 + continue 116 + } 117 + 118 + // Only include tags for this repository that match the digest 119 + if tagRecord.Repository == s.repository && tagRecord.ManifestDigest == desc.Digest.String() { 120 + tags = append(tags, tagRecord.Tag) 121 + } 122 + } 123 + 124 + return tags, nil 125 + }