Rust implementation of OCI Distribution Spec with granular access control

fix gc tests

+15 -8
+14 -7
src/gc.rs
··· 3 3 use std::path::Path; 4 4 use std::time::{SystemTime, UNIX_EPOCH}; 5 5 6 - type BlobInfo = (String, String, u64); // (org, repo, size) 6 + type BlobLocation = (String, String, u64); // (org, repo, size) 7 7 type UnreferencedBlob = (String, String, String, u64); // (org, repo, digest, size) 8 8 9 9 #[derive(Debug, Serialize, Deserialize)] ··· 153 153 /// Scan all blobs in storage 154 154 fn scan_all_blobs( 155 155 stats: &mut GcStats, 156 - ) -> Result<HashMap<String, BlobInfo>, Box<dyn std::error::Error>> { 157 - let mut all_blobs = HashMap::new(); // digest -> (org, repo, size) 156 + ) -> Result<HashMap<String, Vec<BlobLocation>>, Box<dyn std::error::Error>> { 157 + let mut all_blobs: HashMap<String, Vec<BlobLocation>> = HashMap::new(); 158 158 let blobs_dir = Path::new("./tmp/blobs"); 159 159 160 160 if !blobs_dir.exists() { ··· 188 188 let digest = blob_entry.file_name().to_string_lossy().to_string(); 189 189 let size = blob_entry.metadata()?.len(); 190 190 191 - all_blobs.insert(digest.clone(), (org.clone(), repo.clone(), size)); 191 + // Track all locations for this digest 192 + all_blobs 193 + .entry(digest) 194 + .or_default() 195 + .push((org.clone(), repo.clone(), size)); 192 196 } 193 197 } 194 198 } ··· 198 202 199 203 /// Mark unreferenced blobs for deletion 200 204 fn mark_unreferenced_blobs( 201 - all_blobs: &HashMap<String, BlobInfo>, 205 + all_blobs: &HashMap<String, Vec<BlobLocation>>, 202 206 referenced_blobs: &HashSet<String>, 203 207 ) -> Result<Vec<UnreferencedBlob>, Box<dyn std::error::Error>> { 204 208 let mut unreferenced = Vec::new(); 205 209 206 - for (digest, (org, repo, size)) in all_blobs { 210 + for (digest, locations) in all_blobs { 207 211 if !referenced_blobs.contains(digest) { 208 - unreferenced.push((org.clone(), repo.clone(), digest.clone(), *size)); 212 + // Add all locations of this unreferenced blob 213 + for (org, repo, size) in locations { 214 + unreferenced.push((org.clone(), repo.clone(), digest.clone(), *size)); 215 + } 209 216 } 210 217 } 211 218
+1 -1
tests/gc_operations.rs
··· 56 56 let result: serde_json::Value = resp.json().unwrap(); 57 57 58 58 assert!(result["blobs_scanned"].as_u64().unwrap() >= 2); 59 - assert!(result["blobs_to_delete"].as_u64().unwrap() >= 1); 59 + assert!(result["blobs_unreferenced"].as_u64().unwrap() >= 1); 60 60 61 61 // Verify orphaned blob still exists (dry-run) 62 62 let resp = client