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.

fix pushing images when the historical hold does not match the default hold in the account

+93 -16
+1 -1
.tangled/workflows/release.yml
··· 11 11 12 12 environment: 13 13 IMAGE_REGISTRY: atcr.io 14 - IMAGE_USER: evan.jarrett.net 14 + IMAGE_USER: atcr.io 15 15 16 16 steps: 17 17 - name: Login to registry
+12 -4
pkg/appview/storage/routing_repository.go
··· 64 64 return blobStore 65 65 } 66 66 67 - // For pull operations, check database for hold DID from the most recent manifest 68 - // This ensures blobs are fetched from the hold recorded in the manifest, not re-discovered 67 + // Determine if this is a pull (GET) or push (PUT/POST/HEAD/etc) operation 68 + // Pull operations use the historical hold DID from the database (blobs are where they were pushed) 69 + // Push operations use the discovery-based hold DID from user's profile/default 70 + // This allows users to change their default hold and have new pushes go there 71 + isPull := false 72 + if method, ok := ctx.Value("http.request.method").(string); ok { 73 + isPull = method == "GET" 74 + } 75 + 69 76 holdDID := r.Ctx.HoldDID // Default to discovery-based DID 70 77 holdSource := "discovery" 71 78 72 - if r.Ctx.Database != nil { 79 + // Only query database for pull operations 80 + if isPull && r.Ctx.Database != nil { 73 81 // Query database for the latest manifest's hold DID 74 82 if dbHoldDID, err := r.Ctx.Database.GetLatestHoldDIDForRepo(r.Ctx.DID, r.Ctx.Repository); err == nil && dbHoldDID != "" { 75 83 // Use hold DID from database (pull case - use historical reference) 76 84 holdDID = dbHoldDID 77 85 holdSource = "database" 78 - slog.Debug("Using hold from database manifest", "component", "storage/blobs", "did", r.Ctx.DID, "repo", r.Ctx.Repository, "hold", dbHoldDID) 86 + slog.Debug("Using hold from database manifest (pull)", "component", "storage/blobs", "did", r.Ctx.DID, "repo", r.Ctx.Repository, "hold", dbHoldDID) 79 87 } else if err != nil { 80 88 // Log error but don't fail - fall back to discovery-based DID 81 89 slog.Warn("Failed to query database for hold DID", "component", "storage/blobs", "error", err)
+80 -11
pkg/appview/storage/routing_repository_test.go
··· 109 109 assert.NotNil(t, repo.manifestStore) 110 110 } 111 111 112 - // TestRoutingRepository_Blobs_WithDatabase tests blob store with database hold DID 113 - func TestRoutingRepository_Blobs_WithDatabase(t *testing.T) { 112 + // TestRoutingRepository_Blobs_PullUsesDatabase tests that GET (pull) uses database hold DID 113 + func TestRoutingRepository_Blobs_PullUsesDatabase(t *testing.T) { 114 114 dbHoldDID := "did:web:database.hold.io" 115 + discoveryHoldDID := "did:web:discovery.hold.io" 115 116 116 117 ctx := &RegistryContext{ 117 118 DID: "did:plc:test123", 118 119 Repository: "myapp", 119 - HoldDID: "did:web:default.hold.io", // Discovery-based hold (should be overridden) 120 + HoldDID: discoveryHoldDID, // Discovery-based hold (should be overridden for pull) 120 121 ATProtoClient: atproto.NewClient("https://pds.example.com", "did:plc:test123", ""), 121 122 Database: &mockDatabase{holdDID: dbHoldDID}, 122 123 } 123 124 124 125 repo := NewRoutingRepository(nil, ctx) 126 + 127 + // Create context with GET method (pull operation) 128 + pullCtx := context.WithValue(context.Background(), "http.request.method", "GET") 129 + blobStore := repo.Blobs(pullCtx) 130 + 131 + assert.NotNil(t, blobStore) 132 + // Verify the hold DID was updated to use the database value for pull 133 + assert.Equal(t, dbHoldDID, repo.Ctx.HoldDID, "pull (GET) should use database hold DID") 134 + } 135 + 136 + // TestRoutingRepository_Blobs_PushUsesDiscovery tests that push operations use discovery hold DID 137 + func TestRoutingRepository_Blobs_PushUsesDiscovery(t *testing.T) { 138 + dbHoldDID := "did:web:database.hold.io" 139 + discoveryHoldDID := "did:web:discovery.hold.io" 140 + 141 + testCases := []struct { 142 + name string 143 + method string 144 + }{ 145 + {"PUT", "PUT"}, 146 + {"POST", "POST"}, 147 + {"HEAD", "HEAD"}, 148 + {"PATCH", "PATCH"}, 149 + {"DELETE", "DELETE"}, 150 + } 151 + 152 + for _, tc := range testCases { 153 + t.Run(tc.name, func(t *testing.T) { 154 + ctx := &RegistryContext{ 155 + DID: "did:plc:test123", 156 + Repository: "myapp-" + tc.method, // Unique repo to avoid caching 157 + HoldDID: discoveryHoldDID, 158 + ATProtoClient: atproto.NewClient("https://pds.example.com", "did:plc:test123", ""), 159 + Database: &mockDatabase{holdDID: dbHoldDID}, 160 + } 161 + 162 + repo := NewRoutingRepository(nil, ctx) 163 + 164 + // Create context with push method 165 + pushCtx := context.WithValue(context.Background(), "http.request.method", tc.method) 166 + blobStore := repo.Blobs(pushCtx) 167 + 168 + assert.NotNil(t, blobStore) 169 + // Verify the hold DID remains the discovery-based one for push operations 170 + assert.Equal(t, discoveryHoldDID, repo.Ctx.HoldDID, "%s should use discovery hold DID, not database", tc.method) 171 + }) 172 + } 173 + } 174 + 175 + // TestRoutingRepository_Blobs_NoMethodUsesDiscovery tests that missing method defaults to discovery 176 + func TestRoutingRepository_Blobs_NoMethodUsesDiscovery(t *testing.T) { 177 + dbHoldDID := "did:web:database.hold.io" 178 + discoveryHoldDID := "did:web:discovery.hold.io" 179 + 180 + ctx := &RegistryContext{ 181 + DID: "did:plc:test123", 182 + Repository: "myapp-nomethod", 183 + HoldDID: discoveryHoldDID, 184 + ATProtoClient: atproto.NewClient("https://pds.example.com", "did:plc:test123", ""), 185 + Database: &mockDatabase{holdDID: dbHoldDID}, 186 + } 187 + 188 + repo := NewRoutingRepository(nil, ctx) 189 + 190 + // Context without HTTP method (shouldn't happen in practice, but test defensive behavior) 125 191 blobStore := repo.Blobs(context.Background()) 126 192 127 193 assert.NotNil(t, blobStore) 128 - // Verify the hold DID was updated to use the database value 129 - assert.Equal(t, dbHoldDID, repo.Ctx.HoldDID, "should use database hold DID") 194 + // Without method, should default to discovery (safer for push scenarios) 195 + assert.Equal(t, discoveryHoldDID, repo.Ctx.HoldDID, "missing method should use discovery hold DID") 130 196 } 131 197 132 198 // TestRoutingRepository_Blobs_WithoutDatabase tests blob store with discovery-based hold ··· 292 358 assert.NotNil(t, cachedBlobStore) 293 359 } 294 360 295 - // TestRoutingRepository_Blobs_Priority tests that database hold DID takes priority over discovery 296 - func TestRoutingRepository_Blobs_Priority(t *testing.T) { 361 + // TestRoutingRepository_Blobs_PullPriority tests that database hold DID takes priority for pull (GET) 362 + func TestRoutingRepository_Blobs_PullPriority(t *testing.T) { 297 363 dbHoldDID := "did:web:database.hold.io" 298 364 discoveryHoldDID := "did:web:discovery.hold.io" 299 365 300 366 ctx := &RegistryContext{ 301 367 DID: "did:plc:test123", 302 - Repository: "myapp", 368 + Repository: "myapp-priority", 303 369 HoldDID: discoveryHoldDID, // Discovery-based hold 304 370 ATProtoClient: atproto.NewClient("https://pds.example.com", "did:plc:test123", ""), 305 371 Database: &mockDatabase{holdDID: dbHoldDID}, // Database has a different hold DID 306 372 } 307 373 308 374 repo := NewRoutingRepository(nil, ctx) 309 - blobStore := repo.Blobs(context.Background()) 375 + 376 + // For pull (GET), database should take priority 377 + pullCtx := context.WithValue(context.Background(), "http.request.method", "GET") 378 + blobStore := repo.Blobs(pullCtx) 310 379 311 380 assert.NotNil(t, blobStore) 312 - // Database hold DID should take priority over discovery 313 - assert.Equal(t, dbHoldDID, repo.Ctx.HoldDID, "database hold DID should take priority over discovery") 381 + // Database hold DID should take priority over discovery for pull operations 382 + assert.Equal(t, dbHoldDID, repo.Ctx.HoldDID, "database hold DID should take priority over discovery for pull (GET)") 314 383 }